]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the sqlite3_unlock_notify() API. (CVS 6348)
authordanielk1977 <danielk1977@noemail.net>
Mon, 16 Mar 2009 13:19:36 +0000 (13:19 +0000)
committerdanielk1977 <danielk1977@noemail.net>
Mon, 16 Mar 2009 13:19:36 +0000 (13:19 +0000)
FossilOrigin-Name: b649a6cc5bfefddd6a04b1183647d2923e0a0daa

26 files changed:
main.mk
manifest
manifest.uuid
src/backup.c
src/btree.c
src/btreeInt.h
src/main.c
src/notify.c [new file with mode: 0644]
src/prepare.c
src/sqlite.h.in
src/sqliteInt.h
src/tclsqlite.c
src/test1.c
src/test_config.c
src/test_thread.c
src/vdbe.c
src/vdbeaux.c
src/vtab.c
test/backup.test
test/incrblob2.test
test/notify1.test [new file with mode: 0644]
test/notify2.test [new file with mode: 0644]
test/shared.test
test/tclsqlite.test
test/tkt2854.test
tool/mksqlite3c.tcl

diff --git a/main.mk b/main.mk
index f5b6231a9e03be2c8a8bca0a1e92cf66465d5305..718ba49a8baf4f40496d83507c7341bddd9346c2 100644 (file)
--- a/main.mk
+++ b/main.mk
@@ -59,7 +59,7 @@ LIBOBJ+= alter.o analyze.o attach.o auth.o \
          main.o malloc.o mem0.o mem1.o mem2.o mem3.o mem5.o \
          memjournal.o \
          mutex.o mutex_noop.o mutex_os2.o mutex_unix.o mutex_w32.o \
-         opcodes.o os.o os_os2.o os_unix.o os_win.o \
+         notify.o opcodes.o os.o os_os2.o os_unix.o os_win.o \
          pager.o parse.o pcache.o pcache1.o pragma.o prepare.o printf.o \
          random.o resolve.o rowset.o rtree.o select.o status.o \
          table.o tokenize.o trigger.o \
@@ -112,6 +112,7 @@ SRC = \
   $(TOP)/src/mutex_os2.c \
   $(TOP)/src/mutex_unix.c \
   $(TOP)/src/mutex_w32.c \
+  $(TOP)/src/notify.c \
   $(TOP)/src/os.c \
   $(TOP)/src/os.h \
   $(TOP)/src/os_common.h \
index 5ed729050c02b82e1077ddee8bc7072166169d12..da1b9cf72d30a0790e5942856cd298e8e24f0917 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Bump\sthe\sversion\snumber\sto\s3.6.12.\s(CVS\s6347)
-D 2009-03-16T12:30:52
+C Add\sthe\ssqlite3_unlock_notify()\sAPI.\s(CVS\s6348)
+D 2009-03-16T13:19:36
 F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0
 F Makefile.in d64baddbf55cdf33ff030e14da837324711a4ef7
 F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
@@ -83,7 +83,7 @@ F ext/rtree/tkt3363.test 6662237ea75bb431cd5d262dfc9535e1023315fc
 F ext/rtree/viewrtree.tcl 09526398dae87a5a87c5aac2b3854dbaf8376869
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
-F main.mk 2193e5939dbf91449f9b72178d543d31b2315360
+F main.mk bbb170882a34fe51dbd2d2e9c450c6cc0dad3325
 F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a
 F mkextu.sh 416f9b7089d80e5590a29692c9d9280a10dbad9f
 F mkextw.sh 4123480947681d9b434a5e7b1ee08135abe409ac
@@ -100,12 +100,12 @@ F src/alter.c b95815ccc92477b1b5874fb0fe20f65c867e9cc8
 F src/analyze.c 3585d1a4c480ee85b65cf0a676e05d2c29eb6bdb
 F src/attach.c d34589d5c85d81e755e4a8fc946d313915a6fa6d
 F src/auth.c c8b2ab5c8bad4bd90ed7c294694f48269162c627
-F src/backup.c 2d3f31148d7b086c5c72d9edcd04fc2751b0aa6e
+F src/backup.c 0082d0e5a63f04e88faee0dff0a7d63d3e92a78d
 F src/bitvec.c 44f7059ac1f874d364b34af31b9617e52223ba75
 F src/btmutex.c 341502bc496dc0840dcb00cde65680fb0e85c3ab
-F src/btree.c 6e7501d7a207dcc15b099e67231bc8cc86ef7fe9
+F src/btree.c 7d1c7e15ef8f5b139a9267c3f053d10f8885759b
 F src/btree.h 96a019c9f28da38e79940512d7800e419cd8c702
-F src/btreeInt.h 0a4884e6152d7cae9c741e91b830064c19fd2c05
+F src/btreeInt.h 2f1fe3c5f48800de8b542e05a746796cfdda13bd
 F src/build.c 7e5e93011bab2f142cb03faa72bb64ff30e321fc
 F src/callback.c 09c6fedc77a45db99ba25a75d61382830314b357
 F src/complete.c cb14e06dbe79dee031031f0d9e686ff306afe07c
@@ -122,7 +122,7 @@ F src/insert.c 71286d081a919a27ef22eaeccbe2718f93dc6aa9
 F src/journal.c e00df0c0da8413ab6e1bb7d7cab5665d4a9000d0
 F src/legacy.c 8b3b95d48d202614946d7ce7256e7ba898905c3b
 F src/loadext.c 3f96631089fc4f3871a67f02f2e4fc7ea4d51edc
-F src/main.c fd98b89bcc80d27e303c913c8facab9ee939ef15
+F src/main.c 99955156dd3167e79a492187cf05e3a73196fbe2
 F src/malloc.c b9c59b33539af74641362a7496ecc3efd6477f6d
 F src/mem0.c f2f84062d1f35814d6535c9f9e33de3bfb3b132c
 F src/mem1.c 3bfb39e4f60b0179713a7c087b2d4f0dc205735f
@@ -136,6 +136,7 @@ F src/mutex_noop.c f5a07671f25a1a9bd7c10ad7107bc2585446200f
 F src/mutex_os2.c 6b5a74f812082a8483c3df05b47bbaac2424b9a0
 F src/mutex_unix.c 2f936339dfef1a4c142db290d575a3509b77315f
 F src/mutex_w32.c f4b6a4a48f1dfff7f0089cba9b5a371691f17b8b
+F src/notify.c 5787adee6f119c7d36fd8937d31d680467e01ca5
 F src/os.c ed93a6b46132a602c4fd7a58142e2981c829c79d
 F src/os.h fa3f4aa0119ff721a2da4b47ffd74406ac864c05
 F src/os_common.h 8c61457df58f1a4bd5f5adc3e90e01b37bf7afbc
@@ -149,21 +150,21 @@ F src/pcache.c fcf7738c83c4d3e9d45836b2334c8a368cc41274
 F src/pcache.h 9b927ccc5a538e31b4c3bc7eec4f976db42a1324
 F src/pcache1.c f12518540ba776df3051215c4244e9cdc06b09cd
 F src/pragma.c 22ed04836aab8ce134c53be1ca896f3ad20fabdb
-F src/prepare.c 1a2d40c0c5fb9f244cf5e3aacc33abcc1e7b095a
+F src/prepare.c ebd40f8c6a33c52bfea2db710394d6e82c7e594b
 F src/printf.c 9866a9a9c4a90f6d4147407f373df3fd5d5f9b6f
 F src/random.c 676b9d7ac820fe81e6fb2394ac8c10cff7f38628
 F src/resolve.c 094e44450371fb27869eb8bf679aacbe51fdc56d
 F src/rowset.c ba9375f37053d422dd76965a9c370a13b6e1aac4
 F src/select.c 4d0b77fd76ff80f09a798ee98953e344c9de8fbb
 F src/shell.c 0a11f831603f17fea20ca97133c0f64e716af4a7
-F src/sqlite.h.in 14f4d065bafed8500ea558a75a8e2be89c784d61
+F src/sqlite.h.in 5efbb12037347fc1341ca996a5e629734ea1212e
 F src/sqlite3ext.h 1db7d63ab5de4b3e6b83dd03d1a4e64fef6d2a17
-F src/sqliteInt.h ae2dc2e2a063edfae3043e725981e69855bd3c9c
+F src/sqliteInt.h 22332127b67d6eabb4bd09cca4ca74fd17f65238
 F src/sqliteLimit.h ffe93f5a0c4e7bd13e70cd7bf84cfb5c3465f45d
 F src/status.c 237b193efae0cf6ac3f0817a208de6c6c6ef6d76
 F src/table.c 332ab0ea691e63862e2a8bdfe2c0617ee61062a3
-F src/tclsqlite.c c18d6b71b3a01ded68e4479b128116e67eecfd2c
-F src/test1.c f88b447699786d58a0136a3a48b12990abc72c8a
+F src/tclsqlite.c 8a472804b901d4559213eeda538c2eadb2ad7f2a
+F src/test1.c 17300af44640eea439778f5b5e03e0d68a6f00a2
 F src/test2.c 71c22e2974f8094fe0fd1eba8f27872dde9b2a39
 F src/test3.c 88a246b56b824275300e6c899634fbac1dc94b14
 F src/test4.c f79ab52d27ff49b784b631a42e2ccd52cfd5c84c
@@ -176,7 +177,7 @@ F src/test_async.c 75771172f91735f55ebbd2cbfe4b9804bd030206
 F src/test_autoext.c f53b0cdf7bf5f08100009572a5d65cdb540bd0ad
 F src/test_backup.c 5b41518c5499dafe65177b0813b71ac356ee9df1
 F src/test_btree.c d7b8716544611c323860370ee364e897c861f1b0
-F src/test_config.c 9dd62f4bb725ad87d28b187b07377cb4f4a43197
+F src/test_config.c a05378089b6773ba36b85727dedf9ec0a16424ce
 F src/test_devsym.c 9f4bc2551e267ce7aeda195f3897d0f30c5228f4
 F src/test_func.c a55c4d5479ff2eb5c0a22d4d88e9528ab59c953b
 F src/test_hexio.c 2f1122aa3f012fa0142ee3c36ce5c902a70cd12f
@@ -191,7 +192,7 @@ F src/test_pcache.c 29464896d9c67832e4eef916c0682b98d7283d00
 F src/test_schema.c 4b4bf7bb329326458c491b0e6facd4c8c4c5b479
 F src/test_server.c f0a403b5f699c09bd2b1236b6f69830fd6221f6b
 F src/test_tclvar.c 9e42fa59d3d2f064b7ab8628e7ab2dc8a9fe93d4
-F src/test_thread.c adb9175c466e1f487295b5b957603fc0a88ea293
+F src/test_thread.c 6805d05c3b7e08d51b31662b148cf2f51b9ca4cc
 F src/test_wsd.c c297d7d6b8a990239e1bd25935e81d612d8ae31d
 F src/tokenize.c 6987fb7f0d6a87ac53499aee568cabb05eb4bea8
 F src/trigger.c 21f39db410cdc32166a94900ac1b3df98ea560e6
@@ -199,14 +200,14 @@ F src/update.c 8ededddcde6f7b6da981dd0429a5d34518a475b7
 F src/utf.c 1da9c832dba0fa8f865b5b902d93f420a1ee4245
 F src/util.c 469d74f5bf09ed6398702c7da2ef8a34e979a1c1
 F src/vacuum.c 4929a585ef0fb1dfaf46302f8a9c4aa30c2d9cf5
-F src/vdbe.c a193ab2ce10ccb70825615870c5b4413dc3be636
+F src/vdbe.c 230b0b7e73d90e001e811fca94b95b664b6ece66
 F src/vdbe.h d70a68bee196ab228914a3902c79dbd24342a0f2
 F src/vdbeInt.h d12bc259b34d3d610ebf05d648eb6346d48478c3
 F src/vdbeapi.c ffd5d8b493590da6e09fd54b1bea1a9d38247f11
-F src/vdbeaux.c cf84955182b48cd8c65c52c143e150bb4a71f2da
+F src/vdbeaux.c feeafee5f9de51c0d30907e0600ce4db5d032df8
 F src/vdbeblob.c 2852bae14c87129835938db58a77c3121e3ae962
 F src/vdbemem.c 543a79d722734d2f8b7ad70f08218c30bcc5bbf5
-F src/vtab.c e39e011d7443a8d574b1b9cde207a35522e6df43
+F src/vtab.c bf409d2dc068e1bb82beeb9eef120ccfff541afb
 F src/walker.c 42bd3f00ca2ef5ae842304ec0d59903ef051412d
 F src/where.c ac555c8f6ef71a80944b31dcb212f7127c9ae30c
 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
@@ -232,7 +233,7 @@ F test/autoinc.test ab549b48b389cabd92967b86c379ec8b31fa6c16
 F test/autovacuum.test 61260e25744189ff766f61ca3df23c1eeec0060e
 F test/autovacuum_ioerr2.test 598b0663074d3673a9c1bc9a16e80971313bafe6
 F test/avtrans.test 1e901d8102706b63534dbd2bdd4d8f16c4082650
-F test/backup.test 9542691cb9199b1d27051756377ef12e27404cd5
+F test/backup.test 5e487ec8dad73e9d249e9bb9ca5346a03b601b07
 F test/backup2.test 04b84c97b4b5d63b6756592c6d7afe578b52c3cf
 F test/backup_ioerr.test a9b8084e488154341719833783ac9db321e14284
 F test/backup_malloc.test 1e063c6d75143d0d6e0ae77971dd690070369387
@@ -391,7 +392,7 @@ F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75
 F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0
 F test/in4.test f795d65cbcb402d3e5c016ada8f9521d6119eca8
 F test/incrblob.test 4b9437bbb38724343dadbbcca6356bc2a9b435d1
-F test/incrblob2.test 5cca1c3cb29064c504b3b0cc3e2cd43e8053cfdf
+F test/incrblob2.test 7ef4581745dd80155a451637aa779b49df90787d
 F test/incrblob_err.test c577c91d4ed9e8336cdb188b15d6ee2a6fe9604e
 F test/incrvacuum.test d0fb6ef6d747ef5c5ebe878aafa72dd3e178856b
 F test/incrvacuum2.test 46ef65f377e3937cfd1ba66e818309dab46f590d
@@ -477,6 +478,8 @@ F test/misuse.test 30b3a458e5a70c31e74c291937b6c82204c59f33
 F test/mutex1.test 1e5c196d5170bbe3a7d8370b1b905e8c86a9e07c
 F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660
 F test/nan.test c627d79b3d36ea892563fd67584b3e8a18f0618a
+F test/notify1.test 9a985a94f34de1b24daf25fd86b6d5033ba532d0
+F test/notify2.test 828511802c3e899d91f753771cefbe1d41dcb854
 F test/notnull.test 44d600f916b770def8b095a9962dbe3be5a70d82
 F test/null.test a8b09b8ed87852742343b33441a9240022108993
 F test/openv2.test f5dd6b23e4dce828eb211649b600763c42a668df
@@ -524,7 +527,7 @@ F test/selectA.test 06d1032fa9009314c95394f2ca2e60d9f7ae8532
 F test/selectB.test 31e81ac9af7d224850e0706350f070ecb92fcbc7
 F test/selectC.test ae49d258c875bc1712898f1632062bc5c01a7470
 F test/server1.test f5b790d4c0498179151ca8a7715a65a7802c859c
-F test/shared.test 2f6c65de8123c130b92e4e18a516f669eaa02fea
+F test/shared.test 3b448dc0f7a9356e641894ed81c27599f39d809d
 F test/shared2.test 0ee9de8964d70e451936a48c41cb161d9134ccf4
 F test/shared3.test 9c880afc081d797da514ef64bccf36f3fce2f09c
 F test/shared4.test d0fadacb50bb6981b2fb9dc6d1da30fa1edddf83
@@ -549,7 +552,7 @@ F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a
 F test/sync.test ded6b39d8d8ca3c0c5518516c6371b3316d3e3a3
 F test/table.test bf1bc3d9634342a3470bdf64b6190e7445b6b8a6
 F test/tableapi.test 505031f15b18a750184d967d2c896cf88fcc969c
-F test/tclsqlite.test 413a8a887d89ea8fa7055e8d118ffb03b0a4c91a
+F test/tclsqlite.test 8b1150d0486c4848c70d96422513a91c5342be0e
 F test/tempdb.test b88ac8a19823cf771d742bf61eef93ef337c06b1
 F test/temptable.test 5d8ca46be28cc06c887c5a77df650843b7edbae1
 F test/temptrigger.test 03093be9967942623232dfdf2a63b832d4e0e4fa
@@ -597,7 +600,7 @@ F test/tkt2817.test 94646b604c7dbae7058782f6582c05e200700aa9
 F test/tkt2820.test 017fdee33aaef7abc092beab6088816f1942304b
 F test/tkt2822.test a2b27a58df62d1b2e712f91dbe42ad3b7e0e77cc
 F test/tkt2832.test 85cf382ff406de9de35534b86bc7227d609140c0
-F test/tkt2854.test a2bc584ac26bcebe174229e7a1ad4e6d43c3d569
+F test/tkt2854.test b81dc3144901b123fe5674471adf5a47ca48a7c3
 F test/tkt2920.test a8737380e4ae6424e00c0273dc12775704efbebf
 F test/tkt2927.test 4752868b9eeeb07a217f7f19f4cbaac98d6d086d
 F test/tkt2942.test c5c87d179799ca6d1fbe83c815510b87cd5ec7ce
@@ -690,7 +693,7 @@ F tool/lempar.c aeba88b8566ff66f8a67c96b3eb2dd95e7d8908d
 F tool/mkkeywordhash.c 8e57fbe8c4fe2f1800f9190fd361231cb8558407
 F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e x
 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
-F tool/mksqlite3c.tcl a416823e4c06e8d8e18e141e69a8a0e8e12ad861
+F tool/mksqlite3c.tcl 671833bd775e76ebd922b9e82c2508a344562511
 F tool/mksqlite3internalh.tcl 7b43894e21bcb1bb39e11547ce7e38a063357e87
 F tool/omittest.tcl 27d6f6e3b1e95aeb26a1c140e6eb57771c6d794a
 F tool/opcodeDoc.awk b3a2a3d5d3075b8bd90b7afe24283efdd586659c
@@ -704,7 +707,7 @@ F tool/speedtest16.c c8a9c793df96db7e4933f0852abb7a03d48f2e81
 F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
-P 324a1aff300b7349b9fc1dea56d640d86500f100
-R 3843a549b4ba7feed5cd440b16a43795
-U drh
-Z 9336bd2967fe6f29f86215585309dca6
+P 2fcccca3e56e2e3a95bdedeb01ab7da1b24b7ac2
+R 5be2b218449f212c10c446da99e1fb31
+U danielk1977
+Z 7d9c2a290b69196df9bf164217432ca4
index 42e5174816b63f226fc8ad516074c9a638fa9d5a..b775deb5c1590fb75116e4115aa323d8f65051c2 100644 (file)
@@ -1 +1 @@
-2fcccca3e56e2e3a95bdedeb01ab7da1b24b7ac2
\ No newline at end of file
+b649a6cc5bfefddd6a04b1183647d2923e0a0daa
\ No newline at end of file
index 9c55804790fa160603322ad186ff69837ea4ba13..781b4b0bca1dece2420b213b4f7aa42ba643875b 100644 (file)
@@ -12,7 +12,7 @@
 ** This file contains the implementation of the sqlite3_backup_XXX() 
 ** API functions and the related features.
 **
-** $Id: backup.c,v 1.12 2009/02/16 17:55:47 shane Exp $
+** $Id: backup.c,v 1.13 2009/03/16 13:19:36 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include "btreeInt.h"
@@ -292,10 +292,10 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
     int bCloseTrans = 0;               /* True if src db requires unlocking */
 
     /* If the source pager is currently in a write-transaction, return
-    ** SQLITE_LOCKED immediately.
+    ** SQLITE_BUSY immediately.
     */
     if( p->pDestDb && p->pSrc->pBt->inTransaction==TRANS_WRITE ){
-      rc = SQLITE_LOCKED;
+      rc = SQLITE_BUSY;
     }else{
       rc = SQLITE_OK;
     }
index b012e51598c114126a7c7756aa236440c991cc70..ed12137b824470352d857703e66bdf1996b8f656 100644 (file)
@@ -9,7 +9,7 @@
 **    May you share freely, never taking more than you give.
 **
 *************************************************************************
-** $Id: btree.c,v 1.572 2009/03/12 14:43:28 danielk1977 Exp $
+** $Id: btree.c,v 1.573 2009/03/16 13:19:36 danielk1977 Exp $
 **
 ** This file implements a external (disk-based) database using BTrees.
 ** See the header comment on "btreeInt.h" for additional information.
@@ -109,8 +109,9 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){
   /* If some other connection is holding an exclusive lock, the
   ** requested lock may not be obtained.
   */
-  if( pBt->pExclusive && pBt->pExclusive!=p ){
-    return SQLITE_LOCKED;
+  if( pBt->pWriter!=p && pBt->isExclusive ){
+    sqlite3ConnectionBlocked(p->db, pBt->pWriter->db);
+    return SQLITE_LOCKED_SHAREDCACHE;
   }
 
   /* This (along with setSharedCacheTableLock()) is where
@@ -137,7 +138,12 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){
     for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){
       if( pIter->pBtree!=p && pIter->iTable==iTab && 
           (pIter->eLock!=eLock || eLock!=READ_LOCK) ){
-        return SQLITE_LOCKED;
+        sqlite3ConnectionBlocked(p->db, pIter->pBtree->db);
+        if( eLock==WRITE_LOCK ){
+          assert( p==pBt->pWriter );
+          pBt->isPending = 1;
+        }
+        return SQLITE_LOCKED_SHAREDCACHE;
       }
     }
   }
@@ -233,7 +239,7 @@ static void clearAllSharedCacheTableLocks(Btree *p){
 
   while( *ppIter ){
     BtLock *pLock = *ppIter;
-    assert( pBt->pExclusive==0 || pBt->pExclusive==pLock->pBtree );
+    assert( pBt->isExclusive==0 || pBt->pWriter==pLock->pBtree );
     if( pLock->pBtree==p ){
       *ppIter = pLock->pNext;
       sqlite3_free(pLock);
@@ -242,8 +248,22 @@ static void clearAllSharedCacheTableLocks(Btree *p){
     }
   }
 
-  if( pBt->pExclusive==p ){
-    pBt->pExclusive = 0;
+  assert( pBt->isPending==0 || pBt->pWriter );
+  if( pBt->pWriter==p ){
+    pBt->pWriter = 0;
+    pBt->isExclusive = 0;
+    pBt->isPending = 0;
+  }else if( pBt->nTransaction==2 ){
+    /* This function is called when connection p is concluding its 
+    ** transaction. If there currently exists a writer, and p is not
+    ** that writer, then the number of locks held by connections other
+    ** than the writer must be about to drop to zero. In this case
+    ** set the isPending flag to 0.
+    **
+    ** If there is not currently a writer, then BtShared.isPending must
+    ** be zero already. So this next line is harmless in that case.
+    */
+    pBt->isPending = 0;
   }
 }
 #endif /* SQLITE_OMIT_SHARED_CACHE */
@@ -2052,6 +2072,7 @@ static int newDatabase(BtShared *pBt){
 ** proceed.
 */
 int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
+  sqlite3 *pBlock = 0;
   BtShared *pBt = p->pBt;
   int rc = SQLITE_OK;
 
@@ -2073,25 +2094,27 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
     goto trans_begun;
   }
 
+#ifndef SQLITE_OMIT_SHARED_CACHE
   /* If another database handle has already opened a write transaction 
   ** on this shared-btree structure and a second write transaction is
-  ** requested, return SQLITE_BUSY.
+  ** requested, return SQLITE_LOCKED.
   */
-  if( pBt->inTransaction==TRANS_WRITE && wrflag ){
-    rc = SQLITE_BUSY;
-    goto trans_begun;
-  }
-
-#ifndef SQLITE_OMIT_SHARED_CACHE
-  if( wrflag>1 ){
+  if( (wrflag && pBt->inTransaction==TRANS_WRITE) || pBt->isPending ){
+    pBlock = pBt->pWriter->db;
+  }else if( wrflag>1 ){
     BtLock *pIter;
     for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){
       if( pIter->pBtree!=p ){
-        rc = SQLITE_BUSY;
-        goto trans_begun;
+        pBlock = pIter->pBtree->db;
+        break;
       }
     }
   }
+  if( pBlock ){
+    sqlite3ConnectionBlocked(p->db, pBlock);
+    rc = SQLITE_LOCKED_SHAREDCACHE;
+    goto trans_begun;
+  }
 #endif
 
   do {
@@ -2129,9 +2152,10 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
       pBt->inTransaction = p->inTrans;
     }
 #ifndef SQLITE_OMIT_SHARED_CACHE
-    if( wrflag>1 ){
-      assert( !pBt->pExclusive );
-      pBt->pExclusive = p;
+    if( wrflag ){
+      assert( !pBt->pWriter );
+      pBt->pWriter = p;
+      pBt->isExclusive = (wrflag>1);
     }
 #endif
   }
@@ -2940,8 +2964,10 @@ static int btreeCursor(
     if( NEVER(pBt->readOnly) ){
       return SQLITE_READONLY;
     }
-    if( checkForReadConflicts(p, iTable, 0, 0) ){
-      return SQLITE_LOCKED;
+    rc = checkForReadConflicts(p, iTable, 0, 0);
+    if( rc!=SQLITE_OK ){
+      assert( rc==SQLITE_LOCKED_SHAREDCACHE );
+      return rc;
     }
   }
 
@@ -5968,9 +5994,10 @@ static int checkForReadConflicts(
 #endif
     ){
       sqlite3 *dbOther = p->pBtree->db;
-      if( dbOther==0 ||
-         (dbOther!=db && (dbOther->flags & SQLITE_ReadUncommitted)==0) ){
-        return SQLITE_LOCKED;
+      assert(dbOther);
+      if( dbOther!=db && (dbOther->flags & SQLITE_ReadUncommitted)==0 ){
+        sqlite3ConnectionBlocked(db, dbOther);
+        return SQLITE_LOCKED_SHAREDCACHE;
       }
     }
   }
@@ -6007,8 +6034,11 @@ int sqlite3BtreeInsert(
   assert( pBt->inTransaction==TRANS_WRITE );
   assert( !pBt->readOnly );
   assert( pCur->wrFlag );
-  if( checkForReadConflicts(pCur->pBtree, pCur->pgnoRoot, pCur, nKey) ){
-    return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+  rc = checkForReadConflicts(pCur->pBtree, pCur->pgnoRoot, pCur, nKey);
+  if( rc ){             
+    /* The table pCur points to has a read lock */
+    assert( rc==SQLITE_LOCKED_SHAREDCACHE );
+    return rc;
   }
   if( pCur->eState==CURSOR_FAULT ){
     return pCur->skip;
@@ -6104,10 +6134,11 @@ int sqlite3BtreeDelete(BtCursor *pCur){
     return SQLITE_ERROR;  /* The cursor is not pointing to anything */
   }
   assert( pCur->wrFlag );
-  if( checkForReadConflicts(pCur->pBtree, pCur->pgnoRoot,
-                            pCur, pCur->info.nKey)
-  ){
-    return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+  rc = checkForReadConflicts(p, pCur->pgnoRoot, pCur, pCur->info.nKey);
+  if( rc!=SQLITE_OK ){
+    /* The table pCur points to has a read lock */
+    assert( rc==SQLITE_LOCKED_SHAREDCACHE );
+    return rc;
   }
 
   /* Restore the current cursor position (a no-op if the cursor is not in 
@@ -6538,7 +6569,8 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){
   ** occur.
   */
   if( pBt->pCursor ){
-    return SQLITE_LOCKED;
+    sqlite3ConnectionBlocked(p->db, pBt->pCursor->pBtree->db);
+    return SQLITE_LOCKED_SHAREDCACHE;
   }
 
   rc = sqlite3BtreeGetPage(pBt, (Pgno)iTable, &pPage, 0);
@@ -7378,14 +7410,16 @@ void *sqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void *)){
 }
 
 /*
-** Return true if another user of the same shared btree as the argument
-** handle holds an exclusive lock on the sqlite_master table.
+** Return SQLITE_LOCKED_SHAREDCACHE if another user of the same shared 
+** btree as the argument handle holds an exclusive lock on the 
+** sqlite_master table. Otherwise SQLITE_OK.
 */
 int sqlite3BtreeSchemaLocked(Btree *p){
   int rc;
   assert( sqlite3_mutex_held(p->db->mutex) );
   sqlite3BtreeEnter(p);
-  rc = (querySharedCacheTableLock(p, MASTER_ROOT, READ_LOCK)!=SQLITE_OK);
+  rc = querySharedCacheTableLock(p, MASTER_ROOT, READ_LOCK);
+  assert( rc==SQLITE_OK || rc==SQLITE_LOCKED_SHAREDCACHE );
   sqlite3BtreeLeave(p);
   return rc;
 }
@@ -7423,6 +7457,8 @@ int sqlite3BtreeLockTable(Btree *p, int iTab, u8 isWriteLock){
 ** to change the length of the data stored.
 */
 int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){
+  int rc;
+
   assert( cursorHoldsMutex(pCsr) );
   assert( sqlite3_mutex_held(pCsr->pBtree->db->mutex) );
   assert(pCsr->isIncrblobHandle);
@@ -7443,8 +7479,11 @@ int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){
   }
   assert( !pCsr->pBt->readOnly 
           && pCsr->pBt->inTransaction==TRANS_WRITE );
-  if( checkForReadConflicts(pCsr->pBtree, pCsr->pgnoRoot, pCsr, 0) ){
-    return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+  rc = checkForReadConflicts(pCsr->pBtree, pCsr->pgnoRoot, pCsr, 0);
+  if( rc!=SQLITE_OK ){
+    /* The table pCur points to has a read lock */
+    assert( rc==SQLITE_LOCKED_SHAREDCACHE );
+    return rc;
   }
   if( pCsr->eState==CURSOR_INVALID || !pCsr->apPage[pCsr->iPage]->intKey ){
     return SQLITE_ERROR;
index 32aec9615193948c51facdeae9071e80fa6d669a..4652521df361e140bd6fbbb7bbb5b64f02a6da35 100644 (file)
@@ -9,7 +9,7 @@
 **    May you share freely, never taking more than you give.
 **
 *************************************************************************
-** $Id: btreeInt.h,v 1.42 2009/02/03 16:51:25 danielk1977 Exp $
+** $Id: btreeInt.h,v 1.43 2009/03/16 13:19:36 danielk1977 Exp $
 **
 ** This file implements a external (disk-based) database using BTrees.
 ** For a detailed discussion of BTrees, refer to
@@ -356,6 +356,24 @@ struct Btree {
 ** may not be modified once it is initially set as long as nRef>0.
 ** The pSchema field may be set once under BtShared.mutex and
 ** thereafter is unchanged as long as nRef>0.
+**
+** isPending:
+**
+**   If a BtShared client fails to obtain a write-lock on a database
+**   table (because there exists one or more read-locks on the table),
+**   the shared-cache enters 'pending-lock' state and isPending is
+**   set to true.
+**
+**   The shared-cache leaves the 'pending lock' state when either of
+**   the following occur:
+**
+**     1) The current writer (BtShared.pWriter) concludes its transaction, OR
+**     2) The number of locks held by other connections drops to zero.
+**
+**   while in the 'pending-lock' state, no connection may start a new
+**   transaction.
+**
+**   This feature is included to help prevent writer-starvation.
 */
 struct BtShared {
   Pager *pPager;        /* The page cache */
@@ -385,7 +403,9 @@ struct BtShared {
   int nRef;             /* Number of references to this structure */
   BtShared *pNext;      /* Next on a list of sharable BtShared structs */
   BtLock *pLock;        /* List of locks held on this shared-btree struct */
-  Btree *pExclusive;    /* Btree with an EXCLUSIVE lock on the whole db */
+  Btree *pWriter;       /* Btree with currently open write transaction */
+  u8 isExclusive;       /* True if pWriter has an EXCLUSIVE lock on the db */
+  u8 isPending;         /* If waiting for read-locks to clear */
 #endif
   u8 *pTmpSpace;        /* BtShared.pageSize bytes of space for tmp use */
 };
index e4f92e8f0286093f8d816672c5d0c97d5cf67233..9f900cc891784c86882fd6cd0817a0f2a88580b8 100644 (file)
@@ -14,7 +14,7 @@
 ** other files are for internal use by SQLite and should not be
 ** accessed by users of the library.
 **
-** $Id: main.c,v 1.530 2009/02/26 07:15:59 danielk1977 Exp $
+** $Id: main.c,v 1.531 2009/03/16 13:19:36 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 
@@ -629,6 +629,12 @@ int sqlite3_close(sqlite3 *db){
     }
   }
   sqlite3ResetInternalSchema(db, 0);
+
+  /* Tell the code in notify.c that the connection no longer holds any
+  ** locks and does not require any further unlock-notify callbacks.
+  */
+  sqlite3ConnectionClosed(db);
+
   assert( db->nDb<=2 );
   assert( db->aDb==db->aDbStatic );
   for(j=0; j<ArraySize(db->aFunc.a); j++){
diff --git a/src/notify.c b/src/notify.c
new file mode 100644 (file)
index 0000000..c4babbd
--- /dev/null
@@ -0,0 +1,310 @@
+/*
+** 2009 March 3
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**    May you do good and not evil.
+**    May you find forgiveness for yourself and forgive others.
+**    May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file contains the implementation of the sqlite3_unlock_notify()
+** API method and its associated functionality.
+**
+** $Id: notify.c,v 1.1 2009/03/16 13:19:36 danielk1977 Exp $
+*/
+#include "sqliteInt.h"
+#include "btreeInt.h"
+
+/* Omit this entire file if SQLITE_ENABLE_UNLOCK_NOTIFY is not defined. */
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+
+/*
+** Public interfaces:
+**
+**   sqlite3ConnectionBlocked()
+**   sqlite3ConnectionUnlocked()
+**   sqlite3ConnectionClosed()
+**   sqlite3_unlock_notify()
+*/
+
+#define assertMutexHeld() \
+  assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)) )
+
+/*
+** Head of a linked list of all sqlite3 objects created by this process
+** for which either sqlite3.pBlockingConnection or sqlite3.pUnlockConnection
+** is not NULL. This variable may only accessed while the STATIC_MASTER
+** mutex is held.
+*/
+static sqlite3 *SQLITE_WSD sqlite3BlockedList = 0;
+
+#ifndef NDEBUG
+/*
+** This function is a complex assert() that verifies the following 
+** properties of the blocked connections list:
+**
+**   1) Each entry in the list has a non-NULL value for either 
+**      pUnlockConnection or pBlockingConnection, or both.
+**
+**   2) All entries in the list that share a common value for 
+**      xUnlockNotify are grouped together.
+**
+**   3) If the argument db is not NULL, then none of the entries in the
+**      blocked connections list have pUnlockConnection or pBlockingConnection
+**      set to db. This is used when closing connection db.
+*/
+static void checkListProperties(sqlite3 *db){
+  sqlite3 *p;
+  for(p=sqlite3BlockedList; p; p=p->pNextBlocked){
+    int seen = 0;
+    sqlite3 *p2;
+
+    /* Verify property (1) */
+    assert( p->pUnlockConnection || p->pBlockingConnection );
+
+    /* Verify property (2) */
+    for(p2=sqlite3BlockedList; p2!=p; p2=p2->pNextBlocked){
+      if( p2->xUnlockNotify==p->xUnlockNotify ) seen = 1;
+      assert( p2->xUnlockNotify==p->xUnlockNotify || !seen );
+      assert( db==0 || p->pUnlockConnection!=db );
+      assert( db==0 || p->pBlockingConnection!=db );
+    }
+  }
+}
+#else
+# define checkListProperties(x)
+#endif
+
+/*
+** Remove connection db from the blocked connections list. If connection
+** db is not currently a part of the list, this function is a no-op.
+*/
+static void removeFromBlockedList(sqlite3 *db){
+  sqlite3 **pp;
+  assertMutexHeld();
+  for(pp=&sqlite3BlockedList; *pp; pp = &(*pp)->pNextBlocked){
+    if( *pp==db ){
+      *pp = (*pp)->pNextBlocked;
+      break;
+    }
+  }
+}
+
+/*
+** Add connection db to the blocked connections list. It is assumed
+** that it is not already a part of the list.
+*/
+static void addToBlockedList(sqlite3 *db){
+  sqlite3 **pp;
+  assertMutexHeld();
+  for(
+    pp=&sqlite3BlockedList; 
+    *pp && (*pp)->xUnlockNotify!=db->xUnlockNotify; 
+    pp=&(*pp)->pNextBlocked
+  );
+  db->pNextBlocked = *pp;
+  *pp = db;
+}
+
+/*
+** Obtain the STATIC_MASTER mutex.
+*/
+static void enterMutex(){
+  sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+  checkListProperties(0);
+}
+
+/*
+** Release the STATIC_MASTER mutex.
+*/
+static void leaveMutex(){
+  assertMutexHeld();
+  checkListProperties(0);
+  sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+}
+
+/*
+** Register an unlock-notify callback.
+*/
+int sqlite3_unlock_notify(
+  sqlite3 *db,
+  void (*xNotify)(void **, int),
+  void *pArg
+){
+  int rc = SQLITE_OK;
+
+  sqlite3_mutex_enter(db->mutex);
+  enterMutex();
+
+  if( 0==db->pBlockingConnection ){
+    /* The blocking transaction has been concluded. Or there never was a 
+    ** blocking transaction. In either case, invoke the notify callback
+    ** immediately. 
+    */
+    xNotify(&pArg, 1);
+  }else{
+    sqlite3 *p;
+
+    for(p=db->pBlockingConnection; p && p!=db; p=p->pUnlockConnection);
+    if( p ){
+      rc = SQLITE_LOCKED;              /* Deadlock detected. */
+    }else{
+      db->pUnlockConnection = db->pBlockingConnection;
+      db->xUnlockNotify = xNotify;
+      db->pUnlockArg = pArg;
+      removeFromBlockedList(db);
+      addToBlockedList(db);
+    }
+  }
+
+  leaveMutex();
+  assert( !db->mallocFailed );
+  sqlite3Error(db, rc, (rc?"database is deadlocked":0));
+  sqlite3_mutex_leave(db->mutex);
+  return rc;
+}
+
+/*
+** This function is called while stepping or preparing a statement 
+** associated with connection db. The operation will return SQLITE_LOCKED
+** to the user because it requires a lock that will not be available
+** until connection pBlocker concludes its current transaction.
+*/
+void sqlite3ConnectionBlocked(sqlite3 *db, sqlite3 *pBlocker){
+  enterMutex();
+  if( db->pBlockingConnection==0 && db->pUnlockConnection==0 ){
+    addToBlockedList(db);
+  }
+  db->pBlockingConnection = pBlocker;
+  leaveMutex();
+}
+
+/*
+** The transaction opened by database db has just finished. Locks held 
+** by database connection db have been released.
+**
+** This function loops through each entry in the blocked connections
+** list and does the following:
+**
+**   1) If the sqlite3.pBlockingConnection member of a list entry is
+**      set to db, then set pBlockingConnection=0.
+**
+**   2) If the sqlite3.pUnlockConnection member of a list entry is
+**      set to db, then invoke the configured unlock-notify callback and
+**      set pUnlockConnection=0.
+**
+**   3) If the two steps above mean that pBlockingConnection==0 and
+**      pUnlockConnection==0, remove the entry from the blocked connections
+**      list.
+*/
+void sqlite3ConnectionUnlocked(sqlite3 *db){
+  void (*xUnlockNotify)(void **, int) = 0; /* Unlock-notify cb to invoke */
+  int nArg = 0;                            /* Number of entries in aArg[] */
+  sqlite3 **pp;                            /* Iterator variable */
+
+  void *aStatic[16];
+  void **aArg = aStatic;
+  void **aDyn = 0;
+
+  enterMutex();         /* Enter STATIC_MASTER mutex */
+
+  /* This loop runs once for each entry in the blocked-connections list. */
+  for(pp=&sqlite3BlockedList; *pp; /* no-op */ ){
+    sqlite3 *p = *pp;
+
+    /* Step 1. */
+    if( p->pBlockingConnection==db ){
+      p->pBlockingConnection = 0;
+    }
+
+    /* Step 2. */
+    if( p->pUnlockConnection==db ){
+      assert( p->xUnlockNotify );
+      if( p->xUnlockNotify!=xUnlockNotify && nArg!=0 ){
+        xUnlockNotify(aArg, nArg);
+        nArg = 0;
+      }
+
+      sqlite3BeginBenignMalloc();
+      assert( aArg==aDyn || (aDyn==0 && aArg==aStatic) );
+      assert( nArg<=ArraySize(aStatic) || aArg==aDyn );
+      if( (!aDyn && nArg==ArraySize(aStatic))
+       || (aDyn && nArg==(sqlite3DbMallocSize(db, aDyn)/sizeof(void*)))
+      ){
+        /* The aArg[] array needs to grow. */
+        void **pNew = (void **)sqlite3Malloc(nArg*sizeof(void *)*2);
+        if( pNew ){
+          memcpy(pNew, aArg, nArg*sizeof(void *));
+          sqlite3_free(aDyn);
+          aDyn = aArg = pNew;
+        }else{
+          /* This occurs when the array of context pointers that need to
+          ** be passed to the unlock-notify callback is larger than the
+          ** aStatic[] array allocated on the stack and the attempt to 
+          ** allocate a larger array from the heap has failed.
+          **
+          ** This is a difficult situation to handle. Returning an error
+          ** code to the caller is insufficient, as even if an error code
+          ** is returned the transaction on connection db will still be
+          ** closed and the unlock-notify callbacks on blocked connections
+          ** will go unissued. This might cause the application to wait
+          ** indefinitely for an unlock-notify callback that will never 
+          ** arrive.
+          **
+          ** Instead, invoke the unlock-notify callback with the context
+          ** array already accumulated. We can then clear the array and
+          ** begin accumulating any further context pointers without 
+          ** requiring any dynamic allocation. This is sub-optimal because
+          ** it means that instead of one callback with a large array of
+          ** context pointers the application will receive two or more
+          ** callbacks with smaller arrays of context pointers, which will
+          ** reduce the applications ability to prioritize multiple 
+          ** connections. But it is the best that can be done under the
+          ** circumstances.
+          */
+          xUnlockNotify(aArg, nArg);
+          nArg = 0;
+        }
+      }
+      sqlite3EndBenignMalloc();
+
+      aArg[nArg++] = p->pUnlockArg;
+      xUnlockNotify = p->xUnlockNotify;
+      p->pUnlockConnection = 0;
+      p->xUnlockNotify = 0;
+      p->pUnlockArg = 0;
+    }
+
+    /* Step 3. */
+    if( p->pBlockingConnection==0 && p->pUnlockConnection==0 ){
+      /* Remove connection p from the blocked connections list. */
+      *pp = p->pNextBlocked;
+      p->pNextBlocked = 0;
+    }else{
+      pp = &p->pNextBlocked;
+    }
+  }
+
+  if( nArg!=0 ){
+    xUnlockNotify(aArg, nArg);
+  }
+  sqlite3_free(aDyn);
+  leaveMutex();         /* Leave STATIC_MASTER mutex */
+}
+
+/*
+** This is called when the database connection passed as an argument is 
+** being closed. The connection is removed from the blocked list.
+*/
+void sqlite3ConnectionClosed(sqlite3 *db){
+  sqlite3ConnectionUnlocked(db);
+  enterMutex();
+  removeFromBlockedList(db);
+  checkListProperties(db);
+  leaveMutex();
+}
+#endif
+
index 72f8a61f9cd69916d9e1187a2d2258915906bf30..0142af557f148369acd99905754f5315c8324fc6 100644 (file)
@@ -13,7 +13,7 @@
 ** interface, and routines that contribute to loading the database schema
 ** from disk.
 **
-** $Id: prepare.c,v 1.108 2009/03/05 04:20:32 shane Exp $
+** $Id: prepare.c,v 1.109 2009/03/16 13:19:36 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 
@@ -570,10 +570,10 @@ static int sqlite3Prepare(
       rc = sqlite3BtreeSchemaLocked(pBt);
       if( rc ){
         const char *zDb = db->aDb[i].zName;
-        sqlite3Error(db, SQLITE_LOCKED, "database schema is locked: %s", zDb);
+        sqlite3Error(db, rc, "database schema is locked: %s", zDb);
         (void)sqlite3SafetyOff(db);
         testcase( db->flags & SQLITE_ReadUncommitted );
-        return sqlite3ApiExit(db, SQLITE_LOCKED);
+        return sqlite3ApiExit(db, rc);
       }
     }
   }
index 1b954d5f32f17b7cfd0613befcab802c0e3fa87a..9e58b6290bfc47a5b356fbad12a01542551cada3 100644 (file)
@@ -30,7 +30,7 @@
 ** the version number) and changes its name to "sqlite3.h" as
 ** part of the build process.
 **
-** @(#) $Id: sqlite.h.in,v 1.433 2009/02/18 18:37:59 drh Exp $
+** @(#) $Id: sqlite.h.in,v 1.434 2009/03/16 13:19:36 danielk1977 Exp $
 */
 #ifndef _SQLITE3_H_
 #define _SQLITE3_H_
@@ -383,6 +383,8 @@ int sqlite3_exec(
 #define SQLITE_IOERR_CLOSE             (SQLITE_IOERR | (16<<8))
 #define SQLITE_IOERR_DIR_CLOSE         (SQLITE_IOERR | (17<<8))
 
+#define SQLITE_LOCKED_SHAREDCACHE      (SQLITE_LOCKED | (1<<8) )
+
 /*
 ** CAPI3REF: Flags For File Open Operations {H10230} <H11120> <H12700>
 **
@@ -5349,6 +5351,127 @@ int sqlite3_backup_finish(sqlite3_backup *p);
 int sqlite3_backup_remaining(sqlite3_backup *p);
 int sqlite3_backup_pagecount(sqlite3_backup *p);
 
+/*
+** CAPI3REF: Unlock Notification
+** EXPERIMENTAL
+**
+** When running in shared-cache mode, a database operation may fail with
+** an SQLITE_LOCKED error if the required locks on the shared-cache or
+** individual tables within the shared-cache cannot be obtained. See
+** [SQLite Shared-Cache Mode] for a description of shared-cache locking. 
+** This API may be used to register a callback that SQLite will invoke 
+** when the connection currently holding the required lock relinquishes it.
+** This API is only available if the library was compiled with the
+** SQLITE_ENABLE_UNLOCK_NOTIFY C-preprocessor symbol defined.
+**
+** See Also: [Using the SQLite Unlock Notification Feature].
+**
+** Shared-cache locks are released when a database connection concludes
+** its current transaction, either by committing it or rolling it back. 
+**
+** When a connection (known as the blocked connection) fails to obtain a
+** shared-cache lock and SQLITE_LOCKED is returned to the caller, the
+** identity of the database connection (the blocking connection) that
+** has locked the required resource is stored internally. After an 
+** application receives an SQLITE_LOCKED error, it may call the
+** sqlite3_unlock_notify() method with the blocked connection handle as 
+** the first argument to register for a callback that will be invoked
+** when the blocking connections current transaction is concluded. The
+** callback is invoked from within the [sqlite3_step] or [sqlite3_close]
+** call that concludes the blocking connections transaction.
+**
+** If sqlite3_unlock_notify() is called in a multi-threaded application,
+** there is a chance that the blocking connection will have already
+** concluded its transaction by the time sqlite3_unlock_notify() is invoked.
+** If this happens, then the specified callback is invoked immediately,
+** from within the call to sqlite3_unlock_notify().
+**
+** If the blocked connection is attempting to obtain a write-lock on a
+** shared-cache table, and more than one other connection currently holds
+** a read-lock on the same table, then SQLite arbitrarily selects one of 
+** the other connections to use as the blocking connection.
+**
+** There may be at most one unlock-notify callback registered by a 
+** blocked connection. If sqlite3_unlock_notify() is called when the
+** blocked connection already has a registered unlock-notify callback,
+** then the new callback replaces the old. If sqlite3_unlock_notify() is
+** called with a NULL pointer as its second argument, then any existing
+** unlock-notify callback is cancelled. The blocked connections 
+** unlock-notify callback may also be canceled by closing the blocked
+** connection using [sqlite3_close()].
+**
+** The unlock-notify callback is not reentrant. If an application invokes
+** any sqlite3_xxx API functions from within an unlock-notify callback, a
+** crash or deadlock may be the result.
+**
+** Unless deadlock is detected (see below), sqlite3_unlock_notify() always
+** returns SQLITE_OK.
+**
+** <b>Callback Invocation Details</b>
+**
+** When an unlock-notify callback is registered, the application provides a 
+** single void* pointer that is passed to the callback when it is invoked.
+** However, the signature of the callback function allows SQLite to pass
+** it an array of void* context pointers. The first argument passed to
+** an unlock-notify callback is a pointer to an array of void* pointers,
+** and the second is the number of entries in the array.
+**
+** When a blocking connections transaction is concluded, there may be
+** more than one blocked connection that has registered for an unlock-notify
+** callback. If two or more such blocked connections have specified the
+** same callback function, then instead of invoking the callback function
+** multiple times, it is invoked once with the set of void* context pointers
+** specified by the blocked connections bundled together into an array.
+** This gives the application an opportunity to prioritize any actions 
+** related to the set of unblocked database connections.
+**
+** <b>Deadlock Detection</b>
+**
+** Assuming that after registering for an unlock-notify callback a 
+** database waits for the callback to be issued before taking any further
+** action (a reasonable assumption), then using this API may cause the
+** application to deadlock. For example, if connection X is waiting for
+** connection Y's transaction to be concluded, and similarly connection
+** Y is waiting on connection X's transaction, then neither connection
+** will proceed and the system may remain deadlocked indefinitely.
+**
+** To avoid this scenario, the sqlite3_unlock_notify() performs deadlock
+** detection. If a given call to sqlite3_unlock_notify() would put the
+** system in a deadlocked state, then SQLITE_LOCKED is returned and no
+** unlock-notify callback is registered. The system is said to be in
+** a deadlocked state if connection A has registered for an unlock-notify
+** callback on the conclusion of connection B's transaction, and connection
+** B has itself registered for an unlock-notify callback when connection
+** A's transaction is concluded. Indirect deadlock is also detected, so
+** the system is also considered to be deadlocked if connection B has
+** registered for an unlock-notify callback on the conclusion of connection
+** C's transaction, where connection C is waiting on connection A. Any
+** number of levels of indirection are allowed.
+**
+** <b>The "DROP TABLE" Exception</b>
+**
+** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost 
+** always appropriate to call sqlite3_unlock_notify(). There is however,
+** one exception. When executing a "DROP TABLE" or "DROP INDEX" statement,
+** SQLite checks if there are any currently executing SELECT statements
+** that belong to the same connection. If there are, SQLITE_LOCKED is
+** returned. In this case there is no "blocking connection", so invoking
+** sqlite3_unlock_notify() results in the unlock-notify callback being
+** invoked immediately. If the application then re-attempts the "DROP TABLE"
+** or "DROP INDEX" query, an infinite loop might be the result.
+**
+** One way around this problem is to check the extended error code returned
+** by an sqlite3_step() call. If there is a blocking connection, then the
+** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in
+** the special "DROP TABLE/INDEX" case, the extended error code is just 
+** SQLITE_LOCKED.
+*/
+int sqlite3_unlock_notify(
+  sqlite3 *pBlocked,                          /* Waiting connection */
+  void (*xNotify)(void **apArg, int nArg),    /* Callback function to invoke */
+  void *pNotifyArg                            /* Argument to pass to xNotify */
+);
+
 /*
 ** Undo the hack that converts floating point types to integer for
 ** builds on processors without floating point support.
index 2726a46759893b3ad714752da2141e6af0c6f201..fafd5f8466f99a651ed31080a222e18d8e531356 100644 (file)
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.840 2009/03/02 17:18:48 shane Exp $
+** @(#) $Id: sqliteInt.h,v 1.841 2009/03/16 13:19:36 danielk1977 Exp $
 */
 #ifndef _SQLITEINT_H_
 #define _SQLITEINT_H_
@@ -808,6 +808,17 @@ struct sqlite3 {
   Savepoint *pSavepoint;        /* List of active savepoints */
   int nSavepoint;               /* Number of non-transaction savepoints */
   u8 isTransactionSavepoint;    /* True if the outermost savepoint is a TS */
+
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+  /* The following variables are all protected by the STATIC_MASTER 
+  ** mutex, not by sqlite3.mutex. They are used by code in notify.c. 
+  */
+  sqlite3 *pBlockingConnection; /* Connection that caused SQLITE_LOCKED */
+  sqlite3 *pUnlockConnection;           /* Connection to watch for unlock */
+  void *pUnlockArg;                     /* Argument to xUnlockNotify */
+  void (*xUnlockNotify)(void **, int);  /* Unlock notify callback */
+  sqlite3 *pNextBlocked;        /* Next in list of all blocked connections */
+#endif
 };
 
 /*
@@ -2749,6 +2760,17 @@ int sqlite3IsMemJournal(sqlite3_file *);
 u32 sqlite3Get4byte(const u8*);
 void sqlite3Put4byte(u8*, u32);
 
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+  void sqlite3ConnectionBlocked(sqlite3 *, sqlite3 *);
+  void sqlite3ConnectionUnlocked(sqlite3 *db);
+  void sqlite3ConnectionClosed(sqlite3 *db);
+#else
+  #define sqlite3ConnectionBlocked(x,y)
+  #define sqlite3ConnectionUnlocked(x)
+  #define sqlite3ConnectionClosed(x)
+#endif
+
+
 #ifdef SQLITE_SSE
 #include "sseInt.h"
 #endif
index 585360f56850183ef50c7fd0ac3deceba17b0e0b..a429662d89f0eadf63e562ecf21408c08498a049 100644 (file)
@@ -12,7 +12,7 @@
 ** A TCL Interface to SQLite.  Append this file to sqlite3.c and
 ** compile the whole thing to build a TCL-enabled version of SQLite.
 **
-** $Id: tclsqlite.c,v 1.237 2009/02/17 16:29:11 danielk1977 Exp $
+** $Id: tclsqlite.c,v 1.238 2009/03/16 13:19:36 danielk1977 Exp $
 */
 #include "tcl.h"
 #include <errno.h>
@@ -109,6 +109,7 @@ struct SqliteDb {
   SqlFunc *pFunc;            /* List of SQL functions */
   Tcl_Obj *pUpdateHook;      /* Update hook script (if any) */
   Tcl_Obj *pRollbackHook;    /* Rollback hook script (if any) */
+  Tcl_Obj *pUnlockNotify;    /* Unlock notify script (if any) */
   SqlCollate *pCollate;      /* List of SQL collation functions */
   int rc;                    /* Return code of most recent sqlite3_exec() */
   Tcl_Obj *pCollateNeeded;   /* Collation needed script */
@@ -574,6 +575,31 @@ static void DbRollbackHandler(void *clientData){
   }
 }
 
+#ifdef SQLITE_TEST
+static void setTestUnlockNotifyVars(Tcl_Interp *interp, int iArg, int nArg){
+  char zBuf[64];
+  sprintf(zBuf, "%d", iArg);
+  Tcl_SetVar(interp, "sqlite_unlock_notify_arg", zBuf, TCL_GLOBAL_ONLY);
+  sprintf(zBuf, "%d", nArg);
+  Tcl_SetVar(interp, "sqlite_unlock_notify_argcount", zBuf, TCL_GLOBAL_ONLY);
+}
+#else
+  #define setTestUnlockNotifyVars(x,y,z)
+#endif
+
+static void DbUnlockNotify(void **apArg, int nArg){
+  int i;
+  for(i=0; i<nArg; i++){
+    const int flags = (TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT);
+    SqliteDb *pDb = (SqliteDb *)apArg[i];
+    setTestUnlockNotifyVars(pDb->interp, i, nArg);
+    assert( pDb->pUnlockNotify);
+    Tcl_EvalObjEx(pDb->interp, pDb->pUnlockNotify, flags);
+    Tcl_DecrRefCount(pDb->pUnlockNotify);
+    pDb->pUnlockNotify = 0;
+  }
+}
+
 static void DbUpdateHandler(
   void *p, 
   int op,
@@ -993,8 +1019,8 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
     "profile",            "progress",          "rekey",
     "restore",            "rollback_hook",     "status",
     "timeout",            "total_changes",     "trace",
-    "transaction",        "update_hook",       "version",
-    0                    
+    "transaction",        "unlock_notify",     "update_hook",
+    "version",            0                    
   };
   enum DB_enum {
     DB_AUTHORIZER,        DB_BACKUP,           DB_BUSY,
@@ -1007,7 +1033,8 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
     DB_PROFILE,           DB_PROGRESS,         DB_REKEY,
     DB_RESTORE,           DB_ROLLBACK_HOOK,    DB_STATUS,
     DB_TIMEOUT,           DB_TOTAL_CHANGES,    DB_TRACE,
-    DB_TRANSACTION,       DB_UPDATE_HOOK,      DB_VERSION,
+    DB_TRANSACTION,       DB_UNLOCK_NOTIFY,    DB_UPDATE_HOOK,
+    DB_VERSION,
   };
   /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
 
@@ -2450,6 +2477,42 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
     break;
   }
 
+  /*
+  **    $db unlock_notify ?script?
+  */
+  case DB_UNLOCK_NOTIFY: {
+#ifndef SQLITE_ENABLE_UNLOCK_NOTIFY
+    Tcl_AppendResult(interp, "unlock_notify not available in this build", 0);
+    rc = TCL_ERROR;
+#else
+    if( objc!=2 && objc!=3 ){
+      Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
+      rc = TCL_ERROR;
+    }else{
+      void (*xNotify)(void **, int) = 0;
+      void *pNotifyArg = 0;
+
+      if( pDb->pUnlockNotify ){
+        Tcl_DecrRefCount(pDb->pUnlockNotify);
+        pDb->pUnlockNotify = 0;
+      }
+  
+      if( objc==3 ){
+        xNotify = DbUnlockNotify;
+        pNotifyArg = (void *)pDb;
+        pDb->pUnlockNotify = objv[2];
+        Tcl_IncrRefCount(pDb->pUnlockNotify);
+      }
+  
+      if( sqlite3_unlock_notify(pDb->db, xNotify, pNotifyArg) ){
+        Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), 0);
+        rc = TCL_ERROR;
+      }
+    }
+#endif
+    break;
+  }
+
   /*
   **    $db update_hook ?script?
   **    $db rollback_hook ?script?
index 326c67daf365aa2bcef2c3f48a701491cd685e7d..e3e8df14312cf97233110f5f1ac0d2fa9d1d0c44 100644 (file)
@@ -13,7 +13,7 @@
 ** is not included in the SQLite library.  It is used for automated
 ** testing of the SQLite library.
 **
-** $Id: test1.c,v 1.347 2009/02/03 16:51:25 danielk1977 Exp $
+** $Id: test1.c,v 1.348 2009/03/16 13:19:36 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include "tcl.h"
@@ -125,6 +125,7 @@ const char *sqlite3TestErrorName(int rc){
     case SQLITE_ABORT:               zName = "SQLITE_ABORT";             break;
     case SQLITE_BUSY:                zName = "SQLITE_BUSY";              break;
     case SQLITE_LOCKED:              zName = "SQLITE_LOCKED";            break;
+    case SQLITE_LOCKED_SHAREDCACHE:  zName = "SQLITE_LOCKED_SHAREDCACHE";break;
     case SQLITE_NOMEM:               zName = "SQLITE_NOMEM";             break;
     case SQLITE_READONLY:            zName = "SQLITE_READONLY";          break;
     case SQLITE_INTERRUPT:           zName = "SQLITE_INTERRUPT";         break;
@@ -3661,6 +3662,24 @@ static int test_step(
   return TCL_OK;
 }
 
+static int test_sql(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "STMT");
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  Tcl_SetResult(interp, (char *)sqlite3_sql(pStmt), TCL_VOLATILE);
+  return TCL_OK;
+}
+
 /*
 ** Usage: sqlite3_column_count STMT 
 **
@@ -4816,6 +4835,40 @@ static int test_pcache_stats(
   return TCL_OK;
 }
 
+static void test_unlock_notify_cb(void **aArg, int nArg){
+  int ii;
+  for(ii=0; ii<nArg; ii++){
+    Tcl_EvalEx((Tcl_Interp *)aArg[ii], "unlock_notify", -1, TCL_EVAL_GLOBAL);
+  }
+}
+
+/*
+** tclcmd:  sqlite3_unlock_notify db
+*/
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+static int test_unlock_notify(
+  ClientData clientData, /* Unused */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB");
+    return TCL_ERROR;
+  }
+
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+    return TCL_ERROR;
+  }
+  rc = sqlite3_unlock_notify(db, test_unlock_notify_cb, (void *)interp);
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+  return TCL_OK;
+}
+#endif
+
 
 /*
 ** Register commands with the TCL interpreter.
@@ -4916,6 +4969,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
      { "sqlite3_transfer_bindings",     test_transfer_bind ,0 },
      { "sqlite3_changes",               test_changes       ,0 },
      { "sqlite3_step",                  test_step          ,0 },
+     { "sqlite3_sql",                   test_sql           ,0 },
      { "sqlite3_next_stmt",             test_next_stmt     ,0 },
 
      { "sqlite3_release_memory",        test_release_memory,     0},
@@ -5000,6 +5054,9 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
      { "sqlite3_blob_write", test_blob_write, 0  },
 #endif
      { "pcache_stats",       test_pcache_stats, 0  },
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+     { "sqlite3_unlock_notify", test_unlock_notify, 0  },
+#endif
   };
   static int bitmask_size = sizeof(Bitmask)*8;
   int i;
index e3bcf0d7845e8479a2263f4184910174350c0d08..ce809f9436f057d819b6e7a3ef3dfcd913679353 100644 (file)
@@ -16,7 +16,7 @@
 ** The focus of this file is providing the TCL testing layer
 ** access to compile-time constants.
 **
-** $Id: test_config.c,v 1.47 2009/01/12 14:01:45 danielk1977 Exp $
+** $Id: test_config.c,v 1.48 2009/03/16 13:19:36 danielk1977 Exp $
 */
 
 #include "sqliteLimit.h"
@@ -493,6 +493,12 @@ Tcl_SetVar2(interp, "sqlite_options", "long_double",
   Tcl_SetVar2(interp, "sqlite_options", "update_delete_limit", "0", TCL_GLOBAL_ONLY);
 #endif
 
+#if defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+  Tcl_SetVar2(interp, "sqlite_options", "unlock_notify", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "unlock_notify", "0", TCL_GLOBAL_ONLY);
+#endif
+
 #ifdef SQLITE_SECURE_DELETE
   Tcl_SetVar2(interp, "sqlite_options", "secure_delete", "1", TCL_GLOBAL_ONLY);
 #else
index 973ca65592fa4b5582725fd1f6af2276040ed62f..156f896ce8998ceac5559898480e3984b96ad264 100644 (file)
@@ -14,7 +14,7 @@
 ** test that sqlite3 database handles may be concurrently accessed by 
 ** multiple threads. Right now this only works on unix.
 **
-** $Id: test_thread.c,v 1.10 2009/02/03 19:55:20 shane Exp $
+** $Id: test_thread.c,v 1.11 2009/03/16 13:19:36 danielk1977 Exp $
 */
 
 #include "sqliteInt.h"
@@ -55,6 +55,7 @@ struct EvalEvent {
 
 static Tcl_ObjCmdProc sqlthread_proc;
 static Tcl_ObjCmdProc clock_seconds_proc;
+static Tcl_ObjCmdProc blocking_step_proc;
 int Sqlitetest1_Init(Tcl_Interp *);
 
 /*
@@ -106,6 +107,9 @@ static Tcl_ThreadCreateType tclScriptThread(ClientData pSqlThread){
   interp = Tcl_CreateInterp();
   Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0);
   Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, pSqlThread, 0);
+#if defined(OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+  Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0);
+#endif
   Sqlitetest1_Init(interp);
   Sqlitetest_mutex_Init(interp);
 
@@ -359,12 +363,157 @@ static int clock_seconds_proc(
   return TCL_OK;
 }
 
+/*************************************************************************
+** This block contains the implementation of the [sqlite3_blocking_step]
+** command available to threads created by [sqlthread spawn] commands. It
+** is only available on UNIX for now. This is because pthread condition
+** variables are used.
+**
+** The source code for the C functions sqlite3_blocking_step(),
+** blocking_step_notify() and the structure UnlockNotification is
+** automatically extracted from this file and used as part of the
+** documentation for the sqlite3_unlock_notify() API function. This
+** should be considered if these functions are to be extended (i.e. to 
+** support windows) in the future.
+*/ 
+#if defined(OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+
+/* BEGIN_SQLITE_BLOCKING_STEP */
+/* This example uses the pthreads API */
+#include <pthread.h>
+
+/*
+** A pointer to an instance of this structure is passed as the user-context
+** pointer when registering for an unlock-notify callback.
+*/
+typedef struct UnlockNotification UnlockNotification;
+struct UnlockNotification {
+  int fired;                           /* True after unlock event has occured */
+  pthread_cond_t cond;                 /* Condition variable to wait on */
+  pthread_mutex_t mutex;               /* Mutex to protect structure */
+};
+
+/*
+** This function is an unlock-notify callback registered with SQLite.
+*/
+static void blocking_step_notify(void **apArg, int nArg){
+  int i;
+  for(i=0; i<nArg; i++){
+    UnlockNotification *p = (UnlockNotification *)apArg[i];
+    pthread_mutex_lock(&p->mutex);
+    p->fired = 1;
+    pthread_cond_signal(&p->cond);
+    pthread_mutex_unlock(&p->mutex);
+  }
+}
+
+/*
+** This function is a wrapper around the SQLite function sqlite3_step().
+** It functions in the same way as step(), except that if a required
+** shared-cache lock cannot be obtained, this function may block waiting for
+** the lock to become available. In this scenario the normal API step()
+** function always returns SQLITE_LOCKED.
+**
+** If this function returns SQLITE_LOCKED, the caller should rollback
+** the current transaction (if any) and try again later. Otherwise, the
+** system may become deadlocked.
+*/
+int sqlite3_blocking_step(sqlite3_stmt *pStmt){
+  int rc = SQLITE_OK;
+
+  while( rc==SQLITE_OK && SQLITE_LOCKED==(rc = sqlite3_step(pStmt)) ){
+    sqlite3 *db = sqlite3_db_handle(pStmt);
+    UnlockNotification un;
+
+    /* Initialize the UnlockNotification structure. */
+    un.fired = 0;
+    pthread_mutex_init(&un.mutex, 0);
+    pthread_cond_init(&un.cond, 0);
+
+    rc = sqlite3_unlock_notify(db, blocking_step_notify, (void *)&un);
+    assert( rc==SQLITE_LOCKED || rc==SQLITE_OK );
+
+    /* The call to sqlite3_unlock_notify() always returns either 
+    ** SQLITE_LOCKED or SQLITE_OK. 
+    **
+    ** If SQLITE_LOCKED was returned, then the system is deadlocked. In this
+    ** case this function needs to return SQLITE_LOCKED to the caller so 
+    ** that it can roll back the current transaction. Simply leaving rc
+    ** as it is is enough to accomplish that, as the next test of the
+    ** while() condition above will fail and the current value of rc
+    ** (SQLITE_LOCKED) will be returned to the caller. sqlite3_reset() is
+    ** not called on the statement handle, so the caller can still use either
+    ** sqlite3_finalize() or reset() to collect the statement's error code
+    ** after this function returns.
+    **
+    ** Otherwise, if SQLITE_OK was returned, do two things:
+    **
+    **   1) Reset the SQL statement.
+    **   2) Block until the unlock-notify callback is invoked.
+    */
+    if( rc==SQLITE_OK ){
+      sqlite3_reset(pStmt);
+      pthread_mutex_lock(&un.mutex);
+      if( !un.fired ){
+        pthread_cond_wait(&un.cond, &un.mutex);
+      }
+      pthread_mutex_unlock(&un.mutex);
+    }
+
+    /* Destroy the mutex and condition variables created at the top of
+    ** the while loop. */
+    pthread_cond_destroy(&un.cond);
+    pthread_mutex_destroy(&un.mutex);
+  }
+
+  return rc;
+}
+/* END_SQLITE_BLOCKING_STEP */
+
+/*
+** Usage: sqlite3_blocking_step STMT
+**
+** Advance the statement to the next row.
+*/
+static int blocking_step_proc(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  /* Functions from test1.c */
+  void *sqlite3TestTextToPtr(const char *);
+  const char *sqlite3TestErrorName(int);
+
+  sqlite3_stmt *pStmt;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "STMT");
+    return TCL_ERROR;
+  }
+
+  pStmt = (sqlite3_stmt*)sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  rc = sqlite3_blocking_step(pStmt);
+
+  Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), 0);
+  return TCL_OK;
+}
+
+#endif
+/*
+** End of implementation of [sqlite3_blocking_step].
+************************************************************************/
+
 /*
 ** Register commands with the TCL interpreter.
 */
 int SqlitetestThread_Init(Tcl_Interp *interp){
   Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, 0, 0);
   Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0);
+#if defined(OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+  Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0);
+#endif
   return TCL_OK;
 }
 #else
index b5a991baeaf46f039ec885b1797283e6645548ec..26e84e32cf972fcb658e3a1b49d0c07714d48c87 100644 (file)
@@ -43,7 +43,7 @@
 ** in this file for details.  If in doubt, do not deviate from existing
 ** commenting and indentation practices when changing or adding code.
 **
-** $Id: vdbe.c,v 1.824 2009/03/05 04:20:32 shane Exp $
+** $Id: vdbe.c,v 1.825 2009/03/16 13:19:36 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include "vdbeInt.h"
@@ -4819,7 +4819,7 @@ case OP_TableLock: {
   assert( (p->btreeMask & (1<<p1))!=0 );
   assert( isWriteLock==0 || isWriteLock==1 );
   rc = sqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock);
-  if( rc==SQLITE_LOCKED ){
+  if( (rc&0xFF)==SQLITE_LOCKED ){
     const char *z = pOp->p4.z;
     sqlite3SetString(&p->zErrMsg, db, "database table is locked: %s", z);
   }
@@ -4834,8 +4834,8 @@ case OP_TableLock: {
 ** xBegin method for that table.
 **
 ** Also, whether or not P4 is set, check that this is not being called from
-** within a callback to a virtual table xSync() method. If it is, set the
-** error code to SQLITE_LOCKED.
+** within a callback to a virtual table xSync() method. If it is, the error
+** code will be set to SQLITE_LOCKED.
 */
 case OP_VBegin: {
   sqlite3_vtab *pVtab = pOp->p4.pVtab;
index 64cd2f99ea11365880eab1e55a9511a89faef876..42e3f13c72c70c821091344710d99f4ed3061ab9 100644 (file)
@@ -14,7 +14,7 @@
 ** to version 2.8.7, all this code was combined into the vdbe.c source file.
 ** But that file was getting too big so this subroutines were split out.
 **
-** $Id: vdbeaux.c,v 1.441 2009/03/05 04:20:32 shane Exp $
+** $Id: vdbeaux.c,v 1.442 2009/03/16 13:19:36 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include "vdbeInt.h"
@@ -1744,6 +1744,14 @@ int sqlite3VdbeHalt(Vdbe *p){
     p->rc = SQLITE_NOMEM;
   }
 
+  /* If the auto-commit flag is set to true, then any locks that were held
+  ** by connection db have now been released. Call sqlite3ConnectionUnlocked() 
+  ** to invoke any required unlock-notify callbacks.
+  */
+  if( db->autoCommit ){
+    sqlite3ConnectionUnlocked(db);
+  }
+
   return SQLITE_OK;
 }
 
index 83c4a3395081ced827252a38bfced30921f0c12e..4bf67850388574920118a29fe46fa2cc160d90f2 100644 (file)
@@ -11,7 +11,7 @@
 *************************************************************************
 ** This file contains code used to help implement virtual tables.
 **
-** $Id: vtab.c,v 1.81 2008/12/10 19:26:24 drh Exp $
+** $Id: vtab.c,v 1.82 2009/03/16 13:19:36 danielk1977 Exp $
 */
 #ifndef SQLITE_OMIT_VIRTUALTABLE
 #include "sqliteInt.h"
@@ -711,7 +711,7 @@ int sqlite3VtabBegin(sqlite3 *db, sqlite3_vtab *pVtab){
   /* Special case: If db->aVTrans is NULL and db->nVTrans is greater
   ** than zero, then this function is being called from within a
   ** virtual module xSync() callback. It is illegal to write to 
-  ** virtual module tables in this case, so return SQLITE_LOCKED.
+  ** virtual module tables in this case, so return SQLITE_MISUSE.
   */
   if( sqlite3VtabInSync(db) ){
     return SQLITE_LOCKED;
index 61e57b75f6d595da28d3f00172bd72f57d6b923a..24ba6996c814b8ea08f0e2bf408e990085a7aafa 100644 (file)
@@ -11,7 +11,7 @@
 # This file implements regression tests for SQLite library.  The
 # focus of this file is testing the sqlite3_backup_XXX API.
 #
-# $Id: backup.test,v 1.8 2009/02/12 17:01:50 danielk1977 Exp $
+# $Id: backup.test,v 1.9 2009/03/16 13:19:36 danielk1977 Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -736,7 +736,7 @@ do_test backup-7.2.1 {
 } {}
 do_test backup-7.2.2 {
   B step 5000
-} {SQLITE_LOCKED}
+} {SQLITE_BUSY}
 do_test backup-7.2.3 {
   execsql { ROLLBACK }
   B step 5000
index 49e67502dad04a4e6e344d9cf9c8b838c625c2a3..5f7c1d432ca08b44c4c3b067878cf522d48c1460 100644 (file)
@@ -12,7 +12,7 @@
 # Test that it is possible to have two open blob handles on a single
 # blob object.
 #
-# $Id: incrblob2.test,v 1.9 2008/10/03 08:44:54 danielk1977 Exp $
+# $Id: incrblob2.test,v 1.10 2009/03/16 13:19:36 danielk1977 Exp $
 #
 
 set testdir [file dirname $argv0]
@@ -265,7 +265,7 @@ ifcapable shared_cache {
   do_test incrblob2-5.3 {
     set blob [db incrblob t1 data 1]
     catchsql { INSERT INTO t1 VALUES(3, 'klmno') } db2
-  } {1 {database is locked}}
+  } {1 {database table is locked}}
 
   do_test incrblob2-5.4 {
     close $blob
diff --git a/test/notify1.test b/test/notify1.test
new file mode 100644 (file)
index 0000000..4869bc4
--- /dev/null
@@ -0,0 +1,470 @@
+# 2009 March 04
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this file is testing the sqlite3_unlock_notify() API.
+#
+# $Id: notify1.test,v 1.1 2009/03/16 13:19:36 danielk1977 Exp $
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+ifcapable !unlock_notify||!shared_cache {
+  finish_test
+  return
+}
+db close
+set ::enable_shared_cache [sqlite3_enable_shared_cache 1]
+
+#-------------------------------------------------------------------------
+# Warm body test. Test that an unlock-notify callback can be registered 
+# and that it is invoked.
+#
+do_test notify1-1.1 {
+  sqlite3 db test.db
+  sqlite3 db2 test.db
+  execsql { CREATE TABLE t1(a, b) }
+} {}
+do_test notify1-1.2 {
+  execsql {
+    BEGIN;
+    INSERT INTO t1 VALUES(1, 2);
+  }
+  catchsql { INSERT INTO t1 VALUES(3, 4) } db2
+} {1 {database table is locked}}
+do_test notify1-1.3 {
+  set zScript ""
+  db2 unlock_notify {
+    set zScript "db2 eval { INSERT INTO t1 VALUES(3, 4) }"
+  }
+  execsql { SELECT * FROM t1 }
+} {1 2}
+do_test notify1-1.4 {
+  set zScript
+} {}
+do_test notify1-1.5 {
+  execsql { COMMIT }
+  eval $zScript
+  execsql { SELECT * FROM t1 }
+} {1 2 3 4}
+
+#-------------------------------------------------------------------------
+# The following tests, notify1-2.*, test that deadlock is detected 
+# correctly.
+# 
+do_test notify1-2.1 {
+  execsql { 
+    CREATE TABLE t2(a, b);
+    INSERT INTO t2 VALUES('I', 'II');
+  }
+} {}
+
+#
+# Test for simple deadlock involving two database connections.
+#
+# 1. Grab a write-lock on t1 with [db]. Then grab a read-lock on t2 with [db2].
+# 2. Try to grab a read-lock on t1 with [db2] (fails).
+# 3. Have [db2] wait on the read-lock it failed to obtain in step 2.
+# 4. Try to grab a write-lock on t2 with [db] (fails).
+# 5. Try to have [db] wait on the lock from step 4. Fails, as the system
+#    would be deadlocked (since [db2] is already waiting on [db], and this
+#    operation would have [db] wait on [db2]).
+#
+do_test notify1-2.2.1 {
+  execsql {
+    BEGIN;
+    INSERT INTO t1 VALUES(5, 6);
+  }
+  execsql {
+    BEGIN;
+    SELECT * FROM t2;
+  } db2
+} {I II}
+do_test notify1-2.2.2 {
+  catchsql { SELECT * FROM t1 } db2
+} {1 {database table is locked: t1}}
+do_test notify1-2.2.3 {
+  db2 unlock_notify {lappend unlock_notify db2}
+} {}
+do_test notify1-2.2.4 {
+  catchsql { INSERT INTO t2 VALUES('III', 'IV') }
+} {1 {database table is locked: t2}}
+do_test notify1-2.2.5 {
+  set rc [catch { db unlock_notify {lappend unlock_notify db} } msg]
+  list $rc $msg
+} {1 {database is deadlocked}}
+
+#
+# Test for slightly more complex deadlock involving three database
+# connections: db, db2 and db3.
+#
+do_test notify1-2.3.1 {
+  db close
+  db2 close
+  file delete -force test.db test2.db test3.db
+  foreach con {db db2 db3} {
+    sqlite3 $con test.db
+    $con eval { ATTACH 'test2.db' AS aux2 }
+    $con eval { ATTACH 'test3.db' AS aux3 }
+  }
+  execsql {
+    CREATE TABLE main.t1(a, b);
+    CREATE TABLE aux2.t2(a, b);
+    CREATE TABLE aux3.t3(a, b);
+  }
+} {}
+do_test notify1-2.3.2 {
+  execsql { BEGIN ; INSERT INTO t1 VALUES(1, 2) } db
+  execsql { BEGIN ; INSERT INTO t2 VALUES(1, 2) } db2
+  execsql { BEGIN ; INSERT INTO t3 VALUES(1, 2) } db3
+} {}
+do_test notify1-2.3.3 {
+  catchsql { SELECT * FROM t2 } db
+} {1 {database table is locked: t2}}
+do_test notify1-2.3.4 {
+  catchsql { SELECT * FROM t3 } db2
+} {1 {database table is locked: t3}}
+do_test notify1-2.3.5 {
+  catchsql { SELECT * FROM t1 } db3
+} {1 {database table is locked: t1}}
+do_test notify1-2.3.6 {
+  set lUnlock [list]
+  db  unlock_notify {lappend lUnlock db}
+  db2 unlock_notify {lappend lUnlock db2}
+} {}
+do_test notify1-2.3.7 {
+  set rc [catch { db3 unlock_notify {lappend lUnlock db3} } msg]
+  list $rc $msg
+} {1 {database is deadlocked}}
+do_test notify1-2.3.8 {
+  execsql { COMMIT }
+  set lUnlock
+} {}
+do_test notify1-2.3.9 {
+  db3 unlock_notify {lappend lUnlock db3} 
+  set lUnlock
+} {db3}
+do_test notify1-2.3.10 {
+  execsql { COMMIT } db2
+  set lUnlock
+} {db3 db}
+do_test notify1-2.3.11 {
+  execsql { COMMIT } db3
+  set lUnlock
+} {db3 db db2}
+catch { db3 close }
+catch { db2 close }
+catch { db close }
+
+#-------------------------------------------------------------------------
+# The following tests, notify1-3.* and notify1-4.*, test that callbacks 
+# can be issued when there are many (>16) connections waiting on a single 
+# unlock event.
+# 
+foreach {tn nConn} {3 20 4 76} {
+  do_test notify1-$tn.1 {
+    sqlite3 db test.db
+    execsql {
+      BEGIN;
+      INSERT INTO t1 VALUES('a', 'b');
+    }
+  } {}
+  set lUnlock [list]
+  set lUnlockFinal [list]
+  for {set ii 1} {$ii <= $nConn} {incr ii} {
+    do_test notify1-$tn.2.$ii.1 {
+      set cmd "db$ii"
+      sqlite3 $cmd test.db
+      catchsql { SELECT * FROM t1 } $cmd
+    } {1 {database table is locked: t1}}
+    do_test notify1-$tn.2.$ii.2 {
+      $cmd unlock_notify "lappend lUnlock $ii"
+    } {}
+    lappend lUnlockFinal $ii
+  }
+  do_test notify1-$tn.3 {
+    set lUnlock
+  } {}
+  do_test notify1-$tn.4 {
+    execsql {COMMIT}
+    lsort -integer $lUnlock
+  } $lUnlockFinal
+  do_test notify1-$tn.5 {
+    for {set ii 1} {$ii <= $nConn} {incr ii} {
+      "db$ii" close
+    }
+  } {}
+}
+db close
+
+#-------------------------------------------------------------------------
+# These tests, notify1-5.*, test that a malloc() failure that occurs while
+# allocating an array to use as an argument to an unlock-notify callback
+# is handled correctly.
+# 
+source $testdir/malloc_common.tcl
+breakpoint
+do_malloc_test notify1-5 -tclprep {
+  set ::lUnlock [list]
+  execsql {
+    CREATE TABLE t1(a, b);
+    BEGIN;
+    INSERT INTO t1 VALUES('a', 'b');
+  }
+  for {set ii 1} {$ii <= 60} {incr ii} {
+    set cmd "db$ii"
+    sqlite3 $cmd test.db
+    catchsql { SELECT * FROM t1 } $cmd
+    $cmd unlock_notify "lappend ::lUnlock $ii"
+  }
+} -sqlbody {
+  COMMIT;
+} -cleanup {
+  # One of two things should have happened:
+  #
+  #   1) The transaction opened by [db] was not committed. No unlock-notify
+  #      callbacks were invoked, OR
+  #   2) The transaction opened by [db] was committed and 60 unlock-notify
+  #      callbacks were invoked.
+  #
+  do_test notify1-5.systemstate {
+    expr { ([llength $::lUnlock]==0 && [sqlite3_get_autocommit db]==0)
+        || ([llength $::lUnlock]==60 && [sqlite3_get_autocommit db]==1)
+    }
+  } {1}
+  for {set ii 1} {$ii <= 60} {incr ii} { "db$ii" close }
+}
+
+#-------------------------------------------------------------------------
+# Test cases notify1-6.* test cases where the following occur:
+# 
+#   notify1-6.1.*: Test encountering an SQLITE_LOCKED error when the
+#                  "blocking connection" has already been set by a previous
+#                  SQLITE_LOCKED.
+#
+#   notify1-6.2.*: Test encountering an SQLITE_LOCKED error when already
+#                  waiting on an unlock-notify callback.
+#
+#   notify1-6.3.*: Test that if an SQLITE_LOCKED error is encountered while
+#                  already waiting on an unlock-notify callback, and then
+#                  the blocker that caused the SQLITE_LOCKED commits its
+#                  transaction, the unlock-notify callback is not invoked.
+#
+#   notify1-6.4.*: Like 6.3.*, except that instead of the second blocker
+#                  committing its transaction, the first does. The 
+#                  unlock-notify callback is therefore invoked.
+#
+db close
+do_test notify1-6.1.1 {
+  file delete -force test.db test2.db
+  foreach conn {db db2 db3} {
+    sqlite3 $conn test.db
+    execsql { ATTACH 'test2.db' AS two } $conn
+  }
+  execsql {
+    CREATE TABLE t1(a, b);
+    CREATE TABLE two.t2(a, b);
+  }
+  execsql { 
+    BEGIN;
+    INSERT INTO t1 VALUES(1, 2);
+  } db2
+  execsql { 
+    BEGIN;
+    INSERT INTO t2 VALUES(1, 2);
+  } db3
+} {}
+do_test notify1-6.1.2 {
+  catchsql { SELECT * FROM t2 }
+} {1 {database table is locked: t2}}
+do_test notify1-6.1.3 {
+  catchsql { SELECT * FROM t1 }
+} {1 {database table is locked: t1}}
+
+do_test notify1-6.2.1 {
+  set unlocked 0
+  db unlock_notify {set unlocked 1}
+  set unlocked
+} {0}
+do_test notify1-6.2.2 {
+  catchsql { SELECT * FROM t2 }
+} {1 {database table is locked: t2}}
+do_test notify1-6.2.3 {
+  execsql { COMMIT } db2
+  set unlocked
+} {1}
+
+do_test notify1-6.3.1 {
+  execsql { 
+    BEGIN;
+    INSERT INTO t1 VALUES(3, 4);
+  } db2
+} {}
+do_test notify1-6.3.2 {
+  catchsql { SELECT * FROM t1 }
+} {1 {database table is locked: t1}}
+do_test notify1-6.3.3 {
+  set unlocked 0
+  db unlock_notify {set unlocked 1}
+  set unlocked
+} {0}
+do_test notify1-6.3.4 {
+  catchsql { SELECT * FROM t2 }
+} {1 {database table is locked: t2}}
+do_test notify1-6.3.5 {
+  execsql { COMMIT } db3
+  set unlocked
+} {0}
+
+do_test notify1-6.4.1 {
+  execsql { 
+    BEGIN;
+    INSERT INTO t2 VALUES(3, 4);
+  } db3
+  catchsql { SELECT * FROM t2 }
+} {1 {database table is locked: t2}}
+do_test notify1-6.4.2 {
+  execsql { COMMIT } db2
+  set unlocked
+} {1}
+do_test notify1-6.4.3 {
+  execsql { COMMIT } db3
+} {}
+db close
+db2 close
+db3 close
+
+#-------------------------------------------------------------------------
+# Test cases notify1-7.* tests that when more than one distinct 
+# unlock-notify function is registered, all are invoked correctly.
+#
+proc unlock_notify {} {
+  incr ::unlock_notify
+}
+do_test notify1-7.1 {
+  foreach conn {db db2 db3} {
+    sqlite3 $conn test.db
+  }
+  execsql {
+    BEGIN;
+    INSERT INTO t1 VALUES(5, 6);
+  }
+} {}
+do_test notify1-7.2 {
+  catchsql { SELECT * FROM t1 } db2
+} {1 {database table is locked: t1}}
+do_test notify1-7.3 {
+  catchsql { SELECT * FROM t1 } db3
+} {1 {database table is locked: t1}}
+do_test notify1-7.4 {
+  set unlock_notify 0
+  db2 unlock_notify unlock_notify
+  sqlite3_unlock_notify db3
+} {SQLITE_OK}
+do_test notify1-7.5 {
+  set unlock_notify
+} {0}
+do_test notify1-7.6 {
+  execsql { COMMIT }
+  set unlock_notify
+} {2}
+
+#-------------------------------------------------------------------------
+# Test cases notify1-8.* tests that the correct SQLITE_LOCKED extended 
+# error code is returned in various scenarios.
+#
+do_test notify1-8.1 {
+  execsql {
+    BEGIN;
+    INSERT INTO t1 VALUES(7, 8);
+  }
+  sqlite3_extended_result_codes db2 1
+  catchsql { SELECT * FROM t1 } db2
+} {1 {database table is locked: t1}}
+do_test notify1-8.2 {
+  sqlite3_extended_errcode db2
+} {SQLITE_LOCKED_SHAREDCACHE}
+
+do_test notify1-8.3 {
+  execsql {
+    COMMIT;
+    BEGIN EXCLUSIVE;
+  }
+  catchsql { SELECT * FROM t1 } db2
+} {1 {database schema is locked: main}}
+do_test notify1-8.4 {
+  sqlite3_extended_errcode db2
+} {SQLITE_LOCKED_SHAREDCACHE}
+
+do_test notify1-8.X {
+  execsql { COMMIT } 
+} {}
+
+#-------------------------------------------------------------------------
+# Test cases notify1-9.* test the shared-cache 'pending-lock' feature.
+#
+do_test notify1-9.1 {
+  execsql {
+    CREATE TABLE t2(a, b);
+    BEGIN;
+    SELECT * FROM t1;
+  } db2
+} {1 2 3 4 5 6 7 8}
+do_test notify1-9.2 {
+  execsql { SELECT * FROM t1 } db3
+} {1 2 3 4 5 6 7 8}
+do_test notify1-9.3 {
+  catchsql { 
+    BEGIN;
+    INSERT INTO t1 VALUES(9, 10);
+  }
+} {1 {database table is locked: t1}}
+do_test notify1-9.4 {
+  catchsql { SELECT * FROM t2 } db3
+} {1 {database table is locked}}
+do_test notify1-9.5 {
+  execsql  { COMMIT } db2
+  execsql { SELECT * FROM t2 } db3
+} {}
+do_test notify1-9.6 {
+  execsql  { COMMIT }
+} {}
+
+do_test notify1-9.7 {
+  execsql {
+    BEGIN;
+    SELECT * FROM t1;
+  } db2
+} {1 2 3 4 5 6 7 8}
+do_test notify1-9.8 {
+  execsql { SELECT * FROM t1 } db3
+} {1 2 3 4 5 6 7 8}
+do_test notify1-9.9 {
+  catchsql { 
+    BEGIN;
+    INSERT INTO t1 VALUES(9, 10);
+  }
+} {1 {database table is locked: t1}}
+do_test notify1-9.10 {
+  catchsql { SELECT * FROM t2 } db3
+} {1 {database table is locked}}
+do_test notify1-9.11 {
+  execsql  { COMMIT }
+  execsql { SELECT * FROM t2 } db3
+} {}
+do_test notify1-9.12 {
+  execsql  { COMMIT } db2
+} {}
+
+db close
+db2 close
+db3 close
+sqlite3_enable_shared_cache $::enable_shared_cache
+finish_test
diff --git a/test/notify2.test b/test/notify2.test
new file mode 100644 (file)
index 0000000..c6df6e5
--- /dev/null
@@ -0,0 +1,207 @@
+# 2009 March 04
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# $Id: notify2.test,v 1.1 2009/03/16 13:19:36 danielk1977 Exp $
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+# The tests in this file test the sqlite3_blocking_step() function in
+# test_thread.c. sqlite3_blocking_step() is not an SQLite API function,
+# it is just a demonstration of how the sqlite3_unlock_notify() function
+# can be used to synchronize multi-threaded access to SQLite databases
+# in shared-cache mode.
+#
+# Since the implementation of sqlite3_blocking_step() is included on the
+# website as example code, it is important to test that it works.
+#
+# notify2-1.*:
+#
+#   This test uses $nThread threads. Each thread opens the main database
+#   and attaches two other databases. Each database contains a single table.
+#
+#   Each thread repeats transactions over and over for 20 seconds. Each
+#   transaction consists of 3 operations. Each operation is either a read
+#   or a write of one of the tables. The read operations verify an invariant
+#   to make sure that things are working as expected. If an SQLITE_LOCKED
+#   error is returned the current transaction is rolled back immediately.
+#
+#   This exercise is repeated twice, once using sqlite3_step(), and the
+#   other using sqlite3_blocking_step(). The results are compared to ensure
+#   that sqlite3_blocking_step() resulted in higher transaction throughput.
+#
+
+if {[info commands sqlite3_blocking_step] eq ""} {
+  finish_test
+  return
+}
+db close
+set ::enable_shared_cache [sqlite3_enable_shared_cache 1]
+source $testdir/thread_common.tcl
+
+# Number of threads to run simultaneously.
+#
+set nThread 3
+set nSecond 5
+
+# The Tcl script executed by each of the $nThread threads used by this test.
+#
+set ThreadProgram {
+
+  # Proc used by threads to execute SQL.
+  #
+  proc execsql_blocking {db zSql} {
+    set lRes [list]
+    set rc SQLITE_OK
+
+    while {$rc=="SQLITE_OK" && $zSql ne ""} {
+      set STMT [sqlite3_prepare_v2 $db $zSql -1 zSql]
+      while {[set rc [$::xStep $STMT]] eq "SQLITE_ROW"} {
+        for {set i 0} {$i < [sqlite3_column_count $STMT]} {incr i} {
+          lappend lRes [sqlite3_column_text $STMT 0]
+        }
+      }
+      set rc [sqlite3_finalize $STMT]
+    }
+
+    if {$rc != "SQLITE_OK"} { error "$rc [sqlite3_errmsg $db]" }
+    return $lRes
+  }
+
+  proc select_one {args} {
+    set n [llength $args]
+    lindex $args [expr int($n*rand())]
+  }
+
+  # Open a database connection. Attach the two auxillary databases.
+  set ::DB [sqlite3_open test.db]
+  execsql_blocking $::DB {
+    ATTACH 'test2.db' AS aux2;
+    ATTACH 'test3.db' AS aux3;
+  }
+
+  # This loop runs for ~20 seconds.
+  #
+  set iStart [clock_seconds]
+  while { ([clock_seconds]-$iStart) < $nSecond } {
+
+    # Each transaction does 3 operations. Each operation is either a read
+    # or write of a randomly selected table (t1, t2 or t3). Set the variables
+    # $SQL(1), $SQL(2) and $SQL(3) to the SQL commands used to implement
+    # each operation.
+    #
+    for {set ii 1} {$ii <= 3} {incr ii} {
+      set SQL($ii) [string map [list xxx [select_one t1 t2 t3]] [select_one {
+            SELECT 
+              (SELECT b FROM xxx WHERE a=(SELECT max(a) FROM xxx))==total(a) 
+              FROM xxx WHERE a!=(SELECT max(a) FROM xxx);
+      } {
+            DELETE FROM xxx WHERE a<(SELECT max(a)-100 FROM xxx);
+            INSERT INTO xxx SELECT NULL, total(a) FROM xxx;
+      }]]
+    }
+
+    # Execute the SQL transaction.
+    #
+    set rc [catch { execsql_blocking $::DB "
+        BEGIN;
+          $SQL(1);
+          $SQL(2);
+          $SQL(3);
+        COMMIT;
+      "
+    } msg]
+
+    if {$rc && [string match "SQLITE_LOCKED*" $msg]} {
+      # Hit an SQLITE_LOCKED error. Rollback the current transaction.
+      execsql_blocking $::DB ROLLBACK
+    } elseif {$rc} {
+      # Hit some other kind of error. This is a malfunction.
+      error $msg
+    } else {
+      # No error occured. Check that any SELECT statements in the transaction
+      # returned "1". Otherwise, the invariant was false, indicating that
+      # some malfunction has occured.
+      foreach r $msg { if {$r != 1} { puts "Invariant check failed: $msg" } }
+    }
+  }
+
+  # Close the database connection and return 0.
+  #
+  sqlite3_close $::DB
+  expr 0
+}
+
+foreach {iTest xStep} {1 sqlite3_blocking_step 2 sqlite3_step} {
+  file delete -force test.db test2.db test3.db
+
+  set ThreadSetup "set xStep $xStep ; set nSecond $nSecond"
+
+  # Set up the database schema used by this test. Each thread opens file
+  # test.db as the main database, then attaches files test2.db and test3.db
+  # as auxillary databases. Each file contains a single table (t1, t2 and t3, in
+  # files test.db, test2.db and test3.db, respectively). 
+  #
+  do_test notify2-$iTest.1.1 {
+    sqlite3 db test.db
+    execsql {
+      ATTACH 'test2.db' AS aux2;
+      ATTACH 'test3.db' AS aux3;
+      CREATE TABLE main.t1(a INTEGER PRIMARY KEY, b);
+      CREATE TABLE aux2.t2(a INTEGER PRIMARY KEY, b);
+      CREATE TABLE aux3.t3(a INTEGER PRIMARY KEY, b);
+      INSERT INTO t1 SELECT NULL, 0;
+      INSERT INTO t2 SELECT NULL, 0;
+      INSERT INTO t3 SELECT NULL, 0;
+    }
+  } {}
+  do_test notify2-$iTest.1.2 {
+    db close
+  } {}
+
+
+  # Launch $nThread threads. Then wait for them to finish.
+  #
+  puts "Running $xStep test for $nSecond seconds"
+  unset -nocomplain finished
+  for {set ii 0} {$ii < $nThread} {incr ii} {
+    thread_spawn finished($ii) $ThreadSetup $ThreadProgram
+  }
+  for {set ii 0} {$ii < $nThread} {incr ii} {
+    do_test notify2-$iTest.2.$ii {
+      if {![info exists finished($ii)]} { vwait finished($ii) }
+      set finished($ii)
+    } {0}
+  }
+
+  # Count the total number of succesful writes.
+  do_test notify2-$iTest.3.1 {
+    sqlite3 db test.db
+    execsql {
+      ATTACH 'test2.db' AS aux2;
+      ATTACH 'test3.db' AS aux3;
+    }
+    set anWrite($xStep) [execsql {
+      SELECT (SELECT max(a) FROM t1)
+           + (SELECT max(a) FROM t2)
+           + (SELECT max(a) FROM t3)
+    }]
+    db close
+  } {}
+}
+
+do_test notify2-3 {
+  expr {$anWrite(sqlite3_blocking_step) > $anWrite(sqlite3_step)}
+} {1}
+
+sqlite3_enable_shared_cache $::enable_shared_cache
+finish_test
+
index d57f54e739881aef2e51ccefc98ec5060907c8b2..9b049464208211a1c59c7599d9017f0556b540e3 100644 (file)
@@ -9,7 +9,7 @@
 #
 #***********************************************************************
 #
-# $Id: shared.test,v 1.35 2008/11/21 00:10:35 aswift Exp $
+# $Id: shared.test,v 1.36 2009/03/16 13:19:36 danielk1977 Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -875,7 +875,7 @@ do_test shared-$av.11.4 {
 } {0 {}}
 do_test shared-$av.11.5 {
   catchsql {INSERT INTO abc2 VALUES(1, 2, 3);} db2
-} {1 {database is locked}}
+} {1 {database table is locked}}
 do_test shared-$av.11.6 {
   catchsql {SELECT * FROM abc2}
 } {0 {}}
index a8ac08ddf62327350955ecfc3746ce6c83a9bb94..e752aa9fac6c9c1b9f2c35626194684112820fe6 100644 (file)
@@ -15,7 +15,7 @@
 # interface is pretty well tested.  This file contains some addition
 # tests for fringe issues that the main test suite does not cover.
 #
-# $Id: tclsqlite.test,v 1.72 2009/02/04 22:46:47 drh Exp $
+# $Id: tclsqlite.test,v 1.73 2009/03/16 13:19:36 danielk1977 Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -35,7 +35,7 @@ do_test tcl-1.1 {
 do_test tcl-1.2 {
   set v [catch {db bogus} msg]
   lappend v $msg
-} {1 {bad option "bogus": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, transaction, update_hook, or version}}
+} {1 {bad option "bogus": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, transaction, unlock_notify, update_hook, or version}}
 do_test tcl-1.2.1 {
   set v [catch {db cache bogus} msg]
   lappend v $msg
index 7bff008e190ad5c64c8b8ff682924391a43a9e58..862524a775c1abf83b1c702fc8b6015876638b4c 100644 (file)
@@ -9,7 +9,7 @@
 #
 #***********************************************************************
 #
-# $Id: tkt2854.test,v 1.3 2008/07/12 14:52:21 drh Exp $
+# $Id: tkt2854.test,v 1.4 2009/03/16 13:19:36 danielk1977 Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -56,7 +56,7 @@ do_test tkt2854-1.2 {
 } {}
 do_test tkt2854-1.3 {
   catchsql { BEGIN EXCLUSIVE } db
-} {1 {database is locked}}
+} {1 {database table is locked}}
 do_test tkt2854-1.4 {
   execsql { SELECT * FROM abc } db3
 } {}
@@ -99,10 +99,10 @@ do_test tkt2854-1.11 {
 } {SQLITE_ERROR SQLITE_LOCKED}
 do_test tkt2854-1.12 {
   list [sqlite3_step $::STMT2] [sqlite3_finalize $::STMT2]
-} {SQLITE_BUSY SQLITE_BUSY}
+} {SQLITE_ERROR SQLITE_LOCKED}
 do_test tkt2854-1.13 {
   list [sqlite3_step $::STMT3] [sqlite3_finalize $::STMT3]
-} {SQLITE_BUSY SQLITE_BUSY}
+} {SQLITE_ERROR SQLITE_LOCKED}
 do_test tkt2854-1.14 {
   # A regular "BEGIN" doesn't touch any databases. So it succeeds.
   list [sqlite3_step $::STMT4] [sqlite3_finalize $::STMT4]
@@ -136,7 +136,7 @@ do_test tkt2854-1.19 {
 do_test tkt2854-1.20 {
   execsql {BEGIN IMMEDIATE} db4
   catchsql {BEGIN EXCLUSIVE} db
-} {1 {database is locked}}
+} {1 {database table is locked}}
 do_test tkt2854-1.21 {
   execsql {SELECT * FROM abc} db2
 } {}
index 739f27e33d82f3009a1fd06253837cab8a33e2ef..857ecfb7f52c8877538b36e9583df61a6708228a 100644 (file)
@@ -287,6 +287,7 @@ foreach file {
    complete.c
 
    main.c
+   notify.c
 
    fts3.c
    fts3_expr.c