• 常见软件---SQLite3的C语言下使用


    使用背景

    最近在研究Wazuh的时候,发现服务端默认也使用了sqlite,原以为只有嵌入式使用,其实在这种本地化的软件中,使用sqlite也是一种很好的选择,至少软件的依赖性就少了很多,sqlite可以直接编译到产品中,不依赖mysql这种软件。
    在这里插入图片描述

    源码下载

    访问《这里》可以下载sqlite3的源码。
    源码就是4个文件
    在这里插入图片描述
    其中shell.c在不使用命令行的时候,无需编译进自己的产品。
    在这里插入图片描述

    构建环境

    今天学了一个新的工程搭建方式,在开源软件上经常用到的CMake方式。

    [root@localhost sqlite3]# tree
    .
    ├── build
    ├── CMakeLists.txt
    ├── sqlite
    │   ├── shell.c
    │   ├── sqlite3.c
    │   ├── sqlite3ext.h
    │   └── sqlite3.h
    └── src
        └── main.c
    
    3 directories, 6 files
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    CMakeLists.txt内容如下

    cmake_minimum_required (VERSION 3.5)
    project(demo)
    include_directories (sqlite)
    
    add_executable(main ${PROJECT_SOURCE_DIR}/src/main.c ${PROJECT_SOURCE_DIR}/sqlite/sqlite3.c)
    target_link_libraries (main pthread dl)
    
    add_executable(sqliteShell ${PROJECT_SOURCE_DIR}/sqlite/shell.c ${PROJECT_SOURCE_DIR}/sqlite/sqlite3.c)
    target_link_libraries (sqliteShell pthread dl)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这个还挺有意思的,直接就能够根据后面几行,自动生成Makefile,并且编译出main和sqliteShell两个可执行程序。
    看来以后要深入学习一下。
    在这里插入图片描述
    编译方式与运行

    cd build
    cmake .. && make
    ./main
    
    • 1
    • 2
    • 3

    main函数就简单的获取一个版本就可以进行测试了。

    #include 
    #include "sqlite3.h"
    
    int main(void)
    {
        printf("%s\n", sqlite3_libversion());
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    [root@localhost sqlite3]# cd build/
    [root@localhost build]# cmake ..
    -- The C compiler identification is GNU 8.5.0
    -- The CXX compiler identification is GNU 8.5.0
    -- Detecting C compiler ABI info
    -- Detecting C compiler ABI info - done
    -- Check for working C compiler: /usr/bin/cc - skipped
    -- Detecting C compile features
    -- Detecting C compile features - done
    -- Detecting CXX compiler ABI info
    -- Detecting CXX compiler ABI info - done
    -- Check for working CXX compiler: /usr/bin/c++ - skipped
    -- Detecting CXX compile features
    -- Detecting CXX compile features - done
    -- Configuring done
    -- Generating done
    -- Build files have been written to: /home/sqlite3/build
    [root@localhost build]# make
    [ 16%] Building C object CMakeFiles/main.dir/src/main.c.o
    [ 33%] Building C object CMakeFiles/main.dir/sqlite/sqlite3.c.o
    [ 50%] Linking C executable main
    [ 50%] Built target main
    [ 66%] Building C object CMakeFiles/sqliteShell.dir/sqlite/shell.c.o
    [ 83%] Building C object CMakeFiles/sqliteShell.dir/sqlite/sqlite3.c.o
    [100%] Linking C executable sqliteShell
    [100%] Built target sqliteShell
    [root@localhost build]# ./main 
    3.39.3
    
    • 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

    以上内容参考《C语言操作SQLite3简明教程》
    在这里插入图片描述

    C语言方式

    这里主要学习一下sqlite3的一些API函数
    可以参考文章《sqlite3接口API函数备注(2)》,下面介绍一些有意思的调用。
    在这里插入图片描述

    数据库打开

    值得注意的是数据库的打开时候的参数,可以根据情况,例如多线程,进行选择。

    SQLITE_API int sqlite3_open_v2(
      const char *filename,   /* Database filename (UTF-8) */
      sqlite3 **ppDb,         /* OUT: SQLite db handle */
      int flags,              /* Flags */
      const char *zVfs        /* Name of VFS module to use */
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    第一个参数为数据库文件名字,值得注意的点是:

    • 如果文件名为“:memory:”,则会为连接创建一个专用的临时内存数据库。数据库连接关闭时,内存中的数据库将消失。SQLite的未来版本可能会使用以“:”字符开头的其他特殊文件名。建议当数据库文件名实际上以“:”字符开头时,应在文件名前面加上路径名,如“/”以避免歧义。
    • 如果文件名为空字符串,则将创建一个专用的临时磁盘数据库。一旦数据库连接关闭,此专用数据库将自动删除。

    第三个参数flags,含义如下

    参数含义是否必选
    SQLITE_OPEN_READONLY只读模式,如果数据库不存在,会返回错误Y
    SQLITE_OPEN_READWRITE读写模式,如果数据库不存在,会返回错误Y
    SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE读写方式打开。如果数据库不存在,会自动创建,sqlite3_open() and sqlite3_open16()函数就是这种模式Y
    SQLITE_OPEN_URI如果设置了此标志,则文件名可以解释为URIN
    SQLITE_OPEN_MEMORY数据库将作为内存数据库打开。如果启用了共享缓存模式,则出于缓存共享的目的,数据库由“filename”参数命名,但在其他情况下忽略“filename”N
    SQLITE_OPEN_NOMUTEX新数据库连接将使用“多线程”[线程模式])这意味着允许单独的线程同时使用SQLite,只要每个线程使用不同的[数据库连接]N
    SQLITE_OPEN_FULLMUTEX新数据库连接将使用“序列化”[线程模式])这意味着多个线程可以安全地尝试同时使用相同的数据库连接。(互斥锁将阻止任何实际的并发,但在这种模式下,尝试不会有任何危害。)N
    SQLITE_OPEN_SHAREDCACHE数据库启用共享缓存,覆盖[sqlite3_enable_shared_cache()]提供的默认共享缓存设置。)N
    SQLITE_OPEN_PRIVATECACHE数据库禁用共享缓存,覆盖[sqlite3_enable_shared_cache()]提供的默认共享缓存设置。)N
    SQLITE_OPEN_EXRESCODE数据库连接在“扩展结果代码模式”下出现。换句话说,数据库行为具有如果[sqlite3_extended_result_codes(db,1)],其中在连接创建后立即调用数据库连接。除了设置扩展结果代码模式外,该标志还导致[sqlite3_open_v2()]返回扩展结果代码。N
    SQLITE_OPEN_NOFOLLOW数据库名称不能是符号链接N

    部分内容翻译的很直白,见谅见谅
    在这里插入图片描述

    预编译下的增删改操作

    这里主要讲一下数据库数据操作的预编译。

    以前用数据库,就是老老实实每次都调用文本的sql语句,实际上每次都会进行语句的编译再执行,如果进行三次操作,就需要进行三次编译,那么我们可以将重复执行或者部分重复执行的sql语句,提前编译好,然后调用的时候,直接调用,就省去了一部分时间,提升了效率。
    在这里插入图片描述

    请看函数

    // 准备语句 
    int sqlite3_prepare_v2(
      sqlite3 *db,            /* Database handle */
      const char *zSql,       /* SQL statement, UTF-8 encoded */
      int nByte,              /* Maximum length of zSql in bytes. */
      sqlite3_stmt **ppStmt,  /* OUT: Statement handle */
      const char **pzTail     /* OUT: Pointer to unused portion of zSql */
    );
    // 执行 
    int sqlite3_step(sqlite3_stmt*);
    // 完成
    int sqlite3_finalize(sqlite3_stmt *pStmt);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    就是将zSql语句,编译成ppStmt,然后执行的时候就可以直接运行这个编译过的命令。
    如果带有参数的话,需要通过下面的函数,进行参数设置

    // 部分绑定函数接口
    int sqlite3_bind_double(sqlite3_stmt*, int, double);
    int sqlite3_bind_int(sqlite3_stmt*, int, int);
    int sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*));
    int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
    // 语句重置
    int sqlite3_reset(sqlite3_stmt *pStmt);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    直接来一个创建数据库并且增删改查数据的例子吧。
    代码较长,请多指教。
    在这里插入图片描述

    #include 
    #include "sqlite3.h"
    
    
    int callback(void *NotUsed, int argc, char **argv, char **azColName)
    {
        NotUsed = NULL;
    
        for (int i = 0; i < argc; ++i)
        {
            printf("%s = %s\n", azColName[i], (argv[i] ? argv[i] : "NULL"));
        }
    
        printf("\n");
    
        return 0;
    }
    
    int exec_sql(sqlite3 *db,char*   sqltxt)
    {
        char *err_msg = NULL;
    	int rc;
        printf("sqltxt:%s\n",sqltxt);
    	
        rc = sqlite3_exec(db, sqltxt, callback, NULL, &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;
        }
    	return 0;
    }
    
    int main(void) 
    {
        sqlite3 *db = NULL;
        char *err_msg = NULL;
    	int rc = 0;
    	int i = 0;
    	char text[50] = {0};
        const char *sql_init = "DROP TABLE IF EXISTS Testtable;" 
                          "CREATE TABLE Testtable(Id INT, Data TEXT);";
    
    					  
    	const char *sql_insert = "insert into Testtable(Id,Data) values(?,?)";
    	const char *sql_update = "update Testtable set Data = ? where Id = ?;";
    	const char *sql_delete = "delete from Testtable where Id = ?;";
    	const char *sql_search = "select * from Testtable";
    	
    	sqlite3_stmt *stmt_insert;
    	sqlite3_stmt *stmt_update;
    	sqlite3_stmt *stmt_delete;
    	sqlite3_stmt *stmt_search;
    
    	//打开数据库,不存在就创建
    	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));
    
    	//创建数据库和表
        rc = sqlite3_exec(db, sql_init, NULL, NULL, &err_msg);
        if (rc != SQLITE_OK)
        {
            fprintf(stderr, "SQL error: %s\n", err_msg);
            sqlite3_free(err_msg);
            sqlite3_close(db);
    
            return 1;
        }
    
    	//查看数据
        printf("新建表\n");
    	exec_sql(db,sql_search);
    
    	// 插入三条数据
    	rc = sqlite3_prepare_v2(db, sql_insert, -1, &stmt_insert, NULL);
    	for (i = 0; i < 3; i++)
    	{
    		
    		sqlite3_bind_int(stmt_insert, 1, i);
    		sprintf(text, "test%d", i);
    		sqlite3_bind_text(stmt_insert, 2, text, -1, NULL);
    		rc = sqlite3_step(stmt_insert);
    		if (rc != SQLITE_DONE)
    	    {
    	        printf("execution failed: %s\n", sqlite3_errmsg(db));
    	    }
    		sqlite3_reset(stmt_insert);
    	}
    	sqlite3_finalize(stmt_insert);
    
    	//查看数据
        printf("插入三条数据\n");
    	exec_sql(db,sql_search);
    	
    	//修改第二条
    	rc = sqlite3_prepare_v2(db, sql_update, -1, &stmt_update, NULL);
    	sqlite3_bind_int(stmt_update, 2, 1);
    	strcpy(text, "change");
    	sqlite3_bind_text(stmt_update, 1, text, -1, NULL);
    	rc = sqlite3_step(stmt_update);
    	if (rc != SQLITE_DONE)
        {
            printf("execution failed: %s\n", sqlite3_errmsg(db));
        }
    	sqlite3_reset(stmt_update);
    	//查看数据
        printf("修改第二条数据\n");
    	exec_sql(db,sql_search);
    
    	//删除第三条数据
    	rc = sqlite3_prepare_v2(db, sql_delete, -1, &stmt_delete, NULL);
    	sqlite3_bind_int(stmt_delete, 1, 2);
    	rc = sqlite3_step(stmt_delete);
    	if (rc != SQLITE_DONE)
        {
            printf("execution failed: %s\n", sqlite3_errmsg(db));
        }	sqlite3_reset(stmt_delete);
    	
        printf("删除第三条数据\n");
    	exec_sql(db,sql_search);
    
        sqlite3_close(db);
    
        return 0;
    }
    
    
    • 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

    Blob数据类型存储

    Bolb是二进制长对象的意思,通常用于存储大文件,通过二进制数据保存到数据库里,并可以从数据库里恢复指定文件。
    常见的就是存储一些图片数据。
    在这里插入图片描述

    这里用存储并读取几个个结构体数据为例,内部涉及了读取多条数据结果的操作。

    #include 
    #include 
    #include "sqlite3.h"
    
    
    typedef struct 
    {
        int64_t time;
        int32_t value;
    } myData;
    
    
    int main(void)
    {
        sqlite3 *db = NULL;
        char *err_msg = NULL;
        sqlite3_stmt *pStmt = NULL;
    	int rc = 0;
    	int i = 0;
        int bytes = 0;
    	myData *pData = NULL;
        // 创建名为Images的表
        const char *sql_init = "DROP TABLE IF EXISTS Testtable;" 
                          "CREATE TABLE Testtable(Id INTEGER PRIMARY KEY, Data BLOB);";
        const char *sql_insert = "INSERT INTO Testtable(Data) VALUES(?)"; // 向Data列插入新值
        const char *sql_get = "SELECT Data FROM Testtable;";
    
        myData data = {10000, 200}; // 准备要写入的值
        
    	rc = sqlite3_open("test.db", &db);
        if (rc != SQLITE_OK) 
    	{
            
            fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
            return 1;
        }
        
    
        rc = sqlite3_exec(db, sql_init, NULL, NULL, &err_msg);
        if (rc != SQLITE_OK ) {
            
            fprintf(stderr, "Failed to select data\n");
            fprintf(stderr, "SQL error: %s\n", err_msg);
    		goto exit;
    	}
    
        rc = sqlite3_prepare_v2(db, sql_insert, -1, &pStmt, NULL);
        if (rc != SQLITE_OK) 
    	{
            fprintf(stderr, "Cannot prepare statement: %s\n", sqlite3_errmsg(db));
    		goto exit;
        }
    
        sqlite3_bind_blob(pStmt, 1, &data, sizeof(data), SQLITE_STATIC); // 绑定需要写入的值
    	for(i=0;i<5;i++)
    	{
    		data.value=i;
    	    rc = sqlite3_step(pStmt);
    	    if (rc != SQLITE_DONE)
    	    {
    	        printf("execution failed: %s", sqlite3_errmsg(db));
    			goto exit;
    	    }
    
    	}
        sqlite3_finalize(pStmt);    
    
        rc = sqlite3_prepare_v2(db, sql_get, -1, &pStmt, NULL);
         if (rc != SQLITE_OK ) {
            
            fprintf(stderr, "Failed to prepare statement\n");
            fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
    		goto exit;
        } 
        
    	rc = sqlite3_step(pStmt);
    	
        while (rc == SQLITE_ROW)
        {
            bytes = sqlite3_column_bytes(pStmt, 0);
    		pData = (myData*)sqlite3_column_blob(pStmt, 0);
    		printf("bytes: %d, %lld, %d\n", bytes, pData->time, pData->value);
    	    rc = sqlite3_step(pStmt);
    		if(rc == SQLITE_DONE)
    			break;
        }
    
        rc = sqlite3_finalize(pStmt);   
    
    exit:
    	if(err_msg)
    	{
    		sqlite3_free(err_msg);
    	}
    	if(db)
    	{
    		sqlite3_close(db);
    	}
        return 0;
    
    }
    
    
    • 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

    其他操作

    SQLite也和mysql一样,支持命令行的操作,详细方式可以参考。
    《一文掌握SQLite3基本用法》
    《SQLite 教程》
    在这里插入图片描述

    结束语

    好些天没写了,最近有点沉迷工作,荒废了学业。马上就周末了,更新一篇。
    在这里插入图片描述
    最近大家都在玩羊了个羊,大家都集中到了两个困惑上
    1.怎么跳过第一关
    2.怎么能过第二关
    其实我就怀疑这个游戏的内容,全是随机的,根本没有考虑你这套组合,能不能完成,除非你看广告刷够了道具。
    在这里插入图片描述
    不过这也算是个成功的游戏,一个成功的吸引住你的游戏。至于能不能过关,他并不关心。咱们也不必苛责人家,毕竟都是程序员,赚的也不是用户的钱。
    在这里插入图片描述

  • 相关阅读:
    Bigemap如何查看历史影像
    软件工程与计算总结(五)软件需求基础
    【数据库事务日志碎片原理分析与方案】-分析篇
    XXL-Job分布式任务调度框架-知识点汇总5
    程序人生:从小公司到一线大厂,薪资翻倍,我做到了...
    搜维尔科技:【第三期】第九届元宇宙数字人大赛,参赛小组报名确认公告
    看我简单教会你如何按关键字搜索淘宝商品
    Git分布式版本控制工具(一)
    【Java 基础篇】深入理解Java集合嵌套:构建和管理复杂数据结构的终极指南
    React Native(RN)环境搭建
  • 原文地址:https://blog.csdn.net/baidu_19348579/article/details/126847308