diff options
37 files changed, 2416 insertions, 432 deletions
@@ -254,6 +254,7 @@ TESTSRC = \ $(TOP)/src/test_server.c \ $(TOP)/src/test_stat.c \ $(TOP)/src/test_superlock.c \ + $(TOP)/src/test_syscall.c \ $(TOP)/src/test_tclvar.c \ $(TOP)/src/test_thread.c \ $(TOP)/src/test_vfs.c \ @@ -1,5 +1,5 @@ -C Add\sadditional\stest\sdata\sand\sdocumentation\sto\sthe\sfuzzer\svirtual\stable. -D 2011-04-01T20:28:31.337 +C Merge\sthe\sword-fuzzer\sbranch\sinto\strunk. +D 2011-04-01T20:47:27.009 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 6c96e694f446500449f683070b906de9fce17b88 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -101,7 +101,7 @@ F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 -F main.mk a767e12162f02719fa94697a6ff0c8b51bcd62a6 +F main.mk 078e1b601071cc50b4b6ccac9faece349ead1c39 F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a F mkextu.sh 416f9b7089d80e5590a29692c9d9280a10dbad9f F mkextw.sh 4123480947681d9b434a5e7b1ee08135abe409ac @@ -114,15 +114,15 @@ F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 6be1ad09113570e1fc8dcaff84c9b0b337db5ffc F sqlite3.pc.in ae6f59a76e862f5c561eb32a380228a02afc3cad -F src/alter.c 6a0c176e64a34929a4436048066a84ef4f1445b3 -F src/analyze.c a038162344265ac21dfb24b3fcc06c666ebb9c07 +F src/alter.c 280f5c04b11b492703a342222b3de0a999445280 +F src/analyze.c d0a673d303f611690fc7a3293aaefed57cccc5c8 F src/attach.c 438ea6f6b5d5961c1f49b737f2ce0f14ce7c6877 F src/auth.c 523da7fb4979469955d822ff9298352d6b31de34 -F src/backup.c 6728d6d48d55b449af76a3e51c0808849cb32a2e +F src/backup.c 537f89c7ef5021cb580f31f782e556ffffcb2ed1 F src/bitvec.c af50f1c8c0ff54d6bdb7a80e2fceca5a93670bef F src/btmutex.c 96a12f50f7a17475155971a241d85ec5171573ff -F src/btree.c 43302cc4f3de6479b90fa6bb271b65d86333d00e -F src/btree.h e2f2cd9933bf30724f53ffa12c4c5a3a864bbd6e +F src/btree.c 2b9c81ff64da339a67dda4f94c0d763627be0b67 +F src/btree.h 8d36f774ec4b1d0027b8966f8c03d9a72a518c14 F src/btreeInt.h 20f73dc93b1eeb83afd7259fbc6bd7dcf2df7fe4 F src/build.c 6c490fe14dedb094a202f559e3b29a276abffcf8 F src/callback.c 5069f224882cbdccd559f591271d28d7f37745bc @@ -162,7 +162,7 @@ F src/os.c 22ac61d06e72a0dac900400147333b07b13d8e1d F src/os.h 9dbed8c2b9c1f2f2ebabc09e49829d4777c26bf9 F src/os_common.h a8f95b81eca8a1ab8593d23e94f8a35f35d4078f F src/os_os2.c 2596fd2d5d0976c6c0c628d0c3c7c4e7a724f4cf -F src/os_unix.c 942a9dca5d17c599300127c88a48413e6d55666f +F src/os_unix.c a3b4cdf50a9c9be8b50dc4932354ab749962a07f F src/os_win.c 24d72407a90551969744cf9bcbb1b4c72c5fa845 F src/pager.c 6aa906b60a59664ba58d3f746164bb010d407ce1 F src/pager.h 3f8c783de1d4706b40b1ac15b64f5f896bcc78d1 @@ -178,14 +178,14 @@ F src/resolve.c 1c0f32b64f8e3f555fe1f732f9d6f501a7f05706 F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697 F src/select.c d24406c45dd2442eb2eeaac413439066b149c944 F src/shell.c 9dc0b4bb59290c0a35256d278cab0f314987ad6a -F src/sqlite.h.in 2ab8766c32afed165c0ea74292e18483e3aa64b1 +F src/sqlite.h.in e047f69a61d604d4f8be6cf1d1bdfc68be9ba7e5 F src/sqlite3ext.h c90bd5507099f62043832d73f6425d8d5c5da754 F src/sqliteInt.h f8f1d00a22c98fd3f2fbc94da74eeb880879f89f F src/sqliteLimit.h a17dcd3fb775d63b64a43a55c54cb282f9726f44 F src/status.c 4997380fbb915426fef9e500b4872e79c99267fc F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e -F src/tclsqlite.c 44979405594d33c55ec5ef8e82533d3b4133e455 -F src/test1.c 9020310c7617234b33fd1c3064f89524db25f290 +F src/tclsqlite.c e8c3bc4662975cf9a8e0280b1703a3cf427f9831 +F src/test1.c 9ca440e80e16e53920904a0a5ac7feffb9b2c9a1 F src/test2.c 80d323d11e909cf0eb1b6fbb4ac22276483bcf31 F src/test3.c 056093cfef69ff4227a6bdb9108564dc7f45e4bc F src/test4.c 0528360b5025688002a5feb6be906ddce52eaaee @@ -203,14 +203,15 @@ F src/test_demovfs.c 0aed671636735116fc872c5b03706fd5612488b5 F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc F src/test_func.c cbdec5cededa0761daedde5baf06004a9bf416b5 F src/test_fuzzer.c f884f6f32e8513d34248d6e1ac8a32047fead254 -F src/test_hexio.c 1237f000ec7a491009b1233f5c626ea71bce1ea2 +F src/test_hexio.c c4773049603151704a6ab25ac5e936b5109caf5a F src/test_init.c 5d624ffd0409d424cf9adbfe1f056b200270077c F src/test_intarray.c d879bbf8e4ce085ab966d1f3c896a7c8b4f5fc99 F src/test_intarray.h 489edb9068bb926583445cb02589344961054207 F src/test_journal.c 785edd54f963aefb3c1628124170a56697c68c70 F src/test_loadext.c df586c27176e3c2cb2e099c78da67bf14379a56e F src/test_malloc.c fd6188b1501c0010fb4241ddc9f0d5ac402c688d -F src/test_multiplex.c 655cb3b663f87db7d3d2427ea127c9daacae4abc +F src/test_multiplex.c fdabd793ee7a9642c5a8a470def2347144c46d05 +F src/test_multiplex.h e99c571bc4968b7a9363b661481f3934bfead61d F src/test_mutex.c a6bd7b9cf6e19d989e31392b06ac8d189f0d573e F src/test_onefile.c 40cf9e212a377a6511469384a64b01e6e34b2eec F src/test_osinst.c f408c6a181f2fb04c56273afd5c3e1e82f60392c @@ -221,6 +222,7 @@ F src/test_schema.c 8c06ef9ddb240c7a0fcd31bc221a6a2aade58bf0 F src/test_server.c bbba05c144b5fc4b52ff650a4328027b3fa5fcc6 F src/test_stat.c f682704b5d1ba8e1d4e7e882a6d7922e2dcf066c F src/test_superlock.c 2b97936ca127d13962c3605dbc9a4ef269c424cd +F src/test_syscall.c 61bb14684142a19c580a13b4dde680044d37e0f5 F src/test_tclvar.c f4dc67d5f780707210d6bb0eb6016a431c04c7fa F src/test_thread.c bedd05cad673dba53326f3aa468cc803038896c0 F src/test_vfs.c 2ed8853c1e51ac6f9ea091f7ce4e0d618bba8b86 @@ -236,7 +238,7 @@ F src/vdbe.c e3f37ca0afdd72e883475e2a32a06167df2810d0 F src/vdbe.h 4de0efb4b0fdaaa900cf419b35c458933ef1c6d2 F src/vdbeInt.h e1c6254641168507d25b46affb6dfb53c782f553 F src/vdbeapi.c a09ad9164cafc505250d5dd6b69660c960f1308c -F src/vdbeaux.c cfd3f3ac674691ba1166ceb9a2698b0d00b2ef91 +F src/vdbeaux.c 77921792f7ebae267490816deb6a9488f938fa85 F src/vdbeblob.c c3ccb7c8732858c680f442932e66ad06bb036562 F src/vdbemem.c 0498796b6ffbe45e32960d6a1f5adfb6e419883b F src/vdbetrace.c 3ba13bc32bdf16d2bdea523245fd16736bed67b5 @@ -244,21 +246,22 @@ F src/vtab.c e1edca38c4c4310710635bb91bb3c87fdf60f21d F src/wal.c 7334009b396285b658a95a3b6bc6d2b016a1f794 F src/wal.h 7a5fbb00114b7f2cd40c7e1003d4c41ce9d26840 F src/walker.c 3112bb3afe1d85dc52317cb1d752055e9a781f8f -F src/where.c a6e89fe7e56ab7e633be6fdebdddd857e9e5bc99 +F src/where.c 176574bfeee13775761ce56416f773b0ec115d3f F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87 F test/all.test 51756962d522e474338e9b2ebb26e7364d4aa125 -F test/alter.test 15f9224868b290d6bf7a63f31437f31aee070636 +F test/alter.test 4e47fb9ea59348b88fce4e8bb49de530128b104c F test/alter2.test 75f731508f1bf27ba09a6075c66cd02216ba464b F test/alter3.test 8677e48d95536f7a6ed86a1a774744dadcc22b07 F test/alter4.test 1e5dd6b951e9f65ca66422edff02e56df82dd403 F test/altermalloc.test e81ac9657ed25c6c5bb09bebfa5a047cd8e4acfc -F test/analyze.test c1eb87067fc16ece7c07e823d6395fd831b270c5 +F test/analyze.test c8cb89e8736336f1f0646c8123e6028a14c7b55e F test/analyze2.test 8f2b1534d43f5547ce9a6b736c021d4192c75be3 F test/analyze3.test d61f55d8b472fc6e713160b1e577f7a68e63f38b F test/analyze4.test 757b37875cf9bb528d46f74497bc789c88365045 F test/analyze5.test adc89b92fc9fee5ca1cb0bc8512f3206ad0fe5aa -F test/analyze6.test 1ba1aea8fad25a77ffd71f24522d1bb9ecc949fc +F test/analyze6.test c125622a813325bba1b4999040ddc213773c2290 +F test/analyze7.test 8095fed183e1fb999c1b1c4a1606cec1bcb13d1f F test/async.test ad4ba51b77cd118911a3fe1356b0809da9c108c3 F test/async2.test bf5e2ca2c96763b4cba3d016249ad7259a5603b6 F test/async3.test 93edaa9122f498e56ea98c36c72abc407f4fb11e @@ -282,7 +285,7 @@ F test/backup2.test b7c69f937c912e85ac8a5dbd1e1cf290302b2d49 F test/backup_ioerr.test 1f012e692f42c0442ae652443258f70e9f20fa38 F test/backup_malloc.test 7162d604ec2b4683c4b3799a48657fb8b5e2d450 F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f -F test/badutf2.test a47fda0d986d5325aa0ec2a0ebdd2d68db45e623 +F test/badutf2.test f5bc7f2d280670ecd79b9cf4f0f1760c607fe51f F test/between.test 16b1776c6323faadb097a52d673e8e3d8be7d070 F test/bigfile.test a8ec8073a20207456dab01a29ad9cde42b0dd103 F test/bigrow.test f0aeb7573dcb8caaafea76454be3ade29b7fc747 @@ -459,7 +462,7 @@ F test/fts3e.test 1f6c6ac9cc8b772ca256e6b22aaeed50c9350851 F test/fts3expr.test 5e745b2b6348499d9ef8d59015de3182072c564c F test/fts3expr2.test 18da930352e5693eaa163a3eacf96233b7290d1a F test/fts3fault.test f83e556465bb69dc8bc676339eca408dce4ca246 -F test/fts3fault2.test f275554f4a4fc7abf71e2975a9d6f4693f390526 +F test/fts3fault2.test dc96203af6ba31ce20163fc35460e1556e8edf4d F test/fts3malloc.test 9c8cc3f885bb4dfc66d0460c52f68f45e4710d1b F test/fts3matchinfo.test cc0b009edbbf575283d5fdb53271179e0d8019ba F test/fts3near.test 2e318ee434d32babd27c167142e2b94ddbab4844 @@ -561,7 +564,7 @@ F test/mallocH.test 79b65aed612c9b3ed2dcdaa727c85895fd1bfbdb F test/mallocI.test a88c2b9627c8506bf4703d8397420043a786cdb6 F test/mallocJ.test b5d1839da331d96223e5f458856f8ffe1366f62e F test/mallocK.test d79968641d1b70d88f6c01bdb9a7eb4a55582cc9 -F test/malloc_common.tcl 660b82ab528521cc4a48ff6df05ca3b6a00d47c5 +F test/malloc_common.tcl 50d0ed21eed0ae9548b58935bd29ac89a05a54fa F test/manydb.test b3d3bc4c25657e7f68d157f031eb4db7b3df0d3c F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f F test/memdb.test 0825155b2290e900264daaaf0334b6dfe69ea498 @@ -579,7 +582,7 @@ F test/misc5.test 45b2e3ed5f79af2b4f38ae362eaf4c49674575bd F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91 F test/misc7.test 29032efcd3d826fbd409e2a7af873e7939f4a4e3 F test/misuse.test 30b3a458e5a70c31e74c291937b6c82204c59f33 -F test/multiplex.test 92a4839213fd8cba8b59f86d42b7a1da1857db39 +F test/multiplex.test a88f3e2c16e567e72be7296195c59fbdd6a8d3d4 F test/mutex1.test 78b2b9bb320e51d156c4efdb71b99b051e7a4b41 F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660 F test/nan.test a44e04df1486fcfb02d32468cbcd3c8e1e433723 @@ -590,7 +593,7 @@ F test/notnull.test cc7c78340328e6112a13c3e311a9ab3127114347 F test/null.test a8b09b8ed87852742343b33441a9240022108993 F test/omitunique.test bbb2ec4345d9125d9ee21cd9488d97a163020d5f F test/openv2.test af02ed0a9cbc0d2a61b8f35171d4d117e588e4ec -F test/oserror.test d1f085bdbac20456fccdf5877f52016453654fc3 +F test/oserror.test 6c61c859cd94864cfd6af83e0549e2800238c413 F test/pager1.test d8672fd0af5f4f9b99b06283d00f01547809bebe F test/pager2.test 745b911dde3d1f24ae0870bd433dfa83d7c658c1 F test/pager3.test 3856d9c80839be0668efee1b74811b1b7f7fc95f @@ -673,6 +676,8 @@ F test/subselect.test d24fd8757daf97dafd2e889c73ea4c4272dcf4e4 F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a F test/superlock.test 5d7a4954b0059c903f82c7b67867bc5451a7c082 F test/sync.test ded6b39d8d8ca3c0c5518516c6371b3316d3e3a3 +F test/syscall.test d1dae1fee88613cf763d97ad0038d867509e0c42 +F test/sysfault.test c79441d88d23696fbec7b147dba98d42a04f523f F test/table.test 04ba066432430657712d167ebf28080fe878d305 F test/tableapi.test 2674633fa95d80da917571ebdd759a14d9819126 F test/tclsqlite.test 8c154101e704170c2be10f137a5499ac2c6da8d3 @@ -827,6 +832,7 @@ F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff F test/types2.test 3555aacf8ed8dc883356e59efc314707e6247a84 F test/types3.test a0f66bf12f80fad89493535474f7a6d16fa58150 F test/unique.test 083c7fff74695bcc27a71d75699deba3595bc9c2 +F test/unixexcl.test 9d80a54d86d2261f660758928959368ffc36151e F test/update.test 8bc86fd7ef1a00014f76dc6a6a7c974df4aef172 F test/utf16align.test 54cd35a27c005a9b6e7815d887718780b6a462ae F test/vacuum.test 29b60e8cc9e573b39676df6c4a75fe9e02d04a09 @@ -853,11 +859,11 @@ F test/vtabE.test 7c4693638d7797ce2eda17af74292b97e705cc61 F test/vtab_alter.test 9e374885248f69e251bdaacf480b04a197f125e5 F test/vtab_err.test 0d4d8eb4def1d053ac7c5050df3024fd47a3fbd8 F test/vtab_shared.test 0eff9ce4f19facbe0a3e693f6c14b80711a4222d -F test/wal.test f060cae4b2164c4375109a8f803873187234661d -F test/wal2.test 57a218446654ed3e3592c925762633c1d1e85636 -F test/wal3.test ec87d9dd9e9cebabed4024064e8ff531d336ead2 +F test/wal.test 973a4747a69247a43cc03292c44f59cc76f4df65 +F test/wal2.test e561a8c6fdd1c2cd1876f3e39757934e7b7361f8 +F test/wal3.test 5c396cc22497244d627306f4c1d360167353f8dd F test/wal4.test 3404b048fa5e10605facaf70384e6d2943412e30 -F test/wal5.test 3fef990d256cd9e95e9ad97e5dfdf3f150743fce +F test/wal5.test 1bbfaa316dc2a1d0d1fac3f4500c38a90055a41b F test/wal6.test 07aa31ca8892d0527f2c5c5a9a2a87aa421dfaa8 F test/wal_common.tcl a98f17fba96206122eff624db0ab13ec377be4fe F test/walbak.test 4df1c7369da0301caeb9a48fa45997fd592380e4 @@ -874,7 +880,7 @@ F test/walslow.test d21625e2e99e11c032ce949e8a94661576548933 F test/walthread.test a25a393c068a2b42b44333fa3fdaae9072f1617c F test/where.test de337a3fe0a459ec7c93db16a519657a90552330 F test/where2.test 43d4becaf5a5df854e6c21d624a1cb84c6904554 -F test/where3.test c81d4ecfaed54e8aef9c1a8a90ac83c9f5c49090 +F test/where3.test 8e1175c7ef710c70502858fc4fb08d784b3620b9 F test/where4.test e9b9e2f2f98f00379e6031db6a6fca29bae782a2 F test/where5.test fdf66f96d29a064b63eb543e28da4dfdccd81ad2 F test/where6.test 5da5a98cec820d488e82708301b96cb8c18a258b @@ -895,7 +901,7 @@ F tool/lempar.c 01ca97f87610d1dac6d8cd96ab109ab1130e76dc F tool/mkkeywordhash.c d2e6b4a5965e23afb80fbe74bb54648cd371f309 F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 -F tool/mksqlite3c.tcl cf44512a48112b1ba09590548660a5a6877afdb3 +F tool/mksqlite3c.tcl 623e26cc8c83322e4151d3ad85ac69d41221bae8 F tool/mksqlite3h.tcl d76c226a5e8e1f3b5f6593bcabe5e98b3b1ec9ff F tool/mksqlite3internalh.tcl 7b43894e21bcb1bb39e11547ce7e38a063357e87 F tool/omittest.tcl 4f4cc66bb7ca6a5b8f61ee37b6333f60fb8a746a @@ -917,8 +923,9 @@ F tool/speedtest16.c c8a9c793df96db7e4933f0852abb7a03d48f2e81 F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e +F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f -P 7958cbba736a599c1293b06602eec43dfe4fd7d1 -R a5f7c9b6891187773247bee84f644851 +P b477852f82c1fddbda61fad83d55055ad8503dda a6a81d4fdafabba514e8f8e1958d6132b3850772 +R 8fa020ba89288daa4e980326f651b43c U drh -Z b39064b9fd85dd1a63e76912c214c45c +Z f9a8b03fbe76a0642382439288b4d943 diff --git a/manifest.uuid b/manifest.uuid index f8b467bbd..c526826d6 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -a6a81d4fdafabba514e8f8e1958d6132b3850772
\ No newline at end of file +f77609d44194ee8871b3fb281ea6b90a9182f69f
\ No newline at end of file diff --git a/src/alter.c b/src/alter.c index 1534fdf69..aa3fa929f 100644 --- a/src/alter.c +++ b/src/alter.c @@ -371,6 +371,22 @@ static void reloadTableSchema(Parse *pParse, Table *pTab, const char *zName){ } /* +** Parameter zName is the name of a table that is about to be altered +** (either with ALTER TABLE ... RENAME TO or ALTER TABLE ... ADD COLUMN). +** If the table is a system table, this function leaves an error message +** in pParse->zErr (system tables may not be altered) and returns non-zero. +** +** Or, if zName is not a system table, zero is returned. +*/ +static int isSystemTable(Parse *pParse, const char *zName){ + if( sqlite3Strlen30(zName)>6 && 0==sqlite3StrNICmp(zName, "sqlite_", 7) ){ + sqlite3ErrorMsg(pParse, "table %s may not be altered", zName); + return 1; + } + return 0; +} + +/* ** Generate code to implement the "ALTER TABLE xxx RENAME TO yyy" ** command. */ @@ -420,14 +436,11 @@ void sqlite3AlterRenameTable( /* Make sure it is not a system table being altered, or a reserved name ** that the table is being renamed to. */ - if( sqlite3Strlen30(pTab->zName)>6 - && 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7) - ){ - sqlite3ErrorMsg(pParse, "table %s may not be altered", pTab->zName); + if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ){ goto exit_rename_table; } - if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ - goto exit_rename_table; + if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ goto + exit_rename_table; } #ifndef SQLITE_OMIT_VIEW @@ -759,6 +772,9 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ sqlite3ErrorMsg(pParse, "Cannot add a column to a view"); goto exit_begin_add_column; } + if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ){ + goto exit_begin_add_column; + } assert( pTab->addColOffset>0 ); iDb = sqlite3SchemaToIndex(db, pTab->pSchema); diff --git a/src/analyze.c b/src/analyze.c index 0a8339baf..231d41431 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -34,7 +34,8 @@ static void openStatTable( Parse *pParse, /* Parsing context */ int iDb, /* The database we are looking in */ int iStatCur, /* Open the sqlite_stat1 table on this cursor */ - const char *zWhere /* Delete entries associated with this table */ + const char *zWhere, /* Delete entries for this table or index */ + const char *zWhereType /* Either "tbl" or "idx" */ ){ static const struct { const char *zName; @@ -79,7 +80,7 @@ static void openStatTable( sqlite3TableLock(pParse, iDb, aRoot[i], 1, zTab); if( zWhere ){ sqlite3NestedParse(pParse, - "DELETE FROM %Q.%s WHERE tbl=%Q", pDb->zName, zTab, zWhere + "DELETE FROM %Q.%s WHERE %s=%Q", pDb->zName, zTab, zWhereType, zWhere ); }else{ /* The sqlite_stat[12] table already exists. Delete all rows. */ @@ -103,6 +104,7 @@ static void openStatTable( static void analyzeOneTable( Parse *pParse, /* Parser context */ Table *pTab, /* Table whose indices are to be analyzed */ + Index *pOnlyIdx, /* If not NULL, only analyze this one index */ int iStatCur, /* Index of VdbeCursor that writes the sqlite_stat1 table */ int iMem /* Available memory locations begin here */ ){ @@ -113,8 +115,7 @@ static void analyzeOneTable( int i; /* Loop counter */ int topOfLoop; /* The top of the loop */ int endOfLoop; /* The end of the loop */ - int addr = 0; /* The address of an instruction */ - int jZeroRows = 0; /* Jump from here if number of rows is zero */ + int jZeroRows = -1; /* Jump from here if number of rows is zero */ int iDb; /* Index of database containing pTab */ int regTabname = iMem++; /* Register containing table name */ int regIdxname = iMem++; /* Register containing index name */ @@ -125,6 +126,7 @@ static void analyzeOneTable( int regRowid = iMem++; /* Rowid for the inserted record */ #ifdef SQLITE_ENABLE_STAT2 + int addr = 0; /* Instruction address */ int regTemp2 = iMem++; /* Temporary use register */ int regSamplerecno = iMem++; /* Index of next sample to record */ int regRecno = iMem++; /* Current sample index */ @@ -160,9 +162,12 @@ static void analyzeOneTable( iIdxCur = pParse->nTab++; sqlite3VdbeAddOp4(v, OP_String8, 0, regTabname, 0, pTab->zName, 0); for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - int nCol = pIdx->nColumn; - KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx); + int nCol; + KeyInfo *pKey; + if( pOnlyIdx && pOnlyIdx!=pIdx ) continue; + nCol = pIdx->nColumn; + pKey = sqlite3IndexKeyinfo(pParse, pIdx); if( iMem+1+(nCol*2)>pParse->nMem ){ pParse->nMem = iMem+1+(nCol*2); } @@ -319,7 +324,7 @@ static void analyzeOneTable( ** is never possible. */ sqlite3VdbeAddOp2(v, OP_SCopy, iMem, regSampleno); - if( jZeroRows==0 ){ + if( jZeroRows<0 ){ jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, iMem); } for(i=0; i<nCol; i++){ @@ -345,10 +350,10 @@ static void analyzeOneTable( VdbeComment((v, "%s", pTab->zName)); sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regSampleno); sqlite3VdbeAddOp1(v, OP_Close, iIdxCur); + jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regSampleno); }else{ - assert( jZeroRows>0 ); - addr = sqlite3VdbeAddOp0(v, OP_Goto); sqlite3VdbeJumpHere(v, jZeroRows); + jZeroRows = sqlite3VdbeAddOp0(v, OP_Goto); } sqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname); sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regRec, "aaa", 0); @@ -356,9 +361,7 @@ static void analyzeOneTable( sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regRowid); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); if( pParse->nMem<regRec ) pParse->nMem = regRec; - if( jZeroRows ){ - sqlite3VdbeJumpHere(v, addr); - } + sqlite3VdbeJumpHere(v, jZeroRows); } /* @@ -385,20 +388,21 @@ static void analyzeDatabase(Parse *pParse, int iDb){ sqlite3BeginWriteOperation(pParse, 0, iDb); iStatCur = pParse->nTab; pParse->nTab += 2; - openStatTable(pParse, iDb, iStatCur, 0); + openStatTable(pParse, iDb, iStatCur, 0, 0); iMem = pParse->nMem+1; for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ Table *pTab = (Table*)sqliteHashData(k); - analyzeOneTable(pParse, pTab, iStatCur, iMem); + analyzeOneTable(pParse, pTab, 0, iStatCur, iMem); } loadAnalysis(pParse, iDb); } /* ** Generate code that will do an analysis of a single table in -** a database. +** a database. If pOnlyIdx is not NULL then it is a single index +** in pTab that should be analyzed. */ -static void analyzeTable(Parse *pParse, Table *pTab){ +static void analyzeTable(Parse *pParse, Table *pTab, Index *pOnlyIdx){ int iDb; int iStatCur; @@ -408,8 +412,12 @@ static void analyzeTable(Parse *pParse, Table *pTab){ sqlite3BeginWriteOperation(pParse, 0, iDb); iStatCur = pParse->nTab; pParse->nTab += 2; - openStatTable(pParse, iDb, iStatCur, pTab->zName); - analyzeOneTable(pParse, pTab, iStatCur, pParse->nMem+1); + if( pOnlyIdx ){ + openStatTable(pParse, iDb, iStatCur, pOnlyIdx->zName, "idx"); + }else{ + openStatTable(pParse, iDb, iStatCur, pTab->zName, "tbl"); + } + analyzeOneTable(pParse, pTab, pOnlyIdx, iStatCur, pParse->nMem+1); loadAnalysis(pParse, iDb); } @@ -431,6 +439,7 @@ void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){ int i; char *z, *zDb; Table *pTab; + Index *pIdx; Token *pTableName; /* Read the database schema. If an error occurs, leave an error message @@ -455,11 +464,12 @@ void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){ }else{ z = sqlite3NameFromToken(db, pName1); if( z ){ - pTab = sqlite3LocateTable(pParse, 0, z, 0); - sqlite3DbFree(db, z); - if( pTab ){ - analyzeTable(pParse, pTab); + if( (pIdx = sqlite3FindIndex(db, z, 0))!=0 ){ + analyzeTable(pParse, pIdx->pTable, pIdx); + }else if( (pTab = sqlite3LocateTable(pParse, 0, z, 0))!=0 ){ + analyzeTable(pParse, pTab, 0); } + sqlite3DbFree(db, z); } } }else{ @@ -469,11 +479,12 @@ void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){ zDb = db->aDb[iDb].zName; z = sqlite3NameFromToken(db, pTableName); if( z ){ - pTab = sqlite3LocateTable(pParse, 0, z, zDb); - sqlite3DbFree(db, z); - if( pTab ){ - analyzeTable(pParse, pTab); + if( (pIdx = sqlite3FindIndex(db, z, zDb))!=0 ){ + analyzeTable(pParse, pIdx->pTable, pIdx); + }else if( (pTab = sqlite3LocateTable(pParse, 0, z, zDb))!=0 ){ + analyzeTable(pParse, pTab, 0); } + sqlite3DbFree(db, z); } } } diff --git a/src/backup.c b/src/backup.c index 5d8ea7f3f..82be9635b 100644 --- a/src/backup.c +++ b/src/backup.c @@ -488,7 +488,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){ /* Finish committing the transaction to the destination database. */ if( SQLITE_OK==rc - && SQLITE_OK==(rc = sqlite3BtreeCommitPhaseTwo(p->pDest)) + && SQLITE_OK==(rc = sqlite3BtreeCommitPhaseTwo(p->pDest, 0)) ){ rc = SQLITE_DONE; } @@ -502,7 +502,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){ if( bCloseTrans ){ TESTONLY( int rc2 ); TESTONLY( rc2 = ) sqlite3BtreeCommitPhaseOne(p->pSrc, 0); - TESTONLY( rc2 |= ) sqlite3BtreeCommitPhaseTwo(p->pSrc); + TESTONLY( rc2 |= ) sqlite3BtreeCommitPhaseTwo(p->pSrc, 0); assert( rc2==SQLITE_OK ); } diff --git a/src/btree.c b/src/btree.c index 33d746067..088c555fc 100644 --- a/src/btree.c +++ b/src/btree.c @@ -3160,10 +3160,21 @@ static void btreeEndTransaction(Btree *p){ ** the rollback journal (which causes the transaction to commit) and ** drop locks. ** +** Normally, if an error occurs while the pager layer is attempting to +** finalize the underlying journal file, this function returns an error and +** the upper layer will attempt a rollback. However, if the second argument +** is non-zero then this b-tree transaction is part of a multi-file +** transaction. In this case, the transaction has already been committed +** (by deleting a master journal file) and the caller will ignore this +** functions return code. So, even if an error occurs in the pager layer, +** reset the b-tree objects internal state to indicate that the write +** transaction has been closed. This is quite safe, as the pager will have +** transitioned to the error state. +** ** This will release the write lock on the database file. If there ** are no active cursors, it also releases the read lock. */ -int sqlite3BtreeCommitPhaseTwo(Btree *p){ +int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){ if( p->inTrans==TRANS_NONE ) return SQLITE_OK; sqlite3BtreeEnter(p); @@ -3178,7 +3189,7 @@ int sqlite3BtreeCommitPhaseTwo(Btree *p){ assert( pBt->inTransaction==TRANS_WRITE ); assert( pBt->nTransaction>0 ); rc = sqlite3PagerCommitPhaseTwo(pBt->pPager); - if( rc!=SQLITE_OK ){ + if( rc!=SQLITE_OK && bCleanup==0 ){ sqlite3BtreeLeave(p); return rc; } @@ -3198,7 +3209,7 @@ int sqlite3BtreeCommit(Btree *p){ sqlite3BtreeEnter(p); rc = sqlite3BtreeCommitPhaseOne(p, 0); if( rc==SQLITE_OK ){ - rc = sqlite3BtreeCommitPhaseTwo(p); + rc = sqlite3BtreeCommitPhaseTwo(p, 0); } sqlite3BtreeLeave(p); return rc; diff --git a/src/btree.h b/src/btree.h index 6886dd944..468723b33 100644 --- a/src/btree.h +++ b/src/btree.h @@ -87,7 +87,7 @@ int sqlite3BtreeSetAutoVacuum(Btree *, int); int sqlite3BtreeGetAutoVacuum(Btree *); int sqlite3BtreeBeginTrans(Btree*,int); int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster); -int sqlite3BtreeCommitPhaseTwo(Btree*); +int sqlite3BtreeCommitPhaseTwo(Btree*, int); int sqlite3BtreeCommit(Btree*); int sqlite3BtreeRollback(Btree*); int sqlite3BtreeBeginStmt(Btree*,int); diff --git a/src/os_unix.c b/src/os_unix.c index f4e689fa9..f04b6af27 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -371,7 +371,7 @@ static struct unix_syscall { #else { "fallocate", (sqlite3_syscall_ptr)0, 0 }, #endif -#define osFallocate ((int(*)(int,off_t,off_t)aSyscall[15].pCurrent) +#define osFallocate ((int(*)(int,off_t,off_t))aSyscall[15].pCurrent) }; /* End of the overrideable system calls */ @@ -444,18 +444,16 @@ static sqlite3_syscall_ptr unixGetSystemCall( ** system call. */ static const char *unixNextSystemCall(sqlite3_vfs *p, const char *zName){ - unsigned int i; + int i = -1; UNUSED_PARAMETER(p); - if( zName==0 ){ - i = -1; - }else{ - for(i=0; i<sizeof(aSyscall)/sizeof(aSyscall[0])-1; i++){ - if( strcmp(zName, aSyscall[0].zName)==0 ) break; + if( zName ){ + for(i=0; i<ArraySize(aSyscall)-1; i++){ + if( strcmp(zName, aSyscall[i].zName)==0 ) break; } } - for(i++; i<sizeof(aSyscall)/sizeof(aSyscall[0]); i++){ - if( aSyscall[0].pCurrent!=0 ) return aSyscall[0].zName; + for(i++; i<ArraySize(aSyscall); i++){ + if( aSyscall[i].pCurrent!=0 ) return aSyscall[i].zName; } return 0; } @@ -595,9 +593,22 @@ static int robust_ftruncate(int h, sqlite3_int64 sz){ */ static int sqliteErrorFromPosixError(int posixError, int sqliteIOErr) { switch (posixError) { +#if 0 + /* At one point this code was not commented out. In theory, this branch + ** should never be hit, as this function should only be called after + ** a locking-related function (i.e. fcntl()) has returned non-zero with + ** the value of errno as the first argument. Since a system call has failed, + ** errno should be non-zero. + ** + ** Despite this, if errno really is zero, we still don't want to return + ** SQLITE_OK. The system call failed, and *some* SQLite error should be + ** propagated back to the caller. Commenting this branch out means errno==0 + ** will be handled by the "default:" case below. + */ case 0: return SQLITE_OK; - +#endif + case EAGAIN: case ETIMEDOUT: case EBUSY: @@ -619,8 +630,15 @@ static int sqliteErrorFromPosixError(int posixError, int sqliteIOErr) { case EPERM: return SQLITE_PERM; + /* EDEADLK is only possible if a call to fcntl(F_SETLKW) is made. And + ** this module never makes such a call. And the code in SQLite itself + ** asserts that SQLITE_IOERR_BLOCKED is never returned. For these reasons + ** this case is also commented out. If the system does set errno to EDEADLK, + ** the default SQLITE_IOERR_XXX code will be returned. */ +#if 0 case EDEADLK: return SQLITE_IOERR_BLOCKED; +#endif #if EOPNOTSUPP!=ENOTSUP case EOPNOTSUPP: @@ -1039,7 +1057,7 @@ static void closePendingFds(unixFile *pFile){ static void releaseInodeInfo(unixFile *pFile){ unixInodeInfo *pInode = pFile->pInode; assert( unixMutexHeld() ); - if( pInode ){ + if( ALWAYS(pInode) ){ pInode->nRef--; if( pInode->nRef==0 ){ assert( pInode->pShmNode==0 ); @@ -1181,10 +1199,9 @@ static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){ lock.l_start = RESERVED_BYTE; lock.l_len = 1; lock.l_type = F_WRLCK; - if (-1 == osFcntl(pFile->h, F_GETLK, &lock)) { - int tErrno = errno; - rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_CHECKRESERVEDLOCK); - pFile->lastErrno = tErrno; + if( osFcntl(pFile->h, F_GETLK, &lock) ){ + rc = SQLITE_IOERR_CHECKRESERVEDLOCK; + pFile->lastErrno = errno; } else if( lock.l_type!=F_UNLCK ){ reserved = 1; } @@ -1213,6 +1230,9 @@ static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){ ** This function is a pass-through to fcntl(F_SETLK) if pFile is using ** any VFS other than "unix-excl" or if pFile is opened on "unix-excl" ** and is read-only. +** +** Zero is returned if the call completes successfully, or -1 if a call +** to fcntl() fails. In this case, errno is set appropriately (by fcntl()). */ static int unixFileLock(unixFile *pFile, struct flock *pLock){ int rc; @@ -1309,7 +1329,6 @@ static int unixLock(sqlite3_file *id, int eFileLock){ unixFile *pFile = (unixFile*)id; unixInodeInfo *pInode = pFile->pInode; struct flock lock; - int s = 0; int tErrno = 0; assert( pFile ); @@ -1378,11 +1397,10 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ){ lock.l_type = (eFileLock==SHARED_LOCK?F_RDLCK:F_WRLCK); lock.l_start = PENDING_BYTE; - s = unixFileLock(pFile, &lock); - if( s==(-1) ){ + if( unixFileLock(pFile, &lock) ){ tErrno = errno; rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); - if( IS_LOCK_ERROR(rc) ){ + if( rc!=SQLITE_BUSY ){ pFile->lastErrno = tErrno; } goto end_lock; @@ -1396,33 +1414,31 @@ static int unixLock(sqlite3_file *id, int eFileLock){ if( eFileLock==SHARED_LOCK ){ assert( pInode->nShared==0 ); assert( pInode->eFileLock==0 ); + assert( rc==SQLITE_OK ); /* Now get the read-lock */ lock.l_start = SHARED_FIRST; lock.l_len = SHARED_SIZE; - if( (s = unixFileLock(pFile, &lock))==(-1) ){ + if( unixFileLock(pFile, &lock) ){ tErrno = errno; + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); } + /* Drop the temporary PENDING lock */ lock.l_start = PENDING_BYTE; lock.l_len = 1L; lock.l_type = F_UNLCK; - if( unixFileLock(pFile, &lock)!=0 ){ - if( s != -1 ){ - /* This could happen with a network mount */ - tErrno = errno; - rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_UNLOCK); - if( IS_LOCK_ERROR(rc) ){ - pFile->lastErrno = tErrno; - } - goto end_lock; - } + if( unixFileLock(pFile, &lock) && rc==SQLITE_OK ){ + /* This could happen with a network mount */ + tErrno = errno; + rc = SQLITE_IOERR_UNLOCK; } - if( s==(-1) ){ - rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); - if( IS_LOCK_ERROR(rc) ){ + + if( rc ){ + if( rc!=SQLITE_BUSY ){ pFile->lastErrno = tErrno; } + goto end_lock; }else{ pFile->eFileLock = SHARED_LOCK; pInode->nLock++; @@ -1439,22 +1455,20 @@ static int unixLock(sqlite3_file *id, int eFileLock){ */ assert( 0!=pFile->eFileLock ); lock.l_type = F_WRLCK; - switch( eFileLock ){ - case RESERVED_LOCK: - lock.l_start = RESERVED_BYTE; - break; - case EXCLUSIVE_LOCK: - lock.l_start = SHARED_FIRST; - lock.l_len = SHARED_SIZE; - break; - default: - assert(0); + + assert( eFileLock==RESERVED_LOCK || eFileLock==EXCLUSIVE_LOCK ); + if( eFileLock==RESERVED_LOCK ){ + lock.l_start = RESERVED_BYTE; + lock.l_len = 1L; + }else{ + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; } - s = unixFileLock(pFile, &lock); - if( s==(-1) ){ + + if( unixFileLock(pFile, &lock) ){ tErrno = errno; rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); - if( IS_LOCK_ERROR(rc) ){ + if( rc!=SQLITE_BUSY ){ pFile->lastErrno = tErrno; } } @@ -1525,7 +1539,6 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ struct flock lock; int rc = SQLITE_OK; int h; - int tErrno; /* Error code from system call errors */ assert( pFile ); OSTRACE(("UNLOCK %d %d was %d(%d,%d) pid=%d (unix)\n", pFile->h, eFileLock, @@ -1580,15 +1593,16 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ #endif #if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE if( handleNFSUnlock ){ + int tErrno; /* Error code from system call errors */ off_t divSize = SHARED_SIZE - 1; lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = SHARED_FIRST; lock.l_len = divSize; - if( unixFileLock(pFile,, &lock)==(-1) ){ + if( unixFileLock(pFile, &lock)==(-1) ){ tErrno = errno; - rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_UNLOCK); + rc = SQLITE_IOERR_UNLOCK; if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; } @@ -1612,7 +1626,7 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ lock.l_len = SHARED_SIZE-divSize; if( unixFileLock(pFile, &lock)==(-1) ){ tErrno = errno; - rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_UNLOCK); + rc = SQLITE_IOERR_UNLOCK; if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; } @@ -1625,12 +1639,15 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ lock.l_whence = SEEK_SET; lock.l_start = SHARED_FIRST; lock.l_len = SHARED_SIZE; - if( unixFileLock(pFile, &lock)==(-1) ){ - tErrno = errno; - rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_RDLOCK); - if( IS_LOCK_ERROR(rc) ){ - pFile->lastErrno = tErrno; - } + if( unixFileLock(pFile, &lock) ){ + /* In theory, the call to unixFileLock() cannot fail because another + ** process is holding an incompatible lock. If it does, this + ** indicates that the other process is not following the locking + ** protocol. If this happens, return SQLITE_IOERR_RDLOCK. Returning + ** SQLITE_BUSY would confuse the upper layer (in practice it causes + ** an assert to fail). */ + rc = SQLITE_IOERR_RDLOCK; + pFile->lastErrno = errno; goto end_unlock; } } @@ -1639,14 +1656,11 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ lock.l_whence = SEEK_SET; lock.l_start = PENDING_BYTE; lock.l_len = 2L; assert( PENDING_BYTE+1==RESERVED_BYTE ); - if( unixFileLock(pFile, &lock)!=(-1) ){ + if( unixFileLock(pFile, &lock)==0 ){ pInode->eFileLock = SHARED_LOCK; }else{ - tErrno = errno; - rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_UNLOCK); - if( IS_LOCK_ERROR(rc) ){ - pFile->lastErrno = tErrno; - } + rc = SQLITE_IOERR_UNLOCK; + pFile->lastErrno = errno; goto end_unlock; } } @@ -1663,14 +1677,11 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ SimulateIOErrorBenign(1); SimulateIOError( h=(-1) ) SimulateIOErrorBenign(0); - if( unixFileLock(pFile, &lock)!=(-1) ){ + if( unixFileLock(pFile, &lock)==0 ){ pInode->eFileLock = NO_LOCK; }else{ - tErrno = errno; - rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_UNLOCK); - if( IS_LOCK_ERROR(rc) ){ - pFile->lastErrno = tErrno; - } + rc = SQLITE_IOERR_UNLOCK; + pFile->lastErrno = errno; pInode->eFileLock = NO_LOCK; pFile->eFileLock = NO_LOCK; } @@ -1716,29 +1727,27 @@ static int unixUnlock(sqlite3_file *id, int eFileLock){ */ static int closeUnixFile(sqlite3_file *id){ unixFile *pFile = (unixFile*)id; - if( pFile ){ - if( pFile->dirfd>=0 ){ - robust_close(pFile, pFile->dirfd, __LINE__); - pFile->dirfd=-1; - } - if( pFile->h>=0 ){ - robust_close(pFile, pFile->h, __LINE__); - pFile->h = -1; - } + if( pFile->dirfd>=0 ){ + robust_close(pFile, pFile->dirfd, __LINE__); + pFile->dirfd=-1; + } + if( pFile->h>=0 ){ + robust_close(pFile, pFile->h, __LINE__); + pFile->h = -1; + } #if OS_VXWORKS - if( pFile->pId ){ - if( pFile->isDelete ){ - unlink(pFile->pId->zCanonicalName); - } - vxworksReleaseFileId(pFile->pId); - pFile->pId = 0; + if( pFile->pId ){ + if( pFile->isDelete ){ + unlink(pFile->pId->zCanonicalName); } -#endif - OSTRACE(("CLOSE %-3d\n", pFile->h)); - OpenCounter(-1); - sqlite3_free(pFile->pUnused); - memset(pFile, 0, sizeof(unixFile)); + vxworksReleaseFileId(pFile->pId); + pFile->pId = 0; } +#endif + OSTRACE(("CLOSE %-3d\n", pFile->h)); + OpenCounter(-1); + sqlite3_free(pFile->pUnused); + memset(pFile, 0, sizeof(unixFile)); return SQLITE_OK; } @@ -1747,24 +1756,25 @@ static int closeUnixFile(sqlite3_file *id){ */ static int unixClose(sqlite3_file *id){ int rc = SQLITE_OK; - if( id ){ - unixFile *pFile = (unixFile *)id; - unixUnlock(id, NO_LOCK); - unixEnterMutex(); - assert( pFile->pInode==0 || pFile->pInode->nLock>0 - || pFile->pInode->bProcessLock==0 ); - if( pFile->pInode && pFile->pInode->nLock ){ - /* If there are outstanding locks, do not actually close the file just - ** yet because that would clear those locks. Instead, add the file - ** descriptor to pInode->pUnused list. It will be automatically closed - ** when the last lock is cleared. - */ - setPendingFd(pFile); - } - releaseInodeInfo(pFile); - rc = closeUnixFile(id); - unixLeaveMutex(); + unixFile *pFile = (unixFile *)id; + unixUnlock(id, NO_LOCK); + unixEnterMutex(); + + /* unixFile.pInode is always valid here. Otherwise, a different close + ** routine (e.g. nolockClose()) would be called instead. + */ + assert( pFile->pInode->nLock>0 || pFile->pInode->bProcessLock==0 ); + if( ALWAYS(pFile->pInode) && pFile->pInode->nLock ){ + /* If there are outstanding locks, do not actually close the file just + ** yet because that would clear those locks. Instead, add the file + ** descriptor to pInode->pUnused list. It will be automatically closed + ** when the last lock is cleared. + */ + setPendingFd(pFile); } + releaseInodeInfo(pFile); + rc = closeUnixFile(id); + unixLeaveMutex(); return rc; } @@ -1979,7 +1989,7 @@ static int dotlockUnlock(sqlite3_file *id, int eFileLock) { int rc = 0; int tErrno = errno; if( ENOENT != tErrno ){ - rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_UNLOCK); + rc = SQLITE_IOERR_UNLOCK; } if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; @@ -2067,7 +2077,7 @@ static int flockCheckReservedLock(sqlite3_file *id, int *pResOut){ if ( lrc ) { int tErrno = errno; /* unlock failed with an error */ - lrc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_UNLOCK); + lrc = SQLITE_IOERR_UNLOCK; if( IS_LOCK_ERROR(lrc) ){ pFile->lastErrno = tErrno; rc = lrc; @@ -2189,21 +2199,12 @@ static int flockUnlock(sqlite3_file *id, int eFileLock) { } /* no, really, unlock. */ - int rc = robust_flock(pFile->h, LOCK_UN); - if (rc) { - int r, tErrno = errno; - r = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_UNLOCK); - if( IS_LOCK_ERROR(r) ){ - pFile->lastErrno = tErrno; - } + if( robust_flock(pFile->h, LOCK_UN) ){ #ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS - if( (r & SQLITE_IOERR) == SQLITE_IOERR ){ - r = SQLITE_BUSY; - } + return SQLITE_OK; #endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */ - - return r; - } else { + return SQLITE_IOERR_UNLOCK; + }else{ pFile->eFileLock = NO_LOCK; return SQLITE_OK; } @@ -3009,6 +3010,7 @@ static int seekAndWrite(unixFile *id, i64 offset, const void *pBuf, int cnt){ do{ got = osPwrite64(id->h, pBuf, cnt, offset);}while( got<0 && errno==EINTR); #else newOffset = lseek(id->h, offset, SEEK_SET); + SimulateIOError( newOffset-- ); if( newOffset!=offset ){ if( newOffset == -1 ){ ((unixFile*)id)->lastErrno = errno; @@ -3377,12 +3379,16 @@ static int fcntlSizeHint(unixFile *pFile, i64 nByte){ nSize = ((nByte+pFile->szChunk-1) / pFile->szChunk) * pFile->szChunk; if( nSize>(i64)buf.st_size ){ + #if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE - int rc; + /* The code below is handling the return value of osFallocate() + ** correctly. posix_fallocate() is defined to "returns zero on success, + ** or an error number on failure". See the manpage for details. */ + int err; do{ - rc = osFallocate(pFile->.h, buf.st_size, nSize-buf.st_size; - }while( rc<0 && errno=EINTR ); - if( rc ) return SQLITE_IOERR_WRITE; + err = osFallocate(pFile->h, buf.st_size, nSize-buf.st_size); + }while( err==EINTR ); + if( err ) return SQLITE_IOERR_WRITE; #else /* If the OS does not have posix_fallocate(), fake it. First use ** ftruncate() to set the file size, then write a single byte to @@ -5735,7 +5741,9 @@ static int proxyCreateUnixFile( } memset(pNew, 0, sizeof(unixFile)); pNew->openFlags = openFlags; + memset(&dummyVfs, 0, sizeof(dummyVfs)); dummyVfs.pAppData = (void*)&autolockIoFinder; + dummyVfs.zName = "dummy"; pUnused->fd = fd; pUnused->flags = openFlags; pNew->pUnused = pUnused; diff --git a/src/sqlite.h.in b/src/sqlite.h.in index d3be1b32a..c4dc4a6ef 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -2975,7 +2975,9 @@ int sqlite3_column_count(sqlite3_stmt *pStmt); ** column number. ^The leftmost column is number 0. ** ** ^The returned string pointer is valid until either the [prepared statement] -** is destroyed by [sqlite3_finalize()] or until the next call to +** is destroyed by [sqlite3_finalize()] or until the statement is automatically +** reprepared by the first call to [sqlite3_step()] for a particular run +** or until the next call to ** sqlite3_column_name() or sqlite3_column_name16() on the same column. ** ** ^If sqlite3_malloc() fails during the processing of either routine @@ -3001,7 +3003,9 @@ const void *sqlite3_column_name16(sqlite3_stmt*, int N); ** the database name, the _table_ routines return the table name, and ** the origin_ routines return the column name. ** ^The returned string is valid until the [prepared statement] is destroyed -** using [sqlite3_finalize()] or until the same information is requested +** using [sqlite3_finalize()] or until the statement is automatically +** reprepared by the first call to [sqlite3_step()] for a particular run +** or until the same information is requested ** again in a different encoding. ** ** ^The names returned are the original un-aliased names of the diff --git a/src/tclsqlite.c b/src/tclsqlite.c index e77a1ee91..79ee1946b 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -3581,6 +3581,7 @@ static void init_all(Tcl_Interp *interp){ extern int Sqlitequota_Init(Tcl_Interp*); extern int Sqlitemultiplex_Init(Tcl_Interp*); extern int SqliteSuperlock_Init(Tcl_Interp*); + extern int SqlitetestSyscall_Init(Tcl_Interp*); extern int Sqlitetestfuzzer_Init(Tcl_Interp*); #ifdef SQLITE_ENABLE_ZIPVFS @@ -3619,6 +3620,7 @@ static void init_all(Tcl_Interp *interp){ Sqlitequota_Init(interp); Sqlitemultiplex_Init(interp); SqliteSuperlock_Init(interp); + SqlitetestSyscall_Init(interp); Sqlitetestfuzzer_Init(interp); Tcl_CreateObjCommand(interp,"load_testfixture_extensions",init_all_cmd,0,0); diff --git a/src/test1.c b/src/test1.c index 852ecdecc..8a0d09a71 100644 --- a/src/test1.c +++ b/src/test1.c @@ -4889,6 +4889,44 @@ static int file_control_chunksize_test( } /* +** tclcmd: file_control_sizehint_test DB DBNAME SIZE +** +** This TCL command runs the sqlite3_file_control interface and +** verifies correct operation of the SQLITE_GET_LOCKPROXYFILE and +** SQLITE_SET_LOCKPROXYFILE verbs. +*/ +static int file_control_sizehint_test( + ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + sqlite3_int64 nSize; /* Hinted size */ + char *zDb; /* Db name ("main", "temp" etc.) */ + sqlite3 *db; /* Database handle */ + int rc; /* file_control() return code */ + + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME SIZE"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) + || Tcl_GetWideIntFromObj(interp, objv[3], &nSize) + ){ + return TCL_ERROR; + } + zDb = Tcl_GetString(objv[2]); + if( zDb[0]=='\0' ) zDb = NULL; + + rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_SIZE_HINT, (void *)&nSize); + if( rc ){ + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); + return TCL_ERROR; + } + return TCL_OK; +} + +/* ** tclcmd: file_control_lockproxy_test DB PWD ** ** This TCL command runs the sqlite3_file_control interface and @@ -5608,6 +5646,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "file_control_lasterrno_test", file_control_lasterrno_test, 0 }, { "file_control_lockproxy_test", file_control_lockproxy_test, 0 }, { "file_control_chunksize_test", file_control_chunksize_test, 0 }, + { "file_control_sizehint_test", file_control_sizehint_test, 0 }, { "sqlite3_vfs_list", vfs_list, 0 }, { "sqlite3_create_function_v2", test_create_function_v2, 0 }, diff --git a/src/test_hexio.c b/src/test_hexio.c index 02bd60c8a..e3258e869 100644 --- a/src/test_hexio.c +++ b/src/test_hexio.c @@ -312,8 +312,13 @@ static int utf8_to_utf8( sqlite3TestBinToHex(z,nOut); Tcl_AppendResult(interp, (char*)z, 0); sqlite3_free(z); -#endif return TCL_OK; +#else + Tcl_AppendResult(interp, + "[utf8_to_utf8] unavailable - SQLITE_DEBUG not defined", 0 + ); + return TCL_ERROR; +#endif } static int getFts3Varint(const char *p, sqlite_int64 *v){ diff --git a/src/test_multiplex.c b/src/test_multiplex.c index 72a71621b..d8a7db86e 100644 --- a/src/test_multiplex.c +++ b/src/test_multiplex.c @@ -22,7 +22,22 @@ #include "sqlite3.h" #include <string.h> #include <assert.h> -#include "sqliteInt.h" +#include "test_multiplex.h" + +#ifndef SQLITE_CORE + #define SQLITE_CORE 1 /* Disable the API redefinition in sqlite3ext.h */ +#endif +#include "sqlite3ext.h" + +/* +** These should be defined to be the same as the values in +** sqliteInt.h. They are defined seperately here so that +** the multiplex VFS shim can be built as a loadable +** module. +*/ +#define UNUSED_PARAMETER(x) (void)(x) +#define MAX_PAGE_SIZE 0x10000 +#define DEFAULT_SECTOR_SIZE 0x1000 /* ** For a build without mutexes, no-op the mutex calls. @@ -40,14 +55,18 @@ /************************ Shim Definitions ******************************/ +#define SQLITE_MULTIPLEX_VFS_NAME "multiplex" + /* This is the limit on the chunk size. It may be changed by calling -** the sqlite3_multiplex_set() interface. +** the xFileControl() interface. It will be rounded up to a +** multiple of MAX_PAGE_SIZE. We default it here to 1GB. */ -#define SQLITE_MULTIPLEX_CHUNK_SIZE 0x40000000 +#define SQLITE_MULTIPLEX_CHUNK_SIZE (MAX_PAGE_SIZE*16384) + /* Default limit on number of chunks. Care should be taken ** so that values for chunks numbers fit in the SQLITE_MULTIPLEX_EXT_FMT ** format specifier. It may be changed by calling -** the sqlite3_multiplex_set() interface. +** the xFileControl() interface. */ #define SQLITE_MULTIPLEX_MAX_CHUNKS 32 @@ -82,6 +101,9 @@ struct multiplexGroup { char *zName; /* Base filename of this group */ int nName; /* Length of base filename */ int flags; /* Flags used for original opening */ + int nChunkSize; /* Chunk size used for this group */ + int nMaxChunks; /* Max number of chunks for this group */ + int bEnabled; /* TRUE to use Multiplex VFS for this file */ multiplexGroup *pNext, *pPrev; /* Doubly linked list of all group objects */ }; @@ -140,11 +162,6 @@ static struct { */ multiplexGroup *pGroups; - /* Chunk params. - */ - int nChunkSize; - int nMaxChunks; - /* Storage for temp file names. Allocated during ** initialization to the max pathname of the underlying VFS. */ @@ -160,13 +177,28 @@ static struct { static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); } static void multiplexLeave(void){ sqlite3_mutex_leave(gMultiplex.pMutex); } +/* +** Compute a string length that is limited to what can be stored in +** lower 30 bits of a 32-bit signed integer. +** +** The value returned will never be negative. Nor will it ever be greater +** than the actual length of the string. For very long strings (greater +** than 1GiB) the value returned might be less than the true string length. +*/ +int multiplexStrlen30(const char *z){ + const char *z2 = z; + if( z==0 ) return 0; + while( *z2 ){ z2++; } + return 0x3fffffff & (int)(z2 - z); +} + /* Translate an sqlite3_file* that is really a multiplexGroup* into ** the sqlite3_file* for the underlying original VFS. */ static sqlite3_file *multiplexSubOpen(multiplexConn *pConn, int iChunk, int *rc, int *pOutFlags){ multiplexGroup *pGroup = pConn->pGroup; sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ - if( iChunk<gMultiplex.nMaxChunks ){ + if( iChunk<pGroup->nMaxChunks ){ sqlite3_file *pSubOpen = pGroup->pReal[iChunk]; /* Real file descriptor */ if( !pGroup->bOpen[iChunk] ){ memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); @@ -191,6 +223,62 @@ static sqlite3_file *multiplexSubOpen(multiplexConn *pConn, int iChunk, int *rc, return NULL; } +/* +** This is the implementation of the multiplex_control() SQL function. +*/ +static void multiplexControlFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int rc = SQLITE_OK; + sqlite3 *db = sqlite3_context_db_handle(context); + int op; + int iVal; + + if( !db || argc!=2 ){ + rc = SQLITE_ERROR; + }else{ + /* extract params */ + op = sqlite3_value_int(argv[0]); + iVal = sqlite3_value_int(argv[1]); + /* map function op to file_control op */ + switch( op ){ + case 1: + op = MULTIPLEX_CTRL_ENABLE; + break; + case 2: + op = MULTIPLEX_CTRL_SET_CHUNK_SIZE; + break; + case 3: + op = MULTIPLEX_CTRL_SET_MAX_CHUNKS; + break; + default: + rc = SQLITE_NOTFOUND; + break; + } + } + if( rc==SQLITE_OK ){ + rc = sqlite3_file_control(db, 0, op, &iVal); + } + sqlite3_result_error_code(context, rc); +} + +/* +** This is the entry point to register the auto-extension for the +** multiplex_control() function. +*/ +static int multiplexFuncInit( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc; + rc = sqlite3_create_function(db, "multiplex_control", 2, SQLITE_ANY, + 0, multiplexControlFunc, 0, 0); + return rc; +} + /************************* VFS Method Wrappers *****************************/ /* @@ -212,7 +300,7 @@ static int multiplexOpen( multiplexGroup *pGroup; /* Corresponding multiplexGroup object */ sqlite3_file *pSubOpen; /* Real file descriptor */ sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ - int nName = sqlite3Strlen30(zName); + int nName = multiplexStrlen30(zName); int i; int sz; @@ -224,11 +312,11 @@ static int multiplexOpen( multiplexEnter(); pMultiplexOpen = (multiplexConn*)pConn; /* allocate space for group */ - sz = sizeof(multiplexGroup) /* multiplexGroup */ - + (sizeof(sqlite3_file *)*gMultiplex.nMaxChunks) /* pReal[] */ - + (pOrigVfs->szOsFile*gMultiplex.nMaxChunks) /* *pReal */ - + gMultiplex.nMaxChunks /* bOpen[] */ - + nName + 1; /* zName */ + sz = sizeof(multiplexGroup) /* multiplexGroup */ + + (sizeof(sqlite3_file *)*SQLITE_MULTIPLEX_MAX_CHUNKS) /* pReal[] */ + + (pOrigVfs->szOsFile*SQLITE_MULTIPLEX_MAX_CHUNKS) /* *pReal */ + + SQLITE_MULTIPLEX_MAX_CHUNKS /* bOpen[] */ + + nName + 1; /* zName */ #ifndef SQLITE_MULTIPLEX_EXT_OVWR sz += SQLITE_MULTIPLEX_EXT_SZ; assert(nName+SQLITE_MULTIPLEX_EXT_SZ < pOrigVfs->mxPathname); @@ -244,14 +332,18 @@ static int multiplexOpen( char *p = (char *)&pGroup[1]; pMultiplexOpen->pGroup = pGroup; memset(pGroup, 0, sz); + pGroup->bEnabled = -1; + pGroup->nChunkSize = SQLITE_MULTIPLEX_CHUNK_SIZE; + pGroup->nMaxChunks = SQLITE_MULTIPLEX_MAX_CHUNKS; pGroup->pReal = (sqlite3_file **)p; - p += (sizeof(sqlite3_file *)*gMultiplex.nMaxChunks); - for(i=0; i<gMultiplex.nMaxChunks; i++){ + p += (sizeof(sqlite3_file *)*pGroup->nMaxChunks); + for(i=0; i<pGroup->nMaxChunks; i++){ pGroup->pReal[i] = (sqlite3_file *)p; p += pOrigVfs->szOsFile; } + /* bOpen[] vals should all be zero from memset above */ pGroup->bOpen = p; - p += gMultiplex.nMaxChunks; + p += pGroup->nMaxChunks; pGroup->zName = p; /* save off base filename, name length, and original open flags */ memcpy(pGroup->zName, zName, nName+1); @@ -259,6 +351,14 @@ static int multiplexOpen( pGroup->flags = flags; pSubOpen = multiplexSubOpen(pMultiplexOpen, 0, &rc, pOutFlags); if( pSubOpen ){ + /* if this file is already larger than chunk size, disable + ** the multiplex feature. + */ + sqlite3_int64 sz; + int rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); + if( (rc2==SQLITE_OK) && (sz>pGroup->nChunkSize) ){ + pGroup->bEnabled = 0; + } if( pSubOpen->pMethods->iVersion==1 ){ pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV1; }else{ @@ -288,24 +388,29 @@ static int multiplexDelete( ){ sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ int rc = SQLITE_OK; - int nName = sqlite3Strlen30(zName); + int nName = multiplexStrlen30(zName); int i; UNUSED_PARAMETER(pVfs); multiplexEnter(); memcpy(gMultiplex.zName, zName, nName+1); - for(i=0; i<gMultiplex.nMaxChunks; i++){ + for(i=0; i<SQLITE_MULTIPLEX_MAX_CHUNKS; i++){ int rc2; int exists = 0; if( i ){ #ifdef SQLITE_MULTIPLEX_EXT_OVWR - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i); + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, + gMultiplex.zName+nName-SQLITE_MULTIPLEX_EXT_SZ, + SQLITE_MULTIPLEX_EXT_FMT, i); #else - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+nName, SQLITE_MULTIPLEX_EXT_FMT, i); + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, + gMultiplex.zName+nName, + SQLITE_MULTIPLEX_EXT_FMT, i); #endif } - rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, SQLITE_ACCESS_EXISTS, &exists); + rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, + SQLITE_ACCESS_EXISTS, &exists); if( rc2==SQLITE_OK && exists){ /* if it exists, delete it */ rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, syncDir); @@ -367,7 +472,7 @@ static int multiplexClose(sqlite3_file *pConn){ int i; multiplexEnter(); /* close any open handles */ - for(i=0; i<gMultiplex.nMaxChunks; i++){ + for(i=0; i<pGroup->nMaxChunks; i++){ if( pGroup->bOpen[i] ){ sqlite3_file *pSubOpen = pGroup->pReal[i]; int rc2 = pSubOpen->pMethods->xClose(pSubOpen); @@ -398,23 +503,29 @@ static int multiplexRead( sqlite3_int64 iOfst ){ multiplexConn *p = (multiplexConn*)pConn; + multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; multiplexEnter(); - while( iAmt > 0 ){ - int i = (int)(iOfst/gMultiplex.nChunkSize); - sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); - if( pSubOpen ){ - int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize; - if( extra<0 ) extra = 0; - iAmt -= extra; - rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize); - if( rc!=SQLITE_OK ) break; - pBuf = (char *)pBuf + iAmt; - iOfst += iAmt; - iAmt = extra; - }else{ - rc = SQLITE_IOERR_READ; - break; + if( !pGroup->bEnabled ){ + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + rc = ( !pSubOpen ) ? SQLITE_IOERR_READ : pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst); + }else{ + while( iAmt > 0 ){ + int i = (int)(iOfst / pGroup->nChunkSize); + sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); + if( pSubOpen ){ + int extra = ((int)(iOfst % pGroup->nChunkSize) + iAmt) - pGroup->nChunkSize; + if( extra<0 ) extra = 0; + iAmt -= extra; + rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst % pGroup->nChunkSize); + if( rc!=SQLITE_OK ) break; + pBuf = (char *)pBuf + iAmt; + iOfst += iAmt; + iAmt = extra; + }else{ + rc = SQLITE_IOERR_READ; + break; + } } } multiplexLeave(); @@ -432,23 +543,29 @@ static int multiplexWrite( sqlite3_int64 iOfst ){ multiplexConn *p = (multiplexConn*)pConn; + multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; multiplexEnter(); - while( iAmt > 0 ){ - int i = (int)(iOfst/gMultiplex.nChunkSize); - sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); - if( pSubOpen ){ - int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize; - if( extra<0 ) extra = 0; - iAmt -= extra; - rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize); - if( rc!=SQLITE_OK ) break; - pBuf = (char *)pBuf + iAmt; - iOfst += iAmt; - iAmt = extra; - }else{ - rc = SQLITE_IOERR_WRITE; - break; + if( !pGroup->bEnabled ){ + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + rc = ( !pSubOpen ) ? SQLITE_IOERR_WRITE : pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst); + }else{ + while( iAmt > 0 ){ + int i = (int)(iOfst / pGroup->nChunkSize); + sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); + if( pSubOpen ){ + int extra = ((int)(iOfst % pGroup->nChunkSize) + iAmt) - pGroup->nChunkSize; + if( extra<0 ) extra = 0; + iAmt -= extra; + rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst % pGroup->nChunkSize); + if( rc!=SQLITE_OK ) break; + pBuf = (char *)pBuf + iAmt; + iOfst += iAmt; + iAmt = extra; + }else{ + rc = SQLITE_IOERR_WRITE; + break; + } } } multiplexLeave(); @@ -463,35 +580,44 @@ static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){ multiplexConn *p = (multiplexConn*)pConn; multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; - int rc2; - int i; - sqlite3_file *pSubOpen; - sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ multiplexEnter(); - memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); - /* delete the chunks above the truncate limit */ - for(i=(int)(size/gMultiplex.nChunkSize)+1; i<gMultiplex.nMaxChunks; i++){ - /* close any open chunks before deleting them */ - if( pGroup->bOpen[i] ){ - pSubOpen = pGroup->pReal[i]; - rc2 = pSubOpen->pMethods->xClose(pSubOpen); - if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; - pGroup->bOpen[i] = 0; - } + if( !pGroup->bEnabled ){ + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + rc = ( !pSubOpen ) ? SQLITE_IOERR_TRUNCATE : pSubOpen->pMethods->xTruncate(pSubOpen, size); + }else{ + int rc2; + int i; + sqlite3_file *pSubOpen; + sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ + memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); + /* delete the chunks above the truncate limit */ + for(i=(int)(size / pGroup->nChunkSize)+1; i<pGroup->nMaxChunks; i++){ + /* close any open chunks before deleting them */ + if( pGroup->bOpen[i] ){ + pSubOpen = pGroup->pReal[i]; + rc2 = pSubOpen->pMethods->xClose(pSubOpen); + if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; + pGroup->bOpen[i] = 0; + } #ifdef SQLITE_MULTIPLEX_EXT_OVWR - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i); + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, + gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, + SQLITE_MULTIPLEX_EXT_FMT, i); #else - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, i); + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, + gMultiplex.zName+pGroup->nName, + SQLITE_MULTIPLEX_EXT_FMT, i); #endif - rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, 0); - if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; - } - pSubOpen = multiplexSubOpen(p, (int)(size/gMultiplex.nChunkSize), &rc2, NULL); - if( pSubOpen ){ - rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size%gMultiplex.nChunkSize); - if( rc2!=SQLITE_OK ) rc = rc2; - }else{ - rc = SQLITE_IOERR_TRUNCATE; + rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, 0); + if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; + } + pSubOpen = multiplexSubOpen(p, (int)(size / pGroup->nChunkSize), &rc2, NULL); + if( pSubOpen ){ + rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size % pGroup->nChunkSize); + if( rc2!=SQLITE_OK ) rc = rc2; + }else{ + rc = SQLITE_IOERR_TRUNCATE; + } } multiplexLeave(); return rc; @@ -505,7 +631,7 @@ static int multiplexSync(sqlite3_file *pConn, int flags){ int rc = SQLITE_OK; int i; multiplexEnter(); - for(i=0; i<gMultiplex.nMaxChunks; i++){ + for(i=0; i<pGroup->nMaxChunks; i++){ /* if we don't have it open, we don't need to sync it */ if( pGroup->bOpen[i] ){ sqlite3_file *pSubOpen = pGroup->pReal[i]; @@ -527,46 +653,56 @@ static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){ int rc2; int i; multiplexEnter(); - *pSize = 0; - for(i=0; i<gMultiplex.nMaxChunks; i++){ - sqlite3_file *pSubOpen = NULL; - /* if not opened already, check to see if the chunk exists */ - if( pGroup->bOpen[i] ){ - pSubOpen = pGroup->pReal[i]; - }else{ - sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ - int exists = 0; - memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); - if( i ){ + if( !pGroup->bEnabled ){ + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + rc = ( !pSubOpen ) ? SQLITE_IOERR_FSTAT : pSubOpen->pMethods->xFileSize(pSubOpen, pSize); + }else{ + *pSize = 0; + for(i=0; i<pGroup->nMaxChunks; i++){ + sqlite3_file *pSubOpen = NULL; + /* if not opened already, check to see if the chunk exists */ + if( pGroup->bOpen[i] ){ + pSubOpen = pGroup->pReal[i]; + }else{ + sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ + int exists = 0; + memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); + if( i ){ #ifdef SQLITE_MULTIPLEX_EXT_OVWR - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i); + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, + gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, + SQLITE_MULTIPLEX_EXT_FMT, i); #else - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, i); + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, + gMultiplex.zName+pGroup->nName, + SQLITE_MULTIPLEX_EXT_FMT, i); #endif + } + rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, + SQLITE_ACCESS_EXISTS, &exists); + if( rc2==SQLITE_OK && exists){ + /* if it exists, open it */ + pSubOpen = multiplexSubOpen(p, i, &rc, NULL); + }else{ + /* stop at first "gap" */ + break; + } } - rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, SQLITE_ACCESS_EXISTS, &exists); - if( rc2==SQLITE_OK && exists){ - /* if it exists, open it */ - pSubOpen = multiplexSubOpen(p, i, &rc, NULL); + if( pSubOpen ){ + sqlite3_int64 sz; + rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); + if( rc2!=SQLITE_OK ){ + rc = rc2; + }else{ + if( sz>pGroup->nChunkSize ){ + rc = SQLITE_IOERR_FSTAT; + } + *pSize += sz; + } }else{ - /* stop at first "gap" */ break; } } - if( pSubOpen ){ - sqlite3_int64 sz; - rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); - if( rc2!=SQLITE_OK ){ - rc = rc2; - }else{ - if( sz>gMultiplex.nChunkSize ){ - rc = SQLITE_IOERR_FSTAT; - } - *pSize += sz; - } - }else{ - break; - } } multiplexLeave(); return rc; @@ -608,18 +744,62 @@ static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){ return SQLITE_IOERR_CHECKRESERVEDLOCK; } -/* Pass xFileControl requests through to the original VFS unchanged. +/* Pass xFileControl requests through to the original VFS unchanged, +** except for any MULTIPLEX_CTRL_* requests here. */ static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){ multiplexConn *p = (multiplexConn*)pConn; - int rc; + multiplexGroup *pGroup = p->pGroup; + int rc = SQLITE_ERROR; sqlite3_file *pSubOpen; - if ( op==SQLITE_FCNTL_SIZE_HINT || op==SQLITE_FCNTL_CHUNK_SIZE ) return SQLITE_OK; - pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); - if( pSubOpen ){ - return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); + + if( !gMultiplex.isInitialized ) return SQLITE_MISUSE; + switch( op ){ + case MULTIPLEX_CTRL_ENABLE: + if( pArg ) { + int bEnabled = *(int *)pArg; + pGroup->bEnabled = bEnabled; + rc = SQLITE_OK; + } + break; + case MULTIPLEX_CTRL_SET_CHUNK_SIZE: + if( pArg ) { + int nChunkSize = *(int *)pArg; + if( nChunkSize<1 ){ + rc = SQLITE_MISUSE; + }else{ + /* Round up to nearest multiple of MAX_PAGE_SIZE. */ + nChunkSize = (nChunkSize + (MAX_PAGE_SIZE-1)); + nChunkSize &= ~(MAX_PAGE_SIZE-1); + pGroup->nChunkSize = nChunkSize; + rc = SQLITE_OK; + } + } + break; + case MULTIPLEX_CTRL_SET_MAX_CHUNKS: + if( pArg ) { + int nMaxChunks = *(int *)pArg; + if(( nMaxChunks<1 ) || ( nMaxChunks>SQLITE_MULTIPLEX_MAX_CHUNKS )){ + rc = SQLITE_MISUSE; + }else{ + pGroup->nMaxChunks = nMaxChunks; + rc = SQLITE_OK; + } + } + break; + case SQLITE_FCNTL_SIZE_HINT: + case SQLITE_FCNTL_CHUNK_SIZE: + /* no-op these */ + rc = SQLITE_OK; + break; + default: + pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + if( pSubOpen ){ + rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); + } + break; } - return SQLITE_ERROR; + return rc; } /* Pass xSectorSize requests through to the original VFS unchanged. @@ -631,7 +811,7 @@ static int multiplexSectorSize(sqlite3_file *pConn){ if( pSubOpen ){ return pSubOpen->pMethods->xSectorSize(pSubOpen); } - return SQLITE_DEFAULT_SECTOR_SIZE; + return DEFAULT_SECTOR_SIZE; } /* Pass xDeviceCharacteristics requests through to the original VFS unchanged. @@ -706,9 +886,10 @@ static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){ /************************** Public Interfaces *****************************/ /* -** Initialize the multiplex VFS shim. Use the VFS named zOrigVfsName -** as the VFS that does the actual work. Use the default if -** zOrigVfsName==NULL. +** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize() +** +** Use the VFS named zOrigVfsName as the VFS that does the actual work. +** Use the default if zOrigVfsName==NULL. ** ** The multiplex VFS shim is named "multiplex". It will become the default ** VFS if makeDefault is non-zero. @@ -731,14 +912,12 @@ int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){ sqlite3_mutex_free(gMultiplex.pMutex); return SQLITE_NOMEM; } - gMultiplex.nChunkSize = SQLITE_MULTIPLEX_CHUNK_SIZE; - gMultiplex.nMaxChunks = SQLITE_MULTIPLEX_MAX_CHUNKS; gMultiplex.pGroups = NULL; gMultiplex.isInitialized = 1; gMultiplex.pOrigVfs = pOrigVfs; gMultiplex.sThisVfs = *pOrigVfs; gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn); - gMultiplex.sThisVfs.zName = "multiplex"; + gMultiplex.sThisVfs.zName = SQLITE_MULTIPLEX_VFS_NAME; gMultiplex.sThisVfs.xOpen = multiplexOpen; gMultiplex.sThisVfs.xDelete = multiplexDelete; gMultiplex.sThisVfs.xAccess = multiplexAccess; @@ -773,11 +952,14 @@ int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){ gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier; gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap; sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault); + + sqlite3_auto_extension((void*)multiplexFuncInit); + return SQLITE_OK; } /* -** Shutdown the multiplex system. +** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown() ** ** All SQLite database connections must be closed before calling this ** routine. @@ -796,31 +978,9 @@ int sqlite3_multiplex_shutdown(void){ return SQLITE_OK; } -/* -** Adjust chunking params. VFS should be initialized first. -** No files should be open. Re-intializing will reset these -** to the default. -*/ -int sqlite3_multiplex_set( - int nChunkSize, /* Max chunk size */ - int nMaxChunks /* Max number of chunks */ -){ - if( !gMultiplex.isInitialized ) return SQLITE_MISUSE; - if( gMultiplex.pGroups ) return SQLITE_MISUSE; - if( nChunkSize<32 ) return SQLITE_MISUSE; - if( nMaxChunks<1 ) return SQLITE_MISUSE; - if( nMaxChunks>99 ) return SQLITE_MISUSE; - multiplexEnter(); - gMultiplex.nChunkSize = nChunkSize; - gMultiplex.nMaxChunks = nMaxChunks; - multiplexLeave(); - return SQLITE_OK; -} - /***************************** Test Code ***********************************/ #ifdef SQLITE_TEST #include <tcl.h> - extern const char *sqlite3TestErrorName(int); @@ -881,36 +1041,6 @@ static int test_multiplex_shutdown( } /* -** tclcmd: sqlite3_multiplex_set CHUNK_SIZE MAX_CHUNKS -*/ -static int test_multiplex_set( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - int nChunkSize; /* Max chunk size */ - int nMaxChunks; /* Max number of chunks */ - int rc; /* Value returned by sqlite3_multiplex_set() */ - - UNUSED_PARAMETER(clientData); - - /* Process arguments */ - if( objc!=3 ){ - Tcl_WrongNumArgs(interp, 1, objv, "CHUNK_SIZE MAX_CHUNKS"); - return TCL_ERROR; - } - if( Tcl_GetIntFromObj(interp, objv[1], &nChunkSize) ) return TCL_ERROR; - if( Tcl_GetIntFromObj(interp, objv[2], &nMaxChunks) ) return TCL_ERROR; - - /* Invoke sqlite3_multiplex_set() */ - rc = sqlite3_multiplex_set(nChunkSize, nMaxChunks); - - Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); - return TCL_OK; -} - -/* ** tclcmd: sqlite3_multiplex_dump */ static int test_multiplex_dump( @@ -943,16 +1073,16 @@ static int test_multiplex_dump( Tcl_NewIntObj(pGroup->flags)); /* count number of chunks with open handles */ - for(i=0; i<gMultiplex.nMaxChunks; i++){ + for(i=0; i<pGroup->nMaxChunks; i++){ if( pGroup->bOpen[i] ) nChunks++; } Tcl_ListObjAppendElement(interp, pGroupTerm, Tcl_NewIntObj(nChunks)); Tcl_ListObjAppendElement(interp, pGroupTerm, - Tcl_NewIntObj(gMultiplex.nChunkSize)); + Tcl_NewIntObj(pGroup->nChunkSize)); Tcl_ListObjAppendElement(interp, pGroupTerm, - Tcl_NewIntObj(gMultiplex.nMaxChunks)); + Tcl_NewIntObj(pGroup->nMaxChunks)); Tcl_ListObjAppendElement(interp, pResult, pGroupTerm); } @@ -962,6 +1092,68 @@ static int test_multiplex_dump( } /* +** Tclcmd: test_multiplex_control HANDLE DBNAME SUB-COMMAND ?INT-VALUE? +*/ +static int test_multiplex_control( + ClientData cd, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; /* Return code from file_control() */ + int idx; /* Index in aSub[] */ + Tcl_CmdInfo cmdInfo; /* Command info structure for HANDLE */ + sqlite3 *db; /* Underlying db handle for HANDLE */ + int iValue = 0; + void *pArg = 0; + + struct SubCommand { + const char *zName; + int op; + int argtype; + } aSub[] = { + { "enable", MULTIPLEX_CTRL_ENABLE, 1 }, + { "chunk_size", MULTIPLEX_CTRL_SET_CHUNK_SIZE, 1 }, + { "max_chunks", MULTIPLEX_CTRL_SET_MAX_CHUNKS, 1 }, + { 0, 0, 0 } + }; + + if( objc!=5 ){ + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE DBNAME SUB-COMMAND INT-VALUE"); + return TCL_ERROR; + } + + if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){ + Tcl_AppendResult(interp, "expected database handle, got \"", 0); + Tcl_AppendResult(interp, Tcl_GetString(objv[1]), "\"", 0); + return TCL_ERROR; + }else{ + db = *(sqlite3 **)cmdInfo.objClientData; + } + + rc = Tcl_GetIndexFromObjStruct( + interp, objv[3], aSub, sizeof(aSub[0]), "sub-command", 0, &idx + ); + if( rc!=TCL_OK ) return rc; + + switch( aSub[idx].argtype ){ + case 1: + if( Tcl_GetIntFromObj(interp, objv[4], &iValue) ){ + return TCL_ERROR; + } + pArg = (void *)&iValue; + break; + default: + Tcl_WrongNumArgs(interp, 4, objv, "SUB-COMMAND"); + return TCL_ERROR; + } + + rc = sqlite3_file_control(db, Tcl_GetString(objv[2]), aSub[idx].op, pArg); + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); + return (rc==SQLITE_OK) ? TCL_OK : TCL_ERROR; +} + +/* ** This routine registers the custom TCL commands defined in this ** module. This should be the only procedure visible from outside ** of this module. @@ -973,8 +1165,8 @@ int Sqlitemultiplex_Init(Tcl_Interp *interp){ } aCmd[] = { { "sqlite3_multiplex_initialize", test_multiplex_initialize }, { "sqlite3_multiplex_shutdown", test_multiplex_shutdown }, - { "sqlite3_multiplex_set", test_multiplex_set }, { "sqlite3_multiplex_dump", test_multiplex_dump }, + { "sqlite3_multiplex_control", test_multiplex_control }, }; int i; diff --git a/src/test_multiplex.h b/src/test_multiplex.h new file mode 100644 index 000000000..ec1ba9bb2 --- /dev/null +++ b/src/test_multiplex.h @@ -0,0 +1,91 @@ +/* +** 2011 March 18 +** +** 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 a VFS "shim" - a layer that sits in between the +** pager and the real VFS. +** +** This particular shim enforces a multiplex system on DB files. +** This shim shards/partitions a single DB file into smaller +** "chunks" such that the total DB file size may exceed the maximum +** file size of the underlying file system. +** +*/ + +#ifndef _TEST_MULTIPLEX_H +#define _TEST_MULTIPLEX_H + +/* +** CAPI: File-control Operations Supported by Multiplex VFS +** +** Values interpreted by the xFileControl method of a Multiplex VFS db file-handle. +** +** MULTIPLEX_CTRL_ENABLE: +** This file control is used to enable or disable the multiplex +** shim. +** +** MULTIPLEX_CTRL_SET_CHUNK_SIZE: +** This file control is used to set the maximum allowed chunk +** size for a multiplex file set. The chunk size should be +** a multiple of SQLITE_MAX_PAGE_SIZE, and will be rounded up +** if not. +** +** MULTIPLEX_CTRL_SET_MAX_CHUNKS: +** This file control is used to set the maximum number of chunks +** allowed to be used for a mutliplex file set. +*/ +#define MULTIPLEX_CTRL_ENABLE 214014 +#define MULTIPLEX_CTRL_SET_CHUNK_SIZE 214015 +#define MULTIPLEX_CTRL_SET_MAX_CHUNKS 214016 + +/* +** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize() +** +** Use the VFS named zOrigVfsName as the VFS that does the actual work. +** Use the default if zOrigVfsName==NULL. +** +** The multiplex VFS shim is named "multiplex". It will become the default +** VFS if makeDefault is non-zero. +** +** An auto-extension is registered which will make the function +** multiplex_control() available to database connections. This +** function gives access to the xFileControl interface of the +** multiplex VFS shim. +** +** SELECT multiplex_control(<op>,<val>); +** +** <op>=1 MULTIPLEX_CTRL_ENABLE +** <val>=0 disable +** <val>=1 enable +** +** <op>=2 MULTIPLEX_CTRL_SET_CHUNK_SIZE +** <val> int, chunk size +** +** <op>=3 MULTIPLEX_CTRL_SET_MAX_CHUNKS +** <val> int, max chunks +** +** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once +** during start-up. +*/ +extern int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault); + +/* +** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown() +** +** All SQLite database connections must be closed before calling this +** routine. +** +** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while +** shutting down in order to free all remaining multiplex groups. +*/ +extern int sqlite3_multiplex_shutdown(void); + +#endif diff --git a/src/test_syscall.c b/src/test_syscall.c new file mode 100644 index 000000000..a757e66a5 --- /dev/null +++ b/src/test_syscall.c @@ -0,0 +1,654 @@ +/* +** 2011 March 28 +** +** 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. +** +************************************************************************* +** +** The code in this file implements a Tcl interface used to test error +** handling in the os_unix.c module. Wrapper functions that support fault +** injection are registered as the low-level OS functions using the +** xSetSystemCall() method of the VFS. The Tcl interface is as follows: +** +** +** test_syscall install LIST +** Install wrapper functions for all system calls in argument LIST. +** LIST must be a list consisting of zero or more of the following +** literal values: +** +** open close access getcwd stat fstat +** ftruncate fcntl read pread pread64 write +** pwrite pwrite64 fchmod fallocate +** +** test_syscall uninstall +** Uninstall all wrapper functions. +** +** test_syscall fault ?COUNT PERSIST? +** If [test_syscall fault] is invoked without the two arguments, fault +** injection is disabled. Otherwise, fault injection is configured to +** cause a failure on the COUNT'th next call to a system call with a +** wrapper function installed. A COUNT value of 1 means fail the next +** system call. +** +** Argument PERSIST is interpreted as a boolean. If true, the all +** system calls following the initial failure also fail. Otherwise, only +** the single transient failure is injected. +** +** test_syscall errno CALL ERRNO +** Set the value that the global "errno" is set to following a fault +** in call CALL. Argument CALL must be one of the system call names +** listed above (under [test_syscall install]). ERRNO is a symbolic +** name (i.e. "EACCES"). Not all errno codes are supported. Add extra +** to the aErrno table in function test_syscall_errno() below as +** required. +** +** test_syscall reset ?SYSTEM-CALL? +** With no argument, this is an alias for the [uninstall] command. However, +** this command uses a VFS call of the form: +** +** xSetSystemCall(pVfs, 0, 0); +** +** To restore the default system calls. The [uninstall] command restores +** each system call individually by calling (i.e.): +** +** xSetSystemCall(pVfs, "open", 0); +** +** With an argument, this command attempts to reset the system call named +** by the parameter using the same method as [uninstall]. +** +** test_syscall exists SYSTEM-CALL +** Return true if the named system call exists. Or false otherwise. +** +** test_syscall list +** Return a list of all system calls. The list is constructed using +** the xNextSystemCall() VFS method. +*/ + +#include "sqlite3.h" +#include "tcl.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#ifdef SQLITE_OS_UNIX + +/* From test1.c */ +extern const char *sqlite3TestErrorName(int); + +#include <sys/types.h> +#include <errno.h> + +static struct TestSyscallGlobal { + int bPersist; /* 1 for persistent errors, 0 for transient */ + int nCount; /* Fail after this many more calls */ + int nFail; /* Number of failures that have occurred */ +} gSyscall = { 0, 0 }; + +static int ts_open(const char *, int, int); +static int ts_close(int fd); +static int ts_access(const char *zPath, int mode); +static char *ts_getcwd(char *zPath, size_t nPath); +static int ts_stat(const char *zPath, struct stat *p); +static int ts_fstat(int fd, struct stat *p); +static int ts_ftruncate(int fd, off_t n); +static int ts_fcntl(int fd, int cmd, ... ); +static int ts_read(int fd, void *aBuf, size_t nBuf); +static int ts_pread(int fd, void *aBuf, size_t nBuf, off_t off); +static int ts_pread64(int fd, void *aBuf, size_t nBuf, off_t off); +static int ts_write(int fd, const void *aBuf, size_t nBuf); +static int ts_pwrite(int fd, const void *aBuf, size_t nBuf, off_t off); +static int ts_pwrite64(int fd, const void *aBuf, size_t nBuf, off_t off); +static int ts_fchmod(int fd, mode_t mode); +static int ts_fallocate(int fd, off_t off, off_t len); + + +struct TestSyscallArray { + const char *zName; + sqlite3_syscall_ptr xTest; + sqlite3_syscall_ptr xOrig; + int default_errno; /* Default value for errno following errors */ + int custom_errno; /* Current value for errno if error */ +} aSyscall[] = { + /* 0 */ { "open", (sqlite3_syscall_ptr)ts_open, 0, EACCES, 0 }, + /* 1 */ { "close", (sqlite3_syscall_ptr)ts_close, 0, 0, 0 }, + /* 2 */ { "access", (sqlite3_syscall_ptr)ts_access, 0, 0, 0 }, + /* 3 */ { "getcwd", (sqlite3_syscall_ptr)ts_getcwd, 0, 0, 0 }, + /* 4 */ { "stat", (sqlite3_syscall_ptr)ts_stat, 0, 0, 0 }, + /* 5 */ { "fstat", (sqlite3_syscall_ptr)ts_fstat, 0, 0, 0 }, + /* 6 */ { "ftruncate", (sqlite3_syscall_ptr)ts_ftruncate, 0, EIO, 0 }, + /* 7 */ { "fcntl", (sqlite3_syscall_ptr)ts_fcntl, 0, EACCES, 0 }, + /* 8 */ { "read", (sqlite3_syscall_ptr)ts_read, 0, 0, 0 }, + /* 9 */ { "pread", (sqlite3_syscall_ptr)ts_pread, 0, 0, 0 }, + /* 10 */ { "pread64", (sqlite3_syscall_ptr)ts_pread64, 0, 0, 0 }, + /* 11 */ { "write", (sqlite3_syscall_ptr)ts_write, 0, 0, 0 }, + /* 12 */ { "pwrite", (sqlite3_syscall_ptr)ts_pwrite, 0, 0, 0 }, + /* 13 */ { "pwrite64", (sqlite3_syscall_ptr)ts_pwrite64, 0, 0, 0 }, + /* 14 */ { "fchmod", (sqlite3_syscall_ptr)ts_fchmod, 0, 0, 0 }, + /* 15 */ { "fallocate", (sqlite3_syscall_ptr)ts_fallocate, 0, 0, 0 }, + { 0, 0, 0, 0, 0 } +}; + +#define orig_open ((int(*)(const char *, int, int))aSyscall[0].xOrig) +#define orig_close ((int(*)(int))aSyscall[1].xOrig) +#define orig_access ((int(*)(const char*,int))aSyscall[2].xOrig) +#define orig_getcwd ((char*(*)(char*,size_t))aSyscall[3].xOrig) +#define orig_stat ((int(*)(const char*,struct stat*))aSyscall[4].xOrig) +#define orig_fstat ((int(*)(int,struct stat*))aSyscall[5].xOrig) +#define orig_ftruncate ((int(*)(int,off_t))aSyscall[6].xOrig) +#define orig_fcntl ((int(*)(int,int,...))aSyscall[7].xOrig) +#define orig_read ((ssize_t(*)(int,void*,size_t))aSyscall[8].xOrig) +#define orig_pread ((ssize_t(*)(int,void*,size_t,off_t))aSyscall[9].xOrig) +#define orig_pread64 ((ssize_t(*)(int,void*,size_t,off_t))aSyscall[10].xOrig) +#define orig_write ((ssize_t(*)(int,const void*,size_t))aSyscall[11].xOrig) +#define orig_pwrite ((ssize_t(*)(int,const void*,size_t,off_t))\ + aSyscall[12].xOrig) +#define orig_pwrite64 ((ssize_t(*)(int,const void*,size_t,off_t))\ + aSyscall[13].xOrig) +#define orig_fchmod ((int(*)(int,mode_t))aSyscall[14].xOrig) +#define orig_fallocate ((int(*)(int,off_t,off_t))aSyscall[15].xOrig) + +/* +** This function is called exactly once from within each invocation of a +** system call wrapper in this file. It returns 1 if the function should +** fail, or 0 if it should succeed. +*/ +static int tsIsFail(void){ + gSyscall.nCount--; + if( gSyscall.nCount==0 || (gSyscall.nFail && gSyscall.bPersist) ){ + gSyscall.nFail++; + return 1; + } + return 0; +} + +/* +** Return the current error-number value for function zFunc. zFunc must be +** the name of a system call in the aSyscall[] table. +** +** Usually, the current error-number is the value that errno should be set +** to if the named system call fails. The exception is "fallocate". See +** comments above the implementation of ts_fallocate() for details. +*/ +static int tsErrno(const char *zFunc){ + int i; + int nFunc = strlen(zFunc); + for(i=0; aSyscall[i].zName; i++){ + if( strlen(aSyscall[i].zName)!=nFunc ) continue; + if( memcmp(aSyscall[i].zName, zFunc, nFunc) ) continue; + return aSyscall[i].custom_errno; + } + + assert(0); + return 0; +} + +/* +** A wrapper around tsIsFail(). If tsIsFail() returns non-zero, set the +** value of errno before returning. +*/ +static int tsIsFailErrno(const char *zFunc){ + if( tsIsFail() ){ + errno = tsErrno(zFunc); + return 1; + } + return 0; +} + +/* +** A wrapper around open(). +*/ +static int ts_open(const char *zFile, int flags, int mode){ + if( tsIsFailErrno("open") ){ + return -1; + } + return orig_open(zFile, flags, mode); +} + +/* +** A wrapper around close(). +*/ +static int ts_close(int fd){ + if( tsIsFail() ){ + /* Even if simulating an error, close the original file-descriptor. + ** This is to stop the test process from running out of file-descriptors + ** when running a long test. If a call to close() appears to fail, SQLite + ** never attempts to use the file-descriptor afterwards (or even to close + ** it a second time). */ + orig_close(fd); + return -1; + } + return orig_close(fd); +} + +/* +** A wrapper around access(). +*/ +static int ts_access(const char *zPath, int mode){ + if( tsIsFail() ){ + return -1; + } + return orig_access(zPath, mode); +} + +/* +** A wrapper around getcwd(). +*/ +static char *ts_getcwd(char *zPath, size_t nPath){ + if( tsIsFail() ){ + return NULL; + } + return orig_getcwd(zPath, nPath); +} + +/* +** A wrapper around stat(). +*/ +static int ts_stat(const char *zPath, struct stat *p){ + if( tsIsFail() ){ + return -1; + } + return orig_stat(zPath, p); +} + +/* +** A wrapper around fstat(). +*/ +static int ts_fstat(int fd, struct stat *p){ + if( tsIsFailErrno("fstat") ){ + return -1; + } + return orig_fstat(fd, p); +} + +/* +** A wrapper around ftruncate(). +*/ +static int ts_ftruncate(int fd, off_t n){ + if( tsIsFailErrno("ftruncate") ){ + return -1; + } + return orig_ftruncate(fd, n); +} + +/* +** A wrapper around fcntl(). +*/ +static int ts_fcntl(int fd, int cmd, ... ){ + va_list ap; + void *pArg; + if( tsIsFailErrno("fcntl") ){ + return -1; + } + va_start(ap, cmd); + pArg = va_arg(ap, void *); + return orig_fcntl(fd, cmd, pArg); +} + +/* +** A wrapper around read(). +*/ +static int ts_read(int fd, void *aBuf, size_t nBuf){ + if( tsIsFailErrno("read") ){ + return -1; + } + return orig_read(fd, aBuf, nBuf); +} + +/* +** A wrapper around pread(). +*/ +static int ts_pread(int fd, void *aBuf, size_t nBuf, off_t off){ + if( tsIsFailErrno("pread") ){ + return -1; + } + return orig_pread(fd, aBuf, nBuf, off); +} + +/* +** A wrapper around pread64(). +*/ +static int ts_pread64(int fd, void *aBuf, size_t nBuf, off_t off){ + if( tsIsFailErrno("pread64") ){ + return -1; + } + return orig_pread64(fd, aBuf, nBuf, off); +} + +/* +** A wrapper around write(). +*/ +static int ts_write(int fd, const void *aBuf, size_t nBuf){ + if( tsIsFailErrno("write") ){ + return -1; + } + return orig_write(fd, aBuf, nBuf); +} + +/* +** A wrapper around pwrite(). +*/ +static int ts_pwrite(int fd, const void *aBuf, size_t nBuf, off_t off){ + if( tsIsFailErrno("pwrite") ){ + return -1; + } + return orig_pwrite(fd, aBuf, nBuf, off); +} + +/* +** A wrapper around pwrite64(). +*/ +static int ts_pwrite64(int fd, const void *aBuf, size_t nBuf, off_t off){ + if( tsIsFailErrno("pwrite64") ){ + return -1; + } + return orig_pwrite64(fd, aBuf, nBuf, off); +} + +/* +** A wrapper around fchmod(). +*/ +static int ts_fchmod(int fd, mode_t mode){ + if( tsIsFail() ){ + return -1; + } + return orig_fchmod(fd, mode); +} + +/* +** A wrapper around fallocate(). +** +** SQLite assumes that the fallocate() function is compatible with +** posix_fallocate(). According to the Linux man page (2009-09-30): +** +** posix_fallocate() returns zero on success, or an error number on +** failure. Note that errno is not set. +*/ +static int ts_fallocate(int fd, off_t off, off_t len){ + if( tsIsFail() ){ + return tsErrno("fallocate"); + } + return orig_fallocate(fd, off, len); +} + +static int test_syscall_install( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3_vfs *pVfs; + int nElem; + int i; + Tcl_Obj **apElem; + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "SYSCALL-LIST"); + return TCL_ERROR; + } + if( Tcl_ListObjGetElements(interp, objv[2], &nElem, &apElem) ){ + return TCL_ERROR; + } + pVfs = sqlite3_vfs_find(0); + + for(i=0; i<nElem; i++){ + int iCall; + int rc = Tcl_GetIndexFromObjStruct(interp, + apElem[i], aSyscall, sizeof(aSyscall[0]), "system-call", 0, &iCall + ); + if( rc ) return rc; + if( aSyscall[iCall].xOrig==0 ){ + aSyscall[iCall].xOrig = pVfs->xGetSystemCall(pVfs, aSyscall[iCall].zName); + pVfs->xSetSystemCall(pVfs, aSyscall[iCall].zName, aSyscall[iCall].xTest); + } + aSyscall[iCall].custom_errno = aSyscall[iCall].default_errno; + } + + return TCL_OK; +} + +static int test_syscall_uninstall( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3_vfs *pVfs; + int i; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + + pVfs = sqlite3_vfs_find(0); + for(i=0; aSyscall[i].zName; i++){ + if( aSyscall[i].xOrig ){ + pVfs->xSetSystemCall(pVfs, aSyscall[i].zName, 0); + aSyscall[i].xOrig = 0; + } + } + return TCL_OK; +} + +static int test_syscall_reset( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3_vfs *pVfs; + int i; + int rc; + + if( objc!=2 && objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + + pVfs = sqlite3_vfs_find(0); + if( objc==2 ){ + rc = pVfs->xSetSystemCall(pVfs, 0, 0); + for(i=0; aSyscall[i].zName; i++) aSyscall[i].xOrig = 0; + }else{ + int nFunc; + char *zFunc = Tcl_GetStringFromObj(objv[2], &nFunc); + rc = pVfs->xSetSystemCall(pVfs, Tcl_GetString(objv[2]), 0); + for(i=0; rc==SQLITE_OK && aSyscall[i].zName; i++){ + if( strlen(aSyscall[i].zName)!=nFunc ) continue; + if( memcmp(aSyscall[i].zName, zFunc, nFunc) ) continue; + aSyscall[i].xOrig = 0; + } + } + if( rc!=SQLITE_OK ){ + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3TestErrorName(rc), -1)); + return TCL_ERROR; + } + + Tcl_ResetResult(interp); + return TCL_OK; +} + +static int test_syscall_exists( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3_vfs *pVfs; + sqlite3_syscall_ptr x; + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + + pVfs = sqlite3_vfs_find(0); + x = pVfs->xGetSystemCall(pVfs, Tcl_GetString(objv[2])); + + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(x!=0)); + return TCL_OK; +} + +static int test_syscall_fault( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int nCount = 0; + int bPersist = 0; + + if( objc!=2 && objc!=4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?COUNT PERSIST?"); + return TCL_ERROR; + } + + if( objc==4 ){ + if( Tcl_GetIntFromObj(interp, objv[2], &nCount) + || Tcl_GetBooleanFromObj(interp, objv[3], &bPersist) + ){ + return TCL_ERROR; + } + } + + Tcl_SetObjResult(interp, Tcl_NewIntObj(gSyscall.nFail)); + gSyscall.nCount = nCount; + gSyscall.bPersist = bPersist; + gSyscall.nFail = 0; + return TCL_OK; +} + +static int test_syscall_errno( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int iCall; + int iErrno; + int rc; + + struct Errno { + const char *z; + int i; + } aErrno[] = { + { "EACCES", EACCES }, + { "EINTR", EINTR }, + { "EIO", EIO }, + { "EOVERFLOW", EOVERFLOW }, + { "ENOMEM", ENOMEM }, + { "EAGAIN", EAGAIN }, + { "ETIMEDOUT", ETIMEDOUT }, + { "EBUSY", EBUSY }, + { "EPERM", EPERM }, + { "EDEADLK", EDEADLK }, + { "ENOLCK", ENOLCK }, + { 0, 0 } + }; + + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "SYSCALL ERRNO"); + return TCL_ERROR; + } + + rc = Tcl_GetIndexFromObjStruct(interp, + objv[2], aSyscall, sizeof(aSyscall[0]), "system-call", 0, &iCall + ); + if( rc!=TCL_OK ) return rc; + rc = Tcl_GetIndexFromObjStruct(interp, + objv[3], aErrno, sizeof(aErrno[0]), "errno", 0, &iErrno + ); + if( rc!=TCL_OK ) return rc; + + aSyscall[iCall].custom_errno = aErrno[iErrno].i; + return TCL_OK; +} + +static int test_syscall_list( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + const char *zSys; + sqlite3_vfs *pVfs; + Tcl_Obj *pList; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + + pVfs = sqlite3_vfs_find(0); + pList = Tcl_NewObj(); + Tcl_IncrRefCount(pList); + for(zSys = pVfs->xNextSystemCall(pVfs, 0); + zSys!=0; + zSys = pVfs->xNextSystemCall(pVfs, zSys) + ){ + Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj(zSys, -1)); + } + + Tcl_SetObjResult(interp, pList); + Tcl_DecrRefCount(pList); + return TCL_OK; +} + +static int test_syscall( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + struct SyscallCmd { + const char *zName; + Tcl_ObjCmdProc *xCmd; + } aCmd[] = { + { "fault", test_syscall_fault }, + { "install", test_syscall_install }, + { "uninstall", test_syscall_uninstall }, + { "reset", test_syscall_reset }, + { "errno", test_syscall_errno }, + { "exists", test_syscall_exists }, + { "list", test_syscall_list }, + { 0, 0 } + }; + int iCmd; + int rc; + + if( objc<2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ..."); + return TCL_ERROR; + } + rc = Tcl_GetIndexFromObjStruct(interp, + objv[1], aCmd, sizeof(aCmd[0]), "sub-command", 0, &iCmd + ); + if( rc!=TCL_OK ) return rc; + return aCmd[iCmd].xCmd(clientData, interp, objc, objv); +} + +int SqlitetestSyscall_Init(Tcl_Interp *interp){ + struct SyscallCmd { + const char *zName; + Tcl_ObjCmdProc *xCmd; + } aCmd[] = { + { "test_syscall", test_syscall}, + }; + int i; + + for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){ + Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xCmd, 0, 0); + } + return TCL_OK; +} +#else +int SqlitetestSyscall_Init(Tcl_Interp *interp){ + return TCL_OK; +} +#endif + diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 36e0d260b..7cab12f78 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -559,6 +559,7 @@ void sqlite3VdbeChangeP5(Vdbe *p, u8 val){ ** the address of the next instruction to be coded. */ void sqlite3VdbeJumpHere(Vdbe *p, int addr){ + assert( addr>=0 ); sqlite3VdbeChangeP2(p, addr, p->nOp); } @@ -1703,7 +1704,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ - rc = sqlite3BtreeCommitPhaseTwo(pBt); + rc = sqlite3BtreeCommitPhaseTwo(pBt, 0); } } if( rc==SQLITE_OK ){ @@ -1835,7 +1836,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ for(i=0; i<db->nDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ - sqlite3BtreeCommitPhaseTwo(pBt); + sqlite3BtreeCommitPhaseTwo(pBt, 1); } } sqlite3EndBenignMalloc(); diff --git a/src/where.c b/src/where.c index cdcaa98c0..5d8b8f4ad 100644 --- a/src/where.c +++ b/src/where.c @@ -2942,7 +2942,7 @@ static void bestBtreeIndex( if( nRow>(double)1 && nEq==1 && pFirstTerm!=0 ){ if( pFirstTerm->eOperator & (WO_EQ|WO_ISNULL) ){ testcase( pFirstTerm->eOperator==WO_EQ ); - testcase( pFirstTerm->pOperator==WO_ISNULL ); + testcase( pFirstTerm->eOperator==WO_ISNULL ); whereEqualScanEst(pParse, pProbe, pFirstTerm->pExpr->pRight, &nRow); }else if( pFirstTerm->eOperator==WO_IN && bInEst==0 ){ whereInScanEst(pParse, pProbe, pFirstTerm->pExpr->x.pList, &nRow); diff --git a/test/alter.test b/test/alter.test index bf7cf0062..d4b72a6ae 100644 --- a/test/alter.test +++ b/test/alter.test @@ -840,4 +840,23 @@ do_test alter-14.2 { } {1 {Cannot add a PRIMARY KEY column}} +#------------------------------------------------------------------------- +# Test that it is not possible to use ALTER TABLE on any system table. +# +set system_table_list {1 sqlite_master} +catchsql ANALYZE +ifcapable analyze { lappend system_table_list 2 sqlite_stat1 } +ifcapable stat2 { lappend system_table_list 3 sqlite_stat2 } + +foreach {tn tbl} $system_table_list { + do_test alter-15.$tn.1 { + catchsql "ALTER TABLE $tbl RENAME TO xyz" + } [list 1 "table $tbl may not be altered"] + + do_test alter-15.$tn.2 { + catchsql "ALTER TABLE $tbl ADD COLUMN xyz" + } [list 1 "table $tbl may not be altered"] +} + + finish_test diff --git a/test/analyze.test b/test/analyze.test index 177936c22..766cd50d1 100644 --- a/test/analyze.test +++ b/test/analyze.test @@ -96,7 +96,7 @@ do_test analyze-1.11 { execsql { SELECT * FROM sqlite_stat1 } -} {t1 {} 0} +} {} do_test analyze-1.12 { catchsql { ANALYZE t1; @@ -106,7 +106,7 @@ do_test analyze-1.13 { execsql { SELECT * FROM sqlite_stat1 } -} {t1 {} 0} +} {} # Create some indices that can be analyzed. But do not yet add # data. Without data in the tables, no analysis is done. @@ -117,21 +117,21 @@ do_test analyze-2.1 { ANALYZE main.t1; SELECT * FROM sqlite_stat1 ORDER BY idx; } -} {t1 {} 0} +} {} do_test analyze-2.2 { execsql { CREATE INDEX t1i2 ON t1(b); ANALYZE t1; SELECT * FROM sqlite_stat1 ORDER BY idx; } -} {t1 {} 0} +} {} do_test analyze-2.3 { execsql { CREATE INDEX t1i3 ON t1(a,b); ANALYZE main; SELECT * FROM sqlite_stat1 ORDER BY idx; } -} {t1 {} 0} +} {} # Start adding data to the table. Verify that the analysis # is done correctly. diff --git a/test/analyze6.test b/test/analyze6.test index 515747cbf..b090b5b09 100644 --- a/test/analyze6.test +++ b/test/analyze6.test @@ -71,4 +71,52 @@ do_test analyze6-1.2 { } {0 0 0 {SCAN TABLE cat (~16 rows)} 0 1 1 {SEARCH TABLE ev USING COVERING INDEX evy (y=?) (~32 rows)}} +# Ticket [83ea97620bd3101645138b7b0e71c12c5498fe3d] 2011-03-30 +# If ANALYZE is run on an empty table, make sure indices are used +# on the table. +# +do_test analyze6-2.1 { + execsql { + CREATE TABLE t201(x INTEGER PRIMARY KEY, y UNIQUE, z); + CREATE INDEX t201z ON t201(z); + ANALYZE; + } + eqp {SELECT * FROM t201 WHERE z=5} +} {0 0 0 {SEARCH TABLE t201 USING INDEX t201z (z=?) (~10 rows)}} +do_test analyze6-2.2 { + eqp {SELECT * FROM t201 WHERE y=5} +} {0 0 0 {SEARCH TABLE t201 USING INDEX sqlite_autoindex_t201_1 (y=?) (~1 rows)}} +do_test analyze6-2.3 { + eqp {SELECT * FROM t201 WHERE x=5} +} {0 0 0 {SEARCH TABLE t201 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} +do_test analyze6-2.4 { + execsql { + INSERT INTO t201 VALUES(1,2,3); + ANALYZE t201; + } + eqp {SELECT * FROM t201 WHERE z=5} +} {0 0 0 {SEARCH TABLE t201 USING INDEX t201z (z=?) (~10 rows)}} +do_test analyze6-2.5 { + eqp {SELECT * FROM t201 WHERE y=5} +} {0 0 0 {SEARCH TABLE t201 USING INDEX sqlite_autoindex_t201_1 (y=?) (~1 rows)}} +do_test analyze6-2.6 { + eqp {SELECT * FROM t201 WHERE x=5} +} {0 0 0 {SEARCH TABLE t201 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} +do_test analyze6-2.7 { + execsql { + INSERT INTO t201 VALUES(4,5,7); + INSERT INTO t201 SELECT x+100, y+100, z+100 FROM t201; + INSERT INTO t201 SELECT x+200, y+200, z+200 FROM t201; + INSERT INTO t201 SELECT x+400, y+400, z+400 FROM t201; + ANALYZE t201; + } + eqp {SELECT * FROM t201 WHERE z=5} +} {0 0 0 {SEARCH TABLE t201 USING INDEX t201z (z=?) (~10 rows)}} +do_test analyze6-2.8 { + eqp {SELECT * FROM t201 WHERE y=5} +} {0 0 0 {SEARCH TABLE t201 USING INDEX sqlite_autoindex_t201_1 (y=?) (~1 rows)}} +do_test analyze6-2.9 { + eqp {SELECT * FROM t201 WHERE x=5} +} {0 0 0 {SEARCH TABLE t201 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} + finish_test diff --git a/test/analyze7.test b/test/analyze7.test new file mode 100644 index 000000000..9107376e2 --- /dev/null +++ b/test/analyze7.test @@ -0,0 +1,119 @@ +# 2011 April 1 +# +# 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. +# This file implements tests for the ANALYZE command when an idnex +# name is given as the argument. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# There is nothing to test if ANALYZE is disable for this build. +# +ifcapable {!analyze} { + finish_test + return +} + +# Generate some test data +# +do_test analyze7-1.0 { + execsql { + CREATE TABLE sequence(x INTEGER PRIMARY KEY); + INSERT INTO sequence VALUES(1); + INSERT INTO sequence VALUES(2); + INSERT INTO sequence SELECT x+2 FROM sequence; + INSERT INTO sequence SELECT x+4 FROM sequence; + INSERT INTO sequence SELECT x+8 FROM sequence; + INSERT INTO sequence SELECT x+16 FROM sequence; + INSERT INTO sequence SELECT x+32 FROM sequence; + INSERT INTO sequence SELECT x+64 FROM sequence; + INSERT INTO sequence SELECT x+128 FROM sequence; + INSERT INTO sequence SELECT x+256 FROM sequence; + CREATE TABLE t1(a,b,c,d); + CREATE INDEX t1a ON t1(a); + CREATE INDEX t1b ON t1(b); + CREATE INDEX t1cd ON t1(c,d); + INSERT INTO t1 SELECT x, x, x/100, x FROM sequence; + EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123; + } +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?) (~10 rows)}} +do_test analyze7-1.1 { + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b=123;} +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?) (~10 rows)}} +do_test analyze7-1.2 { + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=2;} +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1cd (c=?) (~10 rows)}} + +# Run an analyze on one of the three indices. Verify that this +# effects the row-count estimate on the one query that uses that +# one index. +# +do_test analyze7-2.0 { + execsql {ANALYZE t1a;} + db cache flush + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123;} +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?) (~1 rows)}} +do_test analyze7-2.1 { + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b=123;} +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?) (~10 rows)}} +do_test analyze7-2.2 { + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=2;} +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1cd (c=?) (~10 rows)}} + +# Verify that since the query planner now things that t1a is more +# selective than t1b, it prefers to use t1a. +# +do_test analyze7-2.3 { + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123 AND b=123} +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?) (~1 rows)}} + +# Run an analysis on another of the three indices. Verify that this +# new analysis works and does not disrupt the previous analysis. +# +do_test analyze7-3.0 { + execsql {ANALYZE t1cd;} + db cache flush; + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123;} +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?) (~1 rows)}} +do_test analyze7-3.1 { + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b=123;} +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?) (~10 rows)}} +do_test analyze7-3.2.1 { + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=?;} +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1cd (c=?) (~86 rows)}} +ifcapable stat2 { + # If ENABLE_STAT2 is defined, SQLite comes up with a different estimated + # row count for (c=2) than it does for (c=?). + do_test analyze7-3.2.2 { + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=2;} + } {0 0 0 {SEARCH TABLE t1 USING INDEX t1cd (c=?) (~102 rows)}} +} else { + # If ENABLE_STAT2 is not defined, the expected row count for (c=2) is the + # same as that for (c=?). + do_test analyze7-3.2.3 { + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=2;} + } {0 0 0 {SEARCH TABLE t1 USING INDEX t1cd (c=?) (~86 rows)}} +} +do_test analyze7-3.3 { + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123 AND b=123} +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?) (~1 rows)}} +do_test analyze7-3.4 { + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=123 AND b=123} +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?) (~2 rows)}} +do_test analyze7-3.5 { + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=123 AND c=123} +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?) (~1 rows)}} +do_test analyze7-3.6 { + execsql {EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=123 AND d=123 AND b=123} +} {0 0 0 {SEARCH TABLE t1 USING INDEX t1cd (c=? AND d=?) (~1 rows)}} + +finish_test diff --git a/test/badutf2.test b/test/badutf2.test index 462e98892..36b40fb95 100644 --- a/test/badutf2.test +++ b/test/badutf2.test @@ -105,9 +105,11 @@ foreach { i len uval xstr ustr u2u } { utf8_to_ustr2 [ sqlite3_column_text $S 0 ] } $ustr - do_test badutf2-5.1.$i { - utf8_to_utf8 $uval - } $u2u + ifcapable debug { + do_test badutf2-5.1.$i { + utf8_to_utf8 $uval + } $u2u + } } diff --git a/test/fts3fault2.test b/test/fts3fault2.test index 6d41aee1c..fb877737f 100644 --- a/test/fts3fault2.test +++ b/test/fts3fault2.test @@ -14,6 +14,9 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set ::testprefix fts3fault2 +# If SQLITE_ENABLE_FTS3 is not defined, omit this file. +ifcapable !fts3 { finish_test ; return } + do_test 1.0 { execsql { CREATE VIRTUAL TABLE t1 USING fts4(x); diff --git a/test/malloc_common.tcl b/test/malloc_common.tcl index 379b97074..e7f615648 100644 --- a/test/malloc_common.tcl +++ b/test/malloc_common.tcl @@ -117,8 +117,8 @@ proc do_faultsim_test {name args} { set DEFAULT(-prep) "" set DEFAULT(-body) "" set DEFAULT(-test) "" - set DEFAULT(-install) "" - set DEFAULT(-uninstall) "" + set DEFAULT(-install) "" + set DEFAULT(-uninstall) "" fix_testname name diff --git a/test/multiplex.test b/test/multiplex.test index 742ca5079..518cbe37a 100644 --- a/test/multiplex.test +++ b/test/multiplex.test @@ -14,7 +14,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl source $testdir/malloc_common.tcl -set g_chunk_size 2147483648 +set g_chunk_size [ expr ($::SQLITE_MAX_PAGE_SIZE*16384) ] set g_max_chunks 32 # This handles appending the chunk number @@ -32,13 +32,17 @@ proc multiplex_name {name chunk} { } # This saves off the parameters and calls the -# underlying sqlite3_multiplex_set() API. -proc multiplex_set {chunk_size max_chunks} { +# underlying sqlite3_multiplex_control() API. +proc multiplex_set {db name chunk_size max_chunks} { global g_chunk_size global g_max_chunks - set g_chunk_size $chunk_size + set g_chunk_size [ expr (($chunk_size+($::SQLITE_MAX_PAGE_SIZE-1)) & ~($::SQLITE_MAX_PAGE_SIZE-1)) ] set g_max_chunks $max_chunks - sqlite3_multiplex_set $chunk_size $max_chunks + set rc [catch {sqlite3_multiplex_control $db $name chunk_size $chunk_size} msg] + if { $rc==0 } { + set rc [catch {sqlite3_multiplex_control $db $name max_chunks $max_chunks} msg] + } + list $msg } # This attempts to delete the base file and @@ -70,14 +74,51 @@ do_test multiplex-1.6 { sqlite3_multiplex_shutdown } {SQLITE_OK} do_test multiplex-1.7 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} do_test multiplex-1.8 { sqlite3_multiplex_shutdown } {SQLITE_OK} -do_test multiplex-1.9 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} -do_test multiplex-1.10.1 { multiplex_set 32768 16 } {SQLITE_OK} -do_test multiplex-1.10.2 { multiplex_set 32768 -1 } {SQLITE_MISUSE} -do_test multiplex-1.10.3 { multiplex_set -1 16 } {SQLITE_MISUSE} -do_test multiplex-1.10.4 { multiplex_set 31 16 } {SQLITE_MISUSE} -do_test multiplex-1.10.5 { multiplex_set 32768 100 } {SQLITE_MISUSE} -do_test multiplex-1.11 { sqlite3_multiplex_shutdown } {SQLITE_OK} +do_test multiplex-1.9.1 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} +do_test multiplex-1.9.2 { sqlite3 db test.db } {} +do_test multiplex-1.9.3 { multiplex_set db main 32768 16 } {SQLITE_OK} +do_test multiplex-1.9.4 { multiplex_set db main 32768 -1 } {SQLITE_MISUSE} +do_test multiplex-1.9.5 { multiplex_set db main -1 16 } {SQLITE_MISUSE} +do_test multiplex-1.9.6 { multiplex_set db main 31 16 } {SQLITE_OK} +do_test multiplex-1.9.7 { multiplex_set db main 32768 100 } {SQLITE_MISUSE} +do_test multiplex-1.9.8 { multiplex_set db main 1073741824 1 } {SQLITE_OK} +do_test multiplex-1.9.9 { db close } {} +do_test multiplex-1.9.10 { sqlite3_multiplex_shutdown } {SQLITE_OK} + +do_test multiplex-1.10.1 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} +do_test multiplex-1.10.2 { sqlite3 db test.db } {} +do_test multiplex-1.10.3 { lindex [ catchsql { SELECT multiplex_control(2, 32768); } ] 0 } {0} +do_test multiplex-1.10.4 { lindex [ catchsql { SELECT multiplex_control(3, -1); } ] 0 } {1} +do_test multiplex-1.10.5 { lindex [ catchsql { SELECT multiplex_control(2, -1); } ] 0 } {1} +do_test multiplex-1.10.6 { lindex [ catchsql { SELECT multiplex_control(2, 31); } ] 0 } {0} +do_test multiplex-1.10.7 { lindex [ catchsql { SELECT multiplex_control(3, 100); } ] 0 } {1} +do_test multiplex-1.10.8 { lindex [ catchsql { SELECT multiplex_control(2, 1073741824); } ] 0 } {0} +do_test multiplex-1.10.9 { db close } {} +do_test multiplex-1.10.10 { sqlite3_multiplex_shutdown } {SQLITE_OK} + +do_test multiplex-1.11.1 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} +do_test multiplex-1.11.2 { sqlite3 db test.db } {} +do_test multiplex-1.11.3 { sqlite3_multiplex_control db main enable 0 } {SQLITE_OK} +do_test multiplex-1.11.4 { sqlite3_multiplex_control db main enable 1 } {SQLITE_OK} +do_test multiplex-1.11.5 { sqlite3_multiplex_control db main enable -1 } {SQLITE_OK} +do_test multiplex-1.11.6 { db close } {} +do_test multiplex-1.11.7 { sqlite3_multiplex_shutdown } {SQLITE_OK} + +do_test multiplex-1.12.1 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} +do_test multiplex-1.12.2 { sqlite3 db test.db } {} +do_test multiplex-1.12.3 { lindex [ catchsql { SELECT multiplex_control(1, 0); } ] 0 } {0} +do_test multiplex-1.12.4 { lindex [ catchsql { SELECT multiplex_control(1, 1); } ] 0 } {0} +do_test multiplex-1.12.5 { lindex [ catchsql { SELECT multiplex_control(1, -1); } ] 0 } {0} +do_test multiplex-1.12.6 { db close } {} +do_test multiplex-1.12.7 { sqlite3_multiplex_shutdown } {SQLITE_OK} + +do_test multiplex-1.13.1 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} +do_test multiplex-1.13.2 { sqlite3 db test.db } {} +do_test multiplex-1.13.3 { lindex [ catchsql { SELECT multiplex_control(-1, 0); } ] 0 } {1} +do_test multiplex-1.13.4 { lindex [ catchsql { SELECT multiplex_control(4, 1); } ] 0 } {1} +do_test multiplex-1.13.6 { db close } {} +do_test multiplex-1.13.7 { sqlite3_multiplex_shutdown } {SQLITE_OK} #------------------------------------------------------------------------- # Some simple warm-body tests with a single database file in rollback @@ -98,9 +139,12 @@ do_test multiplex-1.11 { sqlite3_multiplex_shutdown } {SQLITE_OK} # # multiplex-2.6.*: More reading/writing with varying small chunk sizes, as # well as varying journal mode. +# +# multiplex-2.7.*: Disable/enable tests. +# sqlite3_multiplex_initialize "" 1 -multiplex_set 32768 16 +multiplex_set db main 32768 16 do_test multiplex-2.1.2 { sqlite3 db test.db @@ -130,6 +174,7 @@ do_test multiplex-2.3.1 { db2 close } {} + do_test multiplex-2.4.1 { sqlite3_multiplex_shutdown } {SQLITE_MISUSE} @@ -146,11 +191,11 @@ do_test multiplex-2.4.99 { do_test multiplex-2.5.1 { multiplex_delete test.db sqlite3_multiplex_initialize "" 1 - multiplex_set 4096 16 + sqlite3 db test.db + multiplex_set db main 4096 16 } {SQLITE_OK} do_test multiplex-2.5.2 { - sqlite3 db test.db execsql { PRAGMA page_size = 1024; PRAGMA journal_mode = delete; @@ -165,7 +210,9 @@ do_test multiplex-2.5.3 { INSERT INTO t1 VALUES(2, randomblob(4000)); INSERT INTO t1 VALUES(3, 'three'); INSERT INTO t1 VALUES(4, randomblob(4000)); - INSERT INTO t1 VALUES(5, 'five') + INSERT INTO t1 VALUES(5, 'five'); + INSERT INTO t1 VALUES(6, randomblob($g_chunk_size)); + INSERT INTO t1 VALUES(7, randomblob($g_chunk_size)); } } {} @@ -205,11 +252,11 @@ foreach jmode $all_journal_modes { do_test multiplex-2.6.1.$sz.$jmode { multiplex_delete test.db sqlite3_multiplex_initialize "" 1 - multiplex_set $sz 32 + sqlite3 db test.db + multiplex_set db main $sz 32 } {SQLITE_OK} do_test multiplex-2.6.2.$sz.$jmode { - sqlite3 db test.db db eval { PRAGMA page_size = 1024; PRAGMA auto_vacuum = off; @@ -243,6 +290,31 @@ foreach jmode $all_journal_modes { } } +do_test multiplex-2.7.1 { multiplex_delete test.db } {} +do_test multiplex-2.7.2 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} +do_test multiplex-2.7.3 { sqlite3 db test.db } {} +do_test multiplex-2.7.4 { lindex [ catchsql { SELECT multiplex_control(2, 65536); } ] 0 } {0} +do_test multiplex-2.7.5 { lindex [ catchsql { SELECT multiplex_control(1, 0); } ] 0 } {0} +do_test multiplex-2.7.6 { + execsql { + CREATE TABLE t1(a PRIMARY KEY, b); + INSERT INTO t1 VALUES(1, randomblob(1000)); + } +} {} +# verify only one file, and file size is less than chunks size +do_test multiplex-2.7.7 { expr ([file size [multiplex_name test.db 0]] < 65536) } {1} +do_test multiplex-2.7.8 { file exists [multiplex_name test.db 1] } {0} +do_test multiplex-2.7.9 { + execsql { + INSERT INTO t1 VALUES(2, randomblob(65536)); + } +} {} +# verify only one file, and file size exceeds chunks size +do_test multiplex-2.7.10 { expr ([file size [multiplex_name test.db 0]] > 65536) } {1} +do_test multiplex-2.7.11 { file exists [multiplex_name test.db 1] } {0} +do_test multiplex-2.7.12 { db close } {} +do_test multiplex-2.7.13 { sqlite3_multiplex_shutdown } {SQLITE_OK} + #------------------------------------------------------------------------- # Try some tests with more than one connection to a database file. Still # in rollback mode. @@ -255,10 +327,10 @@ foreach jmode $all_journal_modes { do_test multiplex-3.1.1 { multiplex_delete test.db sqlite3_multiplex_initialize "" 1 - multiplex_set 32768 16 + sqlite3 db test.db + multiplex_set db main 32768 16 } {SQLITE_OK} do_test multiplex-3.1.2 { - sqlite3 db test.db execsql { PRAGMA page_size = 1024; PRAGMA journal_mode = delete; @@ -341,7 +413,7 @@ do_test multiplex-3.2.X { # sqlite3_multiplex_initialize "" 1 -multiplex_set 32768 16 +multiplex_set db main 32768 16 # Return a list of all currently defined multiplexs. proc multiplex_list {} { @@ -403,7 +475,7 @@ do_test multiplex-4.1.12 { # sqlite3_multiplex_initialize "" 1 -multiplex_set 32768 16 +multiplex_set db main 32768 16 do_faultsim_test multiplex-5.1 -prep { catch {db close} @@ -448,7 +520,7 @@ do_faultsim_test multiplex-5.5 -prep { catch { sqlite3_multiplex_shutdown } } -body { sqlite3_multiplex_initialize "" 1 - multiplex_set 32768 16 + multiplex_set db main 32768 16 } # test that mismatch filesize is detected @@ -473,20 +545,20 @@ if {0==[info exists ::G(perm:presql)] || $::G(perm:presql) == ""} { do_test multiplex-5.6.2.$jmode { execsql { CREATE TABLE t1(a, b); - INSERT INTO t1 VALUES(1, randomblob(1100)); - INSERT INTO t1 VALUES(2, randomblob(1100)); - INSERT INTO t1 VALUES(3, randomblob(1100)); - INSERT INTO t1 VALUES(4, randomblob(1100)); - INSERT INTO t1 VALUES(5, randomblob(1100)); + INSERT INTO t1 VALUES(1, randomblob(15000)); + INSERT INTO t1 VALUES(2, randomblob(15000)); + INSERT INTO t1 VALUES(3, randomblob(15000)); + INSERT INTO t1 VALUES(4, randomblob(15000)); + INSERT INTO t1 VALUES(5, randomblob(15000)); } db close sqlite3_multiplex_initialize "" 1 - multiplex_set 4096 16 sqlite3 db test.db - } {} + multiplex_set db main 4096 16 + } {SQLITE_OK} do_test multiplex-5.6.3.$jmode { catchsql { - INSERT INTO t1 VALUES(6, randomblob(1100)); + INSERT INTO t1 VALUES(6, randomblob(15000)); } } {1 {disk I/O error}} do_test multiplex-5.6.4.$jmode { diff --git a/test/oserror.test b/test/oserror.test index ecb02116b..d1952a30d 100644 --- a/test/oserror.test +++ b/test/oserror.test @@ -44,7 +44,12 @@ proc do_re_test {tn script expression} { # Tests oserror-1.* test failures in the open() system call. # -# Test a failure in open() due to too many files. +# Test a failure in open() due to too many files. +# +# The xOpen() method of the unix VFS calls getcwd() as well as open(). +# Although this does not appear to be documented in the man page, on OSX +# a call to getcwd() may fail if there are no free file descriptors. So +# an error may be reported for either open() or getcwd() here. # do_test 1.1.1 { set ::log [list] @@ -55,8 +60,9 @@ do_test 1.1.1 { do_test 1.1.2 { catch { for {set i 0} {$i < 2000} {incr i} { dbh_$i close } } } {1} - -do_re_test 1.1.3 { lindex $::log 0 } {^os_unix.c:\d+: \(\d+\) open\(.*test.db\) - } +do_re_test 1.1.3 { + lindex $::log 0 +} {^os_unix.c:\d+: \(\d+\) (open|getcwd)\(.*test.db\) - } # Test a failure in open() due to the path being a directory. diff --git a/test/syscall.test b/test/syscall.test new file mode 100644 index 000000000..21295a59c --- /dev/null +++ b/test/syscall.test @@ -0,0 +1,250 @@ +# 2011 March 29 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +source $testdir/malloc_common.tcl + +if {[llength [info commands test_syscall]]==0} { + finish_test + return +} +set testprefix syscall + + +#------------------------------------------------------------------------- +# Tests for the xSetSystemCall method. +# +do_test 1.1.1 { + list [catch { test_syscall reset open } msg] $msg +} {0 {}} +do_test 1.1.2 { + list [catch { test_syscall reset nosuchcall } msg] $msg +} {1 SQLITE_NOTFOUND} +do_test 1.1.3 { + list [catch { test_syscall reset open } msg] $msg +} {0 {}} +do_test 1.1.4 { + list [catch { test_syscall reset ""} msg] $msg +} {1 SQLITE_NOTFOUND} + +do_test 1.2 { test_syscall reset } {} + +do_test 1.3.1 { test_syscall install {open getcwd access} } {} +do_test 1.3.2 { test_syscall reset } {} + +#------------------------------------------------------------------------- +# Tests for the xGetSystemCall method. +# +do_test 2.1.1 { test_syscall exists open } 1 +do_test 2.1.2 { test_syscall exists nosuchcall } 0 + +#------------------------------------------------------------------------- +# Tests for the xNextSystemCall method. +# +set syscall_list [list \ + open close access getcwd stat fstat ftruncate \ + fcntl read pread write pwrite fchmod \ +] +if {[test_syscall exists fallocate]} {lappend syscall_list fallocate} +do_test 3.1 { test_syscall list } $syscall_list + +#------------------------------------------------------------------------- +# This test verifies that if a call to open() fails and errno is set to +# EINTR, the call is retried. If it succeeds, execution continues as if +# nothing happened. +# +test_syscall reset +forcedelete test.db2 +do_execsql_test 4.1 { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 2); + ATTACH 'test.db2' AS aux; + CREATE TABLE aux.t2(x, y); + INSERT INTO t2 VALUES(3, 4); +} + +db_save_and_close +test_syscall install open +foreach jrnl [list wal delete] { + for {set i 1} {$i < 20} {incr i} { + db_restore_and_reopen + test_syscall fault $i 0 + test_syscall errno open EINTR + + do_test 4.2.$jrnl.$i { + sqlite3 db test.db + execsql { ATTACH 'test.db2' AS aux } + execsql "PRAGMA main.journal_mode = $jrnl" + execsql "PRAGMA aux.journal_mode = $jrnl" + execsql { + BEGIN; + INSERT INTO t1 VALUES(5, 6); + INSERT INTO t2 VALUES(7, 8); + COMMIT; + } + + db close + sqlite3 db test.db + execsql { ATTACH 'test.db2' AS aux } + execsql { + SELECT * FROM t1; + SELECT * FROM t2; + } + } {1 2 5 6 3 4 7 8} + } +} + +#------------------------------------------------------------------------- +# This test verifies that closing database handles does not drop locks +# held by other database handles in the same process on the same file. +# +# The os_unix.c module has to take precautions to prevent this as the +# close() system call drops locks held by other file-descriptors on the +# same file. From the Linux man page: +# +# close() closes a file descriptor, so that it no longer refers to any file +# and may be reused. Any record locks (see fcntl(2)) held on the file it +# was associated with, and owned by the process, are removed (regardless +# of the file descriptor that was used to obtain the lock). +# +catch { db close } +forcedelete test.db test.db2 + +do_multiclient_test tn { + code1 { + sqlite3 dbX1 test.db + sqlite3 dbX2 test.db + } + + do_test syscall-5.$tn.1 { + sql1 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + BEGIN; + INSERT INTO t1 VALUES(3, 4); + } + } {} + + do_test syscall-5.$tn.2 { sql2 { SELECT * FROM t1 } } {1 2} + do_test syscall-5.$tn.3 { + csql2 { INSERT INTO t1 VALUES(5, 6) } + } {1 {database is locked}} + + do_test syscall-5.$tn.4 { + code1 { + dbX1 close + dbX2 close + } + } {} + + do_test syscall-5.$tn.5 { + csql2 { INSERT INTO t1 VALUES(5, 6) } + } {1 {database is locked}} + + do_test syscall-5.$tn.6 { sql1 { COMMIT } } {} + + do_test syscall-5.$tn.7 { + csql2 { INSERT INTO t1 VALUES(5, 6) } + } {0 {}} +} + +catch {db close} +do_test 6.1 { + sqlite3 db1 test.db1 + sqlite3 db2 test.db2 + sqlite3 db3 test.db3 + sqlite3 dbM "" + + db2 close + db3 close + dbM close + db1 close +} {} + +do_test 6.2 { + sqlite3 db test.db + execsql { + PRAGMA temp_store = file; + + PRAGMA main.cache_size = 10; + PRAGMA temp.cache_size = 10; + CREATE TABLE temp.tt(a, b); + INSERT INTO tt VALUES(randomblob(500), randomblob(600)); + INSERT INTO tt SELECT randomblob(500), randomblob(600) FROM tt; + INSERT INTO tt SELECT randomblob(500), randomblob(600) FROM tt; + INSERT INTO tt SELECT randomblob(500), randomblob(600) FROM tt; + INSERT INTO tt SELECT randomblob(500), randomblob(600) FROM tt; + INSERT INTO tt SELECT randomblob(500), randomblob(600) FROM tt; + INSERT INTO tt SELECT randomblob(500), randomblob(600) FROM tt; + INSERT INTO tt SELECT randomblob(500), randomblob(600) FROM tt; + INSERT INTO tt SELECT randomblob(500), randomblob(600) FROM tt; + } + + db close +} {} + +#------------------------------------------------------------------------- +# Test that a database file a single byte in size is treated as an empty +# file. Whereas a file 2 bytes or larger might be considered corrupt. +# +catch { db close } +forcedelete test.db test.db2 + +proc create_db_file {nByte} { + set fd [open test.db w] + fconfigure $fd -translation binary -encoding binary + puts -nonewline $fd [string range "xSQLite" 1 $nByte] + close $fd +} + +foreach {nByte res} { + 1 {0 {}} + 2 {1 {file is encrypted or is not a database}} + 3 {1 {file is encrypted or is not a database}} +} { + do_test 7.$nByte { + create_db_file $nByte + sqlite3 db test.db + catchsql { CREATE TABLE t1(a, b) } + } $res + catch { db close } +} + +#------------------------------------------------------------------------- +# +catch { db close } +forcedelete test.db test.db2 + +do_test 8.1 { + sqlite3 db test.db + file_control_chunksize_test db main 4096 + file size test.db +} {0} + +foreach {tn hint size} { + 1 1000 4096 + 2 1000 4096 + 3 3000 4096 + 4 4096 4096 + 5 4197 8192 +} { + do_test 8.2.$tn { + file_control_sizehint_test db main $hint + file size test.db + } $size +} + + + +finish_test diff --git a/test/sysfault.test b/test/sysfault.test new file mode 100644 index 000000000..07d525ca9 --- /dev/null +++ b/test/sysfault.test @@ -0,0 +1,247 @@ +# 2011 March 28 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +source $testdir/malloc_common.tcl + +if {[llength [info commands test_syscall]]==0} { + finish_test + return +} + +set testprefix sysfault + +set FAULTSIM(vfsfault-transient) [list \ + -injectinstall vfsfault_install \ + -injectstart vfsfault_injectstart_t \ + -injectstop vfsfault_injectstop \ + -injecterrlist {} \ + -injectuninstall {test_syscall uninstall} \ +] +set FAULTSIM(vfsfault-persistent) [list \ + -injectinstall vfsfault_install \ + -injectstart vfsfault_injectstart_p \ + -injectstop vfsfault_injectstop \ + -injecterrlist {} \ + -injectuninstall {test_syscall uninstall} \ +] + +proc vfsfault_injectstart_t {iFail} { test_syscall fault $iFail 0 } +proc vfsfault_injectstart_p {iFail} { test_syscall fault $iFail 1 } +proc vfsfault_injectstop {} { test_syscall fault } + +faultsim_save_and_close + + +set open_and_write_body { + sqlite3 db test.db + db eval { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + PRAGMA journal_mode = WAL; + INSERT INTO t1 VALUES(3, 4); + SELECT * FROM t1; + CREATE TEMP TABLE t2(x); + INSERT INTO t2 VALUES('y'); + } +} + +proc vfsfault_install {} { test_syscall install {open getcwd} } +do_faultsim_test 1 -faults vfsfault-* -prep { + faultsim_restore +} -body $open_and_write_body -test { + faultsim_test_result {0 {wal 1 2 3 4}} \ + {1 {unable to open database file}} \ + {1 {attempt to write a readonly database}} +} + +#------------------------------------------------------------------------- +# Errors in the fstat() function when opening and writing a file. Cases +# where fstat() fails and sets errno to ENOMEM and EOVERFLOW are both +# tested. EOVERFLOW is interpreted as meaning that a file on disk is +# too large to be opened by the OS. +# +foreach {tn errno errlist} { + 1 ENOMEM {{disk I/O error}} + 2 EOVERFLOW {{disk I/O error} {large file support is disabled}} +} { + proc vfsfault_install {} { test_syscall install fstat } + set errs [list] + foreach e $errlist { lappend errs [list 1 $e] } + do_faultsim_test 1.2.$tn -faults vfsfault-* -prep { + faultsim_restore + } -body " + test_syscall errno fstat $errno + $open_and_write_body + " -test " + faultsim_test_result {0 {wal 1 2 3 4}} $errs + " +} + +#------------------------------------------------------------------------- +# Various errors in locking functions. +# +foreach vfs {unix unix-excl} { + foreach {tn errno errlist} { + 1 EAGAIN {{database is locked} {disk I/O error}} + 2 ETIMEDOUT {{database is locked} {disk I/O error}} + 3 EBUSY {{database is locked} {disk I/O error}} + 4 EINTR {{database is locked} {disk I/O error}} + 5 ENOLCK {{database is locked} {disk I/O error}} + 6 EACCES {{database is locked} {disk I/O error}} + 7 EPERM {{access permission denied} {disk I/O error}} + 8 EDEADLK {{disk I/O error}} + 9 ENOMEM {{disk I/O error}} + } { + proc vfsfault_install {} { test_syscall install fcntl } + set errs [list] + foreach e $errlist { lappend errs [list 1 $e] } + + set body [string map [list %VFS% $vfs] { + sqlite3 db test.db + db eval { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + } + set fd [open test.db-journal w] + puts $fd "hello world" + close $fd + sqlite3 db test.db -vfs %VFS% + db eval { + SELECT * FROM t1; + } + }] + + do_faultsim_test 1.3.$vfs.$tn -faults vfsfault-* -prep { + faultsim_restore + } -body " + test_syscall errno fcntl $errno + $body + " -test " + faultsim_test_result {0 {1 2}} $errs + " + } +} + +#------------------------------------------------------------------------- +# Check that a single EINTR error does not affect processing. +# +proc vfsfault_install {} { + test_syscall reset + test_syscall install {open ftruncate close read pread pread64 write fallocate} +} + +forcedelete test.db test.db2 +sqlite3 db test.db +do_test 2.setup { + execsql { + CREATE TABLE t1(a, b, c, PRIMARY KEY(a)); + INSERT INTO t1 VALUES('abc', 'def', 'ghi'); + ATTACH 'test.db2' AS 'aux'; + CREATE TABLE aux.t2(x); + INSERT INTO t2 VALUES(1); + } + faultsim_save_and_close +} {} + +do_faultsim_test 2.1 -faults vfsfault-transient -prep { + catch { db close } + faultsim_restore +} -body { + test_syscall errno open EINTR + test_syscall errno ftruncate EINTR + test_syscall errno close EINTR + test_syscall errno read EINTR + test_syscall errno pread EINTR + test_syscall errno pread64 EINTR + test_syscall errno write EINTR + test_syscall errno fallocate EINTR + + sqlite3 db test.db + file_control_chunksize_test db main 8192 + + set res [db eval { + ATTACH 'test.db2' AS 'aux'; + SELECT * FROM t1; + PRAGMA journal_mode = truncate; + BEGIN; + INSERT INTO t1 VALUES('jkl', 'mno', 'pqr'); + INSERT INTO t1 VALUES(randomblob(10000), 0, 0); + UPDATE t2 SET x = 2; + COMMIT; + DELETE FROM t1 WHERE length(a)>3; + SELECT * FROM t1; + SELECT * FROM t2; + }] + db close + set res +} -test { + faultsim_test_result {0 {abc def ghi truncate abc def ghi jkl mno pqr 2}} +} + +do_faultsim_test 2.2 -faults vfsfault-* -prep { + catch { db close } + faultsim_restore +} -body { + sqlite3 db test.db + set res [db eval { + ATTACH 'test.db2' AS 'aux'; + SELECT * FROM t1; + PRAGMA journal_mode = truncate; + BEGIN; + INSERT INTO t1 VALUES('jkl', 'mno', 'pqr'); + UPDATE t2 SET x = 2; + COMMIT; + SELECT * FROM t1; + SELECT * FROM t2; + }] + db close + set res +} -test { + faultsim_test_result {0 {abc def ghi truncate abc def ghi jkl mno pqr 2}} \ + {1 {unable to open database file}} \ + {1 {unable to open database: test.db2}} \ + {1 {attempt to write a readonly database}} \ + {1 {disk I/O error}} +} + +#------------------------------------------------------------------------- + +proc vfsfault_install {} { + test_syscall reset + test_syscall install {fstat fallocate} +} +do_faultsim_test 3 -faults vfsfault-* -prep { + faultsim_delete_and_reopen + file_control_chunksize_test db main 8192 + execsql { + CREATE TABLE t1(a, b); + BEGIN; + SELECT * FROM t1; + } +} -body { + test_syscall errno fstat EIO + test_syscall errno fallocate EIO + + execsql { + INSERT INTO t1 VALUES(randomblob(10000), randomblob(10000)); + SELECT length(a) + length(b) FROM t1; + COMMIT; + } +} -test { + faultsim_test_result {0 20000} +} + +finish_test + diff --git a/test/unixexcl.test b/test/unixexcl.test new file mode 100644 index 000000000..057ae0af1 --- /dev/null +++ b/test/unixexcl.test @@ -0,0 +1,83 @@ +# 2011 March 30 +# +# 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 tests for the "unix-excl" VFS module (part of +# os_unix.c). +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +source $testdir/malloc_common.tcl + +if {$::tcl_platform(platform)!="unix" || [info commands test_syscall]==""} { + finish_test + return +} +set testprefix unixexcl + + + +# Test that when using VFS "unix-excl", the first time the database is read +# a process-wide exclusive lock is taken on it. This means other connections +# within the process may still access the db normally, but connections from +# outside the process cannot. +# +do_multiclient_test tn { + do_test unixexcl-1.$tn.1 { + sql1 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES('hello', 'world'); + } + } {} + do_test unixexcl-1.$tn.2 { sql2 { SELECT * FROM t1 } } {hello world} + do_test unixexcl-1.$tn.3 { + code1 { + db close + sqlite3 db test.db -vfs unix-excl + db eval { SELECT * FROM t1 } + } + } {hello world} + if {$tn==1} { + do_test unixexcl-1.$tn.4.multiproc { + csql2 { SELECT * FROM t1 } + } {1 {database is locked}} + } else { + do_test unixexcl-1.$tn.4.singleproc { + csql2 { SELECT * FROM t1 } + } {0 {hello world}} + } +} + +# Test that when using VFS "unix-excl", if a file is opened in read-only mode +# the behaviour is the same as if VFS "unix" were used. +# +do_multiclient_test tn { + do_test unixexcl-2.$tn.1 { + sql1 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES('hello', 'world'); + } + } {} + do_test unixexcl-2.$tn.2 { sql2 { SELECT * FROM t1 } } {hello world} + do_test unixexcl-2.$tn.3 { + code1 { + db close + sqlite3 db test.db -readonly yes -vfs unix-excl + db eval { SELECT * FROM t1 } + } + } {hello world} + do_test unixexcl-2.$tn.4 { + csql2 { SELECT * FROM t1 } + } {0 {hello world}} +} + +finish_test diff --git a/test/wal.test b/test/wal.test index 339661e2e..257a87b09 100644 --- a/test/wal.test +++ b/test/wal.test @@ -463,6 +463,7 @@ do_multiclient_test tn { # do_test wal-10.$tn.1 { execsql { + PRAGMA auto_vacuum = 0; PRAGMA journal_mode = wal; CREATE TABLE t1(a, b); INSERT INTO t1 VALUES(1, 2); diff --git a/test/wal2.test b/test/wal2.test index 7bb820cc1..82234330f 100644 --- a/test/wal2.test +++ b/test/wal2.test @@ -343,6 +343,7 @@ file delete -force test.db test.db-wal test.db-journal do_test wal2-4.1 { sqlite3 db test.db execsql { + PRAGMA auto_vacuum = 0; PRAGMA journal_mode = WAL; CREATE TABLE data(x); INSERT INTO data VALUES('need xShmOpen to see this'); @@ -622,6 +623,7 @@ set READMARK1_WRITE { foreach {tn sql res expected_locks} { 2 { + PRAGMA auto_vacuum = 0; PRAGMA journal_mode = WAL; BEGIN; CREATE TABLE t1(x); @@ -707,6 +709,7 @@ tvfs delete do_test wal2-6.5.1 { sqlite3 db test.db execsql { + PRAGMA auto_vacuum = 0; PRAGMA journal_mode = wal; PRAGMA locking_mode = exclusive; CREATE TABLE t2(a, b); @@ -1166,6 +1169,7 @@ foreach {tn sql reslist} { } { faultsim_delete_and_reopen + execsql {PRAGMA auto_vacuum = 0} execsql $sql do_execsql_test wal2-14.$tn.1 { PRAGMA journal_mode = WAL } {wal} diff --git a/test/wal3.test b/test/wal3.test index b00663893..bd296154e 100644 --- a/test/wal3.test +++ b/test/wal3.test @@ -413,6 +413,7 @@ testvfs T -default 1 do_test wal3-6.1.1 { file delete -force test.db test.db-journal test.db wal sqlite3 db test.db + execsql { PRAGMA auto_vacuum = off } execsql { PRAGMA journal_mode = WAL } execsql { CREATE TABLE t1(a, b); @@ -494,6 +495,7 @@ do_test wal3-6.2.1 { file delete -force test.db test.db-journal test.db wal sqlite3 db test.db sqlite3 db2 test.db + execsql { PRAGMA auto_vacuum = off } execsql { PRAGMA journal_mode = WAL } execsql { CREATE TABLE t1(a, b); @@ -617,6 +619,7 @@ do_test wal3-8.1 { sqlite3 db test.db sqlite3 db2 test.db execsql { + PRAGMA auto_vacuum = off; PRAGMA journal_mode = WAL; CREATE TABLE b(c); INSERT INTO b VALUES('Tehran'); diff --git a/test/wal5.test b/test/wal5.test index d571dae0d..ad6bcfc7d 100644 --- a/test/wal5.test +++ b/test/wal5.test @@ -169,6 +169,8 @@ foreach {testprefix do_wal_checkpoint} { sql2 { ATTACH 'test.db2' AS aux } sql3 { ATTACH 'test.db2' AS aux } sql1 { + PRAGMA aux.auto_vacuum = 0; + PRAGMA main.auto_vacuum = 0; PRAGMA main.page_size=1024; PRAGMA main.journal_mode=WAL; PRAGMA aux.page_size=1024; PRAGMA aux.journal_mode=WAL; } @@ -309,6 +311,7 @@ foreach {testprefix do_wal_checkpoint} { do_test 3.$tn.1 { sql1 { + PRAGMA auto_vacuum = 0; PRAGMA journal_mode = WAL; PRAGMA synchronous = normal; CREATE TABLE t1(x, y); diff --git a/test/where3.test b/test/where3.test index ce283905a..ab75fdec1 100644 --- a/test/where3.test +++ b/test/where3.test @@ -222,6 +222,7 @@ do_execsql_test where3-3.0 { CREATE INDEX t301c ON t301(c); INSERT INTO t301 VALUES(1,2,3); CREATE TABLE t302(x, y); + INSERT INTO t302 VALUES(4,5); ANALYZE; explain query plan SELECT * FROM t302, t301 WHERE t302.x=5 AND t301.a=t302.y; } { @@ -341,5 +342,4 @@ do_execsql_test where3-5.3 { 0 0 0 {USE TEMP B-TREE FOR ORDER BY} } - finish_test diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl index d56b06dbc..fa99f2df7 100644 --- a/tool/mksqlite3c.tcl +++ b/tool/mksqlite3c.tcl @@ -51,7 +51,7 @@ puts $out [subst \ {/****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite ** version $VERSION. By combining all the individual C code files into this -** single large file, the entire code can be compiled as a one translation +** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements ** of 5% or more are commonly seen when SQLite is compiled as a single diff --git a/tool/split-sqlite3c.tcl b/tool/split-sqlite3c.tcl new file mode 100644 index 000000000..287b75282 --- /dev/null +++ b/tool/split-sqlite3c.tcl @@ -0,0 +1,82 @@ +#!/usr/bin/tclsh +# +# This script splits the sqlite3.c amalgamated source code files into +# several smaller files such that no single files is more than a fixed +# number of lines in length (32k or 64k). Each of the split out files +# is #include-ed by the master file. +# +# Splitting files up this way allows them to be used with older compilers +# that cannot handle really long source files. +# +set MAX 32768 ;# Maximum number of lines per file. + +set BEGIN {^/\*+ Begin file ([a-zA-Z0-9_.]+) \*+/} +set END {^/\*+ End of %s \*+/} + +set in [open sqlite3.c] +set out1 [open sqlite3-all.c w] + +# Copy the header from sqlite3.c into sqlite3-all.c +# +while {[gets $in line]} { + if {[regexp $BEGIN $line]} break + puts $out1 $line +} + +# Gather the complete content of a file into memory. Store the +# content in $bufout. Store the number of lines is $nout +# +proc gather_one_file {firstline bufout nout} { + regexp $::BEGIN $firstline all filename + set end [format $::END $filename] + upvar $bufout buf $nout n + set buf $firstline\n + global in + set n 0 + while {[gets $in line]>=0} { + incr n + append buf $line\n + if {[regexp $end $line]} break + } +} + +# Write a big chunk of text in to an auxiliary file "sqlite3-NNN.c". +# Also add an appropriate #include to sqlite3-all.c +# +set filecnt 0 +proc write_one_file {content} { + global filecnt + incr filecnt + set out [open sqlite3-$filecnt.c w] + puts -nonewline $out $content + close $out + puts $::out1 "#include \"sqlite3-$filecnt.c\"" +} + +# Continue reading input. Store chunks in separate files and add +# the #includes to the main sqlite3-all.c file as necessary to reference +# the extra chunks. +# +set all {} +set N 0 +while {[regexp $BEGIN $line]} { + set buf {} + set n 0 + gather_one_file $line buf n + if {$n+$N>=$MAX} { + write_one_file $all + set all {} + set N 0 + } + append all $buf + incr N $n + while {[gets $in line]>=0} { + if {[regexp $BEGIN $line]} break + puts $out1 $line + } +} +if {$N>0} { + write_one_file $all +} +close $out1 +close $in |