轉載自網友董淳光的文章。算法
前序:sql
這裏要註明,我是一個跨平臺專一者,並不喜歡只用 windows 平臺。我之前的工做就是爲 unix 平臺寫代碼。下面我所寫的東西,雖然沒有驗證,可是我已儘可能不使用任何 windows 的東西,只使用標準 C 或標準C++。可是,我沒有嘗試過在別的系統、別的編譯器下編譯,所以下面的敘述若是不正確,則留待之後修改。數據庫
下面個人代碼仍然用 VC 編寫,由於我以爲VC是一個很不錯的IDE,能夠加快代碼編寫速度(例如配合 Vassist )。下面我所說的編譯環境,是VC2003。若是讀者以爲本身習慣於 unix 下用 vi 編寫代碼速度較快,能夠不用管個人說明,只須要符合本身習慣便可,由於我用的是標準 C 或 C++ 。不會給任何人帶來不便。
1、版本windows
從 www.sqlite.org 網站可下載到最新的 sqlite 代碼和編譯版本。我寫此文章時,最新代碼是 3.3.17 版本。數組
好久沒有去下載 sqlite 新代碼,所以也不知道 sqlite 變化這麼大。之前不少文件,如今所有合併成一個 sqlite3.c 文件。若是單獨用此文件,是挺好的,省去拷貝一堆文件還擔憂有沒有遺漏。可是也帶來一個問題:此文件太大,快接近7萬行代碼,VC開它整個機器都慢下來了。若是不須要改它代碼,也就不須要打開 sqlite3.c 文件,機器不會慢。可是,下面我要寫經過修改 sqlite 代碼完成加密功能,那時候就比較痛苦了。若是我的水平較高,建議用些簡單的編輯器來編輯,例如UltraEdit 或 Notepad 。速度會快不少。
2、基本編譯緩存
這個不想多說了,在 VC 裏新建 dos 控制檯空白工程,把 sqlite3.c 和 sqlite3.h 添加到工程,再新建一個 main.cpp文件。在裏面寫:安全
1
2
3
4
5
6
7
8
|
extern
"C"
{
#include "./sqlite3.h"
};
int
main(
int
,
char
** )
{
return
0;
}
|
爲何要 extern 「C」 ?若是問這個問題,我不想說太多,這是C++的基礎。要在 C++ 裏使用一段 C 的代碼,必需要用 extern 「C」 括起來。C++跟 C雖然語法上有重疊,可是它們是兩個不一樣的東西,內存裏的佈局是徹底不一樣的,在C++編譯器裏不用extern 「C」括起C代碼,會致使編譯器不知道該如何爲 C 代碼描述內存佈局。性能優化
可能在 sqlite3.c 里人家已經把整段代碼都 extern 「C」 括起來了,可是你遇到一個 .c 文件就自覺的再括一次,也沒什麼很差。
基本工程就這樣創建起來了。編譯,能夠經過。可是有一堆的 warning。能夠無論它。
3、SQLITE操做入門數據結構
sqlite提供的是一些C函數接口,你能夠用這些函數操做數據庫。經過使用這些接口,傳遞一些標準 sql 語句(以 char * 類型)給 sqlite 函數,sqlite 就會爲你操做數據庫。
sqlite 跟MS的access同樣是文件型數據庫,就是說,一個數據庫就是一個文件,此數據庫裏能夠創建不少的表,能夠創建索引、觸發器等等,可是,它實際上獲得的就是一個文件。備份這個文件就備份了整個數據庫。
sqlite 不須要任何數據庫引擎,這意味着若是你須要 sqlite 來保存一些用戶數據,甚至都不須要安裝數據庫(若是你作個小軟件還要求人家必須裝了sqlserver 才能運行,那也太黑心了)。編輯器
下面開始介紹數據庫基本操做。
1 基本流程(1)關鍵數據結構
sqlite 裏最經常使用到的是 sqlite3 * 類型。從數據庫打開開始,sqlite就要爲這個類型準備好內存,直到數據庫關閉,整個過程都須要用到這個類型。當數據庫打開時開始,這個類型的變量就表明了你要操做的數據庫。下面再詳細介紹。
(2)打開數據庫
int sqlite3_open( 文件名, sqlite3 ** );
用這個函數開始數據庫操做。
須要傳入兩個參數,一是數據庫文件名,好比:c://DongChunGuang_Database.db。
文件名不須要必定存在,若是此文件不存在,sqlite 會自動創建它。若是它存在,就嘗試把它當數據庫文件來打開。
sqlite3 ** 參數即前面提到的關鍵數據結構。這個結構底層細節如何,你不要關它。
函數返回值表示操做是否正確,若是是 SQLITE_OK 則表示操做正常。相關的返回值sqlite定義了一些宏。具體這些宏的含義能夠參考 sqlite3.h 文件。裏面有詳細定義(順便說一下,sqlite3 的代碼註釋率自稱是很是高的,實際上也的確很高。只要你會看英文,sqlite 可讓你學到很多東西)。
下面介紹關閉數據庫後,再給一段參考代碼。
(3)關閉數據庫
int sqlite3_close(sqlite3 *);
前面若是用 sqlite3_open 開啓了一個數據庫,結尾時不要忘了用這個函數關閉數據庫。
下面給段簡單的代碼:
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關鍵結構指針
int
result;
//打開數據庫
//須要傳入 db 這個指針的指針,由於 sqlite3_open 函數要爲這個指針分配內存,還要讓db指針指向這個內存區
result = sqlite3_open( 「c:
//Dcg_database.db」, &db );
if
( result != SQLITE_OK )
{
//數據庫打開失敗
return
-1;
}
//數據庫操做代碼
//…
//數據庫打開成功
//關閉數據庫
sqlite3_close( db );
return
0;
}
|
這就是一次數據庫操做過程。
2 SQL語句操做
本節介紹如何用sqlite 執行標準 sql 語法。
(1)執行sql語句
int sqlite3_exec(sqlite3*, const char *sql, sqlite3_callback, void *, char **errmsg );
這就是執行一條 sql 語句的函數。
第1個參數再也不說了,是前面open函數獲得的指針。說了是關鍵數據結構。
第2個參數const char *sql 是一條 sql 語句,以/0結尾。
第3個參數sqlite3_callback 是回調,當這條語句執行以後,sqlite3會去調用你提供的這個函數。(什麼是回調函數,本身找別的資料學習)
第4個參數void * 是你所提供的指針,你能夠傳遞任何一個指針參數到這裏,這個參數最終會傳到回調函數裏面,若是不須要傳遞指針給回調函數,能夠填NULL。等下咱們再看回調函數的寫法,以及這個參數的使用。
第5個參數char ** errmsg 是錯誤信息。注意是指針的指針。sqlite3裏面有不少固定的錯誤信息。執行 sqlite3_exec 以後,執行失敗時能夠查閱這個指針(直接 printf(「%s/n」,errmsg))獲得一串字符串信息,這串信息告訴你錯在什麼地方。sqlite3_exec函數經過修改你傳入的指針的指針,把你提供的指針指向錯誤提示信息,這樣sqlite3_exec函數外面就能夠經過這個 char*獲得具體錯誤提示。
說明:一般,sqlite3_callback 和它後面的 void * 這兩個位置均可以填 NULL。填NULL表示你不須要回調。好比你作insert 操做,作 delete 操做,就沒有必要使用回調。而當你作 select 時,就要使用回調,由於 sqlite3 把數據查出來,得經過回調告訴你查出了什麼數據。
(2)exec 的回調
typedef int (*sqlite3_callback)(void*,int,char**, char**);
你的回調函數必須定義成上面這個函數的類型。下面給個簡單的例子:
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的回調函數
// sqlite 每查到一條記錄,就調用一次這個回調
int LoadMyInfo( void * para, int n_column, char ** column_value, char ** column_name )
{
//para是你在 sqlite3_exec 裏傳入的 void * 參數
//經過para參數,你能夠傳入一些特殊的指針(好比類指針、結構指針),而後在這裏面強制轉換成對應的類型(這裏面是void*類型,必須強制轉換成你的類型纔可用)。而後操做這些數據
//n_column是這一條記錄有多少個字段 (即這條記錄有多少列)
// char ** column_value 是個關鍵值,查出來的數據都保存在這裏,它其實是個1維數組(不要覺得是2維數組),每個元素都是一個 char * 值,是一個字段內容(用字符串來表示,以/0結尾)
//char ** column_name 跟 column_value是對應的,表示這個字段的字段名稱
//這裏,我不使用 para 參數。忽略它的存在.
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( 「c://Dcg_database.db」, &db );
if( result != SQLITE_OK )
{
//數據庫打開失敗
return -1;
}
//數據庫操做代碼
//建立一個測試表,表名叫 MyTable_1,有2個字段: ID 和 name。其中ID是一個自動增長的類型,之後insert時能夠不去指定這個字段,它會本身從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( 「建立表失敗,錯誤碼:%d,錯誤緣由:%s/n」, result, errmsg );
}
//插入一些記錄
result = sqlite3_exec( db, 「insert into MyTable_1( name ) values ( ‘走路' )」, 0, 0, errmsg );
if(result != SQLITE_OK )
{
printf( 「插入記錄失敗,錯誤碼:%d,錯誤緣由:%s/n」, result, errmsg );
}
result = sqlite3_exec( db, 「insert into MyTable_1( name ) values ( ‘騎單車' )」, 0, 0, errmsg );
if(result != SQLITE_OK )
{
printf( 「插入記錄失敗,錯誤碼:%d,錯誤緣由:%s/n」, result, errmsg );
}
result = sqlite3_exec( db, 「insert into MyTable_1( name ) values ( ‘坐汽車' )」, 0, 0, errmsg );
if(result != SQLITE_OK )
{
printf( 「插入記錄失敗,錯誤碼:%d,錯誤緣由:%s/n」, result, errmsg );
}
//開始查詢數據庫
result = sqlite3_exec( db, 「select * from MyTable_1」, LoadMyInfo, NULL, errmsg );
//關閉數據庫
sqlite3_close( db );
return 0;
}
|
經過上面的例子,應該能夠知道如何打開一個數據庫,如何作數據庫基本操做。
有這些知識,基本上能夠應付不少數據庫操做了。
(3)不使用回調查詢數據庫
上面介紹的 sqlite3_exec 是使用回調來執行 select 操做。還有一個方法能夠直接查詢而不須要回調。可是,我我的感受仍是回調好,由於代碼能夠更加整齊,只不過用回調很麻煩,你得聲明一個函數,若是這個函數是類成員函數,你還不得不把它聲明成 static 的(要問爲何?這又是C++基礎了。C++成員函數實際上隱藏了一個參數:this,C++調用類的成員函數的時候,隱含把類指針當成函數的第一個參數傳遞進去。結果,這形成跟前面說的 sqlite 回調函數的參數不相符。只有當把成員函數聲明成 static 時,它纔沒有多餘的隱含的this參數)。
雖然回調顯得代碼整齊,但有時候你仍是想要非回調的 select 查詢。這能夠經過 sqlite3_get_table 函數作到。
int sqlite3_get_table(sqlite3*, const char *sql, char ***resultp, int *nrow, int *ncolumn, char **errmsg );
第1個參數再也不多說,看前面的例子。
第2個參數是 sql 語句,跟 sqlite3_exec 裏的 sql 是同樣的。是一個很普通的以/0結尾的char *字符串。
第3個參數是查詢結果,它依然一維數組(不要覺得是二維數組,更不要覺得是三維數組)。它內存佈局是:第一行是字段名稱,後面是緊接着是每一個字段的值。下面用例子來講事。
第4個參數是查詢出多少條記錄(即查出多少行)。
第5個參數是多少個字段(多少列)。
第6個參數是錯誤信息,跟前面同樣,這裏很少說了。
下面給個簡單例子:
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 ** 類型,兩個*號
int nRow, nColumn;
int i , j;
int index;
result = sqlite3_open( 「c://Dcg_database.db」, &db );
if( result != SQLITE_OK )
{
//數據庫打開失敗
return -1;
}
//數據庫操做代碼
//假設前面已經建立了 MyTable_1 表
//開始查詢,傳入的 dbResult 已是 char **,這裏又加了一個 & 取地址符,傳遞進去的就成了 char ***
result = sqlite3_get_table( db, 「select * from MyTable_1」, &dbResult, &nRow, &nColumn, &errmsg );
if( SQLITE_OK == result )
{
//查詢成功
index = nColumn; //前面說過 dbResult 前面第一行數據是字段名稱,從 nColumn 索引開始纔是真正的數據
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 的字段值是連續的,從第0索引到第 nColumn - 1索引都是字段名稱,從第 nColumn 索引開始,後面都是字段值,它把一個二維的表(傳統的行列表示法)用一個扁平的形式來表示
}
printf( 「-------/n」 );
}
}
//到這裏,不論數據庫查詢是否成功,都釋放 char** 查詢結果,使用 sqlite 提供的功能來釋放
sqlite3_free_table( dbResult );
//關閉數據庫
sqlite3_close( db );
return 0;
}
|
到這個例子爲止,sqlite3 的經常使用用法都介紹完了。
用以上的方法,再配上 sql 語句,徹底能夠應付絕大多數數據庫需求。
但有一種狀況,用上面方法是沒法實現的:須要insert、select 二進制。當須要處理二進制數據時,上面的方法就沒辦法作到。下面這一節說明如何插入二進制數據
3 操做二進制
sqlite 操做二進制數據須要用一個輔助的數據類型:sqlite3_stmt * 。
這個數據類型記錄了一個「sql語句」。爲何我把 「sql語句」 用雙引號引發來?由於你能夠把 sqlite3_stmt * 所表示的內容當作是 sql語句,可是實際上它不是咱們所熟知的sql語句。它是一個已經把sql語句解析了的、用sqlite本身標記記錄的內部數據結構。
正由於這個結構已經被解析了,因此你能夠往這個語句裏插入二進制數據。固然,把二進制數據插到 sqlite3_stmt 結構裏可不能直接 memcpy ,也不能像 std::string 那樣用 + 號。必須用 sqlite 提供的函數來插入。
(1)寫入二進制
下面說寫二進制的步驟。
要插入二進制,前提是這個表的字段的類型是 blob 類型。我假設有這麼一張表:
create table Tbl_2( ID integer, file_content blob )
首先聲明
sqlite3_stmt * stat;
而後,把一個 sql 語句解析到 stat 結構裏去:
sqlite3_prepare( db, 「insert into Tbl_2( ID, file_content) values( 10, ? )」, -1, &stat, 0 );
上面的函數完成 sql 語句的解析。第一個參數跟前面同樣,是個 sqlite3 * 類型變量,第二個參數是一個 sql 語句。
這個 sql 語句特別之處在於 values 裏面有個 ? 號。在sqlite3_prepare函數裏,?號表示一個未定的值,它的值等下才插入。
第三個參數我寫的是-1,這個參數含義是前面 sql 語句的長度。若是小於0,sqlite會自動計算它的長度(把sql語句當成以/0結尾的字符串)。
第四個參數是 sqlite3_stmt 的指針的指針。解析之後的sql語句就放在這個結構裏。
第五個參數我也不知道是幹什麼的。爲0就能夠了。
若是這個函數執行成功(返回值是 SQLITE_OK 且 stat 不爲NULL ),那麼下面就能夠開始插入二進制數據。
sqlite3_bind_blob( stat, 1, pdata, (int)(length_of_data_in_bytes), NULL ); // pdata爲數據緩衝區,length_of_data_in_bytes爲數據大小,以字節爲單位
這個函數一共有5個參數。
第1個參數:是前面prepare獲得的 sqlite3_stmt * 類型變量。
第2個參數:?號的索引。前面prepare的sql語句裏有一個?號,假若有多個?號怎麼插入?方法就是改變 bind_blob 函數第2個參數。這個參數我寫1,表示這裏插入的值要替換 stat 的第一個?號(這裏的索引從1開始計數,而非從0開始)。若是你有多個?號,就寫多個 bind_blob 語句,並改變它們的第2個參數就替換到不一樣的?號。若是有?號沒有替換,sqlite爲它取值null。
第3個參數:二進制數據起始指針。
第4個參數:二進制數據的長度,以字節爲單位。
第5個參數:是個析夠回調函數,告訴sqlite當把數據處理完後調用此函數來析夠你的數據。這個參數我尚未使用過,所以理解也不深入。可是通常都填NULL,須要釋放的內存本身用代碼來釋放。
bind完了以後,二進制數據就進入了你的「sql語句」裏了。你如今能夠把它保存到數據庫裏:
1
|
int result = sqlite3_step( stat );
|
經過這個語句,stat 表示的sql語句就被寫到了數據庫裏。
最後,要把 sqlite3_stmt 結構給釋放:
sqlite3_finalize( stat ); //把剛纔分配的內容析構掉
(2)讀出二進制
下面說讀二進制的步驟。
跟前面同樣,先聲明 sqlite3_stmt * 類型變量:
1
|
sqlite3_stmt * stat;
|
而後,把一個 sql 語句解析到 stat 結構裏去:
1
|
sqlite3_prepare( db, 「select * from Tbl_2」, -1, &stat, 0 );
|
當 prepare 成功以後(返回值是 SQLITE_OK ),開始查詢數據。
1
|
int result = sqlite3_step( stat );
|
這一句的返回值是SQLITE_ROW 時表示成功(不是 SQLITE_OK )。
你能夠循環執行sqlite3_step 函數,一次step查詢出一條記錄。直到返回值不爲 SQLITE_ROW 時表示查詢結束。
而後開始獲取第一個字段:ID 的值。ID是個整數,用下面這個語句獲取它的值:
int id = sqlite3_column_int( stat, 0 ); //第2個參數表示獲取第幾個字段內容,從0開始計算,由於個人表的ID字段是第一個字段,所以這裏我填0
下面開始獲取 file_content 的值,由於 file_content 是二進制,所以我須要獲得它的指針,還有它的長度:
1
2
|
const void * pFileContent = sqlite3_column_blob( stat, 1 );
int len = sqlite3_column_bytes( stat, 1 );
|
這樣就獲得了二進制的值。
把 pFileContent 的內容保存出來以後,不要忘了釋放 sqlite3_stmt 結構:
sqlite3_finalize( stat ); //把剛纔分配的內容析構掉
(3)重複使用 sqlite3_stmt 結構
若是你須要重複使用 sqlite3_prepare 解析好的 sqlite3_stmt 結構,須要用函數: sqlite3_reset。
1
|
result = sqlite3_reset(stat);
|
這樣, stat 結構又成爲 sqlite3_prepare 完成時的狀態,你能夠從新爲它 bind 內容。
4 事務處理
sqlite 是支持事務處理的。若是你知道你要同步刪除不少數據,不仿把它們作成一個統一的事務。
一般一次 sqlite3_exec 就是一次事務,若是你要刪除1萬條數據,sqlite就作了1萬次:開始新事務->刪除一條數據->提交事務->開始新事務->… 的過程。這個操做是很慢的。由於時間都花在了開始事務、提交事務上。
你能夠把這些同類操做作成一個事務,這樣若是操做錯誤,還可以回滾事務。
事務的操做沒有特別的接口函數,它就是一個普通的 sql 語句而已:
分別以下:
1
2
3
4
|
int result;
result = sqlite3_exec( db, "begin transaction", 0, 0, &zErrorMsg ); //開始一個事務
result = sqlite3_exec( db, "commit transaction", 0, 0, &zErrorMsg ); //提交事務
result = sqlite3_exec( db, "rollback transaction", 0, 0, &zErrorMsg ); //回滾事務
|
4、C/C++開發接口簡介1 總覽
SQLite3是SQLite一個全新的版本,它雖然是在SQLite 2.8.13的代碼基礎之上開發的,可是使用了和以前的版本不兼容的數據庫格式和API. SQLite3是爲了知足如下的需求而開發的:
支持UTF-16編碼.
用戶自定義的文本排序方法.
能夠對BLOBs字段創建索引.
所以爲了支持這些特性我改變了數據庫的格式,創建了一個與以前版本不兼容的3.0版. 至於其餘的兼容性的改變,例如全新的API等等,都將在理論介紹以後向你說明,這樣可使你最快的一次性擺脫兼容性問題.
3.0版的和2.X版的API很是類似,可是有一些重要的改變須要注意. 全部API接口函數和數據結構的前綴都由"sqlite_"改成了"sqlite3_". 這是爲了不同時使用SQLite 2.X和SQLite 3.0這兩個版本的時候發生連接衝突.
因爲對於C語言應該用什麼數據類型來存放UTF-16編碼的字符串並無一致的規範. 所以SQLite使用了普通的void* 類型來指向UTF-16編碼的字符串. 客戶端使用過程當中能夠把void*映射成適合他們的系統的任何數據類型.
2 C/C++接口
SQLite 3.0一共有83個API函數,此外還有一些數據結構和預約義(#defines). (完整的API介紹請參看另外一份文檔.) 不過大家能夠放心,這些接口使用起來不會像它的數量所暗示的那麼複雜. 最簡單的程序仍然使用三個函數就能夠完成: sqlite3_open(), sqlite3_exec(), 和 sqlite3_close(). 要是想更好的控制數據庫引擎的執行,可使用提供的sqlite3_prepare()函數把SQL語句編譯成字節碼,而後在使用sqlite3_step()函數來執行編譯後的字節碼. 以sqlite3_column_開頭的一組API函數用來獲取查詢結果集中的信息. 許多接口函數都是成對出現的,同時有UTF-8和UTF-16兩個版本. 而且提供了一組函數用來執行用戶自定義的SQL函數和文本排序函數.
(1)如何打開關閉數據庫
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() 函數返回一個整數錯誤代碼,而不是像第二版中同樣返回一個指向sqlite3結構體的指針. sqlite3_open() 和sqlite3_open16() 的不一樣之處在於sqlite3_open16() 使用UTF-16編碼(使用本地主機字節順序)傳遞數據庫文件名. 若是要建立新數據庫, sqlite3_open16() 將內部文本轉換爲UTF-16編碼, 反之sqlite3_open() 將文本轉換爲UTF-8編碼.
打開或者建立數據庫的命令會被緩存,直到這個數據庫真正被調用的時候纔會被執行. 並且容許使用PRAGMA聲明來設置如本地文本編碼或默認內存頁面大小等選項和參數.
sqlite3_errcode() 一般用來獲取最近調用的API接口返回的錯誤代碼. sqlite3_errmsg() 則用來獲得這些錯誤代碼所對應的文字說明. 這些錯誤信息將以 UTF-8 的編碼返回,而且在下一次調用任何SQLite API函數的時候被清除. sqlite3_errmsg16() 和sqlite3_errmsg() 大致上相同,除了返回的錯誤信息將以 UTF-16 本機字節順序編碼.
SQLite3的錯誤代碼相比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)執行 SQL 語句
typedef int (*sqlite_callback)(void*,int,char**, char**);
int sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void*, char**);
sqlite3_exec 函數依然像它在SQLite2中同樣承擔着不少的工做. 該函數的第二個參數中能夠編譯和執行零個或多個SQL語句. 查詢的結果返回給回調函數. 更多地信息能夠查看API 參考.
在SQLite3裏,sqlite3_exec通常是被準備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語句編譯成字節碼留給後面的執行函數. 使用該接口訪問數據庫是當前比較好的的一種方法.
sqlite3_prepare() 處理的SQL語句應該是UTF-8編碼的. 而sqlite3_prepare16() 則要求是UTF-16編碼的. 輸入的參數中只有第一個SQL語句會被編譯. 第四個參數則用來指向輸入參數中下一個須要編譯的SQL語句存放的SQLite statement對象的指針,任什麼時候候若是調用 sqlite3_finalize() 將銷燬一個準備好的SQL聲明. 在數據庫關閉以前,全部準備好的聲明都必須被釋放銷燬. sqlite3_reset() 函數用來重置一個SQL聲明的狀態,使得它能夠被再次執行.
SQL聲明能夠包含一些型如"?" 或 "?nnn" 或 ":aaa"的標記, 其中"nnn" 是一個整數,"aaa" 是一個字符串. 這些標記表明一些不肯定的字符值(或者說是通配符),能夠在後面用sqlite3_bind 接口來填充這些值. 每個通配符都被分配了一個編號(由它在SQL聲明中的位置決定,從1開始),此外也能夠用 "nnn" 來表示 "?nnn" 這種狀況. 容許相同的通配符在同一個SQL聲明中出現屢次, 在這種狀況下全部相同的通配符都會被替換成相同的值. 沒有被綁定的通配符將自動取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聲明中的通配符賦值的. 沒有綁定的通配符則被認爲是空值.綁定上的值不會被sqlite3_reset()函數重置. 可是在調用了sqlite3_reset()以後全部的通配符均可以被從新賦值.
在SQL聲明準備好以後(其中綁定的步驟是可選的), 須要調用如下的方法來執行:
int sqlite3_step(sqlite3_stmt*);
若是SQL返回了一個單行結果集,sqlite3_step() 函數將返回 SQLITE_ROW , 若是SQL語句執行成功或者正常將返回SQLITE_DONE , 不然將返回錯誤代碼. 若是不能打開數據庫文件則會返回 SQLITE_BUSY . 若是函數的返回值是SQLITE_ROW, 那麼下邊的這些方法能夠用來得到記錄集行中的數據:
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()函數返回結果集中包含的列數. sqlite3_column_count() 能夠在執行了 sqlite3_prepare()以後的任什麼時候刻調用. sqlite3_data_count()除了必須要在sqlite3_step()以後調用以外,其餘跟sqlite3_column_count() 大同小異. 若是調用sqlite3_step() 返回值是 SQLITE_DONE 或者一個錯誤代碼, 則此時調用sqlite3_data_count() 將返回 0 ,然而sqlite3_column_count() 仍然會返回結果集中包含的列數.
返回的記錄集經過使用其它的幾個 sqlite3_column_***() 函數來提取, 全部的這些函數都把列的編號做爲第二個參數. 列編號從左到右以零起始. 請注意它和以前那些從1起始的參數的不一樣.
sqlite3_column_type()函數返回第N列的值的數據類型. 具體的返回值以下:
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 語句中聲明的類型. 它能夠用在當返回類型是空字符串的時候. sqlite3_column_name() 返回第N列的字段名. sqlite3_column_bytes() 用來返回 UTF-8 編碼的BLOBs列的字節數或者TEXT字符串的字節數. sqlite3_column_bytes16() 對於BLOBs列返回一樣的結果,可是對於TEXT字符串則按 UTF-16 的編碼來計算字節數. sqlite3_column_blob() 返回 BLOB 數據. sqlite3_column_text() 返回 UTF-8 編碼的 TEXT 數據. sqlite3_column_text16() 返回 UTF-16 編碼的 TEXT 數據. sqlite3_column_int() 以本地主機的整數格式返回一個整數值. sqlite3_column_int64() 返回一個64位的整數. 最後, sqlite3_column_double() 返回浮點數.
不必定非要按照sqlite3_column_type()接口返回的數據類型來獲取數據. 數據類型不一樣時軟件將自動轉換.
(3)用戶自定義函數
可使用如下的方法來建立用戶自定義的SQL函數:
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 參數用來代表自定義函數的參數個數. 若是參數值爲0,則表示接受任意個數的參數. 用 eTextRep 參數來代表傳入參數的編碼形式. 參數值能夠是上面的五種預約義值. SQLite3 容許同一個自定義函數有多種不一樣的編碼參數的版本. 數據庫引擎會自動選擇轉換參數編碼個數最少的版本使用.
普通的函數只須要設置 xFunc 參數,而把 xStep 和 xFinal 設爲NULL. 聚合函數則須要設置 xStep 和 xFinal 參數,而後把 xFunc 設爲NULL. 該方法和使用sqlite3_create_aggregate() API同樣.
sqlite3_create_function16()和sqlite_create_function()的不一樣就在於自定義的函數名一個要求是 UTF-16 編碼,而另外一個則要求是 UTF-8.
請注意自定函數的參數目前使用了sqlite3_value結構體指針替代了SQLite version 2.X中的字符串指針. 下面的函數用來從sqlite3_value結構體中提取數據:
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*);
|
上面的函數調用如下的API來得到上下文內容和返回結果:
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)用戶自定義排序規則
下面的函數用來實現用戶自定義的排序規則:
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() 函數用來聲明一個排序序列和實現它的比較函數. 比較函數只能用來作文本的比較. eTextRep 參數能夠取以下的預約義值 SQLITE_UTF8, SQLITE_UTF16LE, SQLITE_UTF16BE, SQLITE_ANY,用來表示比較函數所處理的文本的編碼方式. 同一個自定義的排序規則的同一個比較函數能夠有 UTF-8, UTF-16LE 和 UTF-16BE 等多個編碼的版本. sqlite3_create_collation16()和sqlite3_create_collation() 的區別也僅僅在於排序名稱的編碼是 UTF-16 仍是 UTF-8.
可使用 sqlite3_collation_needed() 函數來註冊一個回調函數,當數據庫引擎遇到未知的排序規則時會自動調用該函數. 在回調函數中能夠查找一個類似的比較函數,並激活相應的sqlite_3_create_collation()函數. 回調函數的第四個參數是排序規則的名稱,一樣sqlite3_collation_needed採用 UTF-8 編碼. sqlite3_collation_need16() 採用 UTF-16 編碼.
5、給數據庫加密
前面所說的內容網上已經有不少資料,雖然比較零散,可是花點時間也仍是能夠找到的。如今要說的這個——數據庫加密,資料就很難找。也多是我操做水平不夠,找不到對應資料。但無論這樣,我仍是經過網上能找到的頗有限的資料,探索出了給sqlite數據庫加密的完整步驟。
這裏要提一下,雖然 sqlite 很好用,速度快、體積小巧。可是它保存的文件倒是明文的。若不信能夠用 NotePad 打開數據庫文件瞧瞧,裏面 insert 的內容幾乎盡收眼底。這樣赤裸裸的展示本身,可不是咱們的初衷。固然,若是你在嵌入式系統、智能手機上使用 sqlite,最好是不加密,由於這些系統運算能力有限,你作爲一個新功能提供者,不能把用戶有限的運算能力所有花掉。
Sqlite爲了速度而誕生。所以Sqlite自己不對數據庫加密,要知道,若是你選擇標準AES算法加密,那麼必定有接近50%的時間消耗在加解密算法上,甚至更多(性能主要取決於你算法編寫水平以及你是否能使用cpu提供的底層運算能力,好比MMX或sse系列指令能夠大幅度提高運算速度)。
Sqlite免費版本是不提供加密功能的,固然你也能夠選擇他們的收費版本,那你得支付2000塊錢,並且是USD。我這裏也不是說支付錢很差,若是隻爲了數據庫加密就去支付2000塊,我以爲划不來。由於下面我將要告訴你如何爲免費的Sqlite擴展出加密模塊——本身動手擴展,這是Sqlite容許,也是它提倡的。
那麼,就讓咱們一塊兒開始爲 sqlite3.c 文件擴展出加密模塊。
1 必要的宏
經過閱讀 Sqlite 代碼(固然沒有所有閱讀完,6萬多行代碼,沒有一行是我習慣的風格,我可沒那麼多眼神去看),我搞清楚了兩件事:
Sqlite是支持加密擴展的;
須要 #define 一個宏才能使用加密擴展。
這個宏就是 SQLITE_HAS_CODEC。
你在代碼最前面(也能夠在 sqlite3.h 文件第一行)定義:
#ifndef SQLITE_HAS_CODEC
#define SQLITE_HAS_CODEC
#endif
若是你在代碼裏定義了此宏,可是還可以正常編譯,那麼應該是操做沒有成功。由於你應該會被編譯器提示有一些函數沒法連接纔對。若是你用的是 VC 2003,你能夠在「解決方案」裏右鍵點擊你的工程,而後選「屬性」,找到「C/C++」,再找到「命令行」,在裏面手工添加「/D "SQLITE_HAS_CODEC"」。
定義了這個宏,一些被 Sqlite 故意屏蔽掉的代碼就被使用了。這些代碼就是加解密的接口。
嘗試編譯,vc會提示你有一些函數沒法連接,由於找不到他們的實現。
若是你也用的是VC2003,那麼會獲得下面的提示:
error LNK2019: 沒法解析的外部符號 _sqlite3CodecGetKey ,該符號在函數 _attachFunc 中被引用
error LNK2019: 沒法解析的外部符號 _sqlite3CodecAttach ,該符號在函數 _attachFunc 中被引用
error LNK2019: 沒法解析的外部符號 _sqlite3_activate_see ,該符號在函數 _sqlite3Pragma 中被引用
error LNK2019: 沒法解析的外部符號 _sqlite3_key ,該符號在函數 _sqlite3Pragma 中被引用
fatal error LNK1120: 4 個沒法解析的外部命令
這是正常的,由於Sqlite只留了接口而已,並無給出實現。
下面就讓我來實現這些接口。
2本身實現加解密接口函數
若是真要我從一份 www.sqlite.org 網上down下來的 sqlite3.c 文件,直接摸索出這些接口的實現,我認爲我尚未這個能力。
好在網上還有一些代碼已經實現了這個功能。經過參照他們的代碼以及不斷編譯中vc給出的錯誤提示,最終我把整個接口整理出來。
實現這些預留接口不是那麼容易,要重頭說一次怎麼回事很困難。我把代碼都寫好了,直接把他們按我下面的說明拷貝到 sqlite3.c 文件對應地方便可。我在下面也提供了sqlite3.c 文件,能夠直接參考或取下來使用。
這裏要說一點的是,我另外新建了兩個文件: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 加密關鍵函數庫
***********/
/***********
關鍵加密函數
***********/
int My_Encrypt_Func( unsigned char * pData, unsigned int data_len, const char * key, unsigned int len_of_key );
/***********
關鍵解密函數
***********/
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"
/***********
關鍵加密函數
***********/
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 intlen_of_key )
{
return 0;
}
|
這個文件很容易看,就兩函數,一個加密一個解密。傳進來的參數分別是待處理的數據、數據長度、密鑰、密鑰長度。
處理時直接把結果做用於 pData 指針指向的內容。
你須要定義本身的加解密過程,就改動這兩個函數,其它部分不用動。擴展起來很簡單。
這裏有個特色,data_len 通常老是 1024 字節。正由於如此,你能夠在你的算法裏使用一些特定長度的加密算法,好比AES要求被加密數據必定是128位(16字節)長。這個1024不是碰巧,而是 Sqlite 的頁定義是1024字節,在sqlite3.c文件裏有定義:
# define SQLITE_DEFAULT_PAGE_SIZE 1024
你能夠改動這個值,不過仍是建議沒有必要不要去改它。
上面寫了兩個擴展函數,如何把擴展函數跟 Sqlite 掛接起來,這個過程提及來比較麻煩。我直接貼代碼。
分3個步驟。
首先,在 sqlite3.c 文件頂部,添加下面內容:
1
2
3
|
#ifdef SQLITE_HAS_CODEC
#include "./crypt.h"
/***********
|
用於在 sqlite3 最後關閉時釋放一些內存
1
2
3
|
***********/
void sqlite3pager_free_codecarg(void *pArg);
#endif
|
這個函數之因此要在 sqlite3.c 開頭聲明,是由於下面在 sqlite3.c 裏面某些函數裏要插入這個函數調用。因此要提早聲明。
其次,在sqlite3.c文件裏搜索「sqlite3PagerClose」函數,要找到它的實現代碼(而不是聲明代碼)。
實現代碼裏一開始是:
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 函數大概也是 3.3.17版本左右才更名的,之前版本里是叫 「sqlite3pager_close」。所以你在老版本sqlite代碼裏搜索「sqlite3PagerClose」是搜不到的。
相似的還有「sqlite3pager_get」、「sqlite3pager_unref」、「sqlite3pager_write」、「sqlite3pager_pagecount」等都是老版本函數,它們在 pager.h 文件裏定義。新版本對應函數是在 sqlite3.h 裏定義(由於都合併到 sqlite3.c和sqlite3.h兩文件了)。因此,若是你在使用老版本的sqlite,先看看 pager.h 文件,這些函數不是消失了,也不是新蹦出來的,而是老版本函數更名獲得的。
最後,往sqlite3.c 文件下找。找到最後一行:
/************** End of main.c ************************************************/
在這一行後面,接上本文最下面的代碼段。
這些代碼很長,我再也不解釋,直接接上去就得了。
惟一要提的是 DeriveKey 函數。這個函數是對密鑰的擴展。好比,你要求密鑰是128位,便是16字節,可是若是用戶只輸入 1個字節呢?2個字節呢?或輸入50個字節呢?你得對密鑰進行擴展,使之符合16字節的要求。
DeriveKey 函數就是作這個擴展的。有人把接收到的密鑰求md5,這也是一個辦法,由於md5運算結果固定16字節,不論你有多少字符,最後就是16字節。這是md5算法的特色。可是我不想用md5,由於還得爲它添加包含一些 md5 的.c或.cpp文件。我不想這麼作。我本身寫了一個算法來擴展密鑰,很簡單的算法。固然,你也可使用你的擴展方法,也而可使用md5 算法。只要修改 DeriveKey 函數就能夠了。
在 DeriveKey 函數裏,只管申請空間構造所須要的密鑰,不須要釋放,由於在另外一個函數裏有釋放過程,而那個函數會在數據庫關閉時被調用。參考個人 DeriveKey 函數來申請內存。
這裏我給出我已經修改好的 sqlite3.c 和 sqlite3.h 文件。
若是太懶,就直接使用這兩個文件,編譯確定能經過,運行也正常。固然,你必須按我前面提的,新建 crypt.h 和crypt.c 文件,並且函數要按我前面定義的要求來作。
3 加密使用方法
如今,你代碼已經有了加密功能。
你要把加密功能給用上,除了改 sqlite3.c 文件、給你工程添加 SQLITE_HAS_CODEC 宏,還得修改你的數據庫調用函數。
前面提到過,要開始一個數據庫操做,必須先 sqlite3_open 。
加解密過程就在 sqlite3_open 後面操做。
假設你已經 sqlite3_open 成功了,緊接着寫下面的代碼:
int i;
//添加、使用密碼
i = sqlite3_key( db, "dcg", 3 );
//修改密碼
i = sqlite3_rekey( db, "dcg", 0 );
用 sqlite3_key 函數來提交密碼。
第1個參數是 sqlite3 * 類型變量,表明着用 sqlite3_open 打開的數據庫(或新建數據庫)。
第2個參數是密鑰。
第3個參數是密鑰長度。
用 sqlite3_rekey 來修改密碼。參數含義同 sqlite3_key。
實際上,你能夠在sqlite3_open函數以後,到 sqlite3_close 函數以前任意位置調用 sqlite3_key 來設置密碼。
可是若是你沒有設置密碼,而數據庫以前是有密碼的,那麼你作任何操做都會獲得一個返回值:SQLITE_NOTADB,而且獲得錯誤提示:「file is encrypted or is not a database」。
只有當你用 sqlite3_key 設置了正確的密碼,數據庫纔會正常工做。
若是你要修改密碼,前提是你必須先 sqlite3_open 打開數據庫成功,而後 sqlite3_key 設置密鑰成功,以後才能用sqlite3_rekey 來修改密碼。
若是數據庫有密碼,但你沒有用 sqlite3_key 設置密碼,那麼當你嘗試用 sqlite3_rekey 來修改密碼時會獲得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
|
/***
董淳光定義的加密函數
***/
#ifdef SQLITE_HAS_CODEC
/***
加密結構
***/
#define CRYPT_OFFSET 8
typedef struct _CryptBlock
{
BYTE* ReadKey; // 讀數據庫和寫入事務的密鑰
BYTE* WriteKey; // 寫入數據庫的密鑰
int PageSize; // 頁的大小
BYTE* Data;
} CryptBlock, *LPCryptBlock;
#ifndef DB_KEY_LENGTH_BYTE /*密鑰長度*/
#define DB_KEY_LENGTH_BYTE 16 /*密鑰長度*/
#endif
#ifndef DB_KEY_PADDING /*密鑰位數不足時補充的字符*/
#define DB_KEY_PADDING 0x33 /*密鑰位數不足時補充的字符*/
#endif
/*** 下面是編譯時提示缺乏的函數 ***/
/** 這個函數不須要作任何處理,獲取密鑰的部分在下面 DeriveKey 函數裏實現 **/
void sqlite3CodecGetKey(sqlite3* db, int nDB, void** Key, int* nKey)
{
return ;
}
/*被sqlite 和 sqlite3_key_interop 調用, 附加密鑰到數據庫.*/
int sqlite3CodecAttach(sqlite3 *db, int nDb, const void *pKey, int nKeyLen);
/**
這個函數好像是 sqlite 3.3.17前不久才加的,之前版本的sqlite裏沒有看到這個函數
這個函數我尚未搞清楚是作什麼的,它裏面什麼都不作直接返回,對加解密沒有影響
**/
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);
/***
下面是上面的函數的輔助處理函數
***/
// 從用戶提供的緩衝區中獲得一個加密密鑰
// 用戶提供的密鑰可能位數上知足不了要求,使用這個函數來完成密鑰擴展
static unsigned char * DeriveKey(const void *pKey, int nKeyLen);
//建立或更新一個頁的加密算法索引.此函數會申請緩衝區.
static LPCryptBlock CreateCryptBlock(unsigned char* hKey, Pager *pager, LPCryptBlock pExisting);
//加密/解密函數, 被pager調用
void * sqlite3Codec(void *pArg, unsigned char *data, Pgno nPageNum, int nMode);
//設置密碼函數
int __stdcall sqlite3_key_interop(sqlite3 *db, const void *pKey, int nKeySize);
// 修改密碼函數
int __stdcall sqlite3_rekey_interop(sqlite3 *db, const void *pKey, int nKeySize);
//銷燬一個加密塊及相關的緩衝區,密鑰.
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 );
//加密/解密函數, 被pager調用
void * sqlite3Codec(void *pArg, unsigned char *data, Pgno nPageNum, int nMode)
{
LPCryptBlock pBlock = (LPCryptBlock)pArg;
unsigned int dwPageSize = 0;
if (!pBlock) return data;
// 確保pager的頁長度和加密塊的頁長度相等.若是改變,就須要調整.
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: //重載一個頁
case 3: //載入一個頁
if (!pBlock->ReadKey) break;
dwPageSize = pBlock->PageSize;
My_DeEncrypt_Func(data, dwPageSize, pBlock->ReadKey, DB_KEY_LENGTH_BYTE ); /*調用個人解密函數*/
break;
case 6: //加密一個主數據庫文件的頁
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 ); /*調用個人加密函數*/
break;
case 7: //加密事務文件的頁
/*在正常環境下, 讀密鑰和寫密鑰相同. 當數據庫是被從新加密的,讀密鑰和寫密鑰未必相同.
回滾事務必要用數據庫文件的原始密鑰寫入.所以,當一次回滾被寫入,老是用數據庫的讀密鑰,
這是爲了保證與讀取原始數據的密鑰相同.
*/
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 ); /*調用個人加密函數*/
break;
}
return data;
}
//銷燬一個加密塊及相關的緩衝區,密鑰.
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 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;
//補充密鑰後面的部分
memset( hKey + nKeyLen, DB_KEY_PADDING, j );
}
else
{ //密鑰位數已經足夠,直接把密鑰取過來
memcpy( hKey, pKey, DB_KEY_LENGTH_BYTE );
}
return hKey;
}
//建立或更新一個頁的加密算法索引.此函數會申請緩衝區.
static LPCryptBlock CreateCryptBlock(unsigned char* hKey, Pager *pager, LPCryptBlock pExisting)
{
LPCryptBlock pBlock;
if (!pExisting) //建立新加密塊
{
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 調用, 附加密鑰到數據庫.*/
int sqlite3CodecAttach(sqlite3 *db, int nDb, const void *pKey, int nKeyLen)
{
int rc = SQLITE_ERROR;
unsigned char* hKey = 0;
//若是沒有指定密匙,可能標識用了主數據庫的加密或沒加密.
if (!pKey || !nKeyLen)
{
if (!nDb)
{
return SQLITE_OK; //主數據庫, 沒有指定密鑰因此沒有加密.
}
else //附加數據庫,使用主數據庫的密鑰.
{
//獲取主數據庫的加密塊並複製密鑰給附加數據庫使用
LPCryptBlock pBlock = (LPCryptBlock)sqlite3pager_get_codecarg(sqlite3BtreePager(db->aDb[0].pBt));
if (!pBlock) return SQLITE_OK; //主數據庫沒有加密
if (!pBlock->ReadKey) return SQLITE_OK; //沒有加密
memcpy(pBlock->ReadKey, &hKey, 16);
}
}
else //用戶提供了密碼,從中建立密鑰.
{
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;
}
// 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;
//從新加密一個數據庫,改變pager的寫密鑰, 讀密鑰依舊保留.
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
|
5、性能優化
不少人直接就使用了,並未注意到SQLite也有配置參數,能夠對性能進行調整。有時候,產生的結果會有很大影響。
主要經過pragma指令來實現。
好比: 空間釋放、磁盤同步、Cache大小等。
不要打開。前文提升了,Vacuum的效率很是低!
1 auto_vacuum
PRAGMA auto_vacuum;
PRAGMA auto_vacuum = 0 | 1;
查詢或設置數據庫的auto-vacuum標記。
正常狀況下,當提交一個從數據庫中刪除數據的事務時,數據庫文件不改變大小。未使用的文件頁被標記並在之後的添加操做中再次使用。這種狀況下使用VACUUM命令釋放刪除獲得的空間。
當開啓auto-vacuum,當提交一個從數據庫中刪除數據的事務時,數據庫文件自動收縮, (VACUUM命令在auto-vacuum開啓的數據庫中不起做用)。數據庫會在內部存儲一些信息以便支持這一功能,這使得數據庫文件比不開啓該選項時稍微大一些。
只有在數據庫中未建任何表時才能改變auto-vacuum標記。試圖在已有表的狀況下修改不會致使報錯。
2 cache_size
建議改成8000
PRAGMA cache_size;
PRAGMA cache_size = Number-of-pages;
查詢或修改SQLite一次存儲在內存中的數據庫文件頁數。每頁使用約1.5K內存,缺省的緩存大小是2000. 若須要使用改變大量多行的UPDATE或DELETE命令,而且不介意SQLite使用更多的內存的話,能夠增大緩存以提升性能。
當使用cache_size pragma改變緩存大小時,改變僅對當前對話有效,當數據庫關閉從新打開時緩存大小恢復到缺省大小。 要想永久改變緩存大小,使用default_cache_size pragma.
3 case_sensitive_like
打開。否則搜索中文字串會出錯。
PRAGMA case_sensitive_like;
PRAGMA case_sensitive_like = 0 | 1;
LIKE運算符的缺省行爲是忽略latin1字符的大小寫。所以在缺省狀況下'a' LIKE 'A'的值爲真。能夠經過打開case_sensitive_like pragma來改變這一缺省行爲。當啓用case_sensitive_like,'a' LIKE 'A'爲假而 'a' LIKE 'a'依然爲真。
4 count_changes
打開。便於調試
PRAGMA count_changes;
PRAGMA count_changes = 0 | 1;
查詢或更改count-changes標記。正常狀況下INSERT, UPDATE和DELETE語句不返回數據。 當開啓count-changes,以上語句返回一行含一個整數值的數據——該語句插入,修改或刪除的行數。 返回的行數不包括由觸發器產生的插入,修改或刪除等改變的行數。
5 page_size
PRAGMA page_size;
PRAGMA page_size = bytes;
查詢或設置page-size值。只有在未建立數據庫時才能設置page-size。頁面大小必須是2的整數倍且大於等於512小於等於8192。 上限能夠經過在編譯時修改宏定義SQLITE_MAX_PAGE_SIZE的值來改變。上限的上限是32768.
6 synchronous
若是有按期備份的機制,並且少許數據丟失可接受,用OFF
PRAGMA synchronous;
PRAGMA synchronous = FULL; (2)
PRAGMA synchronous = NORMAL; (1)
PRAGMA synchronous = OFF; (0)
查詢或更改"synchronous"標記的設定。第一種形式(查詢)返回整數值。 當synchronous設置爲FULL (2), SQLite數據庫引擎在緊急時刻會暫停以肯定數據已經寫入磁盤。 這使系統崩潰或電源出問題時能確保數據庫在重起後不會損壞。FULL synchronous很安全但很慢。 當synchronous設置爲NORMAL, SQLite數據庫引擎在大部分緊急時刻會暫停,但不像FULL模式下那麼頻繁。 NORMAL模式下有很小的概率(但不是不存在)發生電源故障致使數據庫損壞的狀況。但實際上,在這種狀況下極可能你的硬盤已經不能使用,或者發生了其餘的不可恢復的硬件錯誤。 設置爲synchronous OFF (0)時,SQLite在傳遞數據給系統之後直接繼續而不暫停。若運行SQLite的應用程序崩潰, 數據不會損傷,但在系統崩潰或寫入數據時意外斷電的狀況下數據庫可能會損壞。另外一方面,在synchronous OFF時 一些操做可能會快50倍甚至更多。
在SQLite 2中,缺省值爲NORMAL.而在3中修改成FULL.
7 temp_store使用2,內存模式。PRAGMA temp_store; PRAGMA temp_store = DEFAULT; (0) PRAGMA temp_store = FILE; (1) PRAGMA temp_store = MEMORY; (2)查詢或更改"temp_store"參數的設置。當temp_store設置爲DEFAULT (0),使用編譯時的C預處理宏 TEMP_STORE來定義儲存臨時表和臨時索引的位置。當設置爲MEMORY (2)臨時表和索引存放於內存中。 當設置爲FILE (1)則存放於文件中。temp_store_directorypragma 可用於指定存放該文件的目錄。當改變temp_store設置,全部已存在的臨時表,索引,觸發器及視圖將被當即刪除。經測試,在類BBS應用上,經過以上調整,效率能夠提升2倍以上。 6、後記(原文後記)寫此教程,可不是一個累字能解釋。可是我仍是以爲欣慰的,由於我好久之前就想寫 sqlite 的教程,一來本身備忘,二而已造福大衆,你們不用再走彎路。本人第一次寫教程,不足的地方請你們指出。 本文可隨意轉載、修改、引用。但不管是轉載、修改、引用,都請附帶個人名字:董淳光。以示對我勞動的確定。