CppSQLite - SQLite 的 C++ 包装器






4.93/5 (226投票s)
SQLite 嵌入式数据库库的 C++ 封装。
- 下载 CppSQLite 演示项目、源代码和 sqlite.dll,适用于 SQLite 2.8.15 - 156 KB
- 下载 CppSQLite 3 演示项目、源代码和 sqlite3.dll,适用于 SQLite 3.0.8 - 171 KB
- 下载 CppSQLite 3.2 演示项目、源代码和 sqlite3.dll,适用于 SQLite 3.4.0 - 252 KB
引言
本文档描述了 CppSQLite
,它是对公共领域的 SQLite 数据库库的一个非常轻量级的 C++ 封装。
本文档提供了如何将应用程序链接到 SQLite 的描述,然后呈现了一个使用 CppSQLite
的示例程序,最后对 CppSQLite
类进行了文档说明。
为了铺垫,这里引用 SQLite 作者的一段话……
SQLite 是一个 C 库,它实现了一个可嵌入的 SQL 数据库引擎。链接 SQLite 库的程序可以在不运行单独的 RDBMS 进程的情况下访问 SQL 数据库。发行版带有一个独立的命令行访问程序(SQLite),可用于管理 SQLite 数据库,并作为如何使用 SQLite 库的示例。
SQLite 不是用于连接大型数据库服务器的客户端库。SQLite 就是服务器。SQLite 库直接读写磁盘上的数据库文件。
背景
我一直在寻找简单而强大的软件开发工具和想法,SQLite 绝对属于这一类。事实上,“Lite”这个名字有点误导,因为它实现了 SQL 标准的大部分子集,包括事务,而且当像 PHP 这样的项目开始将其作为标准捆绑而不是 MySQL 时,你就不得不关注它了。
我觉得编写一个围绕 C 接口的轻量级封装,使其对 C++ 更友好,会很有趣。SQLite 网站上已经列出了不少 C++ 封装,但有一个是商业的,另一个看起来有点复杂,还有一个是专门针对 wxWidgets 框架的。毕竟,SQLite 的作者似乎费尽心思保持简单,所以我也觉得它的 C++ 封装也应该保持简单。
使用 SQLite
在 Windows 平台上,SQLite 提供两个包:已编译的 DLL 和源代码。即使你只想使用 DLL,仍然需要获取源代码,因为它包含了必需的头文件。
如果需要,可以将 SQLite 源代码编译成库(*.lib)文件,以便与你的应用程序进行静态链接,但这不在此文章的讨论范围之内。编译说明可以在 SQLite 网站上找到。
动态链接仍然需要为应用程序链接构建一个 *.lib 文件。这可以使用 Microsoft 的 LIB
命令完成。在我的系统上,它位于 D:\Program Files\Microsoft Visual Studio\VC98\Bin\lib.exe。
解压包含 sqlite.dll 和 sqlite.def 的 sqlite.zip,然后执行以下命令生成 lib 文件。
c:\>lib /def:sqlite.def
sqlite.h 需要在编译时对你的应用程序可见,*sqlite.lib* 也是如此。
sqlite.dll 需要在运行时对你的应用程序可用。
CppSQLite 演示代码
以下代码演示了如何通过 CppSQLite
使用 SQLite 的主要功能,并附有行内注释。
#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 类
定义了以下简单的类来封装 SQLite 的功能。
所有 CppSQLite
类都包含在 2 个文件中:CppSQLite.h 和 CppSQLite.cpp,你需要将它们添加到你的应用程序中。
CppSQLiteException
封装了 SQLite 的错误代码和消息。这里没有什么复杂的地方,如果需要,这个类可以很容易地集成到现有的异常层次结构中。
SQLite 返回的错误消息需要由程序员使用 sqlite_freemem()
释放,而这个类承担了这个责任。请注意,对于 CppSQLite
生成的错误消息,我们不希望释放内存,因此有一个可选的最后一个参数,用于指定 CppSQLiteException
是否释放内存。
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;
};
CppSQLiteDB
封装了一个 SQLite 数据库文件。
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;
};
open()
和 close()
方法不言自明。SQLite 在 sqlite_open()
中提供了 mode
参数,但据文档说明该参数无效,因此 CppSQLite
中未提供此参数。
execDML()
用于执行数据操作语言 (DML) 命令,如 create
/drop
/insert
/update
/delete
语句。它返回受影响的行数。可以用分号分隔的多个 SQL 语句一次性提交和执行。注意:CppSQLite 返回受影响行数的方式存在潜在问题。如果还有其他未完成()的操作正在进行,受影响的行数将是累积的,包括之前语句的行数。因此,如果此功能对您很重要,您必须确保任何尚未析构的 CppSQLiteQuery
和 CppSQLiteStatement
对象在调用 execDML()
之前已调用 finalize()
。
execQuery()
用于执行查询。CppSQLiteQuery
对象按值返回,因为这使程序员不必删除它。
execScalar()
是我从 ADO.NET 获得的灵感。当需要运行简单的聚合函数时,这是一个快捷方式,例如,“select count(*) from emp
” 或 “select max(empno) from emp
”。它返回查询结果的第一行的第一个字段的值。其他列和行将被忽略。
getTable()
允许 SQLite 功能一次性获取整个表的内容,而不是像查询那样逐行获取。实际上,可以通过指定带有 where
子句的查询来获取表行的子集,但整个结果集是一次性返回的。同样,为了方便起见,CppSQLiteTable
对象按值返回。
compileStatement()
支持 SQLite 的实验性预编译 SQL 功能。请参阅下面的 CppSQLiteStatement
。
SQLite 是无类型的,这意味着所有字段都存储为字符串。唯一的例外是 INTEGER PRIMARY KEY
类型,它允许一个自动增量字段,类似于 SQL Server 的 identity 列。lastRowId()
函数用于确定最后插入行的主键值。
interrupt()
在多线程时很有用,允许一个线程中断另一个线程正在进行的某个操作。
setBusyTimeout()
在多线程时也很有用,它允许程序员指定 SQLite 在另一个线程锁定数据库时等待多久后才返回 SQLITE_BUSY
。默认值为 60 秒,在打开数据库时设置。
拷贝构造函数和 operator=()
被设为私有,因为复制 CppSQLiteDB
对象没有意义。
最后,静态方法 SQLiteVersion()
返回底层 SQLite DLL 的版本号。
CppSQLiteQuery
封装了 SQLite 查询结果集。
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;
};
nextRow()
和 eof()
允许遍历查询结果。
numFields()
、fieldValue()
、fieldName()
、fieldType()
和 fieldIsNull()
允许程序员确定字段的数量、它们的名称、值、类型以及它们是否包含 SQL NULL
。有重载版本允许通过索引或名称指定所需的字段。
getIntField()、getFloatField()
和 getStringField()
提供了一种更易于编程的方式来获取字段值,它们永远不会为 SQL NULL
返回 NULL 指针,并且有一个默认的第二个参数允许程序员指定要返回的值。
无法向后遍历结果。原因是 CppSQLite
是一个轻量级封装,不缓存任何返回的行数据。如果需要,应使用 CppSQLiteDB::getTable()
,或者应用程序可以继承此类。
finalize()
释放与查询关联的内存,但析构函数会自动调用它。
CppSQLiteTable
SQLite 提供了一种方法来一次性获取表的全部内容,CppSQLiteTable
封装了此功能。
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;
};
setRow()
提供了一种随机访问的方法来在行之间移动,并可与 numRows()
结合使用来遍历表。此设计决策是为了简化,因为它遵循与 CppSQLiteQuery
相同的模型,需要 bof()
、eof()
、first()
、last()
、next()
和 prev()
等函数。
numFields()
、fieldValue()
、fieldName()
、fieldIsNull()
、getIntField()
、getFloatField()
、getStringField()
、close()
和 operator=()
提供与 CppSQLiteQuery
相同的功能。
CppSQLiteBuffer
封装了 SQLite 的“sprintf
”功能。
SQLite 提供了一个 sqlite_mprintf()
函数,它类似于 C 运行时库的 sprintf()
,但由于 sqlite_mprintf()
使用 malloc
分配足够的内存,因此不会有缓冲区溢出的风险。与 sprintf()
的另一个好处是 %Q
标签,它类似于 %s
,但会处理撇号,使其不会破坏正在构建的 SQL 字符串,并且还会将 NULL
指针转换为 SQL NULL
值。
class CppSQLiteBuffer
{
public:
CppSQLiteBuffer();
~CppSQLiteBuffer();
const char* format(const char* szFormat, ...);
operator const char*() { return mpBuf; }
void clear();
private:
char* mpBuf;
};
operator const char*()
允许程序员将此对象的实例传递给 CppSQLiteDB
上定义的函数。
CppSQLiteBinary
由于 SQLite 将所有数据存储为 NULL
终止字符串,因此无法存储包含嵌入式 NULL
的二进制数据。SQLite 提供了两个函数 sqlite_encode_binary()
和 sqlite_decode_binary()
,可用于允许存储和检索二进制数据。CppSQLiteBinary
封装了这两个函数。
这两个函数目前不作为预编译 DLL 的一部分提供,因此我将 SQLite 的 encode.c 文件内容复制到了 CppSQLite.cpp 文件中。如果将来在 DLL 中提供这些函数,它们可以很容易地从 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;
};
CppSQLiteBinary
可以通过 setEncoded()
和 setBinary()
函数接受编码或二进制形式的数据。无论使用哪种方式,总是会分配足够的内存来存储编码版本,因为编码版本通常更长,因为 null
和单引号必须被转义。
数据通过 getEncoded()
和 getBinary()
函数检索。根据数据在类中的当前形式,可能需要进行转换。
getBinaryLength()
返回存储的二进制数据的长度,如果需要,它还会将当前格式从编码转换为二进制。
allocBuffer()
可用于防止数据通过临时缓冲区进行传递,就像本文档开头示例代码中的情况一样。此函数可用于以下示例,其中数据直接从文件读入 CppSQLiteBinary
对象。
int f = open(gszJpgFile, O_RDONLY|O_BINARY);
int nFileLen = filelength(f);
read(f, blob.allocBuffer(nFileLen), nFileLen);
CppSQLiteStatement
SQLite 提供了一些实验性的功能来处理预编译 SQL。当重复执行相同的 SQL 语句并使用不同的值时,只需编译一次 SQL 语句,然后多次执行,每次使用不同的值,就可以获得显著的性能提升。CppSQLiteStatement
封装了此功能。
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;
};
通过调用 CppSQLiteDB::compileStatement()
并提供包含占位符的 SQL 语句来获取 CppSQLiteStatement
对象,如下所示:
CppSQLiteStatement stmt = db.compileStatement("insert into emp values (?, ?);");
stmt.bind(1, 1);
stmt.bind(2, "Emp Name");
stmt.execDML();
stmt.reset();
然后使用 CppSQLiteStatement::bind()
方法设置占位符的值,之后根据需要调用 execDML()
或 execQuery()
。
在程序员完成 execDML()
或 execQuery()
的结果处理后,可以调用 reset()
方法将语句恢复到编译状态。然后可以再次使用 CppSQLiteStatement::bind()
方法,然后调用 execDML()
或 execQuery()
。如 CppSQLiteDemo 程序所示,循环中的典型用法。
多线程
SQLite 在 Windows 上默认编译为线程安全,CppSQLite
利用了一些 SQLite 功能来帮助多线程使用。本文档附带的源代码中包含第二个演示程序,名为 CppSQLiteDemoMT,它演示了这些功能。
每个希望同时使用同一个数据库文件的 CppSQLite 的线程都必须拥有自己的 CppSQLiteDB
对象,并调用 open()
。换句话说,超过 1 个线程同时调用 CppSQLiteDB
对象是错误的。唯一的例外是 CppSQLiteDB::interrupt()
,它可以从一个线程用于中断另一个线程的工作。
为了多线程使用,对 CppSQLite
的另一个改动是利用 sqlite_busy_timeout()
函数,该函数使 SQLite 在返回 SQLITE_BUSY
之前最多等待指定的毫秒数。默认情况下,CppSQLite
将此值设置为 60,000(60 秒),但可以根据需要使用 CppSQLiteDB::setBusyTimeout()
进行更改。CppSQLiteDemoMT 程序中提供了各种执行此操作的示例。
尚未封装的 SQLite 功能
SQLite 提供了一种机制,允许应用程序开发人员定义可以从 SQL 语句调用的存储过程和聚合函数。这些存储过程由应用程序开发人员用 C 编写,并通过函数指针告知 SQLite。这就是 SQLite 实现内置 SQL 函数的方式,但 CppSQLite
目前不支持此功能。
SQLite 提供了其他一些对已封装函数的变种,鼓励读者研究 SQLite 文档。
托管 C++
可以将 SQLite 和 CppSQLite 编译到托管 C++ 程序中,它“开箱即用”(IJW)。你需要将 CppSQLite.cpp 文件设置为不使用预编译头文件,并且也不使用托管扩展,即不要使用 /clr。
CppSQLite 下载中包含了一个托管 C++ 示例。
SQLite 版本 3
在撰写本文时,SQLite 版本 3 处于 Beta 阶段。有关更多详细信息,请参阅 http://www.sqlite.org/。我已将 CppSQLite
移植到 SQLite 版本 3,以下说明了差异。
有一组新的类,前缀为 CppSQLite3
,例如 CppSQLite3Exception
。这允许程序链接两个版本的 CppSQLite
,就像链接两个版本的 SQLite 本身一样。
起初不支持 UTF-16,因为我没有这方面的经验,也不知道如何测试。稍后可以添加另一组类,例如 CppSQLite3Exception16
等。请注意,一些 sqlite3 的东西,如 sqlite3_exec()
和 sqlite3_get_table()
似乎没有 UTF-16 版本,还有 sqlite3_vmprintf()
,它被 CppSQLiteBuffer
使用。
错误消息现在由 sqlite3_errmsg()
返回,无需释放。为了保持 CppSQLite
和 CppSQLite3
之间的一致性,抛出带有从 SQLite 版本 3 返回的消息的异常的代码已被修改,使其将 DONT_DELETE_MSG 作为最后一个参数传递给 CppSQLite3Exception
。例外情况是 sqlite3_exec() 和 sqlite3_get_table()
返回的消息。
SQLite 版本 3 现在直接支持 BLOB 数据,因此无需进行编码或解码,CppSQLiteBinary
似乎也没有存在的必要。然而,SQLite 版本 3 的更改意味着处理 BLOB 数据的唯一方法似乎是使用准备好的语句(CppSQLiteStatement
)。这本身不是问题,但到目前为止,CppSQLiteBinary
允许在调用 CppSQLiteDB::execQuery()
、CppSQLiteDB::execDML()
以及从 CppSQLiteDB::getTable()
返回的数据中使用(编码的)二进制数据。
sqlite_encode_binary()
和 sqlite_decode_binary()
仍然包含在 SQLite 版本 3 的源代码分发版中,尽管尚不清楚这是否是一个错误,因为它们没有 sqlite3 前缀,也没有从 DLL 导出。CppSQLite3
复制了这两个函数的源代码。这曾是 CppSQlite
直到版本 1.3 的情况,因为直到 SQLite 版本 2.8.15,它们都没有从 DLL 导出。CppSQLite3Binary
是 CppSQLiteBinary
的精确副本,并捆绑了 sqlite_encode_binary()
和 sqlite_decode_binary()
的源代码。这将允许在 CppSQLite
和 CppSQLite3
之间轻松移植。希望使用 sqlite3 BLOB 及其更小存储空间的程序将不需要使用 CppSQLite3Binary
,并且无论如何都需要重写。
SQLite 版本 3 引入了对数据类型系统的更改。请参阅 http://www.sqlite.org/datatype3.html。因此,CppSQLiteQuery::FieldType()
已被两个函数替换:CppSQLiteQuery::FieldDeclType()
返回列的声明数据类型作为字符串,以及 CppSQLiteQuery::FieldDataType()
返回当前行存储的数据的实际类型,表示为 SQLite 版本 3 的 #defined 值之一。
演示程序已稍作修改以演示新功能,并考虑到 SQLite 版本 3 的不同锁定行为。请参阅 http://www.sqlite.org/lockingv3.html。请注意,SQLite
版本 3.0.5 引入了一个编译时选项,该选项改变了锁定行为,更多详细信息请参阅 http://www.sqlite.org/changes.html。
SQLite 版本 3 可在此文章顶部单独下载。
未来工作
我可能会为 CppSQLite
添加对剩余 SQLite 功能的支持。目前,这意味着存储过程和聚合函数。
跨平台能力
自 CppSQLite
版本 1.2 起,我一直努力避免使用任何特定于 Microsoft 的内容,并且已成功在 mingw32 和 Visual C++ 上编译并运行了演示程序。
由于 mingw32 基于 GCC,因此在 Linux/Unix 上应该没有大的问题,尽管多线程演示程序 CppSQLiteDemoMT 使用了 _beginthread()
调用,这显然行不通。使用 pthreads 等技术,这可能很容易修复。
贡献
感谢 Code Project 的其他成员为 CppSQLite
提出的建议和错误修复,也感谢 Mateusz Loskot 的审阅。
结论
CppSQLite
使在 C++ 程序中使用 SQLite 更加容易,同时又不会显著减少比纯 C 接口少的功能或效率。
至少,编写 CppSQLite
为作者提供了对 SQLite 的强大和简单性的深入了解。希望本文的读者也能从中受益。
CppSQLite(目标为 SQLite 2.8.n)历史记录
- 1.0 - 2004 年 3 月 3 日 - 初始版本。
- 1.1 - 2004 年 3 月 10 日
- 将
CppSQLiteException::errorMess()
重命名为CppSQLiteException::errorMessage()
。 - 为
CppSQLiteException()
添加了第二个构造函数。 - 现在在
CppSQLiteException
中将错误代码解码为字符串。 - 在
sqlite_step()
出现问题后,立即调用sqlite_finalize()
以获取错误详情。 - 添加了
CppSQLiteBinary
类。 - 1.2 - 2004 年 4 月 2 日 - 未发布。
- 更新了文章。
- 移除了对 Microsoft 特定扩展的使用(我希望如此)
- 检查
NULL
指针 - 更新以支持 SQLite 2.8.13
- 利用
sqlite_busy_timeout()
和sqlite_interrupt()
来帮助多线程使用 - 为多线程使用添加了第二个演示程序
- 添加了对预编译 SQL 语句的支持
- 添加了从
CppSQLiteQuery
确定列类型的功能 - 添加了
CppSQLiteDB::execScalar()
- 1.2.1 - 2004 年 4 月 15 日
- 根据审阅更新了文章
- 根据审阅,使用 C++ 标准头文件而不是 C 标准头文件
- 1.3 - 2004 年 5 月 21 日
- 在源文件中添加了“BSD 风格”许可证声明
- 修复了 bind() 的错误
- 添加了
getIntField()
、getStringField()
、getFloatField()
- 添加了通过名称访问字段的重载函数
CppSQLiteDB::ExecDML()
使用sqlite_exec()
实现,因此可以一次执行多个语句。- 在文章中添加了关于
CppSQLiteDB::execDML()
返回值潜在问题的注释 - 添加了托管 C++ 示例程序
- 1.4 - 2004 年 8 月 30 日
- 升级到 SQLite 2.8.15
- 删除了 sqlite_encode_binary() 和
sqlite_decode_binary()
的源代码,因为它们现在已从 SQLite DLL 导出 - 添加了关于托管 C++ 的文章部分
CppSQLite3(目标为 SQLite 3.n.n)历史记录
- 3.0 - 2004 年 8 月 30 日
- 与 SQLite 版本 3.0.6 兼容的初始版本
- 3.1 - 2004 年 10 月 26 日
- 升级到 vSQLite 3.0.8
- 添加了
CppSQLiteDB3::tableExists()
函数 - 使用 SQLite3 函数而不是
atoi()
、atof()
等实现了get*Field
- 3.2 - 2011 年 6 月 24 日
- 捆绑了 SQLite3 版本 3.4.0
CppSQLite3DB::SQLiteHeaderVersion()
、CppSQLite3DB::SQLiteLibraryVersion()
、CppSQLite3DB::SQLiteLibraryVersionNumber()
- 修复了
execScalar
以处理NULL
结果 - 在
CppSQLite3Statement
中添加了Int64
函数 - 添加了
CppSQLite3DB::IsAutoCommitOn()
,可用于测试事务是否处于活动状态 - 在错误时从
CppSQLite3DB::close()
抛出异常 - 在
CppSQLite3DB::~CppSQLite3DB()
中捕获上述异常 - 表中的缓冲区大小增加到 256
sqlite3_prepare
被替换为sqlite3_prepare_v2
- Dave Rollins 提供的
CppSQLite3DB::compile()
的修复 - 根据 Dave Rollins 的建议,按名称绑定参数