sqlite3数据库之blob数据类型读写

近期在开发过程中需要在数据库中保存二进制文件,于是去了解了一下sqlite3的数据类型,发现blob可以存储二进制数据。
但是blob要如何操作呢?用什么语句呢?
其实它是需要操作sqlite3库的一些底层API来实现。
下面我们就来做实验。
先上测试代码:

#include "Wdebug.h"

#include "sqlite/Wsqlite.h"
#include <stdlib.h>
#define		TABLE_NAME		"TEST_BLOB"
typedef struct {
	TUint16		len;
	void*		pdata;
} TBlobData;
void PrintList (TBlobData data)
{
	if (data.pdata) {
		TUint8* pvalue = (TUint8*)data.pdata;
		printf ("len(%d):", data.len);
		for (int i = 0; i < data.len; i++) {
			printf ("%d  ", pvalue[i]);
		}
		printf ("\n");
	} else {
		printf ("data is NULL!\n");
	}
}
int main (int argc, char* argv[])
{
	TBlobData data;
	struct TsqlCtrl* This = CreateSqlCtrl("database.db");
	if (This) {
		TsqlCtrlDelTable (This, TABLE_NAME);
		if (TsqlCtrlExec(This, "CREATE TABLE "TABLE_NAME" ("
		"id INTEGER PRIMARY KEY"
		",NAME TEXT NOT NULL"
		",age INTEGER"
		",address TEXT"
		",Data1 BLOB"
		");"
		, NULL, NULL) != WSQLITE_TRUE) {
			Werr ("创建表失败!");
		}
		TsqlCtrlExec (This, "INSERT INTO "TABLE_NAME" (id, NAME, age, address) VALUES (NULL, '张三', 19, '北京')", NULL, NULL);
		TsqlCtrlExec (This, "INSERT INTO "TABLE_NAME" (id, NAME, age, address) VALUES (NULL, '李四', 7, '上海')" , NULL, NULL);
		TsqlCtrlExec (This, "INSERT INTO "TABLE_NAME" (id, NAME, age, address) VALUES (NULL, '刘五', 39, '广州')", NULL, NULL);
		TUint8 value[10];
		for (int i = 0; i < sizeof (value); i++) {
			value[i] = i + 1;
		}
		TsqlCtrlWriteBlob (This, "UPDATE "TABLE_NAME" SET Data1=? WHERE NAME='张三'", value, sizeof (value), 1);
		for (int i = 0; i < sizeof (value); i++) {
			value[i] = i + 5;
		}
		TsqlCtrlWriteBlob (This, "UPDATE "TABLE_NAME" SET Data1=? WHERE NAME='李四'", value, sizeof (value) - 3, 1);
		for (int i = 0; i < sizeof (value); i++) {
			value[i] = i + 20;
		}
		TsqlCtrlWriteBlob (This, "INSERT INTO "TABLE_NAME" (id, NAME, age, address, Data1) VALUES (NULL, '老六', 24, '深圳', ?)", value, sizeof (value), 1);
		data = TsqlCtrlReadBlob (This, "SELECT Data1 FROM "TABLE_NAME" WHERE NAME='张三'", 0, value, sizeof (value));
		PrintList (data);
		data = TsqlCtrlReadBlob (This, "SELECT Data1 FROM "TABLE_NAME" WHERE NAME='李四'", 0, value, sizeof (value));
		PrintList (data);
		data = TsqlCtrlReadBlob (This, "SELECT id FROM "TABLE_NAME" WHERE NAME='李四'", 0, value, sizeof (value));
		PrintList (data);
		data = TsqlCtrlReadBlob (This, "SELECT Data1 FROM "TABLE_NAME" WHERE NAME='老六'", 0, value, sizeof (value));
		PrintList (data);
		TsqlCtrlDestroy(This);
	}
}

TsqlCtrl是我封装的一个结构体。
首先先创建一个带blob数据类型的表。然后再插入几条信息。通过UPDATE和INSERT语句修改和添加blob数据。blob数据部分用?代替。
底层实现代码如下:

sql::Ret sql::sqlite::WriteBlob (const char* const  sqlCmd, void* pdata, const TUint32 length, const TUint8 column_index)
{
	sql::Ret ret = sql::False;
	sqlite3_stmt* stmt = NULL;
	if (!pdata || length == 0) {
		return sql::False;
	}
	std::lock_guard<std::mutex> guard(this->mtx);
	if(sqlite3_prepare(this->db, sqlCmd, -1, &stmt, NULL)==SQLITE_OK) {
		sqlite3_bind_blob(stmt, column_index, pdata, length, NULL);
		if (sqlite3_step(stmt)==SQLITE_DONE) {
			ret = sql::True;
		}
		sqlite3_finalize(stmt);
	}
	return ret;
}

TBlobData sql::sqlite::ReadBlob (const char* const  sqlCmd, const TUint8 column_index, void* pdata, const TUint32 MaxSize)
{
	TBlobData Data;
	sqlite3_stmt* stmt = NULL;
	Data.len = 0;
	Data.pdata = nullptr;
	if (pdata&&MaxSize==0) {
		return Data;
	}
	std::lock_guard<std::mutex> guard(this->mtx);
	if(sqlite3_prepare(this->db, sqlCmd, -1, &stmt, NULL) == SQLITE_OK) {
		if (sqlite3_step(stmt) == SQLITE_ROW) {
			const void* val = sqlite3_column_blob(stmt, column_index);
			Data.len  = sqlite3_column_bytes(stmt, column_index);
			if (pdata) {
				Data.pdata = pdata;
				if (Data.len > MaxSize) Data.len = MaxSize;
			} else {
				Data.pdata = malloc (Data.len);
			}
			if (Data.pdata) {
				memcpy (Data.pdata, val, Data.len);
			} else {
				Data.len = 0;
			}
		}
		sqlite3_finalize(stmt);
	}
	return Data;
}

1、写入过程
这是一条指令修改一个blob数据的代码,先用sqlite3_prepare函数解析sql语句获得sqlite3_stmt。
sqlite3_prepare
第一个参数为数据库句柄
第二个参数为sql语句
第三个参数为sql语句的最大字节长度
第四个参数为获取到的声明句柄sqlite3_stmt*
第五个参数为指向sql语句中未使用部分的指针
然后用sqlite3_bind_blob绑定blob数据到sqlite3_stmt
sqlite3_bind_blob
第一个参数为sqlite3_stmt句柄
第二个参数为?所在列号
第三个参数为需要写入的blob数据起始地址
第四个参数为写入长度
第五个参数类型为void()(void),暂时还不清楚,应该是回调函数指针,置为空即可。

第三步就是用sqlite3_step执行sqlite3_stmt句柄内容;执行完用sqlite3_finalize销毁句柄即可。
注意:
在写入时sqlite3_bind_blob的第二个参数为?所在的列号是从1开始数的,并非从0开始!具体可参考示例代码。

2、读取过程
也是先通过sqlite3_prepare函数解析sql语句获得句柄,然后通过sqlite3_step执行句柄内容获得查询结果。最后通过sqlite3_column_blob函数获得对应列的存储数据以及用sqlite3_column_bytes函数获得对应列的数据长度。最后用sqlite3_finalize释放句柄。
注意:
sqlite3_column_blob的第二个参数为列号,跟写入过程有所不同,这个是从0开始的,具体可参考测试代码。

以上测试代码的执行结果如下:
在这里插入图片描述
看第四行输出为len(1):50
由此可知
data = TsqlCtrlReadBlob (This, “SELECT id FROM “TABLE_NAME” WHERE NAME=‘李四’”, 0, value, sizeof (value));
这一行查询的结果是id的值,50为字符2的ASCII码,而李四这条记录就是在第二行。
在这里插入图片描述于是sqlite3_column_blob和sqlite3_column_bytes的第二个参数如何确定就显而易见了。
看TsqlCtrlWriteBlob (This, “UPDATE “TABLE_NAME” SET Data1=? WHERE NAME=‘李四’”, value, sizeof (value) - 3, 1);和TsqlCtrlWriteBlob (This, “INSERT INTO “TABLE_NAME” (id, NAME, age, address, Data1) VALUES (NULL, ‘老六’, 24, ‘深圳’, ?)”, value, sizeof (value), 1);的执行结果,显然插入与更新的时候?的列号是不受其他字段影响的,只计算?是在所有?中的第几列,且是从1开始递增。

如一次读写多个blob数据,则在在sqlite3_step之前绑定多个数据或读出时读出多个数据即可。
以一次读写两个blob数据类型为例:

sql::Ret sql::sqlite::WriteBlob (const char* const  sqlCmd, TBlobData data)
{
	sql::Ret ret = sql::False;
	sqlite3_stmt* stmt = NULL;
	std::lock_guard<std::mutex> guard(this->mtx);
	if(sqlite3_prepare(this->db, sqlCmd, -1, &stmt, NULL)==SQLITE_OK) {
		// 第二个参数data.GetData(i)->index代表sql语句中?的位置(相对于VALUES中的位置从0开始,如:VALUES(1, ?),则问号的位置为1)
		sqlite3_bind_blob(stmt, data.column[0], data.pdata[0], data.len[0], NULL);
		sqlite3_bind_blob(stmt, data.column[1], data.pdata[1], data.len[1], NULL);
		if (sqlite3_step(stmt)==SQLITE_DONE) {
			ret = sql::True;
		}
		sqlite3_finalize(stmt);
	}
	return ret;
}

TBlobData sql::sqlite::ReadBlob (const char* const  sqlCmd, TBlobData data)
{
	TBlobData errdata;
	sqlite3_stmt* stmt = NULL;
	std::lock_guard<std::mutex> guard(this->mtx);
	if(sqlite3_prepare(this->db, sqlCmd, -1, &stmt, NULL) == SQLITE_OK) {
		if (sqlite3_step(stmt) == SQLITE_ROW) {
			TUint32 slen;
			const void* val;
			val = sqlite3_column_blob(stmt, data.column[0]);
			slen  = sqlite3_column_bytes(stmt,  data.column[0]);
			if (data.len[0] > slen) data.len[0] = slen;
			memcpy (data.pdata[0], val, data.len[0]);
			val = sqlite3_column_blob(stmt, data.column[1]);
			slen  = sqlite3_column_bytes(stmt,  data.column[1]);
			if (data.len[1] > slen) data.len[1] = slen;
			memcpy (data.pdata[1], val, data.len[1]);
		} else {
			sqlite3_finalize(stmt);
			return errdata;
		}
		sqlite3_finalize(stmt);
	} else {
		return errdata;
	}
	return data;
}
void main(int argc, char* argv[])
{
	TBlobData data;
	TBlobData data2;
	struct TsqlCtrl* This = CreateSqlCtrl("database.db");
	if (This) {
		TsqlCtrlDelTable (This, TABLE_NAME);
		if (TsqlCtrlExec(This, "CREATE TABLE "TABLE_NAME" ("
		"id INTEGER PRIMARY KEY"
		",NAME TEXT NOT NULL"
		",age INTEGER"
		",address TEXT"
		",Data1 BLOB"
		",Data2 BLOB"
		");"
		, NULL, NULL) != WSQLITE_TRUE) {
			Werr ("创建表失败!");
		}
		TsqlCtrlExec (This, "INSERT INTO "TABLE_NAME" (id, NAME, age, address) VALUES (NULL, '张三', 19, '北京')", NULL, NULL);
		TsqlCtrlExec (This, "INSERT INTO "TABLE_NAME" (id, NAME, age, address) VALUES (NULL, '李四', 7, '上海')" , NULL, NULL);
		TsqlCtrlExec (This, "INSERT INTO "TABLE_NAME" (id, NAME, age, address) VALUES (NULL, '刘五', 39, '广州')", NULL, NULL);
		TUint8 value[10];
		TUint8 value2[10];
		data2.len[0] = sizeof (value);
		data2.len[1] = sizeof (value2);
		data2.column[0] = 1;
		data2.column[1] = 2;
		data2.pdata[0] = value;
		data2.pdata[1] = value2;
		for (int i = 0; i < sizeof (value); i++) {
			value[i] = i + 1;
			value2[i] = i + 3;
		}
		TsqlCtrlWriteBlob (This, "UPDATE "TABLE_NAME" SET Data1=?,Data2=? WHERE NAME='张三'", data2);
		for (int i = 0; i < sizeof (value); i++) {
			value[i] = i + 5;
			value2[i] = i + 9;
		}
		TsqlCtrlWriteBlob (This, "UPDATE "TABLE_NAME" SET Data1=?,Data2=? WHERE NAME='李四'", data2);
		for (int i = 0; i < sizeof (value); i++) {
			value[i] = i + 20;
			value2[i] = i + 23;
		}
		TsqlCtrlWriteBlob (This, "INSERT INTO "TABLE_NAME" (id, NAME, age, address, Data1, Data2) VALUES (NULL, '老六', 24, '深圳', ?, ?)", data2);
		data2.column[0] = 0;
		data2.column[1] = 1;
		data = TsqlCtrlReadBlob (This, "SELECT Data1,Data2 FROM "TABLE_NAME" WHERE NAME='张三'", data2);
		PrintList (data);
		data = TsqlCtrlReadBlob (This, "SELECT Data1,Data2 FROM "TABLE_NAME" WHERE NAME='李四'", data2);
		PrintList (data);
		data = TsqlCtrlReadBlob (This, "SELECT id,Data1 FROM "TABLE_NAME" WHERE NAME='李四'", data2);
		PrintList (data);
		data = TsqlCtrlReadBlob (This, "SELECT Data1,Data2 FROM "TABLE_NAME" WHERE NAME='老六'", data2);
		PrintList (data);
		TsqlCtrlDestroy(This);
	}
}

以下为执行结果:
在这里插入图片描述
呀!不知不觉已经过了十点半了。。。粗略写写,后面完善了代码,我再上传我的数据库操作接口吧。得赶紧回家洗澡睡觉了。。。

完善的完整代码已上传:源代码下载入口
类使用说明及测试案例
基础篇

各API的使用说明将在后续陆陆续续发表博客进行帮助说明,源代码附带的注释也很详尽,通过注释也可轻松理解代码及框架思路等

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐