一个比较好的SQLite3 C++ wrapper包装类的通常思路是这样的:
数据库连接类,包含连接池,和sqlite3*,负责与数据库文件的连接问题;
一些create table,insert,update,delete等操作都在这里定义,直接使用sqlite3_exec()函数进行就可以,对它的回调函数可以定义为空。
如果对数据库执行查询操作,则需要一个单独的操作类, CQuery,它调用更具体的接口,先获取结果集,再逐行解析字段名和值,最后删除结果集
这里的重点是,首次将列字段名保存下来,再每次获取一行记录,将字段值记录到一个向量中,使用
sqlite3_column_text()将每个字段统一存为一个字符串,在需要处理时再分别转换为具体的数据类型。
当进行下一行解析时,先要将原来的数据清除,在存放当前行的数据。注意存放的是const char*, 这和
const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
定义是相符合的。
上面是两种完全不同的处理方法。
目前我通过实践,摸透了两个例子
https://github.com/wrmsr/SQLiteDB
这个例子我比较欣赏,准备在它的基础上编写一个简单的SQLite C++ wrapper类。参见下面的代码:
//
//
#ifndef __SQLITE3_WRAPPER_H__
#define __SQLITE3_WRAPPER_H__
#include <map>
#include <string>
#include <sstream>
#include <vector>
#include <memory>
#include <stdlib.h>
#include <stdio.h>
#include <sqlite3.h>
class CException: std::exception {
public:
CException(int _rc): rc(_rc) {
stringstream ss;
ss << rc;
msg = ss.str();
}
CException(const char* _msg): msg(_msg) {}
~CException() throw(){}
int getCode() const { return rc; }
const char* getMessage() const { return msg.c_str(); }
protected:
int rc;
string msg;
};
class CSQLite3;
class CQuery{
public:
CQuery(CSQLite3& _db, const char* sql);
~CQuery();
CSQLite3& getDB() const { return db; }
size_t getPos() const { return pos; }
size_t getNumCols() const { return cols.size(); }
//最核心的函数, 会被反复调用, 每次从结果集中读取出一行(ie.一条记录), 存放在vals向量中
bool read_one_row();
int getColIdx(const char* col) const;
const char* getVal(size_t idx) const;
const char* getVal(const char* col) const;
const char* operator[](int idx) const;
const char* operator[](const char* col) const;
int getVals(map<string, string>& pair) const;
private:
CQuery(const CQuery& src);
CSQLite3& db; //database connection object
sqlite3_stmt* stmt; //prepared statement object
size_t pos; //用于区分出列名而非记录
vector<const char*> cols; //列名组成的向量
vector<const char*> vals; //每次只保存一条记录(也就是一行)的数据, 下次处理时会首先清空再存放下一条记录
map<string, int> colIdxs; //列名和索引对应的记录
};
class CSQLite3{
public:
CSQLite3(const char* path, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, const char* zVfs = NULL);
~CSQLite3();
sqlite3* getConn() const { return conn; }
typedef int (*Callback)(void*, int, char**, char**);
void exec(const char* sql, Callback cb = NULL, void* arg = NULL);
string execStr(const char* sql);
long long execInt(const char* sql);
unsigned long long execUInt(const char* sql);
std::auto_ptr<CQuery> execCQuery(const char* sql);
int execOne(const char* sql, map<string, string>& pair);
private:
sqlite3* conn;
};
#endif
下面是源文件
#include "sqlite3_wrapper.h"
CQuery::CQuery(CSQLite3& _db, const char* sql): db(_db), stmt(NULL), pos(0) {
int rc = sqlite3_prepare_v2(db.getConn(), sql, -1, &stmt, NULL);
if(rc)
throw CException(rc);
}
CQuery::~CQuery(){
if(stmt)
sqlite3_finalize(stmt);
}
//该函数会反复调用,每次只保存一行的值
bool CQuery::read_one_row() {
int rc = sqlite3_step(stmt);
if(rc != SQLITE_ROW && rc != SQLITE_DONE)
throw CException(rc);
//仅在开始获取列名向量, 以后直接跳过
if(pos++ < 1) {
size_t numCols = sqlite3_column_count(stmt);
cols.reserve(numCols);
vals.reserve(numCols);
for(size_t i = 0; i < numCols; i++) {
const char* col = sqlite3_column_name(stmt, i);
cols.push_back(col);
colIdxs[std::string(col)] = i;
}
}
//如果没有下一条记录就返回, 这说明我们已经读完了
if(rc == SQLITE_DONE)
return false;
//还有下一条记录可以读, 先清空存放该记录值的向量
vals.clear();
for(size_t i = 0; i < getNumCols(); i++) {
const char* val = (const char*)sqlite3_column_text(stmt, i);
vals.push_back(val);
}
return true;
}
//找出列名在向量中对应的索引值
int CQuery::getColIdx(const char* col) const {
std::map<std::string, int>::const_iterator i = colIdxs.find(std::string(col));
if(i != colIdxs.end())
return (*i).second;
return -1;
}
const char* CQuery::getVal(size_t idx) const {
if(idx < 0 || idx > getNumCols())
return NULL;
return vals[idx];
}
const char* CQuery::getVal(const char* col) const {
return getVal(getColIdx(col));
}
const char* CQuery::operator[](int idx) const {
return getVal(idx);
}
const char* CQuery::operator[](const char* col) const {
return getVal(col);
}
int CQuery::getVals(map<string, string>& pair) const {
for(size_t i = 0; i < getNumCols(); i++)
pair[std::string(cols[i])] = getVal(i);
return 0;
}
CSQLite3::CSQLite3(const char* path, int flags, const char* zVfs): conn(NULL) {
int rc = sqlite3_open_v2(path, &conn, flags, zVfs);
if(rc)
throw CException(rc);
}
CSQLite3::~CSQLite3(){
if(conn)
sqlite3_close(conn);
}
void CSQLite3::exec(const char* sql, Callback cb, void* arg) {
char* error = NULL;
int rc = sqlite3_exec(conn, sql, cb, arg, &error);
if(error) {
CException ex(error);
sqlite3_free(error);
throw ex;
}
if(rc)
throw CException(rc);
}
string CSQLite3::execStr(const char* sql) {
std::string ret;
CQuery c(*this, sql);
if(c.read_one_row() && c.getNumCols() > 0)
ret = c[0];
return ret;
}
long long CSQLite3::execInt(const char* sql) {
CQuery c(*this, sql);
if(!c.read_one_row() || c.getNumCols() < 1)
return -1;
return strtoll(c[0], NULL, 10);
}
unsigned long long CSQLite3::execUInt(const char* sql) {
CQuery c(*this, sql);
if(!c.read_one_row() || c.getNumCols() < 1)
return -1;
return strtoull(c[0], NULL, 10);
}
std::auto_ptr<CQuery> CSQLite3::execCQuery(const char* sql) {
return std::auto_ptr<CQuery>(new CQuery(*this, sql));
}
//成功返回0, 失败返回1
int CSQLite3::execOne(const char* sql, map<string, string>& pair) {
CQuery c(*this, sql);
if(!c.read_one_row()) {
return 1;
}
return c.getVals(pair);
}
下面是测试文件
//g++ -g test_sqlite3.cpp sqlite3_wrapper.cpp -o test_sqlite3 -lsqlite3
//
#include <iostream>
#include <unistd.h>
#include "sqlite3_wrapper.h"
int main() {
try {
unlink("test.db");
CSQLite3 db("test.db");
db.exec("create table test(id int primary key, value int);");
for(int i = 0; i < 100; i++) {
std::stringstream sql;
sql << "insert into test values(" << i << ", " << i * 2 << ");";
db.exec(sql.str().c_str());
}
std::cout << "Max: " << db.execInt("select max(value) from test;") << std::endl;
CQuery query(db, "select * from test order by value desc;");
while(query.read_one_row())
std::cout << query["id"] << " = " << query["value"] << std::endl;
//some arbitrary op to show that you can easily get the sqlite3 object and do whatever to it directly
sqlite3_interrupt(db.getConn());
}
catch(CException ex) {
std::cout << "SQLite exception: " << ex.getMessage() << std::endl;
}
return 0;
}
需要先安装sqlite3动态库,在Ubuntu 14.04 64bit上输入如下命令
sudo apt-get install sqlite3 libsqlite3-dev
在CentOS上面安装
yum -y install sqlite-devel
下面是运行截图
参考文献