65.9K
CodeProject 正在变化。 阅读更多。
Home

CppSQLite - SQLite 的 C++ 包装器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (226投票s)

2004年3月5日

CPOL

16分钟阅读

viewsIcon

2811922

downloadIcon

51896

SQLite 嵌入式数据库库的 C++ 封装。

引言

本文档描述了 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.dllsqlite.defsqlite.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.hCppSQLite.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 返回受影响行数的方式存在潜在问题。如果还有其他未完成()的操作正在进行,受影响的行数将是累积的,包括之前语句的行数。因此,如果此功能对您很重要,您必须确保任何尚未析构的 CppSQLiteQueryCppSQLiteStatement 对象在调用 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() 返回,无需释放。为了保持 CppSQLiteCppSQLite3 之间的一致性,抛出带有从 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 导出。CppSQLite3BinaryCppSQLiteBinary 的精确副本,并捆绑了 sqlite_encode_binary()sqlite_decode_binary() 的源代码。这将允许在 CppSQLiteCppSQLite3 之间轻松移植。希望使用 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 的建议,按名称绑定参数
© . All rights reserved.