前序
Sqlite3 的确很好用。小巧、速度快。但是因為非微軟的産品,幫助文檔總覺得不夠。這些天再次研究它,又有一些收獲,這裡把我對 sqlite3 的研究列出來,以備忘記。
這裡要注明,我是一個跨平台專注者,并不喜歡隻用 windows 平台。我以前的工作就是為 unix 平台寫代碼。下面我所寫的東西,雖然沒有驗證,但是我已盡量不使用任何windows 的東西,隻使用标準 C 或标準C++。但是,我沒有嘗試過在别的系統、别的編譯器下編譯,是以下面的叙述如果不正确,則留待以後修改。
下面我的代碼仍然用 VC 編寫,因為我覺得VC是一個很不錯的IDE,可以加快代碼編寫速度(例如配合 Vassist )。下面我所說的編譯環境,是VC2003。如果讀者覺得自己習慣于 unix 下用 vi 編寫代碼速度較快,可以不用管我的說明,隻需要符合自己習慣即可,因為我用的是标準 C 或 C++ 。不會給任何人帶來不便。
一、版本
從 www.sqlite.org<http://www.sqlite.org/> 網站可下載下傳到最新的 sqlite 代碼和編譯版本。我寫此文章時,最新代碼是 3.3.17 版本。
很久沒有去下載下傳 sqlite 新代碼,是以也不知道 sqlite 變化這麼大。以前很多檔案,現在全部合并成一個 sqlite3.c 檔案。如果單獨用此檔案,是挺好的,省去拷貝一堆檔案還擔心有沒有遺漏。但是也帶來一個問題:此檔案太大,快接近7萬行代碼,VC開它整個機器都慢下來了。如果不需要改它代碼,也就不需要打開sqlite3.c檔案,機器不會慢。但是,下面我要寫通過修改 sqlite 代碼完成加密功能,那時候就比較痛苦了。如果個人水準較高,建議用些簡單的編輯器來編輯,例如 UltraEdit 或 Notepad 。速度會快很多。(源碼網整理,www.codepub.com)
二、基本編譯
這個不想多說了,在 VC 裡建立 dos 控制台空白工程,把 sqlite3.c 和sqlite3.h 添加到工程,再建立一個 main.cpp 檔案。在裡面寫:
[cpp] view plain copy
- 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。可以不管它。
三、SQLITE操作入門
sqlite提供的是一些C函數接口,你可以用這些函數操作資料庫。通過使用這些接口,傳遞一些标準sql 語句(以 char * 類型)給 sqlite 函數,sqlite 就會為你操作資料庫。
sqlite 跟MS的access一樣是檔案型資料庫,就是說,一個資料庫就是一個檔案,此資料庫裡可以建立很多的表,可以建立索引、觸發器等等,但是,它實際上得到的就是一個檔案。備份這個檔案就備份了整個資料庫。
sqlite 不需要任何資料庫引擎,這意味着如果你需要 sqlite 來儲存一些使用者資料,甚至都不需要安裝資料庫
(如果你做個小軟體還要求人家必須裝了sqlserver才能運作,那也太黑心了)。
下面開始介紹資料庫基本操作。
1、 基本流程
(1) 關鍵資料結構
sqlite 裡最常用到的是 sqlite3 * 類型。從資料庫打開開始,sqlite就要為這個類型準備好記憶體,直到資料庫關閉,整個過程都需要用到這個類型。當資料庫打開時開始,這個類型的變量就代表了你要操作的資料庫。下面再詳細介紹。
(2) 打開資料庫
[cpp] view plain copy
- int sqlite3_open( 檔案名, sqlite3 ** );
用這個函數開始資料庫操作。
需要傳入兩個參數,一是資料庫檔案名,比如:c:\\DongChunGuang_Database.db。檔案名不需要一定存在,如果此檔案不存在,sqlite 會自動建立它。如果它存在,就嘗試把它當資料庫檔案來打開。sqlite3 ** 參數即前面提到的關鍵資料結構。這個結構底層細節如何,你不要關它。函數傳回值表示操作是否正确,如果是 SQLITE_OK 則表示操作正常。相關的傳回值sqlite定義了一些宏。具體這些宏的含義可以參考 sqlite3.h 檔案。裡面有詳細定義(順便說一下,sqlite3 的代碼注釋率自稱是非常高的,實際上也的确很高。隻要你會看英文,sqlite 可以讓你學到不少東西)。
下面介紹關閉資料庫後,再給一段參考代碼。
(3) 關閉資料庫
[cpp] view plain copy
- int sqlite3_close(sqlite3 *);
前面如果用 sqlite3_open 開啟了一個資料庫,結尾時不要忘了用這個函數關閉資料庫。
下面給段簡單的代碼:
[cpp] view plain copy
- 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語句
[cpp] view plain copy
- 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\3n”,errmsg))得到一串字元串資訊,這串資訊告訴你錯在什麼地方。sqlite3_exec函數通過修改你傳入的指針的指針,把你提供的指針指向錯誤提示資訊,這樣sqlite3_exec函數外面就可以通過這個 char*得到具體錯誤提示。
說明:通常,sqlite3_callback 和它後面的 void * 這兩個位置都可以填 NULL。填NULL表示你不需要回調。比如你做 insert 操作,做 delete 操作,就沒有必要使用回調。而當你做select 時,就要使用回調,因為 sqlite3 把資料查出來,得通過回調告訴你查出了什麼資料。
(2) exec 的回調
[cpp] view plain copy
- typedef int(*sqlite3_callback)(void*,int,char**, char**);
你的回調函數必須定義成上面這個函數的類型。下面給個簡單的例子:
[cpp] view plain copy
- //sqlite3的回調函數
- // sqlite 每查到一條記錄,就調用一次這個回調
- int LoadMyInfo( void * para, intn_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 integerprimary 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 函數做到。
[cpp] view plain copy
- 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個參數是錯誤資訊,跟前面一樣,這裡不多說了。
下面給個簡單例子:
[cpp] view plain copy
- 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( IDinteger, file_content blob )
首先聲明
[cpp] view plain copy
- sqlite3_stmt * stat;
然後,把一個 sql 語句解析到 stat 結構裡去:
// sqlite3_prepare 接口把一條SQL語句編譯成位元組碼留給後面的執行函數. 使用該接口通路資料庫是目前比較好的的一種方法.
[cpp] view plain copy
- 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 ),那麼下面就可以開始插入二進制資料。
[cpp] view plain copy
- 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語句”裡了。你現在可以把它儲存到資料庫裡:
虛拟機執行位元組碼,執行過程是一個步進(stepwise)的過程,每一步(step)由sqlite3_step()啟動,并由VDBE(sqlite虛拟機)執行一段位元組 碼。由sqlite3_prepare編譯位元組代碼,并由sqlite3_step()啟動虛拟機執行。在周遊結果集的過程中,它傳回SQLITE_ROW,當到達結果末尾時,傳回SQLITE_DONE
[cpp] view plain copy
- int result =sqlite3_step( stat );
通過這個語句,stat 表示的sql語句就被寫到了資料庫裡。
最後,要把 sqlite3_stmt結構給釋放:sqlite3_finalize( stat ); //把剛才配置設定的内容析構掉
(2) 讀出二進制
下面說讀二進制的步驟。
跟前面一樣,
先聲明 sqlite3_stmt *類型變量:
[cpp] view plain copy
- sqlite3_stmt * stat;
然後,把一個 sql 語句解析到 stat 結構裡去:
[cpp] view plain copy
- sqlite3_prepare( db,“select * from Tbl_2”, -1,&stat, 0 );
當 prepare 成功之後(傳回值是 SQLITE_OK ),開始查詢資料。
[cpp] view plain copy
- 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 是二進制,是以我需要得到它的指針,還有它的長度:
[cpp] view plain copy
- const void * pFileContent =sqlite3_column_blob( stat, 1 );
- int len = sqlite3_column_bytes( stat, 1 );
這樣就得到了二進制的值。
把 pFileContent 的内容儲存出來之後,
不要忘了釋放sqlite3_stmt 結構:
[cpp] view plain copy
- sqlite3_finalize( stat ); //把剛才配置設定的内容析構掉
(3) 重複使用 sqlite3_stmt 結構
如果你需要重複使用sqlite3_prepare 解析好的 sqlite3_stmt 結構,需要用函數:sqlite3_reset。
[cpp] view plain copy
- result = sqlite3_reset(stat);
這樣, stat 結構又成為sqlite3_prepare 完成時的狀态,你可以重新為它bind 内容。
(4) 事務處理
sqlite 是支援事務處理的。如果你知道你要同步删除很多資料,不仿把它們做成一個統一的事務。通常一次 sqlite3_exec 就是一次事務,如果你要删除1萬條資料,sqlite就做了1萬次:開始新事務->删除一條資料->送出事務->開始新事務->… 的過程。這個操作是很慢的。因為時間都花在了開始事務、送出事務上。你可以把這些同類操作做成一個事務,這樣如果操作錯誤,還能夠復原事務。
事務的操作沒有特别的接口函數,它就是一個普通的 sql 語句而已:
分别如下:
[cpp] view plain copy
- 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 ); //復原事務
(3) 補充
基本上,使用sqlite3_open, sqlite3_close, sqlite3_exec這三個函數,可以完成大大部分的工作。但還不完善。上面的例子中,都是直接以sql語句的形式來操作資料庫,這樣很容易被注入。是以有必要使用sql參數。
sqlite3_prepare
sqlite3_bind_*
sqlite3_step
sqlite3_column_*
struct sqlite3_stmt
sqlite3_finalize
sqlite3_prepare用來編譯sql語句。sql語句被執行之前,必須先編譯成位元組碼。S
qlite3_stmt是一個結構體,表示sql語句編譯後的位元組碼。
sqlite3_step用來執行編譯後的sql語句。
sqlite3_bind_*用于将sql參數綁定到sql語句。
sqlite3_column_*用于從查詢的結果中擷取資料。
sqlite3_finalize用來釋放sqlite3_stmt對象。
代碼最能說明函數的功能,
下面就用一個例子來示範吧~~
[cpp] view plain copy
- //----------------------------------------------
- //sqlite3_prepare, sqlite3_bind_*,
- //sqlite3_step, sqlite3_column_*,
- //sqlite3_column_type
- //sqlite3_stmt, sqlite3_finalize,sqlite3_reset
- //查詢
- //----------------------------------------------
- sqlite3 *conn = NULL;
- sqlite3_stmt *stmt = NULL;
- const char *err_msg = NULL;
- // 列資料類型
- char col_types[][10] = { "","Integer", "Float", "Text", "Blob", "NULL"};
- sqlite3_open("test.db",&conn);
- sqlite3_prepare(conn, "SELECT * FROM[test_for_cpp] WHERE [id]>?", -1, &stmt, &err_msg);
- sqlite3_bind_int(stmt, 1, 5);
- while (SQLITE_ROW == sqlite3_step(stmt))
- {
- int col_count = sqlite3_column_count(stmt); // 結果集中列的數量
- const char *col_0_name = sqlite3_column_name(stmt, 0); // 擷取列名
- int id = sqlite3_column_int(stmt, 0);
- int id_type = sqlite3_column_type(stmt, 0); // 擷取列資料類型
- const char *col_2_name = sqlite3_column_name(stmt, 2);
- int age = sqlite3_column_int(stmt, 2);
- int age_type = sqlite3_column_type(stmt, 2);
- const char *col_1_name = sqlite3_column_name(stmt, 1);
- char name[80];
- strncpy(name, (const char *)sqlite3_column_text(stmt, 1), 80);
- int name_type = sqlite3_column_type(stmt, 1);
- // 列印結果
- printf("col_count: %d, %s = %d(%s), %s = %s(%s), %s = %d(%s)\n",
- col_count, col_0_name, id, col_types[id_type], col_2_name, name,
- col_types[name_type], col_1_name, age, col_types[age_type]);
- }
- sqlite3_finalize(stmt); // 釋放sqlite3_stmt
- sqlite3_close(conn);
這段代碼查詢id号大于5的所有記錄,并顯示到控制台,最後效果為
Sqlite c/c++ api學習 -stanfordxu - stanfordxu的部落格其他函數
在上面的例子中,還使用了其他的一些函數,如:
sqlite3_column_count用于擷取結果集中列的數量;
sqlite3_column_name用于擷取列的名稱;
sqlite3_column_type用于擷取列的資料類型;
sqlite3_errcode用于擷取最近一次操作出錯的錯誤代碼;
sqlite3_errmsg用于擷取最近一次操作出錯的錯誤說明。 sqlite的api中還有很多的函數,有了上面的基礎,相信你通過查詢官方的文檔,能迅速掌握本文未介紹的api。
字元串編碼
在官網上檢視Sqlite的api的時候,發現有很同函數的名稱都非常相似,隻是最後添加了”_16”,如:sqlite3_open和 sqlite3_open16, sqlite3_errmsg和sqlite3_errmsg16,等等。其實添加了”16”字尾的函數,主要用于支援utf-16編碼的字元串。如 sqlite3_open16可以接收utf-16編碼的資料庫路徑。
在sourceforge上,有一個開源的項目sqlitex,它封裝了這些api,使對sqlite資料庫的操作更加友善。sqlitex的源代碼非常的簡單,感興趣的同學可以下載下傳下來自己研究。
[cpp] view plain copy
- /// 另外一個代碼 ///
- #include <stdio.h>
- #include <stdlib.h>
- #include "sqlite3.h"
- #include <string.h>
- int main(int argc, char **argv)
- {
- int rc, i, ncols;
- sqlite3 *db;
- sqlite3_stmt *stmt;
- char *sql;
- const char *tail;
- //打開資料
- rc = sqlite3_open("foods.db", &db);
- if(rc) {
- fprintf(stderr, "Can't open database: %s\n",
- sqlite3_errmsg(db));
- sqlite3_close(db);
- exit(1);
- }
- sql = "select * from episodes";
- //預處理
- rc = sqlite3_prepare(db, sql, (int)strlen(sql), &stmt, &tail);
- if(rc != SQLITE_OK) {
- fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
- }
- rc = sqlite3_step(stmt);
- ncols = sqlite3_column_count(stmt);
- while(rc == SQLITE_ROW) {
- for(i=0; i < ncols; i++) {
- fprintf(stderr, "'%s' ", sqlite3_column_text(stmt, i));
- }
- fprintf(stderr, "\n");
- rc = sqlite3_step(stmt);
- }
- //釋放statement
- sqlite3_finalize(stmt);
- //關閉資料庫
- sqlite3_close(db);
- return 0;
- //=====================================================================
四、 給資料庫加密
前面所說的内容網上已經有很多資料,雖然比較零散,但是花點時間也還是可以找到的。現在要說的這個——資料庫加密,資料就很難找。也可能是我操作水準不夠,找不到對應資料。但不管這樣,我還是通過網上能找到的很有限的資料,探索出了給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 檔案第一行)定義:
[cpp] view plain copy
- #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如此定義:
[cpp] view plain copy
- <pre name="code" class="cpp">#ifndef DCG_SQLITE_CRYPT_FUNC_
- #define DCG_SQLITE_CRYPT_FUNC_
- 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 int len_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 int len_of_key )
- {
- return 0;
- }</pre>
- <pre></pre>
- <p></p>
- <pre></pre>
- <p></p>
- <p>這個檔案很容易看,就兩函數,一個加密一個解密。傳進來的參數分别是待處理的資料、資料長度、密鑰、密鑰長度。</p>
- <p>處理時直接把結果作用于 pData 指針指向的内容。</p>
- <p>你需要定義自己的加解密過程,就改動這兩個函數,其它部分不用動。擴充起來很簡單。</p>
- <p>這裡有個特點,data_len 一般總是 1024 位元組。正因為如此,你可以在你的算法裡使用一些特定長度的加密算法,比如AES要求被加密資料一定是128位(16位元組)長。這個1024不是碰巧,而是 Sqlite的頁定義是1024位元組,在sqlite3.c檔案裡有定義:</p>
- <p></p>
- <pre name="code" class="cpp"># define SQLITE_DEFAULT_PAGE_SIZE 1024</pre>
- <p></p>
- <p>你可以改動這個值,不過還是建議沒有必要不要去改它。</p>
- <p>上面寫了兩個擴充函數,如何把擴充函數跟 Sqlite 挂接起來,這個過程說起來比較麻煩。我直接貼代碼。</p>
- <p>分3個步驟。</p>
- <p>首先,在 sqlite3.c 檔案頂部,添加下面内容:</p>
- <p></p>
- <pre name="code" class="cpp">#ifdef SQLITE_HAS_CODEC
- #include "./crypt.h"
- void sqlite3pager_free_codecarg(void*pArg);
- #endif</pre>
- <p></p>
- <p>這個函數之是以要在 sqlite3.c 開頭聲明,是因為下面在 sqlite3.c 裡面某些函數裡要插入這個函數調用。是以要提前聲明。</p>
- <p>其次,在sqlite3.c檔案裡搜尋“sqlite3PagerClose”函數,要找到它的實作代碼(而不是聲明代碼)。</p>
- <p>實作代碼裡一開始是:</p>
- <p></p>
- <pre name="code" class="cpp">#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
- ThreadData *pTsd = sqlite3ThreadData();
- assert( pPager );
- assert( pTsd && pTsd->nAlloc );
- #endif</pre>
- <p></p>
- <p>需要在這部分後面緊接着插入:</p>
- <p></p>
- <pre name="code" class="cpp">#ifdef SQLITE_HAS_CODEC
- sqlite3pager_free_codecarg(pPager->pCodecArg);
- #endif</pre>
- <p></p>
- <p>這裡要注意,sqlite3PagerClose 函數大概也是 3.3.17版本左右才改名的,以前版本裡是叫“sqlite3pager_close”。是以你在老版本sqlite代碼裡搜尋“sqlite3PagerClose”是搜不到的。</p>
- <p>類似的還有“sqlite3pager_get”、“sqlite3pager_unref”、“sqlite3pager_write”、“sqlite3pager_pagecount”等都是老版本函數,它們在 pager.h 檔案裡定義。新版本對應函數是在 sqlite3.h 裡定義(因為都合并到 sqlite3.c和sqlite3.h兩檔案了)。是以,如果你在使用老版本的sqlite,先看看 pager.h 檔案,這些函數不是消失了,也不是新蹦出來的,而是老版本函數改名得到的。</p>
- <p>最後,往sqlite3.c 檔案下找。找到最後一行:</p>
- <p></p>
- <p>在這一行後面,接上本文最下面的代碼段。</p>
- <p>這些代碼很長,我不再解釋,直接接上去就得了。</p>
- <p>唯一要提的是 DeriveKey 函數。這個函數是對密鑰的擴充。比如,你要求密鑰是128位,即是16位元組,但是如果使用者隻輸入 1個位元組呢?2個位元組呢?或輸入50個位元組呢?你得對密鑰進行擴充,使之符合16位元組的要求。</p>
- <p>DeriveKey 函數就是做這個擴充的。有人把接收到的密鑰求md5,這也是一個辦法,因為md5運算結果固定16位元組,不論你有多少字元,最後就是16位元組。這是md5算法的特點。但是我不想用md5,因為還得為它添加包含一些 md5 的.c或.cpp檔案。我不想這麼做。我自己寫了一個算法來擴充密鑰,很簡單的算法。當然,你也可以使用你的擴充方法,也而可以使用 md5 算法。隻要修改DeriveKey 函數就可以了。</p>
- <p>在 DeriveKey 函數裡,隻管申請空間構造所需要的密鑰,不需要釋放,因為在另一個函數裡有釋放過程,而那個函數會在資料庫關閉時被調用。參考我的 DeriveKey 函數來申請記憶體。</p>
- <p>這裡我給出我已經修改好的 sqlite3.c 和 sqlite3.h 檔案。</p>
- <p>如果太懶,就直接使用這兩個檔案,編譯肯定能通過,運作也正常。當然,你必須按我前面提的,建立 crypt.h 和 crypt.c 檔案,而且函數要按我前面定義的要求來做。</p>
- <h2><a name="t20"></a>3、 加密使用方法:</h2>
- <p>現在,你代碼已經有了加密功能。</p>
- <p>你要把加密功能給用上,除了改 sqlite3.c 檔案、給你工程添加 SQLITE_HAS_CODEC 宏,還得修改你的資料庫調用函數。</p>
- <p>前面提到過,要開始一個資料庫操作,必須先 sqlite3_open 。</p>
- <p>加解密過程就在 sqlite3_open 後面操作。</p>
- <p>假設你已經 sqlite3_open 成功了,緊接着寫下面的代碼:</p>
- <p></p>
- <pre name="code" class="cpp"> int i;
- //添加、使用密碼
- i = sqlite3_key( db,"dcg", 3 );
- //修改密碼
- i = sqlite3_rekey( db,"dcg", 0 );</pre>
- <p></p>
- <p>用 sqlite3_key 函數來送出密碼。</p>
- <p>第1個參數是 sqlite3 *類型變量,代表着用sqlite3_open 打開的資料庫(或建立資料庫)。</p>
- <p>第2個參數是密鑰。</p>
- <p>第3個參數是密鑰長度。</p>
- <p>用 sqlite3_rekey 來修改密碼。參數含義同 sqlite3_key。</p>
- <p>實際上,你可以在sqlite3_open函數之後,到 sqlite3_close 函數之前任意位置調用 sqlite3_key 來設定密碼。</p>
- <p>但是如果你沒有設定密碼,而資料庫之前是有密碼的,那麼你做任何操作都會得到一個傳回值:SQLITE_NOTADB,并且得到錯誤提示:“file is encrypted or is not a database”。</p>
- <p>隻有當你用 sqlite3_key 設定了正确的密碼,資料庫才會正常工作。</p>
- <p>如果你要修改密碼,前提是你必須先 sqlite3_open 打開資料庫成功,然後 sqlite3_key 設定密鑰成功,之後才能用 sqlite3_rekey 來修改密碼。(源碼網整理:<a href="http://www.codepub.com/">www.codepub.com</a>)</p>
- <p>如果資料庫有密碼,但你沒有用 sqlite3_key 設定密碼,那麼當你嘗試用 sqlite3_rekey 來修改密碼時會得到 SQLITE_NOTADB 傳回值。</p>
- <p>如果你需要清空密碼,可以使用:</p>
- <p></p>
- <pre name="code" class="cpp">//修改密碼
- i = sqlite3_rekey( db, NULL, 0 );</pre>
- <p></p>
- <p>來完成密碼清空功能。</p>
- <h2><a name="t21"></a>4、 sqlite3.c 最後添加代碼段</h2>
- <p></p>
- <pre name="code" class="cpp">
- #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
- void sqlite3CodecGetKey(sqlite3* db, intnDB, void** Key, int* nKey)
- {
- return ;
- }
- int sqlite3CodecAttach(sqlite3 *db, intnDb, const void *pKey, int nKeyLen);
- 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 LPCryptBlockCreateCryptBlock(unsigned char* hKey, Pager *pager, LPCryptBlock pExisting);
- //加密/解密函數, 被pager調用
- void * sqlite3Codec(void *pArg, unsignedchar *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(LPCryptBlockpBlock);
- 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, unsignedchar *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 LPCryptBlockCreateCryptBlock(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;
- }
- 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)
- {
- returnsqlite3_key_interop(db, pKey, nKey);
- }
- int sqlite3_rekey(sqlite3 *db, const void*pKey, int nKey)
- {
- returnsqlite3_rekey_interop(db, pKey, nKey);
- }
- int sqlite3CodecAttach(sqlite3 *db, intnDb, 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 anexisting 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) returnSQLITE_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</pre>
- <p></p>
- <pre></pre>