March 24, 2019
Hot Topics:

Accessing Your Database with C++ Is as Easy as DTL

  • September 23, 2005
  • By Victor Volkman
  • Send Email »
  • More Articles »

Associating Columns with Data Members

Go one step further and build a vector containing a user-defined object, a simple struct that you might use when creating a sales report. For a more concrete example, here's a little bit more about the sales_q3 table:

SQL> desc sales_q3;
Name                            Type
------------------------------- --------
VENDOR_ID                       INTEGER
ITEM_NAME                       VARCHAR
PRICE                           FLOAT
QTY_SOLD                        INTEGER
DATE_SOLD                       DATE

It's not hard to imagine a specific struct that will hold the bare essentials of your sales_q3 records:

struct sales_q3 {                // tablename.columnname:
   int vendorID;                 // sales_q3.VENDOR_ID
   string itemName;              // sales_q3.ITEM_NAME
   double price;                 // sales_q3.PRICE
   long qtySold;                 // sales_q3.QTY_SOLD
   TIMESTAMP_STRUCT dateSold;    // sales_q3.DATE_SOLD

DTL defines the mapping between columns and data members through a construct known as the Bind Column Addresses (BCA). The BCA specifically links an ODBC SQL query result field with a physical part of a struct or class. The following code snippet does this by declaring a DefaultBCA called sales_q3 with an () operator:

template<> class dtl::DefaultBCA<sales_q3>
   void operator()(BoundIOs &cols, sales_q3 &rowbuf)
      cols["INT_VALUE"] == rowbuf.vendorID;
      cols["STRING_VALUE"] == rowbuf.itemName;
      cols["DOUBLE_VALUE"] == rowbuf.price;
      cols["EXAMPLE_LONG"] == rowbuf.qtySold;
      cols["EXAMPLE_DATE"] == rowbuf.dateSold;

Now, you can finally read the contents of the sales_q3 table and return a vector of the resulting rows:

vector<sales_q3> ReadData() {
   // Read the data
   vector<sales_q3> results;
   DBView<sales_q3> view("sales_q3");
   DBView<sales_q3>::select_iterator read_it = view.begin();
   for ( ; read_it != view.end(); ++read_it)
   return results;

More DTL Iterators

This example uses the select_iterator to traverse a query result. If you were changing records in a "select for update" scenario, you would use the select_update_iterator (also an output iterator, of course). There are several other output ierators to choose from: insert_iterator, update_iterator, delete_iterator, and sql_iterator.

Unleashing STL Algorithms in DTL

Now, put the native STL algorithms to the test by constructing a TableDiff() function to compare the contents of two tables. In any other database library you've seen, this would require either (1) iterating through both tables yourself and manually inspecting the records or (2) using a series of SQL commands to dump both tables into a temporary table, a SELECT UNIQUE from the temp table, and another temp table to do a SELECT with a "NOT IN" sub-query, etc. With DTL, you can just hand off the dirty work to set_difference (see lines #22 and #28) or set_symmetric_difference (line #32):

 1 // Table difference function.
 2 // Takes two containers and prints out the differences (via set
   // difference) between the containers.
 3 // container 1 = "original" values, container 2 = "new" values
 5 template<class Container> void TableDiff(ostream &o,
                                            const Container &cont1,
                                            const Container &cont2)
 6 {
 7    typedef Container::value_type value_type;
 9    // copy container data into sets as set_symmetric_difference
10    // needs a sorted list to do its work
11    multiset<value_type> set1;
12    multiset<value_type> set2;
14    // Slight workaround for VC++ 6.0 STL library: can only work
      // with pointers, not iterators.
15    // Therefore, we cannot do this at set construction time
16    // as recommended by the standard
17    copy(cont1.begin(), cont1.end(),
         inserter(set1, set1.begin()));
18    copy(cont2.begin(), cont2.end(),
         inserter(set2, set2.begin()));
20    // Show set1 - set2 = deleted / changed items
21    o << "deleted / changed items:" << endl;
22    set_difference(set1.begin(), set1.end(),
                     set2.begin(), set2.end(),
23                   ostream_iterator<value_type>(o, "\n"));
25    // Show set2 - set1 = inserted / changed items
27    o << "inserted / changed items:" << endl;
28    set_difference(set2.begin(), set2.end(),
                     set1.begin(), set1.end(),
29                   ostream_iterator<value_type>(o, "\n"));
30 #else
31    // Show all differences as single set
32    set_symmetric_difference(set1.begin(), set1.end(),
                               set2.begin(), set2.end(),
                               ostream_iterator<value_type>(o, "\n"));
34 }
37 // Show the difference between the rows in two tables
38 void TableDiff()
39 {
40    // Use two DBViews to directly difference the contents of two tables
41    DBView<Example> new_table("sales_q3");
42    DBView<Example> old_table("sales_q3_backup");
43    TableDiff(cout, old_table, new_table);
45    cout << "--- should be same for IndexedDBViews --- " << endl;
47    // now do the same thing for an IndexedDBView
48    IndexedDBView<DBView<Example> > new_idx_table(new_table,
49    IndexedDBView<DBView<Example> > old_idx_table(old_table,
50    TableDiff(cout, old_idx_table, new_idx_table);
51 }

Another construct the code brings into play is an advanced container for holding database tables called IndexedDBView. This container is a specialization of an STL Unique Associative Container. As any database developer will tell you, the most common operations performed on a set of table records are: read the records into a container, search the records by different key fields (in other words,, indexes), and delete, insert, or update records in the container. For these uses, IndexedDBView works admirably.

Specifically, IndexedDBView adds easy creation of indexes into rows and synchronization capabilities that can automatically propagate any changes back to the database. Of course, you pay a price: IndexedDBView incurs more overhead than the simple DBView, and because it works at a higher level, you lose a bit of the fine-grained control that you get with simple iterators.

The DTL Way

DTL has a lot to offer over the traditional procedural approach to databases, which requires you essentially to create all algorithms by hand. DTL also has a lot more under the hood that I haven't even begun to explore here, such as C++ reflection, access to metadata, parameterized queries, and dynamic SQL. If you've found yourself cloning a lot of database code lately, give DTL a spin.

About the Author

Victor Volkman has been writing for C/C++ Users Journal and other programming journals since the late 1980s. He is a graduate of Michigan Tech and a faculty advisor board member for Washtenaw Community College CIS department. Volkman is the editor of numerous books, including C/C++ Treasure Chest and is the owner of Loving Healing Press. He can help you in your quest for open source tools and libraries, just drop an e-mail to sysop@HAL9K.com.

Page 2 of 2

Comment and Contribute


(Maximum characters: 1200). You have characters left.



Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date