这是 SQLite 数据库的 C 编程教程,它介绍了使用 C 语言进行 SQLite 编程的基础知识。你可能还需要 SQLite tutorial 教程。
SQL数据库
- SQLite 是一个支持嵌入式的关系型数据库引擎。它的开发人员称其为自包含(self-contained)、无服务器(serverless)、零配置和事务性的 SQL 数据库引擎。
- 它目前非常流行,今天全世界有数亿设备在使用。SQLite 用于 Solaris 10、Mac OS、Android 或 iPhone。
- Qt4 库具有对 SQLite 以及 Python 和 PHP 的内置支持。
- 许多流行的应用程序在内部使用 SQLite,比如 Firefox、Google Chrome 或 Amarok。
SQLite3 工具
SQLite3 工具是 SQLite 库的基于终端的前端。它以交互方式来分析用户提供的查询 (queries) 操作,并以多种格式显示结果。它还可以在脚本中使用。它有自己的一组元命令,包括 .tables
、.load
、.database
或.dump
。要获取所有指令的列表,我们只需键入. help 命令。
现在,我们将使用 sqlite3 工具创建一个新数据库。
$ sqlite3 test.db
SQLite version 3.22.0 2018-01-22 18:45:57
Enter ".help" for usage hints.
这里,我们为 sqlite3 工具提供了一个参数;test.db 是一个数据库名称。这是我们磁盘上的一个文件。如果它存在,则会将其打开。如果不是,则创建它。
sqlite> .tables
sqlite> .exit
tables
命令提供 test.db 数据库中所有存在的的表 (table)。当前没有表,因为我们没有创建它。exit
命令终止 sqlite3 命令行工具的交互会话。
退出后,通过 unix 的 ls
命令显示当前工作目录的内容。我们可以看到 test.db 文件。数据库会把所有数据都将存储在此单个文件中。
$ ls
test.db
C99
本教程使用 C99。对于 GNU C 编译器,我们需要使用 -std=c99
选项。对于 Windows 用户,推荐使用 PELES C IDE。(MSVC 不支持 C99。)。
int rc=sqlite3_open(“test.db”,&db);
在 C99 中,我们可以将声明与代码混合使用。但在旧的 C 程序中,我们需要将此行分成两行。
获取 SQLite 版本
在第一个代码示例中,我们将获得 SQLite 数据库的版本。
// -- 获取 SQLite 版本 v1 --
#include
#include
int main(void)
{
printf("%s\n", sqlite3_libversion());
return 0;
}
sqlite3_libversion
函数的作用是:返回一个指示 SQLite 库的字符串。
头文件 sqlite3.h
定义了 SQLite 库提供给客户端程序的接口。它包含定义、函数原型和注释。它是 SQLite API 的权威来源。
我们使用 GNU C 编译器编译程序。然后运行它
$ gcc -o version version.c -lsqlite3 -std=c99
$ ./version
3.22.0
在第二个示例中,我们再次获取 SQLite 数据库的版本。但这次我们将使用 SQL 的查询 (query) 功能。
// -- 获取 SQLite 版本 v2 --
#include
#include
int main(void)
{
sqlite3 *db;
sqlite3_stmt *pStmt;
// 打开数据库
if (sqlite3_open(":memory:", &db) != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 编译 SQL 语句
if (sqlite3_prepare_v2(db, "SELECT SQLITE_VERSION()", -1, &pStmt, 0) != SQLITE_OK) {
fprintf(stderr, "Failed to fetch data: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 执行 SQL 语句
if (sqlite3_step(pStmt) == SQLITE_ROW)
printf("%s\n", sqlite3_column_text(pStmt, 0));
// 释放 SQL 语句句柄并关闭数据库
sqlite3_finalize(pStmt);
sqlite3_close(db);
return 0;
}
SQLITE_VERSION()
查询用于获取 SQLite 库的版本。
sqlite3 *db;
sqlite3_stmt *pStmt;
sqlite3
结构定义了一个数据库句柄。每个打开的 SQLite 数据库由一个数据库句柄表示。
sqlite3_stmt
结构表示单个 SQL 语句 (statement)
sqlite3_open(":memory:", &db);
sqlite3_open
函数的作用是:打开一个数据库。它的参数是数据库名称和数据库句柄。
:memory: 是一个特殊的数据库名称,使用它可以新开一个数据库且该数据存放在内存中。函数的返回代码指示数据库是否已成功打开。成功打开并建立连接时返回 SQLITE_OK。
如果返回代码指示错误,这里我们会将消息打印到控制台,关闭数据库句柄,然后终止程序。
sqlite3_errmsg
函数的作用是:返回错误的描述。
无论打开数据库连接句柄时是否发生错误,都应该通过将其传递给 sqlite3_close()
函数来释放与数据库连接句柄关联的资源。
sqlite3_prepare_v2(db, "SELECT SQLITE_VERSION()", -1, &pStmt, 0);
就像 C/C++ 或者 Java 那样,需要先编译才能执行命令。所以在执行 SQL 语句之前,必须首先使用 sqlite3_prepare
系列函数之一将其编译成字节码 (sqlite3_prepare() 函数已弃用)。
sqlite3_prepare_v2()
函数接受五个参数:
- 第一个参数是从
sqlite3_open()
函数获得的数据库句柄 - 第二个参数是要编译的 SQL 语句
- 第三个参数是 SQL 语句的最大长度 (以字节为单位),传递 -1 会导致 SQL 连续地向后读取语句字符串,直到出现第一个
'\0'
终止符为止,也就是 C 字符串的末尾。根据文档说明,通过传递所提供字符串的确切字节数,可以获得一些较小的性能优势。 - 第四个参数是语句句柄。如果
sqlite3_prepare_v2()
运行成功,它将指向编译好的字节码语句。 - 最后一个参数是指向 SQL 语句未使用部分的指针。因为函数每次只编译 SQL 字符串的第一条语句,因此该参数指向剩下的未编译内容。我们传递 0,因为该参数对我们来说并不重要。
如果成功,sqlite3_prepare_v2()
将返回 SQLITE_OK;否则将返回错误代码。
sqlite3_step(pStmt);
sqlite3_step()
函数用来运行 SQL 语句。SQLITE_ROW 返回代码表示已经准备好下一行 (row) 数据,这说明我们的代码已经被正确执行。因为我们的 SQL 语句就为了获取版本号,它只返回一条数据,因此,我们只调用此函数一次。如果一次执行中可能会返回多条数据,我们可以进行多次调用,每调用一次就获取一条数据,SQL 内部会自动向下迭代。
sqlite3_column_text(pStmt, 0);
正确执行后 pStmt 句柄包含当前所迭代到的数据的索引信息,我们可以用它来访问该条数据信息。
sqlite3_finalize(res)
函数的作用是:销毁我们准备好的语句对象。
最后sqlite3_close()
函数关闭了与数据库的连接。
插入数据
我们创建一个 CARS 表,并在其中插入几行。
// -- SQLite3 数据库插入数据的例子 --
#include
#include
int main(void)
{
sqlite3 *db;
char *err_msg = 0;
// 打开数据库
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 设置 SQL 语句字符串
char *sql = "DROP TABLE IF EXISTS Cars;"
"CREATE TABLE Cars(Id INT, Name TEXT, Price INT);"
"INSERT INTO Cars VALUES(1, 'Audi', 52642);"
"INSERT INTO Cars VALUES(2, 'Mercedes', 57127);"
"INSERT INTO Cars VALUES(3, 'Skoda', 9000);"
"INSERT INTO Cars VALUES(4, 'Volvo', 29000);"
"INSERT INTO Cars VALUES(5, 'Bentley', 350000);"
"INSERT INTO Cars VALUES(6, 'Citroen', 21000);"
"INSERT INTO Cars VALUES(7, 'Hummer', 41400);"
"INSERT INTO Cars VALUES(8, 'Volkswagen', 21600);";
// 编译并执行 SQL 语句字符串
if (sqlite3_exec(db, sql, 0, 0, &err_msg) != SQLITE_OK ) {
fprintf(stderr, "SQL error: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return 1;
}
sqlite3_close(db);
return 0;
}
上面代码中,我们连接到 test.db 数据库,创建一个 CARS 表,并在创建的表中插入 8 行数据。
char *err_msg = 0;
如果出现错误,此指针将指向的错误消息。
然后是设置 SQL 语句字符串, 这些 SQL 语句会创建一个 CARS 表,并用数据填充它。语句必须用分号 ';'
分隔。
rc = sqlite3_exec(db, sql, 0, 0, &err_msg);
sqlite3_exec
函数是 sqlite3_prepare_v2
、sqlite3_step
和 sqlite3_finalize
的便捷封装方式,它允许应用程序运行 SQL 的多条语句,而不必使用大量 C 代码。
- 第三个参数是一个回调函数,用于访问 SQL 语句中的每个结果行。
- 第四个参数是给回调函数的第一个参数。如果我们不需要它们,可以设置为 0 或者 NULL。
- 如果发生错误,则最后一个参数指向分配的错误消息。
sqlite3_free(err_msg);
必须使用 sqlite3_free() 函数调用释放分配的消息字符串。
我们可以使用 sqlite3 工具验证写入的数据。首先,我们修改数据在控制台中的显示方式。这里我们使用清爽的列对齐显示模式,并要求显示每条数据的标题。然后执行 SELECT * FROM Cars;
语句。
sqlite> .mode column
sqlite> .headers on
sqlite> SELECT * FROM Cars;
Id Name Price
---------- ---------- ----------
1 Audi 52642
2 Mercedes 57127
3 Skoda 9000
4 Volvo 29000
5 Bentley 350000
6 Citroen 21000
7 Hummer 41400
8 Volkswagen 21600
这就是我们写入CARS表的数据。
最后插入的行ID
有时,我们需要确定最后插入的行的 ID。为此,我们使用 sqlite3_last_insert_rowid
函数。如下
int last_id = sqlite3_last_insert_rowid(db);
printf("The last Id of the inserted row is %d\n", last_id);
下面语句执行后会在内存中创建一个 Friends 表。其 ID 列自动递增。
char *sql = "CREATE TABLE Friends(Id INTEGER PRIMARY KEY, Name TEXT);"
"INSERT INTO Friends(Name) VALUES ('Tom');"
"INSERT INTO Friends(Name) VALUES ('Rebecca');"
"INSERT INTO Friends(Name) VALUES ('Jim');"
"INSERT INTO Friends(Name) VALUES ('Roger');"
"INSERT INTO Friends(Name) VALUES ('Robert');";
在 SQLite 中,整数主键 (PRIMARY KEY) 列会自动递增。还有一个 AUTOINCREMENT
关键字。当该关键字应用于整数主键时,使用的 ID 创建算法略有不同。当使用自动递增列时,我们需要显式地声明列名。
检索数据
我们已经将一些数据插入到 test.db 数据库中。在下面的示例中,我们将从数据库检索数据。
// -- 检索数据 Callback方法
#include
#include
int callback(void *, int, char **, char **);
int main(void) {
sqlite3 *db;
char *err_msg = 0;
// 打开数据库
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n",
sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 设置语句并编译执行
char *sql = "SELECT * FROM Cars;";
if (sqlite3_exec(db, sql, callback, 0, &err_msg) != SQLITE_OK ) {
fprintf(stderr, "Failed to select data\n");
fprintf(stderr, "SQL error: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return 1;
}
sqlite3_close(db);
return 0;
}
// 回调函数
int callback(void *NotUsed, int argc, char **argv, char **azColName) {
NotUsed = 0;
for (int i = 0; i < argc; i++)
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
printf("\n");
return 0;
}
我们使用 SELECT * FROM Cars;
语句从 CARS 表中检索所有行。
int callback(void *, int, char **, char **);
这是与 sqlite3_exec() 函数结合使用的回调函数的函数原型。
sqlite3_exec(db, sql, callback, 0, &err_msg);
这次 sqlite3_exec
函数中的我们加入了回调函数,sqlite3_exec 在处理求值语句时每得到一个结果行将调用一次 callback
函数。
int callback(void *NotUsed, int argc, char **argv, char **azColName) {
NotUsed = 0;
for (int i = 0; i < argc; i++)
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
printf("\n");
return 0;
}
来看这个回调函数
- 第一个参数是
sqlite3_exec
的第四个参数中提供的数据,通常不使用它。 - 第二个参数是结果行中列的个数。
- 第三个参数是表示结果行中字段的字符串数组。它包含着我们每一行的数据。
- 最后一个参数就是我们设置的标题行,该行的每一列保存着每一列数据的标题名称。
所以上面代码中,我们遍历结果行和标题行中的所有列就并打印出了它们的名称和内容。
$ ./select_all
Id = 1
Name = Audi
Price = 52642
Id = 2
Name = Mercedes
Price = 57127
...
以上是示例的部分输出。
参数化查询
现在我们将用到参数化查询。参数化查询,也称为预准备语句 (prepared statements), 可提高安全性和性能。当我们使用参数化查询时,我们使用占位符,而不是直接将值写入语句。
// -- SQLite3 参数化查询例子 --
#include
#include
int main(void) {
sqlite3 *db;
char *err_msg = 0;
sqlite3_stmt *pStmt;
// 打开数据库
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 设置并预编译 SQL 语句
char *sql = "SELECT Id, Name FROM Cars WHERE Id = ?";
if (sqlite3_prepare_v2(db, sql, -1, &pStmt, 0) == SQLITE_OK)
// 如果预编译成功,则对占位符进行绑定:第一个占位符,设置为 3
sqlite3_bind_int(pStmt, 1, 3);
else
fprintf(stderr, "Failed to execute statement: %s\n", sqlite3_errmsg(db));
// 执行语句获取结果行,如果数据可用则获取数据
if (sqlite3_step(pStmt) == SQLITE_ROW) {
printf("%s: ", sqlite3_column_text(pStmt, 0));
printf("%s\n", sqlite3_column_text(pStmt, 1));
}
sqlite3_finalize(pStmt);
sqlite3_close(db);
return 0;
}
在本例中,问号 '?'
用作占位符,稍后将其替换为实际值并为 SQL 查询提供 Id。
char *sql = "SELECT Id, Name FROM Cars WHERE Id = ?";
sqlite3_bind_int(pStmt, 1, 3);
sqlite3_bind_int
将整数值绑定到准备好的语句,占位符替换为整数值3。
- 第二个参数表明要设置第几个占位符,这里我们只有一个占位符
- 第三个参数表明要把占位符替换成什么
sqlite3_step
函数将执行处理好的 SQL 语句,并索引到执行结果行。
如果有某行数据可用,我们将使用 sqlite3_column_text
函数获取结果行中的两列的值,即
printf("%s: ", sqlite3_column_text(res, 0));
printf("%s\n", sqlite3_column_text(res, 1));
$ ./parameterized
3: Skoda
正如所期待的,该示例返回第三个数据的 Id 和汽车的名称,因为我们绑定到占位符的数据是 3 。
第二个示例使用带有命名占位符的参数化语句
// -- SQLite3 使用命名占位例子 --
#include
#include
int main(void)
{
sqlite3 *db;
char *err_msg = 0;
sqlite3_stmt *pStmt;
// 打开数据库
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 设置并预编译 SQL 语句
char *sql = "SELECT Id, Name FROM Cars WHERE Id = @id";
if (sqlite3_prepare_v2(db, sql, -1, &pStmt, 0) != SQLITE_OK) {
fprintf(stderr, "Failed to execute statement: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 通过占位符名 @name 获取占位符位置
int idx = sqlite3_bind_parameter_index(pStmt, "@id");
int value = 4;
// 把整数值绑定到语句中的占位符位置
sqlite3_bind_int(pStmt, idx, value);
// 执行语句获取结果行,如果数据可用则打印数据
if (sqlite3_step(pStmt) == SQLITE_ROW) {
printf("%s: ", sqlite3_column_text(pStmt, 0));
printf("%s\n", sqlite3_column_text(pStmt, 1));
}
sqlite3_finalize(pStmt);
sqlite3_close(db);
return 0;
}
命名占位符的前缀可以是 '@'
, 也可以是冒号 ':'
。
int idx = sqlite3_bind_parameter_index(pStmt, "@id");
sqlite3_bind_parameter_index
函数的作用是返回给定占位符名称的 SQL 占位符的索引。
插入图片
在本节中,我们将向 SQLite 数据库插入图像。请注意,有些人反对将图像放入数据库。这里我们只演示如何做到这一点。我们没有详细讨论是否应该将图像保存在数据库中的技术问题。
sqlite> CREATE TABLE Images(Id INTEGER PRIMARY KEY, Data BLOB);
对于本例,我们创建一个名为 Images 的新表。其中我们使用 BLOB 数据类型来存放图片,它代表二进制大对象。
#include
#include
#include
int main(int argc, char **argv) {
FILE *fp = fopen("woman.jpg", "rb");
if (fp == NULL) {
fprintf(stderr, "Cannot open image file\n");
return 1;
}
// 获取文件大小,在这里为了简洁,我们没有对获取过程
// 可能出现的出错进行处理,实际开发应注意这一点
fseek(fp, 0, SEEK_END);
int fsize = ftell(fp);
fseek(fp, 0, SEEK_SET);
// 根据获取文件大小获取内存,并将文件读入到内存
char *data = (char*)malloc(sizeof(char)*fsize);
if(data == NULL || fread(data, 1, fsize, fp) != fsize) {
fprintf(stderr, "malloc or readfile error\n");
fclose(fp);
return 1;
}
fclose(fp);
sqlite3 *db;
char *err_msg = 0;
sqlite3_stmt *pStmt;
// 打开数据库
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 语句预处理
char *sql = "INSERT INTO Images(Data) VALUES(?)";
if (sqlite3_prepare(db, sql, -1, &pStmt, 0) != SQLITE_OK) {
fprintf(stderr, "Cannot prepare statement: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 绑定占位符并插入内存中的二进制文件
sqlite3_bind_blob(pStmt, 1, data, fsize, SQLITE_STATIC);
if (sqlite3_step(pStmt) != SQLITE_DONE)
printf("execution failed: %s", sqlite3_errmsg(db));
sqlite3_finalize(pStmt);
sqlite3_close(db);
return 0;
}
在此程序中,我们从当前工作目录读取图像到堆内存中,之后将其写入 SQLite test.db 数据库的 Images 表。
sqlite3_bind_blob(pStmt, 1, data, size, SQLITE_STATIC);
sqlite3_bind_blob
函数的作用是:将二进制数据绑定到编译后的语句中的占位符。SQLITE_STATIC 参数表示指向内容信息的指针是静态的,不需要由 SQLite 释放,这将由我们主动释放。
sqlite3_step(pStmt);
最后调用 sqlite3_step
执行上面处理好的 SQLite 语句 pStmt
便可以将图形写入到表中。
读取图像
在本节中,我们将执行相反的操作,从数据库表中读取图像。
#include
#include
int main(void) {
FILE *fp = fopen("2233.jpg", "wb");
if (fp == NULL) {
fprintf(stderr, "Cannot open image file\n");
return 1;
}
sqlite3 *db;
char *err_msg = 0;
sqlite3_stmt *pStmt;
// 打开数据库
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 语句预处理
char *sql = "SELECT Data FROM Images WHERE Id = 1";
if (sqlite3_prepare_v2(db, sql, -1, &pStmt, 0) != SQLITE_OK) {
fprintf(stderr, "Cannot prepare statement: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 执行语句
if (sqlite3_step(pStmt) == SQLITE_ROW) {
// 先获取对应列数据的大小
int bytes = sqlite3_column_bytes(pStmt, 0);
// 获取二进制文件并将其写入文件
fwrite(sqlite3_column_blob(pStmt, 0), bytes, 1, fp);
}
fclose(fp);
sqlite3_finalize(pStmt);
sqlite3_close(db);
return 0;
}
bytes = sqlite3_column_bytes(pStmt, 0);
fwrite(sqlite3_column_blob(pStmt, 0), bytes, 1, fp);
sqlite3_column_bytes
函数的作用是:返回 BLOB 中的字节数。
sqlite3_column_blob
函数返回指向所选二进制数据的指针。
元数据
元数据是关于数据库中数据的信息。SQLite 中的元数据包含有关表和列的信息,我们在这些表和列中存储数据。受 SQL 语句影响的行数是元数据。结果集中返回的行数和列数也属于元数据。
可以使用 Pragma 命令获取 SQLite 中的元数据。SQLite 对象可能具有属性,这些属性是元数据。最后,我们还可以通过查询 SQLite 系统 sqlite_master 表来获得特定的元数据。
#include
#include
int callback(void *, int, char **, char **);
int main(void) {
sqlite3 *db;
char *err_msg = 0;
// 打开数据库
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 编译并执行语句
char *sql = "PRAGMA table_info(Cars);";
if (sqlite3_exec(db, sql, callback, 0, &err_msg) != SQLITE_OK ) {
fprintf(stderr, "Failed to select data %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return 1;
}
sqlite3_close(db);
return 0;
}
int callback(void *NotUsed, int argc, char **argv, char **azColName)
{
NotUsed = 0;
for (int i = 0; i < argc; i++)
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
printf("------------\n");
return 0;
}
在本例中,我们执行语句 PRAGMA table_info(Cars);
,以获取有关 CARS 表的一些元数据信息。结果集中的列包括列序号、列名、数据类型、列是否可以为NULL以及列的默认值。
$ ./column_names
cid = 0
name = Id
type = INT
notnull = 0
dflt_value = NULL
pk = 1
------------
cid = 1
name = Name
type = TEXT
notnull = 0
dflt_value = NULL
pk = 0
------------
cid = 2
name = Price
type = INT
notnull = 0
dflt_value = NULL
pk = 0
------------
在下一个与元数据相关的示例中,我们将列出test.db数据库中的所有表
#include
#include
int callback(void *, int, char **, char **);
int main(void) {
sqlite3 *db;
char *err_msg = 0;
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
char *sql = "SELECT name FROM sqlite_master WHERE type='table'";
if (sqlite3_exec(db, sql, callback, 0, &err_msg) != SQLITE_OK ) {
fprintf(stderr, "SQL error: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return 1;
}
sqlite3_close(db);
return 0;
}
int callback(void *NotUsed, int argc, char **argv, char **azColName) {
NotUsed = 0;
for (int i = 0; i < argc; i++)
printf("%s\n", argv[i] ? argv[i] : "NULL");
return 0;
}
代码示例将当前数据库中的所有可用表打印到终端。
$ ./list_tables
Friends
Cars
Images
以上就是之前操作的数据内容。
事务
事务是针对一个或多个数据库中的数据执行的数据库操作的原子单元。事务中所有 SQL 语句的效果可以全部提交到数据库,也可以全部回滚。
在 SQLite 中,除 SELECT 之外的任何命令都将启动隐式事务。此外,在事务中,像 create table.、Vacuum、Pragma 这样的命令将在执行之前提交以前的更改。
手动事务以 BEGIN TRANSACTION 语句开始,以 COMMIT 或 ROLLBACK 语句结束。
SQLite 支持三个非标准事务级别:延迟、立即和独占。
自动提交
默认情况下,SQLite 版本 3 在自动提交模式下运行。在自动提交模式下,与当前数据库连接关联的所有操作完成后,立即提交对数据库的所有更改。自动提交模式由 BEGIN 语句禁用,由 COMMIT 或 ROLLBACK 重新启用。
#include
#include
int main() {
sqlite3 *db;
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
printf("Autocommit: %d\n", sqlite3_get_autocommit(db));
sqlite3_close(db);
return 0;
}
printf("Autocommit: %d\n", sqlite3_get_autocommit(db));
此示例检查数据库是否处于自动提交模式,如果数据库未处于自动提交模式,则 sqlite3_get_autoCommit() 函数返回零。如果处于自动提交模式,则返回非零值。
$ ./get_ac_mode
Autocommit: 1
该示例确认 SQLite 默认处于自动提交模式。
下一个示例进一步阐明了自动提交模式。在自动提交模式下,每个非 SELECT 语句都是一个立即提交的小事务。
#include
#include
int main(void) {
sqlite3 *db;
char *err_msg = 0;
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
char *sql = "DROP TABLE IF EXISTS Friends;"
"CREATE TABLE Friends(Id INTEGER PRIMARY KEY, Name TEXT);"
"INSERT INTO Friends(Name) VALUES ('Tom');"
"INSERT INTO Friends(Name) VALUES ('Rebecca');"
"INSERT INTO Friends(Name) VALUES ('Jim');"
"INSERT INTO Friend(Name) VALUES ('Robert');";
if (sqlite3_exec(db, sql, 0, 0, &err_msg) != SQLITE_OK ) {
fprintf(stderr, "SQL error: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return 1;
}
sqlite3_close(db);
return 0;
}
我们创建 Friends 表,并尝试用数据填充它,最后一条 SQL 语句有一个错误;因为没有 Friend 表。
$ ./autocommit
SQL error: no such table: Friend
$ sqlite3 test.db
sqlite> .tables
Cars Friends Images
sqlite> SELECT * FROM Friends;
1|Tom
2|Rebecca
3|Jim
因为每插入一个就自动提交一次,所以虽然插入最后一个数据的时候发生了错误,但是前面的三个数据已经提交到数据库里了。
交易
在下一个示例中,我们将一些 SQL 语句插入到事务中
#include
#include
int main(void) {
sqlite3 *db;
char *err_msg = 0;
if (sqlite3_open("test.db", &db) != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
char *sql = "DROP TABLE IF EXISTS Friends;"
"BEGIN TRANSACTION;"
"CREATE TABLE Friends(Id INTEGER PRIMARY KEY, Name TEXT);"
"INSERT INTO Friends(Name) VALUES ('Tom');"
"INSERT INTO Friends(Name) VALUES ('Rebecca');"
"INSERT INTO Friends(Name) VALUES ('Jim');"
"INSERT INTO Friend(Name) VALUES ('Robert');"
"COMMIT;";
if (sqlite3_exec(db, sql, 0, 0, &err_msg) != SQLITE_OK ) {
fprintf(stderr, "SQL error: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return 1;
}
sqlite3_close(db);
return 0;
}
第一个语句如果存在表 Friends
则将其删除。其他语句则放在事务中。事务在全有或全无模式下工作。要么什么都不做,要么什么都做。
sqlite> .tables
Cars Images
因为最后一条语句有错误,所以事务被回滚,并且没有创建Friends表。