mirror of
https://github.com/pocoproject/poco.git
synced 2025-11-02 14:03:41 +01:00
removing old trunk files
This commit is contained in:
@@ -1,429 +0,0 @@
|
||||
POCO Data Connectors Developer Guide
|
||||
Data
|
||||
|
||||
!!!Overview
|
||||
Developing one's own <*Data Connector*> implementation is rather straight-forward.
|
||||
Just implement the following interfaces:
|
||||
* Poco::Data::AbstractBinder
|
||||
* Poco::Data::AbstractExtractor
|
||||
* Poco::Data::StatementImpl
|
||||
* Poco::Data::SessionImpl
|
||||
* Poco::Data::Connector
|
||||
* optional: Poco::Data::AbstractPreparation
|
||||
|
||||
It is recommended to implement the classes from top to down (ie. start with Binder and Extractor) and to use a
|
||||
namespace that has <[ Poco::Data ]> as parent, e.g.<[ Poco::Data::SQLite ]>.
|
||||
|
||||
!!!AbstractBinder
|
||||
An <[AbstractBinder]> is a class that maps values to placeholders. It is also responsible to bind primitive C++ data types to database
|
||||
data types. The constructor of the subclass should receive everything needed to bind variables to
|
||||
placeholders by position. An example taken from the SQLite implementation would be:
|
||||
|
||||
Binder::Binder(sqlite3_stmt* pStmt):
|
||||
_pStmt(pStmt)
|
||||
{
|
||||
}
|
||||
|
||||
void Binder::bind(std::size_t pos, const Poco::Int32& val)
|
||||
{
|
||||
int rc = sqlite3_bind_int(_pStmt, (int)pos, val);
|
||||
checkReturn(rc);
|
||||
}
|
||||
|
||||
void Binder::bind(std::size_t pos, const Poco::Int16& val)
|
||||
{
|
||||
Poco::Int32 tmp = val;
|
||||
bind(pos, tmp);
|
||||
}
|
||||
----
|
||||
SQLite only needs an <*sqlite3_stmt*> as internal state, Int32 is bound via <*sqlite3_bind_int*> and Int16 values are mapped to Int32 values.
|
||||
|
||||
!!Complete Interface
|
||||
All methods are public.
|
||||
|
||||
AbstractBinder();
|
||||
/// Creates the AbstractBinder.
|
||||
|
||||
virtual ~AbstractBinder();
|
||||
/// Destroys the AbstractBinder.
|
||||
|
||||
virtual void bind(std::size_t pos, const Poco::Int8 &val) = 0;
|
||||
/// Binds an Int8.
|
||||
|
||||
virtual void bind(std::size_t pos, const Poco::UInt8 &val) = 0;
|
||||
/// Binds an UInt8.
|
||||
|
||||
virtual void bind(std::size_t pos, const Poco::Int16 &val) = 0;
|
||||
/// Binds an Int16.
|
||||
|
||||
virtual void bind(std::size_t pos, const Poco::UInt16 &val) = 0;
|
||||
/// Binds an UInt16.
|
||||
|
||||
virtual void bind(std::size_t pos, const Poco::Int32 &val) = 0;
|
||||
/// Binds an Int32.
|
||||
|
||||
virtual void bind(std::size_t pos, const Poco::UInt32 &val) = 0;
|
||||
/// Binds an UInt32.
|
||||
|
||||
virtual void bind(std::size_t pos, const Poco::Int64 &val) = 0;
|
||||
/// Binds an Int64.
|
||||
|
||||
virtual void bind(std::size_t pos, const Poco::UInt64 &val) = 0;
|
||||
/// Binds an UInt64.
|
||||
|
||||
virtual void bind(std::size_t pos, const bool &val) = 0;
|
||||
/// Binds a boolean.
|
||||
|
||||
virtual void bind(std::size_t pos, const float &val) = 0;
|
||||
/// Binds a float.
|
||||
|
||||
virtual void bind(std::size_t pos, const double &val) = 0;
|
||||
/// Binds a double.
|
||||
|
||||
virtual void bind(std::size_t pos, const char &val) = 0;
|
||||
/// Binds a single character.
|
||||
|
||||
virtual void bind(std::size_t pos, const char* const &pVal) = 0;
|
||||
/// Binds a const char ptr.
|
||||
|
||||
virtual void bind(std::size_t pos, const std::string& val) = 0;
|
||||
/// Binds a string.
|
||||
|
||||
virtual void bind(std::size_t pos, const BLOB& val) = 0;
|
||||
/// Binds a BLOB.
|
||||
|
||||
virtual void reset() = 0;
|
||||
/// Resets the internal state, called before a rebind
|
||||
----
|
||||
|
||||
!!!AbstractExtractor
|
||||
An <[AbstractExtractor]> takes a result row and extracts from a given position one single value. It performs the reverse operation to the <[AbstractBinder]>,
|
||||
ie. it maps database types to primitive C++ types. An <[AbstractExtractor]> also has to handle null values. If it detects a null value, it is not allowed to modify
|
||||
the incoming value but will simply return false. An example taken from the SQLite implementation:
|
||||
|
||||
Extractor::Extractor(sqlite3_stmt* pStmt):
|
||||
_pStmt(pStmt)
|
||||
{
|
||||
}
|
||||
|
||||
bool Extractor::extract(std::size_t pos, Poco::Int32& val)
|
||||
{
|
||||
if (isNull(pos<[
|
||||
return false;
|
||||
val = sqlite3_column_int(_pStmt, (int)pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Extractor::extract(std::size_t pos, Poco::Int16& val)
|
||||
{
|
||||
if (isNull(pos<[
|
||||
return false;
|
||||
val = sqlite3_column_int(_pStmt, (int)pos);
|
||||
return true;
|
||||
}
|
||||
----
|
||||
|
||||
!!Complete Interface
|
||||
All methods are public.
|
||||
|
||||
AbstractExtractor();
|
||||
/// Creates the AbstractExtractor.
|
||||
|
||||
virtual ~AbstractExtractor();
|
||||
/// Destroys the AbstractExtractor.
|
||||
|
||||
virtual bool extract(std::size_t pos, Poco::Int8& val) = 0;
|
||||
/// Extracts an Int8. Returns false if null was received.
|
||||
|
||||
virtual bool extract(std::size_t pos, Poco::UInt8& val) = 0;
|
||||
/// Extracts an UInt8. Returns false if null was received.
|
||||
|
||||
virtual bool extract(std::size_t pos, Poco::Int16& val) = 0;
|
||||
/// Extracts an Int16. Returns false if null was received.
|
||||
|
||||
virtual bool extract(std::size_t pos, Poco::UInt16& val) = 0;
|
||||
/// Extracts an UInt16. Returns false if null was received.
|
||||
|
||||
virtual bool extract(std::size_t pos, Poco::Int32& val) = 0;
|
||||
/// Extracts an Int32. Returns false if null was received.
|
||||
|
||||
virtual bool extract(std::size_t pos, Poco::UInt32& val) = 0;
|
||||
/// Extracts an UInt32. Returns false if null was received.
|
||||
|
||||
virtual bool extract(std::size_t pos, Poco::Int64& val) = 0;
|
||||
/// Extracts an Int64. Returns false if null was received.
|
||||
|
||||
virtual bool extract(std::size_t pos, Poco::UInt64& val) = 0;
|
||||
/// Extracts an UInt64. Returns false if null was received.
|
||||
|
||||
virtual bool extract(std::size_t pos, bool& val) = 0;
|
||||
/// Extracts a boolean. Returns false if null was received.
|
||||
|
||||
virtual bool extract(std::size_t pos, float& val) = 0;
|
||||
/// Extracts a float. Returns false if null was received.
|
||||
|
||||
virtual bool extract(std::size_t pos, double& val) = 0;
|
||||
/// Extracts a double. Returns false if null was received.
|
||||
|
||||
virtual bool extract(std::size_t pos, char& val) = 0;
|
||||
/// Extracts a single character. Returns false if null was received.
|
||||
|
||||
virtual bool extract(std::size_t pos, std::string& val) = 0;
|
||||
/// Extracts a string. Returns false if null was received.
|
||||
|
||||
virtual bool extract(std::size_t pos, BLOB& val) = 0;
|
||||
/// Extracts a BLOB. Returns false if null was received.
|
||||
----
|
||||
|
||||
!!!AbstractPreparation
|
||||
<[AbstractPreparation]> is an optional interface responsible for preparing an extract. If you need it depends on the <[DataConnector]> you implement. For example, SQLite can do perfectly without it, ODBC instead requires it.
|
||||
SQLite doesn't need it because it works as follows:
|
||||
* sendQuery
|
||||
* getNextResult
|
||||
* extract single row values from result set
|
||||
|
||||
This works because SQLites <*getNextResult*> provides the data as string, i.e. it doesn't need any type information.
|
||||
|
||||
The ODBC implementation is different:
|
||||
* register/prepare for each column an output location
|
||||
* getNextResult
|
||||
* extract for each row the value by copying the content of the previously registered output location
|
||||
|
||||
<[AbstractPreparation]> is responsible for the first step. A typical prepare implementation will look like that:
|
||||
|
||||
void prepare(std::size_t pos, Poco::Int32 val)
|
||||
{
|
||||
_myVec[pos] = Poco::Any a(val);
|
||||
int* i = AnyCast<int>(&_myVec[pos]);
|
||||
//register int* i for output, Db specific
|
||||
}
|
||||
----
|
||||
Extract now changes to:
|
||||
|
||||
bool Extractor::extract(std::size_t pos, Poco::Int16& val)
|
||||
{
|
||||
if (isNull(pos))
|
||||
return false;
|
||||
val = AnyCast<int>(_myVec[pos]);
|
||||
return true;
|
||||
}
|
||||
----
|
||||
|
||||
!!Complete Interface
|
||||
|
||||
AbstractPreparation();
|
||||
/// Creates the AbstractPreparation.
|
||||
|
||||
virtual ~AbstractPreparation();
|
||||
/// Destroys the AbstractPreparation.
|
||||
|
||||
virtual void prepare(std::size_t pos, Poco::Int8) = 0;
|
||||
/// Prepares an Int8.
|
||||
|
||||
virtual void prepare(std::size_t pos, Poco::UInt8) = 0;
|
||||
/// Prepares an UInt8.
|
||||
|
||||
virtual void prepare(std::size_t pos, Poco::Int16) = 0;
|
||||
/// Prepares an Int16.
|
||||
|
||||
virtual void prepare(std::size_t pos, Poco::UInt16) = 0;
|
||||
/// Prepares an UInt16.
|
||||
|
||||
virtual void prepare(std::size_t pos, Poco::Int32) = 0;
|
||||
/// Prepares an Int32.
|
||||
|
||||
virtual void prepare(std::size_t pos, Poco::UInt32) = 0;
|
||||
/// Prepares an UInt32.
|
||||
|
||||
virtual void prepare(std::size_t pos, Poco::Int64) = 0;
|
||||
/// Prepares an Int64.
|
||||
|
||||
virtual void prepare(std::size_t pos, Poco::UInt64) = 0;
|
||||
/// Prepares an UInt64.
|
||||
|
||||
virtual void prepare(std::size_t pos, bool) = 0;
|
||||
/// Prepares a boolean.
|
||||
|
||||
virtual void prepare(std::size_t pos, float) = 0;
|
||||
/// Prepares a float.
|
||||
|
||||
virtual void prepare(std::size_t pos, double) = 0;
|
||||
/// Prepares a double.
|
||||
|
||||
virtual void prepare(std::size_t pos, char) = 0;
|
||||
/// Prepares a single character.
|
||||
|
||||
virtual void prepare(std::size_t pos, const std::string& ) = 0;
|
||||
/// Prepares a string.
|
||||
|
||||
virtual void prepare(std::size_t pos, const BLOB&) = 0;
|
||||
----
|
||||
|
||||
Note that it is recommended to prepare a statement only once in the compileImpl of <[StatementImpl]>. The AbstractPrepare objects (which make use of <[AbstractPreparation]>
|
||||
can be created by iterating over the Extractor objects of the StatementImpl:
|
||||
|
||||
Poco::Data::AbstractExtractingVec::iterator it = extractings().begin();
|
||||
Poco::Data::AbstractExtractingVec::iterator itEnd = extractings().end();
|
||||
std::size_t pos = 0; // sqlite starts with pos 0 for results! your DB maybe with 1
|
||||
for (; it != itEnd; ++it)
|
||||
{
|
||||
AbstractPrepare* pPrep = (*it)->createPrepareObject(pPreparation, pos);
|
||||
_prepareVec.push_back(pPrep);
|
||||
(*it)->extract(pos);
|
||||
pos += (*it)->numOfColumnsHandled();
|
||||
}
|
||||
----
|
||||
!!!StatementImpl
|
||||
A <[StatementImpl]> stores as member a Binder and an Extractor (optional a Preparation object) and is responsible for compiling, binding, fetching single rows from the database and invoking the <*Extracting*> objects.
|
||||
The interface it has to implement is given as:
|
||||
|
||||
public:
|
||||
StatementImpl();
|
||||
/// Creates the StatementImpl.
|
||||
|
||||
virtual ~StatementImpl();
|
||||
/// Destroys the StatementImpl.
|
||||
|
||||
protected:
|
||||
virtual bool hasNext() = 0;
|
||||
/// Returns true if a call to next() will return data. Note that the
|
||||
/// implementation must support several consecutive calls to hasNext
|
||||
/// without data getting lost, ie. hasNext(); hasNext(); next() must
|
||||
/// be equal to hasNext(); next();
|
||||
|
||||
virtual void next() = 0;
|
||||
/// Retrieves the next row from the resultset.
|
||||
/// Will throw, if the resultset is empty.
|
||||
/// Expects the statement to be compiled and bound
|
||||
|
||||
virtual bool canBind() const = 0;
|
||||
/// Returns if another bind is possible.
|
||||
|
||||
virtual void compileImpl() = 0;
|
||||
/// Compiles the statement, doesn't bind yet.
|
||||
/// From now on AbstractBinder and AbstractExtractor
|
||||
/// will be used
|
||||
|
||||
virtual void bindImpl() = 0;
|
||||
/// Binds parameters.
|
||||
|
||||
virtual AbstractExtractor& extractor() = 0;
|
||||
/// Returns the concrete extractor used by the statement.
|
||||
|
||||
virtual AbstractBinder& binder() = 0;
|
||||
/// Returns the concrete binder used by the statement.
|
||||
----
|
||||
|
||||
The Extracting and Binding objects can be accessed via the calls to the super-class methods <*extractings()*> and <*bindings()*>.
|
||||
A high-level <*bind*> implementation will look like this:
|
||||
|
||||
[...]
|
||||
Poco::Data::AbstractBindingVec& binds = bindings();
|
||||
std::size_t pos = 1; // or 0 depending on your database
|
||||
Poco::Data::AbstractBindingVec::iterator it = binds.begin();
|
||||
Poco::Data::AbstractBindingVec::iterator itEnd = binds.end();
|
||||
for (; it != itEnd && (*it)->canBind(); ++it)
|
||||
{
|
||||
(*it)->bind(pos);
|
||||
pos += (*it)->numOfColumnsHandled();
|
||||
}
|
||||
----
|
||||
|
||||
A high-level <*next*> implementation:
|
||||
|
||||
if (!hasNext())
|
||||
throw Poco::Data::DataException("No data received");
|
||||
int nCol = countColumnsInResult...;
|
||||
poco_assert (columnsHandled() == nCol);
|
||||
Poco::Data::AbstractExtractingVec::iterator it = extractings().begin();
|
||||
Poco::Data::AbstractExtractingVec::iterator itEnd = extractings().end();
|
||||
std::size_t pos = 0; // sqlite starts with pos 0 for results! your DB maybe with 1
|
||||
for (; it != itEnd; ++it)
|
||||
{
|
||||
(*it)->extract(pos);
|
||||
pos += (*it)->numOfColumnsHandled();
|
||||
}
|
||||
enableHasNext();
|
||||
----
|
||||
|
||||
A high-level <*hasNext*> implementation:
|
||||
|
||||
if (enabledhasNext())
|
||||
{
|
||||
checkIfItHasMoreData
|
||||
cacheResult
|
||||
disablehasNext()
|
||||
}
|
||||
|
||||
return cachedResult;
|
||||
----
|
||||
|
||||
A high-level <*compileImpl*>:
|
||||
|
||||
if (compiled)
|
||||
return;
|
||||
|
||||
std::string sqlStmt(toString());
|
||||
|
||||
if database expects placeholders in different format than ":name", parse and replace them
|
||||
|
||||
compile statement;
|
||||
|
||||
create Binder;
|
||||
create Extractor;
|
||||
----
|
||||
|
||||
A high-level <*canBind*>:
|
||||
|
||||
bool ret = false;
|
||||
if (!bindings().empty() && validCompiledStatement)
|
||||
ret = (*bindings().begin())->canBind();
|
||||
|
||||
return ret;
|
||||
----
|
||||
|
||||
!!!SessionImpl
|
||||
The purpose of the <[SessionImpl]> is simply to open/close a connection to the database, to act as factory for <[StatementImpl]> objects, and to handle transactions.
|
||||
The connection is opened in the constructor, and closed in the destructor.
|
||||
|
||||
Poco::Data::StatementImpl* createStatementImpl();
|
||||
/// Returns an SQLite StatementImpl
|
||||
|
||||
void begin();
|
||||
/// Starts a transaction
|
||||
|
||||
void commit();
|
||||
/// Commits and ends a transaction
|
||||
|
||||
void rollback();
|
||||
/// Aborts a transaction
|
||||
----
|
||||
|
||||
!!!Connector
|
||||
Finally, one needs to implement the <[Connector]>.
|
||||
Each <[Connector]> should have a public static const string member named <*KEY*> and must have a factory method to <*create*> <[ Poco::AutoPtr ]> objects of type <[SessionImpl]>.
|
||||
It should also have a static <*addToFactory()*> and a static <*removeFromFactory()*> method:
|
||||
|
||||
class My_API Connector: public Poco::Data::Connector
|
||||
/// Connector instantiates SessionImpl objects.
|
||||
{
|
||||
public:
|
||||
static const std::string KEY;
|
||||
/// Keyword for creating sessions
|
||||
|
||||
Connector();
|
||||
/// Creates the Connector.
|
||||
|
||||
~Connector();
|
||||
/// Destroys the Connector.
|
||||
|
||||
Poco::AutoPtr < Poco::Data::SessionImpl > createSession(const std::string& connectionString);
|
||||
/// Creates a SessionImpl object and initializes it with the given connectionString.
|
||||
|
||||
static void registerConnector();
|
||||
/// Registers the Connector under the Keyword Connector::KEY at the Poco::Data::SessionFactory
|
||||
|
||||
static void unregisterConnector();
|
||||
/// Unregisters the Connector under the Keyword Connector::KEY at the Poco::Data::SessionFactory
|
||||
};
|
||||
----
|
||||
@@ -1,26 +0,0 @@
|
||||
POCO Data Release Notes
|
||||
Data
|
||||
|
||||
!!!Release 1.3
|
||||
|
||||
Release 1.3 of the POCO C++ Libraries is the first official release containing the Data library.
|
||||
The Data library has been available in a development state for the 1.2 release. For the 1.3
|
||||
release, a few things have been changed in an incompatible way that requires changes
|
||||
to existing code.
|
||||
|
||||
!!Summary of Changes
|
||||
|
||||
- Class Poco::Data::RecordSet has been added providing generic access
|
||||
to arbitrary tables.
|
||||
- SessionInstantiator has been renamed Poco::Data::Connector.
|
||||
- Poco::Data::BLOBInputStream and Poco::Data::BLOBOutputStream allow convenient access to Poco::Data::BLOB data.
|
||||
- Poco::Data::Session and Poco::Data::Statement can be used in simpler ways.
|
||||
- The DataConnectors project directory has been merged into the Data project directory.
|
||||
|
||||
|
||||
!!Incompatible Changes and Possible Transition Issues
|
||||
|
||||
SessionInstantiator has been renamed Poco::Data::Connector, and the
|
||||
member functions addToFactory() and removeFromFactory() are now named
|
||||
registerConnector() and unregisterConnector(), respectively.
|
||||
This requires a change in all applications already using POCO Data.
|
||||
@@ -1,529 +0,0 @@
|
||||
POCO Data User Guide
|
||||
Data
|
||||
|
||||
!!!First Steps
|
||||
POCO Data is POCO's database abstraction layer which allows users to easily
|
||||
send/retrieve data to/from various different SQL databases.
|
||||
The following complete example shows how to use it:
|
||||
|
||||
|
||||
#include "Poco/Data/Common.h"
|
||||
#include "Poco/Data/SQLite/Connector.h"
|
||||
#include <iostream>
|
||||
|
||||
using namespace Poco::Data;
|
||||
|
||||
|
||||
void init()
|
||||
{
|
||||
SQLite::Connector::registerConnector();
|
||||
}
|
||||
|
||||
|
||||
void shutdown()
|
||||
{
|
||||
SQLite::Connector::unregisterConnector();
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
init();
|
||||
Session ses("SQLite", "sample.db");
|
||||
int count = 0;
|
||||
ses << "SELECT COUNT(*) FROM PERSON", into(count), now;
|
||||
std::cout << "People in DB " << count;
|
||||
shutdown();
|
||||
}
|
||||
----
|
||||
|
||||
The above example is pretty much self explanatory. The <[Poco/Data/Common.h]> file pulls in some common includes,
|
||||
the SQLite::Connector is used to register the SQLite connector so that we can later create an SQLite session
|
||||
via the SessionFactory. The two-argument constructor
|
||||
|
||||
Sesssion ses("SQLite", "sample.db");
|
||||
----
|
||||
|
||||
is actually equivalent to:
|
||||
Session ses(SessionFactory::instance()::create("SQLite", "sample.db"));
|
||||
----
|
||||
|
||||
The << operator is used to send SQL statements to the Session, the <*into(count)*> simply informs the session where to store the result of the query.
|
||||
Take note of the <!now!> at the end of the SQL statement. It is required, otherwise the statement would not be executed.
|
||||
The <* <[ using namespace Poco::Data ]> *> is for convenience only but highly recommended for good readable code
|
||||
(while <* <[ ses << "SELECT COUNT(*) FROM PERSON", Poco::Data::into(count), Poco::Data::now; ]> *> is valid, it simply looks... strange).
|
||||
|
||||
The remainder of this tutorial is split up into the following parts:
|
||||
* Creating Sessions
|
||||
* Inserting and Retrieving Data: the magic of <* into *> and <*use*>
|
||||
* Working with Statements
|
||||
* Working with Collections: vector, set, multiset, map and multimap
|
||||
* Working with Limits
|
||||
* Working with complex data types: how to map C++ objects to a database table
|
||||
|
||||
!!!Creating Sessions
|
||||
Sessions are always created via the SessionFactory create method, or implicitly
|
||||
via the two-argument Session constructor.
|
||||
|
||||
Session create(const std::string& connectorKey, const std::string& connectionString);
|
||||
----
|
||||
The first parameter contains the type of the Session one wants to create. For the moment "SQLite" is supported
|
||||
directly, and via the ODBC driver support for Oracle, SQLite, DB2, SQLServer and PostgreSQL is available.
|
||||
The second parameter contains the (connector-specific) connection string.
|
||||
In the case of SQLite, the location of the database file is sufficient.
|
||||
|
||||
!!!Inserting and Retrieving Data
|
||||
Inserting data works by <* using *> the content of other variables. Assume we have a table that stores only forenames:
|
||||
|
||||
ForeName (Name VARCHAR(30))
|
||||
----
|
||||
If we want to insert one single forename we could simply write:
|
||||
|
||||
std::string aName("Peter");
|
||||
ses << "INSERT INTO FORENAME VALUES(" << aName << ")", now;
|
||||
----
|
||||
|
||||
Well, we could do that, but we won't. A much better solution is to use <!placeholders!> and connect each placeholder via a <!use!>
|
||||
expression with a variable that will provide the value during execution.
|
||||
Placeholders are recognized by having a <!:!> in front of their name. Rewriting the above code now simply gives
|
||||
|
||||
std::string aName("Peter");
|
||||
ses << "INSERT INTO FORENAME VALUES(:name)", use(aName), now;
|
||||
----
|
||||
|
||||
In this example the <!use!> expression matches the <* :name *> with the <*Peter*> value.
|
||||
Note that apart from the nicer syntax, the real benefit of placeholders - which is performance - doesn't show here.
|
||||
Check the <*Working with Statements*> section to find out more.
|
||||
|
||||
Retrieving data from the Database works similar. The <!into!> expression matches the returned database values to
|
||||
C++ objects, it also allows to provide a default value in case null data is returned from the database:
|
||||
|
||||
std::string aName;
|
||||
ses << "SELECT NAME FROM FORENAME", into(aName), now; // the default is the empty string
|
||||
ses << "SELECT NAME FROM FORENAME", into(aName, "default"), now;
|
||||
----
|
||||
|
||||
It is also possible to combine into and use expressions:
|
||||
|
||||
std::string aName;
|
||||
std::string match("Peter")
|
||||
ses << "SELECT NAME FROM FORENAME WHERE NAME=:name", into(aName), use(match), now;
|
||||
poco_assert (aName == match);
|
||||
----
|
||||
|
||||
Typically, tables will not be so trivial, ie. they will have more than one column which allows for more than one into/use.
|
||||
Lets assume we have a Person table that contains an age, a first and a last name:
|
||||
|
||||
std::string firstName("Peter";
|
||||
std::string lastName("Junior");
|
||||
int age = 0;
|
||||
ses << INSERT INTO PERSON VALUES (:fn, :ln, :age)", use(firstName), use(lastName), use(age), now;
|
||||
ses << "SELECT (firstname, lastname, age) FROM Person", into(firstName), into(lastName), into(age), now;
|
||||
----
|
||||
|
||||
Most important here is the <!order!> of the into and use expressions. The first placeholder is matched by the first <*use*>,
|
||||
the 2nd by the 2nd <*use*> etc.
|
||||
The same is true for the <*into*> statement. We select <*firstname*> as the first column of the result set,
|
||||
thus <*into(firstName)*> must be the first into clause.
|
||||
|
||||
!! Handling NULL entries
|
||||
A common case with databases are optional data fields that can contain NULL. To accomodate for NULL, the <*into*> expression allows
|
||||
you to define default values.
|
||||
For example, assume that age is such an optional field and we want to provide as default value <!-1!>
|
||||
which is done by writing <!into(age, -1)!>:
|
||||
|
||||
std::string firstName("Peter";
|
||||
std::string lastName("Junior");
|
||||
int age = 0;
|
||||
ses << INSERT INTO PERSON VALUES (:fn, :ln, :age)", use(firstName), use(lastName), use(age), now;
|
||||
ses << "SELECT (firstname, lastname, age) FROM Person", into(firstName), into(lastName), into(age, -1), now;
|
||||
----
|
||||
|
||||
While you can achieve the same effect by initializing age previously to -1 (<*int age = -1*>),
|
||||
this won't work with collection types. Here you must provide the second parameter
|
||||
to init. Otherwise, values will be initialized to compiler specific values.
|
||||
|
||||
!!!Working with Statements
|
||||
We often mentioned the term <*Statement*> in the previous section, yet we only worked with database session objects so far,
|
||||
or at least, that's what you have been made believe ;-).
|
||||
In reality, you have already worked with Statements. Lets take a look at the method signature of the << operator at Session:
|
||||
|
||||
template <typename T>
|
||||
Statement Session::operator << (const T& t)
|
||||
----
|
||||
|
||||
Simply ignore the template stuff in front, you won't need it. The only thing that counts here is that the operator <[ << ]> creates a
|
||||
<*Statement*> internally and returns it.
|
||||
What happened in the previous examples is that the returned Statement was never assigned to a variable but simply passed on to the <*now*>
|
||||
part which executed the statement. Afterwards the statement was destroyed.
|
||||
Let's take one of the previous examples and change it so that we assign the statement:
|
||||
|
||||
std::string aName("Peter");
|
||||
Statement stmt = ( ses << "INSERT INTO FORENAME VALUES(:name)", use(aName) );
|
||||
----
|
||||
Note that we must put brackets around the right part of the assignment, otherwise the compiler will complain.
|
||||
If you don't like the above syntax, the following alternative is equivalent:
|
||||
|
||||
Statement stmt(ses);
|
||||
stmt << "INSERT INTO FORENAME VALUES(:name)", use(aName);
|
||||
----
|
||||
|
||||
What did we achieve by assigning the statement to a variable? Well, currently nothing, apart that we can control when to <*execute*>:
|
||||
|
||||
std::string aName("Peter");
|
||||
Statement stmt = ( ses << "INSERT INTO FORENAME VALUES(:name)", use(aName) );
|
||||
stmt.execute();
|
||||
poco_assert (stmt.done());
|
||||
----
|
||||
|
||||
By calling <*execute*> we asserted that our query was executed and that the value was inserted. The check to <[stmt.done()]>
|
||||
simply guarantees that the statement was fully completed.
|
||||
|
||||
!!Prepared Statements
|
||||
A prepared statement is created by omitting the <*now*> clause.
|
||||
|
||||
Statement stmt = ( ses << "INSERT INTO FORENAME VALUES(:name)", use(aName) );
|
||||
----
|
||||
|
||||
The advantage of a prepared statement is performance. Assume the following loop:
|
||||
|
||||
std::string aName();
|
||||
Statement stmt = ( ses << "INSERT INTO FORENAME VALUES(:name)", use(aName) );
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
aName.append("x");
|
||||
stmt.execute();
|
||||
}
|
||||
----
|
||||
|
||||
Instead of creating and parsing the Statement 100 times, we only do this once and then use the placeholder in combination with the <*use*> clause
|
||||
to insert 100 different values into the database.
|
||||
Still, this isn't the best way to insert a collection of values into a database.
|
||||
|
||||
!!Things NOT To Do
|
||||
<!use!> expects as input a <!reference!> parameter, which is bound later during execution.
|
||||
Thus, one can only use variables, but never constants.
|
||||
The following code will very likely fail (but this is platform/compiler dependent and also depends if your
|
||||
building in release or debug mode, it will work from Monday to Thursday but will always fail on Friday, so shortly spoken: the kind of bugs
|
||||
software developers <*really*> love):
|
||||
|
||||
Statement stmt = (ses << INSERT INTO PERSON VALUES (:fn, :ln, :age)", use("Peter"), use("Junior"), use(4)); //ERR!
|
||||
stmt.execute();
|
||||
----
|
||||
The constant values <*Junior*>, <*Peter*> and <*4*> must be assigned to variables prior, otherwise their values will be invalid when execute is called.
|
||||
|
||||
!!!Collection Support
|
||||
If one needs to handle many values at once, one ought to use a collection class.
|
||||
Per default, the following collection types are supported:
|
||||
* vector: no requirements
|
||||
* set: the < operator must be supported by the datatype. Note that duplicate key/value pairs are ignored.
|
||||
* multiset: the < operator must be supported by the datatype
|
||||
* map: the () operator must be supported by the datatype and return the key of the object. Note that duplicate key/value pairs are ignored.
|
||||
* multimap: the () operator must be supported by the datatype and return the key of the object
|
||||
|
||||
A bulk insert example via vector would be:
|
||||
|
||||
std::string aName("");
|
||||
std::vector<std::string> data;
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
aName.append("x");
|
||||
data.push_back(aName);
|
||||
}
|
||||
ses << "INSERT INTO FORENAME VALUES(:name)", use(data), now;
|
||||
----
|
||||
|
||||
The same example would work with set or multiset but not with map and multimap (std::string has no () operator).
|
||||
Note that <!use!> requires <*non-empty*> collections!
|
||||
|
||||
Now reconsider the following example:
|
||||
|
||||
std::string aName;
|
||||
ses << "SELECT NAME FROM FORENAME", into(aName), now;
|
||||
----
|
||||
|
||||
Previously, it worked because the table contained only one single entry but now the database table contains at least 100 strings,
|
||||
yet we only offer storage space for one single result.
|
||||
Thus, the above code will fail and throw an exception.
|
||||
One possible way to handle this is:
|
||||
|
||||
std::vector<std::string> names;
|
||||
ses << "SELECT NAME FROM FORENAME", into(names), now;
|
||||
----
|
||||
|
||||
And again, instead of vector, one could use set or multiset.
|
||||
|
||||
!!!The limit clause
|
||||
Working with collections might be convenient to bulk process data but there is also the risk that large operations will
|
||||
block your application for a very long time. In addition, you might want to have better fine-grained control over your
|
||||
query, e.g. you only want to extract a subset of data until a condition is met.
|
||||
To elevate that problem, one can use the <!limit!> keyword.
|
||||
|
||||
Let's assume we are retrieving thousands of rows from a database to render the data to a GUI.
|
||||
To allow the user to stop fetching data any time (and to avoid having the user franatically click inside the GUI because
|
||||
it doesn't show anything for seconds), we have to partition this process:
|
||||
|
||||
std::vector<std::string> names;
|
||||
ses << "SELECT NAME FROM FORENAME", into(names), limit(50), now;
|
||||
----
|
||||
|
||||
The above example will retrieve up to 50 rows from the database (note that returning nothing is valid!) and <*append*>
|
||||
it to the names collection, i.e. the collection is not cleared!
|
||||
If one wants to make sure that <!exactly!> 50 rows are returned one must set the 2nd limit parameter
|
||||
(which per default is set to <*false*>) to <*true*>:
|
||||
|
||||
std::vector<std::string> names;
|
||||
ses << "SELECT NAME FROM FORENAME", into(names), limit(50, true), now;
|
||||
----
|
||||
|
||||
Iterating over a complete result collection is done via the Statement object until statement.done() returns true.
|
||||
For the next example, we assume that our system knows about 101 forenames:
|
||||
|
||||
std::vector<std::string> names;
|
||||
Statement stmt = (ses << "SELECT NAME FROM FORENAME", into(names), limit(50));
|
||||
stmt.execute(); //names.size() == 50
|
||||
poco_assert (!stmt.done());
|
||||
stmt.execute(); //names.size() == 100
|
||||
poco_assert (!stmt.done());
|
||||
stmt.execute(); //names.size() == 101
|
||||
poco_assert (stmt.done());
|
||||
----
|
||||
|
||||
We previously stated that if no data is returned this is valid too. Thus, executing the following statement on an
|
||||
empty database table will work:
|
||||
|
||||
std::string aName;
|
||||
ses << "SELECT NAME FROM FORENAME", into(aName), now;
|
||||
----
|
||||
|
||||
To guarantee that at least one valid result row is returned use the <!lowerLimit!> clause:
|
||||
|
||||
std::string aName;
|
||||
ses << "SELECT NAME FROM FORENAME", into(aName), lowerLimit(1), now;
|
||||
----
|
||||
If the table is now empty, an exception will be thrown. If the query succeeds, aName is guaranteed to be initialized.
|
||||
Note that <!limit!> is only the short name for <!upperLimit!>. To iterate over a result set step-by-step, e.g. one wants to avoid
|
||||
using a collection class, one would write:
|
||||
|
||||
std::string aName;
|
||||
Statement stmt = (ses << "SELECT NAME FROM FORENAME", into(aName), lowerLimit(1), upperLimit(1));
|
||||
while (!stmt.done())
|
||||
stmt.execute();
|
||||
----
|
||||
|
||||
And for the lazy ones, there is the <!range!> command:
|
||||
|
||||
std::string aName;
|
||||
Statement stmt = (ses << "SELECT NAME FROM FORENAME", into(aName), range(1,1));
|
||||
while (!stmt.done())
|
||||
stmt.execute();
|
||||
----
|
||||
The third parameter to range is an optional boolean value which specifies if the upper limit is a hard limit, ie.
|
||||
if the amount of rows returned by the query must match exactly. Per default exact matching is off.
|
||||
|
||||
!!!Complex Data Type Mapping
|
||||
All the previous examples were contented to work with only the most basic data types: integer, string, ...
|
||||
a situation, unlikely to occur in real-world scenarios.
|
||||
Assume you have a class Person:
|
||||
|
||||
class Person
|
||||
{
|
||||
public:
|
||||
// default constructor+destr.
|
||||
// getter and setter methods for all members
|
||||
[...]
|
||||
|
||||
bool operator <(const Person& p) const
|
||||
/// we need this for set and multiset support
|
||||
{
|
||||
return _socialSecNr < p._socialSecNr;
|
||||
}
|
||||
|
||||
Poco::UInt64 operator()() const
|
||||
/// we need this operator to return the key for the map and multimap
|
||||
{
|
||||
return _socialSecNr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _firstName;
|
||||
std::string _lastName;
|
||||
Poco::UInt64 _socialSecNr;
|
||||
}
|
||||
----
|
||||
|
||||
Ideally, one would like to use a Person as simple as one used a string. All that is needed is a template specialization of the <*TypeHandler*>
|
||||
template. Note that template specializations must be declared in the <!same namespace!> as the original template, i.e. <*Poco::Data*>.
|
||||
The template specialization must implement the following methods:
|
||||
|
||||
namespace Poco {
|
||||
namespace Data {
|
||||
|
||||
template <>
|
||||
class TypeHandler<class Person>
|
||||
{
|
||||
public:
|
||||
static std::size_t size()
|
||||
{
|
||||
return 3; // we handle three columns of the Table!
|
||||
}
|
||||
|
||||
static void bind(std::size_t pos, const Person& obj, AbstractBinder* pBinder)
|
||||
{
|
||||
poco_assert_dbg (pBinder != 0);
|
||||
// the table is defined as Person (FirstName VARCHAR(30), lastName VARCHAR, SocialSecNr INTEGER(3))
|
||||
// Note that we advance pos by the number of columns the datatype uses! For string/int this is one.
|
||||
TypeHandler<std::string>::bind(pos++, obj.getFirstName(), pBinder);
|
||||
TypeHandler<std::string>::bind(pos++, obj.getLastName(), pBinder);
|
||||
TypeHandler<Poco::UInt64>::bind(pos++, obj.getSocialSecNr(), pBinder);
|
||||
}
|
||||
|
||||
static void prepare(std::size_t pos, const Person& obj, AbstractPreparation* pPrepare)
|
||||
{
|
||||
poco_assert_dbg (pBinder != 0);
|
||||
// the table is defined as Person (FirstName VARCHAR(30), lastName VARCHAR, SocialSecNr INTEGER(3))
|
||||
// Note that we advance pos by the number of columns the datatype uses! For string/int this is one.
|
||||
TypeHandler<std::string>::prepare(pos++, obj.getFirstName(), pPrepare);
|
||||
TypeHandler<std::string>::prepare(pos++, obj.getLastName(), pPrepare);
|
||||
TypeHandler<Poco::UInt64>::prepare(pos++, obj.getSocialSecNr(), pPrepare);
|
||||
}
|
||||
|
||||
static void extract(std::size_t pos, Person& obj, const Person& defVal, AbstractExtractor* pExt)
|
||||
/// obj will contain the result, defVal contains values we should use when one column is NULL
|
||||
{
|
||||
poco_assert_dbg (pExt != 0);
|
||||
std::string firstName;
|
||||
std::string lastName;
|
||||
Poco::UInt64 socialSecNr = 0;
|
||||
TypeHandler<std::string>::extract(pos++, firstName, defVal.getFirstName(), pExt);
|
||||
TypeHandler<std::string>::extract(pos++, lastName, defVal.getLastName(), pExt);
|
||||
TypeHandler<Poco::UInt64>::extract(pos++, socialSecNr, defVal.getSocialSecNr(), pExt);
|
||||
obj.setFirstName(firstName);
|
||||
obj.setLastName(lastName);
|
||||
obj.setSocialSecNr(socialSecNr);
|
||||
}
|
||||
};
|
||||
|
||||
} } // namespace Poco::Data
|
||||
----
|
||||
|
||||
And that's all you have to do. Working with Person is now as simple as working with a string:
|
||||
|
||||
std::map<Poco::UInt64, Person> people;
|
||||
ses << "SELECT * FROM Person", into(people), now;
|
||||
----
|
||||
|
||||
!!!RecordSet
|
||||
The Poco::Data::RecordSet class provides a generic way to work with database tables.
|
||||
Using a <[RecordSet]>, one can:
|
||||
- iterate over all columns and rows in a table
|
||||
- obtain meta information about columns (such as name, type, length, etc.)
|
||||
|
||||
To work with a RecordSet, first create a Statement, execute it, and
|
||||
create the RecordSet from the Statement, as follows:
|
||||
|
||||
Statement select(session);
|
||||
select << "SELECT * FROM Person";
|
||||
select.execute();
|
||||
RecordSet rs(select);
|
||||
----
|
||||
|
||||
The number of rows in the RecordSet can be limited by specifying
|
||||
a limit for the Statement.
|
||||
|
||||
Following example demonstrates how to iterate over all rows and columns
|
||||
in a RecordSet:
|
||||
|
||||
bool more = rs.moveFirst();
|
||||
while (more)
|
||||
{
|
||||
for (std::size_t col = 0; col < cols; ++col)
|
||||
{
|
||||
std::cout << rs[col].convert<std::string>() << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
more = rs.moveNext();
|
||||
}
|
||||
----
|
||||
|
||||
As mentioned above, the number of rows retrieved into a RecordSet at a
|
||||
time can be limited using the <[limit]> or <[range]> clause. Iterating
|
||||
over all rows in a table a bunch of rows at a time can thus be done as
|
||||
follows:
|
||||
|
||||
Statement select(session);
|
||||
select << "SELECT * FROM Person", range(0, 10);
|
||||
RecordSet rs(select);
|
||||
while (!select.done())
|
||||
{
|
||||
select.execute();
|
||||
bool more = rs.moveFirst();
|
||||
while (more)
|
||||
{
|
||||
for (std::size_t col = 0; col < cols; ++col)
|
||||
{
|
||||
std::cout << rs[col].convert<std::string>() << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
more = rs.moveNext();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
!!!Tuples
|
||||
Poco::Tuple and vectors of Poco::Tuple provide a convenient way to work with rows when
|
||||
column types are known, because TypeHandlers for them are readily available.
|
||||
|
||||
Consider the following example:
|
||||
|
||||
typedef Poco::Tuple<std::string, std::string, int> Person;
|
||||
typedef std::vector<Person> People;
|
||||
|
||||
People people;
|
||||
people.push_back(Person("Bart Simpson", "Springfield", 12));
|
||||
people.push_back(Person("Lisa Simpson", "Springfield", 10));
|
||||
|
||||
Statement insert(session);
|
||||
insert << "INSERT INTO Person VALUES(:name, :address, :age)",
|
||||
use(people), now;
|
||||
----
|
||||
|
||||
Of course, tuples can also be used in queries:
|
||||
|
||||
Statement select(session);
|
||||
select << "SELECT Name, Address, Age FROM Person",
|
||||
into(people),
|
||||
now;
|
||||
|
||||
for (People::const_iterator it = people.begin(); it != people.end(); ++it)
|
||||
{
|
||||
std::cout << "Name: " << it->get<0>() <<
|
||||
", Address: " << it->get<1>() <<
|
||||
", Age: " << it->get<2>() <<std::endl;
|
||||
}
|
||||
----
|
||||
|
||||
!!!Session Pooling
|
||||
Creating a connection to a database is often a time consuming
|
||||
operation. Therefore it makes sense to save a session object for
|
||||
later reuse once it is no longer needed.
|
||||
|
||||
A Poco::Data::SessionPool manages a collection of sessions.
|
||||
When a session is requested, the SessionPool first
|
||||
looks in its set of already initialized sessions for an
|
||||
available object. If one is found, it is returned to the
|
||||
client and marked as "in-use". If no session is available,
|
||||
the SessionPool attempts to create a new one for the client.
|
||||
To avoid excessive creation of sessions, a limit
|
||||
can be set on the maximum number of objects.
|
||||
|
||||
The following code fragment shows how to use the SessionPool:
|
||||
|
||||
SessionPool pool("ODBC", "...");
|
||||
// ...
|
||||
Session sess(pool.get());
|
||||
----
|
||||
|
||||
Pooled sessions are automatically returned to the pool when the
|
||||
Session variable holding them is destroyed.
|
||||
@@ -1,3 +0,0 @@
|
||||
For the latest version of the documentation go to
|
||||
|
||||
http://appinf.com/poco/wiki/tiki-index.php?page=Data
|
||||
Reference in New Issue
Block a user