ZetCode

SQLite C

最后修改于 2020 年 7 月 6 日

这是一个针对 SQLite 数据库的 C 语言编程教程。它涵盖了使用 C 语言进行 SQLite 编程的基础知识。您可能还想查看 ZetCode 上的 SQLite 教程MySQL C 教程PostgreSQL C 教程

SQLite 数据库

SQLite 是一个嵌入式关系数据库引擎。其开发者称之为一个自包含、无服务器、零配置的事务性 SQL 数据库引擎。它目前非常流行,全世界有数亿个副本在使用。SQLite 被用于 Solaris 10、Mac OS、Android 或 iPhone 中。Qt4 库以及 Python 和 PHP 也内置了对 SQLite 的支持。许多流行的应用程序内部都使用 SQLite,例如 Firefox、Google Chrome 或 Amarok。

sqlite3 工具

sqlite3 工具是一个基于终端的 SQLite 库前端。它能以交互方式评估查询并以多种格式显示结果。它也可以在脚本中使用。它有自己的一套元命令,包括 .tables.load.databases.dump。要获取所有指令的列表,我们输入 .help 命令。

现在我们将使用 sqlite3 工具来创建一个新数据库。

$ sqlite3 test.db
SQLite version 3.8.2 2013-12-06 14:53:30
Enter ".help" for instructions
Enter SQL statements terminated with a ";"

我们向 sqlite3 工具 提供一个参数;test.db 是数据库名称。它是我们磁盘上的一个文件。如果存在,它会被打开。如果不存在,它会被创建。

sqlite> .tables
sqlite> .exit
$ ls
test.db

.tables 命令给出了 test.db 数据库中的表列表。目前没有表。.exit 命令终止 sqlite3 命令行工具的交互式会话。ls Unix 命令显示当前工作目录的内容。我们可以看到 test.db 文件。所有数据都将存储在这个单一的文件中。

C99

本教程使用 C99。对于 GNU C 编译器,我们需要使用 -std=c99 选项。对于 Windows 用户,强烈推荐 Pelles C IDE。(MSVC 不支持 C99。)

int rc = sqlite3_open("test.db", &db);

在 C99 中,我们可以将声明与代码混合在一起。在旧的 C 程序中,我们需要将这一行分成两行。

SQLite C 版本

在第一个代码示例中,我们将获取 SQLite 数据库的版本。

version.c
#include <sqlite3.h>
#include <stdio.h>

int main(void) {
    
    printf("%s\n", sqlite3_libversion()); 
    
    return 0;
}

sqlite3_libversion 函数返回一个指示 SQLite 库版本的字符串。

#include <sqlite3.h>

这个头文件定义了 SQLite 库呈现给客户端程序的接口。它包含定义、函数原型和注释。它是 SQLite API 的权威来源。

$ gcc -o version version.c -lsqlite3 -std=c99

我们使用 GNU C 编译器编译该程序。

$ ./version
3.8.2

这是示例的输出。

在第二个示例中,我们再次获取 SQLite 数据库的版本。这次我们将使用 SQL 查询。

version2.c
#include <sqlite3.h>
#include <stdio.h>

int main(void) {
    
    sqlite3 *db;
    sqlite3_stmt *res;
    
    int rc = sqlite3_open(":memory:", &db);
    
    if (rc != SQLITE_OK) {
        
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        
        return 1;
    }
    
    rc = sqlite3_prepare_v2(db, "SELECT SQLITE_VERSION()", -1, &res, 0);    
    
    if (rc != SQLITE_OK) {
        
        fprintf(stderr, "Failed to fetch data: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        
        return 1;
    }    
    
    rc = sqlite3_step(res);
    
    if (rc == SQLITE_ROW) {
        printf("%s\n", sqlite3_column_text(res, 0));
    }
    
    sqlite3_finalize(res);
    sqlite3_close(db);
    
    return 0;
}

SQLITE_VERSION 查询用于获取 SQLite 库的版本。

sqlite3 *db;

sqlite3 结构体定义了一个数据库句柄。每个打开的 SQLite 数据库都由一个数据库句柄表示。

sqlite3_stmt *res;

sqlite3_stmt 结构体表示一个单一的 SQL 语句。

int rc = sqlite3_open(":memory:", &db);

sqlite3_open 函数打开一个新的数据库连接。它的参数是数据库名称和数据库句柄。:memory: 是一个特殊的数据库名称,使用它会打开一个内存数据库。该函数的返回码指示数据库是否成功打开。当连接成功建立时,返回 SQLITE_OK

if (rc != SQLITE_OK) {
    
    fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
    sqlite3_close(db);
    
    return 1;
}

如果返回码指示有错误,我们会将消息打印到控制台,关闭数据库句柄,并终止程序。sqlite3_errmsg 函数返回错误的描述。无论打开时是否发生错误,与数据库连接句柄相关的资源都应通过将其传递给 sqlite3_close 函数来释放。

rc = sqlite3_prepare_v2(db, "SELECT SQLITE_VERSION()", -1, &res, 0);

在执行 SQL 语句之前,必须首先使用 sqlite3_prepare* 函数之一将其编译成字节码。(sqlite3_prepare 函数已弃用。)

sqlite3_prepare_v2 函数有五个参数。第一个参数是从 sqlite3_open 函数获得的数据库句柄。第二个参数是要编译的 SQL 语句。第三个参数是 SQL 语句的最大长度,以字节为单位。传递 -1 会使 SQL 字符串一直读到第一个零终止符,也就是这里的字符串结尾。根据文档,通过传递所提供 SQL 字符串的确切字节数,可以获得一些小的性能优势。第四个参数是语句句柄。如果 sqlite3_prepare_v2 成功运行,它将指向预编译的语句。最后一个参数是一个指向 SQL 语句未使用部分的指针。只有 SQL 字符串的第一个语句被编译,所以该参数指向未编译的剩余部分。我们传递 0,因为这个参数对我们不重要。

成功时,sqlite3_prepare_v2 返回 SQLITE_OK;否则返回一个错误码。

if (rc != SQLITE_OK) {
    
    fprintf(stderr, "Failed to fetch data: %s\n", sqlite3_errmsg(db));
    sqlite3_close(db);
    
    return 1;
}    

这是针对 sqlite3_prepare_v2 函数调用的错误处理代码。

rc = sqlite3_step(res);

sqlite3_step 运行 SQL 语句。SQLITE_ROW 返回码表示有另一行已准备好。我们的 SQL 语句只返回一行数据,因此,我们只调用这个函数一次。

sqlite3_finalize(res);

sqlite3_finalize 函数销毁预编译的语句对象。

sqlite3_close(db);

sqlite3_close 函数关闭数据库连接。

SQLite C 插入数据

我们创建一个 Cars 表并向其中插入几行数据。

insert_data.c
#include <sqlite3.h>
#include <stdio.h>

int main(void) {
    
    sqlite3 *db;
    char *err_msg = 0;
    
    int rc = sqlite3_open("test.db", &db);
    
    if (rc != SQLITE_OK) {
        
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        
        return 1;
    }
    
    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);";

    rc = sqlite3_exec(db, sql, 0, 0, &err_msg);
    
    if (rc != 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;

如果发生错误,此指针将指向创建的错误消息。

int rc = sqlite3_open("test.db", &db);

test.db 数据库的连接被创建。

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 语句创建了一个 Cars 表并用数据填充它。语句必须用分号分隔。

rc = sqlite3_exec(db, sql, 0, 0, &err_msg);

sqlite3_exec 函数是 sqlite3_prepare_v2sqlite3_stepsqlite3_finalize 的一个便捷包装器,它允许应用程序运行多个 SQL 语句而无需使用大量的 C 代码。

该函数的第三个参数是一个回调函数,对于从评估的 SQL 语句中产生的每个结果行都会调用它。第四个参数是回调函数的第一个参数。如果我们不需要它们,可以为这些参数传递 0。

如果发生错误,最后一个参数会指向已分配的错误消息。

sqlite3_free(err_msg);

分配的消息字符串必须通过调用 sqlite3_free 函数来释放。

sqlite> .mode column  
sqlite> .headers on

我们使用 sqlite3 工具来验证写入的数据。首先,我们修改数据在控制台中的显示方式。我们使用列模式并打开表头。

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 表中的数据。

SQLite C 最后插入行的 ID

有时,我们需要确定最后插入行的 ID。为此,我们有 sqlite3_last_insert_rowid 函数。

last_row_id.c
#include <sqlite3.h>
#include <stdio.h>

int main(void) {
    
    sqlite3 *db;
    char *err_msg = 0;
    
    int rc = sqlite3_open(":memory:", &db);
    
    if (rc != SQLITE_OK) {
        
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        
        return 1;
    }
    
    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');";
        
    
    rc = sqlite3_exec(db, sql, 0, 0, &err_msg);
    
    if (rc != SQLITE_OK ) {
        
        fprintf(stderr, "Failed to create table\n");
        fprintf(stderr, "SQL error: %s\n", err_msg);
        sqlite3_free(err_msg);
        
    } else {
        
        fprintf(stdout, "Table Friends created successfully\n");
    }
    
    int last_id = sqlite3_last_insert_rowid(db);
    printf("The last Id of the inserted row is %d\n", last_id);

    sqlite3_close(db);
    
    return 0;
}

一个 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 中,INTEGER PRIMARY KEY 列是自动递增的。还有一个 AUTOINCREMENT 关键字。当应用于 INTEGER PRIMARY KEY AUTOINCREMENT 时,会使用一种稍微不同的算法来创建 ID。

当使用自动递增列时,我们需要明确声明除自动递增列之外的列名,自动递增列被省略。

int last_id = sqlite3_last_insert_rowid(db);
printf("The last Id of the inserted row is %d\n", last_id);

sqlite3_last_insert_rowid 返回最近一次成功插入到表中的行的 ID。

$ ./last_row_id 
Table Friends created successfully
The last Id of the inserted row is 5

我们看到了程序的输出。

SQLite C 检索数据

我们已经向 test.db 数据库插入了一些数据。在下面的示例中,我们从数据库中检索数据。

select_all.c
#include <sqlite3.h>
#include <stdio.h>


int callback(void *, int, char **, char **);


int main(void) {
    
    sqlite3 *db;
    char *err_msg = 0;
    
    int rc = sqlite3_open("test.db", &db);
    
    if (rc != SQLITE_OK) {
        
        fprintf(stderr, "Cannot open database: %s\n", 
                sqlite3_errmsg(db));
        sqlite3_close(db);
        
        return 1;
    }
    
    char *sql = "SELECT * FROM Cars";
        
    rc = sqlite3_exec(db, sql, callback, 0, &err_msg);
    
    if (rc != 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 SQL 语句来从 Cars 表中检索所有行。

int callback(void *, int, char **, char **);

这是一个与 sqlite3_exec 函数一起使用的回调函数的函数原型。

int rc = sqlite3_open("test.db", &db);

我们连接到 test.db 数据库。

char *sql = "SELECT * FROM Cars";

这里我们定义了用于从 Cars 表中选择所有数据的 SQL 语句。

rc = sqlite3_exec(db, sql, callback, 0, &err_msg);

sqlite3_exec 函数评估 SQL 语句。它的回调函数会为从评估的 SQL 语句中产生的每个结果行被调用。

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 的第 4 个参数中提供的数据;它通常不被使用。第二个参数是结果中的列数。第三个参数是一个表示行中字段的字符串数组。最后一个参数是表示列名的字符串数组。

在函数体中,我们遍历所有列并打印它们的名称和内容。

$ ./select_all 
Id = 1
Name = Audi
Price = 52642

Id = 2
Name = Mercedes
Price = 57127

Id = 3
Name = Skoda
Price = 9000
...

这是示例的部分输出。

SQLite C 参数化查询

现在我们将提到参数化查询。参数化查询,也称为预处理语句,可以提高安全性和性能。当我们使用参数化查询时,我们使用占位符而不是直接将值写入语句中。

parameterized.c
#include <sqlite3.h>
#include <stdio.h>

int main(void) {
    
    sqlite3 *db;
    char *err_msg = 0;
    sqlite3_stmt *res;
    
    int rc = sqlite3_open("test.db", &db);
    
    if (rc != SQLITE_OK) {
        
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        
        return 1;
    }
    
    char *sql = "SELECT Id, Name FROM Cars WHERE Id = ?";
        
    rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
    
    if (rc == SQLITE_OK) {
        
        sqlite3_bind_int(res, 1, 3);
    } else {
        
        fprintf(stderr, "Failed to execute statement: %s\n", sqlite3_errmsg(db));
    }
    
    int step = sqlite3_step(res);
    
    if (step == SQLITE_ROW) {
        
        printf("%s: ", sqlite3_column_text(res, 0));
        printf("%s\n", sqlite3_column_text(res, 1));
        
    } 

    sqlite3_finalize(res);
    sqlite3_close(db);
    
    return 0;
}

在该示例中,问号 (?) 用作占位符,之后会被替换为实际值。

char *sql = "SELECT Id, Name FROM Cars WHERE Id = ?";

问号用于向 SQL 查询提供一个 ID。

rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);

sqlite3_prepare_v2 函数编译 SQL 查询。

sqlite3_bind_int(res, 1, 3);

sqlite3_bind_int 将一个整数值绑定到预处理语句。占位符被整数值 3 替换。该函数的第二个参数是要设置的 SQL 参数的索引,第三个参数是要绑定到该参数的值。

int step = sqlite3_step(res);

sqlite3_step 函数评估 SQL 语句。

if (step == SQLITE_ROW) {
    
    printf("%s: ", sqlite3_column_text(res, 0));
    printf("%s\n", sqlite3_column_text(res, 1));
    
} 

如果有可用的数据行,我们使用 sqlite3_column_text 函数获取两列的值。

$ ./parameterized 
3: Skoda

该示例返回 ID 和汽车的名称。

第二个示例使用带有命名占位符的参数化语句。

named_placeholders.c
#include <sqlite3.h>
#include <stdio.h>

int main(void) {
    
    sqlite3 *db;
    char *err_msg = 0;
    sqlite3_stmt *res;
    
    int rc = sqlite3_open("test.db", &db);
    
    if (rc != SQLITE_OK) {
        
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        
        return 1;
    }
    
    char *sql = "SELECT Id, Name FROM Cars WHERE Id = @id";
        
    rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
    
    if (rc == SQLITE_OK) {
        
        int idx = sqlite3_bind_parameter_index(res, "@id");
        int value = 4;
        sqlite3_bind_int(res, idx, value);
        
    } else {
        
        fprintf(stderr, "Failed to execute statement: %s\n", sqlite3_errmsg(db));
    }
    
    int step = sqlite3_step(res);
    
    if (step == SQLITE_ROW) {
        
        printf("%s: ", sqlite3_column_text(res, 0));
        printf("%s\n", sqlite3_column_text(res, 1));
        
    } 

    sqlite3_finalize(res);
    sqlite3_close(db);
    
    return 0;
}

我们使用命名占位符选择一辆汽车的名称和价格。

char *sql = "SELECT Id, Name FROM Cars WHERE Id = @id";

命名占位符以冒号 (:) 或 @ 符号 (@) 作为前缀。

int idx = sqlite3_bind_parameter_index(res, "@id");

sqlite3_bind_parameter_index 函数根据 SQL 参数的名称返回其索引。

SQLite C 插入图像

在本节中,我们将向 SQLite 数据库中插入一张图像。请注意,一些人反对将图像放入数据库。这里我们只展示如何做到这一点。我们不深入探讨是否应在数据库中保存图像的技术问题。

sqlite> CREATE TABLE Images(Id INTEGER PRIMARY KEY, Data BLOB);

对于这个例子,我们创建一个名为 Images 的新表。对于图像,我们使用 BLOB 数据类型,它代表二进制大对象(Binary Large Objects)。

insert_image.c
#include <sqlite3.h>
#include <stdio.h>


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);
    
    if (ferror(fp)) {
        
        fprintf(stderr, "fseek() failed\n");
        int r = fclose(fp);

        if (r == EOF) {
            fprintf(stderr, "Cannot close file handler\n");          
        }    
        
        return 1;
    }  
    
    int flen = ftell(fp);
    
    if (flen == -1) {
        
        perror("error occurred");
        int r = fclose(fp);

        if (r == EOF) {
            fprintf(stderr, "Cannot close file handler\n");
        }
        
        return 1;     
    }
    
    fseek(fp, 0, SEEK_SET);
    
    if (ferror(fp)) {
        
        fprintf(stderr, "fseek() failed\n");
        int r = fclose(fp);

        if (r == EOF) {
            fprintf(stderr, "Cannot close file handler\n");
        }    
        
        return 1;
    }

    char data[flen+1];

    int size = fread(data, 1, flen, fp);
    
    if (ferror(fp)) {
        
        fprintf(stderr, "fread() failed\n");
        int r = fclose(fp);

        if (r == EOF) {
            fprintf(stderr, "Cannot close file handler\n");
        }
        
        return 1;     
    }
    
    int r = fclose(fp);

    if (r == EOF) {
        fprintf(stderr, "Cannot close file handler\n");
    }    
 
  
    sqlite3 *db;
    char *err_msg = 0;
    
    int rc = sqlite3_open("test.db", &db);
    
    if (rc != SQLITE_OK) {
        
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        
        return 1;
    }
    
    
    sqlite3_stmt *pStmt;

    char *sql = "INSERT INTO Images(Data) VALUES(?)";
    
    rc = sqlite3_prepare(db, sql, -1, &pStmt, 0);
    
    if (rc != SQLITE_OK) {
        
        fprintf(stderr, "Cannot prepare statement: %s\n", sqlite3_errmsg(db));
        
        return 1;
    }    
    
    sqlite3_bind_blob(pStmt, 1, data, size, SQLITE_STATIC);    
    
    rc = sqlite3_step(pStmt);
    
    if (rc != SQLITE_DONE) {
        
        printf("execution failed: %s", sqlite3_errmsg(db));
    }
        
    sqlite3_finalize(pStmt);    

    sqlite3_close(db);

    return 0;
}

在这个程序中,我们从当前工作目录读取一张图像,并将其写入 SQLite test.db 数据库的 Images 表中。

FILE *fp = fopen("woman.jpg", "rb");

if (fp == NULL) {
    
    fprintf(stderr, "Cannot open image file\n");    
    
    return 1;
}

我们从文件系统中读取二进制数据。我们有一张名为 woman.jpg 的 JPG 图像。fopen 函数打开指定的文件用于读取。如果操作成功,它返回一个指向 FILE 对象的指针,否则返回 NULL。

fseek(fp, 0, SEEK_END);

if (ferror(fp)) {
    
    fprintf(stderr, "fseek() failed\n");
    int r = fclose(fp);

    if (r == EOF) {
        fprintf(stderr, "Cannot close file handler\n");          
    }    
    
    return 1;
}  

我们使用 fseek 函数将文件指针移动到文件末尾。我们需要确定图像的大小。如果发生错误,错误指示器会被设置。我们使用 fseek 函数检查该指示器。如果发生错误,已打开的文件句柄将被关闭。

int flen = ftell(fp);

if (flen == -1) {
    
    perror("error occurred");
    int r = fclose(fp);

    if (r == EOF) {
        fprintf(stderr, "Cannot close file handler\n");
    }
    
    return 1;     
}

对于二进制流,ftell 函数返回从文件开头开始的字节数,即图像文件的大小。如果发生错误,该函数返回 -1 并设置 errnoperror 函数将 errno 的值解释为错误消息,并将其打印到标准错误输出流。

char data[flen+1];

这个数组将存储图像数据。

int size = fread(data, 1, flen, fp);

fread 函数从文件指针读取数据并将其存储在数据数组中。该函数返回成功读取的元素数量。

int r = fclose(fp);

if (r == EOF) {
    fprintf(stderr, "Cannot close file handler\n");
}    

数据读取完毕后,我们可以关闭文件句柄。

char *sql = "INSERT INTO Images(Data) VALUES(?)";

这个 SQL 语句用于将图像插入数据库。

rc = sqlite3_prepare(db, sql, -1, &pStmt, 0);

SQL 语句被编译。

sqlite3_bind_blob(pStmt, 1, data, size, SQLITE_STATIC);

sqlite3_bind_blob 函数将二进制数据绑定到已编译的语句。SQLITE_STATIC 参数意味着指向内容信息的指针是静态的,不需要释放。

rc = sqlite3_step(pStmt);

语句被执行,图像被写入表中。

SQLite C 读取图像

在本节中,我们将执行相反的操作。我们将从数据库表中读取一张图像。

read_image.c
#include <sqlite3.h>
#include <stdio.h>


int main(void) {
    
    FILE *fp = fopen("woman2.jpg", "wb");
    
    if (fp == NULL) {
        
        fprintf(stderr, "Cannot open image file\n");    
        
        return 1;
    }    
    
    sqlite3 *db;
    char *err_msg = 0;
    
    int rc = sqlite3_open("test.db", &db);
    
    if (rc != 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";
        
    sqlite3_stmt *pStmt;
    rc = sqlite3_prepare_v2(db, sql, -1, &pStmt, 0);
    
    if (rc != SQLITE_OK ) {
        
        fprintf(stderr, "Failed to prepare statement\n");
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        
        sqlite3_close(db);
        
        return 1;
    } 
    
    rc = sqlite3_step(pStmt);
    
    int bytes = 0;
    
    if (rc == SQLITE_ROW) {

        bytes = sqlite3_column_bytes(pStmt, 0);
    }
        
    fwrite(sqlite3_column_blob(pStmt, 0), bytes, 1, fp);

    if (ferror(fp)) {            
        
        fprintf(stderr, "fwrite() failed\n");

        return 1;      
    }  
    
    int r = fclose(fp);

    if (r == EOF) {
        fprintf(stderr, "Cannot close file handler\n");
    }       
    
    rc = sqlite3_finalize(pStmt);   

    sqlite3_close(db);
    
    return 0;
}    

我们从 Images 表中读取图像数据,并将其写入另一个文件,我们称之为 woman2.jpg

FILE *fp = fopen("woman2.jpg", "wb");

if (fp == NULL) {
    
    fprintf(stderr, "Cannot open image file\n");    
    
    return 1;
}    

我们以写入模式打开一个二进制文件。来自数据库的数据将被写入该文件。

char *sql = "SELECT Data FROM Images WHERE Id = 1";

此 SQL 语句从 Images 表中选择数据。我们从第一行获取二进制数据。

if (rc == SQLITE_ROW) {

    bytes = sqlite3_column_bytes(pStmt, 0);
}

sqlite3_column_bytes 函数返回 BLOB 中的字节数。

fwrite(sqlite3_column_blob(pStmt, 0), bytes, 1, fp);

二进制数据通过 fwrite 函数被写入文件。指向所选二进制数据的指针由 sqlite3_column_blob 函数返回。

if (ferror(fp)) {            
    
    fprintf(stderr, "fwrite() failed\n");

    return 1;      
}  

ferror 函数检查与流关联的错误指示器是否被设置。

SQLite C 元数据

元数据是关于数据库中数据的信息。SQLite 中的元数据包含有关我们存储数据的表和列的信息。受 SQL 语句影响的行数是元数据。结果集中返回的行数和列数也属于元数据。

SQLite 中的元数据可以使用 PRAGMA 命令获取。SQLite 对象可能具有属性,这些属性就是元数据。最后,我们还可以通过查询 SQLite 系统表 sqlite_master 来获取特定的元数据。

column_names.c
#include <sqlite3.h>
#include <stdio.h>

int callback(void *, int, char **, char **);

int main(void) {
    
    sqlite3 *db;
    char *err_msg = 0;
    
    int rc = sqlite3_open("test.db", &db);
    
    if (rc != SQLITE_OK) {
        
        fprintf(stderr, "Cannot open database: %s\n", 
                sqlite3_errmsg(db));
        sqlite3_close(db);
        
        return 1;
    }
    
    char *sql = "PRAGMA table_info(Cars)";
        
    rc = sqlite3_exec(db, sql, callback, 0, &err_msg);
    
    if (rc != 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;
}

在这个例子中,我们发出 PRAGMA table_info(tableName) 命令,以获取关于我们的 Cars 表的一些元数据信息。

char *sql = "PRAGMA table_info(Cars)";

PRAGMA table_info(tableName) 命令为 Cars 表中的每一列返回一行。结果集中的列包括列的顺序号、列名、数据类型、该列是否可以为 NULL,以及该列的默认值。

$ ./column_names 
cid = 0
name = Id
type = INT
notnull = 0
dflt_value = NULL
pk = 0
...

这是示例的输出。

在下一个与元数据相关的示例中,我们将列出 test.db 数据库中的所有表。

list_tables.c
#include <sqlite3.h>
#include <stdio.h>


int callback(void *, int, char **, char **);


int main(void) {
    
    sqlite3 *db;
    char *err_msg = 0;
    
    int rc = sqlite3_open("test.db", &db);
    
    if (rc != 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'";
        
    rc = sqlite3_exec(db, sql, callback, 0, &err_msg);
    
    if (rc != 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\n", argv[i] ? argv[i] : "NULL");
    }
    
    return 0;
}

该代码示例将当前数据库中所有可用的表打印到终端。

char *sql = "SELECT name FROM sqlite_master WHERE type='table'";

表名存储在系统表 sqlite_master 中。

$ ./list_tables 
Cars
Images

这是一个示例输出。

SQLite C 事务

事务是针对一个或多个数据库中的数据执行的数据库操作的原子单位。事务中所有 SQL 语句的效果要么全部提交到数据库,要么全部回滚。

在 SQLite 中,除了 SELECT 之外的任何命令都会启动一个隐式事务。此外,在一个事务中,像 CREATE TABLE ...、VACUUMPRAGMA 这样的命令会在执行前提交之前的更改。

手动事务以 BEGIN TRANSACTION 语句开始,并以 COMMITROLLBACK 语句结束。

SQLite 支持三种非标准的事务级别:DEFERREDIMMEDIATEEXCLUSIVE

自动提交

默认情况下,SQLite 版本 3 在*自动提交模式*下运行。在自动提交模式下,所有对数据库的更改都会在与当前数据库连接相关的所有操作完成后立即提交。自动提交模式被 BEGIN 语句禁用,并被 COMMITROLLBACK 重新启用。

get_ac_mode.c
#include <sqlite3.h>
#include <stdio.h>

int main() {
    
    sqlite3 *db;
    
    int rc = sqlite3_open("test.db", &db);
    
    if (rc != 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 语句都是一个立即提交的小型事务。

autocommit.c
#include <sqlite3.h>
#include <stdio.h>

int main(void) {
    
    sqlite3 *db;
    char *err_msg = 0;
    
    int rc = sqlite3_open("test.db", &db);
    
    if (rc != 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');";

    rc = sqlite3_exec(db, sql, 0, 0, &err_msg);
    
    if (rc != 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 表并尝试用数据填充它。

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');";

最后一个 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 语句放入一个事务中。

transaction.c
#include <sqlite3.h>
#include <stdio.h>

int main(void) {
    
    sqlite3 *db;
    char *err_msg = 0;
    
    int rc = sqlite3_open("test.db", &db);
    
    if (rc != 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;";
                           

    rc = sqlite3_exec(db, sql, 0, 0, &err_msg);
    
    if (rc != 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 表。

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;";

第一条语句如果存在 Friends 表则删除它。其他语句被放置在一个事务中。事务以全有或全无的模式工作。要么全部提交,要么全部不提交。

sqlite> .tables
Cars    Images

由于最后一条语句有错误,事务被回滚,Friends 表没有被创建。

这是 SQLite C 教程。ZetCode 有一本完整的 SQLite Python *电子书*。
SQLite Python 电子书.