removing old trunk files

This commit is contained in:
Aleksandar Fabijanic
2012-04-23 00:43:14 +00:00
parent 2ce14cafb5
commit f9b60296f7
4754 changed files with 0 additions and 943568 deletions

View File

@@ -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
};
----

View File

@@ -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.

View File

@@ -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.

View File

@@ -1,3 +0,0 @@
For the latest version of the documentation go to
http://appinf.com/poco/wiki/tiki-index.php?page=Data