sqlite3的API以及命令行

sqlite是目前最流行的嵌入式数据库。

所谓嵌入式,就是足够简单,可以嵌入到我们自己开发的应用程序之中。

在Linux系统中,sqlite的使用只需要使用它的API,连接它的动态连接库,甚至都不用连接,sqlite的实现只使用一个C语言源程序,直接编译进自己的应用里面就好。

现在sqlite的最新版本是3,所以我们后面会以这个版本为例。

API

sqlite的API简单到什么程序呢?

当我们开发应用的时候,通常的情况下,只需要使用sqlite3_open、sqlite3_exec与sqlite3_close这三个函数就够了。

它们的原型为:

C 复制代码
int sqlite3_open(const char *filename, sqlite3 **ppDb);

int sqlite3_close(sqlite3*);

int sqlite3_exec(  
 sqlite3*,                                  /* An open database */  
 const char *sql,                           /* SQL to be evaluated */  
 int (*callback)(void*,int,char**,char**),  /* Callback function */  
 void *,                                    /* 1st argument to callback */  
 char **errmsg                              /* Error msg written here */  
);

这三个函数都使用了一个sqlite3的指针的指针,指向了我们要操作的数据库实体的指针,不用解释。

sqlite3_open的第一个参数,表示要创建的数据库的路径。

值得一提的是sqlite3_exec。它有一个回调函数以及参数,当有多个返回值的时候,比如select语句被执行之后,将通过callback函数把每一条记录返回。另外还有一个错误字符串,如果出错将被赋值,需要应用层释放

如:

C 复制代码
static int  
get_id_callback (void *para, int n_column, char **column_value,  
                   char **column_name)  
{  
  int *id = (int *)para;  
  if (column_value[0] != NULL)  
    {  
      *id = strtoull (column_value[0], NULL, 10);  
    }  
  return 0;  
}

int main (char *argc, char *argv[])  
{  
  gchar *dirname;  
  sqlite3 *db;
  char *errMsg;
  int rc;
  
  if (sqlite3_open ("/tmp/test.sqlite", &db) != 0)  
    {  
      printf ("Open sqlite db failed: %s.", file, strerror (errno));  
      return 1;  
    }  
  
    rc = sqlite3_exec (db, "select * from media", get_id_callback, NULL, &errMsg);  
  if (rc != SQLITE_OK)  
    {  
      printf ("SQL error: %s in [%s]", errMsg, text);  
      sqlite3_free (errMsg);  
      return 2;
    }
  
  sqlite3_close (db);  
  
  return 0;  
}  

转义

sqlite3_exec执行的sql语句里面,如果有值字符串中含有',需要进行转义。

转义的格式是使用两个',即''。

如:

C 复制代码
const char *lan = "It's a secret !";

这里的lan需要转义为:

C 复制代码
const char *lan = "It''s a secret !";

可以写一个简单的转化函数实现这个功能:

C 复制代码
int convert_str(const char *str, char *output, size_t out_size)
{
    *p;  
    int trans_len;
  
    for (trans_len = 0, p = str; *p && trans_len + 2 < out_size; trans_len++, p++)  
    {  
        output[trans_len] = *p;  
        if (*p == '\'')  
          {  
            output[trans_len + 1] = *p;  
            trans_len++;  
          }  
      }
        
    if (trans_len + 1 >= out_size) {
        printf("str is too long\n");
        return -1;
    }

    output[trans_len] = '\0';
    return trans_len;
}

除了这样转义,还有没有更好的方法呢?

有的。

方法就是使用预编译执行。

预编译执行

所谓预编译执行,就是生成一个sql语句的结构,再赋值进去,之后执行。

这样做有多个好处。

首先是字符串不用转义了。

其次是,生成的sql语句结构可以复用,每次重置,再赋值,可以极大地提升性能。

预编译执行的方法也很简单,就是三步:

  1. 使用sqlite3_prepare生成一个sqlite3_stmt结构。
  2. 使用sqlite3_bind绑定值。
  3. 使用sqlite3_step执行。

sqlite3_prepare_v2

sqlite3_prepare_v2是建议的新版本,其实还有一个为了兼容性的sqlite3_prepare以及更新的sqlite3_prepare_v3。基于"性价比"考量,我们使用sqlite3_prepare_v2就行了。

sqlite3_prepare_v2的原型为:

C 复制代码
SQLITE_API 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 */  
);

其中,db是我们打开的数据库实例,zSql是我们要执行的sql语句,语句中的变量使用?进行占位,而ppStmt是我们生成的sqlite3_stmt结构的指针的指针。

sqlite3_bind_*

为了绑定不同的值,有几个不同的函数可以使用,如:sqlite3_bind_intsqlite3_bind_doublesqlite3_bind_text等。

C 复制代码
SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));  
SQLITE_API int sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64,  
                       void(*)(void*));  
SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double);  
SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int);  
SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);  
SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int);  
SQLITE_API int sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*));
// 省略其它

这些函数都是第一个是前面生成的sqlite3_stmt的指针,第二个是绑定的值的位置,第三个以及后面是值信息。

值得注意的是,这些函数的位置参数,即索引,从1开始。

比如,我们使用了

C 复制代码
sqlite3_bind_int (stmt, 1, 1024);

就是把生成stmt的sql中的第1个?赋值了,不要使用0。

sqlite3_step

赋值完成后,就使用sqlite3_step执行。

这个函数原型特别简单,就是:

C 复制代码
SQLITE_API int sqlite3_step(sqlite3_stmt*);

但是这个函数的返回值很丰富。不同的返回值,需要做不同的处理。

比如,在不出错的情况下,如果返回SQLITE_DONE,表示执行成功。

如果返回SQLITE_ROW,表示返回了一行数据,我们可以使用sqlite_column_*族的函数取得列的值。之后需要再次调用sqlite3_step,直到最后返回了SQLITE_DONE

sqlite3_column_*

C 复制代码
SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);  
SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol);  
SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol);  
SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);  
SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
// 省略其它

可以看到,这些函数基本上跟sqlite3_bind_*是对应的。

唯一需要注意的是,这些函数的索引是从0开始的。

另外,sqlite3_data_count可以取得当前行的列数。

示例

最后来一个示例,总结一下:

C 复制代码
int insert_medias(sqlite3_database *db, struct media *medias, size_t count)
{
    int ret;
    sqlite3_stmt *stmt;
    const char *sql = "INSERT INTO media(path, size) VALUES(?,?);";
    if (sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, NULL) != SQLITE_OK) 
    {
        printf ("sqlite3_prepare_v2() error: %s\n", sqlite3_errmsg (db));
        return -1;
    }
 
    for(int i = 0; i < count; i++)
    {
        sqlite3_bind_text(stmt, 1, medias[i].name, strlen (medias[i].name), NULL);
        sqlite3_bind_int(pstmt, 2, medias[i].size);
 
        if (sqlite3_step(pstmt) != SQLITE_DONE) 
        {
            printf ("sqlite3_step() error: %s\n", sqlite3_errmsg (db));
            return i;
        }
        
        sqlite3_reset(pstmt);
    }
 
    sqlite3_finalize(pstmt);
    return count;
}

void query_media (sqlite3_database *db) 
{
    int ret;
    sqlite3_stmt *stmt;
    const char *sql = "SELECT * FROM media;";
    if (sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, NULL) != SQLITE_OK) 
    {
        printf ("sqlite3_prepare_v2() error: %s\n", sqlite3_errmsg (db));
        return -1;
    }

    ret = sqlite3_step (stmt);
    while (ret == SQLITE_ROW) {
        printf("get media: %s, size: %d\n", sqlite3_coumn_text (stmt, 0), sqlite3_column_int (stmt, 1));
        ret = sqlite3_step (stmt);
    }
    
    if (ret != SQLITE_DONE) {
         printf ("sqlite3_step() error: %s\n", sqlite3_errmsg (db)); 
    }
}

命令行

sqlite3有一个命令行工具,就叫sqlite3。

我们使用sqlite3 /tmp/test.sqlite3就可以打开前面创建的数据库。

在这个命令终端里,以.开头的是内置的命令。

如:

sqlite3 复制代码
> .help 显示帮助
> .tables 显示数据表
> .schema TableName 显示创建数据表的语句,类似mysql里的describe
> .open 文件名 关闭当前,打开另一个数据库
> .quit 退出
> .save 文件名 另存当前数据库到文件
> .headers on/off 在查询结果前面,是否显示列名

执行sql语句,就直接输入,加;回车执行就行了。

相关推荐
"_rainbow_"2 小时前
C++常用函数合集
开发语言·c++·算法
Wendy_robot3 小时前
力扣经典位运算
c++·算法·leetcode
roboko_4 小时前
多路转接poll服务器
linux·网络·c++
蒲公英的孩子4 小时前
Linux下 REEF3D及DIVEMesh 源码编译安装及使用
linux·c++·分布式·开源软件
半青年4 小时前
数据结构之哈希表的原理和应用:从理论到实践的全面解析
java·c语言·数据结构·c++·python·哈希算法
Chenyu_3105 小时前
09.传输层协议 ——— TCP协议
linux·服务器·网络·c++·网络协议·tcp/ip
1白天的黑夜15 小时前
双指针-11.盛水最多的容器-力扣(LeetCode)
c++·算法·leetcode
YuforiaCode6 小时前
第十五届蓝桥杯 2024 C/C++组 下一次相遇
c语言·c++·蓝桥杯
不是仙人的闲人7 小时前
算法之回溯法
开发语言·数据结构·c++·算法