|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis article describes A description of how to link applications with SQLite is provided, then an
example program using To set the scene, here is a quote from the SQLite author...
BackgroundI am always on the lookout for simple yet powerful software development tools and ideas, and SQLite definitely falls into this category. In fact, the "Lite" name is a bit misleading, as it implements a large subset of the SQL standard, including transactions, and when projects such as PHP start to bundle it as standard instead of MySQL, you have to take a look. I thought it would be fun to write a thin wrapper around the C interface to make it C++ friendly. There are already a number of C++ wrappers listed on the SQLite website, but one is commercial, another seemed a bit complex, and another is specific to the wxWidgets framework. After all, the author of SQLite looks to have gone to pains to keep things simple, so I thought a C++ wrapper for it should keep things simple as well. Working With SQLiteSQLite is provided in 2 packages on the Windows platform, as a compiled DLL, and also in source form. Even if you only wish to use the DLL, you will still need to get the source code, as this contains the required header file. If desired, the SQLite source could be compiled into a library (.lib) file for statically linking with your application, but this is not covered in this article. Compilation instructions can be found on the SQLite web site. Linking dynamically still requires that a .lib file is built for
linking with your application. This can be done using Microsoft's
Unzip sqlite.zip which contains sqlite.dll and sqlite.def, and execute the following command to produce the lib file. c:\>lib /def:sqlite.def
sqlite.h needs to be visible to your application at compile time, as does sqlite.lib. sqlite.dll needs to be available to your application at runtime. CppSQLite Demo CodeThe following code demonstrates how to use the main features of SQLite via
#include "CppSQLite.h" #include <ctime> #include <iostream> using namespace std; const char* gszFile = "C:\\test.db"; int main(int argc, char** argv) { try { int i, fld; time_t tmStart, tmEnd; CppSQLiteDB db; cout << "SQLite Version: " << db.SQLiteVersion() << endl; remove(gszFile); db.open(gszFile); cout << endl << "Creating emp table" << endl; db.execDML("create table emp(empno int, empname char(20));"); /////////////////////////////////////////////////////////////// // Execute some DML, and print number of rows affected by each one /////////////////////////////////////////////////////////////// cout << endl << "DML tests" << endl; int nRows = db.execDML("insert into emp values (7, 'David Beckham');"); cout << nRows << " rows inserted" << endl; nRows = db.execDML( "update emp set empname = 'Christiano Ronaldo' where empno = 7;"); cout << nRows << " rows updated" << endl; nRows = db.execDML("delete from emp where empno = 7;"); cout << nRows << " rows deleted" << endl; ///////////////////////////////////////////////////////////////// // Transaction Demo // The transaction could just as easily have been rolled back ///////////////////////////////////////////////////////////////// int nRowsToCreate(50000); cout << endl << "Transaction test, creating " << nRowsToCreate; cout << " rows please wait..." << endl; tmStart = time(0); db.execDML("begin transaction;"); for (i = 0; i < nRowsToCreate; i++) { char buf[128]; sprintf(buf, "insert into emp values (%d, 'Empname%06d');", i, i); db.execDML(buf); } db.execDML("commit transaction;"); tmEnd = time(0); //////////////////////////////////////////////////////////////// // Demonstrate CppSQLiteDB::execScalar() //////////////////////////////////////////////////////////////// cout << db.execScalar("select count(*) from emp;") << " rows in emp table in "; cout << tmEnd-tmStart << " seconds (that was fast!)" << endl; //////////////////////////////////////////////////////////////// // Re-create emp table with auto-increment field //////////////////////////////////////////////////////////////// cout << endl << "Auto increment test" << endl; db.execDML("drop table emp;"); db.execDML( "create table emp(empno integer primary key, empname char(20));"); cout << nRows << " rows deleted" << endl; for (i = 0; i < 5; i++) { char buf[128]; sprintf(buf, "insert into emp (empname) values ('Empname%06d');", i+1); db.execDML(buf); cout << " primary key: " << db.lastRowId() << endl; } /////////////////////////////////////////////////////////////////// // Query data and also show results of inserts into auto-increment field ////////////////////////////////////////////////////////////////// cout << endl << "Select statement test" << endl; CppSQLiteQuery q = db.execQuery("select * from emp order by 1;"); for (fld = 0; fld < q.numFields(); fld++) { cout << q.fieldName(fld) << "(" << q.fieldType(fld) << ")|"; } cout << endl; while (!q.eof()) { cout << q.fieldValue(0) << "|"; cout << q.fieldValue(1) << "|" << endl; q.nextRow(); } /////////////////////////////////////////////////////////////// // SQLite's printf() functionality. Handles embedded quotes and NULLs //////////////////////////////////////////////////////////////// cout << endl << "SQLite sprintf test" << endl; CppSQLiteBuffer bufSQL; bufSQL.format("insert into emp (empname) values (%Q);", "He's bad"); cout << (const char*)bufSQL << endl; db.execDML(bufSQL); bufSQL.format("insert into emp (empname) values (%Q);", NULL); cout << (const char*)bufSQL << endl; db.execDML(bufSQL); //////////////////////////////////////////////////////////////////// // Fetch table at once, and also show how to // use CppSQLiteTable::setRow() method ////////////////////////////////////////////////////////////////// cout << endl << "getTable() test" << endl; CppSQLiteTable t = db.getTable("select * from emp order by 1;"); for (fld = 0; fld < t.numFields(); fld++) { cout << t.fieldName(fld) << "|"; } cout << endl; for (int row = 0; row < t.numRows(); row++) { t.setRow(row); for (int fld = 0; fld < t.numFields(); fld++) { if (!t.fieldIsNull(fld)) cout << t.fieldValue(fld) << "|"; else cout << "NULL" << "|"; } cout << endl; } //////////////////////////////////////////////////////////////////// // Test CppSQLiteBinary by storing/retrieving some binary data, checking // it afterwards to make sure it is the same ////////////////////////////////////////////////////////////////// cout << endl << "Binary data test" << endl; db.execDML("create table bindata(desc char(10), data blob);"); unsigned char bin[256]; CppSQLiteBinary blob; for (i = 0; i < sizeof bin; i++) { bin[i] = i; } blob.setBinary(bin, sizeof bin); bufSQL.format( "insert into bindata values ('testing', %Q);", blob.getEncoded()); db.execDML(bufSQL); cout << "Stored binary Length: " << sizeof bin << endl; q = db.execQuery("select data from bindata where desc = 'testing';"); if (!q.eof()) { blob.setEncoded((unsigned char*)q.fieldValue("data")); cout << "Retrieved binary Length: " << blob.getBinaryLength() << endl; } const unsigned char* pbin = blob.getBinary(); for (i = 0; i < sizeof bin; i++) { if (pbin[i] != i) { cout << "Problem: i: ," << i << " bin[i]: " << pbin[i] << endl; } } ///////////////////////////////////////////////////////// // Pre-compiled Statements Demo ///////////////////////////////////////////////////////////// cout << endl << "Transaction test, creating " << nRowsToCreate; cout << " rows please wait..." << endl; db.execDML("drop table emp;"); db.execDML("create table emp(empno int, empname char(20));"); tmStart = time(0); db.execDML("begin transaction;"); CppSQLiteStatement stmt = db.compileStatement( "insert into emp values (?, ?);"); for (i = 0; i < nRowsToCreate; i++) { char buf[16]; sprintf(buf, "EmpName%06d", i); stmt.bind(1, i); stmt.bind(2, buf); stmt.execDML(); stmt.reset(); } db.execDML("commit transaction;"); tmEnd = time(0); cout << db.execScalar("select count(*) from emp;") << " rows in emp table in "; cout << tmEnd-tmStart << " seconds (that was even faster!)" << endl; cout << endl << "End of tests" << endl; } catch (CppSQLiteException& e) { cerr << e.errorCode() << ":" << e.errorMessage() << endl; } //////////////////////////////////////////////////////////////// // Loop until user enters q or Q /////////////////////////////////////////////////////////// char c(' '); while (c != 'q' && c != 'Q') { cout << "Press q then enter to quit: "; cin >> c; } return 0; } CppSQLite ClassesThe following simple classes are defined to encapsulate the functionality of SQLite. All the CppSQLiteExceptionEncapsulates a SQLite error code and message. Nothing complicated here, and this class could easily be incorporated into an existing exception hierarchy, if required. Error messages returned by SQLite need to be class CppSQLiteException { public: CppSQLiteException(const int nErrCode, char* szErrMess, bool bDeleteMsg=true); CppSQLiteException(const CppSQLiteException& e); virtual ~CppSQLiteException(); const int errorCode() { return mnErrCode; } const char* errorMessage() { return mpszErrMess; } static const char* errorCodeAsString(int nErrCode); private: int mnErrCode; char* mpszErrMess; }; CppSQLiteDBEncapsulates a SQLite database file. class CppSQLiteDB { public: enum CppSQLiteDBOpenMode { openExisting, createNew, openOrCreate }; CppSQLiteDB(); virtual ~CppSQLiteDB(); void open(const char* szFile); void close(); int execDML(const char* szSQL); CppSQLiteQuery execQuery(const char* szSQL); int execScalar(const char* szSQL); CppSQLiteTable getTable(const char* szSQL); CppSQLiteStatement compileStatement(const char* szSQL); int lastRowId(); void interrupt() { sqlite_interrupt(mpDB); } void setBusyTimeout(int nMillisecs); static const char* SQLiteVersion() { return SQLITE_VERSION; } private: CppSQLiteDB(const CppSQLiteDB& db); CppSQLiteDB& operator=(const CppSQLiteDB& db); sqlite_vm* compile(const char* szSQL); void checkDB(); sqlite* mpDB; int mnBusyTimeoutMs; };
SQLite is typeless, which means all fields are stored as strings. The one
exception to this is the
The copy constructor and Finally, the static method CppSQLiteQueryEncapsulates a SQLite query result set. class CppSQLiteQuery { public: CppSQLiteQuery(); CppSQLiteQuery(const CppSQLiteQuery& rQuery); CppSQLiteQuery(sqlite_vm* pVM, bool bEof, int nCols, const char** paszValues, const char** paszColNames, bool bOwnVM=true); CppSQLiteQuery& operator=(const CppSQLiteQuery& rQuery); virtual ~CppSQLiteQuery(); int numFields(); const char* fieldName(int nCol); const char* fieldType(int nCol); const char* fieldValue(int nField); const char* fieldValue(const char* szField); int getIntField(int nField, int nNullValue=0); int getIntField(const char* szField, int nNullValue=0); double getFloatField(int nField, double fNullValue=0.0); double getFloatField(const char* szField, double fNullValue=0.0); const char* getStringField(int nField, const char* szNullValue=""); const char* getStringField(const char* szField, const char* szNullValue=""); bool fieldIsNull(int nField); bool fieldIsNull(const char* szField); bool eof(); void nextRow(); void finalize(); private: void checkVM(); sqlite_vm* mpVM; bool mbEof; int mnCols; const char** mpaszValues; const char** mpaszColNames; bool mbOwnVM; };
getIntField(), It is not possible to iterate backwards through the results. The reason for
this is that
CppSQLiteTableSQLite provides a method to obtain a complete table's contents in a single
block of memory, class CppSQLiteTable { public: CppSQLiteTable(); CppSQLiteTable(const CppSQLiteTable& rTable); CppSQLiteTable(char** paszResults, int nRows, int nCols); virtual ~CppSQLiteTable(); CppSQLiteTable& operator=(const CppSQLiteTable& rTable); int numFields(); int numRows(); const char* fieldName(int nCol); const char* fieldValue(int nField); const char* fieldValue(const char* szField); int getIntField(int nField, int nNullValue=0); int getIntField(const char* szField, int nNullValue=0); double getFloatField(int nField, double fNullValue=0.0); double getFloatField(const char* szField, double fNullValue=0.0); const char* getStringField(int nField, const char* szNullValue=""); const char* getStringField(const char* szField, const char* szNullValue=""); bool fieldIsNull(int nField); bool fieldIsNull(const char* szField); void setRow(int nRow); void finalize(); private: void checkResults(); int mnCols; int mnRows; int mnCurrentRow; char** mpaszResults; };
CppSQLiteBufferEncapsulates SQLite " SQLite provides a function class CppSQLiteBuffer { public: CppSQLiteBuffer(); ~CppSQLiteBuffer(); const char* format(const char* szFormat, ...); operator const char*() { return mpBuf; } void clear(); private: char* mpBuf; };
CppSQLiteBinaryBecause SQLite stores all data as These 2 functions are not currently provided as part of the pre-compiled DLL, so I have copied the entire contents of SQLite's encode.c file into the CppSQLite.cpp file. Should these functions be provided in the DLL at some future point, they can easily be removed from CppSQLite.cpp. class CppSQLiteBinary { public: CppSQLiteBinary(); ~CppSQLiteBinary(); void setBinary(const unsigned char* pBuf, int nLen); void setEncoded(const unsigned char* pBuf); const unsigned char* getEncoded(); const unsigned char* getBinary(); int getBinaryLength(); unsigned char* allocBuffer(int nLen); void clear(); private: unsigned char* mpBuf; int mnBinaryLen; int mnBufferLen; int mnEncodedLen; bool mbEncoded; };
Data is retrieved using the
int f = open(gszJpgFile, O_RDONLY|O_BINARY); int nFileLen = filelength(f); read(f, blob.allocBuffer(nFileLen), nFileLen); CppSQLiteStatementSQLite provides some experimental functionality for working with pre-compiled
SQL. When the same SQL is being executed over and over again with different
values, a significant performance improvement can be had by only compiling the
SQL once, and executing it multiple times, each time with different values.
class CppSQLiteStatement { public: CppSQLiteStatement(); CppSQLiteStatement(const CppSQLiteStatement& rStatement); CppSQLiteStatement(sqlite* pDB, sqlite_vm* pVM); virtual ~CppSQLiteStatement(); CppSQLiteStatement& operator=(const CppSQLiteStatement& rStatement); int execDML(); CppSQLiteQuery execQuery(); void bind(int nParam, const char* szValue); void bind(int nParam, const int nValue); void bind(int nParam, const double dwValue); void bindNull(int nParam); void reset(); void finalize(); private: void checkDB(); void checkVM(); sqlite* mpDB; sqlite_vm* mpVM; }; A CppSQLiteStatement stmt = db.compileStatement("insert into emp values (?, ?);"); stmt.bind(1, 1); stmt.bind(2, "Emp Name"); stmt.execDML(); stmt.reset(); The After the programmer has finished with the result from either
MultithreadingSQLite is compiled as thread-safe on Windows by default, and
Each thread wishing to utilize CppSQLite on the same database file at the
same time must have its own The other change to SQLite Functionality Not Currently WrappedSQLite provides a mechanism that allows the application developer to define
stored procedures and aggregate functions that can be called from SQL
statements. These stored procedures are written in C by the application
developer, and made known to SQLite via function pointers. This is how the SQL
built in functions are implemented by SQLite, but this functionality is not
currently catered for in SQLite provides some other variations on the functions wrapped, and the reader is encouraged to study the SQLite documentation. Managed C++It is possible to compile SQLite and CppSQLite into a managed C++ program, It Just Works (IJW). You will need to set the CppSQLite.cpp file so that it does not use pre-compiled headers and also not to use Managed extensions, i.e. don't use /clr. There is a Managed C++ demo included with the CppSQLite downloads. SQLite Version 3At the time of writing, SQLite version 3 is in beta. See http://www.sqlite.org/
for further details. I have produced a port of There are a new set of classes with the prefix There is not support for UTF-16 initially, as it is not something I have
experience of, and wouldn't know how to test. This can be added later with
another set of classes, called for example Error messages are now returned by SQLite version 3 now has direct support for BLOB data, and therefore no need
to encode or decode it, and there would seem to be no job for
SQLite version 3 introduces changes to the data typing system used. See http://www.sqlite.org/datatype3.html . For this reason,
The demo programs have been changed slightly to demonstrate the new features,
and also to account for SQLite version 3's different locking behaviour. See http://www.sqlite.org/lockingv3.html. Note that
The SQLite version 3 is available as a separate download at the top of this article. Future WorkI may add support for the remaining SQLite features to
Cross Platform CapabilitySince version 1.2 of As mingw32 is based on GCC, there should be no major problems on
Linux/Unix, although the multi threaded demo program CppSQLiteDemoMT uses
the ContributionsThanks to fellow Code Project members for suggestions and buf fixes for
Conclusion
If nothing else, writing History for CppSQLite (Targets SQLite 2.8.n)
History for CppSQLite3 (Targets SQLite 3.n.n)
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||