sqlite是目前最流行的嵌入式數據庫。
所謂嵌入式,就是足夠簡單,可以嵌入到我們自己開發的應用程序之中。
在Linux系統中,sqlite的使用只需要使用它的API,連接它的動態連接庫,甚至都不用連接,sqlite的實現只使用一個C語言源程序,直接編譯進自己的應用里面就好。
現在sqlite的最新版本是3,所以我們后面會以這個版本為例。
API
sqlite的API簡單到什么程序呢?
當我們開發應用的時候,通常的情況下,只需要使用sqlite3_open、sqlite3_exec與sqlite3_close這三個函數就夠了。
它們的原型為:
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函數把每一條記錄返回。另外還有一個錯誤字符串,如果出錯將被賦值,需要應用層釋放。
如:
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語句里面,如果有值字符串中含有’,需要進行轉義。
轉義的格式是使用兩個’,即’'。
如:
const char *lan = "It's a secret !";
這里的lan需要轉義為:
const char *lan = "It''s a secret !";
可以寫一個簡單的轉化函數實現這個功能:
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語句結構可以復用,每次重置,再賦值,可以極大地提升性能。
預編譯執行的方法也很簡單,就是三步:
- 使用
sqlite3_prepare
生成一個sqlite3_stmt
結構。 - 使用
sqlite3_bind
綁定值。 - 使用
sqlite3_step
執行。
sqlite3_prepare_v2
sqlite3_prepare_v2
是建議的新版本,其實還有一個為了兼容性的sqlite3_prepare
以及更新的sqlite3_prepare_v3
。基于“性價比”考量,我們使用sqlite3_prepare_v2
就行了。
sqlite3_prepare_v2
的原型為:
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_int
、sqlite3_bind_double
、sqlite3_bind_text
等。
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開始。
比如,我們使用了
sqlite3_bind_int (stmt, 1, 1024);
就是把生成stmt的sql中的第1個?賦值了,不要使用0。
sqlite3_step
賦值完成后,就使用sqlite3_step
執行。
這個函數原型特別簡單,就是:
SQLITE_API int sqlite3_step(sqlite3_stmt*);
但是這個函數的返回值很豐富。不同的返回值,需要做不同的處理。
比如,在不出錯的情況下,如果返回SQLITE_DONE
,表示執行成功。
如果返回SQLITE_ROW,表示返回了一行數據,我們可以使用sqlite_column_*
族的函數取得列的值。之后需要再次調用sqlite3_step
,直到最后返回了SQLITE_DONE
。
sqlite3_column_*
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
可以取得當前行的列數。
示例
最后來一個示例,總結一下:
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
就可以打開前面創建的數據庫。
在這個命令終端里,以.開頭的是內置的命令。
如:
> .help 顯示幫助
> .tables 顯示數據表
> .schema TableName 顯示創建數據表的語句,類似mysql里的describe
> .open 文件名 關閉當前,打開另一個數據庫
> .quit 退出
> .save 文件名 另存當前數據庫到文件
> .headers on/off 在查詢結果前面,是否顯示列名
執行sql語句,就直接輸入,加;回車執行就行了。