3

我使用 BLOB 将对象插入 SQLite 数据库。插入后,我可以用“SELECT”语句获取数据并且数据是正确的,虽然用“SQLite Database Browser”浏览数据库时“TASK_HEAD”的行是“Empty”。但是,如果我破坏了刚刚插入的对象,就不能再得到正确的数据了,指针“pHead”指向一个地址,其“id”成员的内容是“铪铪铪铪铪铪” " 在调试模式下在 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:我知道将对象插入数据库是一个不好的解决方案,我只想知道为什么我无法读回插入数据库的数据。

4

2 回答 2

2

的内容userData很可能存储在堆上。即使它存储在std::string(for SSO) 内部,它仍然可能在内部使用指向自身的指针,因此当您按位将其复制到内存中的另一个位置时它不起作用(您所做的相当于 a memcpy)。

但是,为什么它不起作用并不重要,因为它只是未定义的行为。只是不要像这样“将对象插入数据库”。要么使用一些序列化库对其进行序列化,然后将其插入,要么使用表中的两列,一列 for id,一列 for userData

于 2012-05-26T16:32:28.803 回答
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);

由于类似的原因,这将不起作用。

于 2012-05-26T16:37:34.173 回答