Chapter 2. Jumping In

dbstreams in Ten Minutes

Table of Contents
The Basics
Containerization
Parameters and Placeholders
dbstreams in Ten Minutes

The Basics

Let's get right to the code.

Example 2-1. Basic Example

		dbstream<dbstatus> db(provider, username, password);
		db.open( servername, catalog );	
		if( !db )
			throw db.status();

		query q( "create table T "
			 "	( t int NULL"
			 "	, r real NULL"
			 "	, s varchar(30) NULL"
			 "	)" );
		db << q;
		if( !db )
			throw db.status();

		db.table( "T" );
		db << 4    << 4.4  << "four"  << endl
		   << null << 5.5  << "five"  << endl; 
		   
		typedef struct { char *name; int number; } nvp;
		static const nvp values[] =  
				{ { "four", 4 }
				, { "six",  6 }
				};
				
		q = "select * from T ";
		q << "where s = " << values[0].name 
		  << "  and t = " << values[0].number;
		  
		db << q;

		std::vector<npv> output(10);

		for( int i(0); db && i < output.size(); db++, i++ ) {
			db >> c("t") >> output[i].number
			   >> c("s") >> output[i].name;
		}
	
Obvious, right? Let's look closer.

Elements of a dbstreams program

Construction

dbstream<dbstatus> db(provider, username, password);

The dbstream [1] constructor's first argument is an enumerated type that indicates the kind of server we want to connect to or, more precisely, the native database library to use. It's called provider here because that's dbstreams's terminology: a dbstreams provider is simlilar to what's known as a driver in other contexts.

If the provider requested is not known to the dbstreams implementation, the constructor throws a std::runtime_error exception.

Connect

db.open( servername, catalog );

Just like an std::iostream: an open method. The catalog is optional because not all servers support one. This function connects to the server.

There is a companion close function. It is called automatically with the dbstream goes out of scope, just as you'd expect.

if( !db ) throw db.status();

The state of the stream can always be tested with operator!. if the native library produced an error message (most do), the default behavior is to write the message to standard error. (More detail can be had by writing the status object to standard error, too.)

Insert Data

db.table( "T" );
db << 4    << 4.4  << "four"  << endl
   << null << 5.5  << "five"  << endl; 

Inserting data is as easy as setting the table and using the familiar operator<<. The dbstream constructs the appropriate insert statement, and some providers, such as the one for Sybase®, support faster, non-SQL features.

This example inserts in column order. A manipulator (seen later in reading results) can be used to control the column order.

Assemble a Query

q = "select * from T ";
q << "where s = '" << values[0].name 
  << "'  and t = " << values[0].number; 

The query object is basically a SQL-aware std::stringstream to facilitate building query strings.

Queries can also contain placeholders. The dbstream accepts the parameter descriptions and data. We'll get to that later.

Execute a query

db << q;

To execute a query, insert it into the stream.

Read Results

for( int i(0); db && i < output.size(); db++, i++ ) {
	db >> c("t") >> output[i].number
	   >> c("s") >> output[i].name;
} 

Data can be extracted in column order, but usually it's clearer if the columns are referenced by name. The manipulator c is used to indicate the column name.

Note the use of operator++ to advance to the next row, and the treatment of the dbstream as a boolean to test of end-of-data.

Keep in Mind

From this one example, you know all the basics of how to use a dbstream.

As with std::iostreams, there are very few methods that need to be called.

The SQL is passed verbatim to the provider, and thence to the server. The dbstream doesn't interpret it.

To distinguish between data and commands — setting/changing stream behavior or communicating with th server — this principle holds: Data are inserted and extracted literally, as built-in datatypes or std::string. Non-data are typically sent as dbstreams objects.

Notes

[1]

Perhaps dbstream<dbstatus> should be basic_dbstream<dbstatus>. That would let us define dbstream as a typedef. The downside would be that the programmer would have no better choice (than presently) were he to derive his own class from dbstatus. That is, if you don't like typing "dbstream<dbstatus>" all the time, you're already free now to typedef it to, say, DbStream.