3

BLOBを使用してオブジェクトをSQLiteデータベースに挿入します。挿入後、「SQLiteデータベースブラウザ」でデータベースを閲覧すると「TASK_HEAD」の行が「Empty」になりますが、「SELECT」文でデータが取得でき、データは正しいです。ただし、挿入したばかりのオブジェクトを破棄すると、正しいデータを取得できなくなります。ポインタ「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_TRANSIENTの代わりにSQLITE_STATICを使用しようとしましたが、まだ機能していません。どうしたの?

追伸:オブジェクトをデータベースに挿入するのは悪い解決策だと知っています。データベースに挿入されたデータを読み戻せない理由を知りたいだけです。

4

2 に答える 2

2

のコンテンツはuserDataヒープに格納される可能性があります。(SSOの場合)内部に格納されている場合std::stringでも、内部でそれ自体へのポインタを使用する可能性があるため、メモリ内の別の場所にビット単位でコピーすると機能しません(実行していることはと同等ですmemcpy)。

ただし、これは未定義の動作であるため、正確に機能しない理由は関係ありません。このように「オブジェクトをデータベースに挿入」しないでください。シリアル化ライブラリを使用してシリアル化してから挿入するか、テーブルに2つの列を使用します。1つはに、idもう1つはに使用しuserDataます。

于 2012-05-26T16:32:28.803 に答える
1

問題は次の場所にあると思います。

nRet = sqlite3_bind_blob (res, 1, &head, head.Size(), SQLITE_TRANSIENT);

このように TASK_HEAD 構造体のアドレスを取得して sqlite に渡すことはできません。ブロブを構築するには、フラットなデータが必要です。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);

上記のようにシリアル化するフィールドを追加することは非常に悪いことに注意してください (この形式はシリアル化を解除できないため)。ブロブを処理するには、適切なシリアライゼーション ライブラリまたは形式 (プロトコル バッファー、メッセージ パック、JSON など) を見つけるか、自分で作成する必要があります。

次のコードに 2 番目の問題があります。

const TASK_HEAD *pHead = (const TASK_HEAD*)sqlite3_column_blob(res, 1);

同様の理由で、これは機能しません。

于 2012-05-26T16:37:34.173 に答える