cs581 - Database Management Systems - 2026 fall

Course webpage for cs581 - 2026 fall taught by Boris Glavic

Columnar Option

Overall goal

If you choose this option then you will implement a small columnar query execution engine that reads data from CSV files.

Assignment 1 - Chunk-based scan of CSV files

In the first assignment, you will build support for reading a table from a CSV file into memory as columar data in horizontal chunks. You need to design the data structures for holding a table chunk which stores data of all rows in a horizontal chunk of the input table of a fixed size, say 4096 rows. The table chunk just holds metadata and references to column chunks which, for fixed size data types like 4-byte integers, should just be an array of elements of this type. For variable length data like strings, you can use the two array (offset + an array concatenating all strings) as a storage format or implement "German" strings where small strings are stored inline and larger ones in a separate array. For convenience, you should also implement a function that can print chunks in a human-readable format.

Assignment 2 - Scans with filters and projections

In the second assignment, we will implement or first two query engine operators and supporting vectorized versions of scalar operations.

  • Projection: A basic projection is trivial to implement in a columnar database. Just remove references to column chunks that are projected out. For scalar expressions, e.g., A+B we need to implement vectorized versions of these operators that take column chunks as inputs and return column chunks.
  • Selection: selection filters rows based on a condition. For our columnar engine, we want to evaluate the individual operations in a selection condition on full table chunks. For that we should implement support for basic primitives like logical operators and comparisons on column chunks. Comparison operators should take two column chunks or constants and return a bitvector encoding for each row whether the condition evaluates to true or not. Logical operators can then be implemented as bitwise operations: (and) is bitwise and, (or) is bitwise or, (not) is bitwise not. Once we have evaluated a full selection condition we have to use the resulting bitvector to filter the actual columns by removing all elements at positions where the bitvector is 0.

Assignment 3 - Hash joins

In this assignment, you will implement a hash join operators (it is ok to only support the version that assumes that the hash table fits into memory). Recall that hash joins work for equality only (other conditions can be applied in a postfilter). You build a hash table on the smaller of the two inputs where the key is the join attributes, e.g., for this query:

1
2
SELECT *
FROM R JOIN S ON (R.A = S.B);

assuming that R is larger, we would build a hash table on S with key B. The values associated with a key c are all the rows from the table (S in our example) that have a join key equals to c.

Then you have to scan through the other input relation (R in our example) and for each row probe the hash table to find matching rows from the other table (S in our example) and output them.

Assignment 4 - Aggregation and zonemaps

In this last assignment, you will implement support for group-by aggregation and zonemap indexes.

Group-by aggregation

We will implement aggregation by hashing. That is you will implement a hash table that stores aggregation state for groups. When scanning through the input relation, for each row either of two things apply:

  • we already have an aggregation state for the row's group: in this case will update the state based on the incoming value (e.g., add to the partial sum if we are computing sum(a))
  • we have not yet seen the row's group: initialize a new aggregation state for the group and insert it into the hash table

Once we have scanned through the whole input, you have to scan through the hash table, finalize each aggregation state, and output the rows.

Zone-map index

A zonemap or small materialized aggregated approximate index splits a table into horizontal fragments (e.g., every 10 pages) based on page boundaries and materializes aggregate statistics per column such as the minimum and maximum values for values in this column in the fragment. These statistics can then be used to prune fragments when evaluating a filter condition on the data. For example, if a fragment's values of attribute A are within [100,200] and the filter condition is A > 300 then the fragment can be skipped as all rows in the fragment are guaranteed to violate the filter condition (will all be filtered). You should extend your table chunk data structure to store zonemaps and to populate them when reading data from a CSV file. Furthermore, you need to extend the selection operator to make use of these zonemaps.

Contest

I will provide a workload of queries expressible with the operators you have implemented and the fastest group wins.