前序:
這里要注明,我是一個(gè)跨平臺(tái)專注者,并不喜歡只用 windows 平臺(tái)。我以前的工作就是為 unix 平臺(tái)寫代碼。下面我所寫的東西,雖然沒有驗(yàn)證,但是我已盡量不使用任何 windows 的東西,只使用標(biāo)準(zhǔn) C 或標(biāo)準(zhǔn)C++。但是,我沒有嘗試過在別的系統(tǒng)、別的編譯器下編譯,因此下面的敘述如果不正確,則留待以后修改。
下面我的代碼仍然用 VC 編寫,因?yàn)槲矣X得VC是一個(gè)很不錯(cuò)的IDE,可以加快代碼編寫速度(例如配合 Vassist )。下面我所說的編譯環(huán)境,是VC2003。如果讀者覺得自己習(xí)慣于 unix 下用 vi 編寫代碼速度較快,可以不用管我的說明,只需要符合自己習(xí)慣即可,因?yàn)槲矣玫氖菢?biāo)準(zhǔn) C 或 C++ 。不會(huì)給任何人帶來不便。
一、版本
從 www.sqlite.org 網(wǎng)站可下載到最新的 sqlite 代碼和編譯版本。我寫此文章時(shí),最新代碼是 3.3.17 版本。
很久沒有去下載 sqlite 新代碼,因此也不知道 sqlite 變化這么大。以前很多文件,現(xiàn)在全部合并成一個(gè) sqlite3.c 文件。如果單獨(dú)用此文件,是挺好的,省去拷貝一堆文件還擔(dān)心有沒有遺漏。但是也帶來一個(gè)問題:此文件太大,快接近7萬行代碼,VC開它整個(gè)機(jī)器都慢下來了。如果不需要改它代碼,也就不需要打開 sqlite3.c 文件,機(jī)器不會(huì)慢。但是,下面我要寫通過修改 sqlite 代碼完成加密功能,那時(shí)候就比較痛苦了。如果個(gè)人水平較高,建議用些簡(jiǎn)單的編輯器來編輯,例如UltraEdit 或 Notepad 。速度會(huì)快很多。
二、基本編譯
這個(gè)不想多說了,在 VC 里新建 dos 控制臺(tái)空白工程,把 sqlite3.c 和 sqlite3.h 添加到工程,再新建一個(gè) main.cpp文件。在里面寫:
1
2
3
4
5
6
7
8
|
extern "C" { #include "./sqlite3.h" }; int main( int , char ** ) { return 0; } |
為什么要 extern “C” ?如果問這個(gè)問題,我不想說太多,這是C++的基礎(chǔ)。要在 C++ 里使用一段 C 的代碼,必須要用 extern “C” 括起來。C++跟 C雖然語法上有重疊,但是它們是兩個(gè)不同的東西,內(nèi)存里的布局是完全不同的,在C++編譯器里不用extern “C”括起C代碼,會(huì)導(dǎo)致編譯器不知道該如何為 C 代碼描述內(nèi)存布局。
可能在 sqlite3.c 里人家已經(jīng)把整段代碼都 extern “C” 括起來了,但是你遇到一個(gè) .c 文件就自覺的再括一次,也沒什么不好。
基本工程就這樣建立起來了。編譯,可以通過。但是有一堆的 warning??梢圆还芩?br />
三、SQLITE操作入門
sqlite提供的是一些C函數(shù)接口,你可以用這些函數(shù)操作數(shù)據(jù)庫。通過使用這些接口,傳遞一些標(biāo)準(zhǔn) sql 語句(以 char * 類型)給 sqlite 函數(shù),sqlite 就會(huì)為你操作數(shù)據(jù)庫。
sqlite 跟MS的access一樣是文件型數(shù)據(jù)庫,就是說,一個(gè)數(shù)據(jù)庫就是一個(gè)文件,此數(shù)據(jù)庫里可以建立很多的表,可以建立索引、觸發(fā)器等等,但是,它實(shí)際上得到的就是一個(gè)文件。備份這個(gè)文件就備份了整個(gè)數(shù)據(jù)庫。
sqlite 不需要任何數(shù)據(jù)庫引擎,這意味著如果你需要 sqlite 來保存一些用戶數(shù)據(jù),甚至都不需要安裝數(shù)據(jù)庫(如果你做個(gè)小軟件還要求人家必須裝了sqlserver 才能運(yùn)行,那也太黑心了)。
下面開始介紹數(shù)據(jù)庫基本操作。
1 基本流程(1)關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
sqlite 里最常用到的是 sqlite3 * 類型。從數(shù)據(jù)庫打開開始,sqlite就要為這個(gè)類型準(zhǔn)備好內(nèi)存,直到數(shù)據(jù)庫關(guān)閉,整個(gè)過程都需要用到這個(gè)類型。當(dāng)數(shù)據(jù)庫打開時(shí)開始,這個(gè)類型的變量就代表了你要操作的數(shù)據(jù)庫。下面再詳細(xì)介紹。
(2)打開數(shù)據(jù)庫
int sqlite3_open( 文件名, sqlite3 ** );
用這個(gè)函數(shù)開始數(shù)據(jù)庫操作。
需要傳入兩個(gè)參數(shù),一是數(shù)據(jù)庫文件名,比如:c://DongChunGuang_Database.db。
文件名不需要一定存在,如果此文件不存在,sqlite 會(huì)自動(dòng)建立它。如果它存在,就嘗試把它當(dāng)數(shù)據(jù)庫文件來打開。
sqlite3 ** 參數(shù)即前面提到的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)。這個(gè)結(jié)構(gòu)底層細(xì)節(jié)如何,你不要關(guān)它。
函數(shù)返回值表示操作是否正確,如果是 SQLITE_OK 則表示操作正常。相關(guān)的返回值sqlite定義了一些宏。具體這些宏的含義可以參考 sqlite3.h 文件。里面有詳細(xì)定義(順便說一下,sqlite3 的代碼注釋率自稱是非常高的,實(shí)際上也的確很高。只要你會(huì)看英文,sqlite 可以讓你學(xué)到不少東西)。
下面介紹關(guān)閉數(shù)據(jù)庫后,再給一段參考代碼。
(3)關(guān)閉數(shù)據(jù)庫
int sqlite3_close(sqlite3 *);
前面如果用 sqlite3_open 開啟了一個(gè)數(shù)據(jù)庫,結(jié)尾時(shí)不要忘了用這個(gè)函數(shù)關(guān)閉數(shù)據(jù)庫。
下面給段簡(jiǎn)單的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
extern "C" { #include "./sqlite3.h" }; int main( int , char ** ) { sqlite3 * db = NULL; //聲明sqlite關(guān)鍵結(jié)構(gòu)指針 int result; //打開數(shù)據(jù)庫 //需要傳入 db 這個(gè)指針的指針,因?yàn)?sqlite3_open 函數(shù)要為這個(gè)指針分配內(nèi)存,還要讓db指針指向這個(gè)內(nèi)存區(qū) result = sqlite3_open( “c: //Dcg_database.db”, &db ); if ( result != SQLITE_OK ) { //數(shù)據(jù)庫打開失敗 return -1; } //數(shù)據(jù)庫操作代碼 //… //數(shù)據(jù)庫打開成功 //關(guān)閉數(shù)據(jù)庫 sqlite3_close( db ); return 0; } |
這就是一次數(shù)據(jù)庫操作過程。
2 SQL語句操作
本節(jié)介紹如何用sqlite 執(zhí)行標(biāo)準(zhǔn) sql 語法。
(1)執(zhí)行sql語句
int sqlite3_exec(sqlite3*, const char *sql, sqlite3_callback, void *, char **errmsg );
這就是執(zhí)行一條 sql 語句的函數(shù)。
第1個(gè)參數(shù)不再說了,是前面open函數(shù)得到的指針。說了是關(guān)鍵數(shù)據(jù)結(jié)構(gòu)。
第2個(gè)參數(shù)const char *sql 是一條 sql 語句,以/0結(jié)尾。
第3個(gè)參數(shù)sqlite3_callback 是回調(diào),當(dāng)這條語句執(zhí)行之后,sqlite3會(huì)去調(diào)用你提供的這個(gè)函數(shù)。(什么是回調(diào)函數(shù),自己找別的資料學(xué)習(xí))
第4個(gè)參數(shù)void * 是你所提供的指針,你可以傳遞任何一個(gè)指針參數(shù)到這里,這個(gè)參數(shù)最終會(huì)傳到回調(diào)函數(shù)里面,如果不需要傳遞指針給回調(diào)函數(shù),可以填NULL。等下我們?cè)倏椿卣{(diào)函數(shù)的寫法,以及這個(gè)參數(shù)的使用。
第5個(gè)參數(shù)char ** errmsg 是錯(cuò)誤信息。注意是指針的指針。sqlite3里面有很多固定的錯(cuò)誤信息。執(zhí)行 sqlite3_exec 之后,執(zhí)行失敗時(shí)可以查閱這個(gè)指針(直接 printf(“%s/n”,errmsg))得到一串字符串信息,這串信息告訴你錯(cuò)在什么地方。sqlite3_exec函數(shù)通過修改你傳入的指針的指針,把你提供的指針指向錯(cuò)誤提示信息,這樣sqlite3_exec函數(shù)外面就可以通過這個(gè) char*得到具體錯(cuò)誤提示。
說明:通常,sqlite3_callback 和它后面的 void * 這兩個(gè)位置都可以填 NULL。填NULL表示你不需要回調(diào)。比如你做insert 操作,做 delete 操作,就沒有必要使用回調(diào)。而當(dāng)你做 select 時(shí),就要使用回調(diào),因?yàn)?sqlite3 把數(shù)據(jù)查出來,得通過回調(diào)告訴你查出了什么數(shù)據(jù)。
(2)exec 的回調(diào)
typedef int (*sqlite3_callback)(void*,int,char**, char**);
你的回調(diào)函數(shù)必須定義成上面這個(gè)函數(shù)的類型。下面給個(gè)簡(jiǎn)單的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
//sqlite3的回調(diào)函數(shù) // sqlite 每查到一條記錄,就調(diào)用一次這個(gè)回調(diào) int LoadMyInfo( void * para, int n_column, char ** column_value, char ** column_name ) { //para是你在 sqlite3_exec 里傳入的 void * 參數(shù) //通過para參數(shù),你可以傳入一些特殊的指針(比如類指針、結(jié)構(gòu)指針),然后在這里面強(qiáng)制轉(zhuǎn)換成對(duì)應(yīng)的類型(這里面是void*類型,必須強(qiáng)制轉(zhuǎn)換成你的類型才可用)。然后操作這些數(shù)據(jù) //n_column是這一條記錄有多少個(gè)字段 (即這條記錄有多少列) // char ** column_value 是個(gè)關(guān)鍵值,查出來的數(shù)據(jù)都保存在這里,它實(shí)際上是個(gè)1維數(shù)組(不要以為是2維數(shù)組),每一個(gè)元素都是一個(gè) char * 值,是一個(gè)字段內(nèi)容(用字符串來表示,以/0結(jié)尾) //char ** column_name 跟 column_value是對(duì)應(yīng)的,表示這個(gè)字段的字段名稱 //這里,我不使用 para 參數(shù)。忽略它的存在. int i; printf( “記錄包含 %d 個(gè)字段/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( “c://Dcg_database.db”, &db ); if( result != SQLITE_OK ) { //數(shù)據(jù)庫打開失敗 return -1; } //數(shù)據(jù)庫操作代碼 //創(chuàng)建一個(gè)測(cè)試表,表名叫 MyTable_1,有2個(gè)字段: ID 和 name。其中ID是一個(gè)自動(dòng)增加的類型,以后insert時(shí)可以不去指定這個(gè)字段,它會(huì)自己從0開始增加 result = sqlite3_exec( db, “create table MyTable_1( ID integer primary key autoincrement, name nvarchar(32) )”, NULL, NULL, errmsg ); if(result != SQLITE_OK ) { printf( “創(chuàng)建表失敗,錯(cuò)誤碼:%d,錯(cuò)誤原因:%s/n”, result, errmsg ); } //插入一些記錄 result = sqlite3_exec( db, “insert into MyTable_1( name ) values ( ‘走路' )”, 0, 0, errmsg ); if(result != SQLITE_OK ) { printf( “插入記錄失敗,錯(cuò)誤碼:%d,錯(cuò)誤原因:%s/n”, result, errmsg ); } result = sqlite3_exec( db, “insert into MyTable_1( name ) values ( ‘騎單車' )”, 0, 0, errmsg ); if(result != SQLITE_OK ) { printf( “插入記錄失敗,錯(cuò)誤碼:%d,錯(cuò)誤原因:%s/n”, result, errmsg ); } result = sqlite3_exec( db, “insert into MyTable_1( name ) values ( ‘坐汽車' )”, 0, 0, errmsg ); if(result != SQLITE_OK ) { printf( “插入記錄失敗,錯(cuò)誤碼:%d,錯(cuò)誤原因:%s/n”, result, errmsg ); } //開始查詢數(shù)據(jù)庫 result = sqlite3_exec( db, “select * from MyTable_1”, LoadMyInfo, NULL, errmsg ); //關(guān)閉數(shù)據(jù)庫 sqlite3_close( db ); return 0; } |
通過上面的例子,應(yīng)該可以知道如何打開一個(gè)數(shù)據(jù)庫,如何做數(shù)據(jù)庫基本操作。
有這些知識(shí),基本上可以應(yīng)付很多數(shù)據(jù)庫操作了。
(3)不使用回調(diào)查詢數(shù)據(jù)庫
上面介紹的 sqlite3_exec 是使用回調(diào)來執(zhí)行 select 操作。還有一個(gè)方法可以直接查詢而不需要回調(diào)。但是,我個(gè)人感覺還是回調(diào)好,因?yàn)榇a可以更加整齊,只不過用回調(diào)很麻煩,你得聲明一個(gè)函數(shù),如果這個(gè)函數(shù)是類成員函數(shù),你還不得不把它聲明成 static 的(要問為什么?這又是C++基礎(chǔ)了。C++成員函數(shù)實(shí)際上隱藏了一個(gè)參數(shù):this,C++調(diào)用類的成員函數(shù)的時(shí)候,隱含把類指針當(dāng)成函數(shù)的第一個(gè)參數(shù)傳遞進(jìn)去。結(jié)果,這造成跟前面說的 sqlite 回調(diào)函數(shù)的參數(shù)不相符。只有當(dāng)把成員函數(shù)聲明成 static 時(shí),它才沒有多余的隱含的this參數(shù))。
雖然回調(diào)顯得代碼整齊,但有時(shí)候你還是想要非回調(diào)的 select 查詢。這可以通過 sqlite3_get_table 函數(shù)做到。
int sqlite3_get_table(sqlite3*, const char *sql, char ***resultp, int *nrow, int *ncolumn, char **errmsg );
第1個(gè)參數(shù)不再多說,看前面的例子。
第2個(gè)參數(shù)是 sql 語句,跟 sqlite3_exec 里的 sql 是一樣的。是一個(gè)很普通的以/0結(jié)尾的char *字符串。
第3個(gè)參數(shù)是查詢結(jié)果,它依然一維數(shù)組(不要以為是二維數(shù)組,更不要以為是三維數(shù)組)。它內(nèi)存布局是:第一行是字段名稱,后面是緊接著是每個(gè)字段的值。下面用例子來說事。
第4個(gè)參數(shù)是查詢出多少條記錄(即查出多少行)。
第5個(gè)參數(shù)是多少個(gè)字段(多少列)。
第6個(gè)參數(shù)是錯(cuò)誤信息,跟前面一樣,這里不多說了。
下面給個(gè)簡(jiǎn)單例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
int main( int , char ** ) { sqlite3 * db; int result; char * errmsg = NULL; char **dbResult; //是 char ** 類型,兩個(gè)*號(hào) int nRow, nColumn; int i , j; int index; result = sqlite3_open( “c://Dcg_database.db”, &db ); if( result != SQLITE_OK ) { //數(shù)據(jù)庫打開失敗 return -1; } //數(shù)據(jù)庫操作代碼 //假設(shè)前面已經(jīng)創(chuàng)建了 MyTable_1 表 //開始查詢,傳入的 dbResult 已經(jīng)是 char **,這里又加了一個(gè) & 取地址符,傳遞進(jìn)去的就成了 char *** result = sqlite3_get_table( db, “select * from MyTable_1”, &dbResult, &nRow, &nColumn, &errmsg ); if( SQLITE_OK == result ) { //查詢成功 index = nColumn; //前面說過 dbResult 前面第一行數(shù)據(jù)是字段名稱,從 nColumn 索引開始才是真正的數(shù)據(jù) printf( “查到%d條記錄/n”, nRow ); for( i = 0; i < nRow ; i++ ) { printf( “第 %d 條記錄/n”, i+1 ); for( j = 0 ; j < nColumn; j++ ) { printf( “字段名:%s ß> 字段值:%s/n”, dbResult[j], dbResult [index] ); ++index; // dbResult 的字段值是連續(xù)的,從第0索引到第 nColumn - 1索引都是字段名稱,從第 nColumn 索引開始,后面都是字段值,它把一個(gè)二維的表(傳統(tǒng)的行列表示法)用一個(gè)扁平的形式來表示 } printf( “-------/n” ); } } //到這里,不論數(shù)據(jù)庫查詢是否成功,都釋放 char** 查詢結(jié)果,使用 sqlite 提供的功能來釋放 sqlite3_free_table( dbResult ); //關(guān)閉數(shù)據(jù)庫 sqlite3_close( db ); return 0; } |
到這個(gè)例子為止,sqlite3 的常用用法都介紹完了。
用以上的方法,再配上 sql 語句,完全可以應(yīng)付絕大多數(shù)數(shù)據(jù)庫需求。
但有一種情況,用上面方法是無法實(shí)現(xiàn)的:需要insert、select 二進(jìn)制。當(dāng)需要處理二進(jìn)制數(shù)據(jù)時(shí),上面的方法就沒辦法做到。下面這一節(jié)說明如何插入二進(jìn)制數(shù)據(jù)
3 操作二進(jìn)制
sqlite 操作二進(jìn)制數(shù)據(jù)需要用一個(gè)輔助的數(shù)據(jù)類型:sqlite3_stmt * 。
這個(gè)數(shù)據(jù)類型記錄了一個(gè)“sql語句”。為什么我把 “sql語句” 用雙引號(hào)引起來?因?yàn)槟憧梢园?sqlite3_stmt * 所表示的內(nèi)容看成是 sql語句,但是實(shí)際上它不是我們所熟知的sql語句。它是一個(gè)已經(jīng)把sql語句解析了的、用sqlite自己標(biāo)記記錄的內(nèi)部數(shù)據(jù)結(jié)構(gòu)。
正因?yàn)檫@個(gè)結(jié)構(gòu)已經(jīng)被解析了,所以你可以往這個(gè)語句里插入二進(jìn)制數(shù)據(jù)。當(dāng)然,把二進(jìn)制數(shù)據(jù)插到 sqlite3_stmt 結(jié)構(gòu)里可不能直接 memcpy ,也不能像 std::string 那樣用 + 號(hào)。必須用 sqlite 提供的函數(shù)來插入。
(1)寫入二進(jìn)制
下面說寫二進(jìn)制的步驟。
要插入二進(jìn)制,前提是這個(gè)表的字段的類型是 blob 類型。我假設(shè)有這么一張表:
create table Tbl_2( ID integer, file_content blob )
首先聲明
sqlite3_stmt * stat;
然后,把一個(gè) sql 語句解析到 stat 結(jié)構(gòu)里去:
sqlite3_prepare( db, “insert into Tbl_2( ID, file_content) values( 10, ? )”, -1, &stat, 0 );
上面的函數(shù)完成 sql 語句的解析。第一個(gè)參數(shù)跟前面一樣,是個(gè) sqlite3 * 類型變量,第二個(gè)參數(shù)是一個(gè) sql 語句。
這個(gè) sql 語句特別之處在于 values 里面有個(gè) ? 號(hào)。在sqlite3_prepare函數(shù)里,?號(hào)表示一個(gè)未定的值,它的值等下才插入。
第三個(gè)參數(shù)我寫的是-1,這個(gè)參數(shù)含義是前面 sql 語句的長(zhǎng)度。如果小于0,sqlite會(huì)自動(dòng)計(jì)算它的長(zhǎng)度(把sql語句當(dāng)成以/0結(jié)尾的字符串)。
第四個(gè)參數(shù)是 sqlite3_stmt 的指針的指針。解析以后的sql語句就放在這個(gè)結(jié)構(gòu)里。
第五個(gè)參數(shù)我也不知道是干什么的。為0就可以了。
如果這個(gè)函數(shù)執(zhí)行成功(返回值是 SQLITE_OK 且 stat 不為NULL ),那么下面就可以開始插入二進(jìn)制數(shù)據(jù)。
sqlite3_bind_blob( stat, 1, pdata, (int)(length_of_data_in_bytes), NULL ); // pdata為數(shù)據(jù)緩沖區(qū),length_of_data_in_bytes為數(shù)據(jù)大小,以字節(jié)為單位
這個(gè)函數(shù)一共有5個(gè)參數(shù)。
第1個(gè)參數(shù):是前面prepare得到的 sqlite3_stmt * 類型變量。
第2個(gè)參數(shù):?號(hào)的索引。前面prepare的sql語句里有一個(gè)?號(hào),假如有多個(gè)?號(hào)怎么插入?方法就是改變 bind_blob 函數(shù)第2個(gè)參數(shù)。這個(gè)參數(shù)我寫1,表示這里插入的值要替換 stat 的第一個(gè)?號(hào)(這里的索引從1開始計(jì)數(shù),而非從0開始)。如果你有多個(gè)?號(hào),就寫多個(gè) bind_blob 語句,并改變它們的第2個(gè)參數(shù)就替換到不同的?號(hào)。如果有?號(hào)沒有替換,sqlite為它取值null。
第3個(gè)參數(shù):二進(jìn)制數(shù)據(jù)起始指針。
第4個(gè)參數(shù):二進(jìn)制數(shù)據(jù)的長(zhǎng)度,以字節(jié)為單位。
第5個(gè)參數(shù):是個(gè)析夠回調(diào)函數(shù),告訴sqlite當(dāng)把數(shù)據(jù)處理完后調(diào)用此函數(shù)來析夠你的數(shù)據(jù)。這個(gè)參數(shù)我還沒有使用過,因此理解也不深刻。但是一般都填NULL,需要釋放的內(nèi)存自己用代碼來釋放。
bind完了之后,二進(jìn)制數(shù)據(jù)就進(jìn)入了你的“sql語句”里了。你現(xiàn)在可以把它保存到數(shù)據(jù)庫里:
1
|
int result = sqlite3_step( stat ); |
通過這個(gè)語句,stat 表示的sql語句就被寫到了數(shù)據(jù)庫里。
最后,要把 sqlite3_stmt 結(jié)構(gòu)給釋放:
sqlite3_finalize( stat ); //把剛才分配的內(nèi)容析構(gòu)掉
(2)讀出二進(jìn)制
下面說讀二進(jìn)制的步驟。
跟前面一樣,先聲明 sqlite3_stmt * 類型變量:
1
|
sqlite3_stmt * stat; |
然后,把一個(gè) sql 語句解析到 stat 結(jié)構(gòu)里去:
1
|
sqlite3_prepare( db, “select * from Tbl_2”, -1, &stat, 0 ); |
當(dāng) prepare 成功之后(返回值是 SQLITE_OK ),開始查詢數(shù)據(jù)。
1
|
int result = sqlite3_step( stat ); |
這一句的返回值是SQLITE_ROW 時(shí)表示成功(不是 SQLITE_OK )。
你可以循環(huán)執(zhí)行sqlite3_step 函數(shù),一次step查詢出一條記錄。直到返回值不為 SQLITE_ROW 時(shí)表示查詢結(jié)束。
然后開始獲取第一個(gè)字段:ID 的值。ID是個(gè)整數(shù),用下面這個(gè)語句獲取它的值:
int id = sqlite3_column_int( stat, 0 ); //第2個(gè)參數(shù)表示獲取第幾個(gè)字段內(nèi)容,從0開始計(jì)算,因?yàn)槲业谋淼腎D字段是第一個(gè)字段,因此這里我填0
下面開始獲取 file_content 的值,因?yàn)?file_content 是二進(jìn)制,因此我需要得到它的指針,還有它的長(zhǎng)度:
1
2
|
const void * pFileContent = sqlite3_column_blob( stat, 1 ); int len = sqlite3_column_bytes( stat, 1 ); |
這樣就得到了二進(jìn)制的值。
把 pFileContent 的內(nèi)容保存出來之后,不要忘了釋放 sqlite3_stmt 結(jié)構(gòu):
sqlite3_finalize( stat ); //把剛才分配的內(nèi)容析構(gòu)掉
(3)重復(fù)使用 sqlite3_stmt 結(jié)構(gòu)
如果你需要重復(fù)使用 sqlite3_prepare 解析好的 sqlite3_stmt 結(jié)構(gòu),需要用函數(shù): sqlite3_reset。
1
|
result = sqlite3_reset(stat); |
這樣, stat 結(jié)構(gòu)又成為 sqlite3_prepare 完成時(shí)的狀態(tài),你可以重新為它 bind 內(nèi)容。
4 事務(wù)處理
sqlite 是支持事務(wù)處理的。如果你知道你要同步刪除很多數(shù)據(jù),不仿把它們做成一個(gè)統(tǒng)一的事務(wù)。
通常一次 sqlite3_exec 就是一次事務(wù),如果你要?jiǎng)h除1萬條數(shù)據(jù),sqlite就做了1萬次:開始新事務(wù)->刪除一條數(shù)據(jù)->提交事務(wù)->開始新事務(wù)->… 的過程。這個(gè)操作是很慢的。因?yàn)闀r(shí)間都花在了開始事務(wù)、提交事務(wù)上。
你可以把這些同類操作做成一個(gè)事務(wù),這樣如果操作錯(cuò)誤,還能夠回滾事務(wù)。
事務(wù)的操作沒有特別的接口函數(shù),它就是一個(gè)普通的 sql 語句而已:
分別如下:
1
2
3
4
|
int result; result = sqlite3_exec( db, "begin transaction", 0, 0, &zErrorMsg ); //開始一個(gè)事務(wù) result = sqlite3_exec( db, "commit transaction", 0, 0, &zErrorMsg ); //提交事務(wù) result = sqlite3_exec( db, "rollback transaction", 0, 0, &zErrorMsg ); //回滾事務(wù) |
四、C/C++開發(fā)接口簡(jiǎn)介1 總覽
SQLite3是SQLite一個(gè)全新的版本,它雖然是在SQLite 2.8.13的代碼基礎(chǔ)之上開發(fā)的,但是使用了和之前的版本不兼容的數(shù)據(jù)庫格式和API. SQLite3是為了滿足以下的需求而開發(fā)的:
支持UTF-16編碼.
用戶自定義的文本排序方法.
可以對(duì)BLOBs字段建立索引.
因此為了支持這些特性我改變了數(shù)據(jù)庫的格式,建立了一個(gè)與之前版本不兼容的3.0版. 至于其他的兼容性的改變,例如全新的API等等,都將在理論介紹之后向你說明,這樣可以使你最快的一次性擺脫兼容性問題.
3.0版的和2.X版的API非常相似,但是有一些重要的改變需要注意. 所有API接口函數(shù)和數(shù)據(jù)結(jié)構(gòu)的前綴都由"sqlite_"改為了"sqlite3_". 這是為了避免同時(shí)使用SQLite 2.X和SQLite 3.0這兩個(gè)版本的時(shí)候發(fā)生鏈接沖突.
由于對(duì)于C語言應(yīng)該用什么數(shù)據(jù)類型來存放UTF-16編碼的字符串并沒有一致的規(guī)范. 因此SQLite使用了普通的void* 類型來指向UTF-16編碼的字符串. 客戶端使用過程中可以把void*映射成適合他們的系統(tǒng)的任何數(shù)據(jù)類型.
2 C/C++接口
SQLite 3.0一共有83個(gè)API函數(shù),此外還有一些數(shù)據(jù)結(jié)構(gòu)和預(yù)定義(#defines). (完整的API介紹請(qǐng)參看另一份文檔.) 不過你們可以放心,這些接口使用起來不會(huì)像它的數(shù)量所暗示的那么復(fù)雜. 最簡(jiǎn)單的程序仍然使用三個(gè)函數(shù)就可以完成: sqlite3_open(), sqlite3_exec(), 和 sqlite3_close(). 要是想更好的控制數(shù)據(jù)庫引擎的執(zhí)行,可以使用提供的sqlite3_prepare()函數(shù)把SQL語句編譯成字節(jié)碼,然后在使用sqlite3_step()函數(shù)來執(zhí)行編譯后的字節(jié)碼. 以sqlite3_column_開頭的一組API函數(shù)用來獲取查詢結(jié)果集中的信息. 許多接口函數(shù)都是成對(duì)出現(xiàn)的,同時(shí)有UTF-8和UTF-16兩個(gè)版本. 并且提供了一組函數(shù)用來執(zhí)行用戶自定義的SQL函數(shù)和文本排序函數(shù).
(1)如何打開關(guān)閉數(shù)據(jù)庫
1
2
3
4
5
6
7
|
typedef struct sqlite3 sqlite3; int sqlite3_open(const char*, sqlite3**); int sqlite3_open16(const void*, sqlite3**); int sqlite3_close(sqlite3*); const char *sqlite3_errmsg(sqlite3*); const void *sqlite3_errmsg16(sqlite3*); int sqlite3_errcode(sqlite3*); |
sqlite3_open() 函數(shù)返回一個(gè)整數(shù)錯(cuò)誤代碼,而不是像第二版中一樣返回一個(gè)指向sqlite3結(jié)構(gòu)體的指針. sqlite3_open() 和sqlite3_open16() 的不同之處在于sqlite3_open16() 使用UTF-16編碼(使用本地主機(jī)字節(jié)順序)傳遞數(shù)據(jù)庫文件名. 如果要?jiǎng)?chuàng)建新數(shù)據(jù)庫, sqlite3_open16() 將內(nèi)部文本轉(zhuǎn)換為UTF-16編碼, 反之sqlite3_open() 將文本轉(zhuǎn)換為UTF-8編碼.
打開或者創(chuàng)建數(shù)據(jù)庫的命令會(huì)被緩存,直到這個(gè)數(shù)據(jù)庫真正被調(diào)用的時(shí)候才會(huì)被執(zhí)行. 而且允許使用PRAGMA聲明來設(shè)置如本地文本編碼或默認(rèn)內(nèi)存頁面大小等選項(xiàng)和參數(shù).
sqlite3_errcode() 通常用來獲取最近調(diào)用的API接口返回的錯(cuò)誤代碼. sqlite3_errmsg() 則用來得到這些錯(cuò)誤代碼所對(duì)應(yīng)的文字說明. 這些錯(cuò)誤信息將以 UTF-8 的編碼返回,并且在下一次調(diào)用任何SQLite API函數(shù)的時(shí)候被清除. sqlite3_errmsg16() 和sqlite3_errmsg() 大體上相同,除了返回的錯(cuò)誤信息將以 UTF-16 本機(jī)字節(jié)順序編碼.
SQLite3的錯(cuò)誤代碼相比SQLite2沒有任何的改變,它們分別是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#define SQLITE_OK 0 /* Successful result */ #define SQLITE_ERROR 1 /* SQL error or missing database */ #define SQLITE_INTERNAL 2 /* An 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 sqlite_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 /* (Internal Only) 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 /* Database lock protocol error */ #define SQLITE_EMPTY 16 /* (Internal Only) Database table is empty */ #define SQLITE_SCHEMA 17 /* The database schema changed */ #define SQLITE_TOOBIG 18 /* Too much data for one row of a table */ #define SQLITE_CONSTRAINT 19 /* Abort due to contraint 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_ROW 100 /* sqlite_step() has another row ready */ #define SQLITE_DONE 101 /* sqlite_step() has finished executing */ |
(2)執(zhí)行 SQL 語句
typedef int (*sqlite_callback)(void*,int,char**, char**);
int sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void*, char**);
sqlite3_exec 函數(shù)依然像它在SQLite2中一樣承擔(dān)著很多的工作. 該函數(shù)的第二個(gè)參數(shù)中可以編譯和執(zhí)行零個(gè)或多個(gè)SQL語句. 查詢的結(jié)果返回給回調(diào)函數(shù). 更多地信息可以查看API 參考.
在SQLite3里,sqlite3_exec一般是被準(zhǔn)備SQL語句接口封裝起來使用的.
1
2
3
4
5
|
typedef struct sqlite3_stmt sqlite3_stmt; int sqlite3_prepare(sqlite3*, const char*, int, sqlite3_stmt**, const char**); int sqlite3_prepare16(sqlite3*, const void*, int, sqlite3_stmt**, const void**); int sqlite3_finalize(sqlite3_stmt*); int sqlite3_reset(sqlite3_stmt*); |
sqlite3_prepare 接口把一條SQL語句編譯成字節(jié)碼留給后面的執(zhí)行函數(shù). 使用該接口訪問數(shù)據(jù)庫是當(dāng)前比較好的的一種方法.
sqlite3_prepare() 處理的SQL語句應(yīng)該是UTF-8編碼的. 而sqlite3_prepare16() 則要求是UTF-16編碼的. 輸入的參數(shù)中只有第一個(gè)SQL語句會(huì)被編譯. 第四個(gè)參數(shù)則用來指向輸入?yún)?shù)中下一個(gè)需要編譯的SQL語句存放的SQLite statement對(duì)象的指針,任何時(shí)候如果調(diào)用 sqlite3_finalize() 將銷毀一個(gè)準(zhǔn)備好的SQL聲明. 在數(shù)據(jù)庫關(guān)閉之前,所有準(zhǔn)備好的聲明都必須被釋放銷毀. sqlite3_reset() 函數(shù)用來重置一個(gè)SQL聲明的狀態(tài),使得它可以被再次執(zhí)行.
SQL聲明可以包含一些型如"?" 或 "?nnn" 或 ":aaa"的標(biāo)記, 其中"nnn" 是一個(gè)整數(shù),"aaa" 是一個(gè)字符串. 這些標(biāo)記代表一些不確定的字符值(或者說是通配符),可以在后面用sqlite3_bind 接口來填充這些值. 每一個(gè)通配符都被分配了一個(gè)編號(hào)(由它在SQL聲明中的位置決定,從1開始),此外也可以用 "nnn" 來表示 "?nnn" 這種情況. 允許相同的通配符在同一個(gè)SQL聲明中出現(xiàn)多次, 在這種情況下所有相同的通配符都會(huì)被替換成相同的值. 沒有被綁定的通配符將自動(dòng)取NULL值.
1
2
3
4
5
6
7
8
|
int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); int sqlite3_bind_double(sqlite3_stmt*, int, double); int sqlite3_bind_int(sqlite3_stmt*, int, int); int sqlite3_bind_int64(sqlite3_stmt*, int, long long int); int sqlite3_bind_null(sqlite3_stmt*, int); int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*)); int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); |
以上是 sqlite3_bind 所包含的全部接口,它們是用來給SQL聲明中的通配符賦值的. 沒有綁定的通配符則被認(rèn)為是空值.綁定上的值不會(huì)被sqlite3_reset()函數(shù)重置. 但是在調(diào)用了sqlite3_reset()之后所有的通配符都可以被重新賦值.
在SQL聲明準(zhǔn)備好之后(其中綁定的步驟是可選的), 需要調(diào)用以下的方法來執(zhí)行:
int sqlite3_step(sqlite3_stmt*);
如果SQL返回了一個(gè)單行結(jié)果集,sqlite3_step() 函數(shù)將返回 SQLITE_ROW , 如果SQL語句執(zhí)行成功或者正常將返回SQLITE_DONE , 否則將返回錯(cuò)誤代碼. 如果不能打開數(shù)據(jù)庫文件則會(huì)返回 SQLITE_BUSY . 如果函數(shù)的返回值是SQLITE_ROW, 那么下邊的這些方法可以用來獲得記錄集行中的數(shù)據(jù):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); int sqlite3_column_bytes(sqlite3_stmt*, int iCol); int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); int sqlite3_column_count(sqlite3_stmt*); const char *sqlite3_column_decltype(sqlite3_stmt *, int iCol); const void *sqlite3_column_decltype16(sqlite3_stmt *, int iCol); double sqlite3_column_double(sqlite3_stmt*, int iCol); int sqlite3_column_int(sqlite3_stmt*, int iCol); long long int sqlite3_column_int64(sqlite3_stmt*, int iCol); const char *sqlite3_column_name(sqlite3_stmt*, int iCol); const void *sqlite3_column_name16(sqlite3_stmt*, int iCol); const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); int sqlite3_column_type(sqlite3_stmt*, int iCol); |
sqlite3_column_count()函數(shù)返回結(jié)果集中包含的列數(shù). sqlite3_column_count() 可以在執(zhí)行了 sqlite3_prepare()之后的任何時(shí)刻調(diào)用. sqlite3_data_count()除了必需要在sqlite3_step()之后調(diào)用之外,其他跟sqlite3_column_count() 大同小異. 如果調(diào)用sqlite3_step() 返回值是 SQLITE_DONE 或者一個(gè)錯(cuò)誤代碼, 則此時(shí)調(diào)用sqlite3_data_count() 將返回 0 ,然而sqlite3_column_count() 仍然會(huì)返回結(jié)果集中包含的列數(shù).
返回的記錄集通過使用其它的幾個(gè) sqlite3_column_***() 函數(shù)來提取, 所有的這些函數(shù)都把列的編號(hào)作為第二個(gè)參數(shù). 列編號(hào)從左到右以零起始. 請(qǐng)注意它和之前那些從1起始的參數(shù)的不同.
sqlite3_column_type()函數(shù)返回第N列的值的數(shù)據(jù)類型. 具體的返回值如下:
1
2
3
4
5
|
#define SQLITE_INTEGER 1 #define SQLITE_FLOAT 2 #define SQLITE_TEXT 3 #define SQLITE_BLOB 4 #define SQLITE_NULL 5 |
sqlite3_column_decltype() 則用來返回該列在 CREATE TABLE 語句中聲明的類型. 它可以用在當(dāng)返回類型是空字符串的時(shí)候. sqlite3_column_name() 返回第N列的字段名. sqlite3_column_bytes() 用來返回 UTF-8 編碼的BLOBs列的字節(jié)數(shù)或者TEXT字符串的字節(jié)數(shù). sqlite3_column_bytes16() 對(duì)于BLOBs列返回同樣的結(jié)果,但是對(duì)于TEXT字符串則按 UTF-16 的編碼來計(jì)算字節(jié)數(shù). sqlite3_column_blob() 返回 BLOB 數(shù)據(jù). sqlite3_column_text() 返回 UTF-8 編碼的 TEXT 數(shù)據(jù). sqlite3_column_text16() 返回 UTF-16 編碼的 TEXT 數(shù)據(jù). sqlite3_column_int() 以本地主機(jī)的整數(shù)格式返回一個(gè)整數(shù)值. sqlite3_column_int64() 返回一個(gè)64位的整數(shù). 最后, sqlite3_column_double() 返回浮點(diǎn)數(shù).
不一定非要按照sqlite3_column_type()接口返回的數(shù)據(jù)類型來獲取數(shù)據(jù). 數(shù)據(jù)類型不同時(shí)軟件將自動(dòng)轉(zhuǎn)換.
(3)用戶自定義函數(shù)
可以使用以下的方法來創(chuàng)建用戶自定義的SQL函數(shù):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
typedef struct sqlite3_value sqlite3_value; int sqlite3_create_function( sqlite3 *, const char *zFunctionName, int nArg, int eTextRep, void*, void (*xFunc)(sqlite3_context*,int,sqlite3_value**), void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xFinal)(sqlite3_context*) ); int sqlite3_create_function16( sqlite3*, const void *zFunctionName, int nArg, int eTextRep, void*, void (*xFunc)(sqlite3_context*,int,sqlite3_value**), void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xFinal)(sqlite3_context*) ); #define SQLITE_UTF8 1 #define SQLITE_UTF16 2 #define SQLITE_UTF16BE 3 #define SQLITE_UTF16LE 4 #define SQLITE_ANY 5 |
nArg 參數(shù)用來表明自定義函數(shù)的參數(shù)個(gè)數(shù). 如果參數(shù)值為0,則表示接受任意個(gè)數(shù)的參數(shù). 用 eTextRep 參數(shù)來表明傳入?yún)?shù)的編碼形式. 參數(shù)值可以是上面的五種預(yù)定義值. SQLite3 允許同一個(gè)自定義函數(shù)有多種不同的編碼參數(shù)的版本. 數(shù)據(jù)庫引擎會(huì)自動(dòng)選擇轉(zhuǎn)換參數(shù)編碼個(gè)數(shù)最少的版本使用.
普通的函數(shù)只需要設(shè)置 xFunc 參數(shù),而把 xStep 和 xFinal 設(shè)為NULL. 聚合函數(shù)則需要設(shè)置 xStep 和 xFinal 參數(shù),然后把 xFunc 設(shè)為NULL. 該方法和使用sqlite3_create_aggregate() API一樣.
sqlite3_create_function16()和sqlite_create_function()的不同就在于自定義的函數(shù)名一個(gè)要求是 UTF-16 編碼,而另一個(gè)則要求是 UTF-8.
請(qǐng)注意自定函數(shù)的參數(shù)目前使用了sqlite3_value結(jié)構(gòu)體指針替代了SQLite version 2.X中的字符串指針. 下面的函數(shù)用來從sqlite3_value結(jié)構(gòu)體中提取數(shù)據(jù):
1
2
3
4
5
6
7
8
9
|
const void *sqlite3_value_blob(sqlite3_value*); int sqlite3_value_bytes(sqlite3_value*); int sqlite3_value_bytes16(sqlite3_value*); double sqlite3_value_double(sqlite3_value*); int sqlite3_value_int(sqlite3_value*); long long int sqlite3_value_int64(sqlite3_value*); const unsigned char *sqlite3_value_text(sqlite3_value*); const void *sqlite3_value_text16(sqlite3_value*); int sqlite3_value_type(sqlite3_value*); |
上面的函數(shù)調(diào)用以下的API來獲得上下文內(nèi)容和返回結(jié)果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
void *sqlite3_aggregate_context(sqlite3_context*, int nbyte); void *sqlite3_user_data(sqlite3_context*); void sqlite3_result_blob(sqlite3_context*, const void*, int n, void(*)(void*)); void qlite3_result_double(sqlite3_context*, double ); void sqlite3_result_error(sqlite3_context*, const char *, int ); void sqlite3_result_error16(sqlite3_context*, const void*, int ); void sqlite3_result_int(sqlite3_context*, int ); void sqlite3_result_int64(sqlite3_context*, long long int ); void sqlite3_result_null(sqlite3_context*); void sqlite3_result_text(sqlite3_context*, const char *, int n, void(*)(void*)); void sqlite3_result_text16(sqlite3_context*, const void*, int n, void(*)(void*)); void sqlite3_result_value(sqlite3_context*, sqlite3_value*); void *sqlite3_get_auxdata(sqlite3_context*, int ); void sqlite3_set_auxdata(sqlite3_context*, int , void*, void (*)(void*)); |
(4)用戶自定義排序規(guī)則
下面的函數(shù)用來實(shí)現(xiàn)用戶自定義的排序規(guī)則:
1
2
3
4
5
6
7
8
|
sqlite3_create_collation(sqlite3*, const char *zName, int eTextRep, void*, int (*xCompare)(void*, int ,const void*, int ,const void*)); sqlite3_create_collation16(sqlite3*, const void *zName, int eTextRep, void*, int (*xCompare)(void*, int ,const void*, int ,const void*)); sqlite3_collation_needed(sqlite3*, void*, void(*)(void*,sqlite3*, int eTextRep,const char *)); sqlite3_collation_needed16(sqlite3*, void*, void(*)(void*,sqlite3*, int eTextRep,const void*)); |
sqlite3_create_collation() 函數(shù)用來聲明一個(gè)排序序列和實(shí)現(xiàn)它的比較函數(shù). 比較函數(shù)只能用來做文本的比較. eTextRep 參數(shù)可以取如下的預(yù)定義值 SQLITE_UTF8, SQLITE_UTF16LE, SQLITE_UTF16BE, SQLITE_ANY,用來表示比較函數(shù)所處理的文本的編碼方式. 同一個(gè)自定義的排序規(guī)則的同一個(gè)比較函數(shù)可以有 UTF-8, UTF-16LE 和 UTF-16BE 等多個(gè)編碼的版本. sqlite3_create_collation16()和sqlite3_create_collation() 的區(qū)別也僅僅在于排序名稱的編碼是 UTF-16 還是 UTF-8.
可以使用 sqlite3_collation_needed() 函數(shù)來注冊(cè)一個(gè)回調(diào)函數(shù),當(dāng)數(shù)據(jù)庫引擎遇到未知的排序規(guī)則時(shí)會(huì)自動(dòng)調(diào)用該函數(shù). 在回調(diào)函數(shù)中可以查找一個(gè)相似的比較函數(shù),并激活相應(yīng)的sqlite_3_create_collation()函數(shù). 回調(diào)函數(shù)的第四個(gè)參數(shù)是排序規(guī)則的名稱,同樣sqlite3_collation_needed采用 UTF-8 編碼. sqlite3_collation_need16() 采用 UTF-16 編碼.
五、給數(shù)據(jù)庫加密
前面所說的內(nèi)容網(wǎng)上已經(jīng)有很多資料,雖然比較零散,但是花點(diǎn)時(shí)間也還是可以找到的?,F(xiàn)在要說的這個(gè)——數(shù)據(jù)庫加密,資料就很難找。也可能是我操作水平不夠,找不到對(duì)應(yīng)資料。但不管這樣,我還是通過網(wǎng)上能找到的很有限的資料,探索出了給sqlite數(shù)據(jù)庫加密的完整步驟。
這里要提一下,雖然 sqlite 很好用,速度快、體積小巧。但是它保存的文件卻是明文的。若不信可以用 NotePad 打開數(shù)據(jù)庫文件瞧瞧,里面 insert 的內(nèi)容幾乎一覽無余。這樣赤裸裸的展現(xiàn)自己,可不是我們的初衷。當(dāng)然,如果你在嵌入式系統(tǒng)、智能手機(jī)上使用 sqlite,最好是不加密,因?yàn)檫@些系統(tǒng)運(yùn)算能力有限,你做為一個(gè)新功能提供者,不能把用戶有限的運(yùn)算能力全部花掉。
Sqlite為了速度而誕生。因此Sqlite本身不對(duì)數(shù)據(jù)庫加密,要知道,如果你選擇標(biāo)準(zhǔn)AES算法加密,那么一定有接近50%的時(shí)間消耗在加解密算法上,甚至更多(性能主要取決于你算法編寫水平以及你是否能使用cpu提供的底層運(yùn)算能力,比如MMX或sse系列指令可以大幅度提升運(yùn)算速度)。
Sqlite免費(fèi)版本是不提供加密功能的,當(dāng)然你也可以選擇他們的收費(fèi)版本,那你得支付2000塊錢,而且是USD。我這里也不是說支付錢不好,如果只為了數(shù)據(jù)庫加密就去支付2000塊,我覺得劃不來。因?yàn)橄旅嫖覍⒁嬖V你如何為免費(fèi)的Sqlite擴(kuò)展出加密模塊——自己動(dòng)手?jǐn)U展,這是Sqlite允許,也是它提倡的。
那么,就讓我們一起開始為 sqlite3.c 文件擴(kuò)展出加密模塊。
1 必要的宏
通過閱讀 Sqlite 代碼(當(dāng)然沒有全部閱讀完,6萬多行代碼,沒有一行是我習(xí)慣的風(fēng)格,我可沒那么多眼神去看),我搞清楚了兩件事:
Sqlite是支持加密擴(kuò)展的;
需要 #define 一個(gè)宏才能使用加密擴(kuò)展。
這個(gè)宏就是 SQLITE_HAS_CODEC。
你在代碼最前面(也可以在 sqlite3.h 文件第一行)定義:
#ifndef SQLITE_HAS_CODEC
#define SQLITE_HAS_CODEC
#endif
如果你在代碼里定義了此宏,但是還能夠正常編譯,那么應(yīng)該是操作沒有成功。因?yàn)槟銘?yīng)該會(huì)被編譯器提示有一些函數(shù)無法鏈接才對(duì)。如果你用的是 VC 2003,你可以在“解決方案”里右鍵點(diǎn)擊你的工程,然后選“屬性”,找到“C/C++”,再找到“命令行”,在里面手工添加“/D "SQLITE_HAS_CODEC"”。
定義了這個(gè)宏,一些被 Sqlite 故意屏蔽掉的代碼就被使用了。這些代碼就是加解密的接口。
嘗試編譯,vc會(huì)提示你有一些函數(shù)無法鏈接,因?yàn)檎也坏剿麄兊膶?shí)現(xiàn)。
如果你也用的是VC2003,那么會(huì)得到下面的提示:
error LNK2019: 無法解析的外部符號(hào) _sqlite3CodecGetKey ,該符號(hào)在函數(shù) _attachFunc 中被引用
error LNK2019: 無法解析的外部符號(hào) _sqlite3CodecAttach ,該符號(hào)在函數(shù) _attachFunc 中被引用
error LNK2019: 無法解析的外部符號(hào) _sqlite3_activate_see ,該符號(hào)在函數(shù) _sqlite3Pragma 中被引用
error LNK2019: 無法解析的外部符號(hào) _sqlite3_key ,該符號(hào)在函數(shù) _sqlite3Pragma 中被引用
fatal error LNK1120: 4 個(gè)無法解析的外部命令
這是正常的,因?yàn)镾qlite只留了接口而已,并沒有給出實(shí)現(xiàn)。
下面就讓我來實(shí)現(xiàn)這些接口。
2自己實(shí)現(xiàn)加解密接口函數(shù)
如果真要我從一份 www.sqlite.org 網(wǎng)上down下來的 sqlite3.c 文件,直接摸索出這些接口的實(shí)現(xiàn),我認(rèn)為我還沒有這個(gè)能力。
好在網(wǎng)上還有一些代碼已經(jīng)實(shí)現(xiàn)了這個(gè)功能。通過參照他們的代碼以及不斷編譯中vc給出的錯(cuò)誤提示,最終我把整個(gè)接口整理出來。
實(shí)現(xiàn)這些預(yù)留接口不是那么容易,要重頭說一次怎么回事很困難。我把代碼都寫好了,直接把他們按我下面的說明拷貝到 sqlite3.c 文件對(duì)應(yīng)地方即可。我在下面也提供了sqlite3.c 文件,可以直接參考或取下來使用。
這里要說一點(diǎn)的是,我另外新建了兩個(gè)文件:crypt.c和crypt.h。
其中crypt.h如此定義:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#ifndef DCG_SQLITE_CRYPT_FUNC_ #define DCG_SQLITE_CRYPT_FUNC_ /*********** 董淳光寫的 SQLITE 加密關(guān)鍵函數(shù)庫 ***********/ /*********** 關(guān)鍵加密函數(shù) ***********/ int My_Encrypt_Func( unsigned char * pData, unsigned int data_len, const char * key, unsigned int len_of_key ); /*********** 關(guān)鍵解密函數(shù) ***********/ int My_DeEncrypt_Func( unsigned char * pData, unsigned int data_len, const char * key, unsigned intlen_of_key ); #endif 其中的 crypt.c 如此定義: #include "./crypt.h" #include "memory.h" /*********** 關(guān)鍵加密函數(shù) ***********/ int My_Encrypt_Func( unsigned char * pData, unsigned int data_len, const char * key, unsigned int len_of_key ) { return 0; } /*********** 關(guān)鍵解密函數(shù) ***********/ int My_DeEncrypt_Func( unsigned char * pData, unsigned int data_len, const char * key, unsigned intlen_of_key ) { return 0; } |
這個(gè)文件很容易看,就兩函數(shù),一個(gè)加密一個(gè)解密。傳進(jìn)來的參數(shù)分別是待處理的數(shù)據(jù)、數(shù)據(jù)長(zhǎng)度、密鑰、密鑰長(zhǎng)度。
處理時(shí)直接把結(jié)果作用于 pData 指針指向的內(nèi)容。
你需要定義自己的加解密過程,就改動(dòng)這兩個(gè)函數(shù),其它部分不用動(dòng)。擴(kuò)展起來很簡(jiǎn)單。
這里有個(gè)特點(diǎn),data_len 一般總是 1024 字節(jié)。正因?yàn)槿绱?,你可以在你的算法里使用一些特定長(zhǎng)度的加密算法,比如AES要求被加密數(shù)據(jù)一定是128位(16字節(jié))長(zhǎng)。這個(gè)1024不是碰巧,而是 Sqlite 的頁定義是1024字節(jié),在sqlite3.c文件里有定義:
# define SQLITE_DEFAULT_PAGE_SIZE 1024
你可以改動(dòng)這個(gè)值,不過還是建議沒有必要不要去改它。
上面寫了兩個(gè)擴(kuò)展函數(shù),如何把擴(kuò)展函數(shù)跟 Sqlite 掛接起來,這個(gè)過程說起來比較麻煩。我直接貼代碼。
分3個(gè)步驟。
首先,在 sqlite3.c 文件頂部,添加下面內(nèi)容:
1
2
3
|
#ifdef SQLITE_HAS_CODEC #include "./crypt.h" /*********** |
用于在 sqlite3 最后關(guān)閉時(shí)釋放一些內(nèi)存
1
2
3
|
***********/ void sqlite3pager_free_codecarg(void *pArg); #endif |
這個(gè)函數(shù)之所以要在 sqlite3.c 開頭聲明,是因?yàn)橄旅嬖?sqlite3.c 里面某些函數(shù)里要插入這個(gè)函數(shù)調(diào)用。所以要提前聲明。
其次,在sqlite3.c文件里搜索“sqlite3PagerClose”函數(shù),要找到它的實(shí)現(xiàn)代碼(而不是聲明代碼)。
實(shí)現(xiàn)代碼里一開始是:
1
2
3
4
5
6
7
8
9
10
|
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT /* A malloc() cannot fail in sqlite3ThreadData() as one or more calls to ** malloc() must have already been made by this thread before it gets ** to this point. This means the ThreadData must have been allocated already ** so that ThreadData.nAlloc can be set. */ ThreadData *pTsd = sqlite3ThreadData(); assert( pPager ); assert( pTsd && pTsd->nAlloc ); #endif |
需要在這部分后面緊接著插入:
1
2
3
|
#ifdef SQLITE_HAS_CODEC sqlite3pager_free_codecarg(pPager->pCodecArg); #endif |
這里要注意,sqlite3PagerClose 函數(shù)大概也是 3.3.17版本左右才改名的,以前版本里是叫 “sqlite3pager_close”。因此你在老版本sqlite代碼里搜索“sqlite3PagerClose”是搜不到的。
類似的還有“sqlite3pager_get”、“sqlite3pager_unref”、“sqlite3pager_write”、“sqlite3pager_pagecount”等都是老版本函數(shù),它們?cè)?pager.h 文件里定義。新版本對(duì)應(yīng)函數(shù)是在 sqlite3.h 里定義(因?yàn)槎己喜⒌?sqlite3.c和sqlite3.h兩文件了)。所以,如果你在使用老版本的sqlite,先看看 pager.h 文件,這些函數(shù)不是消失了,也不是新蹦出來的,而是老版本函數(shù)改名得到的。
最后,往sqlite3.c 文件下找。找到最后一行:
/************** End of main.c ************************************************/
在這一行后面,接上本文最下面的代碼段。
這些代碼很長(zhǎng),我不再解釋,直接接上去就得了。
唯一要提的是 DeriveKey 函數(shù)。這個(gè)函數(shù)是對(duì)密鑰的擴(kuò)展。比如,你要求密鑰是128位,即是16字節(jié),但是如果用戶只輸入 1個(gè)字節(jié)呢?2個(gè)字節(jié)呢?或輸入50個(gè)字節(jié)呢?你得對(duì)密鑰進(jìn)行擴(kuò)展,使之符合16字節(jié)的要求。
DeriveKey 函數(shù)就是做這個(gè)擴(kuò)展的。有人把接收到的密鑰求md5,這也是一個(gè)辦法,因?yàn)閙d5運(yùn)算結(jié)果固定16字節(jié),不論你有多少字符,最后就是16字節(jié)。這是md5算法的特點(diǎn)。但是我不想用md5,因?yàn)檫€得為它添加包含一些 md5 的.c或.cpp文件。我不想這么做。我自己寫了一個(gè)算法來擴(kuò)展密鑰,很簡(jiǎn)單的算法。當(dāng)然,你也可以使用你的擴(kuò)展方法,也而可以使用md5 算法。只要修改 DeriveKey 函數(shù)就可以了。
在 DeriveKey 函數(shù)里,只管申請(qǐng)空間構(gòu)造所需要的密鑰,不需要釋放,因?yàn)樵诹硪粋€(gè)函數(shù)里有釋放過程,而那個(gè)函數(shù)會(huì)在數(shù)據(jù)庫關(guān)閉時(shí)被調(diào)用。參考我的 DeriveKey 函數(shù)來申請(qǐng)內(nèi)存。
這里我給出我已經(jīng)修改好的 sqlite3.c 和 sqlite3.h 文件。
如果太懶,就直接使用這兩個(gè)文件,編譯肯定能通過,運(yùn)行也正常。當(dāng)然,你必須按我前面提的,新建 crypt.h 和crypt.c 文件,而且函數(shù)要按我前面定義的要求來做。
3 加密使用方法
現(xiàn)在,你代碼已經(jīng)有了加密功能。
你要把加密功能給用上,除了改 sqlite3.c 文件、給你工程添加 SQLITE_HAS_CODEC 宏,還得修改你的數(shù)據(jù)庫調(diào)用函數(shù)。
前面提到過,要開始一個(gè)數(shù)據(jù)庫操作,必須先 sqlite3_open 。
加解密過程就在 sqlite3_open 后面操作。
假設(shè)你已經(jīng) sqlite3_open 成功了,緊接著寫下面的代碼:
int i;
//添加、使用密碼
i = sqlite3_key( db, "dcg", 3 );
//修改密碼
i = sqlite3_rekey( db, "dcg", 0 );
用 sqlite3_key 函數(shù)來提交密碼。
第1個(gè)參數(shù)是 sqlite3 * 類型變量,代表著用 sqlite3_open 打開的數(shù)據(jù)庫(或新建數(shù)據(jù)庫)。
第2個(gè)參數(shù)是密鑰。
第3個(gè)參數(shù)是密鑰長(zhǎng)度。
用 sqlite3_rekey 來修改密碼。參數(shù)含義同 sqlite3_key。
實(shí)際上,你可以在sqlite3_open函數(shù)之后,到 sqlite3_close 函數(shù)之前任意位置調(diào)用 sqlite3_key 來設(shè)置密碼。
但是如果你沒有設(shè)置密碼,而數(shù)據(jù)庫之前是有密碼的,那么你做任何操作都會(huì)得到一個(gè)返回值:SQLITE_NOTADB,并且得到錯(cuò)誤提示:“file is encrypted or is not a database”。
只有當(dāng)你用 sqlite3_key 設(shè)置了正確的密碼,數(shù)據(jù)庫才會(huì)正常工作。
如果你要修改密碼,前提是你必須先 sqlite3_open 打開數(shù)據(jù)庫成功,然后 sqlite3_key 設(shè)置密鑰成功,之后才能用sqlite3_rekey 來修改密碼。
如果數(shù)據(jù)庫有密碼,但你沒有用 sqlite3_key 設(shè)置密碼,那么當(dāng)你嘗試用 sqlite3_rekey 來修改密碼時(shí)會(huì)得到SQLITE_NOTADB 返回值。
如果你需要清空密碼,可以使用:
//修改密碼
i = sqlite3_rekey( db, NULL, 0 );
來完成密碼清空功能。
4 sqlite3.c 最后添加代碼段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
|
/*** 董淳光定義的加密函數(shù) ***/ #ifdef SQLITE_HAS_CODEC /*** 加密結(jié)構(gòu) ***/ #define CRYPT_OFFSET 8 typedef struct _CryptBlock { BYTE* ReadKey; // 讀數(shù)據(jù)庫和寫入事務(wù)的密鑰 BYTE* WriteKey; // 寫入數(shù)據(jù)庫的密鑰 int PageSize; // 頁的大小 BYTE* Data; } CryptBlock, *LPCryptBlock; #ifndef DB_KEY_LENGTH_BYTE /*密鑰長(zhǎng)度*/ #define DB_KEY_LENGTH_BYTE 16 /*密鑰長(zhǎng)度*/ #endif #ifndef DB_KEY_PADDING /*密鑰位數(shù)不足時(shí)補(bǔ)充的字符*/ #define DB_KEY_PADDING 0x33 /*密鑰位數(shù)不足時(shí)補(bǔ)充的字符*/ #endif /*** 下面是編譯時(shí)提示缺少的函數(shù) ***/ /** 這個(gè)函數(shù)不需要做任何處理,獲取密鑰的部分在下面 DeriveKey 函數(shù)里實(shí)現(xiàn) **/ void sqlite3CodecGetKey(sqlite3* db, int nDB, void** Key, int* nKey) { return ; } /*被sqlite 和 sqlite3_key_interop 調(diào)用, 附加密鑰到數(shù)據(jù)庫.*/ int sqlite3CodecAttach(sqlite3 *db, int nDb, const void *pKey, int nKeyLen); /** 這個(gè)函數(shù)好像是 sqlite 3.3.17前不久才加的,以前版本的sqlite里沒有看到這個(gè)函數(shù) 這個(gè)函數(shù)我還沒有搞清楚是做什么的,它里面什么都不做直接返回,對(duì)加解密沒有影響 **/ void sqlite3_activate_see(const char* right ) { return; } int sqlite3_key(sqlite3 *db, const void *pKey, int nKey); int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey); /*** 下面是上面的函數(shù)的輔助處理函數(shù) ***/ // 從用戶提供的緩沖區(qū)中得到一個(gè)加密密鑰 // 用戶提供的密鑰可能位數(shù)上滿足不了要求,使用這個(gè)函數(shù)來完成密鑰擴(kuò)展 static unsigned char * DeriveKey(const void *pKey, int nKeyLen); //創(chuàng)建或更新一個(gè)頁的加密算法索引.此函數(shù)會(huì)申請(qǐng)緩沖區(qū). static LPCryptBlock CreateCryptBlock(unsigned char* hKey, Pager *pager, LPCryptBlock pExisting); //加密/解密函數(shù), 被pager調(diào)用 void * sqlite3Codec(void *pArg, unsigned char *data, Pgno nPageNum, int nMode); //設(shè)置密碼函數(shù) int __stdcall sqlite3_key_interop(sqlite3 *db, const void *pKey, int nKeySize); // 修改密碼函數(shù) int __stdcall sqlite3_rekey_interop(sqlite3 *db, const void *pKey, int nKeySize); //銷毀一個(gè)加密塊及相關(guān)的緩沖區(qū),密鑰. 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 ); //加密/解密函數(shù), 被pager調(diào)用 void * sqlite3Codec(void *pArg, unsigned char *data, Pgno nPageNum, int nMode) { LPCryptBlock pBlock = (LPCryptBlock)pArg; unsigned int dwPageSize = 0; if (!pBlock) return data; // 確保pager的頁長(zhǎng)度和加密塊的頁長(zhǎng)度相等.如果改變,就需要調(diào)整. 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: // Undo a "case 7" journal file encryption case 2: //重載一個(gè)頁 case 3: //載入一個(gè)頁 if (!pBlock->ReadKey) break; dwPageSize = pBlock->PageSize; My_DeEncrypt_Func(data, dwPageSize, pBlock->ReadKey, DB_KEY_LENGTH_BYTE ); /*調(diào)用我的解密函數(shù)*/ break; case 6: //加密一個(gè)主數(shù)據(jù)庫文件的頁 if (!pBlock->WriteKey) break; memcpy(pBlock->Data + CRYPT_OFFSET, data, pBlock->PageSize); data = pBlock->Data + CRYPT_OFFSET; dwPageSize = pBlock->PageSize; My_Encrypt_Func(data , dwPageSize, pBlock->WriteKey, DB_KEY_LENGTH_BYTE ); /*調(diào)用我的加密函數(shù)*/ break; case 7: //加密事務(wù)文件的頁 /*在正常環(huán)境下, 讀密鑰和寫密鑰相同. 當(dāng)數(shù)據(jù)庫是被重新加密的,讀密鑰和寫密鑰未必相同. 回滾事務(wù)必要用數(shù)據(jù)庫文件的原始密鑰寫入.因此,當(dāng)一次回滾被寫入,總是用數(shù)據(jù)庫的讀密鑰, 這是為了保證與讀取原始數(shù)據(jù)的密鑰相同. */ if (!pBlock->ReadKey) break; memcpy(pBlock->Data + CRYPT_OFFSET, data, pBlock->PageSize); data = pBlock->Data + CRYPT_OFFSET; dwPageSize = pBlock->PageSize; My_Encrypt_Func( data, dwPageSize, pBlock->ReadKey, DB_KEY_LENGTH_BYTE ); /*調(diào)用我的加密函數(shù)*/ break; } return data; } //銷毀一個(gè)加密塊及相關(guān)的緩沖區(qū),密鑰. 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; } // 從用戶提供的緩沖區(qū)中得到一個(gè)加密密鑰 static unsigned char * DeriveKey(const void *pKey, int nKeyLen) { unsigned char * hKey = NULL; int j; if( pKey == NULL || nKeyLen == 0 ) { return NULL; } hKey = sqliteMalloc( DB_KEY_LENGTH_BYTE + 1 ); if( hKey == NULL ) { return NULL; } hKey[ DB_KEY_LENGTH_BYTE ] = 0; if( nKeyLen < DB_KEY_LENGTH_BYTE ) { memcpy( hKey, pKey, nKeyLen ); //先拷貝得到密鑰前面的部分 j = DB_KEY_LENGTH_BYTE - nKeyLen; //補(bǔ)充密鑰后面的部分 memset( hKey + nKeyLen, DB_KEY_PADDING, j ); } else { //密鑰位數(shù)已經(jīng)足夠,直接把密鑰取過來 memcpy( hKey, pKey, DB_KEY_LENGTH_BYTE ); } return hKey; } //創(chuàng)建或更新一個(gè)頁的加密算法索引.此函數(shù)會(huì)申請(qǐng)緩沖區(qū). static LPCryptBlock CreateCryptBlock(unsigned char* hKey, Pager *pager, LPCryptBlock pExisting) { LPCryptBlock pBlock; if (!pExisting) //創(chuàng)建新加密塊 { pBlock = sqliteMalloc(sizeof(CryptBlock)); memset(pBlock, 0, sizeof(CryptBlock)); pBlock->ReadKey = hKey; pBlock->WriteKey = hKey; pBlock->PageSize = pager->pageSize; pBlock->Data = (unsigned char*)sqliteMalloc(pBlock->PageSize + CRYPT_OFFSET); } else //更新存在的加密塊 { pBlock = pExisting; if ( pBlock->PageSize != pager->pageSize && !pBlock->Data){ sqliteFree(pBlock->Data); pBlock->PageSize = pager->pageSize; pBlock->Data = (unsigned char*)sqliteMalloc(pBlock->PageSize + CRYPT_OFFSET); } } memset(pBlock->Data, 0, pBlock->PageSize + CRYPT_OFFSET); return pBlock; } /* ** Set the codec for this pager */ void sqlite3pager_set_codec( Pager *pPager, void *(*xCodec)(void*,void*,Pgno,int), void *pCodecArg ) { pPager->xCodec = xCodec; pPager->pCodecArg = pCodecArg; } int sqlite3_key(sqlite3 *db, const void *pKey, int nKey) { return sqlite3_key_interop(db, pKey, nKey); } int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) { return sqlite3_rekey_interop(db, pKey, nKey); } /*被sqlite 和 sqlite3_key_interop 調(diào)用, 附加密鑰到數(shù)據(jù)庫.*/ int sqlite3CodecAttach(sqlite3 *db, int nDb, const void *pKey, int nKeyLen) { int rc = SQLITE_ERROR; unsigned char* hKey = 0; //如果沒有指定密匙,可能標(biāo)識(shí)用了主數(shù)據(jù)庫的加密或沒加密. if (!pKey || !nKeyLen) { if (!nDb) { return SQLITE_OK; //主數(shù)據(jù)庫, 沒有指定密鑰所以沒有加密. } else //附加數(shù)據(jù)庫,使用主數(shù)據(jù)庫的密鑰. { //獲取主數(shù)據(jù)庫的加密塊并復(fù)制密鑰給附加數(shù)據(jù)庫使用 LPCryptBlock pBlock = (LPCryptBlock)sqlite3pager_get_codecarg(sqlite3BtreePager(db->aDb[0].pBt)); if (!pBlock) return SQLITE_OK; //主數(shù)據(jù)庫沒有加密 if (!pBlock->ReadKey) return SQLITE_OK; //沒有加密 memcpy(pBlock->ReadKey, &hKey, 16); } } else //用戶提供了密碼,從中創(chuàng)建密鑰. { hKey = DeriveKey(pKey, nKeyLen); } //創(chuàng)建一個(gè)新的加密塊,并將解碼器指向新的附加數(shù)據(jù)庫. 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; } // Changes the encryption key for an existing database. 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; //重新加密一個(gè)數(shù)據(jù)庫,改變pager的寫密鑰, 讀密鑰依舊保留. if (!pBlock) //加密一個(gè)未加密的數(shù)據(jù)庫 { pBlock = CreateCryptBlock(hKey, p, NULL); pBlock->ReadKey = 0; // 原始數(shù)據(jù)庫未加密 sqlite3pager_set_codec(sqlite3BtreePager(pbt), sqlite3Codec, pBlock); } else // 改變已加密數(shù)據(jù)庫的寫密鑰 { pBlock->WriteKey = hKey; } // 開始一個(gè)事務(wù) rc = sqlite3BtreeBeginTrans(pbt, 1); if (!rc) { // 用新密鑰重寫所有的頁到數(shù)據(jù)庫。 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); } } } // 如果成功,提交事務(wù)。 if (!rc) { rc = sqlite3BtreeCommit(pbt); } // 如果失敗,回滾。 if (rc) { sqlite3BtreeRollback(pbt); } // 如果成功,銷毀先前的讀密鑰。并使讀密鑰等于當(dāng)前的寫密鑰。 if (!rc) { if (pBlock->ReadKey) { sqliteFree(pBlock->ReadKey); } pBlock->ReadKey = pBlock->WriteKey; } else// 如果失敗,銷毀當(dāng)前的寫密鑰,并恢復(fù)為當(dāng)前的讀密鑰。 { if (pBlock->WriteKey) { sqliteFree(pBlock->WriteKey); } pBlock->WriteKey = pBlock->ReadKey; } // 如果讀密鑰和寫密鑰皆為空,就不需要再對(duì)頁進(jìn)行編解碼。 // 銷毀加密塊并移除頁的編解碼器 if (!pBlock->ReadKey && !pBlock->WriteKey) { sqlite3pager_set_codec(p, NULL, NULL); DestroyCryptBlock(pBlock); } return rc; } /*** 下面是加密函數(shù)的主體 ***/ int __stdcall sqlite3_key_interop(sqlite3 *db, const void *pKey, int nKeySize) { return sqlite3CodecAttach(db, 0, pKey, nKeySize); } // 釋放與一個(gè)頁相關(guān)的加密塊 void sqlite3pager_free_codecarg(void *pArg) { if (pArg) DestroyCryptBlock((LPCryptBlock)pArg); } #endif //#ifdef SQLITE_HAS_CODEC |
五、性能優(yōu)化
很多人直接就使用了,并未注意到SQLite也有配置參數(shù),可以對(duì)性能進(jìn)行調(diào)整。有時(shí)候,產(chǎn)生的結(jié)果會(huì)有很大影響。
主要通過pragma指令來實(shí)現(xiàn)。
比如: 空間釋放、磁盤同步、Cache大小等。
不要打開。前文提高了,Vacuum的效率非常低!
1 auto_vacuum
PRAGMA auto_vacuum;
PRAGMA auto_vacuum = 0 | 1;
查詢或設(shè)置數(shù)據(jù)庫的auto-vacuum標(biāo)記。
正常情況下,當(dāng)提交一個(gè)從數(shù)據(jù)庫中刪除數(shù)據(jù)的事務(wù)時(shí),數(shù)據(jù)庫文件不改變大小。未使用的文件頁被標(biāo)記并在以后的添加操作中再次使用。這種情況下使用VACUUM命令釋放刪除得到的空間。
當(dāng)開啟auto-vacuum,當(dāng)提交一個(gè)從數(shù)據(jù)庫中刪除數(shù)據(jù)的事務(wù)時(shí),數(shù)據(jù)庫文件自動(dòng)收縮, (VACUUM命令在auto-vacuum開啟的數(shù)據(jù)庫中不起作用)。數(shù)據(jù)庫會(huì)在內(nèi)部存儲(chǔ)一些信息以便支持這一功能,這使得數(shù)據(jù)庫文件比不開啟該選項(xiàng)時(shí)稍微大一些。
只有在數(shù)據(jù)庫中未建任何表時(shí)才能改變auto-vacuum標(biāo)記。試圖在已有表的情況下修改不會(huì)導(dǎo)致報(bào)錯(cuò)。
2 cache_size
建議改為8000
PRAGMA cache_size;
PRAGMA cache_size = Number-of-pages;
查詢或修改SQLite一次存儲(chǔ)在內(nèi)存中的數(shù)據(jù)庫文件頁數(shù)。每頁使用約1.5K內(nèi)存,缺省的緩存大小是2000. 若需要使用改變大量多行的UPDATE或DELETE命令,并且不介意SQLite使用更多的內(nèi)存的話,可以增大緩存以提高性能。
當(dāng)使用cache_size pragma改變緩存大小時(shí),改變僅對(duì)當(dāng)前對(duì)話有效,當(dāng)數(shù)據(jù)庫關(guān)閉重新打開時(shí)緩存大小恢復(fù)到缺省大小。 要想永久改變緩存大小,使用default_cache_size pragma.
3 case_sensitive_like
打開。不然搜索中文字串會(huì)出錯(cuò)。
PRAGMA case_sensitive_like;
PRAGMA case_sensitive_like = 0 | 1;
LIKE運(yùn)算符的缺省行為是忽略latin1字符的大小寫。因此在缺省情況下'a' LIKE 'A'的值為真??梢酝ㄟ^打開case_sensitive_like pragma來改變這一缺省行為。當(dāng)啟用case_sensitive_like,'a' LIKE 'A'為假而 'a' LIKE 'a'依然為真。
4 count_changes
打開。便于調(diào)試
PRAGMA count_changes;
PRAGMA count_changes = 0 | 1;
查詢或更改count-changes標(biāo)記。正常情況下INSERT, UPDATE和DELETE語句不返回?cái)?shù)據(jù)。 當(dāng)開啟count-changes,以上語句返回一行含一個(gè)整數(shù)值的數(shù)據(jù)——該語句插入,修改或刪除的行數(shù)。 返回的行數(shù)不包括由觸發(fā)器產(chǎn)生的插入,修改或刪除等改變的行數(shù)。
5 page_size
PRAGMA page_size;
PRAGMA page_size = bytes;
查詢或設(shè)置page-size值。只有在未創(chuàng)建數(shù)據(jù)庫時(shí)才能設(shè)置page-size。頁面大小必須是2的整數(shù)倍且大于等于512小于等于8192。 上限可以通過在編譯時(shí)修改宏定義SQLITE_MAX_PAGE_SIZE的值來改變。上限的上限是32768.
6 synchronous
如果有定期備份的機(jī)制,而且少量數(shù)據(jù)丟失可接受,用OFF
PRAGMA synchronous;
PRAGMA synchronous = FULL; (2)
PRAGMA synchronous = NORMAL; (1)
PRAGMA synchronous = OFF; (0)
查詢或更改"synchronous"標(biāo)記的設(shè)定。第一種形式(查詢)返回整數(shù)值。 當(dāng)synchronous設(shè)置為FULL (2), SQLite數(shù)據(jù)庫引擎在緊急時(shí)刻會(huì)暫停以確定數(shù)據(jù)已經(jīng)寫入磁盤。 這使系統(tǒng)崩潰或電源出問題時(shí)能確保數(shù)據(jù)庫在重起后不會(huì)損壞。FULL synchronous很安全但很慢。 當(dāng)synchronous設(shè)置為NORMAL, SQLite數(shù)據(jù)庫引擎在大部分緊急時(shí)刻會(huì)暫停,但不像FULL模式下那么頻繁。 NORMAL模式下有很小的幾率(但不是不存在)發(fā)生電源故障導(dǎo)致數(shù)據(jù)庫損壞的情況。但實(shí)際上,在這種情況下很可能你的硬盤已經(jīng)不能使用,或者發(fā)生了其他的不可恢復(fù)的硬件錯(cuò)誤。 設(shè)置為synchronous OFF (0)時(shí),SQLite在傳遞數(shù)據(jù)給系統(tǒng)以后直接繼續(xù)而不暫停。若運(yùn)行SQLite的應(yīng)用程序崩潰, 數(shù)據(jù)不會(huì)損傷,但在系統(tǒng)崩潰或?qū)懭霐?shù)據(jù)時(shí)意外斷電的情況下數(shù)據(jù)庫可能會(huì)損壞。另一方面,在synchronous OFF時(shí) 一些操作可能會(huì)快50倍甚至更多。
在SQLite 2中,缺省值為NORMAL.而在3中修改為FULL.
7 temp_store
使用2,內(nèi)存模式。
PRAGMA temp_store;
PRAGMA temp_store = DEFAULT; (0)
PRAGMA temp_store = FILE; (1)
PRAGMA temp_store = MEMORY; (2)
查詢或更改"temp_store"參數(shù)的設(shè)置。當(dāng)temp_store設(shè)置為DEFAULT (0),使用編譯時(shí)的C預(yù)處理宏 TEMP_STORE來定義儲(chǔ)存臨時(shí)表和臨時(shí)索引的位置。當(dāng)設(shè)置為MEMORY (2)臨時(shí)表和索引存放于內(nèi)存中。 當(dāng)設(shè)置為FILE (1)則存放于文件中。temp_store_directorypragma 可用于指定存放該文件的目錄。當(dāng)改變temp_store設(shè)置,所有已存在的臨時(shí)表,索引,觸發(fā)器及視圖將被立即刪除。
經(jīng)測(cè)試,在類BBS應(yīng)用上,通過以上調(diào)整,效率可以提高2倍以上。
六、后記
(原文后記)
寫此教程,可不是一個(gè)累字能解釋。
但是我還是覺得欣慰的,因?yàn)槲液芫靡郧熬拖雽?sqlite 的教程,一來自己備忘,二而已造福大眾,大家不用再走彎路。
本人第一次寫教程,不足的地方請(qǐng)大家指出。
本文可隨意轉(zhuǎn)載、修改、引用。但無論是轉(zhuǎn)載、修改、引用,都請(qǐng)附帶我的名字:董淳光。以示對(duì)我勞動(dòng)的肯定。
(補(bǔ)充后記)