最近在研究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
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)
这个还挺有意思的,直接就能够根据后面几行,自动生成Makefile,并且编译出main和sqliteShell两个可执行程序。
看来以后要深入学习一下。
编译方式与运行
cd build
cmake .. && make
./main
main函数就简单的获取一个版本就可以进行测试了。
#include
#include "sqlite3.h"
int main(void)
{
printf("%s\n", sqlite3_libversion());
return 0;
}
[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
以上内容参考《C语言操作SQLite3简明教程》
这里主要学习一下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 */
);
第一个参数为数据库文件名字,值得注意的点是:
第三个参数flags,含义如下
参数 | 含义 | 是否必选 |
---|---|---|
SQLITE_OPEN_READONLY | 只读模式,如果数据库不存在,会返回错误 | Y |
SQLITE_OPEN_READWRITE | 读写模式,如果数据库不存在,会返回错误 | Y |
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | 读写方式打开。如果数据库不存在,会自动创建,sqlite3_open() and sqlite3_open16()函数就是这种模式 | Y |
SQLITE_OPEN_URI | 如果设置了此标志,则文件名可以解释为URI | N |
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);
就是将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);
直接来一个创建数据库并且增删改查数据的例子吧。
代码较长,请多指教。
#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;
}
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;
}
SQLite也和mysql一样,支持命令行的操作,详细方式可以参考。
《一文掌握SQLite3基本用法》
《SQLite 教程》
好些天没写了,最近有点沉迷工作,荒废了学业。马上就周末了,更新一篇。
最近大家都在玩羊了个羊,大家都集中到了两个困惑上
1.怎么跳过第一关
2.怎么能过第二关
其实我就怀疑这个游戏的内容,全是随机的,根本没有考虑你这套组合,能不能完成,除非你看广告刷够了道具。
不过这也算是个成功的游戏,一个成功的吸引住你的游戏。至于能不能过关,他并不关心。咱们也不必苛责人家,毕竟都是程序员,赚的也不是用户的钱。