Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Fix handling of attempts to modify the database schema, application_id or user_version within an UNLOCKED transaction. |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | begin-concurrent |
Files: | files | file ages | folders |
SHA1: |
5b9f272113d21fd606903509d6f830fe |
User & Date: | dan 2015-08-24 19:08:10 |
Wiki: | begin-concurrent |
Context
2015-08-24
| ||
19:56 | Change "BEGIN UNLOCKED" to "BEGIN CONCURRENT". check-in: ba1ab858 user: dan tags: begin-concurrent | |
19:08 | Fix handling of attempts to modify the database schema, application_id or user_version within an UNLOCKED transaction. check-in: 5b9f2721 user: dan tags: begin-concurrent | |
16:00 | Fix compilation without SQLITE_ENABLE_UNLOCKED. Also other code organization issues. check-in: 04113557 user: dan tags: begin-concurrent | |
Changes
Changes to src/vdbe.c.
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
....
6106
6107
6108
6109
6110
6111
6112
6113
6114
6115
6116
6117
6118
6119
|
assert( DbMaskTest(p->btreeMask, pOp->p1) ); assert( p->readOnly==0 ); pDb = &db->aDb[pOp->p1]; assert( pDb->pBt!=0 ); assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) ); pIn3 = &aMem[pOp->p3]; sqlite3VdbeMemIntegerify(pIn3); /* See note about index shifting on OP_ReadCookie */ rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, (int)pIn3->u.i); if( pOp->p2==BTREE_SCHEMA_VERSION ){ if( db->bUnlocked ){ sqlite3VdbeError(p, "cannot modify database schema - " "UNLOCKED transaction" ); rc = SQLITE_ERROR; }else{ /* When the schema cookie changes, record the new cookie internally */ pDb->pSchema->schema_cookie = (int)pIn3->u.i; db->flags |= SQLITE_InternChanges; } }else if( pOp->p2==BTREE_FILE_FORMAT ){ /* Record changes in the file format */ pDb->pSchema->file_format = (u8)pIn3->u.i; } if( pOp->p1==1 ){ /* Invalidate all prepared statements whenever the TEMP database ** schema is changed. Ticket #1644 */ ................................................................................ ** P2 contains the root-page of the table to lock. ** ** P4 contains a pointer to the name of the table being locked. This is only ** used to generate an error message if the lock cannot be obtained. */ case OP_TableLock: { u8 isWriteLock = (u8)pOp->p3; if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommitted) ){ int p1 = pOp->p1; assert( p1>=0 && p1<db->nDb ); assert( DbMaskTest(p->btreeMask, p1) ); assert( isWriteLock==0 || isWriteLock==1 ); rc = sqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock); if( (rc&0xFF)==SQLITE_LOCKED ){ |
>
>
>
>
>
>
>
>
>
>
>
<
<
<
<
<
<
|
>
|
|
<
>
>
>
>
>
>
>
>
>
|
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
....
6111
6112
6113
6114
6115
6116
6117
6118
6119
6120
6121
6122
6123
6124
6125
6126
6127
6128
6129
6130
6131
6132
6133
|
assert( DbMaskTest(p->btreeMask, pOp->p1) ); assert( p->readOnly==0 ); pDb = &db->aDb[pOp->p1]; assert( pDb->pBt!=0 ); assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) ); pIn3 = &aMem[pOp->p3]; sqlite3VdbeMemIntegerify(pIn3); #ifdef SQLITE_ENABLE_UNLOCKED if( db->bUnlocked && (pOp->p2==BTREE_USER_VERSION || pOp->p2==BTREE_APPLICATION_ID) ){ rc = SQLITE_ERROR; sqlite3VdbeError(p, "cannot modify %s within UNLOCKED transaction", pOp->p2==BTREE_USER_VERSION ? "user_version" : "application_id" ); break; } #endif /* See note about index shifting on OP_ReadCookie */ rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, (int)pIn3->u.i); if( pOp->p2==BTREE_SCHEMA_VERSION ){ /* When the schema cookie changes, record the new cookie internally */ assert( db->bUnlocked==0 ); pDb->pSchema->schema_cookie = (int)pIn3->u.i; db->flags |= SQLITE_InternChanges; }else if( pOp->p2==BTREE_FILE_FORMAT ){ /* Record changes in the file format */ pDb->pSchema->file_format = (u8)pIn3->u.i; } if( pOp->p1==1 ){ /* Invalidate all prepared statements whenever the TEMP database ** schema is changed. Ticket #1644 */ ................................................................................ ** P2 contains the root-page of the table to lock. ** ** P4 contains a pointer to the name of the table being locked. This is only ** used to generate an error message if the lock cannot be obtained. */ case OP_TableLock: { u8 isWriteLock = (u8)pOp->p3; #ifdef SQLITE_ENABLE_UNLOCKED if( isWriteLock && db->bUnlocked && pOp->p2==1 ){ rc = SQLITE_ERROR; sqlite3VdbeError(p, "cannot modify database schema within UNLOCKED transaction"); rc = SQLITE_ERROR; break; } #endif if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommitted) ){ int p1 = pOp->p1; assert( p1>=0 && p1<db->nDb ); assert( DbMaskTest(p->btreeMask, p1) ); assert( isWriteLock==0 || isWriteLock==1 ); rc = sqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock); if( (rc&0xFF)==SQLITE_LOCKED ){ |
Changes to src/wal.c.
1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 .... 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 .... 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 .... 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 |
} WALTRACE(("WAL%p: closed\n", pWal)); sqlite3_free((void *)pWal->apWiData); sqlite3_free(pWal); } return rc; } /* ** Try to read the wal-index header. Return 0 on success and 1 if ** there is a problem. ** ** The wal-index is in shared memory. Another thread or process might ** be writing the header at the same time this procedure is trying to ................................................................................ ** pWal->hdr, then pWal->hdr is updated to the content of the new header ** and *pChanged is set to 1. ** ** If the checksum cannot be verified return non-zero. If the header ** is read successfully and the checksum verified, return zero. */ static int walIndexTryHdr(Wal *pWal, int *pChanged){ u32 aCksum[2]; /* Checksum on the header content */ WalIndexHdr h1, h2; /* Two copies of the header content */ WalIndexHdr volatile *aHdr; /* Header in shared memory */ /* The first page of the wal-index must be mapped at this point. */ assert( pWal->nWiData>0 && pWal->apWiData[0] ); /* Read the header. This might happen concurrently with a write to the ** same area of shared memory on a different CPU in a SMP, ** meaning it is possible that an inconsistent snapshot is read ** from the file. If this happens, return non-zero. ** ** There are two copies of the header at the beginning of the wal-index. ** When reading, read [0] first then [1]. Writes are in the reverse order. ** Memory barriers are used to prevent the compiler or the hardware from ** reordering the reads and writes. */ aHdr = walIndexHdr(pWal); memcpy(&h1, (void *)&aHdr[0], sizeof(h1)); walShmBarrier(pWal); memcpy(&h2, (void *)&aHdr[1], sizeof(h2)); if( memcmp(&h1, &h2, sizeof(h1))!=0 ){ return 1; /* Dirty read */ } if( h1.isInit==0 ){ return 1; /* Malformed header - probably all zeros */ } walChecksumBytes(1, (u8*)&h1, sizeof(h1)-sizeof(h1.aCksum), 0, aCksum); if( aCksum[0]!=h1.aCksum[0] || aCksum[1]!=h1.aCksum[1] ){ return 1; /* Checksum does not match */ } if( memcmp(&pWal->hdr, &h1, sizeof(WalIndexHdr)) ){ *pChanged = 1; memcpy(&pWal->hdr, &h1, sizeof(WalIndexHdr)); pWal->szPage = (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16); testcase( pWal->szPage<=32768 ); ................................................................................ ** a) None of the pages in pList have been modified since the ** transaction opened, and ** ** b) The database schema cookie has not been modified since the ** transaction was started. */ if( rc==SQLITE_OK ){ volatile WalIndexHdr *pHead; /* Head of the wal file */ pHead = walIndexHdr(pWal); /* TODO: Check header checksum is good here. */ if( memcmp(&pWal->hdr, (void*)pHead, sizeof(WalIndexHdr))!=0 ){ int iHash; int iLastHash = walFramePage(pHead->mxFrame); u32 iFirst = pWal->hdr.mxFrame+1; /* First wal frame to check */ if( memcmp(pWal->hdr.aSalt, (u32*)pHead->aSalt, sizeof(u32)*2) ){ assert( pWal->readLock==0 ); iFirst = 1; } for(iHash=walFramePage(iFirst); iHash<=iLastHash; iHash++){ volatile ht_slot *aHash; volatile u32 *aPgno; u32 iZero; ................................................................................ rc = walHashGet(pWal, iHash, &aHash, &aPgno, &iZero); if( rc==SQLITE_OK ){ int i; int iMin = (iFirst - iZero); int iMax = (iHash==0) ? HASHTABLE_NPAGE_ONE : HASHTABLE_NPAGE; if( iMin<1 ) iMin = 1; if( iMax>pHead->mxFrame ) iMax = pHead->mxFrame; for(i=iMin; i<=iMax; i++){ PgHdr *pPg; if( aPgno[i]==1 ){ /* Check that the schema cookie has not been modified. If ** it has not, the commit can proceed. */ u8 aNew[4]; u8 *aOld = &((u8*)pPage1->pData)[40]; |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < < < < < < < < < < < < < < < < < < < < < > | < < < < < < < < | > | < > > > > | | | | |
1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 .... 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 .... 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 .... 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 |
} WALTRACE(("WAL%p: closed\n", pWal)); sqlite3_free((void *)pWal->apWiData); sqlite3_free(pWal); } return rc; } /* ** Try to copy the wal-index header from shared-memory into (*pHdr). Return ** zero if successful or non-zero otherwise. If the header is corrupted ** (either because the two copies are inconsistent or because the checksum ** values are incorrect), the read fails and non-zero is returned. */ static int walIndexLoadHdr(Wal *pWal, WalIndexHdr *pHdr){ u32 aCksum[2]; /* Checksum on the header content */ WalIndexHdr h2; /* Second copy of the header content */ WalIndexHdr volatile *aHdr; /* Header in shared memory */ /* The first page of the wal-index must be mapped at this point. */ assert( pWal->nWiData>0 && pWal->apWiData[0] ); /* Read the header. This might happen concurrently with a write to the ** same area of shared memory on a different CPU in a SMP, ** meaning it is possible that an inconsistent snapshot is read ** from the file. If this happens, return non-zero. ** ** There are two copies of the header at the beginning of the wal-index. ** When reading, read [0] first then [1]. Writes are in the reverse order. ** Memory barriers are used to prevent the compiler or the hardware from ** reordering the reads and writes. */ aHdr = walIndexHdr(pWal); memcpy(pHdr, (void *)&aHdr[0], sizeof(h2)); walShmBarrier(pWal); memcpy(&h2, (void *)&aHdr[1], sizeof(h2)); if( memcmp(&h2, pHdr, sizeof(h2))!=0 ){ return 1; /* Dirty read */ } if( h2.isInit==0 ){ return 1; /* Malformed header - probably all zeros */ } walChecksumBytes(1, (u8*)&h2, sizeof(h2)-sizeof(h2.aCksum), 0, aCksum); if( aCksum[0]!=h2.aCksum[0] || aCksum[1]!=h2.aCksum[1] ){ return 1; /* Checksum does not match */ } return 0; } /* ** Try to read the wal-index header. Return 0 on success and 1 if ** there is a problem. ** ** The wal-index is in shared memory. Another thread or process might ** be writing the header at the same time this procedure is trying to ................................................................................ ** pWal->hdr, then pWal->hdr is updated to the content of the new header ** and *pChanged is set to 1. ** ** If the checksum cannot be verified return non-zero. If the header ** is read successfully and the checksum verified, return zero. */ static int walIndexTryHdr(Wal *pWal, int *pChanged){ WalIndexHdr h1; /* Copy of the header content */ if( walIndexLoadHdr(pWal, &h1) ){ return 1; } if( memcmp(&pWal->hdr, &h1, sizeof(WalIndexHdr)) ){ *pChanged = 1; memcpy(&pWal->hdr, &h1, sizeof(WalIndexHdr)); pWal->szPage = (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16); testcase( pWal->szPage<=32768 ); ................................................................................ ** a) None of the pages in pList have been modified since the ** transaction opened, and ** ** b) The database schema cookie has not been modified since the ** transaction was started. */ if( rc==SQLITE_OK ){ WalIndexHdr head; if( walIndexLoadHdr(pWal, &head) ){ /* This branch is taken if the wal-index header is corrupted. This ** occurs if some other writer has crashed while committing a ** transaction to this database since the current unlocked transaction ** was opened. */ rc = SQLITE_BUSY_SNAPSHOT; }else if( memcmp(&pWal->hdr, (void*)&head, sizeof(WalIndexHdr))!=0 ){ int iHash; int iLastHash = walFramePage(head.mxFrame); u32 iFirst = pWal->hdr.mxFrame+1; /* First wal frame to check */ if( memcmp(pWal->hdr.aSalt, (u32*)head.aSalt, sizeof(u32)*2) ){ assert( pWal->readLock==0 ); iFirst = 1; } for(iHash=walFramePage(iFirst); iHash<=iLastHash; iHash++){ volatile ht_slot *aHash; volatile u32 *aPgno; u32 iZero; ................................................................................ rc = walHashGet(pWal, iHash, &aHash, &aPgno, &iZero); if( rc==SQLITE_OK ){ int i; int iMin = (iFirst - iZero); int iMax = (iHash==0) ? HASHTABLE_NPAGE_ONE : HASHTABLE_NPAGE; if( iMin<1 ) iMin = 1; if( iMax>head.mxFrame ) iMax = head.mxFrame; for(i=iMin; i<=iMax; i++){ PgHdr *pPg; if( aPgno[i]==1 ){ /* Check that the schema cookie has not been modified. If ** it has not, the commit can proceed. */ u8 aNew[4]; u8 *aOld = &((u8*)pPage1->pData)[40]; |
Changes to test/unlocked.test.
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
...
404
405
406
407
408
409
410
411
412
413
|
#------------------------------------------------------------------------- # UNLOCKED transactions may not modify the db schema. # foreach {tn sql} { 1 { CREATE TABLE xx(a, b) } 2 { DROP TABLE t1 } } { do_catchsql_test 1.7.$tn.1 " BEGIN UNLOCKED; $sql " {1 {cannot modify database schema - UNLOCKED transaction}} do_execsql_test 1.7.$tn.2 ROLLBACK } #------------------------------------------------------------------------- # If an auto-vacuum database is written within an UNLOCKED transaction, it # is handled in the same way as for a non-UNLOCKED transaction. # reset_db ................................................................................ do_test 2.$tn.7.3 { list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db] } {0 {} SQLITE_OK} do_test 2.$tn.7.4 { sql3 { PRAGMA integrity_check } } ok } finish_test |
>
>
>
|
|
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
...
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
|
#------------------------------------------------------------------------- # UNLOCKED transactions may not modify the db schema. # foreach {tn sql} { 1 { CREATE TABLE xx(a, b) } 2 { DROP TABLE t1 } 3 { CREATE INDEX i1 ON t1(a) } 4 { CREATE VIEW v1 AS SELECT * FROM t1 } 5 { CREATE TEMP TABLE xx(a, b) } } { do_catchsql_test 1.7.$tn.1 " BEGIN UNLOCKED; $sql " {1 {cannot modify database schema within UNLOCKED transaction}} do_execsql_test 1.7.$tn.2 { SELECT sql FROM sqlite_master; SELECT sql FROM sqlite_temp_master; } {{CREATE TABLE t1(a, b)}} do_execsql_test 1.7.$tn.3 COMMIT } #------------------------------------------------------------------------- # If an auto-vacuum database is written within an UNLOCKED transaction, it # is handled in the same way as for a non-UNLOCKED transaction. # reset_db ................................................................................ do_test 2.$tn.7.3 { list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db] } {0 {} SQLITE_OK} do_test 2.$tn.7.4 { sql3 { PRAGMA integrity_check } } ok } #------------------------------------------------------------------------- # Unlocked transactions may not modify the user_version or application_id. # reset_db do_execsql_test 3.0 { PRAGMA journal_mode = wal; CREATE TABLE t1(x, y); INSERT INTO t1 VALUES('a', 'b'); PRAGMA user_version = 10; } {wal} do_execsql_test 3.1 { BEGIN UNLOCKED; INSERT INTO t1 VALUES('c', 'd'); SELECT * FROM t1; } {a b c d} do_catchsql_test 3.2 { PRAGMA user_version = 11; } {1 {cannot modify user_version within UNLOCKED transaction}} do_execsql_test 3.3 { PRAGMA user_version; SELECT * FROM t1; } {10 a b c d} do_catchsql_test 3.4 { PRAGMA application_id = 11; } {1 {cannot modify application_id within UNLOCKED transaction}} do_execsql_test 3.5 { COMMIT; PRAGMA user_version; PRAGMA application_id; SELECT * FROM t1; } {10 0 a b c d} finish_test |