Computer Science 236

Database Lab Session


During this lab session you will write some of the code needed for Project 3. You will need to write additional code after the lab session to fully complete Project 3. (Note that the files Scheme.h, Tuple.h, Relation.h, and main.cpp mentioned in the steps below will be created by you from scratch. There is no starter code provided for this lab.)


Part 1: Scheme and Tuple classes


  1. Write a Scheme class (Scheme.h).
    A Scheme holds the names of the columns of a Relation. A Scheme is initialized by passing a 'vector' of names to the constructor. You can write your Scheme class using either delegation or inheritance.

    ** Delegation **

    With delegation a Scheme contains a 'vector' of names and must define functions such as size() and at() that it will delegate to the vector class.

    class Scheme {
    
    private:
    
      vector<string> names;
    
    public:
    
      Scheme(vector<string> names) : names(names) { }
    
      unsigned size() const {
        return names.size();
      }
    
      const string& at(int index) const {
        return names.at(index);
      }
    
      // TODO: add more delegation functions as needed
    
    };
    

    ** Inheritance **

    With inheritance a Scheme inherits a 'vector' of names and inherits functions such as size() and at() from the vector class with no added code.

    class Scheme : public vector<string> {
    
     public:
    
      Scheme(vector<string> names) : vector<string>(names) { }
    
    };
    
  2. Write a Tuple class (Tuple.h).
    A Tuple holds the values stored in one row of a Relation. A Tuple is initialized by passing a 'vector' of values to the constructor. You can write your Tuple class using either delegation or inheritance.

    ** Delegation **

    With delegation a Tuple must define functions such as size() and at() that it will delegate to the vector class.

    class Tuple {
    
    private:
    
      vector<string> values;
    
    public:
    
      Tuple(vector<string> values) : values(values) { }
    
      unsigned size() const {
        return values.size();
      }
    
      const string& at(int index) const {
        return values.at(index);
      }
    
      bool operator<(const Tuple t) const {
        return values < t.values;
      }
    
      // TODO: add more delegation functions as needed
    
    };
    

    ** Inheritance **

    With inheritance a Tuple inherits functions such as size() and at() from the vector class with no added code.

    class Tuple : public vector<string> {
    
     public:
    
      Tuple(vector<string> values) : vector<string>(values) { }
    
    };
    

    The Tuple class needs an 'operator<()' function to allow Tuple objects to be stored in a set. The 'set' data type compares it's members using the less-than operator to organize them into a binary search tree. With delegation the 'operator<()' function must be defined in the Tuple class. With inheritance the 'operator<()' function is inherited with no added code.

  3. Write a 'toString' function for the Tuple class (Tuple.h).
    This 'toString' function is a bit wonky because it combines information from both a Scheme and a Tuple to produce it's output. This happens because Project 3 prints Schemes and Tuples together as "name=value" pairs.

    For example, given a Scheme with names: ID, Name, Major and a Tuple with values: '42', 'Ann', 'CS' the 'toString' function should output: ID='42', Name='Ann', Major='CS'

    The 'toString' function could be located in either the Scheme class or the Tuple class. The approach shown here puts it in the Tuple class and passes the needed Scheme object as a parameter.

    The 'toString' function shown below is not complete. You need to add code to loop over the names and values in the Scheme and Tuple and print the "name=value" pairs.

      string toString(const Scheme& scheme) const {
        const Tuple& tuple = *this;
        stringstream out;
        // fix the code to print "name=value" pairs
        out << scheme.size();
        out << scheme.at(0);
        out << tuple.size();
        out << tuple.at(0);
        return out.str();
      }
    
  4. Test the 'toString' function (main.cpp)

    (In the code below the 'for' loop iterates over the 'values' array and sets 'value' to each vector in the array. The 'auto&' syntax asks the compiler to automatically determine the type of the 'value' variable.)

    int main() {
    
      vector<string> names = { "ID", "Name", "Major" };
    
      Scheme scheme(names);
    
      vector<string> values[] = {
        {"'42'", "'Ann'", "'CS'"},
        {"'32'", "'Bob'", "'CS'"},
        {"'64'", "'Ned'", "'EE'"},
        {"'16'", "'Jim'", "'EE'"},
      };
    
      for (auto& value : values) {
        Tuple tuple(value);
        cout << tuple.toString(scheme) << endl;
      }
    
    }
    

    Compile and test.
    The output should look something like this:

    ID='42', Name='Ann', Major='CS'
    ID='32', Name='Bob', Major='CS'
    ID='64', Name='Ned', Major='EE'
    ID='16', Name='Jim', Major='EE'
    
  5. Take a screenshot showing your terminal and the resulting output. (You can also take a screenshot of an IDE showing similar results.)


Part 2: Relation class


  1. Write a Relation class (Relation.h).
    A Relation object represents a table in a database. A Relation has a name, a Scheme, and a set of Tuples. The name is used to uniquely identify the Relation. The Scheme gives the names of the columns of the Relation. The set of Tuples gives the values of the rows of the Relation.

    The Scheme and Tuples of a Relation need to be the same size. For example, consider the following Scheme and Tuple.

    Scheme: ID, Name, Major
    Tuple: '42', 'Ann', 'CS'
    

    The Scheme determines the number of columns in the Relation. (In this case the Relation has three columns.) Each Tuple in this Relation needs to have three values, one for each of the columns.

    Schemes and Tuples are related to each other by position. The name at a given position in the Scheme corresponds to the value at that same position in the Tuple. In the example above, 'Major' is at index 2 in the Scheme. The value for 'Major' in the Tuple is 'CS' because it is at index 2 in the Tuple.

    class Relation {
    
     private:
    
      string name;
      Scheme scheme;
      set<Tuple> tuples;
    
     public:
    
      Relation(const string& name, const Scheme& scheme)
        : name(name), scheme(scheme) { }
    
      void addTuple(const Tuple& tuple) {
        tuples.insert(tuple);
      }
    
    };
    
  2. Write a 'toString' function for the Relation class (Relation.h).
    The 'toString' function prints each Tuple in the Relation on a separate line.

    The 'toString' function shown below is not complete. You need to add code to loop over the Relation and print each Tuple on a separate line.

      string toString() const {
        stringstream out;
        // add code to print the Tuples, one per line
        return out.str();
      }
    
  3. Test the 'toString' function (main.cpp)

    int main() {
    
      vector<string> names = { "ID", "Name", "Major" };
    
      Scheme scheme(names);
    
      Relation relation("student", scheme);
    
      vector<string> values[] = {
        {"'42'", "'Ann'", "'CS'"},
        {"'32'", "'Bob'", "'CS'"},
        {"'64'", "'Ned'", "'EE'"},
        {"'16'", "'Jim'", "'EE'"},
      };
    
      for (auto& value : values) {
        Tuple tuple(value);
        cout << tuple.toString(scheme) << endl;
        relation.addTuple(tuple);
      }
    
      cout << "relation:" << endl;
      cout << relation.toString();
    
    }
    

    Compile and test.
    The output should look something like this:

    ID='42', Name='Ann', Major='CS'
    ID='32', Name='Bob', Major='CS'
    ID='64', Name='Ned', Major='EE'
    ID='16', Name='Jim', Major='EE'
    relation:
    ID='16', Name='Jim', Major='EE'
    ID='32', Name='Bob', Major='CS'
    ID='42', Name='Ann', Major='CS'
    ID='64', Name='Ned', Major='EE'
    

    Why is the order of the Tuples different when the Relation is printed?

  4. Take a screenshot showing your terminal and the resulting output. (You can also take a screenshot of an IDE showing similar results.)


Part 3: select function


  1. Write a 'select' function for the Relation class (Relation.h).
    The 'select' function returns a new Relation that contains a subset of Tuples from an existing Relation that meet a select condition. In this case the select condition is that a given position in the Tuple must have a given value.

    The 'select' function shown below is not complete. You need to add code to loop over the Relation and add tuples to the result if they meet the condition.

      Relation select(int index, const string& value) const {
        Relation result(name, scheme);
        // add tuples to the result if they meet the condition
        return result;
      }
    

    The following psuedo-code describes the condition testing loop.

      for each tuple in the relation
        if the value at the given index equals the given value
          add the tuple to the result
    
  2. Test the 'select' function (main.cpp)

    int main() {
    
      vector<string> names = { "ID", "Name", "Major" };
    
      Scheme scheme(names);
    
      Relation relation("student", scheme);
    
      vector<string> values[] = {
        {"'42'", "'Ann'", "'CS'"},
        {"'32'", "'Bob'", "'CS'"},
        {"'64'", "'Ned'", "'EE'"},
        {"'16'", "'Jim'", "'EE'"},
      };
    
      for (auto& value : values) {
        Tuple tuple(value);
        cout << tuple.toString(scheme) << endl;
        relation.addTuple(tuple);
      }
    
      cout << "relation:" << endl;
      cout << relation.toString();
    
      Relation result = relation.select(2, "'CS'");
    
      cout << "select Major='CS' result:" << endl;
      cout << result.toString();
    
    }
    

    Compile and test.
    The output should look something like this:

    ID='42', Name='Ann', Major='CS'
    ID='32', Name='Bob', Major='CS'
    ID='64', Name='Ned', Major='EE'
    ID='16', Name='Jim', Major='EE'
    relation:
    ID='16', Name='Jim', Major='EE'
    ID='32', Name='Bob', Major='CS'
    ID='42', Name='Ann', Major='CS'
    ID='64', Name='Ned', Major='EE'
    select Major='CS' result:
    ID='32', Name='Bob', Major='CS'
    ID='42', Name='Ann', Major='CS'
    

    Note that the result of the select should only include tuples where the 'Major' is 'CS'.

  3. Take a screenshot showing your terminal and the resulting output. (You can also take a screenshot of an IDE showing similar results.)

  4. Submit your screenshots and a zip file containing the code you wrote during this session to Learning Suite.