文档库 最新最全的文档下载
当前位置:文档库 › sqlite3api编程手册

sqlite3api编程手册

sqlite3api编程手册
sqlite3api编程手册

SQLite3API编程手册

前序: (1)

一、版本 (1)

二、基本编译 (2)

三、 SQLITE操作入门 (2)

(1)基本流程 (2)

(2) SQL语句操作 (4)

(3)操作二进制 (8)

(4)事务处理 (10)

四、给数据库加密 (10)

五、后记 (25)

前序:

Sqlite3 的确很好用。小巧、速度快。但是因为非微软的产品,帮助文档总觉得不够。这些天再次研究它,又有一些收获,这里把我对 sqlite3 的研究列出来,以备忘记。

这里要注明,我是一个跨平台专注者,并不喜欢只用 windows 平台。我以前的工作就是为 unix 平台写代码。下面我所写的东西,虽然没有验证,但是我已尽量不使用任何 windows 的东西,只使用标准 C 或标准C++。但是,我没有尝试过在别的系统、别的编译器下编译,因此下面的叙述如果不正确,则留待以后修改。

下面我的代码仍然用 VC 编写,因为我觉得VC是一个很不错的IDE,可以加快代码编写速度(例如配合 Vassist )。下面我所说的编译环境,是VC2003。如果读者觉得自己习惯于 unix 下用 vi 编写代码速度较快,可以不用管我的说明,只需要符合自己习惯即可,因为我用的是标准 C 或 C++ 。不会给任何人带来不便。

一、版本

从??网站可下载到最新的 sqlite 代码和编译版本。我写此文章时,最新代码是版本。

很久没有去下载 sqlite 新代码,因此也不知道 sqlite 变化这么大。以前很多文件,现在全部合并成一个文件。如果单独用此文件,是挺好的,省去拷贝一堆文件还担心有没有遗漏。但是也带来一个问题:此文件太大,快接近7万行代码,VC开它整个机器都慢下来了。如果不需要改它代码,也就不需要打开文件,机器不会慢。但是,下面我要写通过修改 sqlite 代码完成加密功能,那时候就比较痛苦了。如果个人水平较高,建议用些简单的编辑器来编辑,例如UltraEdit 或 Notepad 。速度会快很多。

二、基本编译

这个不想多说了,在 VC 里新建 dos 控制台空白工程,把和添加到工程,再新建一个文件。在里面写:

extern "C"

{

#include "./"

};

int main( int , char** )

{

return 0;

}

为什么要extern “C” ?如果问这个问题,我不想说太多,这是C++的基础。要在 C++ 里使用一段 C 的代码,必须要用extern “C” 括起来。C++跟 C虽然语法上有重叠,但是它们是两个不同的东西,内存里的布局是完全不同的,在C++编译器里不用extern “C”括起C代码,会导致编译器不知道该如何为 C 代码描述内存布局。

可能在里人家已经把整段代码都extern “C” 括起来了,但是你遇到一个 .c 文件就自觉的再括一次,也没什么不好。

基本工程就这样建立起来了。编译,可以通过。但是有一堆的 warning。可以不管它。

三、 SQLITE操作入门

sqlite提供的是一些C函数接口,你可以用这些函数操作数据库。通过使用这些接口,传递一些标准 sql 语句(以 char * 类型)给 sqlite 函数,sqlite 就会为你操作数据库。

sqlite 跟MS的access一样是文件型数据库,就是说,一个数据库就是一个文件,此数据库里可以建立很多的表,可以建立索引、触发器等等,但是,它实际上得到的就是一个文件。备份这个文件就备份了整个数据库。

sqlite 不需要任何数据库引擎,这意味着如果你需要 sqlite 来保存一些用户数据,甚至都不需要安装数据库(如果你做个小软件还要求人家必须装了sqlserver 才能运行,那也太黑心了)。

下面开始介绍数据库基本操作。

(1)基本流程

关键数据结构

sqlite 里最常用到的是 sqlite3 * 类型。从数据库打开开始,sqlite就要为这个类型准备好内存,直到数据库关闭,整个过程都需要用到这个类型。当数据库打开时开始,这个类型的变量就代表了你要操作的数据库。下面再详细介绍。打开数据库

int sqlite3_open( 文件名, sqlite3 ** );

用这个函数开始数据库操作。

需要传入两个参数,一是数据库文件名,比如:,如果此文件不存在,sqlite 会自动建立它。如果它存在,就尝试把它当数据库文件来打开。

sqlite3 ** 参数即前面提到的关键数据结构。这个结构底层细节如何,你不要关它。

函数返回值表示操作是否正确,如果是 SQLITE_OK 则表示操作正常。相关的返回值sqlite定义了一些宏。具体这些宏的含义可以参考文件。里面有详细定义(顺便说一下,sqlite3 的代码注释率自称是非常高的,实际上也的确很高。只要你会看英文,sqlite 可以让你学到不少东西)。

下面介绍关闭数据库后,再给一段参考代码。

关闭数据库

int sqlite3_close(sqlite3 *);

前面如果用 sqlite3_open 开启了一个数据库,结尾时不要忘了用这个函数关闭数据库。

下面给段简单的代码:

extern "C"

{

#include "./"

};

int main( int , char** )

{

sqlite3 * db = NULL; int i;

printf( “记录包含 %d 个字段/n”, n_column );

for( i = 0 ; i < n_column; i ++ )

{

printf( “字段名:%s ?> 字段值:%s/n”, column_name[i], column_value[i] );

}

printf( “------------------/n“ );

return 0;

}

int main( int , char ** )

{

sqlite3 * db;

int result;

char * errmsg = NULL;

result = sqlite3_open( “, &db );

if( result != SQLITE_OK )

{

"

#include ""

int My_Encrypt_Func( unsigned char * pData, unsigned int data_len, const char * key, unsigned int len_of_key )

{

return 0;

}

int My_DeEncrypt_Func( unsigned char * pData, unsigned int data_len, const char * key, unsigned int

len_of_key )

{

return 0;

}

这个文件很容易看,就两函数,一个加密一个解密。传进来的参数分别是待处理的数据、数据长度、密钥、密钥长度。处理时直接把结果作用于 pData 指针指向的内容。

你需要定义自己的加解密过程,就改动这两个函数,其它部分不用动。扩展起来很简单。

这里有个特点,data_len 一般总是 1024 字节。正因为如此,你可以在你的算法里使用一些特定长度的加密算法,比如AES要求被加密数据一定是128位(16字节)长。这个1024不是碰巧,而是 Sqlite 的页定义是1024字节,在文件里有定义:

# define SQLITE_DEFAULT_PAGE_SIZE 1024

你可以改动这个值,不过还是建议没有必要不要去改它。

上面写了两个扩展函数,如何把扩展函数跟 Sqlite 挂接起来,这个过程说起来比较麻烦。我直接贴代码。

分3个步骤。

首先,在文件顶部,添加下面内容:

#ifdef SQLITE_HAS_CODEC

#include "./"

void sqlite3pager_free_codecarg(void *pArg);

#endif

这个函数之所以要在开头声明,是因为下面在里面某些函数里要插入这个函数调用。所以要提前声明。

其次,在文件里搜索“sqlite3PagerClose”函数,要找到它的实现代码(而不是声明代码)。

实现代码里一开始是:

#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT

ThreadData *pTsd = sqlite3ThreadData();

assert( pPager );

assert( pTsd && pTsd->nAlloc );

#endif

需要在这部分后面紧接着插入:

#ifdef SQLITE_HAS_CODEC

sqlite3pager_free_codecarg(pPager->pCodecArg);

#endif

这里要注意,sqlite3PagerClose 函数大概也是版本左右才改名的,以前版本里是叫“sqlite3pager_close”。因此你在老版本sqlite代码里搜索“sqlite3PagerClose”是搜不到的。

类似的还有“sqlite3pager_get”、“sqlite3pager_unref”、“sqlite3pager_write”、“sqlite3pager_pagecount”等都是老版本函数,它们在文件里定义。新版本对应函数是在里定义(因为都合并到和两文件了)。所以,如果你在使用老版本的sqlite,先看看文件,这些函数不是消失了,也不是新蹦出来的,而是老版本函数改名得到的。?

最后,往文件下找。找到最后一行:

在这一行后面,接上本文最下面的代码段。

这些代码很长,我不再解释,直接接上去就得了。

唯一要提的是 DeriveKey 函数。这个函数是对密钥的扩展。比如,你要求密钥是128位,即是16字节,但是如果用户只输入 1个字节呢?2个字节呢?或输入50个字节呢?你得对密钥进行扩展,使之符合16字节的要求。

DeriveKey 函数就是做这个扩展的。有人把接收到的密钥求md5,这也是一个办法,因为md5运算结果固定16字节,不论你有多少字符,最后就是16字节。这是md5算法的特点。但是我不想用md5,因为还得为它添加包含一些 md5 的.c 或.cpp文件。我不想这么做。我自己写了一个算法来扩展密钥,很简单的算法。当然,你也可以使用你的扩展方法,也而可以使用 md5 算法。只要修改 DeriveKey 函数就可以了。

在 DeriveKey 函数里,只管申请空间构造所需要的密钥,不需要释放,因为在另一个函数里有释放过程,而那个函数会在数据库关闭时被调用。参考我的 DeriveKey 函数来申请内存。

这里我给出我已经修改好的和文件。

如果太懒,就直接使用这两个文件,编译肯定能通过,运行也正常。当然,你必须按我前面提的,新建和文件,而且函数要按我前面定义的要求来做。

加密使用方法:

现在,你代码已经有了加密功能。

你要把加密功能给用上,除了改文件、给你工程添加 SQLITE_HAS_CODEC 宏,还得修改你的数据库调用函数。

前面提到过,要开始一个数据库操作,必须先 sqlite3_open 。

加解密过程就在 sqlite3_open 后面操作。

假设你已经 sqlite3_open 成功了,紧接着写下面的代码:

int i;

函数会申请缓冲区.

static LPCryptBlock CreateCryptBlock(unsigned char* hKey, Pager *pager, LPCryptBlock pExisting);

static void DestroyCryptBlock(LPCryptBlock pBlock);

static void * sqlite3pager_get_codecarg(Pager *pPager);

void sqlite3pager_set_codec(Pager *pPager,void *(*xCodec)(void*,void*,Pgno,int),void *pCodecArg );

?

果改变,就需要调整.

if (nMode != 2)

{

PgHdr *pageHeader;

pageHeader = DATA_TO_PGHDR(data);

if (pageHeader->pPager->pageSize != pBlock->PageSize)

{

CreateCryptBlock(0, pageHeader->pPager, pBlock);

}

}

?

switch(nMode)

{

case 0:

static void DestroyCryptBlock(LPCryptBlock pBlock)

{

if (pBlock->ReadKey){

sqliteFree(pBlock->ReadKey);

}

if (pBlock->WriteKey && pBlock->WriteKey != pBlock->ReadKey){

sqliteFree(pBlock->WriteKey);

}

if(pBlock->Data){

sqliteFree(pBlock->Data);

}

sqliteFree(pBlock);

}

static void * sqlite3pager_get_codecarg(Pager *pPager)

{

return (pPager->xCodec) ? pPager->pCodecArg: NULL;

}

函数会申请缓冲区.

static LPCryptBlock CreateCryptBlock(unsigned char* hKey, Pager *pager, LPCryptBlock pExisting) {

LPCryptBlock pBlock;

?

if (!pExisting) if (!pKey || !nKeyLen)

{

if (!nDb)

{

return SQLITE_OK; }

else {

Bt));

if (!pBlock) return SQLITE_OK; {

hKey = DeriveKey(pKey, nKeyLen);

}

if (hKey)

{

LPCryptBlock pBlock = CreateCryptBlock(hKey, sqlite3BtreePager(db->aDb[nDb].pBt), NULL); sqlite3pager_set_codec(sqlite3BtreePager(db->aDb[nDb].pBt), sqlite3Codec, pBlock);

rc = SQLITE_OK;

}

return rc;

}

int __stdcall sqlite3_rekey_interop(sqlite3 *db, const void *pKey, int nKeySize)

{

Btree *pbt = db->aDb[0].pBt;

Pager *p = sqlite3BtreePager(pbt);

LPCryptBlock pBlock = (LPCryptBlock)sqlite3pager_get_codecarg(p);

unsigned char * hKey = DeriveKey(pKey, nKeySize);

int rc = SQLITE_ERROR;

if (!pBlock && !hKey) return SQLITE_OK;

if (!pBlock) //加密一个未加密的数据库

{

pBlock = CreateCryptBlock(hKey, p, NULL);

pBlock->ReadKey = 0; // 原始数据库未加密

sqlite3pager_set_codec(sqlite3BtreePager(pbt), sqlite3Codec, pBlock);

}

else // 改变已加密数据库的写密钥

{

pBlock->WriteKey = hKey;

}

// 开始一个事务

rc = sqlite3BtreeBeginTrans(pbt, 1);

if (!rc)

// 用新密钥重写所有的页到数据库。

Pgno nPage = sqlite3PagerPagecount(p);

Pgno nSkip = PAGER_MJ_PGNO(p);

void *pPage;

Pgno n;

for(n = 1; rc == SQLITE_OK && n <= nPage; n )

{

if (n == nSkip) continue;

rc = sqlite3PagerGet(p, n, &pPage);

if(!rc)

{

rc = sqlite3PagerWrite(pPage);

sqlite3PagerUnref(pPage);

}

}

}

// 如果成功,提交事务。

if (!rc)

{

rc = sqlite3BtreeCommit(pbt);

}

// 如果失败,回滚。

if (rc)

{

sqlite3BtreeRollback(pbt);

}

?

?

// 如果成功,销毁先前的读密钥。并使读密钥等于当前的写密钥。if (!rc)

{

if (pBlock->ReadKey)

{

sqliteFree(pBlock->ReadKey);

}

pBlock->ReadKey = pBlock->WriteKey;

}

else// 如果失败,销毁当前的写密钥,并恢复为当前的读密钥。

{

if (pBlock->WriteKey)

{

sqliteFree(pBlock->WriteKey);

}

pBlock->WriteKey = pBlock->ReadKey;

}

?

// 如果读密钥和写密钥皆为空,就不需要再对页进行编解码。

// 销毁加密块并移除页的编解码器

if (!pBlock->ReadKey && !pBlock->WriteKey)

{

sqlite3pager_set_codec(p, NULL, NULL);

DestroyCryptBlock(pBlock);

}

return rc;

}

int __stdcall sqlite3_key_interop(sqlite3 *db, const void *pKey, int nKeySize) {

return sqlite3CodecAttach(db, 0, pKey, nKeySize);

}

?

// 释放与一个页相关的加密块

void sqlite3pager_free_codecarg(void *pArg)

{

if (pArg)

DestroyCryptBlock((LPCryptBlock)pArg);

}

#endif //#ifdef SQLITE_HAS_CODEC

相关文档