我使用BLOB将对象插入SQLite数据库。插入后,我可以使用“SELECT”语句获取数据,并且数据正确,但在使用“SQLite数据库浏览器”浏览数据库时,“TASK_HEAD”行为“Empty”。 但是,如果我销毁刚刚插入的对象,我就不能再得到正确的数据了,指针“pHead”指向一个地址,其“id”成员的内容是“一二七二”铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪铪UG?”在调试模式下在VS2008中读取时。
以下是一个例子:
// user-defined data type
typedef std::string TASK_ID;
struct TASK_HEAD
{
TASK_ID id;
std::string userData;
int Size()
{
return (id.size() + userData.size()) * sizeof(TCHAR);
}
};
// when TEST_INSIDE is defined, pHead is invalid; but if undef it, I can get the "head" I just inserted
// and if the blob data is a string (when USING_STRING is defined), I can get the string inserted into the db even though the "test" string has been destroyed
void CDBWriter::WriteTestData()
{
// open db
sqlite3* db = NULL;
int nRet = sqlite3_open(DATABASE_NAME.c_str(), &db);
if (nRet != SQLITE_OK)
{
return;
}
if (db != NULL)
{
// create a table
std::string cmdCreate("CREATE TABLE IF NOT EXISTS testTable (id TEXT NOT NULL, TASK_HEAD BLOB, PRIMARY KEY(id));");
char* errMsg = NULL;
nRet = sqlite3_exec( db , cmdCreate.c_str() , 0 , 0 , &errMsg );
if( errMsg != NULL )
{
sqlite3_free( errMsg );
errMsg = NULL;
return;
}
//#define USING_STRING
#define TEST_INSIDE
#ifndef TEST_INSIDE
TASK_HEAD head;
#endif // TEST_INSIDE
// insert blob data
const TASK_ID newID(NewGUID()); // NewGUID returns string like this: "5811307F-7AA7-4C44-831F-774FC5832627"
string query = "INSERT OR REPLACE INTO testTable (id, TASK_HEAD) VALUES ('";
query += newID;
query += "', ?1);";
sqlite3_stmt* res = NULL;
nRet = sqlite3_prepare_v2(db, query.c_str(), query.length(), &res, 0);
{
#ifdef TEST_INSIDE
TASK_HEAD head;
#endif // TEST_INSIDE
head.id = newID;
#ifdef USING_STRING
std::string test("ewsjoafijdoaijeofsafsd");
nRet = sqlite3_bind_blob (res, 1, test.c_str(), test.size(), SQLITE_TRANSIENT);
#else
int nsizeHead = sizeof(head);
int nSizeHeadSt = sizeof(TASK_HEAD);
int sizeString = sizeof(std::string);
size_t nLen = newID.size();
//nRet = sqlite3_bind_blob (res, 1, &head, sizeof(head), SQLITE_TRANSIENT);
nRet = sqlite3_bind_blob (res, 1, &head, head.Size(), SQLITE_TRANSIENT);
#endif // USING_STRING
if (SQLITE_OK == nRet)
{
nRet = sqlite3_step(res);
}
if (nRet != SQLITE_OK && nRet != SQLITE_DONE)
{
return;
}
}
// get all columns in the database
query = "SELECT * FROM testTable;";
nRet = sqlite3_prepare_v2 (db, query.c_str(), query.length(), &res, 0);
if (SQLITE_OK == nRet)
{
while (SQLITE_ROW == sqlite3_step(res))
{
#ifdef USING_STRING
const char* pHead = (const char*)sqlite3_column_blob(res, 1);
#else
const TASK_HEAD *pHead = (const TASK_HEAD*)sqlite3_column_blob(res, 1);
#endif // USING_STRING
continue;
}
}
sqlite3_finalize(res);
sqlite3_close(db);
}
}
起初,我认为这可能是传递给sqlite3_bind_blob的字节问题,所以我用一个愚蠢的方法获取对象的字节,正如你在这里看到的那样(TASK_HEAD的size()函数),但是这不是没有帮助。 然后我尝试使用SQLITE_STATIC而不是SQLITE_TRANSIENT,仍然无法正常工作。 怎么了?
Ps:我知道将对象插入数据库是一个糟糕的解决方案,我只想知道为什么我无法读回插入数据库的数据。
答案 0 :(得分:1)
userData
的内容可能存储在堆上。即使它存储在std::string
内(对于SSO),它仍然可以在内部使用指向自身的指针,因此当你将其按位复制到内存中的另一个地方时它不会起作用(什么你做的等同于memcpy
)。
但是,为什么它无法正常工作并不重要,因为它只是未定义的行为。只是不要将对象插入数据库"像这样。使用某个序列化库对其进行序列化,然后然后插入它,或者在表格中使用两列,一列用于id
,另一列用于userData
。
答案 1 :(得分:1)
我认为问题出在:
nRet = sqlite3_bind_blob (res, 1, &head, head.Size(), SQLITE_TRANSIENT);
您无法获取TASK_HEAD结构的地址并将其传递给sqlite。要构建blob,您需要平面数据,没有指针和动态缓冲区,如std :: string对象。
您需要在绑定操作之前在缓冲区中序列化TASK_HEAD结构。 例如:
struct TASK_HEAD
{
TASK_ID id;
std::string userData;
std::string Data()
{
return id+userData;
}
int Size()
{
return (id.size() + userData.size()) * sizeof(TCHAR);
}
};
和
nRet = sqlite3_bind_blob (res, 1, head.Data().c_str(), head.Size(), SQLITE_TRANSIENT);
请注意,如上所示添加要序列化的字段非常差(因为此格式无法反序列化)。要处理blob,你需要找到一个好的序列化库或格式(协议缓冲区,消息包,JSON等......)或自己动手。
您的代码中存在第二个问题:
const TASK_HEAD *pHead = (const TASK_HEAD*)sqlite3_column_blob(res, 1);
由于类似的原因,这不起作用。