Assignment 1: Tables and Rows

Over a series of programming assignments, you will build a C++ implementation of a rudimentary database system. This database system will support only a single type for values, std::string. You will start in this assignment by implementing tables and rows.

You are not starting from scratch. This archive contains:

Download the archive, and use it as the starting point for your work.

What's in the archive

Your implementation will comprise the classes Database, Table, ColumnNames, and Row. A Database is a set of Tables. ColumnNames describes the columns of each Table. Each Row of a Table contains a value for each of the columns.

Database

The implementation of this class is provided. It creates new tables and keeps track of them. You should not need to modify any Database code.

ColumnNames

This class is also provided. It records the names of a Table's columns, and their order. It can tell you the position of a column within the table, something that will be useful in your implementation. (E.g. if you create a Table with columns (a, b), then a is at position 0, and b is at position 1.)

Mostly empty implementations of Table and Row

The Table class is partially implemented. It has data members for storing metadata: the table's name and the column's names. You need to extend the representation to provide a place to store Rows. You will need to modify Table::add to actually store the Rows. Note that the current implementation of Table::add says IMPLEMENT_ME(). You will see these calls wherever you need to provide code.

You may find that additional member functions are required. You can add whatever you feel is necessary, but you may not remove any public data members or member functions.

Similarly, Row is partially implemented. You will again need to provide a representation, and operate on it.

Tests

The tests are in test.cpp. main.cpp has the main() function which simply invokes the tests.

Here is a typical positive test:
static void has_row_ok() { Table* t = Database::new_table("t", ColumnNames{"a", "b"}); assert(!has(t, {"1", "2"})); assert(add(t, {"1", "2"})); assert(add(t, {"3", "4"})); assert(add(t, {"5", "6"})); assert(add(t, {"7", "8"})); assert(has(t, {"1", "2"})); assert(has(t, {"3", "4"})); assert(has(t, {"5", "6"})); assert(has(t, {"7", "8"})); assert(!has(t, {"8", "7"})); assert(!has(t, {"5", "5"})); }

This tests the Table::has member function. It creates a table t with columns a and b. Rows are added to the table (The test relies on helper functions, has and add, which are located in test.cpp. These functions call Table::has and Table::add, respectively.) The assert function evaluates the expression, and records a test failure if the expression evaluates to false.

The first assertion tests that t, which is empty, does not contain the row (1, 2). Then four rows are added, various rows are checked for membership. While the point of this test is to check that Table::has is working correctly, the boolean output from row addition is checked also. More thorough testing of Table::add is done in other tests in test.cpp.

Here is a typical negative test:
static void create_table_no_columns() { try { Table* t = Database::new_table("t", ColumnNames()); fail(); } catch (TableException& e) { // expected } }

This code tries to create a table with no columns. Correct behavior is to throw a TableException. If an exception is not thrown, then the fail() call is reached, which causes the test to fail.

Utilities

These files contain various useful but uninteresting things that you shouldn't have to touch.

Makefile

A Makefile is provided. If you add .cpp or .h source, (which is unexpected), be sure to modify the Makefile appropriately.

Building the code

Unpack the archive, cd into the a1 directory, and then just run make: jao@mintyzack ~/115/assignments/a1/a1 $ make g++ -std=c++11 -Wall -Wno-unused-function -c ColumnNames.cpp -o ColumnNames.o g++ -std=c++11 -Wall -Wno-unused-function -c Database.cpp -o Database.o g++ -std=c++11 -Wall -Wno-unused-function -c Row.cpp -o Row.o g++ -std=c++11 -Wall -Wno-unused-function -c RowCompare.cpp -o RowCompare.o g++ -std=c++11 -Wall -Wno-unused-function -c Table.cpp -o Table.o g++ -std=c++11 -Wall -Wno-unused-function -c main.cpp -o main.o g++ -std=c++11 -Wall -Wno-unused-function -c test.cpp -o test.o g++ -std=c++11 -Wall -Wno-unused-function -c unittest.cpp -o unittest.o g++ -std=c++11 -Wall -Wno-unused-function -c util.cpp -o util.o g++ -std=c++11 -Wall -Wno-unused-function ColumnNames.o Database.o Row.o RowCompare.o Table.o main.o test.o unittest.o util.o -o a1

Running the code

If you run the code without modification, you should see this output:
jao@mintyzack ~/115/assignments/a1/a1 $ ./a1 :-( 1/14 -- create_table_no_columns: FAILED -- Not yet implemented! :-( 2/14 -- create_table_duplicate_columns: FAILED -- Not yet implemented! :-( 3/14 -- create_table_duplicate_names: FAILED -- Not yet implemented! :-( 4/14 -- create_table_ok: FAILED -- Not yet implemented! :-( 5/14 -- add_row_with_too_few_columns: FAILED -- Not yet implemented! :-( 6/14 -- add_row_with_too_many_columns: FAILED -- Not yet implemented! :-( 7/14 -- add_row_ok: FAILED -- Not yet implemented! :-( 8/14 -- add_row_suppress_duplicates: FAILED -- Not yet implemented! :-( 9/14 -- remove_from_empty_table: FAILED -- Not yet implemented! :-( 10/14 -- remove_row_missing: FAILED -- Not yet implemented! :-( 11/14 -- remove_row_present: FAILED -- Not yet implemented! :-( 12/14 -- remove_incompatible_row: FAILED -- Not yet implemented! :-( 13/14 -- has_bad_row: FAILED -- Not yet implemented! :-( 14/14 -- has_row_ok: FAILED -- Not yet implemented! SUMMARY: 14 tests 0 passed 14 failed

When you successfully implement Row and Table, you should see this:

jao@mintyzack ~/115/assignments/a1/a1 $ ./a1 :-) 1/14 -- create_table_no_columns: ok :-) 2/14 -- create_table_duplicate_columns: ok :-) 3/14 -- create_table_duplicate_names: ok :-) 4/14 -- create_table_ok: ok :-) 5/14 -- add_row_with_too_few_columns: ok :-) 6/14 -- add_row_with_too_many_columns: ok :-) 7/14 -- add_row_ok: ok :-) 8/14 -- add_row_suppress_duplicates: ok :-) 9/14 -- remove_from_empty_table: ok :-) 10/14 -- remove_row_missing: ok :-) 11/14 -- remove_row_present: ok :-) 12/14 -- remove_incompatible_row: ok :-) 13/14 -- has_bad_row: ok :-) 14/14 -- has_row_ok: ok SUMMARY: 14 tests 14 passed 0 failed

Guidelines

You are expected to modify this code significantly. A few general comments on how to proceed:

Valgrind

Running valgrind should be part of your development workflow. Run it frequently to make sure that you don't introduce any memory problems as your develop your code. If you start with a clean run of valgrind, then you know that problems turned up by it later must be due to code changes since the previous, clean run.

E.g., here is a valgrind run from my implementation of this assignment: jao@mintyzack ~/115/assignments/a1/a1.master $ valgrind ./a1 ==108065== Memcheck, a memory error detector ==108065== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. ==108065== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info ==108065== Command: ./a1 ==108065== :-) 1/14 -- create_table_no_columns: ok :-) 2/14 -- create_table_duplicate_columns: ok :-) 3/14 -- create_table_duplicate_names: ok :-) 4/14 -- create_table_ok: ok :-) 5/14 -- add_row_with_too_few_columns: ok :-) 6/14 -- add_row_with_too_many_columns: ok :-) 7/14 -- add_row_ok: ok :-) 8/14 -- add_row_suppress_duplicates: ok :-) 9/14 -- remove_from_empty_table: ok :-) 10/14 -- remove_row_missing: ok :-) 11/14 -- remove_row_present: ok :-) 12/14 -- remove_incompatible_row: ok :-) 13/14 -- has_bad_row: ok :-) 14/14 -- has_row_ok: ok SUMMARY: 14 tests 14 passed 0 failed ==108065== ==108065== HEAP SUMMARY: ==108065== in use at exit: 72,704 bytes in 1 blocks ==108065== total heap usage: 309 allocs, 308 frees, 90,197 bytes allocated ==108065== ==108065== LEAK SUMMARY: ==108065== definitely lost: 0 bytes in 0 blocks ==108065== indirectly lost: 0 bytes in 0 blocks ==108065== possibly lost: 0 bytes in 0 blocks ==108065== still reachable: 72,704 bytes in 1 blocks ==108065== suppressed: 0 bytes in 0 blocks ==108065== Rerun with --leak-check=full to see details of leaked memory ==108065== ==108065== For counts of detected and suppressed errors, rerun with: -v ==108065== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

You want to see no corruption (of course), and 0 bytes lost in the leak summary. The still reachable part is fine; that appears to be related to memory used by valgrind itself.