>Date: Wed, 17 Jul 2013 17:21:15 +0100
>From: Simon Slavin <slav...@bigfraud.org>
>To: General Discussion of SQLite Database <sqlite-users@sqlite.org>
>Subject: Re: [sqlite] Sqlite locking issue with ATTACH'ed databases
>Message-ID: <f0ad438b-1165-419d-99bf-57faf9de5...@bigfraud.org>
>Content-Type: text/plain; charset=us-ascii


>On 16 Jul 2013, at 11:24pm, Loren Keagle <loren.kea...@braemarllc.com> wrote:

>> Begin EXCLUSIVE TRANSACTION;
>> insert several rows of data;
>> Commit transaction;
>>
>> Prepare query statement;
>> Iterate through one or more rows;
>> Reset statement;
>>
>> Attempt to begin transaction; <--- SQLITE_BUSY!!!!
>> Would like to write more here, but can't unless I close/open the
>> connection;

>I assume you're checking the result codes returned by all the API calls before 
>the second BEGIN to see that they all return SQLITE_OK.

>Please add a _finalize() after the _reset() just for testing purposes.  I know 
>you may not want it as part of your production code.

>Is the statement that gets a busy a BEGIN or BEGIN IMMEDIATE or BEGIN 
>EXCLUSIVE ?

>Simon.


Thanks for the reply.  I've written wrapper classes in C++ that automatically 
check all return codes for every sqlite API call I make.  The only return error 
is the SQLITE_BUSY from the transaction statement (It's EXCLUSIVE, btw, but it 
doesn't seem to matter in this context).

I've tried finalizing all statements.  It definitely seems to be related to 
having the same database attached multiple times with different names.  I've 
done this because my data is split up amongst multiple sub-databases, and I 
simply have a reader and writer object that can work independently.  Of course, 
they can both end up pointing at the same sub-database, but I never would have 
thought this was a problem.

I've written some sample code to illustrate my problem.  I've commented out the 
actions that don't seem to make any difference.  Simply the fact that I've 
attached the second database causes the failure.  As soon as I detach it, I can 
write on the first again:

        // Open master database
        sqlite3* db = NULL;
        int ret = sqlite3_open_v2("Test.sqlite", &db, SQLITE_OPEN_FULLMUTEX | 
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
        if(ret != SQLITE_OK)
        {
                exit(1);
        }
        sqlite3_extended_result_codes(db, TRUE);

        // Create table on main.  This probably serves no purpose.
        ret = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS MainTable (id 
INTEGER PRIMARY KEY, IntColumn INTEGER);", NULL, NULL, NULL);
        if(ret != SQLITE_OK)
                exit(3);

        // Attach write database
        ret = sqlite3_exec(db, "ATTACH DATABASE 'TestSub.sqlite' as write1;", 
NULL, NULL, NULL);
        if(ret != SQLITE_OK)
                exit(2);

        // Create table on subdb
        ret = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS write1.TestTable (id 
INTEGER PRIMARY KEY, IntColumn INTEGER);", NULL, NULL, NULL);
        if(ret != SQLITE_OK)
                exit(3);

        // Insert some data in write table
        sqlite3_stmt * insert = nullptr;
        const char* tail = nullptr;
        ret = sqlite3_prepare_v2(db, "INSERT INTO write1.TestTable (IntColumn) 
VALUES (?1);", -1, &insert, &tail);
        if (ret != SQLITE_OK)
                exit(4);

        for (int i = 0; i < 10; ++i)
        {
                ret = sqlite3_bind_int(insert, 1, i);
                if (ret != SQLITE_OK)
                        exit(5);

                ret = sqlite3_step(insert);
                if(ret != SQLITE_DONE)
                        exit(6);

                ret = sqlite3_reset(insert);
                if (ret != SQLITE_OK)
                        exit(7);
        }
        ret = sqlite3_reset(insert);
        if (ret != SQLITE_OK)
                exit(8);

        // Attach read database
        ret = sqlite3_exec(db, "ATTACH DATABASE 'TestSub.sqlite' as read1;", 
NULL, NULL, NULL);
        if(ret != SQLITE_OK)
                exit(9);

        //sqlite3_stmt * readRow = nullptr;
        //ret = sqlite3_prepare_v2(db, "SELECT * FROM read1.TestTable;", -1, 
&readRow, &tail);
        //if (ret == SQLITE_BUSY || ret == SQLITE_LOCKED)
        //      exit(10);

        //// Iterate through the inserted rows
        //do
        //{
        //      ret = sqlite3_step(readRow);
        //      if (ret == SQLITE_BUSY || ret == SQLITE_LOCKED)
        //              exit(11);

        //      int id = sqlite3_column_int(readRow, 0);
        //      int val = sqlite3_column_int(readRow, 1);
        //} while (ret != SQLITE_DONE);

        //ret = sqlite3_reset(readRow);
        //if (ret != SQLITE_OK)
        //      exit(12);

        //// Finalize open read statement. Has no effect on the transaction, 
but is necessary for detaching?
        //ret = sqlite3_finalize(readRow);
        //if (ret != SQLITE_OK)
        //      exit(13);

        //// Detach read db.  This will allow the transaction to succeed.
        //ret = sqlite3_exec(db, "DETACH DATABASE read1;", NULL, NULL, NULL);
        //if(ret != SQLITE_OK)
        //      exit(14);

        // Now attempt to create a transaction
        ret = sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL, 
NULL);
        if(ret != SQLITE_OK)
                exit(15); // <-- This fails if the 2nd db is attached

This email, including any attachments and files transmitted with it, are for 
the sole use of the intended recipient(s) to whom this email is addressed, and 
may contain confidential and/or privileged information. Any unauthorized 
review, use, disclosure or distribution is prohibited. If you are not the 
intended recipient, please be advised that you have received this email in 
error, and please contact the sender by reply email and destroy all copies 
(including all electronic and hard copies) of the original message. Thank you.

_______________________________________________
sqlite-users mailing list
sqlite-users@sqlite.org
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-users

Reply via email to