Provider Classes

provider Class

All providers are derived from one abstract base class: provider. This provider class defines a set of pure virtual functions that constitute the provider interface to the dbstream class. The stream requires of the provider some typedefs and some functions.

 class provider , public class provider_data {
  protected urhandle _handle;
  protected mutable STATUS state;

  public virtual ~provider();
  protected urhandle _handle;
  protected mutable STATUS state;
  public virtual ~provider();

  public virtual const STATUS& open(const std::string& server, const std::string& dbname);
  public virtual const STATUS& open(const std::string& tablename);
  public virtual void close(= 0;);
  public virtual const query& insert(int icol, metadata::nullitude datum);
  public virtual const query& insert(int icol, const char * data, streamsize n);
  public virtual const query& insert(int icol, const std::string& datum);
  public virtual const query& insert(int icol, const int& datum);
  public virtual const query& insert(int icol, const float& datum);
  public virtual const query& insert(int icol, const double& datum);
  public virtual const STATUS& send(= 0;);
  public virtual const STATUS& execute(const dbstreams::query& sql);
  public virtual const STATUS& execute(const parameter_list_t& parameters);
  public std::stringstream msg;
  public << ": " << __func__ << " not implemented by provider";
  public throw std::logic_error();
  public const } virtual bool extract(int icol, std::string& datum, size_t& len);
  public virtual const bool extract(int icol, int& datum, size_t& len);
  public virtual const bool extract(int icol, double& datum, size_t& len);
  public virtual const bool extract(int icol, const char*& datum, size_t& len);
  public virtual const bool extract(int icol, const void*& datum, size_t& len);
  public virtual const const cell& extract(int icol);
  public virtual const const metadata& meta(int icol);
  public virtual int ncolumns(const = 0;);
  public virtual const STATUS& next_row(= 0;);
  public virtual const std::string& SQL(const = 0;);
  public virtual const std::string& query_separator(const = 0;);
  public const urhandle& handle();
  public const STATUS& status();
  public void ignore(int msgno);
  public const const metadata& meta(const std::string& colname);
  public const const cell& extract(const std::string& colname);
  protected const const cell& lookup(const std::string& colname);
}

dbstatus Class

Purpose

dbstatus holds all the status information produced by the native library. It is populated by the provider and read by the stream (and by the application). Because no driver produces information for every field in dbstatus, at any one time some are inevitably not used. (It's up to the provider documentation to tell the application programmer what to expect.)

In addition to native status information, dbstatus holds the stream's state word. Precisely analogous to std::ios_base::iostate, dbstreams::dbstatus::state reduces the state of the connection to good, bad, fail, and eof.  

The main service provided by dbstreams is, if you will, streamification: the representation of the database connection as a stream. Being the layer closest to the connection, it is the provider's job to set the stream status.

State and Error Philosophy

The application programmer is better off getting the native library's errors verbatim. He knows what those mean. Attempts to classify them or be otherwise helpful will only tend to obfuscate them.

Most errors seen by the application will therefore come from the native library. Very few are caught by the dbstream or provider layer. The primary exception is stream state management.

Managing a dbstream State

The provider supplies status information to the stream via a dbstatus object, or something that has the same interface as dbstatus has. To make that easier to do, the provider has a template argument for the status class. The application programmer may extend dbstatus without changing dbstream or any provider.

The dbstream object is relatively lightweight, in that it doesn't track the state of the connection, doesn't require that functions be called in a certain order. It just translates the stream operations into calls to the provider. [1]

For this reason, the provider may call on the native library to do something stupid, such as fetching rows before opening a connection. If that happens, the least the provider should do, of course, is return the native error. But if the provider is clever enough to detect such logical sequence errors (perhaps with the aid of the native library), it may throw an error. That will help the application programmer find his mistake.

dbstatus

 class dbstatus : public std::runtime_error {
  public enum iostate;
  public typedef std::deque<int> ignore_list;

  private iostate resetbits();
  private iostate state;
  private ignore_list ignoring;
  private int nmissing_parameters;
  public int srcline;
  public const char *srcfile;
  public const char *driver_function;
  public int msgno;
  public int msgstate;
  public int severity;
  public int os_error;
  public std::string servername;
  public std::string procedure_name;
  public int line_number;
  public std::string msg;
  public unsigned long nrows;
  public  dbstatus(const std::string& msg = std::string);
  public  dbstatus(const char *srcfile, int srcline, const std::string& msg);

  public ~dbstatus();
  public void fetched(iostate state, int nrows = 1, bool fail = false);
  public bool good();
  public bool bad();
  public bool fail();
  public bool eof();
  public iostate rdstate();
  public iostate setstate(iostate state);
  public iostate clear(iostate state = goodbit);
  public dbstatus& operator=(int msgno);
  public dbstatus& operator=(const std::string&);
  public operator void*();
  public bool operator !();
  public const bool operator==(iostate state);
  public const bool operator!=(iostate state);
  public const bool operator==();
  public const bool operator!=();
  public const std::ostream& notify(std::ostream& os = std::cerr);
  public void ignore(int msgno);
  public const ignore_list& ignore(const ignore_list&);
  public const ignore_list& ignore();
  public bool quit();
  public const char* what(const throw);
  public const dbstatus& make_ready(const dbstatus& reset);
  public int missing_parameters();
  public int missing_parameters(int nmissing);
  public dbstatus& assemble_error(HANDLE db, const char *srcfile, int srcline);
}

Return Errors

Every native database C library has some way to return an error number and an associated message. The provider puts these in its STATUS object, which both can be queried by the status() method, and is returned (as a const reference) by many functions.

When an error occurs, the provider calls STATUS::notify(). The application can override that method (by deriving from dbstatus) and use it to report errors to the user.

Manage State

It is the provider's responsibility to set the iostate of the stream.

The stream's overall state is communicated to the application via the bitmask in dbstatus::state. To the application, the only important question is if the stream is usable and whether results are pending. The application learns the answer by examining the stream's state:

Stream States

goodbit

OK, no data pending

eofbit

last row of current results read, more pending

failbit

current operation failed, no data pending

badbit

stream is unusable

The end-of-file marker handling differs somewhat from the std::iostream model and bears explication.

Recall that when reading a file with std::istream, ios::eofbit is set when the end of the file has been read. Because the operation failed (almost always), failbit is set as well. If tested if() or while(), the ifstream returns false. A query may contain more than one SELECT statement. Some kind of result-separator is required to indicate to the application that the stream has reached the end of one result, and whether or not more are pending. The eofbit status serves this purpose.

On reaching the end of one resultset, the provider sets eofbit. It then determines if more data are pending (from a subsequent SELECT statement). If there are pending data, *only* eofbit is set.

The combination eofbit + failbit indicates the last row-reading operation failed. It is how the application knows it has reached the end of the data sent by the server.

To be precise, there may be no data pending: the next resultset may be empty. Consider this query: select 1 as 'one' select 'a' as 'A' where 0=1 The state sequence will be:

StateMeaning
goodbitgot row 1 from resultset 1
eofbitend of resultset 1
eofbit | failbitend of resultset 2

Queries that return no results (no columns or an empty set) leave the stream in goodbit state.

If the query fails, failbit is set and no attempt is made to fetch rows. The application sees this as:

if( !db && !db.eof() )

or

if( db.error() )

Getting state right is one of the challenges of writing a provider. The consolation is that the challenge, now answered by the provider, is no longer left to the application.

provider_data Class

The provider_data class, strictly speaking, is optional. It's handy to derive a provider from it privately, because its constructor initializes the data and most providers need provider_data's members.

 class provider_data {
  public typedef dbstreams::parameter_list_t parameter_list_t;
  protected std::ostream *plog;
  protected query sql;
  protected parameter_list_t parameters;
  protected typedef std::vector<cell> row_type;
  protected mutable row_type row;
  protected bool eoq;

  public  provider_data(: plog);
  public std::ostream * log();
  public std::ostream * log(std::ostream *pos);
  public  std::swap();
  public return pos;
  public const const metadata& meta(int icol);
  public return row[c];
  protected template <typename T> const dbstreams::query& insert_quoted(int icol, T datum);
  protected template <typename T> const dbstreams::query& insert_unquoted(int icol, T datum);
}
The log method sets a log stream for debugging purposes.  It returns the prior log stream in case the caller wants to restore it later. It's up to the provider to decide what to put in the log.

Notes

[1]

This is not optimal. The sooner and higher an error is caught, the better. Certainly it's better, at the very least, for dbstream to insist on rational use of the stream.