开源SQL数据库SQlite3的移植和使用心得

介绍
在近几个月以来,你也许听过一个新的PHP数据库扩展类库SQLite.好多人认为SQLite是自有面包片以来最好的东东, 其提供了一个快速的访问平面文件数据库的接口.并且提供了访问大容量数据库的简洁的手段,但是并没有所意想的功能或者速度上的损失.在本文中,我们将探讨这个新的激动人心的扩展库,并且希望以此来验证其传说中的优势和好处.

啥是SQLite?
SQLite 是实现了SQL 92标准的一个大子集的嵌入式数据库.其以在一个库中组合了数据库引擎和接口,能将所有数据存储于单个文件中而著名.我觉得SQLite的功能一定程度上居于MySQL 和PostgreSQL之间.尽管如此,在性能上面,SQLite常常快2-3倍 (甚至更多).这利益于其高度调整了的内部架构,因为它除去了服务器端到客户端和客户端到服务器端的通信.所有这些都集在一个包中,也仅仅比MySQL的客户端的库稍微大一点.而令人印象深刻的特点是你可将你的整个数据库系统放在其中.利用非常高效的内存组织,SQLite只需在很小的内存中维护其很小的尺寸,远远比其它任何数据库系统都小.这些特点使得其成为在需要高效地应用数据库的任务中一个非常方便的工具.

它对我有啥用?
除了速度和效率,SQLite还有其它好多的优势使得其能成为许多任务中一个理想的解决方案.因为SQLite的数据库都是简单文件,因此无须一个管理队伍花时间来构造复杂的权限结构来保护用户的数据库.因为权限通过文件系统自动进行.这也同时意味着(数据库空间的大小只与环境有关,与本身无关)无段特殊的规则来了解用户磁盘空间.用户可以从创建他们想要的任意多的数据库和对其对这些数据库的绝对控制权而得到好处.
数据库就是一个文件的事实使用SQLite可以轻易地在服务器间移动.SQLite也除去了需要大量内存和其它系统资源的伺候进程.即使当数据库在大量地使用时也是如此.
开源的东西就是强大,可惜,都是老外的,我们正好相反,越好的东西(相对好些,其实问题一堆)越是藏着,掖着,然后自以为是的炫耀,收取版权费。发点牢骚!
同样源码全是C写的,移植非常方便,对于常见的平台,Windows, Unix, Mac, Linux(类Unix), OS2等,只要把相应的宏打开,直接编译即可。
但是我们的联芯平台用的是ThreadX,官方并没有现成的,因此要自己移植。

这里说下SQlite3的源码种类,有2大类,一类是amalgamation版本,这个版本只有一个.c,是所有源码的集合,我用的就是这个版本,省去了自己写makefile,移植更容易。另一类则是按目录分的源码包。

下面说下SQlite3 的移植:
原则上SQlite3只适用于多线程的平台,目前我还不清楚怎么往多任务但是却无线程概念的系统上移植。SQlite3默认用的是pthread库,因此对于有了pthread库的平台,在线程部分不需修改,否则,要么往自己的平台上移植pthread,要么就是把SQlite3中线程接口改成自己平台适用的,看自己需求了。
另一个要一直的则是文件系统接口,原则上,完全可以用标准C的fopen, fclose..函数集,但是那样的话,就不支持SQlite3的高级特性,比如数据库加锁啊,多线程的同步访问啊,但是对于嵌入式设备,这些功能往往也需要拥有,因此只要将文件系统改成标准C的接口,或者自己平台适用的接口。
因为我联芯的手机平台已经有了pthread库,因此,我只移植了文件系统部分(小小得意一把,其实本来移植工作是另外一位同事的,但因为我需求比较早,所以我花了一天时间移植了一个版本,而那个同事花了整整一个礼拜,他把线程都改了,佩服他的耐心啊!)

文件系统的移植方法简单说下:顺着sqlite3_os_init(void)修改就行了,将所有涉及文件操作的改下,没什么难度的,不过超过500 行。
接着说下适用方法,和XML Parser expat一样,也是基于回调的机制:
首先打开或者创建一个数据库:
sqlite3 *db;
sqlite3_open(DATABASE_NAME, &db);
函数成功返回0,如果数据库不存在,则是创建数据库,反之,则是打开
和内存的申请和释放一样,open后,一定要有成对的close
sqlite3_close(db);
函数成功返回0,
下面是常见的错误返回:

    #define SQLITE_OK           0   /* Successful result */
    /* beginning-of-error-codes */
    #define SQLITE_ERROR        1   /* SQL error or missing database */
    #define SQLITE_INTERNAL     2   /* Internal logic error in SQLite */
    #define SQLITE_PERM         3   /* Access permission denied */
    #define SQLITE_ABORT        4   /* Callback routine requested an abort */
    #define SQLITE_BUSY         5   /* The database file is locked */
    #define SQLITE_LOCKED       6   /* A table in the database is locked */
    #define SQLITE_NOMEM        7   /* A malloc() failed */
    #define SQLITE_READONLY     8   /* Attempt to write a readonly database */
    #define SQLITE_INTERRUPT    9   /* Operation terminated by sqlite3_interrupt()*/
    #define SQLITE_IOERR       10   /* Some kind of disk I/O error occurred */
    #define SQLITE_CORRUPT     11   /* The database disk image is malformed */
    #define SQLITE_NOTFOUND    12   /* NOT USED. Table or record not found */
    #define SQLITE_FULL        13   /* Insertion failed because database is full */
    #define SQLITE_CANTOPEN    14   /* Unable to open the database file */
    #define SQLITE_PROTOCOL    15   /* NOT USED. Database lock protocol error */
    #define SQLITE_EMPTY       16   /* Database is empty */
    #define SQLITE_SCHEMA      17   /* The database schema changed */
    #define SQLITE_TOOBIG      18   /* String or BLOB exceeds size limit */
    #define SQLITE_CONSTRAINT  19   /* Abort due to constraint violation */
    #define SQLITE_MISMATCH    20   /* Data type mismatch */
    #define SQLITE_MISUSE      21   /* Library used incorrectly */
    #define SQLITE_NOLFS       22   /* Uses OS features not supported on host */
    #define SQLITE_AUTH        23   /* Authorization denied */
    #define SQLITE_FORMAT      24   /* Auxiliary database format error */
    #define SQLITE_RANGE       25   /* 2nd parameter to sqlite3_bind out of range */
   #define SQLITE_NOTADB      26   /* File opened that is not a database file */
   #define SQLITE_ROW         100  /* sqlite3_step() has another row ready */
   #define SQLITE_DONE        101  /* sqlite3_step() has finished executing */

对于打开的数据,要执行操作,只要传入标准的SQL语句即可,对于二进制数据有特殊的方法,过会在讲,
主要调用

      SQLITE_API int sqlite3_exec(
                            sqlite3*,                                  /* An open database */
                            const char *sql,                           /* SQL to be evaluated */
                            int (*callback)(void*,int,char**,char**),  /* Callback function */
                            void *,                                    /* 1st argument to callback */
                            char **errmsg                              /* Error msg written here */
                            );

第一个参数不用说了,第二就是SQL语句,在之前可以用sptintf格式化一下,第三个则是个回调,第四个是回调函数的第一个参数,也就是 UserData,最后一个参数是指向一个返回错误信息的字串的指针。
没什么花头,说下回调函数:
原型是int (*callback)(void*,int,char**,char**)
第一个是UserData,就是sqlite3_exec()中的第四个参数,对于查询操作,比如你调用了sqlite3_exec(db, "SELECT * FROM TABLE_NAME"), 对应每条查询的记录,都会cakkback一次,对于第二个int参数,是指这条记录有多少字段,第三个参数,是指向一串字段值的指针数组,最后一个参数是字段的名字。
举个完整的简单例子,说下用法:
比如我要建张名为"Student”的表,表里有3个字段,"姓名", "年龄", "性别",
并且我要添加2条记录分别是:
"张诚", 24, '男'
"朱丽叶", 23, '女'
并且我要查询记录。
完整的程序如下:

      sqlite3    *db;
      int          iRet = 0;
      char       szExec[256] = {0};
      iRet = sqlite3_open(c:/student.db, &db);//在C盘根目录下创建名为student.db的数据库。
      sprintf(szExec, "CREATE TABLE STUDENT_TABLE(Name TEXT, Age INT, Sex TEXT)");
      iRet = sqlite3_exec(db, szExec, NULL, NULL, NULL);
      //在student.db数据库中创建名为STUDENT_TABLE的表,包含3个字段,分别是Name 字串型, Age INT型, Sex字串型
      sprintf(szExec, "INSERT INTO STUDENT_TABLE(Name, Age, Sex) VALUES('张诚', 24, '男')");//添加记录
      iRet = sqlite3_exec(db, szExec, NULL, NULL, NULL);
      sprintf(szExec, "INSERT INTO STUDENT_TABLE(Name, Age, Sex) VALUES('朱丽叶', 23, '女')");//添加记录
      iRet = sqlite3_exec(db, szExec, NULL, NULL, NULL);
      sprintf(szExec, "SELECT * FROM STUDENT_TABLE");
      iRet = sqlite3_exec(db, szExec, select_callback, NULL, NULL);
      //查询student.db数据库中名为STUDENT_TABLE的表中所有数据
      iRet = sqlite3_close(db);
      其中, select_callback定义如下:
      int select_callback(void *UserData, int argc, char **argv, char **azColName)
      {
            int   i = 0;
            int   iAge = 0;
            char szName[16] = {0};
            char szSex[8] = {0};
            for(i = 0; i < argc, i++)
            {
                  switch(i)
                  {
                        case 0://select name
                            strcpy(szName, argv[i]);
                            break;
                        case 1://select age
                            iAge = atoi(argv[i]);
                            break;
                        case 2://select sex
                            strcpy(szSex, argv[i]);
                            break;
                  }
            }
      }

对应于二进制数据,比如一张图片的存取,则有特殊的方法:

首先要定义一个sqlite3_stmt的指针,sqlite3_stmt *stmt;

打开一个数据库sqlite3_open(DATABASE_NAME, &db);

比如存储一张图片数据,图片名也为二进制:

sprintf(szExec, "UPDATE TABLE_NAME SET ImageData = ? WHERE ImageName = ?");

接着调用
int sqlite3_prepare(
sqlite3 *db, /* Database handle */
const char *zSql, /* SQL statement, UTF-8 encoded */
int nByte, /* Maximum length of zSql in bytes. */
sqlite3_stmt **ppStmt, /* OUT: Statement handle */
const char **pzTail /* OUT: Pointer to unused portion of zSql */
);

前2个参数不说了,第三个参数是SQL语句的长度,一般填-1即可,即SQlite3会将第二个参数作为以'\0'为结束的字串计算长度,第四个参数则是&stmt, 第五个参数则比较有意思,看到SQL语句的里面的问号没?这个参数代表问号的序列数,比如这里我要先提交图片数据,则此参数为0,如果提交文件名,则为 1.

然后调用SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));绑定二进制数据,第二个参数是要绑定的二进制数据的Buffer,第三个参数是数据的长度,最后一个一般为NULL,此时,包含数据的SQL已经被封装在stmt结构中。
最后要使数据库执行更新操作,调用int sqlite3_step(sqlite3_stmt*);
对于连续操作,需要在执行一次操作后,执行int sqlite3_reset(sqlite3_stmt *pStmt);
全部操作完后,执行int sqlite3_finalize(sqlite3_stmt *pStmt);

发表评论