本文简要讲述一下windows平台下sqlite3的编译及简单使用。当前我们的编译环境如下:

  • 操作系统: 64位win7

  • 编译器: vs2013

1. sqlite3的编译

我们到sqlite3官网下载最新版本(v3.29.0)的如下4个压缩包:

  • sqlite-amalgamation-3290000.zip: sqlite3源码包Amalgamation版本(混合版)。sqlite3源代码库总共大概有100多个C文件,所谓Amalgamation版本,即是把这100多个C文件合并到了一个sqlite3.c文件中了。

  • sqlite-doc-3290000.zip: sqlite3的相关文档

  • sqlite-dll-win32-x86-3290000.zip: 预编译好的sqlite3动态链接库(32位版)

  • sqlite-dll-win64-x64-3290000.zip: 预编译好的sqlite3动态链接库(64位版)

  • sqlite-src-3290000.zip: sqlite3源码包(原始版)

这里我们可以有如下两种方式来编译sqlite3,生成我们想要的sqlite3.lib以及sqlite3.dll:

  • 基于预编译好的sqlite3动态链接库生成sqlite3.lib,以及sqlite3.h头文件

  • 基于Amalgamation源代码编译sqlite3.lib以及sqlite3.dll

  • 基于sqlite3源码编译sqlite3.lib以及sqlite3.dll

说明: 如果没有特殊要求,可直接下载sqlite-dll-xxx.zip内的dll,如果不放心就自己编译。正常情况下建议直接下载sqlite-dll-xxx.zip来使用

下面我们分别介绍。

1.1 基于预编译好的动态链接库来编译

这里我们使用sqlite-dll-win64-x64-3290000.zip来编译出sqlite3.lib。解压该压缩文件,可以看到有如下:

# dir
2019/07/11  02:44             6,010 sqlite3.def
2019/07/11  02:44         1,902,080 sqlite3.dll

接着,我们以管理员身份运行“VS2013 开发人员命令提示”,然后切换到上面的解压目录,执行如下命令:

# cd D:\workspace\sqlite-dll-win64-x64-3290000

# lib /def:sqlite3.def                 //采用缺省的方式生成lib库(32位版)
# lib /def:sqlite3.def /machine:x86    //明确指定生成x86版本的lib库(32位版)
# lib /def:sqlite3.def  /machine:x64   //明确指定生成x64版本的lib库 (我们这里用此命令)
Microsoft (R) Library Manager Version 12.00.40629.0
Copyright (C) Microsoft Corporation.  All rights reserved.

   正在创建库 sqlite3.lib 和对象 sqlite3.exp

# dir
 D:\workspace\sqlite-dll-win64-x64-3290000 的目录

2019/07/11  02:44             6,010 sqlite3.def
2019/07/11  02:44         1,902,080 sqlite3.dll
2019/09/10  12:35            37,700 sqlite3.exp
2019/09/10  12:35            62,596 sqlite3.lib

注: 这里要以管理员身份运行,否则可能出现各种怪异的问题

之后,我们从sqlite-amalgamation-3290000.zip拷贝出sqlite3.h头文件即可。


1.2 基于sqlite3 Amalgamation版源代码编译

这里我们可以根据自身需要选择编译sqlite3动态库或者sqlite3静态库。下面分别介绍:

1.2.1 sqlite3动态库编译

进行如下步骤:

1) 使用vs2013创建win32工程sqlite3-dynamic,然后选择DLL和空项目 (正常情况下,vc6-vs2015其实都应该支持)

2) 把sqlite3.c、sqlite3.h、sqlite3ext.h、sqlite3.def拷贝到工程源文件目录,前3个文件位于sqlite-amalgamation-3290000.zip压缩包中,后一个文件位于sqlite-dll-win64-x64-3290000.zip压缩包中。(这里采用64位版)

3) 通过工程的资源管理器把上述4个文件添加到工程中: 将sqlite3.c添加到源文件项中,将sqlite3.h、sqlite3ext.h添加到头文件项中

4) 修改工程配置,在配置属性–>c/c++–>预处理器–>预处理器定义,加入

SQLITE_ENABLE_RTREE
SQLITE_ENABLE_COLUMN_METADATA
SQLITE_ENABLE_DESERIALIZE
SQLITE_ENABLE_FTS5
SQLITE_ENABLE_FTS3

注: 此步骤在编译Debug版本时,可以不进行设置,但是编译Release版本时需要进行设置

5) 修改工程配置,在配置属性–>链接器–>输入–>模块定义文件加入sqlite3.def

6) 编译时选择好平台为x64,并在配置属性–>常规–>目标文件名 出将生成的目标文件名改为sqlite3

1.2.2 sqlite3静态库编译

进行如下步骤:

1) 使用vs2013创建win32工程sqlite3-static,然后选择静态库,去掉预编译头(正常情况下,vc6-vs2015其实都应该支持)

2) 把sqlite3.c、sqlite3.h、sqlite3ext.h、sqlite3.def拷贝到工程源文件目录,前3个文件位于sqlite-amalgamation-3290000.zip压缩包中,后一个文件位于sqlite-dll-win64-x64-3290000.zip压缩包中。(这里采用64位版)

3) 通过工程的资源管理器把上述4个文件添加到工程中: 将sqlite3.c添加到源文件项中,将sqlite3.h、sqlite3ext.h添加到头文件项中

4) 修改工程配置,在配置属性–>库管理器–>常规–>模块定义文件加入sqlite3.def

5) 编译时选择好平台为x64,并在配置属性–>常规–>目标文件名 出将生成的目标文件名改为sqlite3


1.3 基于sqlite3源码编译

1.3.1 安装tcl

虽然sqlite3本身并不依赖tcl,但是编译时会用到tcl工具,这里我们可以到tcl官网去下载安装。这里我们安装ActiveTcl 8.5版本。安装步骤较为简单,这里不做介绍。

1.3.2 编译sqlite3源码

这里我们首先解压sqlite-src-3290000.zip,然后可能需要对解压出来的文件夹名称进行重命名为sqlite,这是因为我们通过脚本编译时,里面脚本默认的工程路径名为sqlite

之后,我们以管理员身份运行“VS2013 开发人员命令提示”,然后切换到上面的解压目录,执行如下命令:

#cd D:\workspace\sqlite-src-3290000\sqlite
# nmake /f Makefile.msc TOP=..\sqlite
# nmake /f Makefile.msc clean TOP=..\sqlite

2. sqlite3的使用

我们可以使用sqlite3.exe来打开sqlite数据库,其基本命令如下:

sqlite> .help
.archive ...             Manage SQL archives
.auth ON|OFF             Show authorizer callbacks
.backup ?DB? FILE        Backup DB (default "main") to FILE
.bail on|off             Stop after hitting an error.  Default OFF
.binary on|off           Turn binary output on or off.  Default OFF
.cd DIRECTORY            Change the working directory to DIRECTORY
.changes on|off          Show number of rows changed by SQL
.check GLOB              Fail if output since .testcase does not match
.clone NEWDB             Clone data into NEWDB from the existing database
.databases               List names and files of attached databases
.dbconfig ?op? ?val?     List or change sqlite3_db_config() options
.dbinfo ?DB?             Show status information about the database
.dump ?TABLE? ...        Render all database content as SQL
.echo on|off             Turn command echo on or off
.eqp on|off|full|...     Enable or disable automatic EXPLAIN QUERY PLAN
.excel                   Display the output of next command in a spreadsheet
.exit ?CODE?             Exit this program with return-code CODE
.expert                  EXPERIMENTAL. Suggest indexes for specified queries
.fullschema ?--indent?   Show schema and the content of sqlite_stat tables
.headers on|off          Turn display of headers on or off
.help ?-all? ?PATTERN?   Show help text for PATTERN
.import FILE TABLE       Import data from FILE into TABLE
.imposter INDEX TABLE    Create imposter table TABLE on index INDEX
.indexes ?TABLE?         Show names of indexes
.iotrace FILE            Enable I/O diagnostic logging to FILE
.limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT
.lint OPTIONS            Report potential schema issues.
.load FILE ?ENTRY?       Load an extension library
.log FILE|off            Turn logging on or off.  FILE can be stderr/stdout
.mode MODE ?TABLE?       Set output mode
.nullvalue STRING        Use STRING in place of NULL values
.once (-e|-x|FILE)       Output for the next SQL command only to FILE
.open ?OPTIONS? ?FILE?   Close existing database and reopen FILE
.output ?FILE?           Send output to FILE or stdout if FILE is omitted
.parameter CMD ...       Manage SQL parameter bindings
.print STRING...         Print literal STRING
.progress N              Invoke progress handler after every N opcodes
.prompt MAIN CONTINUE    Replace the standard prompts
.quit                    Exit this program
.read FILE               Read input from FILE
.restore ?DB? FILE       Restore content of DB (default "main") from FILE
.save FILE               Write in-memory database into FILE
.scanstats on|off        Turn sqlite3_stmt_scanstatus() metrics on or off
.schema ?PATTERN?        Show the CREATE statements matching PATTERN
.selftest ?OPTIONS?      Run tests defined in the SELFTEST table
.separator COL ?ROW?     Change the column and row separators
.session ?NAME? CMD ...  Create or control sessions
.sha3sum ...             Compute a SHA3 hash of database content
.shell CMD ARGS...       Run CMD ARGS... in a system shell
.show                    Show the current values for various settings
.stats ?on|off?          Show stats or turn stats on or off
.system CMD ARGS...      Run CMD ARGS... in a system shell
.tables ?TABLE?          List names of tables matching LIKE pattern TABLE
.testcase NAME           Begin redirecting output to 'testcase-out.txt'
.timeout MS              Try opening locked tables for MS milliseconds
.timer on|off            Turn SQL timer on or off
.trace ?OPTIONS?         Output each SQL statement as it is run
.vfsinfo ?AUX?           Information about the top-level VFS
.vfslist                 List all available VFSes
.vfsname ?AUX?           Print the name of the VFS stack
.width NUM1 NUM2 ...     Set column widths for "column" mode
sqlite>

但相关命令使用不便,建议使用SQLite Expert Professional这一图形界面工具来操作sqlite3。

3. 通过sqlite3 API操作数据库

3.1 程序示例

参看如下示例:

// sqlite3_sample.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "sqlite3.h"
#include <string.h>
#include <windows.h>
#include <wchar.h>
#include <string>
#include <vector>
using namespace std;



#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif


int print_record(void *params, int n_column, char **column_value, char **column_name);

//UTF-8转Unicode 
std::wstring Utf82Unicode(const std::string& utf8string)
{
	int widesize = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, NULL, 0);
	if (widesize == ERROR_NO_UNICODE_TRANSLATION)
	{
		throw std::exception("Invalid UTF-8 sequence.");
	}
	if (widesize == 0)
	{
		throw std::exception("Error in conversion.");
	}
	std::vector<wchar_t> resultstring(widesize);
	int convresult = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, &resultstring[0], widesize);
	if (convresult != widesize)
	{
		throw std::exception("La falla!");
	}
	return std::wstring(&resultstring[0]);
}
//unicode 转为 ascii 
string WideByte2Acsi(wstring& wstrcode)
{
	int asciisize = ::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, NULL, 0, NULL, NULL);
	if (asciisize == ERROR_NO_UNICODE_TRANSLATION)
	{
		throw std::exception("Invalid UTF-8 sequence.");
	}
	if (asciisize == 0)
	{
		throw std::exception("Error in conversion.");
	}
	std::vector<char> resultstring(asciisize);
	int convresult = ::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, &resultstring[0], asciisize, NULL, NULL);
	if (convresult != asciisize)
	{
		throw std::exception("La falla!");
	}
	return std::string(&resultstring[0]);
}
//utf-8 转 ascii 
string UTF_82ASCII(string& strUtf8Code)
{
	string strRet("");
	//先把 utf8 转为 unicode 
	wstring wstr = Utf82Unicode(strUtf8Code);
	//最后把 unicode 转为 ascii 
	strRet = WideByte2Acsi(wstr);
	return strRet;
}


//ascii 转 Unicode 
wstring Acsi2WideByte(string& strascii)
{
	int widesize = MultiByteToWideChar(CP_ACP, 0, (char*)strascii.c_str(), -1, NULL, 0);
	if (widesize == ERROR_NO_UNICODE_TRANSLATION)
	{
		throw std::exception("Invalid UTF-8 sequence.");
	}
	if (widesize == 0)
	{
		throw std::exception("Error in conversion.");
	}
	std::vector<wchar_t> resultstring(widesize);
	int convresult = MultiByteToWideChar(CP_ACP, 0, (char*)strascii.c_str(), -1, &resultstring[0], widesize);
	if (convresult != widesize)
	{
		throw std::exception("La falla!");
	}
	return std::wstring(&resultstring[0]);
}
//Unicode 转 Utf8 
std::string Unicode2Utf8(const std::wstring& widestring)
{
	int utf8size = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, NULL, 0, NULL, NULL);
	if (utf8size == 0)
	{
		throw std::exception("Error in conversion.");
	}
	std::vector<char> resultstring(utf8size);
	int convresult = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, &resultstring[0], utf8size, NULL, NULL);
	if (convresult != utf8size)
	{
		throw std::exception("La falla!");
	}
	return std::string(&resultstring[0]);
}

//ascii 转 Utf8 
string ASCII2UTF_8(string& strAsciiCode)
{
	string strRet("");
	//先把 ascii 转为 unicode 
	wstring wstr = Acsi2WideByte(strAsciiCode);
	//最后把 unicode 转为 utf8 
	strRet = Unicode2Utf8(wstr);
	return strRet;
}

// 注释:多字节包括GBK和UTF-8
int GBK2UTF8(char *szGbk, char *szUtf8, int Len)
{
	// 先将多字节GBK(CP_ACP或ANSI)转换成宽字符UTF-16
	// 得到转换后,所需要的内存字符数
	int n = MultiByteToWideChar(CP_ACP, 0, szGbk, -1, NULL, 0);
	// 字符数乘以 sizeof(WCHAR) 得到字节数
	WCHAR *str1 = new WCHAR[sizeof(WCHAR) * n];
	// 转换
	MultiByteToWideChar(CP_ACP,  // MultiByte的代码页Code Page
		0,            //附加标志,与音标有关
		szGbk,        // 输入的GBK字符串
		-1,           // 输入字符串长度,-1表示由函数内部计算
		str1,         // 输出
		n             // 输出所需分配的内存
		);

	// 再将宽字符(UTF-16)转换多字节(UTF-8)
	n = WideCharToMultiByte(CP_UTF8, 0, str1, -1, NULL, 0, NULL, NULL);
	if (n > Len)
	{
		delete[]str1;
		return -1;
	}
	WideCharToMultiByte(CP_UTF8, 0, str1, -1, szUtf8, n, NULL, NULL);
	delete[]str1;
	str1 = NULL;

	return 0;
}


//UTF-8 GBK
int UTF82GBK(char *szUtf8, char *szGbk, int Len)
{
	int n = MultiByteToWideChar(CP_UTF8, 0, szUtf8, -1, NULL, 0);
	WCHAR * wszGBK = new WCHAR[sizeof(WCHAR) * n];
	memset(wszGBK, 0, sizeof(WCHAR) * n);
	MultiByteToWideChar(CP_UTF8, 0, szUtf8, -1, wszGBK, n);

	n = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
	if (n > Len)
	{
		delete[]wszGBK;
		return -1;
	}

	WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGbk, n, NULL, NULL);

	delete[]wszGBK;
	wszGBK = NULL;

	return 0;

}



int _tmain(int argc, _TCHAR* argv[])
{
	sqlite3 *db = NULL;
	sqlite3_stmt *stmt = NULL;
	int ret;
	char **dbresult;
	int nrow, ncolumn, index;
	char *errmsg = NULL;
	const char *database_name = "./sqlite3-demo.db";
	const char *sql_drop_table = "drop table if exists student";
	const char *sql_create_table = "create table student(id int primary key,name varchar(128))";
	


	// create or open database
	ret = sqlite3_open(database_name, &db);
	if (ret != SQLITE_OK){
		fprintf(stderr, "Cannot open database '%s'\n", sqlite3_errmsg(db));
		return -1;
	}

	printf("Open database '%s' success\n", database_name);

	// create or drop table
	ret = sqlite3_exec(db, sql_drop_table, NULL, NULL, &errmsg);
	if (ret != SQLITE_OK){
		fprintf(stderr, "drop table '%s' failure: %s\n", "student", errmsg);
	}
	ret = sqlite3_exec(db, sql_create_table, NULL, NULL, &errmsg);
	if (ret != SQLITE_OK){
		fprintf(stderr, "create table '%s' failure: %s\n", "student", errmsg);
		return -1;
	}


	//insert data
	char *sql_str1 = "INSERT INTO student(id, name) VALUES(1001, 'Ady Liu');";
	char *sql_str2 = "INSERT INTO student(id, name) VALUES(1002, 'ivan');";
	char *sql_str3 = "INSERT INTO student(id, name) VALUES(1003, '刘德华');";
	ret = sqlite3_exec(db, sql_str1, NULL, NULL, &errmsg);
	printf("Insert a record %s\n", ret == SQLITE_OK ? "OK" : "FAIL");
	ret = sqlite3_exec(db, sql_str2, NULL, NULL, &errmsg);
	printf("Insert a record %s\n", ret == SQLITE_OK ? "OK" : "FAIL");
	ret = sqlite3_exec(db, sql_str3, NULL, NULL, &errmsg);
	printf("Insert a record %s\n", ret == SQLITE_OK ? "OK" : "FAIL");

	//delete data
	char *sql_str4 = "DELETE FROM student WHERE id = 1002;";
	ret = sqlite3_exec(db, sql_str4, NULL, NULL, &errmsg);
	printf("Delete a record %s\n", ret == SQLITE_OK ? "OK" : "FAIL");
	
	//prepare statement(根据文档说明,优先使用sqlite3_prepare_v2())
	char *sql_str5 = "INSERT INTO student(id, name) VALUES(?,?);";
	char buf[256];
	sqlite3_prepare_v2(db, sql_str5, -1, &stmt, 0);
	for (int i = 10; i<20; i++){
		sprintf_s(buf, "HELLO#%i", i);
		sqlite3_bind_int(stmt, 1, i);
		sqlite3_bind_text(stmt, 2, buf, int(strlen(buf)), SQLITE_STATIC);
		sqlite3_step(stmt);
		sqlite3_reset(stmt);
	}
	sqlite3_finalize(stmt);

	//select data
	char *sql_str6 = "SELECT * FROM student LIMIT 5;";
	ret = sqlite3_exec(db, sql_str6, print_record, NULL, &errmsg);
	if (ret != SQLITE_OK){
		fprintf(stderr, "query SQL error: %s\n", errmsg);
	}

	//update data
	char *sql_str7 = "UPDATE student set name='MESSAGE#10' WHERE id=10;";
	ret = sqlite3_exec(db, sql_str7, NULL, NULL, &errmsg);
	if (ret != SQLITE_OK){
		fprintf(stderr, "update failure: %s\n", errmsg);
	}

	//select table
	char *sql_str8 = "SELECT * FROM student;";
	ret = sqlite3_get_table(db, sql_str8, &dbresult, &nrow, &ncolumn, &errmsg);
	if (ret == SQLITE_OK){
		printf("query %i records.\n", nrow);
		index = ncolumn;
		for (int i = 0; i<nrow; i++){
			printf("[%2i]", i);
			for (int j = 0; j<ncolumn; j++){
				//printf(" %s", dbresult[index]);
				UTF82GBK(dbresult[index], buf, 256);
				printf(" %s", buf);
				index++;
			}
			printf("\n");
		}
	}
	else{
		fprintf(stderr, "query with get_table error: %s\n", errmsg);
	}
	sqlite3_free_table(dbresult);


	//delete table
	char *sql_str9 = "DELETE FROM student;";
	ret = sqlite3_exec(db, sql_str9, NULL, NULL, &errmsg);
	if (ret == SQLITE_OK){
		printf("delete records: %i\n", sqlite3_changes(db));
	}

	sqlite3_free(errmsg);

	//close database
	sqlite3_close(db);
	printf("Close database\n");

	return 0;
}

int print_record(void *params, int n_column, char **column_value, char **column_name){
	int i;
	char buf[256];
	for (i = 0; i<n_column; i++){
		//printf("\t%s", column_value[i]);
		UTF82GBK(column_value[i], buf, 256);
		printf("\t%s", buf);
	}
	printf("\n");
	return 0;
}

程序执行情况如下:

Open database './sqlite3-demo.db' success
Insert a record OK
Insert a record OK
Insert a record OK
Delete a record OK
        1001    Ady Liu
        1003    刘德华
        10      HELLO#10
        11      HELLO#11
        12      HELLO#12
query 12 records.
[ 0] 1001 Ady Liu
[ 1] 1003 刘德华
[ 2] 10 MESSAGE#10
[ 3] 11 HELLO#11
[ 4] 12 HELLO#12
[ 5] 13 HELLO#13
[ 6] 14 HELLO#14
[ 7] 15 HELLO#15
[ 8] 16 HELLO#16
[ 9] 17 HELLO#17
[10] 18 HELLO#18
[11] 19 HELLO#19
delete records: 12
Close database
请按任意键继续. . .

3.2 sqlite乱码处理

通过sqlite.dll接口对sqlite数据库进行操作,包括打开数据库,插入、查询数据库等,如果操作接口输入参数包含中文字符,会导致操作异常。例如调用sqlite3_open()打开数据库文件,如果文件路径出现中文,就会导致打开失败。sqlite3_exec()执行sql语句,如果包含中文对应字符就会变成乱码。

这是由于sqlite数据库使用的是UTF-8编码方式,而传入的字符串时ASCII编码或者Unicode编码,导致字符串格式错误。解决的方案是在调用sqlite接口之前,先将字符串转换成UTF-8编码。

1) 发送数据 这里我们向sqite3发送数据时,通过如下方式来自动完成UTF-8编码的装换:

#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif

2) 查询数据

查询数据时,sqlite3返回给我们的数据格式也是utf-8编码。但是我们的命令行控制台的编码格式为GBK:

cmd-encode

因此我们在这里读取出数据的时候,需要调用UTF82GBK()函数来进行转换。

3.3 关于VC平台字符编码的补充

关于vc平台上字符编码问题,有如下:

1) 一个wchar_t字符串,例如L”这是一个abc字符串”,是否是UTF-16格式?

答: 不是。是UNICODE。

2) 而char字符串,例如”这是一个abc字符串”,是否是GBK格式?

答: 是。也是ASCII

3) 在VS工程的设置里面,“使用UNICODE字符集”或者“使用多字节字符集”也并没有直接说明到底是哪种编码

答: Vs中UNICODE字符集就是UNICODE编码,多字符集就是GBK编码(或ASCII)

4) 听说Windows一般情况下并不默认使用UTF-8,那么以多字节字符集为例,是不是如果我用简体中文版Windows就是GBK,用日文版Windows就是JIS?

答: 是

5) 有没有办法完全设置为UTF-8编码的代码文本,UTF-8编码的原生字符串?

答: 暂时我没有办法

6) 若C++DLL导出函数的一个参数是const char*,C#对应参数是string,那么C++被传入的似乎是一个UTF-8编码的char字符串。若是把这字符串与VC原生的字符串混用就很尴尬。而且,若是我要给C#回传字符串,似乎也得是UTF8才行。那么,VC有什么好办法处理UTF8字符串与原生字符串的相互转换呢?

答: 使用编码转换函数,例如MultiByteToWideChar



[参看]:

  1. Windows下编译sqlite3

  2. windows下sqlite3静态库和动态库的编译

  3. C++ VS2013环境编译使用sqlite数据库全过程

  4. SQLite 教程

  5. sqlite3API函数

  6. 简单有效的内存池实现

  7. VC++ UTF-8与GBK格式转换

  8. sqlite中文乱码问题原因分析及解决(utf8和ascii相互转换)

  9. VC2017中,字符串的默认编码格式是什么?

  10. c++日志

  11. Sqlite3数据库API手册