Compare commits

...

211 Commits

Author SHA1 Message Date
dan 1c3641e05f Update this branch with the "PRAGMA noop_update" fix from begin-concurrent-report.
FossilOrigin-Name: 1d98472277ff131960a7718cd57af5b6ed4ead811a9220a6eb35ff505bf587e5
2020-08-31 20:37:00 +00:00
dan 5d936ec5b0 Update this branch with the "PRAGMA noop_update" fix from begin-concurrent-pnu.
FossilOrigin-Name: 3c0423f0a8685cf8ef165914e6f9bd8f191d32db1c8287aed2a3ba6e272fc9f0
2020-08-31 20:29:47 +00:00
dan 5e9d4c221d Restore the "PRAGMA noop_update" feature that was broken by [2b3241cf67c8eea7].
FossilOrigin-Name: 5fa4cbcb785125e72c7fd5cc2050637f2a2e854ee22aee05e8f09f751cd36291
2020-08-31 20:27:44 +00:00
dan c262c97910 Improve performance of wal-mode locking on unix in cases where there are hundreds of connections to a single database within the same process.
FossilOrigin-Name: 30214727f7af9b5471654b0dd2cd234f0e284b24661104e55a9be771baf9577a
2020-08-29 15:36:34 +00:00
dan ba4aea7b7e Update this branch with latest from begin-concurrent-pnu.
FossilOrigin-Name: 3ee73880ed280f33d554269d4e964cce0c44b5c6c43937da4e547c39f16e860b
2020-08-11 14:06:38 +00:00
dan a1c7d4a6d0 Do not run tests in concreport.test for non SQLITE_ENABLE_PREUPDATE_HOOK builds.
FossilOrigin-Name: e39e7a6fc793912ab26e5ffc746b38941ec69a7edc100a6034d3ffb1edb606a7
2020-08-11 14:01:32 +00:00
drh 23dbdf125c Merge recent trunk fixes into the begin-concurrent-pnu branch.
FossilOrigin-Name: 2ae78c993e6a4ffd9ac1c8aaadf4976eacb5486e23c228988e9d3f379ed801c3
2020-08-10 21:30:17 +00:00
dan 91fb6675a7 Merge begin-concurrent-pnu changes into this branch.
FossilOrigin-Name: fd01e78827f22f8453ed11e5b0ec736302cc45465e5105241568eb6686ced882
2020-07-30 19:56:53 +00:00
dan bd14b600fa Merge latest trunk change into this branch.
FossilOrigin-Name: 2b3241cf67c8eea761760ac27df7a136bcca2dca10c152a36b216c5dc88cdb53
2020-07-30 19:37:38 +00:00
dan 9075693331 Bring this branch up to date with begin-concurrent-pnu.
FossilOrigin-Name: 12a909fb3d80fcf84646644f2904be9827c643d0a98257448777c83b4f1dc90c
2020-07-17 11:07:27 +00:00
dan 3dc850321e Allow BEGIN CONCURRENT transactions to contain schema modifications. However, this causes them to conflict with all other transactions.
FossilOrigin-Name: e9393a18cb987d258fff56f80ad6b1525f124fb19e8e4a9c953b86a57ef9a7e6
2020-07-16 20:39:59 +00:00
drh 5a344ef67c Merge versoin 3.32.1 into the begin-concurrent-pnu branch.
FossilOrigin-Name: e8d79d2bae50d7443ea6b7274ca36ded4f64e0f540494651d705612474f9aeb1
2020-05-25 18:03:34 +00:00
drh e3e03777c7 Merge the 3.32.0 release into the begin-concurrent-pnu branch.
FossilOrigin-Name: 79663e66129f9522c4c2287d095129c7989f5bee41f2073f7c2c60ca5739f2f6
2020-05-22 18:17:06 +00:00
drh 27fab1c045 Merge recent changes from trunk.
FossilOrigin-Name: c69f030b1ed9bddcc9ea79fe2361f39901bf1461ba3efdb32ddabcaa44319b2a
2020-05-18 16:19:59 +00:00
drh cd35fbc9ca Merge recent trunk enhancements into the begin-concurrent-pnu branch.
FossilOrigin-Name: cedd138c749b777de0479a9e57aa9fe4f21cb99660abdc78c14e61afdef4f2f3
2020-04-09 18:44:15 +00:00
dan 6fff4502ad Merge SQLITE_NOOP_UPDATE fix into this branch.
FossilOrigin-Name: e2cc655e0d49ea8482f59aab910b6bec22873d0241beca70643783b6f641747f
2020-03-13 07:28:08 +00:00
dan f17089eb01 Fix a typo causing SQLITE_NOOP_UPDATE builds to fail.
FossilOrigin-Name: 1e2109fd339cfb3b906673479672ed2e5a0dc17a94210f364c6fc1a657a62ae8
2020-03-13 07:23:20 +00:00
dan 5ce296e585 Add link from begin_concurrent_report.md back to branch timeline.
FossilOrigin-Name: 61869f8b483a6bafa8fe321a9e9417da5ff13043795b8b676281c5f8ac3bf3f5
2020-03-12 19:37:16 +00:00
dan 1636746065 Add documentation notes and fix outstanding issues on this branch.
FossilOrigin-Name: b288bbd1df227f9566792491945c603d3736f238ca8a6ac1803cc385e6c3b621
2020-03-11 19:30:16 +00:00
dan 368c19a03c Support reporting of blob fields.
FossilOrigin-Name: 724a4a844d2a8fdffb79c14e1c2ff50f0db1c3e868afa4a88393dd0bddf51280
2020-03-11 16:46:21 +00:00
dan 76d155286c Fix test and other problems with reporting pages read and written.
FossilOrigin-Name: 9ae970fdc1c2c3ef9e50b4d6b98480fe255d15bbe6e1379e6e55d66516c5fde4
2020-03-10 21:14:55 +00:00
dan ecdf3d3f1d Fix problem in test script concreport.test.
FossilOrigin-Name: 32d1b7210a28d53665e089df7192fdf9730b247970c159368f6a6e4e7acce79a
2020-03-10 19:55:17 +00:00
dan 43f2b82e0a Report on pages, as well as keys, read and written by BEGIN CONCURRENT transactions.
FossilOrigin-Name: b661ccc88f45057f10b54c420f83c88c65e586bfe39bf1072b1b9efc8995418d
2020-03-09 21:00:13 +00:00
dan b7d8503461 Fix reporting cases related to UPDATE. And of WITHOUT ROWID tables.
FossilOrigin-Name: d28d1739a65599609765fd19e13a219a018a6f23834e002c8bbcddec12a96d30
2020-03-07 16:51:06 +00:00
dan e7e3d0f4c0 Enhancement to log logical writes as well as reads.
FossilOrigin-Name: 1c1f180eb69400689f0b3d14e51bcca57dafe0abd00efd2590e74912f7543041
2020-03-06 20:51:31 +00:00
dan c5d81f6f76 Fix reporting for indexed range scans that extend past the end of the data.
FossilOrigin-Name: ab458a319faa2797ac4ccee7777c8f108bd5148fac96eb58b7befade5436c128
2020-03-05 20:59:08 +00:00
dan d89f35652d Fix cases to do with index scans.
FossilOrigin-Name: 6ec5a8995fabb5a66aa846d149cfdb3d8a79933eeef79965dbb600baad1acf59
2020-03-04 19:17:14 +00:00
dan 04e141bdd6 Extend begin_concurrent_report() so that it reports on accesses to non-intkey tables as well.
FossilOrigin-Name: 427e2c9d95a92308406079b238aed63e32ddb5073e016e6f147ddec0918a6c86
2020-03-03 21:01:04 +00:00
dan cb96497699 Fix reporting for some range scan cases.
FossilOrigin-Name: ad961aee15f626ee0e60d3dce4711a4b457819cac86095551f1f0ae892a953aa
2020-03-02 21:00:58 +00:00
dan ef785abeb4 Merge latest begin-concurrent-pnu changes with this branch.
FossilOrigin-Name: b1880b1899df74e3d14fcd0441504dad98c9dd99c803140eb8f7646b89b9cf57
2020-03-02 15:05:53 +00:00
dan ab18639258 Merge latest trunk changes, including the SQLITE_ENABLE_CORRUPT_PGNO patch, into this branch.
FossilOrigin-Name: 23bc80cc8681762e42376e5d878fe60ccc9a6533b7d017e54280996ea363c3f5
2020-02-04 20:09:45 +00:00
drh d4a1c953a1 Merge version 3.31.0
FossilOrigin-Name: e65e6c01d632beec9a21758cce6f7ac2e3bc2c5fac88b3bb393b9040fb2cbea6
2020-01-22 21:06:17 +00:00
dan 43e09f5316 Begin adding instrumentation to record and report on the set of key-ranges a concurrent transaction reads from the database.
FossilOrigin-Name: c39e3c14a9d9b72da0f507786e01455c097646952dcb59465503203235fa3b33
2020-01-21 21:01:23 +00:00
drh 93b95c6c84 Merge all recent enhancements and fixes from trunk.
FossilOrigin-Name: 110a081f12ec83870bd42efe69363ca6c10d7d550c950008b40848f030919ecb
2020-01-15 13:48:19 +00:00
drh c697613beb Bring the begin-concurrent-pnu branch up-to-date with version 3.30.0.
FossilOrigin-Name: a4d26bcfb44a827d610d383b42a142ec807bb31700b9ba0842d9caaa47e8d9c6
2019-10-04 16:08:18 +00:00
drh f5078e74dd Merge version 3.30.0 beta 1 changes from trunk.
FossilOrigin-Name: ac4b64426822b8857acd6d4a97dffa9ce74ed85902a0f0e5c896ff2ec839a9d4
2019-09-30 16:43:38 +00:00
drh 47f677d8d7 Merge recent trunk enhancements into the begin-concurrent-pnu branch.
FossilOrigin-Name: 54930b7e0a585d0db3633673f33cac28cb257633224d6436ae29abdccbab1032
2019-09-16 12:17:11 +00:00
drh 3b5a4942f1 Merge the 3.29.0 changes into the begin-concurrent-pnu branch.
FossilOrigin-Name: c89949ccd102a180bf6254aef825bd0c29494d450a64bd1c3e0d347cb3dfad66
2019-07-10 18:21:47 +00:00
drh 42f5c182f6 Merge enhancements from trunk into the begin-concurrent-pnu branch.
FossilOrigin-Name: 05aa93afdbf9ae3569c827b24973b6c0af4c03b5faecb4283ba571f8bc64f0e9
2019-07-08 21:12:38 +00:00
drh 2bbacc1cf3 Merge the latest trunk enhancements into begin-concurrent-pnu.
FossilOrigin-Name: e70878b363db8ed0ed8aa345342a4174d54a8d95330b1bffc2843f0b07332526
2019-05-14 21:51:57 +00:00
drh 7773fffb18 Merge the latest enhancements from trunk.
FossilOrigin-Name: 6caaaf21c930b91636127480b6e06f6fc86b1a2e34c96780b64cd16e6e4c2d81
2019-04-15 15:07:11 +00:00
drh bfaa3dbd61 Bring this branch into closer alignment with begin-concurrent.
FossilOrigin-Name: 6433d366175c5be586a0e11b540e76a2515466b358332c4356388f4c449ec0d7
2019-03-26 13:28:15 +00:00
drh 841efe0ca1 Merge the latest trunk changes.
FossilOrigin-Name: 51e3e835490a12dca6359ca02b8cbf31091fc4376b265995a986d002d376fad9
2019-03-26 12:16:24 +00:00
drh 7e1f717b09 Merge all the latest enhancements from trunk.
FossilOrigin-Name: 667cce3dce39487e970c4eb43efd1bae7efed7ac6ee31030c582bfafa846dcce
2019-03-08 15:30:48 +00:00
dan db6fe08914 Merge latest begin-concurrent changes into this branch.
FossilOrigin-Name: a93ca38b432e0c9fb2e00499cfc88d09f59f27f908f377d8ae99b6717e548a60
2018-12-18 17:47:53 +00:00
dan b031157128 Merge latest trunk changes into this branch.
FossilOrigin-Name: 123cbb3312917ad5b3c32556547c5b7e8ba4e2d2def8651ff80f9fc1bdc1875c
2018-12-18 17:20:01 +00:00
drh ef180caded Merge fixes from trunk.
FossilOrigin-Name: f2083ee410b7504e8478f7373e76ded913e3a6a92cb7345b8c1ac27554f6edf8
2018-12-06 03:13:02 +00:00
drh 9c1eb40d69 Merge bug fixes from trunk.
FossilOrigin-Name: 1e13aaa29fb6324b60b3758bdab0491fdef9727e0de603d3da3e1885b52c5caa
2018-12-06 02:08:05 +00:00
drh 9ee5641ae0 Merge ALTER TABLE and sqlite3_normalized_sql() bug fixes from trunk.
FossilOrigin-Name: 3793e5d5d60f909480c83bc2a6473d27e414d69b0c0e3289413c9bfd2f15bb08
2018-12-06 00:08:48 +00:00
drh 254fa054ec Merge ALTER TABLE and sqlite3_normalized_sql() fixes from trunk.
FossilOrigin-Name: acf10b3f8d6675ddf787f7db55a9ff0ec5729adb6fa70b48b13d2cd71862cffd
2018-12-06 00:05:18 +00:00
drh a693b000df Merge enhancements from trunk, especially the sqlite3_normalized_sql()
enhancement.

FossilOrigin-Name: 342c9538d9c6a993ac0acaa6f74ad58886bcef7bb53783d053f9b24c131aec5d
2018-12-05 13:49:23 +00:00
drh f8d7432aa3 Merge enhancements from trunk, especially the enhanced
sqlite3_normalized_sql() interface.

FossilOrigin-Name: 47b73f6bfee8c5e41c408fb70dff0e4596c0a3eb0aeba40ad232a6cb1fe75532
2018-12-05 13:44:19 +00:00
dan 50232dd821 Fix a problem with SQLITE_ENABLE_EXPENSIVE_ASSERT builds on this branch.
FossilOrigin-Name: ddb4a6fbf8619db058b5eb8fcd687084ed4b65a6f69810357e324158257a911f
2018-12-04 13:51:43 +00:00
dan 834c48c279 Minor change to wal.c on this branch to make it more similar to trunk.
FossilOrigin-Name: 6a7af3ead5949c461430c1fa92798dc2bbbc58c8cd504005c5afa38993f0be82
2018-12-03 20:38:15 +00:00
dan 51883dfc8b Cherrypick a couple of fixes from begin-concurrent-pnu into this branch. The
differences between the two branches are now that this one does not have
"PRAGMA noop_update" or the mutex-free PRNG.

FossilOrigin-Name: a56506b9387a067ef259504d127694ad20223f4b08781d1676ff7f5fdd9443d8
2018-12-03 19:29:37 +00:00
drh 15e8543ecd Merge in all changes for release 3.26.0.
FossilOrigin-Name: 85fd92c71fd5b29bd45bb1717a5be761600fc23bed12eaff9eed708701a0fdf7
2018-12-03 18:24:31 +00:00
drh a63585eb8e Bring up to date with version 3.26.0.
FossilOrigin-Name: f0ddb358cc68e5ec6d9e758893ab3da058a3b2e705124a7449279c992e672a4a
2018-12-03 18:15:52 +00:00
dan 9b5c67f784 Merge the mutex-free PRNG change into this branch.
FossilOrigin-Name: 81e626f4a44be75425cf916ec61b6b36df0907ebac4adbf6786f87ad4f3a0674
2018-11-30 16:26:39 +00:00
dan 8e90c85bfb Merge latest begin-concurrent changes into this branch.
FossilOrigin-Name: 76608f750ab13c0a165def9672759fee43cf4e9895df3bfa21765e08358b07a0
2018-11-26 07:34:33 +00:00
dan dcb3992c66 Merge latest trunk changes into this branch.
FossilOrigin-Name: 28a615a2e0f48b0fee3eaf7841ff902e069fa6c221df6ad9a57b8709c88561fb
2018-11-26 07:21:06 +00:00
dan 0131a914d3 Merge latest begin-concurrent changes into this branch.
FossilOrigin-Name: 6f3dd9809fdef7d6195f1f93428a662d5a8c01dba9815daa22d1b94059a2eb43
2018-09-28 21:11:12 +00:00
dan 2dd93c8838 Merge latest trunk into this branch.
FossilOrigin-Name: 86750c927cb5ba36acad33d5279629699c42b598e70fdc4887b40a1a16771ff6
2018-09-28 20:58:10 +00:00
dan c1b7fdd43e Merge latest begin-concurrent changes with this branch.
FossilOrigin-Name: d33527d2231db9eda53124d3e11582a6b2eefa994eb7d9435fb349d3d8522ed7
2018-07-12 19:39:45 +00:00
dan fccd5d43a9 Merge latest trunk changes into this branch.
FossilOrigin-Name: 6a00a34e198408d69d638a25763f64b8a5beec61a60affb4d344426a2e232d72
2018-07-12 19:28:43 +00:00
dan ec6e332f01 Merge begin-concurrent changes into this branch.
FossilOrigin-Name: af17432eb135895f9a55115c0eb364745715bb2dc14da01cbb2dc498e61c1853
2018-07-10 15:48:39 +00:00
dan eb30704cc9 Merge latest trunk changes into this branch.
FossilOrigin-Name: e9a3e8642e7acc8e63b35662ffae3a5a0da8d4f1ecefcb1db3110be19cbdab2b
2018-07-10 15:45:54 +00:00
drh de26c16ea1 Merge the 3.24.0 changes plus a few subsequent enhancements.
FossilOrigin-Name: be7004a971a174ae5c862cb3fa29f3586882d9ed1630d3e50e686d7310ccbc91
2018-06-06 17:12:46 +00:00
drh 0c1fa5c8c6 Merge changes from trunk, including all the 3.24.0 changes plus some
later enhancements.

FossilOrigin-Name: d7299bfeb1eb031d6106b46139715047fe759d27b34b617ca43f2cd377b8188e
2018-06-06 17:03:53 +00:00
dan 6ccceeb3bd Merge latest trunk changes into this branch.
FossilOrigin-Name: 72f39efa9b1b97a54fe35d005b48f7e8b57d6285ba42f36487f796e09c710958
2018-05-15 11:55:15 +00:00
dan 4921c159bd Merge latest trunk changes with this branch.
FossilOrigin-Name: ae86cf60b6648a7ec789e233c9b0cc826efbbb0f301140b4848dc84d691ccd4f
2018-05-15 11:45:47 +00:00
dan 1d735ad3bb Instead of just the flags byte, include the first 8 bytes of the relevant page
in an on-commit conflict log message.

FossilOrigin-Name: 52e443eb5523963a6b09be66ab1c7281930d0155bf3df13eee0ec9066dbc7f0b
2018-05-15 11:33:42 +00:00
dan b7ee5667c2 Instead of just the flags byte, include the first 8 bytes of the relevant page
in an on-commit conflict log message.

FossilOrigin-Name: e7dc03e7439f224e4bec458f27fe364f2fb1ac637328657a578ada693ae22842
2018-05-15 11:28:36 +00:00
dan a233f09330 Include the value of the "flags" byte of the relevant page in the log message
emitted when a BEGIN CONCURRENT commit conflict is detected.

FossilOrigin-Name: de19abb950e4186c491c39112a52062871ac44fc2bdc5b611e9fdfa611352716
2018-05-15 09:03:57 +00:00
dan bfbe2b8c80 Include the value of the "flags" byte of the relevant page in the log message
emitted when a BEGIN CONCURRENT commit conflict is detected.

FossilOrigin-Name: fbfa547177ac7d57dcb0775d761a7f92fc1f2263cd83e2843cd109139a8c5121
2018-05-15 08:51:05 +00:00
dan 886fac36ba Merge latest changes from trunk. Including the "ORDER BY ... LIMIT" optimization.
FossilOrigin-Name: 83a4c9f0885768ed3cbe556aed9f4709a1d7a1cd52f2e3632ade28091febee47
2018-04-26 18:01:48 +00:00
dan 4af5f425f2 Merge latest changes from trunk. Including the "ORDER BY ... LIMIT"
optimization.

FossilOrigin-Name: d8ae7ba083551f05b963f13c5164ff60257c7b615cd48d675027cb6289b463da
2018-04-26 17:54:11 +00:00
dan 8ba5d0d4f7 Update this branch with latest changes from trunk.
FossilOrigin-Name: 2d8d13e4a169cbfa957c4e978b559817a13927a85e30fe275129bf7e3fb54920
2018-04-24 19:22:49 +00:00
dan 51abf049c7 Merge latest trunk changes into this branch.
FossilOrigin-Name: b27bd799ea66748b89223044c536813d56d0aa513f077499cbf8ed9c974d7b01
2018-04-24 19:21:38 +00:00
drh ed432167dc Merge all recent trunk enchancements, and especially the autoincrement
write reduction fix.

FossilOrigin-Name: 257900494f9aac7e4181b65d0eddc9cf2e9a52dc794ad68284f085d1c402addb
2018-03-20 14:00:25 +00:00
drh cdbf6d01a4 Remove debugging puts from concurrrent3.test.
FossilOrigin-Name: 13b4975681f249831a22562d1c26958f841ea77ca779858b0bee735dd30b710e
2018-03-20 13:54:22 +00:00
drh 3a5fc00808 Merge all recent enhancements from trunk.
FossilOrigin-Name: b0c2f760a637ee973f4dcc27308eec44950e6d0a9c5ab5c828c1210c1f868efa
2018-03-20 13:52:42 +00:00
dan eb816a7547 Merge latest changes from begin-concurrent into this branch.
FossilOrigin-Name: 8ade94ba6712827c672ddad8436f8b97cc520ced122e7d0043ad3c3ba94f1ed6
2018-03-02 18:26:50 +00:00
dan 908803f5e0 Update this branch with recent checkpoint related changes from trunk.
FossilOrigin-Name: fb6b7938601505186c0b1f0df6c45630039027f85ff394d290bc2c86b16a3a07
2018-03-02 17:59:37 +00:00
dan 699bd8121e Merge latest trunk changes into this branch.
FossilOrigin-Name: 36801effa9ec67b551f58972e21794466420f10cd0420701fcd87695e6cd11ee
2018-03-02 17:40:23 +00:00
dan c7a82772dc Add extra code to log details when corruption is detected in the pointer-map
structure maintained by the b-tree layer in begin-concurrent transactions.

FossilOrigin-Name: 570233716032f258b878d52c4d5a47e07292d66fa84e3a85c0388ec15efee625
2018-02-20 21:00:45 +00:00
dan 5d9c916150 Fix problem causing free-list corruption when merging free-lists for two
concurrent transactions that have both used page X as an in-memory free-list
trunk page, where X lies past the end of the initial database images.

FossilOrigin-Name: dc0fc2aa7cbefeb5f0ba8c992fd3e9adcfb5a4d61e2321c1bd93f4d36ba9aafc
2018-01-04 18:36:39 +00:00
dan 55fba4f05c Fix a spurious SQLITE_CORRUPT error that could occur within a COMMIT of a
concurrent transaction.

FossilOrigin-Name: 50c8952c92b9f0c61935fb0df04ed1426d9e266a812071b7bf5b0215c5552757
2018-01-02 19:57:26 +00:00
dan 02bc6f8fb9 Merge latest begin-concurrent changes into this branch.
FossilOrigin-Name: 3fde0b4d05c249b9d2a54dd721185202c353cee23c0351b634cac349dc0a7b14
2017-12-12 18:17:09 +00:00
drh cb0267185c Abort on an invalid paramater to sqlite3BitvecSet().
FossilOrigin-Name: 163c870950f386f6b0bb1ff9b3886cf95ba0deed414cae75baf87621ed3528c2
2017-12-11 14:02:10 +00:00
dan 165ba4a2a5 Merge changes from trunk. This fixes the SQLITE_ENABLE_UPDATE_DELETE_LIMIT functionality so that it works with views and WITHOUT ROWID tables.
FossilOrigin-Name: d90e5f346bcf7adab26ca8dad9dfbd0fbb86604a15f2fe827f11b3faab036750
2017-11-14 20:06:15 +00:00
dan 8706485d1a Merge latest trunk changes into this branch.
FossilOrigin-Name: 7f217edab4575554f657d38e2a1bc6b3f577998fdbecb04eb200aeb8b8406581
2017-11-06 20:02:08 +00:00
dan c0f69fcec7 Allow "BEGIN CONCURRENT" transactions to modify the temp schema.
FossilOrigin-Name: 0fb6d91cea347384fc081ce4c79582b365801dd4f56f5bf2ed40922bbfca0344
2017-11-06 10:04:45 +00:00
drh 4d13729861 Fix the SQLITE_NoopUpdate #define so that it occurs under the correct
conditions.

FossilOrigin-Name: bdf791f9f7f7ab4b1871e1d3a0d5edcad46fc67d7336ae7f3f7b20e69801be8e
2017-11-01 19:30:41 +00:00
drh 56391f2769 The "PRAGMA noop_update" command now requires SQLITE_ENABLE_NOOP_UPDATE
and no longer requires SQLITE_DEBUG.

FossilOrigin-Name: 81baf67c4493468e4feb2f4990bf82d59804ce4f3149252c0e1e8c43f90d6bc1
2017-11-01 18:48:54 +00:00
dan 7393c7424e Cherrypick [ec37ad6d08] into this branch. With this patch, if
SQLITE_SHARED_MAPPING is defined at build-time SQLite will use a single memory
mapping for multiple connections to the same database file within a single
process.

FossilOrigin-Name: c7a5880d6d898299b4c9414b7702cfa450aa5f7bf4ec8f417b94d2a7b6558264
2017-09-22 11:09:09 +00:00
dan 3eba5d00b6 Merge latest trunk changes into this branch.
FossilOrigin-Name: 307b802e8627c93a51e4c54851a4fab33db5061bb80e3d327ce53b127d6d511b
2017-09-22 10:49:03 +00:00
drh 03d405f046 Add the highly-experimental "PRAGMA noop_update=TRUE" command.
FossilOrigin-Name: afe45271b9c0cd379cf0beb94657e2396068c4a18f84003c4c48297760fd83ee
2017-09-12 20:09:31 +00:00
drh b25057c291 Merge recent enhancements from trunk.
FossilOrigin-Name: d53108e76307fb24ef1d8f75fe82c180f5588ad8e65eeceace92f4cecadfa452
2017-08-28 17:19:35 +00:00
dan 9c54156a9c Test BEGIN CONCURRENT transactions that consist entirely of read-only
statements.

FossilOrigin-Name: c3fe1f4b7e8dcadcb516622719d000b808effe3ad497244ba44f57d52dc2cc08
2017-08-24 10:10:28 +00:00
dan d828d0debc Add a more rigorous test case for the bug fixed by the previous commit on this
branch.

FossilOrigin-Name: 4256072399f44f48ed0856aa8112226af6feaf8676923612bde6cea239ebf920
2017-08-12 14:06:15 +00:00
dan 6aab028db0 Fix a problem allowing a conflicting transaction to be committed in the case
where more than one 32KB shared-memory page has been written to since the
transaction was started.

FossilOrigin-Name: 38dd9b50fe260d853cbc2551bc1bb60ddf5752f0456e0da3afe4cbf728c891d8
2017-08-11 21:16:23 +00:00
drh 9f16a96d4b Add another PAGERTRACE() macro to show when pages are added to Pager.pAllRead.
No impact on production builds.

FossilOrigin-Name: 11054cf5e8c24ef9ca869d558a0ca6750b56103c3b3793dc4afcef75192ea943
2017-08-11 20:22:30 +00:00
drh 741802e825 Add a PAGERTRACE() macro to log when frames are written into the WAL file.
This is for analysis only and is a no-op for production builds.

FossilOrigin-Name: 39f39e3d9a083eebe2f8dd5812d20c5b07cc17607b04b573a52fde6d00666f36
2017-08-11 19:31:53 +00:00
drh fd80ce4b10 Merge the latest enhancements from trunk.
FossilOrigin-Name: 47e716952d4a5d893b75726a2c52202cb0bc8ce1f75131e920a3ac2e202a507e
2017-08-11 19:16:55 +00:00
drh 9ee9b0f4c3 Merge recent enhancements from trunk.
FossilOrigin-Name: aafe1fec803ddf207bbcb3c669da0a9639411375fc04a52066dde4eb99c3c11b
2017-08-01 13:54:55 +00:00
drh c5856def1e Minor fix to the concurrent.test module so that it works on windows.
FossilOrigin-Name: 58553d61d199477f88ea9b45055ddaf821eca2aff1bf67c7d81fa80b23c2575a
2017-07-20 19:28:49 +00:00
drh 0a2afca904 Fix compiler warnings.
FossilOrigin-Name: 44c760d150b494ddd88382360cfcc4734fec936ed620f88b9df33ea7215c2fc2
2017-07-20 19:08:35 +00:00
drh 1af0a4e1df Fix a faulty #ifdef on the previous check-in
FossilOrigin-Name: 7355e74239fc20f3d948867902675069e6a4efd103bf734f1b24a132f6d2edd1
2017-07-20 18:56:21 +00:00
drh 7365bcd72c Get the build working with SQLITE_OMIT_CONCURRENT.
FossilOrigin-Name: a29401e924e6f82ca1c3eec98c6c0b2f1d5b9d7fba9af41a78516f21a7bab3a2
2017-07-20 18:28:33 +00:00
drh 1c6276eac6 Merge all the latest trunk enhancements.
FossilOrigin-Name: 213c61cb82d4ee51cc638fc5bb2b54cd51b963c40d088b2f01caf77032ea32fb
2017-07-20 17:47:12 +00:00
dan 827c9b785e Remove sqlite3_log() and abort() calls added to this branch to debug the
pointer-map problem ([fda22108]).

FossilOrigin-Name: 79544fc2856f30cac8b0962d00698974e8918562f09769a68264d17e1e1176fe
2017-06-10 17:23:20 +00:00
dan 4956bd5f9a Update the recent auto-vacuum fix so that it works for the in-memory
pointer-map structure used by this branch.

FossilOrigin-Name: 8e311a6dba202e8733830d8f31b8f0ce11eaefb3a0ab5e5e95ac0d2e5136043b
2017-06-08 16:23:55 +00:00
dan 63624a0ab8 Merge 3.19.3 changes with this branch.
FossilOrigin-Name: e2d38d51a9cf1c3dfef742507ec76e3d35853bd09b0d09bf2d404c4b036a184d
2017-06-08 16:10:41 +00:00
dan dc1bf28ad4 Merge changes for version 3.19.0 with this branch.
FossilOrigin-Name: 0f59bb94d5e8e16a47c53ef0d6138a35c87230ad384b58008e8e7127780446d8
2017-06-08 15:54:02 +00:00
drh d6396e9b16 Version 3.19.3
FossilOrigin-Name: 0ee482a1e0eae22e08edc8978c9733a96603d4509645f348ebf55b579e89636b
2017-06-08 14:26:16 +00:00
drh d4d01a689d Fix an issue with OPT_FEATURE_FLAGS in configure.ac.
FossilOrigin-Name: 97b5c4a53d0af3792c5ae5ceafdb02245f841c2bd4d79bcfd0ffdf6c5ff27c9e
2017-06-08 11:32:19 +00:00
drh a096fcc73c Increase the version number to 3.19.3.
FossilOrigin-Name: 903fff53b3f02cbc011c4cd47d841f45a447746fdf3bcb01202e27c3f2b57589
2017-06-08 11:27:05 +00:00
drh 50179f91f3 Ensure pointer map entries are always added when a row that does use overflow
pages replaces one that does not in an auto-vacuum database. Fix for
[fda22108].

FossilOrigin-Name: 9478106ca963e3ae5cfe59da40301c5a0a07494d03e656b5eb10ab45e441b870
2017-06-08 11:26:13 +00:00
dan 35dcd0e892 Extend even further the logging designed to find problems in the pointer-map
structure. Call abort() to dump a core as soon as such a problem is seen.

FossilOrigin-Name: f131677dcb4937e0dd62626afa91756aa28079e92acd6e9e127f6f676aa334f9
2017-06-02 09:31:27 +00:00
dan 7f002db7e3 Generate extra log messages in response to irregularites in the pointer-map
used by "BEGIN CONCURRENT" transactions.

FossilOrigin-Name: f7e3e2bc88f110d9282ce5d2fa58580c585faeb57cb707253f05001e5f4bd91b
2017-05-31 17:06:13 +00:00
dan 995b2457e2 Instead of a root page number, log the object (table or index) name if a page
level locking conflict is detected.

FossilOrigin-Name: 9ad846e57bd427adc7c29768cabca18905f7f978168e0642a5917d894fda8bfd
2017-05-29 19:23:56 +00:00
dan 7fff2e1cb9 Enhance the log messages emitted when a page conflict is detected.
FossilOrigin-Name: 92618492b048867af38922825f3d094eaaa2dd919b1ed2f7372483cc53f892bf
2017-05-29 14:27:37 +00:00
dan e3b047b3d7 Adjust the bitvec related sqlite3_log messages added by [9527089b].
FossilOrigin-Name: a7e0e7a4835314a04e065839a7f88b074eda795da2b2da5741b2afc096421a32
2017-05-26 18:18:51 +00:00
dan 7037787d30 Add extra test cases for deferred page allocation.
FossilOrigin-Name: 9df0195780306a26709e87daabf9d426856aebf2082060b98818f2d48c7472ec
2017-05-26 16:51:15 +00:00
dan 606f718480 Fix a problem with deferred page allocation in transactions that revert page
allocations by savepoint rollbacks.

FossilOrigin-Name: a4a3bbe64690856403642352f36e664a5a7fba686463a63446c6ada99df4e89f
2017-05-26 16:15:05 +00:00
dan e3c3be8511 Fix a problem with the deferred page allocation on this branch that could
occur when the database file is just slightly smaller than the PENDING_BYTE
page offset.

FossilOrigin-Name: 47a7dd92355ffd74645cf8da8186d5799c05a95907877a09c28de41135deeede
2017-05-25 21:02:00 +00:00
dan aa59505ae8 Add experimental new API sqlite3_wal_info().
FossilOrigin-Name: 5b9d498f6e9de6ee2ab86370c02c28a2a8b83d717b96d23b1fc52107677e45a2
2017-05-23 19:23:45 +00:00
dan 55b36d5c07 Invoke sqlite3_log() in response to irregularities surrounding the
Pager.pAllRead bit-vector.

FossilOrigin-Name: 9527089b7aa3695cd577f31b263b4777e9bd62dbbc1bd3af892c570e52e8c3a1
2017-05-19 19:57:15 +00:00
dan 6dbb452b38 Fix a problem on this branch causing some page-level read-locks to be omitted.
FossilOrigin-Name: 0eed152162b4721f7aaba8b480426476978a869e9578f100fca7b1d32942fe1a
2017-05-18 20:47:31 +00:00
drh 2ecf1ec8c9 Merge the latest changes from trunk.
FossilOrigin-Name: 14ea84003600ada6f4605e47028cb72fe621a14bcce3637ad48b1512dcce41cd
2017-05-15 17:34:23 +00:00
drh 9a89254e7a Merge changes from the 3.18.0 release.
FossilOrigin-Name: 785c37d9dbdba9f3deedecbdd72f6761428ebfb58e9b9393b9ca4feab3f66f02
2017-03-30 20:35:30 +00:00
drh fe329a7370 Version 3.18.0 Release Candidate
FossilOrigin-Name: 424a0d380332858ee55bdebc4af3789f74e70a2b3ba1cf29d84b9b4bcf3e2e37
2017-03-28 18:48:43 +00:00
drh 4d55d35df6 Merge all recent enhancements from trunk.
FossilOrigin-Name: fd5676fe7f55b3d4fa15ca119af7c064c6f9d053affdfd5e748785e300affbeb
2017-03-24 15:09:47 +00:00
dan 246e1d8faf Merge the "changebatch" functionality into this branch.
FossilOrigin-Name: 50fb1eb368f14e4ddce2da875601c227f9f937fd
2017-01-09 07:00:41 +00:00
dan 0195d8ef5d Disable the optimization from [8cb8516d] (omit TableLock instructions for
non-sharable databases) on this branch. This branch uses the TableLock
instructions to ensure that the db schema is not written from within a BEGIN
CONCURRENT transaction.

FossilOrigin-Name: c8ca3e0a8d82dbb077551c2d952cb2043f78333b
2017-01-09 06:53:00 +00:00
dan e32c7c2b64 Upgrade this branch to 3.16 plus the various fixes that appeared after its
release.

FossilOrigin-Name: d0e212d08f82dfb5e42a156b3e2bb03dd8e21258
2017-01-09 06:33:50 +00:00
dan 0ba9725489 Merge latest trunk changes into this branch.
FossilOrigin-Name: 391344d88a284f92c59b8d96a315b69292641de0
2016-12-07 07:46:50 +00:00
dan ff8e42e2ca Add the sqlite3changebatch_db() API.
FossilOrigin-Name: bee44ebc532f37e3fe61c18878e0d3db06805190
2016-08-24 19:14:59 +00:00
dan a8dee8df64 Add further tests for changebatch. And a fix to prevent a changeset from conflicting with itself.
FossilOrigin-Name: 506d6ff4b64c72d4ca65f0d15e1fdf8a132556bc
2016-08-23 19:02:21 +00:00
drh 7d931b985a Fix harmless compiler warnings in changebatch.
FossilOrigin-Name: a721a738184d914fcde3f5684099984a9373dff3
2016-08-23 18:09:37 +00:00
dan 8bbf544747 Add a new method to sessions - sqlite3sessions_fullchangeset() - to return a changeset that always contains values for all old.* fields. Update changebatch to use these values to more reliably detect multi-column UNIQUE constraint violations.
FossilOrigin-Name: efa761b2f509844b9212dd20bf0d082c6338e83f
2016-08-23 17:02:28 +00:00
dan a7d16a5cd1 Add a couple of extra tests to changebatch1.test.
FossilOrigin-Name: 207d970b7956c38af42c389b91a741a68b2c4eec
2016-08-22 21:01:39 +00:00
dan c18d304066 Add an experimental module to detect conflicts between sessions changesets.
FossilOrigin-Name: 0c9fd6b723041955b5182caa430312e5124fdc83
2016-08-22 20:49:06 +00:00
drh 3d4ad18cbe Merge version 3.14 plus some subsequent patches (including the page-cache
performance patch) from trunk.

FossilOrigin-Name: d9f8918c5b7b6c8540b3f433142e1b4aa4433885
2016-08-11 19:12:25 +00:00
dan 21ac1de75b Fix a typo in test program bc_test1.c.
FossilOrigin-Name: 2c61b7ab1808d5576a21fea84c3f414db51abfa5
2016-07-06 08:32:53 +00:00
dan 96b9dc9b36 Minor tweaks to the bc_test1 test program.
FossilOrigin-Name: d0d0bab4e92402b6af98366be6e8955588613a51
2016-05-30 05:45:32 +00:00
dan adeb47e57b Add further instrumentation to the bc_test1.c test app.
FossilOrigin-Name: 5528de4a53c19557798b6169e1d738f1a301e131
2016-05-26 20:52:15 +00:00
dan 12891cc492 Use pthreads mutexes and conditions to synchronize threads in bc_test1.
FossilOrigin-Name: f33aa76f074d8686a5a5c0edecabb71cb259c48d
2016-05-21 18:50:56 +00:00
dan 820fc4933b Add options to bc_test1.c to make it more flexible.
FossilOrigin-Name: ec6ef5f2c2dc18e1a19c205f365f4071f0870b68
2016-05-07 18:02:53 +00:00
dan b00083429a Add test/bc_test1.c, for testing the degree of concurrency provided by this branch under various conditions.
FossilOrigin-Name: 128c7eaed5479b615d75ebce1d781ea661e0fb2d
2016-05-06 21:04:47 +00:00
drh b0b18f6dca Merge the latest enhancements from trunk.
FossilOrigin-Name: 91e5c07eaf884d3598df8eb54f0910a80df48397
2016-04-29 16:01:18 +00:00
drh 76cb74da4d Merge all recent enhancements from trunk.
FossilOrigin-Name: 1f709fbf931e4d75848fc90532e2bc67ccd47cd4
2016-04-20 12:16:05 +00:00
drh db454d72b6 Merge 3.12.0 changes.
FossilOrigin-Name: d7381efff47c0a2f307478f196d03df7534f19e3
2016-03-29 10:36:07 +00:00
drh 99b14894eb Merge all recent changes from trunk.
FossilOrigin-Name: 8ee7d346c1d9beedd02bf11d0a45810985a45480
2016-03-21 15:54:46 +00:00
drh 03206c433c Merge the latest enhancements from trunk.
FossilOrigin-Name: a7978ab6d26b865f55c6b3e86ba3a24777a3c1f0
2016-03-16 19:34:45 +00:00
drh 93f88ebbb1 Merge version 3.11.0
FossilOrigin-Name: c393ddc71a041a711f8eaa6fbd75371309df5602
2016-02-15 17:44:45 +00:00
drh faf010d77a Merge PRAGMA synchronous=EXTRA and OOM performance enhancements from trunk.
FossilOrigin-Name: 332c8036ca8a97087ced210e322c8a91be2a9036
2016-02-05 14:34:51 +00:00
drh 7c9ecc62c5 Merge recent enhancements from trunk.
FossilOrigin-Name: 347f6a80c2aa6dde9b58d28069156db22cd21b6d
2016-02-02 02:53:49 +00:00
drh 04b9cf1e7e Merge recent enhancements from trunk.
FossilOrigin-Name: 5520f600eade720f465575ab1661dcddaba5adcf
2016-01-20 12:18:31 +00:00
dan f687ba597f Merge the latest enhancements and fixes from trunk.
FossilOrigin-Name: fccc5f20c360fa17c3692a9f1fc1d16612632a7f
2016-01-14 15:46:31 +00:00
drh 76ff0699bb Merge changes for version 3.10.0.
FossilOrigin-Name: e1d2ffc06d47b550ca9447a71443dea08b98d1ad
2016-01-06 15:49:19 +00:00
drh 607d4c2184 Merge recent trunk enhancements.
FossilOrigin-Name: 6bea792c3d71be34ccb9c14a0df8a5244d2d3678
2016-01-01 14:14:55 +00:00
drh 8c9ea487d0 Merge recent enhancements from trunk.
FossilOrigin-Name: cb22efaf50d83d9a73fdf8d986e6ea2fc6500cfb
2015-12-16 19:55:57 +00:00
drh 1d9497a0d0 Merge recent enhancements from trunk.
FossilOrigin-Name: 9130661a786e4c158f15103be57467a5cc03875a
2015-12-02 20:53:14 +00:00
drh 31441fb50b Merge all the latest enhancements and fixes from trunk.
FossilOrigin-Name: 41c8b8e39bc0483cfbc0b4bfcc8ef8b2737a70a9
2015-11-20 13:49:01 +00:00
drh f12cc2a68c Merge the sqlite3_db_cacheflush() enhancements and other changes from trunk.
FossilOrigin-Name: f2cde4cfc58cc372f59ae274bf0c2f7cf6e7ddf9
2015-10-30 17:17:12 +00:00
drh cd9491ca2f Merge the 3.9.1 updates from trunk.
FossilOrigin-Name: bf866e6c0d95d48744c86ff0c0be9b2653629a18
2015-10-16 20:55:12 +00:00
drh e8a1ea6d8b Merge in the 3.9.0 changes from trunk.
FossilOrigin-Name: 5c3a2a6ed64f4d0e10238720bc2e4ae3af3eded3
2015-10-15 07:44:58 +00:00
drh 0faad42d59 Merge all recent trunk enhancements and fixes into the begin-concurrent branch.
FossilOrigin-Name: c63c1e15f8cc8981aa4dbec82d510ed70cd09797
2015-09-24 15:17:54 +00:00
drh 411547ec6c Merge enhancements from trunk.
FossilOrigin-Name: fc4d1de8aeb39a4c0ea9275a0bd2447535f1a955
2015-09-15 19:16:47 +00:00
drh 9a5a469c7a Merge parser enhancements and other improvements and bug fixes from trunk.
FossilOrigin-Name: 9cf3e51bcce1268dbb22cc8fa77160db3cb72746
2015-09-07 20:22:22 +00:00
drh 573ebef187 Merge the latest trunk changes, and especially the fix for allowing
strings as identifiers in CREATE INDEX statements.

FossilOrigin-Name: a9b84885aa572b7f92e5aafa246af328d13e3e6e
2015-09-04 17:22:03 +00:00
drh 144cd3df05 Remove the EXCLUSIVE and CONCURRENT tokens from the tokenizer. Let the
BEGIN statement be followed by an ID, but throw a syntax error if the ID
is anything other than EXCLUSIVE or CONCURRENT.

FossilOrigin-Name: c0bf92eca4d3d4666e8a0476ef30fa8123de1cb0
2015-09-04 16:39:16 +00:00
drh 3561ec2ede Merge performance enhancements from trunk. This branch now runs (slightly)
faster than the 3.8.11.1 release, though still slightly slower than trunk.

FossilOrigin-Name: c490bfb150c66763226a35e30ba289abbf29906d
2015-09-03 20:52:44 +00:00
drh 01be463eeb Changes from ENABLE_CONCURRENT (default off) to OMIT_CONCURRENT (default on).
This is not a clear-cut decision and might be changed back.

FossilOrigin-Name: f8ae9bfd05abc35293ad6bc62ab1bdbe357d964e
2015-09-03 15:17:12 +00:00
drh a62b6bd93c Merge trunk optimizations.
FossilOrigin-Name: 71e7299e8d501618b10f8c1f78572789b1b6853a
2015-09-03 14:04:05 +00:00
drh f56af5ee2d Merge the latest trunk enhancements.
FossilOrigin-Name: 3dea047465fa8e3031046a33016b6ed760336ddc
2015-09-01 18:01:16 +00:00
drh 3f531da564 Fixes so that it builds without warnings both with and without
SQLITE_ENABLE_CONCURRENT.

FossilOrigin-Name: 5ed2a445a164f0f0c2669c6681ea76618e639961
2015-09-01 17:48:54 +00:00
dan 7b8996c878 Merge latest trunk changes with this branch.
FossilOrigin-Name: 57bc0194f41dbcd2c343e665e7af475cd4dd7328
2015-08-28 09:27:51 +00:00
dan c8a9d15887 Add header comments for new methods in pager.c.
FossilOrigin-Name: 437c7e219d3240767a28f73487bc26c3be3044b3
2015-08-27 19:57:16 +00:00
dan 6b3e51bd33 Add test cases for concurrent transactions and long-lived SELECT statements.
FossilOrigin-Name: fd4798cb7af263409c20d3cf81236b830bd68570
2015-08-27 19:22:56 +00:00
dan 987f821f79 Fix a problem whereby concurrent transactions would not consider pages read by the transaction before the first write statement.
FossilOrigin-Name: fc17f73170a27c2fe511ed6b6d488535c4e35bae
2015-08-27 17:42:38 +00:00
dan f6cf5ea790 Fix an assert() in pager.c that could fail in a concurrent transaction.
FossilOrigin-Name: 69394ddaa2bc9d26477b4359c676c598b733ac9f
2015-08-26 18:54:45 +00:00
dan de36c76a23 Fix a problem allowing some conflicting transactions to be committed.
FossilOrigin-Name: a0566382d564ca17fd13475a44fed8f714742d97
2015-08-26 18:02:20 +00:00
dan fef3410f7f Add miscellaneous test cases for concurrent transactions.
FossilOrigin-Name: 779b1d0e17bc54062b2b09cdbf94e9e2f4bae4f7
2015-08-25 19:10:29 +00:00
dan 57888f7300 If "PRAGMA integrity_check" is run while the database is being written by a CONCURRENT transaction, do not consider unreferenced pages to be an error. They may be part of the free-page list, which is not visible at the b-tree layer when running a CONCURRENT transaction.
FossilOrigin-Name: f32b57b49311693eb0c0c9f6f14859e7b1fa93d8
2015-08-25 17:16:33 +00:00
dan 4073b26a20 Test that if a corrupt wal-index header is encountered when attempting to commit a concurrent transaction, SQLITE_BUSY_SNAPSHOT is returned to the caller.
FossilOrigin-Name: c746e0bd20cb136eed2b691f326657d266e2f1ed
2015-08-25 16:01:04 +00:00
dan ac0a42233a Fix a segfault that could occur following an OOM condition in the concurrent transaction code.
FossilOrigin-Name: 231b5880228cf01efe3981bc8be3150d79b422e5
2015-08-25 14:37:39 +00:00
dan 3c40ed4281 Merge latest trunk changes with this branch.
FossilOrigin-Name: 3e7d6dd62dfa63d7def00bd00ac055a606a0c80d
2015-08-25 11:16:02 +00:00
mistachkin 0cd4f69c0c Remove duplicated line of code.
FossilOrigin-Name: 47280f2a2b7cc83bf11ab86284204f565c278c55
2015-08-24 22:06:02 +00:00
dan bf3cf57e15 Change "BEGIN UNLOCKED" to "BEGIN CONCURRENT".
FossilOrigin-Name: ba1ab858e2997c88dd7eee6e6893a8616d85c665
2015-08-24 19:56:04 +00:00
dan f5e89dba9d Fix handling of attempts to modify the database schema, application_id or user_version within an UNLOCKED transaction.
FossilOrigin-Name: 5b9f272113d21fd606903509d6f830fe60fac039
2015-08-24 19:08:10 +00:00
dan 0408529b48 Fix compilation without SQLITE_ENABLE_UNLOCKED. Also other code organization issues.
FossilOrigin-Name: 041135575417201bbcf0544cc69dcb7369c7fb34
2015-08-24 16:00:08 +00:00
dan 5cf03728a6 Consolidate two blocks of similar code in btreeFixUnlocked().
FossilOrigin-Name: 701302b4bd62ca7aefe643eac096a0ee672a62fa
2015-08-24 10:05:03 +00:00
dan 8b994f29a5 Merge trunk changes with this branch.
FossilOrigin-Name: 876810c28b3ad573ae46050ec699ef7eea4e313d
2015-08-24 06:44:17 +00:00
dan 654a965367 Fix another problem involving unlocked transactions and wal-file restarts.
FossilOrigin-Name: 4460764ea8fc948fe02f0a09476857839b3aa1ae
2015-08-24 06:43:25 +00:00
dan 0c52b373a0 Fix a problem to do with detecting unlocked transaction conflicts if another client restarts the wal while the transaction is running.
FossilOrigin-Name: e3968b256282d8c0a87c26667b1ba02faf7a5a17
2015-08-22 20:32:39 +00:00
dan f5cebf71fe Add further tests for deferred page allocation. And fixes for the same.
FossilOrigin-Name: ed0a31be726e60115a5dd73d4ed580201b400ab7
2015-08-22 17:28:55 +00:00
dan c299a11c02 Merge further trunk changes.
FossilOrigin-Name: c2327a3b8e5d604ab948b1e9f6cfc401429e51db
2015-08-22 07:56:49 +00:00
dan 8c5847a257 Merge trunk changes into this branch.
FossilOrigin-Name: 9021f7369f4b32db6126b3dc0ac75d11039625be
2015-08-22 07:55:08 +00:00
dan b87b25f219 Fix many minor issues in the unlocked transaction code.
FossilOrigin-Name: 53aaeea6c98f82f3b55d4b3ab139ee41b727243c
2015-08-21 20:11:23 +00:00
dan 572a21c8e9 When committing an UNLOCKED transaction, try to move pages allocated at the end of the file to free slots within the file (like an incremental-vacuum operation does).
FossilOrigin-Name: 069679162d8d50e9731831e658aa58f280dbb3e7
2015-08-21 18:55:22 +00:00
dan 70af25d03d Fix a problem with UNLOCKED transactions that free pages allocated within the same transaction.
FossilOrigin-Name: 227bb8a1815c4dc6084970f06b0a6bfccdff3fd2
2015-08-21 17:57:16 +00:00
dan cec0beebb3 Merge trunk changes with this branch.
FossilOrigin-Name: deaf3b18569025d8d168da29fae76142cfffe9e5
2015-08-21 16:22:04 +00:00
dan 64b310ed1a Add extra tests and a fix for rollbacks of UNLOCKED transactions.
FossilOrigin-Name: 82cd837e72ed4cf5821be717369694be29a2997e
2015-08-21 14:21:22 +00:00
dan 699bdf056b Fix a problem causing corruption when an UNLOCKED transaction is rolled back.
FossilOrigin-Name: 7c36147846484c96023939864417b5624f3bc5f8
2015-08-20 20:25:56 +00:00
dan 7b3d71e9cc When committing an unlocked transaction, relocate newly allocated database pages within the file to avoid conflicting with committed transactions. There are lots of things still to fix in this code.
FossilOrigin-Name: 3bbc31d515ba9fc920c5cbc7059d3eb1ba9c7f8e
2015-08-19 20:27:05 +00:00
dan 1a9cde3ba9 Handle writes to auto-vacuum databases within UNLOCKED transactions in the same way as for non-UNLOCKED transactions.
FossilOrigin-Name: de1ea450db33b140b11af5b801ea6a15875e774e
2015-08-15 18:16:46 +00:00
dan 773d2d6c09 Only allow UNLOCKED transactions to commit if none of the pages read by the transaction have been modified since it was opened.
FossilOrigin-Name: 0b9718426e44df092850c5d095ce1b84a1e567cf
2015-07-29 12:14:28 +00:00
dan 37d36205f3 Add some test cases and fix some small problems with BEGIN UNLOCKED transactions.
FossilOrigin-Name: 6da0e962ad2aa5e52c1f1b5c3dbf77a2cb16ac2d
2015-07-28 16:46:49 +00:00
dan 3d39434c36 Add an experimental "BEGIN UNLOCKED" command.
FossilOrigin-Name: 8079421604dbd40d03471dad6d12115119b554c2
2015-07-27 19:31:45 +00:00
60 changed files with 8081 additions and 1348 deletions
+107
View File
@@ -0,0 +1,107 @@
Begin Concurrent
================
## Overview
Usually, SQLite allows at most one writer to proceed concurrently. The
BEGIN CONCURRENT enhancement allows multiple writers to process write
transactions simultanously if the database is in "wal" or "wal2" mode,
although the system still serializes COMMIT commands.
When a write-transaction is opened with "BEGIN CONCURRENT", actually
locking the database is deferred until a COMMIT is executed. This means
that any number of transactions started with BEGIN CONCURRENT may proceed
concurrently. The system uses optimistic page-level-locking to prevent
conflicting concurrent transactions from being committed.
When a BEGIN CONCURRENT transaction is committed, the system checks whether
or not any of the database pages that the transaction has read have been
modified since the BEGIN CONCURRENT was opened. In other words - it asks
if the transaction being committed operates on a different set of data than
all other concurrently executing transactions. If the answer is "yes, this
transaction did not read or modify any data modified by any concurrent
transaction", then the transaction is committed as normal. Otherwise, if the
transaction does conflict, it cannot be committed and an SQLITE_BUSY_SNAPSHOT
error is returned. At this point, all the client can do is ROLLBACK the
transaction.
If SQLITE_BUSY_SNAPSHOT is returned, messages are output via the sqlite3_log
mechanism indicating the page and table or index on which the conflict
occurred. This can be useful when optimizing concurrency.
## Application Programming Notes
In order to serialize COMMIT processing, SQLite takes a lock on the database
as part of each COMMIT command and releases it before returning. At most one
writer may hold this lock at any one time. If a writer cannot obtain the lock,
it uses SQLite's busy-handler to pause and retry for a while:
<a href=https://www.sqlite.org/c3ref/busy_handler.html>
https://www.sqlite.org/c3ref/busy_handler.html
</a>
If there is significant contention for the writer lock, this mechanism can be
inefficient. In this case it is better for the application to use a mutex or
some other mechanism that supports blocking to ensure that at most one writer
is attempting to COMMIT a BEGIN CONCURRENT transaction at a time. This is
usually easier if all writers are part of the same operating system process.
If all database clients (readers and writers) are located in the same OS
process, and if that OS is a Unix variant, then it can be more efficient to
the built-in VFS "unix-excl" instead of the default "unix". This is because it
uses more efficient locking primitives.
The key to maximizing concurrency using BEGIN CONCURRENT is to ensure that
there are a large number of non-conflicting transactions. In SQLite, each
table and each index is stored as a separate b-tree, each of which is
distributed over a discrete set of database pages. This means that:
* Two transactions that write to different sets of tables never
conflict, and that
* Two transactions that write to the same tables or indexes only
conflict if the values of the keys (either primary keys or indexed
rows) are fairly close together. For example, given a large
table with the schema:
<pre> CREATE TABLE t1(a INTEGER PRIMARY KEY, b BLOB);</pre>
writing two rows with adjacent values for "a" probably will cause a
conflict (as the two keys are stored on the same page), but writing two
rows with vastly different values for "a" will not (as the keys will likly
be stored on different pages).
Note that, in SQLite, if values are not explicitly supplied for an INTEGER
PRIMARY KEY, as for example in:
>
INSERT INTO t1(b) VALUES(&lt;blob-value>);
then monotonically increasing values are assigned automatically. This is
terrible for concurrency, as it all but ensures that all new rows are
added to the same database page. In such situations, it is better to
explicitly assign random values to INTEGER PRIMARY KEY fields.
This problem also comes up for non-WITHOUT ROWID tables that do not have an
explicit INTEGER PRIMARY KEY column. In these cases each table has an implicit
INTEGER PRIMARY KEY column that is assigned increasing values, leading to the
same problem as omitting to assign a value to an explicit INTEGER PRIMARY KEY
column.
For both explicit and implicit INTEGER PRIMARY KEYs, it is possible to have
SQLite assign values at random (instead of the monotonically increasing
values) by writing a row with a rowid equal to the largest possible signed
64-bit integer to the table. For example:
INSERT INTO t1(a) VALUES(9223372036854775807);
Applications should take care not to malfunction due to the presence of such
rows.
The nature of some types of indexes, for example indexes on timestamp fields,
can also cause problems (as concurrent transactions may assign similar
timestamps that will be stored on the same db page to new records). In these
cases the database schema may need to be rethought to increase the concurrency
provided by page-level-locking.
+117
View File
@@ -0,0 +1,117 @@
Begin Concurrent Report
=======================
## Overview
<a href=../../../timeline?r=begin-concurrent-report>
This patch</a> enhances the library so that it may provide a report on each
transaction that uses "BEGIN CONCURRENT". The report includes the following
details:
* Whether or not the transaction was successfully committed.
* A list of database pages modified (if the transaction was successfully
committed).
* A list of database pages read from the db by the transaction..
* A copy of each tuple written, overwritten or deleted from the database
by the transaction.
* A list of ranges of index keys scanned by the transaction.
Only operations on the main database are reported on. Reads and writes of
attached or temp databases are not included in reports.
The second and third points above describe the data that the
optimistic-concurrency page-locking system currently uses to determine at
COMMIT time if a BEGIN CONCURRENT transaction conflicts with any other
transaction that executed concurrently. The fourth and fifth points represent
the data that we could use to identify conflicts in a row-locking system.
Using this patch we can gather a series of reports from a real installation,
then by analyzing those reports offline determine whether or not concurrency
would have been increased if the system supported row-locking instead of page
locking.
## Programming
Capturing the data required for the reports is disabled by default. It is
enabled or disabled on a per-connection basis. To enable capturing of
report data, call the sqlite3\_begin\_concurrent\_report() function with the
bEnable argument set to non-zero. To disable it, call the same function with
bEnable set to zero. If capturing report data is enabled or disabled in the
middle of a transaction, the report for that transaction may be incomplete.
<pre>
void sqlite3_begin_concurrent_report_enable(sqlite3 *db, int bEnable);
</pre>
The following API returns a pointer to the full text of the report for the
most recently completed BEGIN CONCURRENT transaction. The API may be called
at any point after the BEGIN CONCURRENT transaction is committed or rolled
back until the next invocation of the command "BEGIN CONCURRENT". The returned
pointer is valid until either the database handle is closed or the
sqlite3\_begin\_concurrent\_report() is called again on the same database
handle.
<pre>
const char *sqlite3_begin_concurrent_report(sqlite3 *db);
</pre>
## Report Format
The report is broken into lines separated by "\\n" characters.
If a transaction is successfully committed, the report contains entries
for the set of pages read by the transaction and the set of pages written.
For example:
<pre>
R:{3 4 8 10}
W:{4 8}
</pre>
indicates that the transaction read pages 3, 4, 8 and 10, and wrote to pages
4 and 8.
If a transaction cannot be committed due to conflicts, then its report
contains only a list of database pages written. As follows:
<pre>
F:{3 4 8 10}
</pre>
For each row written, overwritten or deleted from the database, the report
contains an entry similar to the following:
<pre>
3<-(44){NULL,2,'three'}
</pre>
which means that a record with rowid 44 was written (or overwritten or deleted)
to the table with root page 3. The values in the tuple are NULL, 2 and 'three'.
The rowid value is always 0 for WITHOUT ROWID tables.
Ranges scanned by the transaction are represented by lines similar to the
following:
<pre>
4:[{'one'}..{'three'})
4:({'three'}..EOF)
3:[44..44]
</pre>
Assuming that page 3 is the root page of table t1, and page 4 is the root page
of an index on column "c", the lines above might be produced by SQL similar to:
<pre>
SELECT * FROM t1 WHERE c>='one' AND c<'three';
SELECT * FROM t1 WHERE c>'three';
SELECT * FROM t1 WHERE rowid=44;
</pre>
+222
View File
@@ -0,0 +1,222 @@
# 2016 August 23
#
# 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.
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source $testdir/tester.tcl
ifcapable !session {finish_test; return}
set testprefix changebatch1
proc sql_to_changeset {method sql} {
sqlite3session S db main
S attach *
execsql $sql
set ret [S $method]
S delete
return $ret
}
proc do_changebatch_test {tn method args} {
set C [list]
foreach a $args {
lappend C [sql_to_changeset $method $a]
}
sqlite3changebatch cb db
set i 1
foreach ::cs [lrange $C 0 end-1] {
set rc [cb add $::cs]
if {$rc!="SQLITE_OK"} { error "expected SQLITE_OK, got $rc (i=$i)" }
incr i
}
set ::cs [lindex $C end]
do_test $tn { cb add [set ::cs] } SQLITE_CONSTRAINT
cb delete
}
proc do_changebatch_test1 {tn args} {
uplevel do_changebatch_test $tn changeset $args
}
proc do_changebatch_test2 {tn args} {
uplevel do_changebatch_test $tn fullchangeset $args
}
#-------------------------------------------------------------------------
# The body of the following loop contains tests for database schemas
# that do not feature multi-column UNIQUE constraints. In this case
# it doesn't matter if the changesets are generated using
# sqlite3session_changeset() or sqlite3session_fullchangeset().
#
foreach {tn testfunction} {
1 do_changebatch_test1
2 do_changebatch_test2
} {
reset_db
#-------------------------------------------------------------------------
#
do_execsql_test $tn.1.0 {
CREATE TABLE t1(a PRIMARY KEY, b);
}
$testfunction $tn.1.1 {
INSERT INTO t1 VALUES(1, 1);
} {
DELETE FROM t1 WHERE a=1;
}
do_execsql_test $tn.1.2.0 {
INSERT INTO t1 VALUES(1, 1);
INSERT INTO t1 VALUES(2, 2);
INSERT INTO t1 VALUES(3, 3);
}
$testfunction $tn.1.2.1 {
DELETE FROM t1 WHERE a=2;
} {
INSERT INTO t1 VALUES(2, 2);
}
#-------------------------------------------------------------------------
#
do_execsql_test $tn.2.0 {
CREATE TABLE x1(a, b PRIMARY KEY, c UNIQUE);
CREATE TABLE x2(a PRIMARY KEY, b UNIQUE, c UNIQUE);
CREATE INDEX x1a ON x1(a);
INSERT INTO x1 VALUES(1, 1, 'a');
INSERT INTO x1 VALUES(1, 2, 'b');
INSERT INTO x1 VALUES(1, 3, 'c');
}
$testfunction $tn.2.1 {
DELETE FROM x1 WHERE b=2;
} {
UPDATE x1 SET c='b' WHERE b=3;
}
$testfunction $tn.2.2 {
DELETE FROM x1 WHERE b=1;
} {
INSERT INTO x1 VALUES(1, 5, 'a');
}
set L [list]
for {set i 1000} {$i < 10000} {incr i} {
lappend L "INSERT INTO x2 VALUES($i, $i, 'x' || $i)"
}
lappend L "DELETE FROM x2 WHERE b=1005"
$testfunction $tn.2.3 {*}$L
execsql { INSERT INTO x1 VALUES('f', 'f', 'f') }
$testfunction $tn.2.4 {
INSERT INTO x2 VALUES('f', 'f', 'f');
} {
INSERT INTO x1 VALUES('g', 'g', 'g');
} {
DELETE FROM x1 WHERE b='f';
} {
INSERT INTO x2 VALUES('g', 'g', 'g');
} {
INSERT INTO x1 VALUES('f', 'f', 'f');
}
execsql {
DELETE FROM x1;
INSERT INTO x1 VALUES(1.5, 1.5, 1.5);
}
$testfunction $tn.2.5 {
DELETE FROM x1 WHERE b BETWEEN 1 AND 2;
} {
INSERT INTO x1 VALUES(2.5, 2.5, 2.5);
} {
INSERT INTO x1 VALUES(1.5, 1.5, 1.5);
}
execsql {
DELETE FROM x2;
INSERT INTO x2 VALUES(X'abcd', X'1234', X'7890');
INSERT INTO x2 VALUES(X'0000', X'0000', X'0000');
}
breakpoint
$testfunction $tn.2.6 {
UPDATE x2 SET c = X'1234' WHERE a=X'abcd';
INSERT INTO x2 VALUES(X'1234', X'abcd', X'7890');
} {
DELETE FROM x2 WHERE b=X'0000';
} {
INSERT INTO x2 VALUES(1, X'0000', NULL);
}
}
#-------------------------------------------------------------------------
# Test some multi-column UNIQUE constraints. First Using _changeset() to
# demonstrate the problem, then using _fullchangeset() to show that it has
# been fixed.
#
reset_db
do_execsql_test 3.0 {
CREATE TABLE y1(a PRIMARY KEY, b, c, UNIQUE(b, c));
INSERT INTO y1 VALUES(1, 1, 1);
INSERT INTO y1 VALUES(2, 2, 2);
INSERT INTO y1 VALUES(3, 3, 3);
INSERT INTO y1 VALUES(4, 3, 4);
BEGIN;
}
do_test 3.1.1 {
set c1 [sql_to_changeset changeset { DELETE FROM y1 WHERE a=4 }]
set c2 [sql_to_changeset changeset { UPDATE y1 SET c=4 WHERE a=3 }]
sqlite3changebatch cb db
cb add $c1
cb add $c2
} {SQLITE_OK}
do_test 3.1.2 {
cb delete
execsql ROLLBACK
} {}
do_test 3.1.1 {
set c1 [sql_to_changeset fullchangeset { DELETE FROM y1 WHERE a=4 }]
set c2 [sql_to_changeset fullchangeset { UPDATE y1 SET c=4 WHERE a=3 }]
sqlite3changebatch cb db
cb add $c1
cb add $c2
} {SQLITE_CONSTRAINT}
do_test 3.1.2 {
cb delete
} {}
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 4.0 {
CREATE TABLE t1(x, y, z, PRIMARY KEY(x, y), UNIQUE(z));
}
do_test 4.1 {
set c1 [sql_to_changeset fullchangeset { INSERT INTO t1 VALUES(1, 2, 3) }]
execsql {
DROP TABLE t1;
CREATE TABLE t1(w, x, y, z, PRIMARY KEY(x, y), UNIQUE(z));
}
sqlite3changebatch cb db
list [catch { cb add $c1 } msg] $msg
} {1 SQLITE_RANGE}
finish_test
+42
View File
@@ -0,0 +1,42 @@
# 2011 Mar 21
#
# 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 focus of this file is testing the session module.
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source [file join [file dirname [info script]] session_common.tcl]
source $testdir/tester.tcl
ifcapable !session {finish_test; return}
set testprefix changebatchfault
do_execsql_test 1.0 {
CREATE TABLE t1(a, b, c PRIMARY KEY, UNIQUE(a, b));
INSERT INTO t1 VALUES('a', 'a', 'a');
INSERT INTO t1 VALUES('b', 'b', 'b');
}
set ::c1 [changeset_from_sql { delete from t1 where c='a' }]
set ::c2 [changeset_from_sql { insert into t1 values('c', 'c', 'c') }]
do_faultsim_test 1 -faults oom-* -body {
sqlite3changebatch cb db
cb add $::c1
cb add $::c2
} -test {
faultsim_test_result {0 SQLITE_OK} {1 SQLITE_NOMEM}
catch { cb delete }
}
finish_test
+485
View File
@@ -0,0 +1,485 @@
#if !defined(SQLITE_TEST) || (defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK))
#include "sqlite3session.h"
#include "sqlite3changebatch.h"
#include <assert.h>
#include <string.h>
typedef struct BatchTable BatchTable;
typedef struct BatchIndex BatchIndex;
typedef struct BatchIndexEntry BatchIndexEntry;
typedef struct BatchHash BatchHash;
struct sqlite3_changebatch {
sqlite3 *db; /* Database handle used to read schema */
BatchTable *pTab; /* First in linked list of tables */
int iChangesetId; /* Current changeset id */
int iNextIdxId; /* Next available index id */
int nEntry; /* Number of entries in hash table */
int nHash; /* Number of hash buckets */
BatchIndexEntry **apHash; /* Array of hash buckets */
};
struct BatchTable {
BatchIndex *pIdx; /* First in linked list of UNIQUE indexes */
BatchTable *pNext; /* Next table */
char zTab[1]; /* Table name */
};
struct BatchIndex {
BatchIndex *pNext; /* Next index on same table */
int iId; /* Index id (assigned internally) */
int bPk; /* True for PK index */
int nCol; /* Size of aiCol[] array */
int *aiCol; /* Array of columns that make up index */
};
struct BatchIndexEntry {
BatchIndexEntry *pNext; /* Next colliding hash table entry */
int iChangesetId; /* Id of associated changeset */
int iIdxId; /* Id of index this key is from */
int szRecord;
char aRecord[1];
};
/*
** Allocate and zero a block of nByte bytes. Must be freed using cbFree().
*/
static void *cbMalloc(int *pRc, int nByte){
void *pRet;
if( *pRc ){
pRet = 0;
}else{
pRet = sqlite3_malloc(nByte);
if( pRet ){
memset(pRet, 0, nByte);
}else{
*pRc = SQLITE_NOMEM;
}
}
return pRet;
}
/*
** Free an allocation made by cbMalloc().
*/
static void cbFree(void *p){
sqlite3_free(p);
}
/*
** Return the hash bucket that pEntry belongs in.
*/
static int cbHash(sqlite3_changebatch *p, BatchIndexEntry *pEntry){
unsigned int iHash = (unsigned int)pEntry->iIdxId;
unsigned char *pEnd = (unsigned char*)&pEntry->aRecord[pEntry->szRecord];
unsigned char *pIter;
for(pIter=(unsigned char*)pEntry->aRecord; pIter<pEnd; pIter++){
iHash += (iHash << 7) + *pIter;
}
return (int)(iHash % p->nHash);
}
/*
** Resize the hash table.
*/
static int cbHashResize(sqlite3_changebatch *p){
int rc = SQLITE_OK;
BatchIndexEntry **apNew;
int nNew = (p->nHash ? p->nHash*2 : 512);
int i;
apNew = cbMalloc(&rc, sizeof(BatchIndexEntry*) * nNew);
if( rc==SQLITE_OK ){
int nHash = p->nHash;
p->nHash = nNew;
for(i=0; i<nHash; i++){
BatchIndexEntry *pEntry;
while( (pEntry=p->apHash[i])!=0 ){
int iHash = cbHash(p, pEntry);
p->apHash[i] = pEntry->pNext;
pEntry->pNext = apNew[iHash];
apNew[iHash] = pEntry;
}
}
cbFree(p->apHash);
p->apHash = apNew;
}
return rc;
}
/*
** Allocate a new sqlite3_changebatch object.
*/
int sqlite3changebatch_new(sqlite3 *db, sqlite3_changebatch **pp){
sqlite3_changebatch *pRet;
int rc = SQLITE_OK;
*pp = pRet = (sqlite3_changebatch*)cbMalloc(&rc, sizeof(sqlite3_changebatch));
if( pRet ){
pRet->db = db;
}
return rc;
}
/*
** Add a BatchIndex entry for index zIdx to table pTab.
*/
static int cbAddIndex(
sqlite3_changebatch *p,
BatchTable *pTab,
const char *zIdx,
int bPk
){
int nCol = 0;
sqlite3_stmt *pIndexInfo = 0;
BatchIndex *pNew = 0;
int rc;
char *zIndexInfo;
zIndexInfo = (char*)sqlite3_mprintf("PRAGMA main.index_info = %Q", zIdx);
if( zIndexInfo ){
rc = sqlite3_prepare_v2(p->db, zIndexInfo, -1, &pIndexInfo, 0);
sqlite3_free(zIndexInfo);
}else{
rc = SQLITE_NOMEM;
}
if( rc==SQLITE_OK ){
while( SQLITE_ROW==sqlite3_step(pIndexInfo) ){ nCol++; }
rc = sqlite3_reset(pIndexInfo);
}
pNew = (BatchIndex*)cbMalloc(&rc, sizeof(BatchIndex) + sizeof(int) * nCol);
if( rc==SQLITE_OK ){
pNew->nCol = nCol;
pNew->bPk = bPk;
pNew->aiCol = (int*)&pNew[1];
pNew->iId = p->iNextIdxId++;
while( SQLITE_ROW==sqlite3_step(pIndexInfo) ){
int i = sqlite3_column_int(pIndexInfo, 0);
int j = sqlite3_column_int(pIndexInfo, 1);
pNew->aiCol[i] = j;
}
rc = sqlite3_reset(pIndexInfo);
}
if( rc==SQLITE_OK ){
pNew->pNext = pTab->pIdx;
pTab->pIdx = pNew;
}else{
cbFree(pNew);
}
sqlite3_finalize(pIndexInfo);
return rc;
}
/*
** Free the object passed as the first argument.
*/
static void cbFreeTable(BatchTable *pTab){
BatchIndex *pIdx;
BatchIndex *pIdxNext;
for(pIdx=pTab->pIdx; pIdx; pIdx=pIdxNext){
pIdxNext = pIdx->pNext;
cbFree(pIdx);
}
cbFree(pTab);
}
/*
** Find or create the BatchTable object named zTab.
*/
static int cbFindTable(
sqlite3_changebatch *p,
const char *zTab,
BatchTable **ppTab
){
BatchTable *pRet = 0;
int rc = SQLITE_OK;
for(pRet=p->pTab; pRet; pRet=pRet->pNext){
if( 0==sqlite3_stricmp(zTab, pRet->zTab) ) break;
}
if( pRet==0 ){
int nTab = strlen(zTab);
pRet = (BatchTable*)cbMalloc(&rc, nTab + sizeof(BatchTable));
if( pRet ){
sqlite3_stmt *pIndexList = 0;
char *zIndexList = 0;
int rc2;
memcpy(pRet->zTab, zTab, nTab);
zIndexList = sqlite3_mprintf("PRAGMA main.index_list = %Q", zTab);
if( zIndexList==0 ){
rc = SQLITE_NOMEM;
}else{
rc = sqlite3_prepare_v2(p->db, zIndexList, -1, &pIndexList, 0);
sqlite3_free(zIndexList);
}
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pIndexList) ){
if( sqlite3_column_int(pIndexList, 2) ){
const char *zIdx = (const char*)sqlite3_column_text(pIndexList, 1);
const char *zTyp = (const char*)sqlite3_column_text(pIndexList, 3);
rc = cbAddIndex(p, pRet, zIdx, (zTyp[0]=='p'));
}
}
rc2 = sqlite3_finalize(pIndexList);
if( rc==SQLITE_OK ) rc = rc2;
if( rc==SQLITE_OK ){
pRet->pNext = p->pTab;
p->pTab = pRet;
}else{
cbFreeTable(pRet);
pRet = 0;
}
}
}
*ppTab = pRet;
return rc;
}
/*
** Extract value iVal from the changeset iterator passed as the first
** argument. Set *ppVal to point to the value before returning.
**
** This function attempts to extract the value using function xVal
** (which is always either sqlite3changeset_new or sqlite3changeset_old).
** If the call returns SQLITE_OK but does not supply an sqlite3_value*
** pointer, an attempt to extract the value is made using the xFallback
** function.
*/
static int cbGetChangesetValue(
sqlite3_changeset_iter *pIter,
int (*xVal)(sqlite3_changeset_iter*,int,sqlite3_value**),
int (*xFallback)(sqlite3_changeset_iter*,int,sqlite3_value**),
int iVal,
sqlite3_value **ppVal
){
int rc = xVal(pIter, iVal, ppVal);
if( rc==SQLITE_OK && *ppVal==0 && xFallback ){
rc = xFallback(pIter, iVal, ppVal);
}
return rc;
}
static int cbAddToHash(
sqlite3_changebatch *p,
sqlite3_changeset_iter *pIter,
BatchIndex *pIdx,
int (*xVal)(sqlite3_changeset_iter*,int,sqlite3_value**),
int (*xFallback)(sqlite3_changeset_iter*,int,sqlite3_value**),
int *pbConf
){
BatchIndexEntry *pNew;
int sz = pIdx->nCol;
int i;
int iOut = 0;
int rc = SQLITE_OK;
for(i=0; rc==SQLITE_OK && i<pIdx->nCol; i++){
sqlite3_value *pVal;
rc = cbGetChangesetValue(pIter, xVal, xFallback, pIdx->aiCol[i], &pVal);
if( rc==SQLITE_OK ){
int eType = 0;
if( pVal ) eType = sqlite3_value_type(pVal);
switch( eType ){
case 0:
case SQLITE_NULL:
return SQLITE_OK;
case SQLITE_INTEGER:
sz += 8;
break;
case SQLITE_FLOAT:
sz += 8;
break;
default:
assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
sz += sqlite3_value_bytes(pVal);
break;
}
}
}
pNew = cbMalloc(&rc, sizeof(BatchIndexEntry) + sz);
if( pNew ){
pNew->iChangesetId = p->iChangesetId;
pNew->iIdxId = pIdx->iId;
pNew->szRecord = sz;
for(i=0; i<pIdx->nCol; i++){
int eType;
sqlite3_value *pVal;
rc = cbGetChangesetValue(pIter, xVal, xFallback, pIdx->aiCol[i], &pVal);
if( rc!=SQLITE_OK ) break; /* coverage: condition is never true */
eType = sqlite3_value_type(pVal);
pNew->aRecord[iOut++] = eType;
switch( eType ){
case SQLITE_INTEGER: {
sqlite3_int64 i64 = sqlite3_value_int64(pVal);
memcpy(&pNew->aRecord[iOut], &i64, 8);
iOut += 8;
break;
}
case SQLITE_FLOAT: {
double d64 = sqlite3_value_double(pVal);
memcpy(&pNew->aRecord[iOut], &d64, sizeof(double));
iOut += sizeof(double);
break;
}
default: {
int nByte = sqlite3_value_bytes(pVal);
const char *z = (const char*)sqlite3_value_blob(pVal);
memcpy(&pNew->aRecord[iOut], z, nByte);
iOut += nByte;
break;
}
}
}
}
if( rc==SQLITE_OK && p->nEntry>=(p->nHash/2) ){
rc = cbHashResize(p);
}
if( rc==SQLITE_OK ){
BatchIndexEntry *pIter;
int iHash = cbHash(p, pNew);
assert( iHash>=0 && iHash<p->nHash );
for(pIter=p->apHash[iHash]; pIter; pIter=pIter->pNext){
if( pNew->szRecord==pIter->szRecord
&& 0==memcmp(pNew->aRecord, pIter->aRecord, pNew->szRecord)
){
if( pNew->iChangesetId!=pIter->iChangesetId ){
*pbConf = 1;
}
cbFree(pNew);
pNew = 0;
break;
}
}
if( pNew ){
pNew->pNext = p->apHash[iHash];
p->apHash[iHash] = pNew;
p->nEntry++;
}
}else{
cbFree(pNew);
}
return rc;
}
/*
** Add a changeset to the current batch.
*/
int sqlite3changebatch_add(sqlite3_changebatch *p, void *pBuf, int nBuf){
sqlite3_changeset_iter *pIter; /* Iterator opened on pBuf/nBuf */
int rc; /* Return code */
int bConf = 0; /* Conflict was detected */
rc = sqlite3changeset_start(&pIter, nBuf, pBuf);
if( rc==SQLITE_OK ){
int rc2;
for(rc2 = sqlite3changeset_next(pIter);
rc2==SQLITE_ROW;
rc2 = sqlite3changeset_next(pIter)
){
BatchTable *pTab;
BatchIndex *pIdx;
const char *zTab; /* Table this change applies to */
int nCol; /* Number of columns in table */
int op; /* UPDATE, INSERT or DELETE */
sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
rc = cbFindTable(p, zTab, &pTab);
assert( pTab || rc!=SQLITE_OK );
if( pTab ){
for(pIdx=pTab->pIdx; pIdx && rc==SQLITE_OK; pIdx=pIdx->pNext){
if( op==SQLITE_UPDATE && pIdx->bPk ) continue;
if( op==SQLITE_UPDATE || op==SQLITE_DELETE ){
rc = cbAddToHash(p, pIter, pIdx, sqlite3changeset_old, 0, &bConf);
}
if( op==SQLITE_UPDATE || op==SQLITE_INSERT ){
rc = cbAddToHash(p, pIter, pIdx,
sqlite3changeset_new, sqlite3changeset_old, &bConf
);
}
}
}
if( rc!=SQLITE_OK ) break;
}
rc2 = sqlite3changeset_finalize(pIter);
if( rc==SQLITE_OK ) rc = rc2;
}
if( rc==SQLITE_OK && bConf ){
rc = SQLITE_CONSTRAINT;
}
p->iChangesetId++;
return rc;
}
/*
** Zero an existing changebatch object.
*/
void sqlite3changebatch_zero(sqlite3_changebatch *p){
int i;
for(i=0; i<p->nHash; i++){
BatchIndexEntry *pEntry;
BatchIndexEntry *pNext;
for(pEntry=p->apHash[i]; pEntry; pEntry=pNext){
pNext = pEntry->pNext;
cbFree(pEntry);
}
}
cbFree(p->apHash);
p->nHash = 0;
p->apHash = 0;
}
/*
** Delete a changebatch object.
*/
void sqlite3changebatch_delete(sqlite3_changebatch *p){
BatchTable *pTab;
BatchTable *pTabNext;
sqlite3changebatch_zero(p);
for(pTab=p->pTab; pTab; pTab=pTabNext){
pTabNext = pTab->pNext;
cbFreeTable(pTab);
}
cbFree(p);
}
/*
** Return the db handle.
*/
sqlite3 *sqlite3changebatch_db(sqlite3_changebatch *p){
return p->db;
}
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
+82
View File
@@ -0,0 +1,82 @@
#if !defined(SQLITECHANGEBATCH_H_)
#define SQLITECHANGEBATCH_H_ 1
typedef struct sqlite3_changebatch sqlite3_changebatch;
/*
** Create a new changebatch object for detecting conflicts between
** changesets associated with a schema equivalent to that of the "main"
** database of the open database handle db passed as the first
** parameter. It is the responsibility of the caller to ensure that
** the database handle is not closed until after the changebatch
** object has been deleted.
**
** A changebatch object is used to detect batches of non-conflicting
** changesets. Changesets that do not conflict may be applied to the
** target database in any order without affecting the final state of
** the database.
**
** The changebatch object only works reliably if PRIMARY KEY and UNIQUE
** constraints on tables affected by the changesets use collation
** sequences that are equivalent to built-in collation sequence
** BINARY for the == operation.
**
** If successful, SQLITE_OK is returned and (*pp) set to point to
** the new changebatch object. If an error occurs, an SQLite error
** code is returned and the final value of (*pp) is undefined.
*/
int sqlite3changebatch_new(sqlite3 *db, sqlite3_changebatch **pp);
/*
** Argument p points to a buffer containing a changeset n bytes in
** size. Assuming no error occurs, this function returns SQLITE_OK
** if the changeset does not conflict with any changeset passed
** to an sqlite3changebatch_add() call made on the same
** sqlite3_changebatch* handle since the most recent call to
** sqlite3changebatch_zero(). If the changeset does conflict with
** an earlier such changeset, SQLITE_CONSTRAINT is returned. Or,
** if an error occurs, some other SQLite error code may be returned.
**
** One changeset is said to conflict with another if
** either:
**
** * the two changesets contain operations (INSERT, UPDATE or
** DELETE) on the same row, identified by primary key, or
**
** * the two changesets contain operations (INSERT, UPDATE or
** DELETE) on rows with identical values in any combination
** of fields constrained by a UNIQUE constraint.
**
** Even if this function returns SQLITE_CONFLICT, the current
** changeset is added to the internal data structures - so future
** calls to this function may conflict with it. If this function
** returns any result code other than SQLITE_OK or SQLITE_CONFLICT,
** the result of any future call to sqlite3changebatch_add() is
** undefined.
**
** Only changesets may be passed to this function. Passing a
** patchset to this function results in an SQLITE_MISUSE error.
*/
int sqlite3changebatch_add(sqlite3_changebatch*, void *p, int n);
/*
** Zero a changebatch object. This causes the records of all earlier
** calls to sqlite3changebatch_add() to be discarded.
*/
void sqlite3changebatch_zero(sqlite3_changebatch*);
/*
** Return a copy of the first argument passed to the sqlite3changebatch_new()
** call used to create the changebatch object passed as the only argument
** to this function.
*/
sqlite3 *sqlite3changebatch_db(sqlite3_changebatch*);
/*
** Delete a changebatch object.
*/
void sqlite3changebatch_delete(sqlite3_changebatch*);
#endif /* !defined(SQLITECHANGEBATCH_H_) */
+39 -17
View File
@@ -25,6 +25,13 @@ typedef struct SessionInput SessionInput;
# endif
#endif
/*
** The three different types of changesets generated.
*/
#define SESSIONS_PATCHSET 0
#define SESSIONS_CHANGESET 1
#define SESSIONS_FULLCHANGESET 2
static int sessions_strm_chunk_size = SESSIONS_STRM_CHUNK_SIZE;
typedef struct SessionHook SessionHook;
@@ -2054,7 +2061,7 @@ static void sessionAppendCol(
*/
static int sessionAppendUpdate(
SessionBuffer *pBuf, /* Buffer to append to */
int bPatchset, /* True for "patchset", 0 for "changeset" */
int ePatchset, /* True for "patchset", 0 for "changeset" */
sqlite3_stmt *pStmt, /* Statement handle pointing at new row */
SessionChange *p, /* Object containing old values */
u8 *abPK /* Boolean array - true for PK columns */
@@ -2117,8 +2124,8 @@ static int sessionAppendUpdate(
/* Add a field to the old.* record. This is omitted if this modules is
** currently generating a patchset. */
if( bPatchset==0 ){
if( bChanged || abPK[i] ){
if( ePatchset!=SESSIONS_PATCHSET ){
if( ePatchset==SESSIONS_FULLCHANGESET || bChanged || abPK[i] ){
sessionAppendBlob(pBuf, pCsr, nAdvance, &rc);
}else{
sessionAppendByte(pBuf, 0, &rc);
@@ -2127,7 +2134,7 @@ static int sessionAppendUpdate(
/* Add a field to the new.* record. Or the only record if currently
** generating a patchset. */
if( bChanged || (bPatchset && abPK[i]) ){
if( bChanged || (ePatchset==SESSIONS_PATCHSET && abPK[i]) ){
sessionAppendCol(&buf2, pStmt, i, &rc);
}else{
sessionAppendByte(&buf2, 0, &rc);
@@ -2153,7 +2160,7 @@ static int sessionAppendUpdate(
*/
static int sessionAppendDelete(
SessionBuffer *pBuf, /* Buffer to append to */
int bPatchset, /* True for "patchset", 0 for "changeset" */
int eChangeset, /* One of SESSIONS_CHANGESET etc. */
SessionChange *p, /* Object containing old values */
int nCol, /* Number of columns in table */
u8 *abPK /* Boolean array - true for PK columns */
@@ -2163,7 +2170,7 @@ static int sessionAppendDelete(
sessionAppendByte(pBuf, SQLITE_DELETE, &rc);
sessionAppendByte(pBuf, p->bIndirect, &rc);
if( bPatchset==0 ){
if( eChangeset!=SESSIONS_PATCHSET ){
sessionAppendBlob(pBuf, p->aRecord, p->nRecord, &rc);
}else{
int i;
@@ -2336,12 +2343,12 @@ static int sessionSelectBind(
*/
static void sessionAppendTableHdr(
SessionBuffer *pBuf, /* Append header to this buffer */
int bPatchset, /* Use the patchset format if true */
int ePatchset, /* Use the patchset format if true */
SessionTable *pTab, /* Table object to append header for */
int *pRc /* IN/OUT: Error code */
){
/* Write a table header */
sessionAppendByte(pBuf, (bPatchset ? 'P' : 'T'), pRc);
sessionAppendByte(pBuf, (ePatchset==SESSIONS_PATCHSET) ? 'P' : 'T', pRc);
sessionAppendVarint(pBuf, pTab->nCol, pRc);
sessionAppendBlob(pBuf, pTab->abPK, pTab->nCol, pRc);
sessionAppendBlob(pBuf, (u8 *)pTab->zName, (int)strlen(pTab->zName)+1, pRc);
@@ -2359,7 +2366,7 @@ static void sessionAppendTableHdr(
*/
static int sessionGenerateChangeset(
sqlite3_session *pSession, /* Session object */
int bPatchset, /* True for patchset, false for changeset */
int ePatchset, /* One of SESSIONS_CHANGESET etc. */
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut, /* First argument for xOutput */
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
@@ -2404,7 +2411,7 @@ static int sessionGenerateChangeset(
}
/* Write a table header */
sessionAppendTableHdr(&buf, bPatchset, pTab, &rc);
sessionAppendTableHdr(&buf, ePatchset, pTab, &rc);
/* Build and compile a statement to execute: */
if( rc==SQLITE_OK ){
@@ -2428,10 +2435,10 @@ static int sessionGenerateChangeset(
sessionAppendCol(&buf, pSel, iCol, &rc);
}
}else{
rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK);
rc = sessionAppendUpdate(&buf, ePatchset, pSel, p, abPK);
}
}else if( p->op!=SQLITE_INSERT ){
rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK);
rc = sessionAppendDelete(&buf, ePatchset, p, nCol, abPK);
}
if( rc==SQLITE_OK ){
rc = sqlite3_reset(pSel);
@@ -2488,7 +2495,8 @@ int sqlite3session_changeset(
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
void **ppChangeset /* OUT: Buffer containing changeset */
){
return sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset);
return sessionGenerateChangeset(
pSession, SESSIONS_CHANGESET, 0, 0, pnChangeset, ppChangeset);
}
/*
@@ -2499,7 +2507,8 @@ int sqlite3session_changeset_strm(
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
){
return sessionGenerateChangeset(pSession, 0, xOutput, pOut, 0, 0);
return sessionGenerateChangeset(
pSession, SESSIONS_CHANGESET, xOutput, pOut, 0, 0);
}
/*
@@ -2510,7 +2519,8 @@ int sqlite3session_patchset_strm(
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
){
return sessionGenerateChangeset(pSession, 1, xOutput, pOut, 0, 0);
return sessionGenerateChangeset(
pSession, SESSIONS_PATCHSET, xOutput, pOut, 0, 0);
}
/*
@@ -2525,9 +2535,20 @@ int sqlite3session_patchset(
int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */
void **ppPatchset /* OUT: Buffer containing changeset */
){
return sessionGenerateChangeset(pSession, 1, 0, 0, pnPatchset, ppPatchset);
return sessionGenerateChangeset(
pSession, SESSIONS_PATCHSET, 0, 0, pnPatchset, ppPatchset);
}
int sqlite3session_fullchangeset(
sqlite3_session *pSession, /* Session object */
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
void **ppChangeset /* OUT: Buffer containing changeset */
){
return sessionGenerateChangeset(
pSession, SESSIONS_FULLCHANGESET, 0, 0, pnChangeset, ppChangeset);
}
/*
** Enable or disable the session object passed as the first argument.
*/
@@ -4933,10 +4954,11 @@ static int sessionChangegroupOutput(
** hash tables attached to the SessionTable objects in list p->pList.
*/
for(pTab=pGrp->pList; rc==SQLITE_OK && pTab; pTab=pTab->pNext){
int eChangeset = pGrp->bPatch ? SESSIONS_PATCHSET : SESSIONS_CHANGESET;
int i;
if( pTab->nEntry==0 ) continue;
sessionAppendTableHdr(&buf, pGrp->bPatch, pTab, &rc);
sessionAppendTableHdr(&buf, eChangeset, pTab, &rc);
for(i=0; i<pTab->nChange; i++){
SessionChange *p;
for(p=pTab->apChange[i]; p; p=p->pNext){
+13
View File
@@ -323,6 +323,19 @@ int sqlite3session_changeset(
void **ppChangeset /* OUT: Buffer containing changeset */
);
/*
** CAPI3REF: Generate A Full Changeset From A Session Object
**
** This function is similar to sqlite3session_changeset(), except that for
** each row affected by an UPDATE statement, all old.* values are recorded
** as part of the changeset, not just those modified.
*/
int sqlite3session_fullchangeset(
sqlite3_session *pSession, /* Session object */
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
void **ppChangeset /* OUT: Buffer containing changeset */
);
/*
** CAPI3REF: Load The Difference Between Tables Into A Session
** METHOD: sqlite3_session
+141 -11
View File
@@ -218,6 +218,7 @@ static int testStreamOutput(
** $session indirect INTEGER
** $session patchset
** $session table_filter SCRIPT
** $session fullchangeset
*/
static int SQLITE_TCLAPI test_session_cmd(
void *clientData,
@@ -231,17 +232,17 @@ static int SQLITE_TCLAPI test_session_cmd(
const char *zSub;
int nArg;
const char *zMsg;
int iSub;
} aSub[] = {
{ "attach", 1, "TABLE", }, /* 0 */
{ "changeset", 0, "", }, /* 1 */
{ "delete", 0, "", }, /* 2 */
{ "enable", 1, "BOOL", }, /* 3 */
{ "indirect", 1, "BOOL", }, /* 4 */
{ "isempty", 0, "", }, /* 5 */
{ "table_filter", 1, "SCRIPT", }, /* 6 */
{ "attach", 1, "TABLE" }, /* 0 */
{ "changeset", 0, "" }, /* 1 */
{ "delete", 0, "" }, /* 2 */
{ "enable", 1, "BOOL" }, /* 3 */
{ "indirect", 1, "BOOL" }, /* 4 */
{ "isempty", 0, "" }, /* 5 */
{ "table_filter", 1, "SCRIPT" }, /* 6 */
{ "patchset", 0, "", }, /* 7 */
{ "diff", 2, "FROMDB TBL", }, /* 8 */
{ "diff", 2, "FROMDB TBL" }, /* 8 */
{ "fullchangeset",0, "" }, /* 9 */
{ 0 }
};
int iSub;
@@ -271,10 +272,11 @@ static int SQLITE_TCLAPI test_session_cmd(
break;
}
case 9: /* fullchangeset */
case 7: /* patchset */
case 1: { /* changeset */
TestSessionsBlob o = {0, 0};
if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){
if( iSub!=9 && test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){
void *pCtx = (void*)&o;
if( iSub==7 ){
rc = sqlite3session_patchset_strm(pSession, testStreamOutput, pCtx);
@@ -284,6 +286,8 @@ static int SQLITE_TCLAPI test_session_cmd(
}else{
if( iSub==7 ){
rc = sqlite3session_patchset(pSession, &o.n, &o.p);
}else if( iSub==9 ){
rc = sqlite3session_fullchangeset(pSession, &o.n, &o.p);
}else{
rc = sqlite3session_changeset(pSession, &o.n, &o.p);
}
@@ -298,6 +302,7 @@ static int SQLITE_TCLAPI test_session_cmd(
break;
}
case 2: /* delete */
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
break;
@@ -478,7 +483,7 @@ static int test_filter_handler(
Tcl_DecrRefCount(pEval);
return res;
}
}
static int test_conflict_handler(
void *pCtx, /* Pointer to TestConflictHandler structure */
@@ -1120,6 +1125,127 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach(
return TCL_OK;
}
#include "sqlite3changebatch.h"
typedef struct TestChangebatch TestChangebatch;
struct TestChangebatch {
sqlite3_changebatch *pChangebatch;
};
/*
** Tclcmd: $changebatch add BLOB
** $changebatch zero
** $changebatch delete
*/
static int SQLITE_TCLAPI test_changebatch_cmd(
void *clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
TestChangebatch *p = (TestChangebatch*)clientData;
sqlite3_changebatch *pChangebatch = p->pChangebatch;
struct SessionSubcmd {
const char *zSub;
int nArg;
const char *zMsg;
int iSub;
} aSub[] = {
{ "add", 1, "CHANGESET", }, /* 0 */
{ "zero", 0, "", }, /* 1 */
{ "delete", 0, "", }, /* 2 */
{ 0 }
};
int iSub;
int rc;
if( objc<2 ){
Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
return TCL_ERROR;
}
rc = Tcl_GetIndexFromObjStruct(interp,
objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
);
if( rc!=TCL_OK ) return rc;
if( objc!=2+aSub[iSub].nArg ){
Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
return TCL_ERROR;
}
switch( iSub ){
case 0: { /* add */
int nArg;
unsigned char *pArg = Tcl_GetByteArrayFromObj(objv[2], &nArg);
rc = sqlite3changebatch_add(pChangebatch, pArg, nArg);
if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT ){
return test_session_error(interp, rc, 0);
}else{
extern const char *sqlite3ErrName(int);
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
}
break;
}
case 1: { /* zero */
sqlite3changebatch_zero(pChangebatch);
break;
}
case 2: /* delete */
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
break;
}
return TCL_OK;
}
static void SQLITE_TCLAPI test_changebatch_del(void *clientData){
TestChangebatch *p = (TestChangebatch*)clientData;
sqlite3changebatch_delete(p->pChangebatch);
ckfree((char*)p);
}
/*
** Tclcmd: sqlite3changebatch CMD DB-HANDLE
*/
static int SQLITE_TCLAPI test_sqlite3changebatch(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
sqlite3 *db;
Tcl_CmdInfo info;
int rc; /* sqlite3session_create() return code */
TestChangebatch *p; /* New wrapper object */
if( objc!=3 ){
Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE");
return TCL_ERROR;
}
if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){
Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
return TCL_ERROR;
}
db = *(sqlite3 **)info.objClientData;
p = (TestChangebatch*)ckalloc(sizeof(TestChangebatch));
memset(p, 0, sizeof(TestChangebatch));
rc = sqlite3changebatch_new(db, &p->pChangebatch);
if( rc!=SQLITE_OK ){
ckfree((char*)p);
return test_session_error(interp, rc, 0);
}
Tcl_CreateObjCommand(
interp, Tcl_GetString(objv[1]), test_changebatch_cmd, (ClientData)p,
test_changebatch_del
);
Tcl_SetObjResult(interp, objv[1]);
return TCL_OK;
}
/*
** tclcmd: CMD configure REBASE-BLOB
** tclcmd: CMD rebase CHANGESET
@@ -1302,6 +1428,10 @@ int TestSession_Init(Tcl_Interp *interp){
Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0);
}
Tcl_CreateObjCommand(
interp, "sqlite3changebatch", test_sqlite3changebatch, 0, 0
);
return TCL_OK;
}
+5 -1
View File
@@ -437,7 +437,8 @@ TESTSRC2 = \
$(TOP)/ext/async/sqlite3async.c \
$(TOP)/ext/misc/stmt.c \
$(TOP)/ext/session/sqlite3session.c \
$(TOP)/ext/session/test_session.c
$(TOP)/ext/session/sqlite3changebatch.c \
$(TOP)/ext/session/test_session.c
# Header files used by all library source files.
#
@@ -1001,6 +1002,9 @@ THREADTEST3_SRC = $(TOP)/test/threadtest3.c \
threadtest3$(EXE): sqlite3.o $(THREADTEST3_SRC) $(TOP)/src/test_multiplex.c
$(TCCX) $(TOP)/test/threadtest3.c $(TOP)/src/test_multiplex.c sqlite3.o -o $@ $(THREADLIB)
bc_test1$(EXE): sqlite3.o $(TOP)/test/bc_test1.c $(TOP)/test/tt3_core.c
$(TCCX) $(TOP)/test/bc_test1.c sqlite3.o -o $@ $(THREADLIB)
threadtest: threadtest3$(EXE)
./threadtest3$(EXE)
+63 -45
View File
@@ -1,5 +1,5 @@
C Simplify\s#ifdefs\sassociated\swith\sParse.eParseMode.\s\sFix\san\s#ifdef\serror\nassociated\swith\sSQLITE_OMIT_AUTOVACUUM.
D 2020-08-10T21:01:32.620
C Update\sthis\sbranch\swith\sthe\s"PRAGMA\snoop_update"\sfix\sfrom\sbegin-concurrent-report.
D 2020-08-31T20:37:00.353
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -38,6 +38,8 @@ F configure a97f98dfff699495aef66ae3d9c424345778a663f583e0d6e7522670518f87c1 x
F configure.ac 40d01e89cb325c28b33f5957e61fede0bd17da2b5e37d9b223a90c8a318e88d4
F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad
F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd
F doc/begin_concurrent.md 4bee2c3990d1eb800f1ce3726a911292a8e4b889300b2ffd4b08d357370db299
F doc/begin_concurrent_report.md 58c032ebc2d5980e3b25bbe805f2addd31be787d1ebe51f74f4f48044fa9e2c0
F doc/lemon.html 1edc0f916e771212792d4d077aedc05168bf13fd65d64d41b2c13e46ac0063a8
F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710
F doc/trusted-schema.md 33625008620e879c7bcfbbfa079587612c434fa094d338b08242288d358c3e8a
@@ -419,6 +421,8 @@ F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
F ext/rtree/util/randomshape.tcl 54ee03d0d4a1c621806f7f44d5b78d2db8fac26e0e8687c36c4bd0203b27dbff
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
F ext/rtree/visual01.txt e9c2564083bcd30ec51b07f881bffbf0e12b50a3f6fced0c222c5c1d2f94ac66
F ext/session/changebatch1.test 9ceaa8f7b2a505933e250fbe6cbc550e4ce1e59d
F ext/session/changebatchfault.test be49c793219bf387ad692a60856b921f0854ad6d
F ext/session/changeset.c 7a1e6a14c7e92d36ca177e92e88b5281acd709f3b726298dc34ec0fb58869cb5
F ext/session/changesetfuzz.c 227076ab0ae4447d742c01ee88a564da6478bbf26b65108bf8fac9cd8b0b24aa
F ext/session/changesetfuzz1.test 2e1b90d888fbf0eea5e1bd2f1e527a48cc85f8e0ff75df1ec4e320b21f580b3a
@@ -448,16 +452,18 @@ F ext/session/sessioninvert.test 04075517a9497a80d39c495ba6b44f3982c7371129b89e2
F ext/session/sessionrebase.test ccfa716b23bd1d3b03217ee58cfd90c78d4b99f53e6a9a2f05e82363b9142810
F ext/session/sessionstat1.test 218d351cf9fcd6648f125a26b607b140310160184723c2666091b54450a68fb5
F ext/session/sessionwor.test 67b5ab91d4f93ce65ff1f58240ac5ddf73f8670facc1ffa49cef56293d52818d
F ext/session/sqlite3session.c fc8c6c13dc0456943ff24abf574ced10418eec66a548c97d3eafbebe9fc5e908
F ext/session/sqlite3session.h a2db5b72b938d12c727b4b4ec632254ca493670a9c0de597af3271a7f774fc57
F ext/session/test_session.c 98797aba475a799376c9a42214f2d1debf2d0c3cb657d9c8bbf4f70bf3fb4aec
F ext/session/sqlite3changebatch.c d5553b79e012ee2cb06c0a96bdf9dfe19e66354390ea0036cc46c4953142d517
F ext/session/sqlite3changebatch.h e72016998c9a22d439ddfd547b69e1ebac810c24
F ext/session/sqlite3session.c 6aa98b2158033fc9a20f789df5a7a7815284a458b18ed1c719d073be0bd635eb
F ext/session/sqlite3session.h ba1b5dc7b159a76d9300228719e1248a4cb5ba7ee6ebd4c19a32fdc23891d22a
F ext/session/test_session.c 60e15d5db8ae7a0f521e70a7504ba1f74fc50548a25a5397808f487bc6a92b5d
F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
F main.mk b1cd0bc6aedad7ebb667b7f74f835f932f60ee33be2a5c3051fd93eb465f5c75
F main.mk bfb659254b7af99ca95b630a6abe903f70c2d78ff39e7790c14b30a13d9fc7f9
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
@@ -473,13 +479,13 @@ F src/alter.c 4bc16666a0df99d49658ec66f00ca36e541ec6cb39673ccc51be6a981fb9b2ce
F src/analyze.c 5cffff3d355858cd22bfc6e20ac7203510d2e1cc935086eb06f4abb2f579f628
F src/attach.c 0b11e00c166b622c84ec176773b1d691c61ad07d247809e3e1635d4e99e71d30
F src/auth.c a3d5bfdba83d25abed1013a8c7a5f204e2e29b0c25242a56bc02bb0c07bf1e06
F src/backup.c b1c90cd4110248c8e1273ff4578d3a84c0c34725e1b96dacd4a6294a908702de
F src/bitvec.c 17ea48eff8ba979f1f5b04cc484c7bb2be632f33
F src/backup.c ce116c9f5180a22a446391057a06a0cf6810260f7d314d5d99b12cfa6f17ac97
F src/bitvec.c a012cfe503cddd1df1a75c139baf4586acc32f25c66f79039caf1d06474434f2
F src/btmutex.c 8acc2f464ee76324bf13310df5692a262b801808984c1b79defb2503bbafadb6
F src/btree.c 1439fd9b45d4d1883c53752daef42af489adaa1a1508fa39dedbc9c80ea21a2f
F src/btree.h 7af72bbb4863c331c8f6753277ab40ee67d2a2125a63256d5c25489722ec162b
F src/btreeInt.h 83166f6daeb91062b6ae9ee6247b3ad07e40eba58f3c05ba9e8dedad4ab1ea38
F src/build.c dde514651cf3cf3a3a64a6e5c62384c01486c6faeabd165b3fe6dec6d2a0e113
F src/btree.c abc4d4ef16281dbfd453f4d719e390792ede4db8a39c98fc853b56922e2f3748
F src/btree.h 1b31533bd7efd217604df5da57ca12b5854d8d989a42382584d4fd0c0498073b
F src/btreeInt.h 6c3d3586c044f2447c8c56f1feb838f461071389ac42ad295695846ba446e3a8
F src/build.c 11d915b843fa8f31c6cc8b62c7f4e68a65496effa6fbe35882ab949635b8d0bc
F src/callback.c d0b853dd413255d2e337b34545e54d888ea02f20da5ad0e63585b389624c4a6c
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
F src/ctime.c e98518d2d3d4029a13c805e07313fb60c877be56db76e90dd5f3af73085d0ce6
@@ -490,16 +496,16 @@ F src/delete.c 410c771c25afc113c273d9efad6ab6881bda28c75a1838b9d2c52ba20d1dc704
F src/expr.c 58c06940d964c2cf455b979cf66a648499d294a5ee6dadcaeaed447257c1dc75
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
F src/fkey.c 83372403298e6a7dd989a47aaacdbaa5b4307b5199dbd56e07d4896066b3de72
F src/func.c 2333eb4277f55a5efdc12ef754e7d7ec9105d257b2fd00301d23ce1e8fa67dc0
F src/func.c 3796e7e269a9f23867ad3e64152399d5f850f83b9ee3ba57a931da7e46ef9c00
F src/global.c 943256ac44f333039d35a9830c18d075a81fa6b6bf2af05771494a9acfb9a40b
F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19
F src/hash.h 9d56a9079d523b648774c1784b74b89bd93fac7b365210157482e4319a468f38
F src/hwtime.h cb1d7e3e1ed94b7aa6fde95ae2c2daccc3df826be26fc9ed7fd90d1750ae6144
F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
F src/insert.c 957254a2d0542597455d0d4c640e4e3f3eea8c6d78f04582df03dfc626f07925
F src/insert.c c1bbc78590a7538abcef5b7dfd05edd86176387848daa565def9eb375e523d12
F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
F src/loadext.c 436af4968c6954d304fce9efa12719367bd8f37b19b93b71d6ad607e85adbb47
F src/main.c f41d2aea8ac534e4054deb893ad93d292f37a214de1a9cff34fbde407ef685e6
F src/main.c 9f6b3c7363b07892a2d8d0f9046b05545f863268e24176c037fafed53355d750
F src/malloc.c 22d5bdd9fe88ae4fad1b91a1b9735104b82853ffef868f1f05517d60dc1875f5
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de
@@ -519,34 +525,34 @@ F src/os.c 80e4cf3e5da06be03ca641661e331ce60eeeeabf0d7354dbb1c0e166d0eedbbe
F src/os.h 48388821692e87da174ea198bf96b1b2d9d83be5dfc908f673ee21fafbe0d432
F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85
F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586
F src/os_unix.c 9b1b860163fd2d4d7679b5260d384d1a9f88ef917a90f28963eca8acd472d8c8
F src/os_unix.c 6315625481f2f2e65a33324730ad94357818c84a00c4459728e00d9cc82c81b9
F src/os_win.c a2149ff0a85c1c3f9cc102a46c673ce87e992396ba3411bfb53db66813b32f1d
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
F src/pager.c 3700a1c55427a3d4168ad1f1b8a8b0cb9ace1d107e4506e30a8f1e66d8a1195e
F src/pager.h 4bf9b3213a4b2bebbced5eaa8b219cf25d4a82f385d093cd64b7e93e5285f66f
F src/parse.y 5bdb760a29c0b25caf7e80e82210b81cd2ea3066d5199ca29e6eac40b34bc184
F src/pager.c 6da497a9d6071d09c5b7d4ae8c5c022a89a625e286d03cc2e1589025cb9a3c07
F src/pager.h ed1f7bc91ceae75fa918d51f7c2ad071a5458cc3dc0085ca7577f8d51bf2d7cb
F src/parse.y b2a9db24fee32edf26e0e4970c64394eed90f0c3608fae6f3433cc5dd3404a83
F src/pcache.c 385ff064bca69789d199a98e2169445dc16e4291fa807babd61d4890c3b34177
F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586
F src/pcache1.c 6596e10baf3d8f84cc1585d226cf1ab26564a5f5caf85a15757a281ff977d51a
F src/pragma.c bdb600be936f66b9fe69d26dfbba4528beaaf4f95c479c85b328a92484e0bf71
F src/pragma.h 8dc78ab7e9ec6ce3ded8332810a2066f1ef6267e2e03cd7356ee00276125c6cf
F src/pragma.h b93fd693bd38cefeaf8e2c7e8c929717f7799a9770104cdd2bae8df2a84e2da7
F src/prepare.c 3d5a761d026052bc888d1b803a06dd2bfe245e8e836d4689f927003549148b0f
F src/printf.c 9efcd4e984f22bcccb1ded37a1178cac98f6e3a0534e1e0629f64899971f8838
F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
F src/random.c f27af4099afaea7284ade5c206224dcfdb2334cfd119d018b470d46356b3f27d
F src/resolve.c d74715aceed2a8f493ba244d535646fa93132042a4400a29dfd26ec841514048
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
F src/select.c 510fdf819f218be3dac2683d3eaaf64e5080f548061a4dd12205590beda976bb
F src/select.c 02c5b12ca8dc6c7c1a5fec01f0eb09867692962263ddcaf47d6d7264eebdc55d
F src/shell.c.in b9b819feede7b85585ab0826490a352e04e2ee46e8132c92597d29972b2be1d7
F src/sqlite.h.in d2c03414a8ee5d4a6855c04dd7cd5998e45139b0fe66b65bae86d4223edd091f
F src/sqlite.h.in 017b0ed87e86a5261b7ce3070784aaefb4c0355743575af0aa1427cc81318b2a
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h 2d1af80082edffd71c6f96f70ad1ce6a4fb46615ad10291fc77fe0dea9ff0197
F src/sqliteInt.h a938ad036cc25fd1a9c7ab1586cbd651ac9c99c1ae57f539f88f9a7271f12ac8
F src/sqliteInt.h 2b9bd070a108e58b4f91b94cac81d9782483e01d68d942a585c9b1313d56b6fd
F src/sqliteLimit.h 95cb8479ca459496d9c1c6a9f76b38aee12203a56ce1092fe13e50ae2454c032
F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
F src/tclsqlite.c 986b6391f02cd9b53c1d688be55899f6ffddeb8e8014cd83c1b73ff912579a71
F src/test1.c 77d0bf0140d92e5e8234dd3e158a985fc905e5bd9d94254370d4e9421f7c2224
F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
F src/test1.c efd3d31a4ab31629333e74fc2a3cfb0726436ef6b7cf7f5abf69fd36382c541c
F src/test2.c cb988be1ee1b972dc471e6b076087cfa9cb9ce5c2aa31e98ecf41c3256a72a53
F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644
F src/test4.c 7c4420e01c577b5c4add2cb03119743b1a357543d347773b9e717195ea967159
F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d
@@ -560,7 +566,7 @@ F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0
F src/test_bestindex.c 78809f11026f18a93fcfd798d9479cba37e1201c830260bf1edc674b2fa9b857
F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce
F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274
F src/test_config.c 5ea19bf0972a9d91728518b4d30e91477acce80496003ecbef3a7fb18d0bd081
F src/test_config.c 2882bb5dfb6429a79736029766fd12f4fcf25a721dd9e48e71eeca3ccf2c94df
F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f
F src/test_demovfs.c 86142ba864d4297d54c5b2e972e74f3141ae4b30f05b3a95824184ed2d3d7f91
F src/test_devsym.c 6109b45c3db3ef7b002320947ed448c027356ab8b885156ff535fd8684d4a571
@@ -602,16 +608,16 @@ F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
F src/tokenize.c 4dc01b267593537e2a0d0efe9f80dabe24c5b6f7627bc6971c487fa6a1dacbbf
F src/treeview.c 4b92992176fb2caefbe06ba5bd06e0e0ebcde3d5564758da672631f17aa51cda
F src/trigger.c ef67bde309a831515dc3c2173d792574309f2f42d45f8c078743fae9f7f98c75
F src/update.c fb15bec5b54fd098f4b84f6abc83c7103b45ba8484011fff8edf5ae31656eab6
F src/update.c 4fb5ffdc515da442a174a77e57e08e26554037899c40732bb25894426cad3266
F src/upsert.c 2920de71b20f04fe25eb00b655d086f0ba60ea133c59d7fa3325c49838818e78
F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0
F src/util.c c8bf30c4356b091bcc3b624d0e24b2b4d11b8be4d6c90d8e0705971e15cc819b
F src/vacuum.c 1c4f8e2f39d950037f4cf946b6858c993d3a54c3101f78e05c76460a073afcf0
F src/vdbe.c e9f7f818f128c8600058c0eabb6b3975974c95153a104d340f419adabbc15b9f
F src/vacuum.c 4f586072e58b950f31737a2f5294acf9ae077ce4dc62f991b71874c650b9e276
F src/vdbe.c c45eb00686187826c04349deaa1e933245e032aa71efcc70d7f87733e7cb7058
F src/vdbe.h 83603854bfa5851af601fc0947671eb260f4363e62e960e8a994fb9bbcd2aaa1
F src/vdbeInt.h 762abffb7709f19c2cb74af1bba73a900f762e64f80d69c31c9ae89ed1066b60
F src/vdbeapi.c c5e7cb2ab89a24d7f723e87b508f21bfb1359a04db5277d8a99fd1e015c12eb9
F src/vdbeaux.c 73854da7a9a4f12db72a855758214173c82f46a14be6cb19e63677ba02c97cae
F src/vdbeInt.h 808c424389e4aad8b379aa9d29baec4a2f2680d6f0c5caeb7fce629beb357e82
F src/vdbeapi.c 1b210d5a5b82783f6fadbe5f54298f01ad55eeedc5f298bb28950007d50c45ab
F src/vdbeaux.c 35730cc4c8d16bc313e3b1d4107f713dc5c5d75fbfbb4743a69047cda8bb8b75
F src/vdbeblob.c 253ed82894924c362a7fa3079551d3554cd1cdace39aa833da77d3bc67e7c1b1
F src/vdbemem.c 947f2a65910edb4014dc981d33e414a68c51f169f9df8c4c493a0ba840b6eb1f
F src/vdbesort.c 2be76d26998ce2b3324cdcc9f6443728e54b6c7677c553ad909c7d7cfab587df
@@ -619,12 +625,12 @@ F src/vdbetrace.c fa3bf238002f0bbbdfb66cc8afb0cea284ff9f148d6439bc1f6f2b4c3b7143
F src/vdbevtab.c f99b275366c5fc5e2d99f734729880994ab9500bdafde7fae3b02d562b9d323c
F src/vtab.c 5f5fc793092f53bbdfde296c50f563fb7bda58cf48e9cf6a8bdfbc5abd409845
F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
F src/wal.c 7a05a519a02ffb7f2a458838a25853c7300c9e6d9ef546ee48469378ac0404f9
F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a
F src/wal.c 5cdd43b7538a4133438a85fcc7c2d8a1cdae2cc851ce58c9f2d2f849974a9436
F src/wal.h 7ffe787437f20a098af347011967a6d3bb8e5c3dc645e6be59eff44d2b2c5297
F src/walker.c 3df26a33dc4f54e8771600fb7fdebe1ece0896c2ad68c30ab40b017aa4395049
F src/where.c 2ea911238674e9baaeddf105dddabed92692a01996073c4d4983f9a7efe481f9
F src/whereInt.h 6b874aa15f94e43a2cec1080be64d955b04deeafeac90ffb5d6975c0d511be3c
F src/wherecode.c 8064fe5c042824853a9b1fda670054a51a49033a6c79059988c97751ccf8088e
F src/wherecode.c a1e168ea0c8348fd0e1a9787672131908a6c84cf179dd02d69eb6a0baf6198a2
F src/whereexpr.c 264d58971eaf8256eb5b0917bcd7fc7a1f1109fdda183a8382308a1b18a2dce7
F src/window.c edd6f5e25a1e8f2b6f5305b7f5f7da7bb35f07f0d432b255b1d4c2fcab4205aa
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
@@ -699,6 +705,7 @@ F test/backup_malloc.test 0c9abdf74c51e7bedb66d504cd684f28d4bd4027
F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f
F test/badutf2.test f310fd3b24a491b6b77bccdf14923b85d6ebcce751068c180d93a6b8ff854399
F test/bc_common.tcl b5e42d80305be95697e6370e015af571e5333a1c
F test/bc_test1.c e0a092579552e066ed4ce7bcdaecfa69c4aacc8d
F test/bestindex1.test 705b57d7f51d53ee5fd043dd9666236e1fc18f4d59abf51da0ea5ea1b4804947
F test/bestindex2.test 9a0ccd320b6525eec3a706aae6cdab7e1b7b5abca75027e39f39f755e76e5928
F test/bestindex3.test 7622e792ff2da16d262d3cea6ad914591ac4806d57ed128e6c940b7920b47b84
@@ -759,6 +766,16 @@ F test/collateA.test b8218ab90d1fa5c59dcf156efabb1b2599c580d6
F test/collateB.test 1e68906951b846570f29f20102ed91d29e634854ee47454d725f2151ecac0b95
F test/colmeta.test 2c765ea61ee37bc43bbe6d6047f89004e6508eb1
F test/colname.test 87ad5458bb8709312dac0d6755fd30e8e4ca83298d0a9ef6e5c24277a3c3390e
F test/concfault.test af01b1f6cca45b8f17bb13b4406ec43ab1689181a08850b990596c7a9f4e656f
F test/concreport.test 08b32b6e042ded033a822d086a22b7acfdf118c23fd0c124fee685723175a942
F test/concurrent.test a0248ec6e3e79a5948453649cf86b5b359175cba55ea636b15426d6f0fa6c3da
F test/concurrent2.test de748c7dd749c77e2af2c4b914b9b09a28ac09608042ca498c0251dc6f46aa1a
F test/concurrent3.test 530671ac706f6a1d0f4992dbdd33a86408330d03cd90fb9e82ecb1b27f5fd081
F test/concurrent4.test e0b12cd467137e50259df3b4f837507e82aaa07c35941c88664dc8ed1d089c44
F test/concurrent5.test 0c16cbf7446af162a14e6def30445e94016064eb994e5aa4ebb2bebc59554176
F test/concurrent6.test a7860e9ca13bb5fb76bcf41c5524fbfa9c37e6e258ecf84ffb5748a272488c67
F test/concurrent7.test b96fa5c4cfdf8d5c0bc66b6934214500bad0260884a736f054ccc76e81aae85d
F test/concurrent8.test b93937e74a8efb8b84f2fea7595b53418c5f29777bbe9cbdb5dc219b3dd72a7d
F test/conflict.test 58857e2533fb9f2e0358ea7cb191215657846be1dd9da3b3d6df3e750c02ae03
F test/conflict2.test bb0b94cf7196c64a3cbd815c66d3ee98c2fecd9c
F test/conflict3.test 81865d9599609aca394fb3b9cd5f561d4729ea5b176bece3644f6ecb540f88ac
@@ -1202,6 +1219,7 @@ F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660
F test/nan.test 437d40e6d0778b050d7750726c0cbd2c9936b81962926e8f8c48ca698f00f4d1
F test/nockpt.test 8c43b25af63b0bd620cf1b003529e37b6f1dc53bd22690e96a1bd73f78dde53a
F test/nolock.test f196cf8b8fbea4e2ca345140a2b3f3b0da45c76e
F test/noop_update.test 74d71bbe5d37f7861cc2402ed620eb165b8e6b00159cdcada7df716420336651
F test/normalize.test f23b6c5926c59548635fcf39678ac613e726121e073dd902a3062fbb83903b72
F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf
F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161
@@ -1239,7 +1257,6 @@ F test/pagerfault2.test caf4c7facb914fd3b03a17b31ae2b180c8d6ca1f
F test/pagerfault3.test 1003fcda009bf48a8e22a516e193b6ef0dd1bbd8
F test/pageropt.test 84e4cc5cbca285357f7906e99b21be4f2bf5abc0
F test/pagesize.test 5769fc62d8c890a83a503f67d47508dfdc543305
F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035ce4b3
F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
@@ -1437,7 +1454,7 @@ F test/thread2.test f35d2106452b77523b3a2b7d1dcde2e5ee8f9e46
F test/thread_common.tcl 334639cadcb9f912bf82aa73f49efd5282e6cadd
F test/threadtest1.c 6029d9c5567db28e6dc908a0c63099c3ba6c383b
F test/threadtest2.c a70a8e94bef23339d34226eb9521015ef99f4df8
F test/threadtest3.c 38a612ea62854349ed66372f330a40d73c5cf956
F test/threadtest3.c 4b413718ab3ef5e4f9f1ab0502b89707c5a9ccdc
F test/threadtest4.c c1e67136ceb6c7ec8184e56ac61db28f96bd2925
F test/time-wordcount.sh 8e0b0f8109367827ad5d58f5cc849705731e4b90
F test/tkt-02a8e81d44.test 6c80d9c7514e2a42d4918bf87bf6bc54f379110c
@@ -1611,6 +1628,7 @@ F test/triggerG.test 2b816093c91ba73c733cfa8aedcc210ad819d72a98b1da30768a3c56505
F test/triggerupfrom.test d25961fa70a99b6736193da7b49a36d8c1d28d56188f0be6406d4366315cd6e4
F test/trustschema1.test 4e970aef0bfe0cee139703cc7209d0e0f07725d999b180ba50770f49edef1494
F test/tt3_checkpoint.c 9e75cf7c1c364f52e1c47fd0f14c4340a9db0fe1
F test/tt3_core.c 8cd89ead95410f70e7fb02c79f1e040f9c5ad5cf
F test/tt3_index.c 39eec10a35f57672225be4d182862152896dee4a
F test/tt3_lookaside1.c 0377e202c3c2a50d688cb65ba203afeda6fafeb9
F test/tt3_stress.c f9a769ca8b026ecc76ee93ca8c9700a5619f8e51c581107c4053ba6ac97f616f
@@ -1673,7 +1691,7 @@ F test/vtab_err.test dcc8b7b9cb67522b3fe7a272c73856829dae4ab7fdb30399aea1b6981bd
F test/vtab_shared.test 5253bff2355a9a3f014c15337da7e177ab0ef8ad
F test/vtabdrop.test 65d4cf6722972e5499bdaf0c0d70ee3b8133944a4e4bc31862563f32a7edca12
F test/wal.test 16180bc4becda176428ad02eaea437b4b8f5ae099314de443a4e12b2dcc007a2
F test/wal2.test 31f6e2c404b9f2cdf9ca19b105a1742fdc19653c2c936da39e3658c617524046
F test/wal2.test 88d34cc62bf5460ad6b1f837cd56ab55731bb0637ff99e113b790873d9b60b14
F test/wal3.test 2a93004bc0fb2b5c29888964024695bade278ab2
F test/wal4.test 4744e155cd6299c6bd99d3eab1c82f77db9cdb3c
F test/wal5.test 9c11da7aeccd83a46d79a556ad11a18d3cb15aa9
@@ -1682,7 +1700,7 @@ F test/wal64k.test 2a525c0f45d709bae3765c71045ccec5df7d100ccbd3a7860fdba46c9addb
F test/wal7.test 2ae8f427d240099cc4b2dfef63cff44e2a68a1bd
F test/wal8.test d9df3fba4caad5854ed69ed673c68482514203c8
F test/wal9.test 378e76a9ad09cd9bee06c172ad3547b0129a6750
F test/wal_common.tcl a98f17fba96206122eff624db0ab13ec377be4fe
F test/wal_common.tcl 37902864b63794e9bf59393b8887e21a9cbd4db8
F test/walbak.test 018d4e5a3d45c6298d11b99f09a8ef6876527946
F test/walbig.test f437473a16cfb314867c6b5d1dbcd519e73e3434
F test/walblock.test be48f3a75eff0b4456209f26b3ce186c2015497d
@@ -1812,7 +1830,7 @@ F tool/mkmsvcmin.tcl 6ecab9fe22c2c8de4d82d4c46797bda3d2deac8e763885f5a38d0c44a89
F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c
F tool/mkopcodeh.tcl 352a4319c0ad869eb26442bf7c3b015aa15594c21f1cce5a6420dbe999367c21
F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
F tool/mkpragmatab.tcl ae5585ae76ca26e4d6ccd5ea9cdebaf5efefb318bf989497a0e846cd711d9ab1
F tool/mkpragmatab.tcl d189900fe12be39539f7101222968ac7a5774a63638471f0b6bff9c35006743a
F tool/mkshellc.tcl 70a9978e363b0f3280ca9ce1c46d72563ff479c1930a12a7375e3881b7325712
F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
@@ -1879,7 +1897,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 680bdc6524ad6af0e74401e96e3a576145042fb865c1582bfaffc11d4ff76a4f
R b720a86de77c44208b3db65976426d46
U drh
Z 737e0d499b7474145d7a36af76d98f48
P 30214727f7af9b5471654b0dd2cd234f0e284b24661104e55a9be771baf9577a 3c0423f0a8685cf8ef165914e6f9bd8f191d32db1c8287aed2a3ba6e272fc9f0
R 8414831f731e41c187bf652e236d0254
U dan
Z 9d321d7497cdfa0c4d4a1078d8e37e70
+1 -1
View File
@@ -1 +1 @@
5bbd4bddd3b9fa64d134ed62bce3eb4a09456bf24dec2474b5d764a3a3775964
1d98472277ff131960a7718cd57af5b6ed4ead811a9220a6eb35ff505bf587e5
+2 -2
View File
@@ -503,7 +503,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
}
}
if( rc==SQLITE_OK ){
rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 1);
rc = sqlite3PagerCommitPhaseOne(pDestPager, p->pDest, 0, 1);
}
/* Write the extra pages and truncate the database file as required */
@@ -532,7 +532,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
}
}else{
sqlite3PagerTruncateImage(pDestPager, nDestTruncate);
rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 0);
rc = sqlite3PagerCommitPhaseOne(pDestPager, p->pDest, 0, 0);
}
/* Finish committing the transaction to the destination database. */
+82
View File
@@ -171,6 +171,12 @@ int sqlite3BitvecSet(Bitvec *p, u32 i){
if( p==0 ) return SQLITE_OK;
assert( i>0 );
assert( i<=p->iSize );
if( i>p->iSize || i==0 ){
sqlite3_log(SQLITE_ERROR,
"Bitvec: setting bit %d of bitvec size %d\n", (int)i, (int)p->iSize
);
abort();
}
i--;
while((p->iSize > BITVEC_NBIT) && p->iDivisor) {
u32 bin = i/p->iDivisor;
@@ -293,6 +299,82 @@ u32 sqlite3BitvecSize(Bitvec *p){
return p->iSize;
}
static int bitvecCount(Bitvec *p){
int nRet = 0;
if( p ){
if( p->iDivisor ){
int i;
for(i=0; i<BITVEC_NPTR; i++){
nRet += bitvecCount(p->u.apSub[i]);
}
}else if( p->iSize<=BITVEC_NBIT ){
int i;
for(i=0; i<BITVEC_NELEM; i++){
BITVEC_TELEM x = p->u.aBitmap[i];
assert( sizeof(x)==1 );
if( x & 0x80 ) nRet++;
if( x & 0x40 ) nRet++;
if( x & 0x20 ) nRet++;
if( x & 0x10 ) nRet++;
if( x & 0x08 ) nRet++;
if( x & 0x04 ) nRet++;
if( x & 0x02 ) nRet++;
if( x & 0x01 ) nRet++;
}
}else{
nRet += p->nSet;
}
}
return nRet;
}
static int bitvecArray(Bitvec *p, u32 *aElem, u32 iOff){
int nRet = 0;
if( p ){
if( p->iDivisor ){
int i;
for(i=0; i<BITVEC_NPTR; i++){
nRet += bitvecArray(p->u.apSub[i], &aElem[nRet], i*p->iDivisor + iOff);
}
}else if( p->iSize<=BITVEC_NBIT ){
int i;
for(i=0; i<BITVEC_NELEM; i++){
BITVEC_TELEM x = p->u.aBitmap[i];
assert( sizeof(x)==1 );
if( x & 0x01 ){ aElem[nRet++] = iOff + i*8 + 0; }
if( x & 0x02 ){ aElem[nRet++] = iOff + i*8 + 1; }
if( x & 0x04 ){ aElem[nRet++] = iOff + i*8 + 2; }
if( x & 0x08 ){ aElem[nRet++] = iOff + i*8 + 3; }
if( x & 0x10 ){ aElem[nRet++] = iOff + i*8 + 4; }
if( x & 0x20 ){ aElem[nRet++] = iOff + i*8 + 5; }
if( x & 0x40 ){ aElem[nRet++] = iOff + i*8 + 6; }
if( x & 0x80 ){ aElem[nRet++] = iOff + i*8 + 7; }
}
}else{
int i;
for(i=0; i<BITVEC_NINT; i++){
if( p->u.aHash[i] ){
aElem[nRet++] = p->u.aHash[i]+iOff-1;
}
}
assert( nRet==p->nSet );
}
}
return nRet;
}
int sqlite3BitvecArray(Bitvec *p, u32 **pa, int *pn){
int nElem;
u32 *aElem;
nElem = bitvecCount(p);
aElem = sqlite3MallocZero(nElem*sizeof(u32));
if( aElem==0 ) return SQLITE_NOMEM;
bitvecArray(p, aElem, 1);
*pa = aElem;
*pn = nElem;
return SQLITE_OK;
}
#ifndef SQLITE_UNTESTABLE
/*
** Let V[] be an array of unsigned characters sufficient to hold
+1170 -54
View File
File diff suppressed because it is too large Load Diff
+19
View File
@@ -331,6 +331,8 @@ int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask);
int sqlite3BtreeIsReadonly(Btree *pBt);
int sqlite3HeaderSizeBtree(void);
int sqlite3BtreeExclusiveLock(Btree *pBt);
#ifndef NDEBUG
int sqlite3BtreeCursorIsValid(BtCursor*);
#endif
@@ -387,5 +389,22 @@ void sqlite3BtreeCursorList(Btree*);
# define sqlite3SchemaMutexHeld(X,Y,Z) 1
#endif
int sqlite3BtreeScanStart(
BtCursor *pCsr,
UnpackedRecord *pKey,
i64 iKey,
int opcode,
int eqOnly
);
int sqlite3BtreeScanLimit(
BtCursor *pCsr,
UnpackedRecord *pKey,
i64 iKey,
int opcode
);
int sqlite3BtreeScanWrite(BtCursor *pCsr, int op, i64 id, const u8 *a, int n);
typedef struct PgHdr PgHdr;
int sqlite3BtreeScanDirty(Btree *pBtree, Bitvec *pRead, PgHdr *pList);
#endif /* SQLITE_BTREE_H */
+18 -3
View File
@@ -232,6 +232,7 @@
typedef struct MemPage MemPage;
typedef struct BtLock BtLock;
typedef struct CellInfo CellInfo;
typedef struct BtreePtrmap BtreePtrmap;
/*
** This is a magic string that appears at the beginning of every
@@ -276,6 +277,9 @@ struct MemPage {
u8 intKey; /* True if table b-trees. False for index b-trees */
u8 intKeyLeaf; /* True if the leaf of an intKey table */
Pgno pgno; /* Page number for this page */
#ifndef SQLITE_OMIT_CONCURRENT
Pgno pgnoRoot; /* Root page of b-tree that this page belongs to */
#endif
/* Only the first 8 bytes (above) are zeroed by pager.c when a new page
** is allocated. All fields that follow must be initialized before use */
u8 leaf; /* True if a leaf page */
@@ -438,6 +442,9 @@ struct BtShared {
Btree *pWriter; /* Btree with currently open write transaction */
#endif
u8 *pTmpSpace; /* Temp space sufficient to hold a single cell */
#ifndef SQLITE_OMIT_CONCURRENT
BtreePtrmap *pMap;
#endif
};
/*
@@ -513,6 +520,7 @@ struct BtCursor {
Btree *pBtree; /* The Btree to which this cursor belongs */
Pgno *aOverflow; /* Cache of overflow page locations */
void *pKey; /* Saved key that was cursor last known position */
CursorScan *pCScan;
/* All fields above are zeroed when the cursor is allocated. See
** sqlite3BtreeCursorZero(). Fields that follow must be manually
** initialized. */
@@ -654,12 +662,19 @@ struct BtCursor {
** (sqliteMallocRaw), it is not possible to use conditional compilation.
** So, this macro is defined instead.
*/
#ifndef SQLITE_OMIT_AUTOVACUUM
#define ISAUTOVACUUM (pBt->autoVacuum)
#else
#ifdef SQLITE_OMIT_AUTOVACUUM
#define ISAUTOVACUUM 0
#else
#define ISAUTOVACUUM (pBt->autoVacuum)
#endif
#ifdef SQLITE_OMIT_CONCURRENT
# define ISCONCURRENT 0
#else
# define ISCONCURRENT (pBt->pMap!=0)
#endif
#define REQUIRE_PTRMAP (ISAUTOVACUUM || ISCONCURRENT)
/*
** This structure is passed around through all the sanity checking routines
+4 -2
View File
@@ -59,8 +59,10 @@ void sqlite3TableLock(
TableLock *p;
assert( iDb>=0 );
#ifdef SQLITE_OMIT_CONCURRENT
if( iDb==1 ) return;
if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return;
#endif
for(i=0; i<pToplevel->nTableLock; i++){
p = &pToplevel->aTableLock[i];
if( p->iDb==iDb && p->iTab==iTab ){
@@ -4644,13 +4646,13 @@ void sqlite3BeginTransaction(Parse *pParse, int type){
}
v = sqlite3GetVdbe(pParse);
if( !v ) return;
if( type!=TK_DEFERRED ){
if( type==TK_IMMEDIATE || type==TK_EXCLUSIVE ){
for(i=0; i<db->nDb; i++){
sqlite3VdbeAddOp2(v, OP_Transaction, i, (type==TK_EXCLUSIVE)+1);
sqlite3VdbeUsesBtree(v, i);
}
}
sqlite3VdbeAddOp0(v, OP_AutoCommit);
sqlite3VdbeAddOp3(v, OP_AutoCommit, 0, 0, (type==TK_CONCURRENT));
}
/*
+4 -2
View File
@@ -511,8 +511,9 @@ static void randomFunc(
sqlite3_value **NotUsed2
){
sqlite_int64 r;
sqlite3 *db = sqlite3_context_db_handle(context);
UNUSED_PARAMETER2(NotUsed, NotUsed2);
sqlite3_randomness(sizeof(r), &r);
sqlite3FastRandomness(&db->sPrng, sizeof(r), &r);
if( r<0 ){
/* We need to prevent a random number of 0x8000000000000000
** (or -9223372036854775808) since when you do abs() of that
@@ -538,6 +539,7 @@ static void randomBlob(
){
sqlite3_int64 n;
unsigned char *p;
sqlite3 *db = sqlite3_context_db_handle(context);
assert( argc==1 );
UNUSED_PARAMETER(argc);
n = sqlite3_value_int64(argv[0]);
@@ -546,7 +548,7 @@ static void randomBlob(
}
p = contextMalloc(context, n);
if( p ){
sqlite3_randomness(n, p);
sqlite3FastRandomness(&db->sPrng, n, p);
sqlite3_result_blob(context, (char*)p, n, sqlite3_free);
}
}
+3 -3
View File
@@ -2323,13 +2323,13 @@ void sqlite3CompleteInsertion(
pik_flags |= OPFLAG_NCHANGE;
pik_flags |= (update_flags & OPFLAG_SAVEPOSITION);
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
if( update_flags==0 ){
if( 1 || update_flags==0 ){
int r = sqlite3GetTempReg(pParse);
sqlite3VdbeAddOp2(v, OP_Integer, 0, r);
sqlite3VdbeAddOp4(v, OP_Insert,
sqlite3VdbeAddOp4(v, OP_Insert,
iIdxCur+i, aRegIdx[i], r, (char*)pTab, P4_TABLE
);
sqlite3VdbeChangeP5(v, OPFLAG_ISNOOP);
sqlite3VdbeChangeP5(v, OPFLAG_ISNOOP|(update_flags&OPFLAG_ISUPDATE));
sqlite3ReleaseTempReg(pParse, r);
}
#endif
+34 -1
View File
@@ -1280,6 +1280,10 @@ void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
** passed to sqlite3_close (meaning that it is a zombie). Therefore,
** go ahead and free all resources.
*/
sqlite3BtreeScanDerefList(db->pCScanList);
db->pCScanList = 0;
sqlite3DbFree(db, db->zBCReport);
db->zBCReport = 0;
/* If a transaction is open, roll it back. This also ensures that if
** any database schemas have been modified by an uncommitted transaction
@@ -3143,7 +3147,7 @@ static int openDatabase(
db->aDb = db->aDbStatic;
db->lookaside.bDisable = 1;
db->lookaside.sz = 0;
sqlite3FastPrngInit(&db->sPrng);
assert( sizeof(db->aLimit)==sizeof(aHardLimit) );
memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit));
db->aLimit[SQLITE_LIMIT_WORKER_THREADS] = SQLITE_DEFAULT_WORKER_THREADS;
@@ -4549,6 +4553,35 @@ void sqlite3_snapshot_free(sqlite3_snapshot *pSnapshot){
}
#endif /* SQLITE_ENABLE_SNAPSHOT */
SQLITE_EXPERIMENTAL int sqlite3_wal_info(
sqlite3 *db, const char *zDb,
unsigned int *pnPrior, unsigned int *pnFrame
){
int rc = SQLITE_OK;
#ifndef SQLITE_OMIT_WAL
Btree *pBt;
int iDb;
#ifdef SQLITE_ENABLE_API_ARMOR
if( !sqlite3SafetyCheckOk(db) ){
return SQLITE_MISUSE_BKPT;
}
#endif
sqlite3_mutex_enter(db->mutex);
iDb = sqlite3FindDbName(db, zDb);
if( iDb<0 ){
return SQLITE_ERROR;
}
pBt = db->aDb[iDb].pBt;
rc = sqlite3PagerWalInfo(sqlite3BtreePager(pBt), pnPrior, pnFrame);
sqlite3_mutex_leave(db->mutex);
#endif /* SQLITE_OMIT_WAL */
return rc;
}
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
/*
** Given the name of a compile-time option, return true if that option
+131 -50
View File
@@ -1176,6 +1176,10 @@ struct unixInodeInfo {
sem_t *pSem; /* Named POSIX semaphore */
char aSemName[MAX_PATHNAME+2]; /* Name of that semaphore */
#endif
#ifdef SQLITE_SHARED_MAPPING
sqlite3_int64 nSharedMapping; /* Size of mapped region in bytes */
void *pSharedMapping; /* Memory mapped region */
#endif
};
/*
@@ -1328,6 +1332,13 @@ static void releaseInodeInfo(unixFile *pFile){
pInode->nRef--;
if( pInode->nRef==0 ){
assert( pInode->pShmNode==0 );
#ifdef SQLITE_SHARED_MAPPING
if( pInode->pSharedMapping ){
osMunmap(pInode->pSharedMapping, pInode->nSharedMapping);
pInode->pSharedMapping = 0;
pInode->nSharedMapping = 0;
}
#endif
sqlite3_mutex_enter(pInode->pLockMutex);
closePendingFds(pFile);
sqlite3_mutex_leave(pInode->pLockMutex);
@@ -2187,6 +2198,14 @@ static int nolockUnlock(sqlite3_file *NotUsed, int NotUsed2){
** Close the file.
*/
static int nolockClose(sqlite3_file *id) {
#ifdef SQLITE_SHARED_MAPPING
unixFile *pFd = (unixFile*)id;
if( pFd->pInode ){
unixEnterMutex();
releaseInodeInfo(pFd);
unixLeaveMutex();
}
#endif
return closeUnixFile(id);
}
@@ -4020,6 +4039,9 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){
*(i64*)pArg = pFile->mmapSizeMax;
if( newLimit>=0 && newLimit!=pFile->mmapSizeMax && pFile->nFetchOut==0 ){
pFile->mmapSizeMax = newLimit;
#ifdef SQLITE_SHARED_MAPPING
if( pFile->pInode==0 )
#endif
if( pFile->mmapSize>0 ){
unixUnmapfile(pFile);
rc = unixMapfile(pFile, -1);
@@ -4254,6 +4276,7 @@ struct unixShmNode {
char **apRegion; /* Array of mapped shared-memory regions */
int nRef; /* Number of unixShm objects pointing to this */
unixShm *pFirst; /* All unixShm objects pointing to this */
int aLock[SQLITE_SHM_NLOCK]; /* # shared locks on slot, -1==excl lock */
#ifdef SQLITE_DEBUG
u8 exclMask; /* Mask of exclusive locks held */
u8 sharedMask; /* Mask of shared locks held */
@@ -4794,6 +4817,38 @@ shmpage_out:
return rc;
}
/*
** Check that the pShmNode->aLock[] array comports with the locking bitmasks
** held by each client. Return true if it does, or false otherwise. This
** is to be used in an assert(). e.g.
**
** assert( assertLockingArrayOk(pShmNode) );
*/
#ifdef SQLITE_DEBUG
static int assertLockingArrayOk(unixShmNode *pShmNode){
unixShm *pX;
int aLock[SQLITE_SHM_NLOCK];
assert( sqlite3_mutex_held(pShmNode->pShmMutex) );
memset(aLock, 0, sizeof(aLock));
for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
int i;
for(i=0; i<SQLITE_SHM_NLOCK; i++){
if( pX->exclMask & (1<<i) ){
assert( aLock[i]==0 );
aLock[i] = -1;
}else if( pX->sharedMask & (1<<i) ){
assert( aLock[i]>=0 );
aLock[i]++;
}
}
}
assert( 0==memcmp(pShmNode->aLock, aLock, sizeof(aLock)) );
return (memcmp(pShmNode->aLock, aLock, sizeof(aLock))==0);
}
#endif
/*
** Change the lock state for a shared-memory segment.
**
@@ -4810,10 +4865,10 @@ static int unixShmLock(
){
unixFile *pDbFd = (unixFile*)fd; /* Connection holding shared memory */
unixShm *p = pDbFd->pShm; /* The shared memory being locked */
unixShm *pX; /* For looping over all siblings */
unixShmNode *pShmNode = p->pShmNode; /* The underlying file iNode */
int rc = SQLITE_OK; /* Result code */
u16 mask; /* Mask of locks to take or release */
int *aLock = pShmNode->aLock;
assert( pShmNode==pDbFd->pInode->pShmNode );
assert( pShmNode->pInode==pDbFd->pInode );
@@ -4852,78 +4907,76 @@ static int unixShmLock(
mask = (1<<(ofst+n)) - (1<<ofst);
assert( n>1 || mask==(1<<ofst) );
sqlite3_mutex_enter(pShmNode->pShmMutex);
assert( assertLockingArrayOk(pShmNode) );
if( flags & SQLITE_SHM_UNLOCK ){
u16 allMask = 0; /* Mask of locks held by siblings */
if( (p->exclMask|p->sharedMask) & mask ){
int ii;
int bUnlock = 1;
/* See if any siblings hold this same lock */
for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
if( pX==p ) continue;
assert( (pX->exclMask & (p->exclMask|p->sharedMask))==0 );
allMask |= pX->sharedMask;
for(ii=ofst; ii<ofst+n; ii++){
if( aLock[ii]>((p->sharedMask & (1<<ii)) ? 1 : 0) ){
bUnlock = 0;
}
}
if( bUnlock ){
rc = unixShmSystemLock(pDbFd, F_UNLCK, ofst+UNIX_SHM_BASE, n);
if( rc==SQLITE_OK ){
memset(&aLock[ofst], 0, sizeof(int)*n);
}
}else if( p->sharedMask & (1<<ofst) ){
assert( n==1 && aLock[ofst]>1 );
aLock[ofst]--;
}
/* Undo the local locks */
if( rc==SQLITE_OK ){
p->exclMask &= ~mask;
p->sharedMask &= ~mask;
}
}
/* Unlock the system-level locks */
if( (mask & allMask)==0 ){
rc = unixShmSystemLock(pDbFd, F_UNLCK, ofst+UNIX_SHM_BASE, n);
}else{
rc = SQLITE_OK;
}
/* Undo the local locks */
if( rc==SQLITE_OK ){
p->exclMask &= ~mask;
p->sharedMask &= ~mask;
}
}else if( flags & SQLITE_SHM_SHARED ){
u16 allShared = 0; /* Union of locks held by connections other than "p" */
/* Find out which shared locks are already held by sibling connections.
** If any sibling already holds an exclusive lock, go ahead and return
** SQLITE_BUSY.
*/
for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
if( (pX->exclMask & mask)!=0 ){
assert( n==1 );
assert( (p->exclMask & (1<<ofst))==0 );
if( (p->sharedMask & mask)==0 ){
if( aLock[ofst]<0 ){
rc = SQLITE_BUSY;
break;
}
allShared |= pX->sharedMask;
}
/* Get shared locks at the system level, if necessary */
if( rc==SQLITE_OK ){
if( (allShared & mask)==0 ){
}else if( aLock[ofst]==0 ){
rc = unixShmSystemLock(pDbFd, F_RDLCK, ofst+UNIX_SHM_BASE, n);
}else{
rc = SQLITE_OK;
}
}
/* Get the local shared locks */
if( rc==SQLITE_OK ){
p->sharedMask |= mask;
/* Get the local shared locks */
if( rc==SQLITE_OK ){
p->sharedMask |= mask;
aLock[ofst]++;
}
}
}else{
/* Make sure no sibling connections hold locks that will block this
** lock. If any do, return SQLITE_BUSY right away.
*/
for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
if( (pX->exclMask & mask)!=0 || (pX->sharedMask & mask)!=0 ){
** lock. If any do, return SQLITE_BUSY right away. */
int ii;
for(ii=ofst; ii<ofst+n; ii++){
assert( (p->sharedMask & mask)==0 );
if( (p->exclMask & (1<<ii))==0 && aLock[ii] ){
rc = SQLITE_BUSY;
break;
}
}
/* Get the exclusive locks at the system level. Then if successful
** also mark the local connection as being locked.
*/
/* Get the exclusive locks at the system level. Then if successful
** also update the in-memory values. */
if( rc==SQLITE_OK ){
rc = unixShmSystemLock(pDbFd, F_WRLCK, ofst+UNIX_SHM_BASE, n);
if( rc==SQLITE_OK ){
assert( (p->sharedMask & mask)==0 );
p->exclMask |= mask;
for(ii=ofst; ii<ofst+n; ii++){
aLock[ii] = -1;
}
}
}
}
assert( assertLockingArrayOk(pShmNode) );
sqlite3_mutex_leave(pShmNode->pShmMutex);
OSTRACE(("SHM-LOCK shmid-%d, pid-%d got %03x,%03x\n",
p->id, osGetpid(0), p->sharedMask, p->exclMask));
@@ -5014,6 +5067,9 @@ static int unixShmUnmap(
*/
static void unixUnmapfile(unixFile *pFd){
assert( pFd->nFetchOut==0 );
#ifdef SQLITE_SHARED_MAPPING
if( pFd->pInode ) return;
#endif
if( pFd->pMapRegion ){
osMunmap(pFd->pMapRegion, pFd->mmapSizeActual);
pFd->pMapRegion = 0;
@@ -5145,6 +5201,28 @@ static int unixMapfile(unixFile *pFd, i64 nMap){
nMap = pFd->mmapSizeMax;
}
#ifdef SQLITE_SHARED_MAPPING
if( pFd->pInode ){
unixInodeInfo *pInode = pFd->pInode;
if( pFd->pMapRegion ) return SQLITE_OK;
unixEnterMutex();
if( pInode->pSharedMapping==0 ){
u8 *pNew = osMmap(0, nMap, PROT_READ, MAP_SHARED, pFd->h, 0);
if( pNew==MAP_FAILED ){
unixLogError(SQLITE_OK, "mmap", pFd->zPath);
pFd->mmapSizeMax = 0;
}else{
pInode->pSharedMapping = pNew;
pInode->nSharedMapping = nMap;
}
}
pFd->pMapRegion = pInode->pSharedMapping;
pFd->mmapSizeActual = pFd->mmapSize = pInode->nSharedMapping;
unixLeaveMutex();
return SQLITE_OK;
}
#endif
assert( nMap>0 || (pFd->mmapSize==0 && pFd->pMapRegion==0) );
if( nMap!=pFd->mmapSize ){
unixRemapfile(pFd, nMap);
@@ -5582,6 +5660,9 @@ static int fillInUnixFile(
if( pLockingStyle == &posixIoMethods
#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE
|| pLockingStyle == &nfsIoMethods
#endif
#ifdef SQLITE_SHARED_MAPPING
|| pLockingStyle == &nolockIoMethods
#endif
){
unixEnterMutex();
+198 -24
View File
@@ -656,6 +656,9 @@ struct Pager {
u32 cksumInit; /* Quasi-random value added to every checksum */
u32 nSubRec; /* Number of records written to sub-journal */
Bitvec *pInJournal; /* One bit for each page in the database file */
#ifndef SQLITE_OMIT_CONCURRENT
Bitvec *pAllRead; /* Pages read within current CONCURRENT trans. */
#endif
sqlite3_file *fd; /* File descriptor for database */
sqlite3_file *jfd; /* File descriptor for main journal */
sqlite3_file *sjfd; /* File descriptor for sub-journal */
@@ -912,7 +915,9 @@ static int assert_pager_state(Pager *p){
if( !pagerUseWal(pPager) ){
assert( p->eLock>=RESERVED_LOCK );
}
assert( pPager->dbSize==pPager->dbOrigSize );
#ifndef SQLITE_OMIT_CONCURRENT
assert( pPager->dbSize==pPager->dbOrigSize || pPager->pAllRead );
#endif
assert( pPager->dbOrigSize==pPager->dbFileSize );
assert( pPager->dbOrigSize==pPager->dbHintSize );
assert( pPager->setSuper==0 );
@@ -1794,6 +1799,53 @@ static int addToSavepointBitvecs(Pager *pPager, Pgno pgno){
return rc;
}
#ifndef SQLITE_OMIT_CONCURRENT
/*
** If they are not already, begin recording all pages read from the pager layer
** by the b-tree layer This is used by concurrent transactions. Return
** SQLITE_OK if successful, or an SQLite error code (SQLITE_NOMEM) if an error
** occurs.
*/
int sqlite3PagerBeginConcurrent(Pager *pPager){
int rc = SQLITE_OK;
if( pPager->pAllRead==0 ){
pPager->pAllRead = sqlite3BitvecCreate(pPager->dbSize);
pPager->dbOrigSize = pPager->dbSize;
if( pPager->pAllRead==0 ){
rc = SQLITE_NOMEM;
}
}
return rc;
}
/* !defined(SQLITE_OMIT_CONCURRENT)
**
** Stop recording all pages read from the pager layer by the b-tree layer
** and discard any current records.
*/
void sqlite3PagerEndConcurrent(Pager *pPager){
sqlite3BitvecDestroy(pPager->pAllRead);
pPager->pAllRead = 0;
}
/* !defined(SQLITE_OMIT_CONCURRENT)
**
** Return true if the database is in wal mode. False otherwise.
*/
int sqlite3PagerIsWal(Pager *pPager){
return pPager->pWal!=0;
}
#endif /* SQLITE_OMIT_CONCURRENT */
/*
** Free the Pager.pInJournal and Pager.pAllRead bitvec objects.
*/
static void pagerFreeBitvecs(Pager *pPager){
sqlite3BitvecDestroy(pPager->pInJournal);
pPager->pInJournal = 0;
sqlite3PagerEndConcurrent(pPager);
}
/*
** This function is a no-op if the pager is in exclusive mode and not
** in the ERROR state. Otherwise, it switches the pager to PAGER_OPEN
@@ -1818,8 +1870,7 @@ static void pager_unlock(Pager *pPager){
|| pPager->eState==PAGER_ERROR
);
sqlite3BitvecDestroy(pPager->pInJournal);
pPager->pInJournal = 0;
pagerFreeBitvecs(pPager);
releaseAllSavepoints(pPager);
if( pagerUseWal(pPager) ){
@@ -2086,8 +2137,7 @@ static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){
}
#endif
sqlite3BitvecDestroy(pPager->pInJournal);
pPager->pInJournal = 0;
pagerFreeBitvecs(pPager);
pPager->nRec = 0;
if( rc==SQLITE_OK ){
if( MEMDB || pagerFlushOnCommit(pPager, bCommit) ){
@@ -3083,8 +3133,24 @@ static int pagerRollbackWal(Pager *pPager){
** + Reload page content from the database (if refcount>0).
*/
pPager->dbSize = pPager->dbOrigSize;
rc = sqlite3WalUndo(pPager->pWal, pagerUndoCallback, (void *)pPager);
rc = sqlite3WalUndo(pPager->pWal, pagerUndoCallback, (void *)pPager,
#ifdef SQLITE_OMIT_CONCURRENT
0
#else
pPager->pAllRead!=0
#endif
);
pList = sqlite3PcacheDirtyList(pPager->pPCache);
#ifndef SQLITE_OMIT_CONCURRENT
/* If this is an CONCURRENT transaction, then page 1 must be reread from
** the db file, even if it is not dirty. This is because the b-tree layer
** may have already zeroed the nFree and iTrunk header fields. */
if( rc==SQLITE_OK && (pList==0 || pList->pgno!=1) && pPager->pAllRead ){
rc = pagerUndoCallback((void*)pPager, 1);
}
#endif
while( pList && rc==SQLITE_OK ){
PgHdr *pNext = pList->pDirty;
rc = pagerUndoCallback((void *)pPager, pList->pgno);
@@ -3134,6 +3200,8 @@ static int pagerWalFrames(
if( p->pgno<=nTruncate ){
ppNext = &p->pDirty;
nList++;
PAGERTRACE(("TO-WAL %d page %d hash(%08x)\n",
PAGERID(pPager), p->pgno, pager_pagehash(p)));
}
}
assert( pList );
@@ -4197,7 +4265,7 @@ static int syncJournal(Pager *pPager, int newHdr){
assert( assert_pager_state(pPager) );
assert( !pagerUseWal(pPager) );
rc = sqlite3PagerExclusiveLock(pPager);
rc = sqlite3PagerExclusiveLock(pPager, 0, 0);
if( rc!=SQLITE_OK ) return rc;
if( !pPager->noSync ){
@@ -4548,6 +4616,12 @@ static int pagerStress(void *p, PgHdr *pPg){
pPager->aStat[PAGER_STAT_SPILL]++;
pPg->pDirty = 0;
if( pagerUseWal(pPager) ){
#ifndef SQLITE_OMIT_CONCURRENT
/* If the transaction is a "BEGIN CONCURRENT" transaction, the page
** cannot be flushed to disk. Return early in this case. */
if( pPager->pAllRead ) return SQLITE_OK;
#endif
/* Write a single frame for this page to the log. */
rc = subjournalPageIfRequired(pPg);
if( rc==SQLITE_OK ){
@@ -5466,6 +5540,18 @@ static int getPageNormal(
assert( assert_pager_state(pPager) );
assert( pPager->hasHeldSharedLock==1 );
#ifndef SQLITE_OMIT_CONCURRENT
/* If this is an CONCURRENT transaction and the page being read was
** present in the database file when the transaction was opened,
** mark it as read in the pAllRead vector. */
pPg = 0;
if( pPager->pAllRead && pgno<=pPager->dbOrigSize ){
PAGERTRACE(("USING page %d\n", pgno));
rc = sqlite3BitvecSet(pPager->pAllRead, pgno);
if( rc!=SQLITE_OK ) goto pager_acquire_err;
}
#endif
if( pgno==0 ) return SQLITE_CORRUPT_BKPT;
pBase = sqlite3PcacheFetch(pPager->pPCache, pgno, 3);
if( pBase==0 ){
@@ -5803,11 +5889,14 @@ static int pager_open_journal(Pager *pPager){
** Begin a write-transaction on the specified pager object. If a
** write-transaction has already been opened, this function is a no-op.
**
** If the exFlag argument is false, then acquire at least a RESERVED
** lock on the database file. If exFlag is true, then acquire at least
** If the exFlag argument is 0, then acquire at least a RESERVED
** lock on the database file. If exFlag is >0, then acquire at least
** an EXCLUSIVE lock. If such a lock is already held, no locking
** functions need be called.
**
** If (exFlag<0) and the database is in WAL mode, do not take any locks.
** The transaction will run in CONCURRENT mode instead.
**
** If the subjInMemory argument is non-zero, then any sub-journal opened
** within this transaction will be opened as an in-memory file. This
** has no effect if the sub-journal is already opened (as it may be when
@@ -5825,7 +5914,6 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
if( ALWAYS(pPager->eState==PAGER_READER) ){
assert( pPager->pInJournal==0 );
if( pagerUseWal(pPager) ){
/* If the pager is configured to use locking_mode=exclusive, and an
** exclusive lock on the database is not already held, obtain it now.
@@ -5841,9 +5929,10 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
/* Grab the write lock on the log file. If successful, upgrade to
** PAGER_RESERVED state. Otherwise, return an error code to the caller.
** The busy-handler is not invoked if another connection already
** holds the write-lock. If possible, the upper layer will call it.
*/
rc = sqlite3WalBeginWriteTransaction(pPager->pWal);
** holds the write-lock. If possible, the upper layer will call it. */
if( exFlag>=0 ){
rc = sqlite3WalBeginWriteTransaction(pPager->pWal);
}
}else{
/* Obtain a RESERVED lock on the database file. If the exFlag parameter
** is true, then immediately upgrade this to an EXCLUSIVE lock. The
@@ -5851,7 +5940,7 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
** lock, but not when obtaining the RESERVED lock.
*/
rc = pagerLockDb(pPager, RESERVED_LOCK);
if( rc==SQLITE_OK && exFlag ){
if( rc==SQLITE_OK && exFlag>0 ){
rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
}
}
@@ -6151,7 +6240,7 @@ int sqlite3PagerWrite(PgHdr *pPg){
** to sqlite3PagerWrite(). In other words, return TRUE if it is ok
** to change the content of the page.
*/
#ifndef NDEBUG
#if !defined(SQLITE_OMIT_CONCURRENT) || !defined(NDEBUG)
int sqlite3PagerIswriteable(DbPage *pPg){
return pPg->flags & PGHDR_WRITEABLE;
}
@@ -6307,17 +6396,26 @@ int sqlite3PagerSync(Pager *pPager, const char *zSuper){
}
/*
** This function may only be called while a write-transaction is active in
** rollback. If the connection is in WAL mode, this call is a no-op.
** Otherwise, if the connection does not already have an EXCLUSIVE lock on
** the database file, an attempt is made to obtain one.
** This function is called to ensure that all locks required to commit the
** current write-transaction to the database file are held. If the db is
** in rollback mode, this means the EXCLUSIVE lock on the database file.
**
** If the EXCLUSIVE lock is already held or the attempt to obtain it is
** successful, or the connection is in WAL mode, SQLITE_OK is returned.
** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is
** returned.
** Or, if this is a non-CONCURRENT transaction on a wal-mode database, this
** function is a no-op.
**
** If this is an CONCURRENT transaction on a wal-mode database, this function
** attempts to obtain the WRITER lock on the wal file and also checks to
** see that the transaction can be safely committed (does not commit with
** any other transaction committed since it was opened).
**
** If the required locks are already held or successfully obtained and
** the transaction can be committed, SQLITE_OK is returned. If a required lock
** cannot be obtained, SQLITE_BUSY is returned. Or, if the current transaction
** is CONCURRENT and cannot be committed due to a conflict, SQLITE_BUSY_SNAPSHOT
** is returned. Otherwise, if some other error occurs (IO error, OOM etc.),
** and SQLite error code is returned.
*/
int sqlite3PagerExclusiveLock(Pager *pPager){
int sqlite3PagerExclusiveLock(Pager *pPager, PgHdr *pPage1, Pgno *piConflict){
int rc = pPager->errCode;
assert( assert_pager_state(pPager) );
if( rc==SQLITE_OK ){
@@ -6329,10 +6427,77 @@ int sqlite3PagerExclusiveLock(Pager *pPager){
if( 0==pagerUseWal(pPager) ){
rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
}
#ifndef SQLITE_OMIT_CONCURRENT
else{
if( pPager->pAllRead ){
/* This is an CONCURRENT transaction. Attempt to lock the wal database
** here. If SQLITE_BUSY (but not SQLITE_BUSY_SNAPSHOT) is returned,
** invoke the busy-handler and try again for as long as it returns
** non-zero. */
do {
rc = sqlite3WalLockForCommit(
pPager->pWal, pPage1, pPager->pAllRead, piConflict
);
}while( rc==SQLITE_BUSY
&& pPager->xBusyHandler(pPager->pBusyHandlerArg)
);
}
}
#endif /* SQLITE_OMIT_CONCURRENT */
}
return rc;
}
#ifndef SQLITE_OMIT_CONCURRENT
void sqlite3PagerScanFailure(Btree *pBt, Pager *pPager){
sqlite3BtreeScanDirty(pBt, pPager->pAllRead, 0);
}
/*
** This function is called as part of committing an CONCURRENT transaction.
** At this point the wal WRITER lock is held, and all pages in the cache
** except for page 1 are compatible with the snapshot at the head of the
** wal file.
**
** This function updates the in-memory data structures and reloads the
** contents of page 1 so that the client is operating on the snapshot
** at the head of the wal file.
**
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
*/
int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage *pPage1){
int rc;
assert( pPager->pWal && pPager->pAllRead );
rc = sqlite3WalUpgradeSnapshot(pPager->pWal);
if( rc==SQLITE_OK ){
rc = readDbPage(pPage1);
}
return rc;
}
/* !defined(SQLITE_OMIT_CONCURRENT)
**
** Set the in-memory cache of the database file size to nSz pages.
*/
void sqlite3PagerSetDbsize(Pager *pPager, Pgno nSz){
pPager->dbSize = nSz;
}
/* !defined(SQLITE_OMIT_CONCURRENT)
**
** If this is a WAL mode connection and the WRITER lock is currently held,
** relinquish it.
*/
void sqlite3PagerDropExclusiveLock(Pager *pPager){
if( pagerUseWal(pPager) ){
sqlite3WalEndWriteTransaction(pPager->pWal);
}
}
#endif /* SQLITE_OMIT_CONCURRENT */
/*
** Sync the database file for the pager pPager. zSuper points to the name
** of a super-journal file that should be written into the individual
@@ -6361,6 +6526,7 @@ int sqlite3PagerExclusiveLock(Pager *pPager){
*/
int sqlite3PagerCommitPhaseOne(
Pager *pPager, /* Pager object */
Btree *pBtree,
const char *zSuper, /* If not NULL, the super-journal name */
int noSync /* True to omit the xSync on the db file */
){
@@ -6408,6 +6574,9 @@ int sqlite3PagerCommitPhaseOne(
if( ALWAYS(pList) ){
rc = pagerWalFrames(pPager, pList, pPager->dbSize, 1);
}
if( rc==SQLITE_OK ){
rc = sqlite3BtreeScanDirty(pBtree, pPager->pAllRead, pList);
}
sqlite3PagerUnref(pPageOne);
if( rc==SQLITE_OK ){
sqlite3PcacheCleanAll(pPager->pPCache);
@@ -7673,6 +7842,11 @@ void sqlite3PagerSnapshotUnlock(Pager *pPager){
}
#endif /* SQLITE_ENABLE_SNAPSHOT */
int sqlite3PagerWalInfo(Pager *pPager, u32 *pnPrior, u32 *pnFrame){
return sqlite3WalInfo(pPager->pWal, pnPrior, pnFrame);
}
#endif /* !SQLITE_OMIT_WAL */
#ifdef SQLITE_ENABLE_ZIPVFS
+23 -3
View File
@@ -42,6 +42,8 @@ typedef struct Pager Pager;
*/
typedef struct PgHdr DbPage;
typedef struct Btree Btree;
/*
** Page number PAGER_MJ_PGNO is never used in an SQLite database (it is
** reserved for working around a windows/posix incompatibility). It is
@@ -161,8 +163,8 @@ void *sqlite3PagerGetExtra(DbPage *);
/* Functions used to manage pager transactions and savepoints. */
void sqlite3PagerPagecount(Pager*, int*);
int sqlite3PagerBegin(Pager*, int exFlag, int);
int sqlite3PagerCommitPhaseOne(Pager*,const char *zSuper, int);
int sqlite3PagerExclusiveLock(Pager*);
int sqlite3PagerCommitPhaseOne(Pager*,Btree*,const char *zSuper, int);
int sqlite3PagerExclusiveLock(Pager*, DbPage *pPage1, Pgno*);
int sqlite3PagerSync(Pager *pPager, const char *zSuper);
int sqlite3PagerCommitPhaseTwo(Pager*);
int sqlite3PagerRollback(Pager*);
@@ -170,6 +172,7 @@ int sqlite3PagerOpenSavepoint(Pager *pPager, int n);
int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
int sqlite3PagerSharedLock(Pager *pPager);
#ifndef SQLITE_OMIT_WAL
int sqlite3PagerCheckpoint(Pager *pPager, sqlite3*, int, int*, int*);
int sqlite3PagerWalSupported(Pager *pPager);
@@ -224,10 +227,27 @@ void sqlite3PagerTruncateImage(Pager*,Pgno);
void sqlite3PagerRekey(DbPage*, Pgno, u16);
#ifndef SQLITE_OMIT_CONCURRENT
void sqlite3PagerEndConcurrent(Pager*);
int sqlite3PagerBeginConcurrent(Pager*);
void sqlite3PagerDropExclusiveLock(Pager*);
int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage*);
void sqlite3PagerSetDbsize(Pager *pPager, Pgno);
int sqlite3PagerIsWal(Pager*);
void sqlite3PagerScanFailure(Btree *pBt, Pager *pPager);
#else
# define sqlite3PagerEndConcurrent(x)
#endif
#if defined(SQLITE_DEBUG) || !defined(SQLITE_OMIT_CONCURRENT)
int sqlite3PagerIswriteable(DbPage*);
#endif
int sqlite3PagerWalInfo(Pager*, u32 *pnPrior, u32 *pnFrame);
/* Functions to support testing and debugging. */
#if !defined(NDEBUG) || defined(SQLITE_TEST)
Pgno sqlite3PagerPagenumber(DbPage*);
int sqlite3PagerIswriteable(DbPage*);
#endif
#ifdef SQLITE_TEST
int *sqlite3PagerStats(Pager*);
+19 -3
View File
@@ -99,6 +99,13 @@
*/
struct TrigEvent { int a; IdList * b; };
/*
** Generate a syntax error
*/
static void parserSyntaxError(Parse *pParse, Token *p){
sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", p);
}
struct FrameBound { int eType; Expr *pExpr; };
/*
@@ -158,7 +165,16 @@ trans_opt ::= TRANSACTION nm.
transtype(A) ::= . {A = TK_DEFERRED;}
transtype(A) ::= DEFERRED(X). {A = @X; /*A-overwrites-X*/}
transtype(A) ::= IMMEDIATE(X). {A = @X; /*A-overwrites-X*/}
transtype(A) ::= EXCLUSIVE(X). {A = @X; /*A-overwrites-X*/}
transtype(A) ::= ID(X). {
Token *p = &X;
if( p->n==9 && sqlite3_strnicmp(p->z,"exclusive",9)==0 ){
A = TK_EXCLUSIVE;
}else if( p->n==10 && sqlite3_strnicmp(p->z,"concurrent",10)==0 ){
A = TK_CONCURRENT; /*A-overwrites-X*/
}else{
parserSyntaxError(pParse, p);
}
}
cmd ::= COMMIT|END(X) trans_opt. {sqlite3EndTransaction(pParse,@X);}
cmd ::= ROLLBACK(X) trans_opt. {sqlite3EndTransaction(pParse,@X);}
@@ -278,7 +294,6 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);}
//
%token_class id ID|INDEXED.
// And "ids" is an identifer-or-string.
//
%token_class ids ID|STRING.
@@ -1066,7 +1081,7 @@ expr(A) ::= VARIABLE(X). {
Token t = X; /*A-overwrites-X*/
assert( t.n>=2 );
if( pParse->nested==0 ){
sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t);
parserSyntaxError(pParse, &t);
A = 0;
}else{
A = sqlite3PExpr(pParse, TK_REGISTER, 0, 0);
@@ -1806,6 +1821,7 @@ filter_clause(A) ::= FILTER LP WHERE expr(X) RP. { A = X; }
UPLUS /* Unary plus */
TRUTH /* IS TRUE or IS FALSE or IS NOT TRUE or IS NOT FALSE */
REGISTER /* Reference to a VDBE register */
CONCURRENT /* BEGIN CONCURRENT */
VECTOR /* Vector */
SELECT_COLUMN /* Choose a single column from a multi-column SELECT */
IF_NULL_ROW /* the if-null-row operator */
+9
View File
@@ -434,6 +434,15 @@ static const PragmaName aPragmaName[] = {
/* iArg: */ 0 },
#endif
#endif
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
#if defined(SQLITE_ENABLE_NOOP_UPDATE)
{/* zName: */ "noop_update",
/* ePragTyp: */ PragTyp_FLAG,
/* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
/* ColNames: */ 0, 0,
/* iArg: */ SQLITE_NoopUpdate },
#endif
#endif
{/* zName: */ "optimize",
/* ePragTyp: */ PragTyp_OPTIMIZE,
+22
View File
@@ -106,6 +106,28 @@ void sqlite3_randomness(int N, void *pBuf){
sqlite3_mutex_leave(mutex);
}
/*
** Initialize a fast PRNG. A Fast PRNG is called "fast" because it does
** not need a mutex to operate, though it does use a mutex to initialize.
** The quality of the randomness is not as good as the global PRNG.
*/
void sqlite3FastPrngInit(FastPrng *pPrng){
sqlite3_randomness(sizeof(*pPrng), pPrng);
pPrng->x |= 1;
}
/*
** Generate N bytes of pseudo-randomness using a FastPrng
*/
void sqlite3FastRandomness(FastPrng *pPrng, int N, void *P){
unsigned char *pOut = (unsigned char*)P;
while( N-->0 ){
pPrng->x = ((pPrng->x)>>1) ^ ((1+~((pPrng->x)&1)) & 0xd0000001);
pPrng->y = (pPrng->y)*1103515245 + 12345;
*(pOut++) = (pPrng->x ^ pPrng->y) & 0xff;
}
}
#ifndef SQLITE_UNTESTABLE
/*
** For testing purposes, we sometimes want to preserve the state of
+1 -1
View File
@@ -2030,7 +2030,7 @@ int sqlite3ColumnsFromExprList(
if( zName[j]==':' ) nName = j;
}
zName = sqlite3MPrintf(db, "%.*z:%u", nName, zName, ++cnt);
if( cnt>3 ) sqlite3_randomness(sizeof(cnt), &cnt);
if( cnt>3 ) sqlite3FastRandomness(&db->sPrng, sizeof(cnt), &cnt);
}
pCol->zName = zName;
pCol->hName = sqlite3StrIHash(zName);
+28
View File
@@ -9679,6 +9679,31 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
*/
SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
/*
** CAPI3REF: Wal related information regarding the most recent COMMIT
** EXPERIMENTAL
**
** This function reports on the state of the wal file (if any) for database
** zDb, which should be "main", "temp", or the name of the attached database.
** Its results - the values written to the output parameters - are only
** defined if the most recent SQL command on the connection was a successful
** COMMIT that wrote data to wal-mode database zDb.
**
** Assuming the above conditions are met, output parameter (*pnFrame) is set
** to the total number of frames in the wal file. Parameter (*pnPrior) is
** set to the number of frames that were present in the wal file before the
** most recent transaction was committed. So that the number of frames written
** by the most recent transaction is (*pnFrame)-(*pnPrior).
**
** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code. It
** is not an error if this function is called at a time when the results
** are undefined.
*/
SQLITE_EXPERIMENTAL int sqlite3_wal_info(
sqlite3 *db, const char *zDb,
unsigned int *pnPrior, unsigned int *pnFrame
);
/*
** CAPI3REF: Serialize a database
**
@@ -9801,6 +9826,9 @@ int sqlite3_deserialize(
#define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */
#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */
const char *sqlite3_begin_concurrent_report(sqlite3*);
void sqlite3_begin_concurrent_report_enable(sqlite3 *db, int bEnable);
/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
+59
View File
@@ -1115,6 +1115,7 @@ typedef struct Db Db;
typedef struct Schema Schema;
typedef struct Expr Expr;
typedef struct ExprList ExprList;
typedef struct FastPrng FastPrng;
typedef struct FKey FKey;
typedef struct FuncDestructor FuncDestructor;
typedef struct FuncDef FuncDef;
@@ -1233,6 +1234,14 @@ typedef int VList;
# define SQLITE_DEFAULT_WAL_SYNCHRONOUS SQLITE_DEFAULT_SYNCHRONOUS
#endif
/*
** State of a simple PRNG used for the per-connection and per-pager
** pseudo-random number generators.
*/
struct FastPrng {
unsigned int x, y;
};
/*
** Each database file to be accessed by the system is an instance
** of the following structure. There are normally two of these structures
@@ -1450,6 +1459,36 @@ void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**);
#endif /* SQLITE_OMIT_DEPRECATED */
#define SQLITE_TRACE_NONLEGACY_MASK 0x0f /* Normal flags */
#define CURSORSCAN_WRITE 0x0001
#define CURSORSCAN_MINVALID 0x0002
#define CURSORSCAN_MAXVALID 0x0004
#define CURSORSCAN_REVERSE 0x0008
#define CURSORSCAN_MININCL 0x0010
#define CURSORSCAN_MAXINCL 0x0020
#define CURSORSCAN_LIMITVALID 0x0040
#define CURSORSCAN_LIMITINCL 0x0080
#define CURSORSCAN_LIMITMAX 0x0100
#define CURSORSCAN_OOM 0x0200
#define CURSORSCAN_PGWRITE 0x0400
#define CURSORSCAN_PGFAIL 0x0800
typedef struct CursorScan CursorScan;
struct CursorScan {
int tnum; /* Root page of scanned b-tree */
int flags; /* Mask of CURSORSCAN_* flags */
i64 iMin;
i64 iMax;
i64 iLimit;
u8 *aMin;
u8 *aMax;
u8 *aLimit;
int nRef; /* Number of pointers to this structure */
KeyInfo *pKeyInfo; /* KeyInfo structure for indexes */
CursorScan *pNext; /* Next CursorScan object in list */
};
/*
** Each database connection is an instance of the following structure.
@@ -1473,6 +1512,10 @@ struct sqlite3 {
u16 dbOptFlags; /* Flags to enable/disable optimizations */
u8 enc; /* Text encoding */
u8 autoCommit; /* The auto-commit flag. */
u8 eConcurrent; /* CONCURRENT_* value */
u8 bConcurrentReport; /* Concurrent transaction reports enabled */
CursorScan *pCScanList;
char *zBCReport;
u8 temp_store; /* 1: file 2: memory 0: default */
u8 mallocFailed; /* True if we have seen a malloc failure */
u8 bBenignMalloc; /* Do not require OOMs if true */
@@ -1486,6 +1529,7 @@ struct sqlite3 {
u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */
int nextPagesize; /* Pagesize after VACUUM if >0 */
u32 magic; /* Magic number for detect library misuse */
FastPrng sPrng; /* State of the per-connection PRNG */
int nChange; /* Value returned by sqlite3_changes() */
int nTotalChange; /* Value returned by sqlite3_total_changes() */
int aLimit[SQLITE_N_LIMIT]; /* Limits */
@@ -1592,6 +1636,13 @@ struct sqlite3 {
#endif
};
/*
** Candidate values for sqlite3.eConcurrent
*/
#define CONCURRENT_NONE 0
#define CONCURRENT_OPEN 1
#define CONCURRENT_SCHEMA 2
/*
** A macro to discover the encoding of a database.
*/
@@ -1651,6 +1702,9 @@ struct sqlite3 {
/* DELETE, or UPDATE and return */
/* the count using a callback. */
/* Flags used by the Pragma noop_update enhancement */
#define SQLITE_NoopUpdate HI(0x0001000) /* UPDATE operations are no-ops */
/* Flags used only if debugging */
#ifdef SQLITE_DEBUG
#define SQLITE_SqlTrace HI(0x0100000) /* Debug print SQL as it executes */
@@ -4212,6 +4266,7 @@ int sqlite3BitvecSet(Bitvec*, u32);
void sqlite3BitvecClear(Bitvec*, u32, void*);
void sqlite3BitvecDestroy(Bitvec*);
u32 sqlite3BitvecSize(Bitvec*);
int sqlite3BitvecArray(Bitvec*, u32 **pa, int *pn);
#ifndef SQLITE_UNTESTABLE
int sqlite3BitvecBuiltinTest(int,int*);
#endif
@@ -4342,6 +4397,8 @@ Vdbe *sqlite3GetVdbe(Parse*);
void sqlite3PrngSaveState(void);
void sqlite3PrngRestoreState(void);
#endif
void sqlite3FastPrngInit(FastPrng*);
void sqlite3FastRandomness(FastPrng*, int N, void *P);
void sqlite3RollbackAll(sqlite3*,int);
void sqlite3CodeVerifySchema(Parse*, int);
void sqlite3CodeVerifyNamedSchema(Parse*, const char *zDb);
@@ -5004,4 +5061,6 @@ void sqlite3VectorErrorMsg(Parse*, Expr*);
const char **sqlite3CompileOptions(int *pnOpt);
#endif
void sqlite3BtreeScanDerefList(CursorScan*);
#endif /* SQLITEINT_H */
+87 -2
View File
@@ -7773,6 +7773,41 @@ static int SQLITE_TCLAPI test_dbconfig_maindbname_icecube(
}
}
/*
** Usage: sqlite3_wal_info DB DBNAME
*/
static int SQLITE_TCLAPI test_wal_info(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
int rc;
sqlite3 *db;
char *zName;
unsigned int nPrior;
unsigned int nFrame;
if( objc!=3 ){
Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
return TCL_ERROR;
}
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
zName = Tcl_GetString(objv[2]);
rc = sqlite3_wal_info(db, zName, &nPrior, &nFrame);
if( rc!=SQLITE_OK ){
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
return TCL_ERROR;
}else{
Tcl_Obj *pNew = Tcl_NewObj();
Tcl_ListObjAppendElement(interp, pNew, Tcl_NewWideIntObj((i64)nPrior));
Tcl_ListObjAppendElement(interp, pNew, Tcl_NewWideIntObj((i64)nFrame));
Tcl_SetObjResult(interp, pNew);
}
return TCL_OK;
}
/*
** Usage: sqlite3_mmap_warm DB DBNAME
*/
@@ -7802,6 +7837,53 @@ static int SQLITE_TCLAPI test_mmap_warm(
}
}
/*
** Usage: sqlite3_begin_concurrent_report DB
*/
static int SQLITE_TCLAPI test_begin_concurrent_report(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
const char *zReport = 0;
sqlite3 *db;
if( objc!=2 ){
Tcl_WrongNumArgs(interp, 1, objv, "DB");
return TCL_ERROR;
}
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
zReport = sqlite3_begin_concurrent_report(db);
Tcl_SetObjResult(interp, Tcl_NewStringObj(zReport?zReport:"", -1));
return TCL_OK;
}
/*
** Usage: sqlite3_begin_concurrent_report_enable DB ENABLE
*/
static int SQLITE_TCLAPI test_begin_concurrent_report_enable(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
sqlite3 *db;
int iVal;
if( objc!=3 ){
Tcl_WrongNumArgs(interp, 1, objv, "DB ENABLE");
return TCL_ERROR;
}
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
if( Tcl_GetIntFromObj(interp, objv[2], &iVal) ) return TCL_ERROR;
sqlite3_begin_concurrent_report_enable(db, iVal);
Tcl_ResetResult(interp);
return TCL_OK;
}
/*
** Usage: decode_hexdb TEXT
**
@@ -8161,11 +8243,14 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
{ "sqlite3_snapshot_open_blob", test_snapshot_open_blob, 0 },
{ "sqlite3_snapshot_cmp_blob", test_snapshot_cmp_blob, 0 },
#endif
{ "sqlite3_delete_database", test_delete_database, 0 },
{ "atomic_batch_write", test_atomic_batch_write, 0 },
{ "sqlite3_delete_database", test_delete_database, 0 },
{ "sqlite3_wal_info", test_wal_info, 0 },
{ "atomic_batch_write", test_atomic_batch_write, 0 },
{ "sqlite3_mmap_warm", test_mmap_warm, 0 },
{ "sqlite3_config_sorterref", test_config_sorterref, 0 },
{ "decode_hexdb", test_decode_hexdb, 0 },
{ "sqlite3_begin_concurrent_report", test_begin_concurrent_report, 0 },
{ "sqlite3_begin_concurrent_report_enable", test_begin_concurrent_report_enable, 0 },
};
static int bitmask_size = sizeof(Bitmask)*8;
static int longdouble_size = sizeof(LONGDOUBLE_TYPE);
+1 -1
View File
@@ -147,7 +147,7 @@ static int SQLITE_TCLAPI pager_commit(
return TCL_ERROR;
}
pPager = sqlite3TestTextToPtr(argv[1]);
rc = sqlite3PagerCommitPhaseOne(pPager, 0, 0);
rc = sqlite3PagerCommitPhaseOne(pPager, 0, 0, 0);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
return TCL_ERROR;
+6
View File
@@ -662,6 +662,12 @@ Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY);
Tcl_SetVar2(interp, "sqlite_options", "truncate_opt", "1", TCL_GLOBAL_ONLY);
#endif
#ifndef SQLITE_OMIT_CONCURRENT
Tcl_SetVar2(interp, "sqlite_options", "concurrent", "1", TCL_GLOBAL_ONLY);
#else
Tcl_SetVar2(interp, "sqlite_options", "concurrent", "0", TCL_GLOBAL_ONLY);
#endif
#ifdef SQLITE_OMIT_UTF16
Tcl_SetVar2(interp, "sqlite_options", "utf16", "0", TCL_GLOBAL_ONLY);
#else
+11
View File
@@ -447,6 +447,17 @@ void sqlite3Update(
*/
chngRowid = chngPk = 0;
for(i=0; i<pChanges->nExpr; i++){
#if defined(SQLITE_ENABLE_NOOP_UPDATE) && !defined(SQLITE_OMIT_FLAG_PRAGMAS)
if( db->flags & SQLITE_NoopUpdate ){
Token x;
sqlite3ExprDelete(db, pChanges->a[i].pExpr);
x.z = pChanges->a[i].zEName;
x.n = sqlite3Strlen30(x.z);
pChanges->a[i].pExpr =
sqlite3PExpr(pParse, TK_UPLUS, sqlite3ExprAlloc(db, TK_ID, &x, 0), 0);
if( db->mallocFailed ) goto update_cleanup;
}
#endif
/* If this is an UPDATE with a FROM clause, do not resolve expressions
** here. The call to sqlite3Select() below will do that. */
if( nChangeFrom==0 && sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){
+1
View File
@@ -387,6 +387,7 @@ end_of_vacuum:
** is closed by the DETACH.
*/
db->autoCommit = 1;
assert( db->eConcurrent==0 );
if( pDb ){
sqlite3BtreeClose(pDb->pBt);
+90 -23
View File
@@ -91,7 +91,7 @@ static void updateMaxBlobsize(Mem *p){
** hook are enabled for database connect DB.
*/
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
# define HAS_UPDATE_HOOK(DB) ((DB)->xPreUpdateCallback||(DB)->xUpdateCallback)
# define HAS_UPDATE_HOOK(DB) ((DB)->xPreUpdateCallback||(DB)->xUpdateCallback||(DB)->eConcurrent)
#else
# define HAS_UPDATE_HOOK(DB) ((DB)->xUpdateCallback)
#endif
@@ -2088,6 +2088,11 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
/* Handle the common case of integer comparison here, as an
** optimization, to avoid a call to sqlite3MemCompare() */
if( (pIn1->flags & pIn3->flags & MEM_Int)!=0 ){
if( pOp->p4type==P4_INT32 ){
sqlite3BtreeScanLimit(
p->apCsr[pOp->p4.i]->uc.pCursor, 0, pIn1->u.i, pOp->opcode
);
}
if( pIn3->u.i > pIn1->u.i ){ res = +1; goto compare_op; }
if( pIn3->u.i < pIn1->u.i ){ res = -1; goto compare_op; }
res = 0;
@@ -2112,8 +2117,8 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
flags3 = (pIn3->flags & ~MEM_TypeMask) | (flags3 & MEM_TypeMask);
}
}
assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 );
res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl);
/* assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 ); */
res = sqlite3MemCompare(pIn3,pIn1,pOp->p4type==P4_COLLSEQ?pOp->p4.pColl:0);
}
compare_op:
/* At this point, res is negative, zero, or positive if reg[P1] is
@@ -3335,6 +3340,7 @@ case OP_Savepoint: {
** is committed.
*/
int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint;
assert( db->eConcurrent==0 || db->isTransactionSavepoint==0 );
if( isTransaction && p1==SAVEPOINT_RELEASE ){
if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){
goto vdbe_return;
@@ -3418,23 +3424,31 @@ case OP_Savepoint: {
break;
}
/* Opcode: AutoCommit P1 P2 * * *
/* Opcode: AutoCommit P1 P2 P3 * *
**
** Set the database auto-commit flag to P1 (1 or 0). If P2 is true, roll
** back any currently active btree transactions. If there are any active
** VMs (apart from this one), then a ROLLBACK fails. A COMMIT fails if
** there are active writing VMs or active VMs that use shared cache.
**
** If P3 is non-zero, then this instruction is being executed as part of
** a "BEGIN CONCURRENT" command.
**
** This instruction causes the VM to halt.
*/
case OP_AutoCommit: {
int desiredAutoCommit;
int iRollback;
int bConcurrent;
int hrc;
desiredAutoCommit = pOp->p1;
iRollback = pOp->p2;
bConcurrent = pOp->p3;
assert( desiredAutoCommit==1 || desiredAutoCommit==0 );
assert( desiredAutoCommit==1 || iRollback==0 );
assert( desiredAutoCommit==0 || bConcurrent==0 );
assert( db->autoCommit==0 || db->eConcurrent==CONCURRENT_NONE );
assert( db->nVdbeActive>0 ); /* At least this one VM is active */
assert( p->bIsReader );
@@ -3443,10 +3457,17 @@ case OP_AutoCommit: {
assert( desiredAutoCommit==1 );
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
db->autoCommit = 1;
}else if( desiredAutoCommit && db->nVdbeWrite>0 ){
/* If this instruction implements a COMMIT and other VMs are writing
** return an error indicating that the other VMs must complete first.
*/
db->eConcurrent = CONCURRENT_NONE;
}else if( desiredAutoCommit
&& (db->nVdbeWrite>0 || (db->eConcurrent && db->nVdbeActive>1)) ){
/* A transaction may only be committed if there are no other active
** writer VMs. If the transaction is CONCURRENT, then it may only be
** committed if there are no active VMs at all (readers or writers).
**
** If this instruction is a COMMIT and the transaction may not be
** committed due to one of the conditions above, return an error
** indicating that other VMs must complete before the COMMIT can
** be processed. */
sqlite3VdbeError(p, "cannot commit transaction - "
"SQL statements in progress");
rc = SQLITE_BUSY;
@@ -3454,14 +3475,22 @@ case OP_AutoCommit: {
}else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){
goto vdbe_return;
}else{
if( desiredAutoCommit==0 ){
sqlite3BtreeScanDerefList(db->pCScanList);
db->pCScanList = 0;
}
db->autoCommit = (u8)desiredAutoCommit;
}
if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){
hrc = sqlite3VdbeHalt(p);
if( (hrc & 0xFF)==SQLITE_BUSY ){
p->pc = (int)(pOp - aOp);
db->autoCommit = (u8)(1-desiredAutoCommit);
p->rc = rc = SQLITE_BUSY;
p->rc = hrc;
rc = SQLITE_BUSY;
goto vdbe_return;
}
assert( bConcurrent==CONCURRENT_NONE || bConcurrent==CONCURRENT_OPEN );
db->eConcurrent = (u8)bConcurrent;
sqlite3CloseSavepoints(db);
if( p->rc==SQLITE_OK ){
rc = SQLITE_DONE;
@@ -3657,6 +3686,17 @@ case OP_SetCookie: {
pDb = &db->aDb[pOp->p1];
assert( pDb->pBt!=0 );
assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) );
#ifndef SQLITE_OMIT_CONCURRENT
if( db->eConcurrent
&& (pOp->p2==BTREE_USER_VERSION || pOp->p2==BTREE_APPLICATION_ID)
){
rc = SQLITE_ERROR;
sqlite3VdbeError(p, "cannot modify %s within CONCURRENT transaction",
pOp->p2==BTREE_USER_VERSION ? "user_version" : "application_id"
);
goto abort_due_to_error;
}
#endif
/* See note about index shifting on OP_ReadCookie */
rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, pOp->p3);
if( pOp->p2==BTREE_SCHEMA_VERSION ){
@@ -3803,6 +3843,11 @@ case OP_OpenWrite:
pX = pDb->pBt;
assert( pX!=0 );
if( pOp->opcode==OP_OpenWrite ){
#ifndef SQLITE_OMIT_CONCURRENT
if( db->eConcurrent==CONCURRENT_OPEN && p2==1 && iDb!=1 ){
db->eConcurrent = CONCURRENT_SCHEMA;
}
#endif
assert( OPFLAG_FORDELETE==BTREE_FORDELETE );
wrFlag = BTREE_WRCSR | (pOp->p5 & OPFLAG_FORDELETE);
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
@@ -4278,6 +4323,7 @@ case OP_SeekGT: { /* jump, in3, group */
}
}
rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, 0, (u64)iKey, 0, &res);
sqlite3BtreeScanStart(pC->uc.pCursor, 0, iKey, pOp->opcode, 0);
pC->movetoTarget = iKey; /* Used by OP_Delete */
if( rc!=SQLITE_OK ){
goto abort_due_to_error;
@@ -4325,6 +4371,7 @@ case OP_SeekGT: { /* jump, in3, group */
#endif
r.eqSeen = 0;
rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, &r, 0, 0, &res);
sqlite3BtreeScanStart(pC->uc.pCursor, &r, 0, pOp->opcode, eqOnly);
if( rc!=SQLITE_OK ){
goto abort_due_to_error;
}
@@ -4682,6 +4729,7 @@ notExistsWithKey:
assert( pCrsr!=0 );
res = 0;
rc = sqlite3BtreeMovetoUnpacked(pCrsr, 0, iKey, 0, &res);
sqlite3BtreeScanStart(pCrsr, 0, iKey, pOp->opcode, 0);
assert( rc==SQLITE_OK || res==0 );
pC->movetoTarget = iKey; /* Used by OP_Delete */
pC->nullRow = 0;
@@ -4932,8 +4980,13 @@ case OP_Insert: {
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
/* Invoke the pre-update hook, if any */
if( pTab ){
if( db->xPreUpdateCallback && !(pOp->p5 & OPFLAG_ISUPDATE) ){
sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey,pOp->p2);
sqlite3BtreeScanWrite(
pC->uc.pCursor, SQLITE_INSERT, pC->movetoTarget, (u8*)pData->z, pData->n
);
if( !(pOp->p5 & OPFLAG_ISUPDATE) && db->xPreUpdateCallback ){
if( db->xPreUpdateCallback ){
sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT,zDb,pTab,x.nKey,pOp->p2);
}
}
if( db->xUpdateCallback==0 || pTab->aCol==0 ){
/* Prevent post-update hook from running in cases when it should not */
@@ -5057,16 +5110,20 @@ case OP_Delete: {
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
/* Invoke the pre-update-hook if required. */
if( db->xPreUpdateCallback && pOp->p4.pTab ){
assert( !(opflags & OPFLAG_ISUPDATE)
|| HasRowid(pTab)==0
|| (aMem[pOp->p3].flags & MEM_Int)
);
sqlite3VdbePreUpdateHook(p, pC,
(opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE,
zDb, pTab, pC->movetoTarget,
pOp->p3
);
if( pOp->p4.pTab ){
rc = sqlite3BtreeScanWrite(pC->uc.pCursor, 0, pC->movetoTarget, 0, 0);
if( rc ) goto abort_due_to_error;
if( db->xPreUpdateCallback ){
assert( !(opflags & OPFLAG_ISUPDATE)
|| HasRowid(pTab)==0
|| (aMem[pOp->p3].flags & MEM_Int)
);
sqlite3VdbePreUpdateHook(p, pC,
(opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE,
zDb, pTab, pC->movetoTarget,
pOp->p3
);
}
}
if( opflags & OPFLAG_ISNOOP ) break;
#endif
@@ -5368,6 +5425,7 @@ case OP_Last: { /* jump */
}
}
rc = sqlite3BtreeLast(pCrsr, &res);
sqlite3BtreeScanStart(pCrsr, 0, 0, pOp->opcode, 0);
pC->nullRow = (u8)res;
pC->deferredMoveto = 0;
pC->cacheStatus = CACHE_STALE;
@@ -5471,6 +5529,7 @@ case OP_Rewind: { /* jump */
pCrsr = pC->uc.pCursor;
assert( pCrsr );
rc = sqlite3BtreeFirst(pCrsr, &res);
sqlite3BtreeScanStart(pCrsr, 0, 0, OP_Rewind, 0);
pC->deferredMoveto = 0;
pC->cacheStatus = CACHE_STALE;
}
@@ -5903,6 +5962,7 @@ case OP_IdxGE: { /* jump */
#endif
res = 0; /* Not needed. Only used to silence a warning. */
rc = sqlite3VdbeIdxKeyCompare(db, pC, &r, &res);
sqlite3BtreeScanLimit(pC->uc.pCursor, &r, 0, pOp->opcode);
assert( (OP_IdxLE&1)==(OP_IdxLT&1) && (OP_IdxGE&1)==(OP_IdxGT&1) );
if( (pOp->opcode&1)==(OP_IdxLT&1) ){
assert( pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxLT );
@@ -5913,7 +5973,9 @@ case OP_IdxGE: { /* jump */
}
VdbeBranchTaken(res>0,2);
if( rc ) goto abort_due_to_error;
if( res>0 ) goto jump_to_p2;
if( res>0 ){
goto jump_to_p2;
}
break;
}
@@ -7177,6 +7239,11 @@ case OP_CursorUnlock: {
*/
case OP_TableLock: {
u8 isWriteLock = (u8)pOp->p3;
#ifndef SQLITE_OMIT_CONCURRENT
if( isWriteLock && db->eConcurrent && pOp->p2==1 && pOp->p1!=1 ){
db->eConcurrent = CONCURRENT_SCHEMA;
}
#endif
if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommit) ){
int p1 = pOp->p1;
assert( p1>=0 && p1<db->nDb );
+1
View File
@@ -467,6 +467,7 @@ struct PreUpdate {
VdbeCursor *pCsr; /* Cursor to read old values from */
int op; /* One of SQLITE_INSERT, UPDATE, DELETE */
u8 *aRecord; /* old.* database record */
int nRecord; /* Size of aRecord in bytes */
KeyInfo keyinfo;
UnpackedRecord *pUnpacked; /* Unpacked version of aRecord[] */
UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */
+1
View File
@@ -1855,6 +1855,7 @@ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
goto preupdate_old_out;
}
p->aRecord = aRec;
p->nRecord = nRec;
}
pMem = *ppValue = &p->pUnpacked->aMem[iIdx];
+23 -3
View File
@@ -2686,10 +2686,27 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
assert( i!=1 );
nTrans++;
}
rc = sqlite3PagerExclusiveLock(pPager);
rc = sqlite3BtreeExclusiveLock(pBt);
sqlite3BtreeLeave(pBt);
}
}
#ifndef SQLITE_OMIT_CONCURRENT
if( db->eConcurrent && (rc & 0xFF)==SQLITE_BUSY ){
/* An SQLITE_BUSY or SQLITE_BUSY_SNAPSHOT was encountered while
** attempting to take the WRITER lock on a wal file. Release the
** WRITER locks on all wal files and return early. */
for(i=0; i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
if( sqlite3BtreeIsInTrans(pBt) ){
sqlite3BtreeEnter(pBt);
sqlite3PagerDropExclusiveLock(sqlite3BtreePager(pBt));
sqlite3BtreeLeave(pBt);
}
}
}
#endif
if( rc!=SQLITE_OK ){
return rc;
}
@@ -3086,6 +3103,7 @@ int sqlite3VdbeHalt(Vdbe *p){
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
sqlite3CloseSavepoints(db);
db->autoCommit = 1;
db->eConcurrent = CONCURRENT_NONE;
p->nChange = 0;
}
}
@@ -3121,9 +3139,9 @@ int sqlite3VdbeHalt(Vdbe *p){
** is required. */
rc = vdbeCommit(db, p);
}
if( rc==SQLITE_BUSY && p->readOnly ){
if( (rc & 0xFF)==SQLITE_BUSY && p->readOnly ){
sqlite3VdbeLeave(p);
return SQLITE_BUSY;
return rc;
}else if( rc!=SQLITE_OK ){
p->rc = rc;
sqlite3RollbackAll(db, SQLITE_OK);
@@ -3148,6 +3166,7 @@ int sqlite3VdbeHalt(Vdbe *p){
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
sqlite3CloseSavepoints(db);
db->autoCommit = 1;
db->eConcurrent = CONCURRENT_NONE;
p->nChange = 0;
}
}
@@ -3169,6 +3188,7 @@ int sqlite3VdbeHalt(Vdbe *p){
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
sqlite3CloseSavepoints(db);
db->autoCommit = 1;
db->eConcurrent = CONCURRENT_NONE;
p->nChange = 0;
}
}
+322 -88
View File
@@ -459,8 +459,10 @@ struct Wal {
WalIndexHdr hdr; /* Wal-index header for current transaction */
u32 minFrame; /* Ignore wal frames before this one */
u32 iReCksum; /* On commit, recalculate checksums from here */
u32 nPriorFrame; /* For sqlite3WalInfo() */
const char *zWalName; /* Name of WAL file */
u32 nCkpt; /* Checkpoint sequence counter in the wal-header */
FastPrng sPrng; /* Random number generator */
#ifdef SQLITE_DEBUG
u8 lockError; /* True if a locking error has occurred */
#endif
@@ -852,7 +854,7 @@ static const char *walLockName(int lockIdx){
/*
** Set or release locks on the WAL. Locks are either shared or exclusive.
** A lock cannot be moved directly between shared and exclusive - it must go
** through the unlocked state first.
** through the concurrent state first.
**
** In locking_mode=EXCLUSIVE, all of these routines become no-ops.
*/
@@ -1155,7 +1157,7 @@ static int walIndexRecover(Wal *pWal){
/* Obtain an exclusive lock on all byte in the locking range not already
** locked by the caller. The caller is guaranteed to have locked the
** WAL_WRITE_LOCK byte, and may have also locked the WAL_CKPT_LOCK byte.
** If successful, the same bytes that are locked here are unlocked before
** If successful, the same bytes that are locked here are concurrent before
** this function returns.
*/
assert( pWal->ckptLock==1 || pWal->ckptLock==0 );
@@ -1443,6 +1445,7 @@ int sqlite3WalOpen(
pRet->syncHeader = 1;
pRet->padToSectorBoundary = 1;
pRet->exclusiveMode = (bNoShm ? WAL_HEAPMEMORY_MODE: WAL_NORMAL_MODE);
sqlite3FastPrngInit(&pRet->sPrng);
/* Open file handle on the write-ahead log file. */
flags = (SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_WAL);
@@ -2080,7 +2083,7 @@ static int walCheckpoint(
rc = SQLITE_BUSY;
}else if( eMode>=SQLITE_CHECKPOINT_RESTART ){
u32 salt1;
sqlite3_randomness(4, &salt1);
sqlite3FastRandomness(&pWal->sPrng, 4, &salt1);
assert( pInfo->nBackfill==pWal->hdr.mxFrame );
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(1), WAL_NREADER-1);
if( rc==SQLITE_OK ){
@@ -2196,6 +2199,49 @@ int sqlite3WalClose(
return rc;
}
/*
** Try to copy the wal-index header from shared-memory into (*pHdr). Return
** zero if successful or non-zero otherwise. If the header is corrupted
** (either because the two copies are inconsistent or because the checksum
** values are incorrect), the read fails and non-zero is returned.
*/
static int walIndexLoadHdr(Wal *pWal, WalIndexHdr *pHdr){
u32 aCksum[2]; /* Checksum on the header content */
WalIndexHdr h2; /* Second copy of the header content */
WalIndexHdr volatile *aHdr; /* Header in shared memory */
/* The first page of the wal-index must be mapped at this point. */
assert( pWal->nWiData>0 && pWal->apWiData[0] );
/* Read the header. This might happen concurrently with a write to the
** same area of shared memory on a different CPU in a SMP,
** meaning it is possible that an inconsistent snapshot is read
** from the file. If this happens, return non-zero.
**
** There are two copies of the header at the beginning of the wal-index.
** When reading, read [0] first then [1]. Writes are in the reverse order.
** Memory barriers are used to prevent the compiler or the hardware from
** reordering the reads and writes.
*/
aHdr = walIndexHdr(pWal);
memcpy(pHdr, (void *)&aHdr[0], sizeof(h2));
walShmBarrier(pWal);
memcpy(&h2, (void *)&aHdr[1], sizeof(h2));
if( memcmp(&h2, pHdr, sizeof(h2))!=0 ){
return 1; /* Dirty read */
}
if( h2.isInit==0 ){
return 1; /* Malformed header - probably all zeros */
}
walChecksumBytes(1, (u8*)&h2, sizeof(h2)-sizeof(h2.aCksum), 0, aCksum);
if( aCksum[0]!=h2.aCksum[0] || aCksum[1]!=h2.aCksum[1] ){
return 1; /* Checksum does not match */
}
return 0;
}
/*
** Try to read the wal-index header. Return 0 on success and 1 if
** there is a problem.
@@ -2214,43 +2260,10 @@ int sqlite3WalClose(
** is read successfully and the checksum verified, return zero.
*/
static SQLITE_NO_TSAN int walIndexTryHdr(Wal *pWal, int *pChanged){
u32 aCksum[2]; /* Checksum on the header content */
WalIndexHdr h1, h2; /* Two copies of the header content */
WalIndexHdr volatile *aHdr; /* Header in shared memory */
WalIndexHdr h1; /* Copy of the header content */
/* The first page of the wal-index must be mapped at this point. */
assert( pWal->nWiData>0 && pWal->apWiData[0] );
/* Read the header. This might happen concurrently with a write to the
** same area of shared memory on a different CPU in a SMP,
** meaning it is possible that an inconsistent snapshot is read
** from the file. If this happens, return non-zero.
**
** tag-20200519-1:
** There are two copies of the header at the beginning of the wal-index.
** When reading, read [0] first then [1]. Writes are in the reverse order.
** Memory barriers are used to prevent the compiler or the hardware from
** reordering the reads and writes. TSAN and similar tools can sometimes
** give false-positive warnings about these accesses because the tools do not
** account for the double-read and the memory barrier. The use of mutexes
** here would be problematic as the memory being accessed is potentially
** shared among multiple processes and not all mutex implementions work
** reliably in that environment.
*/
aHdr = walIndexHdr(pWal);
memcpy(&h1, (void *)&aHdr[0], sizeof(h1)); /* Possible TSAN false-positive */
walShmBarrier(pWal);
memcpy(&h2, (void *)&aHdr[1], sizeof(h2));
if( memcmp(&h1, &h2, sizeof(h1))!=0 ){
return 1; /* Dirty read */
}
if( h1.isInit==0 ){
return 1; /* Malformed header - probably all zeros */
}
walChecksumBytes(1, (u8*)&h1, sizeof(h1)-sizeof(h1.aCksum), 0, aCksum);
if( aCksum[0]!=h1.aCksum[0] || aCksum[1]!=h1.aCksum[1] ){
return 1; /* Checksum does not match */
if( walIndexLoadHdr(pWal, &h1) ){
return 1;
}
if( memcmp(&pWal->hdr, &h1, sizeof(WalIndexHdr)) ){
@@ -2945,6 +2958,7 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
testcase( rc==SQLITE_PROTOCOL );
testcase( rc==SQLITE_OK );
pWal->nPriorFrame = pWal->hdr.mxFrame;
#ifdef SQLITE_ENABLE_SNAPSHOT
if( rc==SQLITE_OK ){
if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){
@@ -3050,8 +3064,7 @@ int sqlite3WalFindFrame(
return SQLITE_OK;
}
/* Search the hash table or tables for an entry matching page number
** pgno. Each iteration of the following for() loop searches one
/* Each iteration of the following for() loop searches one
** hash table (each hash table indexes up to HASHTABLE_NPAGE frames).
**
** This code might run concurrently to the code in walIndexAppend()
@@ -3157,6 +3170,35 @@ Pgno sqlite3WalDbsize(Wal *pWal){
return 0;
}
/*
** Take the WRITER lock on the WAL file. Return SQLITE_OK if successful,
** or an SQLite error code otherwise. This routine does not invoke any
** busy-handler callbacks, that is done at a higher level.
*/
static int walWriteLock(Wal *pWal){
int rc;
/* Cannot start a write transaction without first holding a read lock */
assert( pWal->readLock>=0 );
assert( pWal->writeLock==0 );
assert( pWal->iReCksum==0 );
/* If this is a read-only connection, obtaining a write-lock is not
** possible. In this case return SQLITE_READONLY. Otherwise, attempt
** to grab the WRITER lock. Set Wal.writeLock to true and return
** SQLITE_OK if successful, or leave Wal.writeLock clear and return
** an SQLite error code (possibly SQLITE_BUSY) otherwise. */
if( pWal->readOnly ){
rc = SQLITE_READONLY;
}else{
rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1);
if( rc==SQLITE_OK ){
pWal->writeLock = 1;
}
}
return rc;
}
/*
** This function starts a write transaction on the WAL.
@@ -3183,38 +3225,208 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal){
return SQLITE_OK;
}
#endif
/* Cannot start a write transaction without first holding a read
** transaction. */
assert( pWal->readLock>=0 );
assert( pWal->writeLock==0 && pWal->iReCksum==0 );
if( pWal->readOnly ){
return SQLITE_READONLY;
rc = walWriteLock(pWal);
if( rc==SQLITE_OK ){
/* If another connection has written to the database file since the
** time the read transaction on this connection was started, then
** the write is disallowed. Release the WRITER lock and return
** SQLITE_BUSY_SNAPSHOT in this case. */
if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){
walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
pWal->writeLock = 0;
rc = SQLITE_BUSY_SNAPSHOT;
}
}
/* Only one writer allowed at a time. Get the write lock. Return
** SQLITE_BUSY if unable.
*/
rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1);
if( rc ){
return rc;
}
pWal->writeLock = 1;
/* If another connection has written to the database file since the
** time the read transaction on this connection was started, then
** the write is disallowed.
*/
if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){
walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
pWal->writeLock = 0;
rc = SQLITE_BUSY_SNAPSHOT;
}
return rc;
}
/*
** This function is called by a writer that has a read-lock on aReadmark[0]
** (pWal->readLock==0). This function relinquishes that lock and takes a
** lock on a different aReadmark[] slot.
**
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
*/
static int walUpgradeReadlock(Wal *pWal){
int cnt;
int rc;
assert( pWal->writeLock && pWal->readLock==0 );
walUnlockShared(pWal, WAL_READ_LOCK(0));
pWal->readLock = -1;
cnt = 0;
do{
int notUsed;
rc = walTryBeginRead(pWal, &notUsed, 1, ++cnt);
}while( rc==WAL_RETRY );
assert( (rc&0xff)!=SQLITE_BUSY ); /* BUSY not possible when useWal==1 */
testcase( (rc&0xff)==SQLITE_IOERR );
testcase( rc==SQLITE_PROTOCOL );
testcase( rc==SQLITE_OK );
return rc;
}
#ifndef SQLITE_OMIT_CONCURRENT
/*
** This function is only ever called when committing a "BEGIN CONCURRENT"
** transaction. It may be assumed that no frames have been written to
** the wal file. The second parameter is a pointer to the in-memory
** representation of page 1 of the database (which may or may not be
** dirty). The third is a bitvec with a bit set for each page in the
** database file that was read by the current concurrent transaction.
**
** This function performs three tasks:
**
** 1) It obtains the WRITER lock on the wal file,
**
** 2) It checks that there are no conflicts between the current
** transaction and any transactions committed to the wal file since
** it was opened, and
**
** 3) It ejects any non-dirty pages from the page-cache that have been
** written by another client since the CONCURRENT transaction was started
** (so as to avoid ending up with an inconsistent cache after the
** current transaction is committed).
**
** If no error occurs and the caller may proceed with committing the
** transaction, SQLITE_OK is returned. SQLITE_BUSY is returned if the WRITER
** lock cannot be obtained. Or, if the WRITER lock can be obtained but there
** are conflicts with a committed transaction, SQLITE_BUSY_SNAPSHOT. Finally,
** if an error (i.e. an OOM condition or IO error), an SQLite error code
** is returned.
*/
int sqlite3WalLockForCommit(
Wal *pWal,
PgHdr *pPg1,
Bitvec *pAllRead,
Pgno *piConflict
){
int rc = walWriteLock(pWal);
/* If the database has been modified since this transaction was started,
** check if it is still possible to commit. The transaction can be
** committed if:
**
** a) None of the pages in pList have been modified since the
** transaction opened, and
**
** b) The database schema cookie has not been modified since the
** transaction was started.
*/
if( rc==SQLITE_OK ){
WalIndexHdr head;
if( walIndexLoadHdr(pWal, &head) ){
/* This branch is taken if the wal-index header is corrupted. This
** occurs if some other writer has crashed while committing a
** transaction to this database since the current concurrent transaction
** was opened. */
rc = SQLITE_BUSY_SNAPSHOT;
}else if( memcmp(&pWal->hdr, (void*)&head, sizeof(WalIndexHdr))!=0 ){
int iHash;
int iLast = walFramePage(head.mxFrame);
u32 iFirst = pWal->hdr.mxFrame+1; /* First wal frame to check */
if( memcmp(pWal->hdr.aSalt, (u32*)head.aSalt, sizeof(u32)*2) ){
assert( pWal->readLock==0 );
iFirst = 1;
}
if( pPg1==0 ){
/* If pPg1==0, then the current transaction modified the database
** schema. This means it conflicts with all other transactions. */
*piConflict = 1;
rc = SQLITE_BUSY_SNAPSHOT;
}
for(iHash=walFramePage(iFirst); rc==SQLITE_OK && iHash<=iLast; iHash++){
WalHashLoc sLoc;
rc = walHashGet(pWal, iHash, &sLoc);
if( rc==SQLITE_OK ){
u32 i, iMin, iMax;
assert( head.mxFrame>=sLoc.iZero );
iMin = (sLoc.iZero >= iFirst) ? 1 : (iFirst - sLoc.iZero);
iMax = (iHash==0) ? HASHTABLE_NPAGE_ONE : HASHTABLE_NPAGE;
if( iMax>(head.mxFrame-sLoc.iZero) ) iMax = (head.mxFrame-sLoc.iZero);
for(i=iMin; rc==SQLITE_OK && i<=iMax; i++){
PgHdr *pPg;
if( sLoc.aPgno[i]==1 ){
/* Check that the schema cookie has not been modified. If
** it has not, the commit can proceed. */
u8 aNew[4];
u8 *aOld = &((u8*)pPg1->pData)[40];
int sz;
i64 iOffset;
sz = pWal->hdr.szPage;
sz = (sz&0xfe00) + ((sz&0x0001)<<16);
iOffset = walFrameOffset(i+sLoc.iZero, sz) + WAL_FRAME_HDRSIZE+40;
rc = sqlite3OsRead(pWal->pWalFd, aNew, sizeof(aNew), iOffset);
if( rc==SQLITE_OK && memcmp(aOld, aNew, sizeof(aNew)) ){
rc = SQLITE_BUSY_SNAPSHOT;
}
}else if( sqlite3BitvecTestNotNull(pAllRead, sLoc.aPgno[i]) ){
*piConflict = sLoc.aPgno[i];
rc = SQLITE_BUSY_SNAPSHOT;
}else if( (pPg = sqlite3PagerLookup(pPg1->pPager, sLoc.aPgno[i])) ){
/* Page aPgno[i], which is present in the pager cache, has been
** modified since the current CONCURRENT transaction was started.
** However it was not read by the current transaction, so is not
** a conflict. There are two possibilities: (a) the page was
** allocated at the of the file by the current transaction or
** (b) was present in the cache at the start of the transaction.
**
** For case (a), do nothing. This page will be moved within the
** database file by the commit code to avoid the conflict. The
** call to PagerUnref() is to release the reference grabbed by
** the sqlite3PagerLookup() above.
**
** In case (b), drop the page from the cache - otherwise
** following the snapshot upgrade the cache would be inconsistent
** with the database as stored on disk. */
if( sqlite3PagerIswriteable(pPg) ){
sqlite3PagerUnref(pPg);
}else{
sqlite3PcacheDrop(pPg);
}
}
}
}
}
}
}
pWal->nPriorFrame = pWal->hdr.mxFrame;
return rc;
}
/* !defined(SQLITE_OMIT_CONCURRENT)
**
** This function is called as part of committing an CONCURRENT transaction.
** It is assumed that sqlite3WalLockForCommit() has already been successfully
** called and so (a) the WRITER lock is held and (b) it is known that the
** wal-index-header stored in shared memory is not corrupt.
**
** Before returning, this function upgrades the client so that it is
** operating on the database snapshot currently at the head of the wal file
** (even if the CONCURRENT transaction ran against an older snapshot).
**
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
*/
int sqlite3WalUpgradeSnapshot(Wal *pWal){
int rc = SQLITE_OK;
assert( pWal->writeLock );
memcpy(&pWal->hdr, (void*)walIndexHdr(pWal), sizeof(WalIndexHdr));
/* If this client has its read-lock on slot aReadmark[0] and the entire
** wal has not been checkpointed, switch it to a different slot. Otherwise
** any reads performed between now and committing the transaction will
** read from the old snapshot - not the one just upgraded to. */
if( pWal->readLock==0 && pWal->hdr.mxFrame!=walCkptInfo(pWal)->nBackfill ){
rc = walUpgradeReadlock(pWal);
}
return rc;
}
#endif /* SQLITE_OMIT_CONCURRENT */
/*
** End a write transaction. The commit has already been done. This
** routine merely releases the lock.
@@ -3241,9 +3453,14 @@ int sqlite3WalEndWriteTransaction(Wal *pWal){
** Otherwise, if the callback function does not return an error, this
** function returns SQLITE_OK.
*/
int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){
int sqlite3WalUndo(
Wal *pWal,
int (*xUndo)(void *, Pgno),
void *pUndoCtx,
int bConcurrent /* True if this is a CONCURRENT transaction */
){
int rc = SQLITE_OK;
if( ALWAYS(pWal->writeLock) ){
if( pWal->writeLock ){
Pgno iMax = pWal->hdr.mxFrame;
Pgno iFrame;
@@ -3251,6 +3468,13 @@ int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){
** was in before the client began writing to the database.
*/
memcpy(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr));
#ifndef SQLITE_OMIT_CONCURRENT
if( bConcurrent ){
pWal->hdr.aCksum[0]++;
}
#else
UNUSED_PARAMETER(bConcurrent);
#endif
for(iFrame=pWal->hdr.mxFrame+1;
ALWAYS(rc==SQLITE_OK) && iFrame<=iMax;
@@ -3282,7 +3506,6 @@ int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){
** point in the event of a savepoint rollback (via WalSavepointUndo()).
*/
void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){
assert( pWal->writeLock );
aWalData[0] = pWal->hdr.mxFrame;
aWalData[1] = pWal->hdr.aFrameCksum[0];
aWalData[2] = pWal->hdr.aFrameCksum[1];
@@ -3298,7 +3521,7 @@ void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){
int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){
int rc = SQLITE_OK;
assert( pWal->writeLock );
assert( pWal->writeLock || aWalData[0]==pWal->hdr.mxFrame );
assert( aWalData[3]!=pWal->nCkpt || aWalData[0]<=pWal->hdr.mxFrame );
if( aWalData[3]!=pWal->nCkpt ){
@@ -3334,14 +3557,13 @@ int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){
*/
static int walRestartLog(Wal *pWal){
int rc = SQLITE_OK;
int cnt;
if( pWal->readLock==0 ){
volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
assert( pInfo->nBackfill==pWal->hdr.mxFrame );
if( pInfo->nBackfill>0 ){
u32 salt1;
sqlite3_randomness(4, &salt1);
sqlite3FastRandomness(&pWal->sPrng, 4, &salt1);
rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
if( rc==SQLITE_OK ){
/* If all readers are using WAL_READ_LOCK(0) (in other words if no
@@ -3355,21 +3577,21 @@ static int walRestartLog(Wal *pWal){
** to handle if this transaction is rolled back. */
walRestartHdr(pWal, salt1);
walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
pWal->nPriorFrame = 0;
}else if( rc!=SQLITE_BUSY ){
return rc;
}
}
walUnlockShared(pWal, WAL_READ_LOCK(0));
pWal->readLock = -1;
cnt = 0;
do{
int notUsed;
rc = walTryBeginRead(pWal, &notUsed, 1, ++cnt);
}while( rc==WAL_RETRY );
assert( (rc&0xff)!=SQLITE_BUSY ); /* BUSY not possible when useWal==1 */
testcase( (rc&0xff)==SQLITE_IOERR );
testcase( rc==SQLITE_PROTOCOL );
testcase( rc==SQLITE_OK );
/* Regardless of whether or not the wal file was restarted, change the
** read-lock held by this client to a slot other than aReadmark[0].
** Clients with a lock on aReadmark[0] read from the database file
** only - never from the wal file. This means that if a writer holding
** a lock on aReadmark[0] were to commit a transaction but not close the
** read-transaction, subsequent read operations would read directly from
** the database file - ignoring the new pages just appended
** to the wal file. */
rc = walUpgradeReadlock(pWal);
}
return rc;
}
@@ -3553,7 +3775,7 @@ int sqlite3WalFrames(
sqlite3Put4byte(&aWalHdr[4], WAL_MAX_VERSION);
sqlite3Put4byte(&aWalHdr[8], szPage);
sqlite3Put4byte(&aWalHdr[12], pWal->nCkpt);
if( pWal->nCkpt==0 ) sqlite3_randomness(8, pWal->hdr.aSalt);
if( pWal->nCkpt==0 ) sqlite3FastRandomness(&pWal->sPrng, 8, pWal->hdr.aSalt);
memcpy(&aWalHdr[16], pWal->hdr.aSalt, 8);
walChecksumBytes(1, aWalHdr, WAL_HDRSIZE-2*4, 0, aCksum);
sqlite3Put4byte(&aWalHdr[24], aCksum[0]);
@@ -3630,6 +3852,7 @@ int sqlite3WalFrames(
p->flags |= PGHDR_WAL_APPEND;
}
/* Recalculate checksums within the wal file if required. */
if( isCommit && pWal->iReCksum ){
rc = walRewriteChecksums(pWal, iFrame);
@@ -3813,7 +4036,6 @@ int sqlite3WalCheckpoint(
/* Copy data from the log to the database file. */
if( rc==SQLITE_OK ){
if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){
rc = SQLITE_CORRUPT_BKPT;
}else{
@@ -4046,4 +4268,16 @@ sqlite3_file *sqlite3WalFile(Wal *pWal){
return pWal->pWalFd;
}
/*
** Return the values required by sqlite3_wal_info().
*/
int sqlite3WalInfo(Wal *pWal, u32 *pnPrior, u32 *pnFrame){
int rc = SQLITE_OK;
if( pWal ){
*pnFrame = pWal->hdr.mxFrame;
*pnPrior = pWal->nPriorFrame;
}
return rc;
}
#endif /* #ifndef SQLITE_OMIT_WAL */
+14 -2
View File
@@ -34,7 +34,7 @@
# define sqlite3WalDbsize(y) 0
# define sqlite3WalBeginWriteTransaction(y) 0
# define sqlite3WalEndWriteTransaction(x) 0
# define sqlite3WalUndo(x,y,z) 0
# define sqlite3WalUndo(w,x,y,z) 0
# define sqlite3WalSavepoint(y,z)
# define sqlite3WalSavepointUndo(y,z) 0
# define sqlite3WalFrames(u,v,w,x,y,z) 0
@@ -83,7 +83,7 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal);
int sqlite3WalEndWriteTransaction(Wal *pWal);
/* Undo any frames written (but not committed) to the log */
int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx);
int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx, int);
/* Return an integer that records the current (uncommitted) write
** position in the WAL */
@@ -136,6 +136,15 @@ int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot);
void sqlite3WalSnapshotUnlock(Wal *pWal);
#endif
#ifndef SQLITE_OMIT_CONCURRENT
/* Tell the wal layer that we want to commit a concurrent transaction */
int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pPg, Bitvec *pRead, Pgno*);
/* Upgrade the state of the client to take into account changes written
** by other connections */
int sqlite3WalUpgradeSnapshot(Wal *pWal);
#endif /* SQLITE_OMIT_CONCURRENT */
#ifdef SQLITE_ENABLE_ZIPVFS
/* If the WAL file is not empty, return the number of bytes of content
** stored in each frame (i.e. the db page-size when the WAL was created).
@@ -151,5 +160,8 @@ int sqlite3WalWriteLock(Wal *pWal, int bLock);
void sqlite3WalDb(Wal *pWal, sqlite3 *db);
#endif
/* sqlite3_wal_info() data */
int sqlite3WalInfo(Wal *pWal, u32 *pnPrior, u32 *pnFrame);
#endif /* ifndef SQLITE_OMIT_WAL */
#endif /* SQLITE_WAL_H */
+1
View File
@@ -1577,6 +1577,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
iRowidReg = ++pParse->nMem;
sqlite3VdbeAddOp2(v, OP_Rowid, iCur, iRowidReg);
sqlite3VdbeAddOp3(v, testOp, memEndValue, addrBrk, iRowidReg);
sqlite3VdbeChangeP4(v, -1, SQLITE_INT_TO_PTR(iCur), P4_INT32);
VdbeCoverageIf(v, testOp==OP_Le);
VdbeCoverageIf(v, testOp==OP_Lt);
VdbeCoverageIf(v, testOp==OP_Ge);
+556
View File
@@ -0,0 +1,556 @@
/*
** 2016-05-07
**
** 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.
**
*************************************************************************
*/
#include <sqlite3.h>
#include <stdlib.h>
#include <stddef.h>
#include "tt3_core.c"
#ifdef USE_OSINST
# include "../src/test_osinst.c"
#else
# define vfslog_time() 0
#endif
typedef struct Config Config;
typedef struct ThreadCtx ThreadCtx;
#define THREAD_TIME_INSERT 0
#define THREAD_TIME_COMMIT 1
#define THREAD_TIME_ROLLBACK 2
#define THREAD_TIME_WRITER 3
#define THREAD_TIME_CKPT 4
struct ThreadCtx {
Config *pConfig;
Sqlite *pDb;
Error *pErr;
sqlite3_int64 aTime[5];
};
struct Config {
int nIPT; /* --inserts-per-transaction */
int nThread; /* --threads */
int nSecond; /* --seconds */
int bMutex; /* --mutex */
int nAutoCkpt; /* --autockpt */
int bRm; /* --rm */
int bClearCache; /* --clear-cache */
int nMmap; /* mmap limit in MB */
char *zFile;
int bOsinst; /* True to use osinst */
ThreadCtx *aCtx; /* Array of size nThread */
pthread_cond_t cond;
pthread_mutex_t mutex;
int nCondWait; /* Number of threads waiting on hCond */
sqlite3_vfs *pVfs;
};
typedef struct VfsWrapperFd VfsWrapperFd;
struct VfsWrapperFd {
sqlite3_file base; /* Base class */
int bWriter; /* True if holding shm WRITER lock */
int iTid;
Config *pConfig;
sqlite3_file *pFd; /* Underlying file descriptor */
};
/* Methods of the wrapper VFS */
static int vfsWrapOpen(sqlite3_vfs*, const char*, sqlite3_file*, int, int*);
static int vfsWrapDelete(sqlite3_vfs*, const char*, int);
static int vfsWrapAccess(sqlite3_vfs*, const char*, int, int*);
static int vfsWrapFullPathname(sqlite3_vfs*, const char *, int, char*);
static void *vfsWrapDlOpen(sqlite3_vfs*, const char*);
static void vfsWrapDlError(sqlite3_vfs*, int, char*);
static void (*vfsWrapDlSym(sqlite3_vfs*,void*, const char*))(void);
static void vfsWrapDlClose(sqlite3_vfs*, void*);
static int vfsWrapRandomness(sqlite3_vfs*, int, char*);
static int vfsWrapSleep(sqlite3_vfs*, int);
static int vfsWrapCurrentTime(sqlite3_vfs*, double*);
static int vfsWrapGetLastError(sqlite3_vfs*, int, char*);
static int vfsWrapCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
static int vfsWrapSetSystemCall(sqlite3_vfs*, const char*, sqlite3_syscall_ptr);
static sqlite3_syscall_ptr vfsWrapGetSystemCall(sqlite3_vfs*, const char*);
static const char *vfsWrapNextSystemCall(sqlite3_vfs*, const char*);
/* Methods of wrapper sqlite3_io_methods object (see vfsWrapOpen()) */
static int vfsWrapClose(sqlite3_file*);
static int vfsWrapRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
static int vfsWrapWrite(sqlite3_file*, const void*, int iAmt, sqlite3_int64);
static int vfsWrapTruncate(sqlite3_file*, sqlite3_int64 size);
static int vfsWrapSync(sqlite3_file*, int flags);
static int vfsWrapFileSize(sqlite3_file*, sqlite3_int64 *pSize);
static int vfsWrapLock(sqlite3_file*, int);
static int vfsWrapUnlock(sqlite3_file*, int);
static int vfsWrapCheckReservedLock(sqlite3_file*, int *pResOut);
static int vfsWrapFileControl(sqlite3_file*, int op, void *pArg);
static int vfsWrapSectorSize(sqlite3_file*);
static int vfsWrapDeviceCharacteristics(sqlite3_file*);
static int vfsWrapShmMap(sqlite3_file*, int iPg, int, int, void volatile**);
static int vfsWrapShmLock(sqlite3_file*, int offset, int n, int flags);
static void vfsWrapShmBarrier(sqlite3_file*);
static int vfsWrapShmUnmap(sqlite3_file*, int deleteFlag);
static int vfsWrapFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **);
static int vfsWrapUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p);
static int vfsWrapOpen(
sqlite3_vfs *pVfs,
const char *zName,
sqlite3_file *pFd,
int flags,
int *fout
){
static sqlite3_io_methods methods = {
3,
vfsWrapClose, vfsWrapRead, vfsWrapWrite,
vfsWrapTruncate, vfsWrapSync, vfsWrapFileSize,
vfsWrapLock, vfsWrapUnlock, vfsWrapCheckReservedLock,
vfsWrapFileControl, vfsWrapSectorSize, vfsWrapDeviceCharacteristics,
vfsWrapShmMap, vfsWrapShmLock, vfsWrapShmBarrier,
vfsWrapShmUnmap, vfsWrapFetch, vfsWrapUnfetch
};
Config *pConfig = (Config*)pVfs->pAppData;
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
int rc;
memset(pWrapper, 0, sizeof(VfsWrapperFd));
if( flags & SQLITE_OPEN_MAIN_DB ){
pWrapper->iTid = (int)sqlite3_uri_int64(zName, "tid", 0);
}
pWrapper->pFd = (sqlite3_file*)&pWrapper[1];
pWrapper->pConfig = pConfig;
rc = pConfig->pVfs->xOpen(pConfig->pVfs, zName, pWrapper->pFd, flags, fout);
if( rc==SQLITE_OK ){
pWrapper->base.pMethods = &methods;
}
return rc;
}
static int vfsWrapDelete(sqlite3_vfs *pVfs, const char *a, int b){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xDelete(pConfig->pVfs, a, b);
}
static int vfsWrapAccess(sqlite3_vfs *pVfs, const char *a, int b, int *c){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xAccess(pConfig->pVfs, a, b, c);
}
static int vfsWrapFullPathname(sqlite3_vfs *pVfs, const char *a, int b, char*c){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xFullPathname(pConfig->pVfs, a, b, c);
}
static void *vfsWrapDlOpen(sqlite3_vfs *pVfs, const char *a){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xDlOpen(pConfig->pVfs, a);
}
static void vfsWrapDlError(sqlite3_vfs *pVfs, int a, char *b){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xDlError(pConfig->pVfs, a, b);
}
static void (*vfsWrapDlSym(sqlite3_vfs *pVfs, void *a, const char *b))(void){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xDlSym(pConfig->pVfs, a, b);
}
static void vfsWrapDlClose(sqlite3_vfs *pVfs, void *a){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xDlClose(pConfig->pVfs, a);
}
static int vfsWrapRandomness(sqlite3_vfs *pVfs, int a, char *b){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xRandomness(pConfig->pVfs, a, b);
}
static int vfsWrapSleep(sqlite3_vfs *pVfs, int a){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xSleep(pConfig->pVfs, a);
}
static int vfsWrapCurrentTime(sqlite3_vfs *pVfs, double *a){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xCurrentTime(pConfig->pVfs, a);
}
static int vfsWrapGetLastError(sqlite3_vfs *pVfs, int a, char *b){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xGetLastError(pConfig->pVfs, a, b);
}
static int vfsWrapCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *a){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xCurrentTimeInt64(pConfig->pVfs, a);
}
static int vfsWrapSetSystemCall(
sqlite3_vfs *pVfs,
const char *a,
sqlite3_syscall_ptr b
){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xSetSystemCall(pConfig->pVfs, a, b);
}
static sqlite3_syscall_ptr vfsWrapGetSystemCall(
sqlite3_vfs *pVfs,
const char *a
){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xGetSystemCall(pConfig->pVfs, a);
}
static const char *vfsWrapNextSystemCall(sqlite3_vfs *pVfs, const char *a){
Config *pConfig = (Config*)pVfs->pAppData;
return pConfig->pVfs->xNextSystemCall(pConfig->pVfs, a);
}
static int vfsWrapClose(sqlite3_file *pFd){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
pWrapper->pFd->pMethods->xClose(pWrapper->pFd);
pWrapper->pFd = 0;
return SQLITE_OK;
}
static int vfsWrapRead(sqlite3_file *pFd, void *a, int b, sqlite3_int64 c){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xRead(pWrapper->pFd, a, b, c);
}
static int vfsWrapWrite(
sqlite3_file *pFd,
const void *a, int b,
sqlite3_int64 c
){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xWrite(pWrapper->pFd, a, b, c);
}
static int vfsWrapTruncate(sqlite3_file *pFd, sqlite3_int64 a){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xTruncate(pWrapper->pFd, a);
}
static int vfsWrapSync(sqlite3_file *pFd, int a){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xSync(pWrapper->pFd, a);
}
static int vfsWrapFileSize(sqlite3_file *pFd, sqlite3_int64 *a){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xFileSize(pWrapper->pFd, a);
}
static int vfsWrapLock(sqlite3_file *pFd, int a){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xLock(pWrapper->pFd, a);
}
static int vfsWrapUnlock(sqlite3_file *pFd, int a){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xUnlock(pWrapper->pFd, a);
}
static int vfsWrapCheckReservedLock(sqlite3_file *pFd, int *a){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xCheckReservedLock(pWrapper->pFd, a);
}
static int vfsWrapFileControl(sqlite3_file *pFd, int a, void *b){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xFileControl(pWrapper->pFd, a, b);
}
static int vfsWrapSectorSize(sqlite3_file *pFd){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xSectorSize(pWrapper->pFd);
}
static int vfsWrapDeviceCharacteristics(sqlite3_file *pFd){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xDeviceCharacteristics(pWrapper->pFd);
}
static int vfsWrapShmMap(
sqlite3_file *pFd,
int a, int b, int c,
void volatile **d
){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xShmMap(pWrapper->pFd, a, b, c, d);
}
static int vfsWrapShmLock(sqlite3_file *pFd, int offset, int n, int flags){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
Config *pConfig = pWrapper->pConfig;
int bMutex = 0;
int rc;
if( (offset==0 && n==1)
&& (flags & SQLITE_SHM_LOCK) && (flags & SQLITE_SHM_EXCLUSIVE)
){
pthread_mutex_lock(&pConfig->mutex);
pWrapper->bWriter = 1;
bMutex = 1;
if( pWrapper->iTid ){
sqlite3_int64 t = vfslog_time();
pConfig->aCtx[pWrapper->iTid-1].aTime[THREAD_TIME_WRITER] -= t;
}
}
rc = pWrapper->pFd->pMethods->xShmLock(pWrapper->pFd, offset, n, flags);
if( (rc!=SQLITE_OK && bMutex)
|| (offset==0 && (flags & SQLITE_SHM_UNLOCK) && pWrapper->bWriter)
){
assert( pWrapper->bWriter );
pthread_mutex_unlock(&pConfig->mutex);
pWrapper->bWriter = 0;
if( pWrapper->iTid ){
sqlite3_int64 t = vfslog_time();
pConfig->aCtx[pWrapper->iTid-1].aTime[THREAD_TIME_WRITER] += t;
}
}
return rc;
}
static void vfsWrapShmBarrier(sqlite3_file *pFd){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xShmBarrier(pWrapper->pFd);
}
static int vfsWrapShmUnmap(sqlite3_file *pFd, int a){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xShmUnmap(pWrapper->pFd, a);
}
static int vfsWrapFetch(sqlite3_file *pFd, sqlite3_int64 a, int b, void **c){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xFetch(pWrapper->pFd, a, b, c);
}
static int vfsWrapUnfetch(sqlite3_file *pFd, sqlite3_int64 a, void *b){
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
return pWrapper->pFd->pMethods->xUnfetch(pWrapper->pFd, a, b);
}
static void create_vfs(Config *pConfig){
static sqlite3_vfs vfs = {
3, 0, 0, 0, "wrapper", 0,
vfsWrapOpen, vfsWrapDelete, vfsWrapAccess,
vfsWrapFullPathname, vfsWrapDlOpen, vfsWrapDlError,
vfsWrapDlSym, vfsWrapDlClose, vfsWrapRandomness,
vfsWrapSleep, vfsWrapCurrentTime, vfsWrapGetLastError,
vfsWrapCurrentTimeInt64, vfsWrapSetSystemCall, vfsWrapGetSystemCall,
vfsWrapNextSystemCall
};
sqlite3_vfs *pVfs;
pVfs = sqlite3_vfs_find(0);
vfs.mxPathname = pVfs->mxPathname;
vfs.szOsFile = pVfs->szOsFile + sizeof(VfsWrapperFd);
vfs.pAppData = (void*)pConfig;
pConfig->pVfs = pVfs;
sqlite3_vfs_register(&vfs, 1);
}
/*
** Wal hook used by connections in thread_main().
*/
static int thread_wal_hook(
void *pArg, /* Pointer to ThreadCtx object */
sqlite3 *db,
const char *zDb,
int nFrame
){
ThreadCtx *pCtx = (ThreadCtx*)pArg;
Config *pConfig = pCtx->pConfig;
if( pConfig->nAutoCkpt && nFrame>=pConfig->nAutoCkpt ){
pCtx->aTime[THREAD_TIME_CKPT] -= vfslog_time();
pthread_mutex_lock(&pConfig->mutex);
if( pConfig->nCondWait>=0 ){
pConfig->nCondWait++;
if( pConfig->nCondWait==pConfig->nThread ){
execsql(pCtx->pErr, pCtx->pDb, "PRAGMA wal_checkpoint");
pthread_cond_broadcast(&pConfig->cond);
}else{
pthread_cond_wait(&pConfig->cond, &pConfig->mutex);
}
pConfig->nCondWait--;
}
pthread_mutex_unlock(&pConfig->mutex);
pCtx->aTime[THREAD_TIME_CKPT] += vfslog_time();
}
return SQLITE_OK;
}
static char *thread_main(int iTid, void *pArg){
Config *pConfig = (Config*)pArg;
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
int nAttempt = 0; /* Attempted transactions */
int nCommit = 0; /* Successful transactions */
int j;
ThreadCtx *pCtx = &pConfig->aCtx[iTid-1];
char *zUri = 0;
#ifdef USE_OSINST
char *zOsinstName = 0;
char *zLogName = 0;
if( pConfig->bOsinst ){
zOsinstName = sqlite3_mprintf("osinst%d", iTid);
zLogName = sqlite3_mprintf("bc_test1.log.%d.%d", (int)getpid(), iTid);
zUri = sqlite3_mprintf(
"file:%s?vfs=%s&tid=%d", pConfig->zFile, zOsinstName, iTid
);
sqlite3_vfslog_new(zOsinstName, 0, zLogName);
opendb(&err, &db, zUri, 0);
}else
#endif
{
zUri = sqlite3_mprintf("file:%s?tid=%d", pConfig->zFile, iTid);
opendb(&err, &db, zUri, 0);
}
sqlite3_busy_handler(db.db, 0, 0);
sql_script_printf(&err, &db,
"PRAGMA wal_autocheckpoint = 0;"
"PRAGMA synchronous = 0;"
"PRAGMA mmap_size = %lld;",
(i64)(pConfig->nMmap) * 1024 * 1024
);
pCtx->pConfig = pConfig;
pCtx->pErr = &err;
pCtx->pDb = &db;
sqlite3_wal_hook(db.db, thread_wal_hook, (void*)pCtx);
while( !timetostop(&err) ){
execsql(&err, &db, "BEGIN CONCURRENT");
pCtx->aTime[THREAD_TIME_INSERT] -= vfslog_time();
for(j=0; j<pConfig->nIPT; j++){
execsql(&err, &db,
"INSERT INTO t1 VALUES"
"(randomblob(10), randomblob(20), randomblob(30), randomblob(200))"
);
}
pCtx->aTime[THREAD_TIME_INSERT] += vfslog_time();
pCtx->aTime[THREAD_TIME_COMMIT] -= vfslog_time();
execsql(&err, &db, "COMMIT");
pCtx->aTime[THREAD_TIME_COMMIT] += vfslog_time();
pCtx->aTime[THREAD_TIME_ROLLBACK] -= vfslog_time();
nAttempt++;
if( err.rc==SQLITE_OK ){
nCommit++;
}else{
clear_error(&err, SQLITE_BUSY);
execsql(&err, &db, "ROLLBACK");
}
pCtx->aTime[THREAD_TIME_ROLLBACK] += vfslog_time();
if( pConfig->bClearCache ){
sqlite3_db_release_memory(db.db);
}
}
closedb(&err, &db);
#ifdef USE_OSINST
if( pConfig->bOsinst ){
sqlite3_vfslog_finalize(zOsinstName);
sqlite3_free(zOsinstName);
sqlite3_free(zLogName);
}
#endif
sqlite3_free(zUri);
pthread_mutex_lock(&pConfig->mutex);
pConfig->nCondWait = -1;
pthread_cond_broadcast(&pConfig->cond);
pthread_mutex_unlock(&pConfig->mutex);
return sqlite3_mprintf("commits: %d/%d insert: %dms"
" commit: %dms"
" rollback: %dms"
" writer: %dms"
" checkpoint: %dms",
nCommit, nAttempt,
(int)(pCtx->aTime[THREAD_TIME_INSERT]/1000),
(int)(pCtx->aTime[THREAD_TIME_COMMIT]/1000),
(int)(pCtx->aTime[THREAD_TIME_ROLLBACK]/1000),
(int)(pCtx->aTime[THREAD_TIME_WRITER]/1000),
(int)(pCtx->aTime[THREAD_TIME_CKPT]/1000)
);
}
int main(int argc, const char **argv){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
Threadset threads = {0}; /* Test threads */
Config conf = {5, 3, 5};
int i;
CmdlineArg apArg[] = {
{ "-seconds", CMDLINE_INT, offsetof(Config, nSecond) },
{ "-inserts", CMDLINE_INT, offsetof(Config, nIPT) },
{ "-threads", CMDLINE_INT, offsetof(Config, nThread) },
{ "-mutex", CMDLINE_BOOL, offsetof(Config, bMutex) },
{ "-rm", CMDLINE_BOOL, offsetof(Config, bRm) },
{ "-autockpt",CMDLINE_INT, offsetof(Config, nAutoCkpt) },
{ "-mmap", CMDLINE_INT, offsetof(Config, nMmap) },
{ "-clear-cache", CMDLINE_BOOL, offsetof(Config, bClearCache) },
{ "-file", CMDLINE_STRING, offsetof(Config, zFile) },
{ "-osinst", CMDLINE_BOOL, offsetof(Config, bOsinst) },
{ 0, 0, 0 }
};
conf.nAutoCkpt = 1000;
cmdline_process(apArg, argc, argv, (void*)&conf);
if( err.rc==SQLITE_OK ){
char *z = cmdline_construct(apArg, (void*)&conf);
printf("With: %s\n", z);
sqlite3_free(z);
}
if( conf.zFile==0 ){
conf.zFile = "xyz.db";
}
/* Create the special VFS - "wrapper". And the mutex and condition
** variable. */
create_vfs(&conf);
pthread_mutex_init(&conf.mutex, 0);
pthread_cond_init(&conf.cond, 0);
conf.aCtx = sqlite3_malloc(sizeof(ThreadCtx) * conf.nThread);
memset(conf.aCtx, 0, sizeof(ThreadCtx) * conf.nThread);
/* Ensure the schema has been created */
opendb(&err, &db, conf.zFile, conf.bRm);
sql_script(&err, &db,
"PRAGMA journal_mode = wal;"
"CREATE TABLE IF NOT EXISTS t1(a PRIMARY KEY, b, c, d) WITHOUT ROWID;"
"CREATE INDEX IF NOT EXISTS t1b ON t1(b);"
"CREATE INDEX IF NOT EXISTS t1c ON t1(c);"
);
setstoptime(&err, conf.nSecond*1000);
if( conf.nThread==1 ){
char *z = thread_main(1, (void*)&conf);
printf("Thread 0 says: %s\n", (z==0 ? "..." : z));
fflush(stdout);
}else{
for(i=0; i<conf.nThread; i++){
launch_thread(&err, &threads, thread_main, (void*)&conf);
}
join_all_threads(&err, &threads);
}
if( err.rc==SQLITE_OK ){
printf("Database is %dK\n", (int)(filesize(&err, conf.zFile) / 1024));
}
if( err.rc==SQLITE_OK ){
char *zWal = sqlite3_mprintf("%s-wal", conf.zFile);
printf("Wal file is %dK\n", (int)(filesize(&err, zWal) / 1024));
}
closedb(&err, &db);
print_and_free_err(&err);
return 0;
}
+126
View File
@@ -0,0 +1,126 @@
# 2015 Aug 25
#
# 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 fault injection tests designed to test the concurrent
# transactions feature.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/malloc_common.tcl
set testprefix concfault
# This test will not work with an in-memory journal, as the database will
# become corrupt if an error is injected into a transaction after it starts
# writing data out to the db file.
ifcapable !concurrent {
finish_test
return
}
do_test 1-pre1 {
execsql {
PRAGMA journal_mode = wal;
CREATE TABLE t1(a PRIMARY KEY, b);
INSERT INTO t1 VALUES(randomblob(1000), randomblob(100));
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
DELETE FROM t1 WHERE rowid%2;
}
faultsim_save_and_close
} {}
do_faultsim_test 1.1 -prep {
faultsim_restore_and_reopen
} -body {
execsql {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(randomblob(1000), randomblob(100));
COMMIT;
}
} -test {
faultsim_test_result {0 {}}
catchsql { ROLLBACK }
faultsim_integrity_check
}
do_faultsim_test 1.2 -prep {
faultsim_restore_and_reopen
} -body {
execsql {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(randomblob(1000), randomblob(100));
ROLLBACK;
}
} -test {
faultsim_test_result {0 {}}
catchsql { ROLLBACK }
faultsim_integrity_check
}
do_faultsim_test 1.3 -prep {
faultsim_restore_and_reopen
} -body {
execsql {
BEGIN CONCURRENT;
DELETE FROM t1;
COMMIT;
}
} -test {
faultsim_test_result {0 {}}
catchsql { ROLLBACK }
faultsim_integrity_check
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
PRAGMA auto_vacuum = 0;
PRAGMA journal_mode = wal;
CREATE TABLE t1(a PRIMARY KEY, b);
CREATE TABLE t2(a PRIMARY KEY, b);
INSERT INTO t1 VALUES(randomblob(1000), randomblob(100));
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
DELETE FROM t1 WHERE rowid%2;
} {wal}
faultsim_save_and_close
do_faultsim_test 1 -prep {
faultsim_restore_and_reopen
execsql {
SELECT * FROM t1;
BEGIN CONCURRENT;
INSERT INTO t2 VALUES(1, 2);
}
sqlite3 db2 test.db
execsql {
PRAGMA journal_size_limit = 10000;
INSERT INTO t1 VALUES(randomblob(1000), randomblob(1000));
} db2
db2 close
} -body {
execsql { COMMIT }
} -test {
faultsim_test_result {0 {}}
catchsql { ROLLBACK }
set res [catchsql { SELECT count(*) FROM t1 }]
if {$res!="0 9"} { error "expected {0 9} got {$res}" }
faultsim_integrity_check
}
finish_test
+488
View File
@@ -0,0 +1,488 @@
# 2020 Jan 20
#
# 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
set ::testprefix concreport
ifcapable !concurrent||!preupdate {
finish_test
return
}
proc filtered_report {db {writes 0}} {
set report [split [sqlite3_begin_concurrent_report $db] "\n"]
set report [lrange $report 0 end-1]
set res [list]
foreach r $report {
set c [string range $r 0 0]
if {$writes==0 && ($c=="R" || $c=="W" || $c=="F")} continue
if {$writes==1 && $c!="R" && $c!="W" && $c!="F"} continue
lappend res $r
}
set res
}
proc do_concreport_test {tn sql res report} {
uplevel [list do_execsql_test $tn.1 "
BEGIN CONCURRENT;
$sql ;
COMMIT;
" $res]
uplevel [list do_test $tn.2 {
filtered_report db 0
} [list {*}$report]]
}
proc do_pagereport_test {tn sql res report} {
uplevel [list do_catchsql_test $tn.1 "
BEGIN CONCURRENT;
$sql ;
COMMIT;
" $res]
uplevel [list do_test $tn.2 {
filtered_report db 1
} [list {*}$report]]
}
sqlite3_begin_concurrent_report_enable db 1
do_execsql_test 1.0 {
PRAGMA journal_mode = wal;
} {wal}
do_execsql_test 1.1 {
CREATE TABLE t1(k INTEGER PRIMARY KEY, v);
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100
)
INSERT INTO t1 SELECT i, i FROM s;
BEGIN CONCURRENT;
SELECT * FROM t1 WHERE k = 5;
SELECT * FROM t1 WHERE k = 10;
COMMIT;
} {5 5 10 10}
do_test 1.1.1 {
list {*}[sqlite3_begin_concurrent_report db]
} [list {2:[10..10]} {2:[5..5]}]
do_concreport_test 1.2 {
SELECT * FROM t1 WHERE k = 100;
} {100 100} {
2:[100..100]
}
do_concreport_test 1.3 {
SELECT * FROM t1 WHERE k >= 10 AND k <= 13;
} {10 10 11 11 12 12 13 13} {
2:[10..13]
}
do_concreport_test 1.4 {
SELECT max(k) FROM t1;
} {100} {
2:[100..EOF)
}
do_concreport_test 1.5 {
SELECT min(k) FROM t1;
} {1} {
2:(EOF..1]
}
do_concreport_test 1.6 {
SELECT * FROM t1 WHERE k IN (4, 6, 8)
} {4 4 6 6 8 8} {
2:[8..8]
2:[6..6]
2:[4..4]
}
do_concreport_test 1.7 {
SELECT v FROM t1 WHERE k > 22 LIMIT 5
} {23 24 25 26 27} {
2:(22..27]
}
do_concreport_test 1.8 {
SELECT v FROM t1 WHERE k > 22 ORDER BY k DESC LIMIT 5
} {100 99 98 97 96} {
2:[96..EOF)
}
do_concreport_test 1.9 {
SELECT v FROM t1 WHERE k > 21 AND k < 25
} {22 23 24} {
2:(21..25)
}
do_concreport_test 1.10 {
SELECT v FROM t1 WHERE k > 21 AND k < 25 ORDER BY k DESC
} {24 23 22} {
2:(21..25)
}
do_concreport_test 1.11 {
SELECT v FROM t1 WHERE k > 98 AND k < 200
} {99 100} {
2:(98..200)
}
do_concreport_test 1.12 {
SELECT v FROM t1 WHERE k >= 98 AND k <= 200
} {98 99 100} {
2:[98..200]
}
# BUG: This case does not work. The result should be [101..200], but as
# there are no rows matching (k>=101) the system never sees the (k<=200)
# constraint. Hence "..EOF".
do_concreport_test 1.13 {
SELECT v FROM t1 WHERE k >= 101 AND k <= 200
} {} {
2:[101..EOF)
}
do_concreport_test 1.14 {
SELECT v FROM t1 WHERE +v=5
} {5} {
2:(EOF..EOF)
}
#--------------------------------------------------------------------------
reset_db
sqlite3_begin_concurrent_report_enable db 1
do_execsql_test 2.0 {
PRAGMA journal_mode = wal;
CREATE TABLE t2(k PRIMARY KEY, v);
INSERT INTO t2 VALUES('a', 1);
INSERT INTO t2 VALUES('b', 2);
INSERT INTO t2 VALUES('c', 3);
INSERT INTO t2 VALUES('d', 4);
INSERT INTO t2 VALUES('e', 5);
INSERT INTO t2 VALUES('f', 6);
INSERT INTO t2 VALUES('g', 7);
INSERT INTO t2 VALUES('i', 9);
INSERT INTO t2 VALUES('j', 10);
INSERT INTO t2 VALUES('k', 11);
} {wal}
do_concreport_test 2.1 {
SELECT * FROM t2 WHERE k = 'e'
} {e 5} {
3:[{'e'}..{'e'}]
}
do_concreport_test 2.2 {
SELECT * FROM t2 WHERE k = 'h'
} {} {
3:[{'h'}..{'h'}]
}
do_concreport_test 2.3 {
SELECT * FROM t2 WHERE k >= 'c' AND k <= 'f';
} {c 3 d 4 e 5 f 6} {
3:[{'c'}..{'f'}]
}
do_concreport_test 2.4 {
SELECT max(k) FROM t2;
} {k} {
3:[{'k',10}..EOF)
}
do_concreport_test 2.5 {
SELECT min(k) FROM t2;
} {a} {
3:({NULL}..{'a',1}]
}
do_concreport_test 2.6 {
SELECT * FROM t2 WHERE k IN ('c', 'e', 'g')
} {c 3 e 5 g 7} {
3:[{'g'}..{'g'}]
3:[{'e'}..{'e'}]
3:[{'c'}..{'c'}]
}
do_concreport_test 2.7 {
SELECT v FROM t2 WHERE k > 'b' LIMIT 5
} {3 4 5 6 7} {
3:({'b'}..{'g',7}]
}
do_concreport_test 2.8 {
SELECT v FROM t2 WHERE k > 'b' ORDER BY k DESC LIMIT 5
} {11 10 9 7 6} {
3:[{'f',6}..EOF)
}
do_concreport_test 2.9 {
SELECT v FROM t2 WHERE k > 'b' AND k < 'f'
} {3 4 5} {
3:({'b'}..{'f'})
}
do_concreport_test 2.10 {
SELECT v FROM t2 WHERE k > 'b' AND k < 'f' ORDER BY k DESC
} {5 4 3} {
3:({'b'}..{'f'})
}
do_concreport_test 2.11 {
SELECT v FROM t2 WHERE k > 'j' AND k < 'q'
} {11} {
3:({'j'}..{'q'})
}
do_concreport_test 2.12 {
SELECT v FROM t2 WHERE k >= 'j' AND k <= 'q'
} {10 11} {
3:[{'j'}..{'q'}]
}
do_concreport_test 2.13 {
SELECT v FROM t2 WHERE k > 'j' AND k < 'q' ORDER BY k DESC
} {11} {
3:({'j'}..{'q'})
}
do_concreport_test 2.14 {
SELECT v FROM t2 WHERE k >= 'j' AND k <= 'q' ORDER BY k DESC
} {11 10} {
3:[{'j'}..{'q'}]
}
# BUG: This case does not work. The result should be [101..200], but as there
# are no rows matching (k>=101) the system never sees the (k<=200) constraint.
# Hence "..EOF".
do_concreport_test 2.15 {
SELECT v FROM t2 WHERE k >= 'q' AND k <= 's'
} {} {
3:[{'q'}..EOF)
}
do_concreport_test 2.16 {
SELECT v FROM t2 WHERE +v=5 ORDER BY k
} {5} {
3:(EOF..EOF)
}
#--------------------------------------------------------------------------
reset_db
sqlite3_begin_concurrent_report_enable db 1
do_execsql_test 3.1 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b)) WITHOUT ROWID;
CREATE INDEX t1c ON t1(c);
INSERT INTO t1 VALUES(1, 1, 'one');
INSERT INTO t1 VALUES(1, 2, 'one.two');
INSERT INTO t1 VALUES(2, 1, 'two');
INSERT INTO t1 VALUES(2, 2, 'two.two');
INSERT INTO t1 VALUES(3, 1, 'three');
INSERT INTO t1 VALUES(3, 2, 'three.two');
INSERT INTO t1 VALUES(4, 1, 'four');
INSERT INTO t1 VALUES(4, 2, 'four.two');
} {wal}
do_concreport_test 3.1 {
SELECT c FROM t1 WHERE a=1;
} {one one.two} {
2:[{1}..{1}]
}
do_concreport_test 3.2 {
SELECT c FROM t1 WHERE (a, b) = (1, 2);
} {one.two} {
2:[{1,2}..{1,2}]
}
do_concreport_test 3.3 {
SELECT c FROM t1 WHERE (a, b) >= (3, 2);
} {three.two four four.two} {
2:[{3,2}..EOF)
}
# BUG: This is reported as if the query included rows with PK (3,2), even
# though it does not (i.e. result should be "2:({3,2}..EOF)").
do_concreport_test 3.4 {
SELECT c FROM t1 WHERE (a, b) > (3, 2);
} {four four.two} {
2:[{3,2}..EOF)
}
do_concreport_test 3.5 {
SELECT c FROM t1 WHERE a > 3;
} {four four.two} {
2:({3}..EOF)
}
do_concreport_test 3.6 {
SELECT c FROM t1 WHERE a>1 AND a<4;
} {two two.two three three.two} {
2:({1}..{4})
}
do_concreport_test 3.7 {
SELECT c FROM t1 WHERE a>=2 AND a<=3;
} {two two.two three three.two} {
2:[{2}..{3}]
}
do_concreport_test 3.8 {
SELECT a FROM t1 WHERE c IN('two', 'three', 'four');
} {4 3 2} {
3:[{'two'}..{'two'}]
3:[{'three'}..{'three'}]
3:[{'four'}..{'four'}]
}
do_concreport_test 3.9 {
SELECT a FROM t1 ORDER BY c;
} {4 4 1 1 3 3 2 2} {
3:(EOF..EOF)
}
#--------------------------------------------------------------------------
reset_db
sqlite3_begin_concurrent_report_enable db 1
do_execsql_test 4.0 {
PRAGMA journal_mode = wal;
CREATE TABLE yy(a INTEGER PRIMARY KEY, one, two);
CREATE INDEX yytwo ON yy(two);
CREATE TABLE xx(c INTEGER PRIMARY KEY, three, four);
CREATE INDEX xxthree ON xx(three);
INSERT INTO yy VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4);
INSERT INTO xx VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4);
} {wal}
do_concreport_test 4.1 {
SELECT count(one) FROM xx,yy
} {16} {
5:(EOF..EOF)
5:(EOF..EOF)
5:(EOF..EOF)
5:(EOF..EOF)
2:(EOF..EOF)
}
do_concreport_test 4.2 {
SELECT count(one) FROM xx, yy WHERE two=three;
} {4} {
5:[{4}..{4}]
5:[{3}..{3}]
5:[{2}..{2}]
5:[{1}..{1}]
2:(EOF..EOF)
}
#--------------------------------------------------------------------------
reset_db
sqlite3_begin_concurrent_report_enable db 1
do_execsql_test 5.0 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID;
} {wal}
do_concreport_test 5.1.1 {
INSERT INTO t1 VALUES(1, 2, 3);
} {} {
2<-(1){NULL,2,3}
2:[1..1]
}
do_concreport_test 5.1.2 {
UPDATE t1 SET c='three' WHERE rowid=1;
} {} {
2<-(1){NULL,2,'three'}
2<-(1){NULL,2,3}
2:[1..1]
}
do_concreport_test 5.1.3 {
DELETE FROM t1 WHERE rowid=1
} {} {
2<-(1){NULL,2,'three'}
2:[1..1]
}
do_concreport_test 5.2.1 {
INSERT INTO t2 VALUES(1, 2, 3);
} {} {
3<-(0){1,2,3}
}
do_concreport_test 5.2.2 {
UPDATE t2 SET c='three' WHERE a=1;
} {} {
3<-(0){1,2,'three'}
3<-(0){1,2,3}
3:[{1}..{1}]
}
do_concreport_test 5.2.3 {
DELETE FROM t2 WHERE a=1
} {} {
3<-(0){1,2,'three'}
3:[{1}..{1}]
}
do_concreport_test 5.3 {
INSERT INTO t2 VALUES(44, x'abcdef', x'001100');
} {} {
3<-(0){44,X'ABCDEF',X'001100'}
}
#--------------------------------------------------------------------------
reset_db
sqlite3_begin_concurrent_report_enable db 1
do_execsql_test 6.0 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID;
} {wal}
do_pagereport_test 6.1 {
INSERT INTO t1 VALUES(1, 2, 3);
} {0 {}} {
W:{2} R:{2}
}
do_pagereport_test 6.2 {
INSERT INTO t2 VALUES(1, 2, 3);
} {0 {}} {
W:{3} R:{3}
}
do_pagereport_test 6.3 {
SELECT * FROM t1;
INSERT INTO t2 VALUES(4, 5, 6);
} {0 {1 2 3}} {
W:{3} {R:{2 3}}
}
sqlite3 db2 test.db
proc write_t2 {a b c} {
db2 eval {INSERT INTO t2 VALUES($a, $b, $c)}
}
db func write_t2 write_t2
do_pagereport_test 6.3 {
SELECT * FROM t1;
INSERT INTO t2 VALUES(7, 8, 9);
SELECT write_t2(7, 8, 9);
} {1 {database is locked}} {
{F:{2 3}}
}
finish_test
+687
View File
@@ -0,0 +1,687 @@
# 2015 July 26
#
# 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
set ::testprefix concurrent
ifcapable !concurrent {
finish_test
return
}
do_execsql_test 1.0 {
PRAGMA journal_mode = wal;
} {wal}
do_execsql_test 1.1 {
CREATE TABLE t1(k INTEGER PRIMARY KEY, v);
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(1, 'abcd');
COMMIT;
}
do_execsql_test 1.2 {
SELECT * FROM t1;
} {1 abcd}
do_execsql_test 1.3 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(2, 'efgh');
ROLLBACK;
}
do_execsql_test 1.4 {
SELECT * FROM t1;
} {1 abcd}
#-------------------------------------------------------------------------
# CONCURRENT transactions cannot do cache spills.
#
foreach {tn trans spill} {
1 {BEGIN CONCURRENT} 0
2 {BEGIN} 1
} {
do_test 1.5.$tn {
sqlite3 db2 test.db
set walsz [file size test.db-wal]
execsql { PRAGMA cache_size = 10 } db2
execsql $trans db2
execsql {
WITH cnt(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM cnt WHERE i<50)
INSERT INTO t1(v) SELECT randomblob(900) FROM cnt;
} db2
expr {[file size test.db-wal]==$walsz}
} [expr !$spill]
execsql ROLLBACK db2
db2 close
}
#-------------------------------------------------------------------------
# CONCURRENT transactions man not be committed while there are active
# readers.
do_execsql_test 1.6.setup {
DROP TABLE t1;
CREATE TABLE t1(a, b);
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(3, 4);
INSERT INTO t1 VALUES(5, 6);
}
foreach {tn trans commit_ok} {
1 {BEGIN CONCURRENT} 0
2 {BEGIN} 1
} {
do_test 1.6.$tn.1 {
set stmt [sqlite3_prepare db "SELECT * FROM t1" -1 dummy]
sqlite3_step $stmt
} SQLITE_ROW
do_test 1.6.$tn.2 {
execsql $trans
execsql { INSERT INTO t1 VALUES(7, 8) }
} {}
if { $commit_ok } {
do_test 1.6.$tn.3 { catchsql COMMIT } {0 {}}
} else {
do_test 1.6.$tn.4 { catchsql COMMIT } {/1 {cannot commit transaction .*}/}
}
sqlite3_finalize $stmt
catchsql ROLLBACK
}
#-------------------------------------------------------------------------
# CONCURRENT transactions may not modify the db schema.
#
sqlite3 db2 test.db
foreach {tn sql} {
1 { CREATE TABLE xx(a, b) }
2 { DROP TABLE t1 }
3 { CREATE INDEX i1 ON t1(a) }
4 { CREATE VIEW v1 AS SELECT * FROM t1 }
} {
do_catchsql_test 1.7.0.$tn.1 "
BEGIN CONCURRENT;
$sql
" {0 {}}
db2 eval {INSERT INTO t1 DEFAULT VALUES}
do_catchsql_test 1.7.0.$tn.2 {
COMMIT
} {1 {database is locked}}
do_execsql_test 1.7.0.$tn.2 ROLLBACK
do_execsql_test 1.7.0.$tn.3 {
SELECT sql FROM sqlite_master;
SELECT sql FROM sqlite_temp_master;
} {{CREATE TABLE t1(a, b)}}
#do_execsql_test 1.7.0.$tn.3 COMMIT
}
# Except the temp db schema.
foreach {tn sql} {
1 { CREATE TEMP TABLE xx(a, b) }
2 { DROP TABLE xx }
3 { CREATE TEMP TABLE yy(a, b) }
4 { CREATE VIEW temp.v1 AS SELECT * FROM t1 }
5 { CREATE INDEX yyi1 ON yy(a); }
6 { CREATE TABLE temp.zz(a, b) }
} {
do_catchsql_test 1.7.1.$tn.1 "
BEGIN CONCURRENT;
$sql
" {0 {}}
do_execsql_test 1.7.1.$tn.2 COMMIT
}
do_execsql_test 1.7.1.x {
SELECT sql FROM sqlite_master;
SELECT sql FROM sqlite_temp_master;
} {
{CREATE TABLE t1(a, b)}
{CREATE TABLE yy(a, b)}
{CREATE VIEW v1 AS SELECT * FROM t1}
{CREATE INDEX yyi1 ON yy(a)}
{CREATE TABLE zz(a, b)}
}
#-------------------------------------------------------------------------
# If an auto-vacuum database is written within an CONCURRENT transaction, it
# is handled in the same way as for a non-CONCURRENT transaction.
#
reset_db
do_execsql_test 1.8.1 {
PRAGMA auto_vacuum = 1;
PRAGMA journal_mode = wal;
CREATE TABLE t1(x, y);
INSERT INTO t1 VALUES('x', 'y');
} {wal}
do_execsql_test 1.8.2 {
BEGIN CONCURRENT;
SELECT * FROM t1;
COMMIT;
} {x y}
do_catchsql_test 1.8.3 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES('a', 'b');
} {0 {}}
do_test 1.8.4 {
sqlite3 db2 test.db
catchsql {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES('c', 'd');
} db2
} {1 {database is locked}}
do_test 1.8.5 {
db eval COMMIT
db2 eval COMMIT
} {}
db close
db2 close
do_multiclient_test tn {
#-----------------------------------------------------------------------
# 1. Start an CONCURRENT transaction using [db1].
#
# 2. Start and then rollback a regular transaction using [db2]. This
# can be done as the ongoing [db1] transaction is CONCURRENT.
#
# 3. The [db1] transaction can now be committed, as [db2] has relinquished
# the write lock.
#
do_test 2.$tn.1.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(k INTEGER PRIMARY KEY, v);
INSERT INTO t1 VALUES(1, 'one');
}
sql1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(2, 'two');
}
code1 { sqlite3_get_autocommit db }
} 0
do_test 2.$tn.1.2 {
sql2 {
BEGIN;
INSERT INTO t1 VALUES(3, 'three');
ROLLBACK;
}
} {}
do_test 2.$tn.1.3 {
sql1 COMMIT
sql2 { SELECT * FROM t1 }
} {1 one 2 two}
#-----------------------------------------------------------------------
# 1. Start an CONCURRENT transaction using [db1].
#
# 2. Commit a transaction using [db2].
#
# 3. Try to commit with [db1]. Check that SQLITE_BUSY_SNAPSHOT is returned,
# and the transaction is not rolled back.
#
do_test 2.$tn.2.1 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(-1, 'hello world');
}
} {}
do_test 2.$tn.2.2 {
sql2 {
INSERT INTO t1 VALUES(3, 'three');
}
} {}
do_test 2.$tn.2.3.1 {
set rc [catch { sql1 COMMIT } msg]
list $rc $msg
} {1 {database is locked}}
do_test 2.$tn.2.3.2 {
code1 { list [sqlite3_extended_errcode db] [sqlite3_get_autocommit db] }
} {SQLITE_BUSY_SNAPSHOT 0}
do_test 2.$tn.2.3.3 {
sql1 {
SELECT * FROM t1;
ROLLBACK;
}
} {-1 {hello world} 1 one 2 two}
#-----------------------------------------------------------------------
# 1. Start an CONCURRENT transaction using [db1].
#
# 2. Open a transaction using [db2].
#
# 3. Try to commit with [db1]. Check that SQLITE_BUSY is returned,
# and the transaction is not rolled back.
#
# 4. Have [db2] roll its transaction back. Then check that [db1] can
# commit.
#
do_test 2.$tn.3.1 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(4, 'four');
}
} {}
do_test 2.$tn.3.2 {
sql2 {
BEGIN;
INSERT INTO t1 VALUES(-1, 'xyz');
}
} {}
do_test 2.$tn.3.3.1 {
set rc [catch { sql1 COMMIT } msg]
list $rc $msg
} {1 {database is locked}}
do_test 2.$tn.3.3.2 {
code1 { list [sqlite3_extended_errcode db] [sqlite3_get_autocommit db] }
} {SQLITE_BUSY 0}
do_test 2.$tn.3.3.3 {
sql1 { SELECT * FROM t1; }
} {1 one 2 two 3 three 4 four}
do_test 2.$tn.3.4 {
sql2 ROLLBACK
sql1 COMMIT
sql1 { SELECT * FROM t1; }
} {1 one 2 two 3 three 4 four}
#-----------------------------------------------------------------------
# 1. Create a second table - t2.
#
# 2. Write to t1 with [db] and t2 with [db2].
#
# 3. See if it worked.
#
do_test 2.$tn.4.1 {
sql1 { CREATE TABLE t2(a, b) }
} {}
do_test 2.$tn.4.2 {
sql2 {
BEGIN CONCURRENT;
INSERT INTO t2 VALUES('i', 'n');
}
sql1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(5, 'five');
COMMIT;
}
sql2 COMMIT
} {}
do_test 2.$tn.4.3.1 {
sql2 {SELECT * FROM t1}
} {1 one 2 two 3 three 4 four 5 five}
do_test 2.$tn.4.3.2 {
sql1 {SELECT * FROM t1}
} {1 one 2 two 3 three 4 four 5 five}
do_test 2.$tn.4.3.3 { sql2 {SELECT * FROM t2} } {i n}
do_test 2.$tn.4.3.4 { sql1 {SELECT * FROM t2} } {i n}
#-----------------------------------------------------------------------
# The "schema cookie" issue.
#
# 1. Begin and CONCURRENT write to "t1" using [db]
#
# 2. Create an index on t1 using [db2].
#
# 3. Attempt to commit the CONCURRENT write. This is an SQLITE_BUSY_SNAPSHOT,
# even though there is no page collision.
#
do_test 2.$tn.5.1 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(6, 'six');
}
} {}
do_test 2.$tn.5.2 {
sql2 { CREATE INDEX i1 ON t1(v); }
} {}
do_test 2.$tn.5.3 {
list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
} {1 {database is locked} SQLITE_BUSY_SNAPSHOT}
do_test 2.$tn.5.4 {
sql2 { PRAGMA integrity_check }
} {ok}
catch { sql1 ROLLBACK }
#-----------------------------------------------------------------------
#
# 1. Begin an CONCURRENT write to "t1" using [db]
#
# 2. Lots of inserts into t2. Enough to grow the db file and modify page 1.
#
# 3. Check that the CONCURRENT transaction can not be committed.
#
do_test 2.$tn.6.1 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(6, 'six');
}
} {}
do_test 2.$tn.6.2 {
sql2 {
WITH src(a,b) AS (
VALUES(1,1) UNION ALL SELECT a+1,b+1 FROM src WHERE a<10000
) INSERT INTO t2 SELECT * FROM src;
}
} {}
do_test 2.$tn.6.3 {
sql1 { SELECT count(*) FROM t2 }
list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
} {1 {database is locked} SQLITE_BUSY_SNAPSHOT}
sql1 ROLLBACK
do_test 2.$tn.6.4 {
sql1 {
SELECT count(*) FROM t1;
SELECT count(*) FROM t2;
}
} {5 10001}
#-----------------------------------------------------------------------
#
# 1. Begin an big CONCURRENT write to "t1" using [db] - large enough to
# grow the db file.
#
# 2. Lots of inserts into t2. Also enough to grow the db file.
#
# 3. Check that the CONCURRENT transaction cannot be committed (due to a clash
# on page 1 - the db size field).
#
do_test 2.$tn.7.1 {
sql1 {
BEGIN CONCURRENT;
WITH src(a,b) AS (
VALUES(10000,10000) UNION ALL SELECT a+1,b+1 FROM src WHERE a<20000
) INSERT INTO t1 SELECT * FROM src;
}
} {}
do_test 2.$tn.7.2 {
sql2 {
WITH src(a,b) AS (
VALUES(1,1) UNION ALL SELECT a+1,b+1 FROM src WHERE a<10000
) INSERT INTO t2 SELECT * FROM src;
}
} {}
do_test 2.$tn.7.3 {
list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
} {0 {} SQLITE_OK}
do_test 2.$tn.7.4 { sql3 { PRAGMA integrity_check } } ok
}
#-------------------------------------------------------------------------
# Concurrent transactions may not modify the user_version or application_id.
#
reset_db
do_execsql_test 3.0 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(x, y);
INSERT INTO t1 VALUES('a', 'b');
PRAGMA user_version = 10;
} {wal}
do_execsql_test 3.1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES('c', 'd');
SELECT * FROM t1;
} {a b c d}
do_catchsql_test 3.2 {
PRAGMA user_version = 11;
} {1 {cannot modify user_version within CONCURRENT transaction}}
do_execsql_test 3.3 {
PRAGMA user_version;
SELECT * FROM t1;
} {10 a b c d}
do_catchsql_test 3.4 {
PRAGMA application_id = 11;
} {1 {cannot modify application_id within CONCURRENT transaction}}
do_execsql_test 3.5 {
COMMIT;
PRAGMA user_version;
PRAGMA application_id;
SELECT * FROM t1;
} {10 0 a b c d}
#-------------------------------------------------------------------------
# However, another transaction modifying the user_version or application_id
# should not cause a conflict. And committing a concurrent transaction does not
# clobber the modification - even if the concurrent transaction allocates or
# frees database pages.
#
do_multiclient_test tn {
do_test 4.$tn.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE ttt(y UNIQUE, z UNIQUE);
PRAGMA user_version = 14;
BEGIN CONCURRENT;
INSERT INTO ttt VALUES('y', 'z');
}
} {wal}
do_test 4.$tn.2 {
sql2 { PRAGMA user_version = 16 }
sql1 COMMIT
sql1 { PRAGMA user_version }
} {16}
do_test 4.$tn.3 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO ttt VALUES(randomblob(10000), randomblob(4));
PRAGMA user_version;
}
} {16}
do_test 4.$tn.4 {
sql2 { PRAGMA user_version = 1234 }
sql1 {
PRAGMA user_version;
COMMIT;
PRAGMA user_version;
PRAGMA integrity_check;
}
} {16 1234 ok}
do_test 4.$tn.5 {
sql1 {
BEGIN CONCURRENT;
DELETE FROM ttt;
PRAGMA user_version;
}
} {1234}
do_test 4.$tn.4 {
sql2 { PRAGMA user_version = 5678 }
sql1 {
PRAGMA user_version;
COMMIT;
PRAGMA user_version;
PRAGMA integrity_check;
}
} {1234 5678 ok}
}
do_multiclient_test tn {
do_test 5.$tn.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE tt(a INTEGER PRIMARY KEY, b);
CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
INSERT INTO tt VALUES(1, randomblob(400));
BEGIN CONCURRENT;
}
} {wal}
do_test 5.$tn.2 {
sql1 { UPDATE t2 SET b=5 WHERE a=3 }
sql2 { INSERT INTO tt VALUES(2, randomblob(6000)) }
} {}
do_test 5.$tn.3 {
sql1 { COMMIT }
} {}
}
do_multiclient_test tn {
do_test 6.$tn.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t2 VALUES(2, 'two');
}
} {wal}
do_test 6.$tn.2 {
sql2 {
BEGIN CONCURRENT;
SELECT * FROM t2;
INSERT INTO t1 VALUES(3, 'three');
}
} {2 two}
do_test 6.$tn.3 {
sql1 {
INSERT INTO t2 VALUES(3, 'three');
}
} {}
do_test 6.$tn.2 {
list [catch { sql2 { COMMIT } } msg] $msg
} {1 {database is locked}}
}
do_multiclient_test tn {
do_test 7.$tn.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100)
INSERT INTO t1 SELECT NULL, randomblob(400) FROM s;
CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<50000)
INSERT INTO t2 SELECT NULL, randomblob(400) FROM s;
CREATE TABLE t3(a INTEGER PRIMARY KEY, b);
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100)
INSERT INTO t3 SELECT NULL, randomblob(400) FROM s;
CREATE TABLE t4(a INTEGER PRIMARY KEY, b);
}
set {} {}
} {}
do_test 7.$tn.2 {
sql2 {
BEGIN CONCURRENT;
SELECT * FROM t1;
INSERT INTO t4 VALUES(1, 2);
}
set {} {}
} {}
do_test 7.$tn.3 {
sql3 {
BEGIN CONCURRENT;
SELECT * FROM t3;
INSERT INTO t4 VALUES(1, 2);
}
set {} {}
} {}
do_test 7.$tn.4 {
sql1 {
UPDATE t1 SET b=randomblob(400);
UPDATE t2 SET b=randomblob(400);
UPDATE t3 SET b=randomblob(400);
}
} {}
do_test 7.$tn.5 {
csql2 { COMMIT }
} {1 {database is locked}}
do_test 7.$tn.6 {
csql3 { COMMIT }
} {1 {database is locked}}
csql2 ROLLBACK
csql3 ROLLBACK
# The following test works with $tn==1 (sql2 and sql3 use separate
# processes), but is quite slow. So only run it with $tn==2 (all
# connections in the same process).
#
if {$tn==2} {
do_test 7.$tn.7 {
for {set i 1} {$i < 10000} {incr i} {
sql3 {
PRAGMA wal_checkpoint;
BEGIN CONCURRENT;
SELECT * FROM t3;
INSERT INTO t4 VALUES(1, 2);
}
sql1 {
UPDATE t2 SET b = randomblob(400) WHERE rowid <= $i;
UPDATE t3 SET b = randomblob(400) WHERE rowid = 1;
}
if {[csql3 COMMIT]!={1 {database is locked}}} {
error "Failed at i=$i"
}
csql3 ROLLBACK
}
} {}
}
}
finish_test
+626
View File
@@ -0,0 +1,626 @@
# 2015 July 26
#
# 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.
#
#***********************************************************************
#
# Miscellaneous tests for transactions started with BEGIN CONCURRENT.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl
source $testdir/wal_common.tcl
set ::testprefix concurrent2
ifcapable !concurrent {
finish_test
return
}
do_multiclient_test tn {
do_test 1.$tn.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(x);
CREATE TABLE t2(y);
}
} {wal}
do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
# Test that an CONCURRENT transaction that allocates/frees no pages does
# not conflict with a transaction that does allocate pages.
do_test 1.$tn.2 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(4);
}
sql2 {
INSERT INTO t2 VALUES(randomblob(1500));
}
sql1 {
COMMIT;
}
} {}
do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
# But that an CONCURRENT transaction does conflict with a transaction
# that modifies the db schema.
do_test 1.$tn.3 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(5);
}
sql2 {
CREATE TABLE t3(z);
}
list [catch { sql1 COMMIT } msg] $msg
} {1 {database is locked}}
do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
# Test that an CONCURRENT transaction that allocates at least one page
# does not conflict with a transaction that allocates no pages.
do_test 1.$tn.4 {
sql1 {
ROLLBACK;
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(randomblob(1500));
}
sql2 {
INSERT INTO t2 VALUES(8);
}
sql1 {
COMMIT;
}
} {}
do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
}
do_multiclient_test tn {
do_test 2.$tn.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(x UNIQUE);
CREATE TABLE t2(y UNIQUE);
}
} {wal}
do_test 2.$tn.2 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(randomblob(1500));
}
sql2 {
INSERT INTO t2 VALUES(randomblob(1500));
}
sql1 COMMIT
} {}
do_test 2.$tn.3 { sql3 { PRAGMA integrity_check } } {ok}
do_test 2.$tn.4 {
sql1 {
BEGIN CONCURRENT;
DELETE FROM t1;
}
sql2 {
DELETE FROM t2;
}
sql1 COMMIT
} {}
do_test 2.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
do_test 2.$tn.6 {
sql1 {
INSERT INTO t1 VALUES(randomblob(1500));
INSERT INTO t1 VALUES(randomblob(1500));
INSERT INTO t2 VALUES(randomblob(1500));
DELETE FROM t1 WHERE rowid=1;
}
sql1 {
BEGIN CONCURRENT;
DELETE FROM t1 WHERE rowid=2;
}
sql2 {
DELETE FROM t2;
}
sql1 COMMIT
} {}
do_test 2.$tn.7 { sql3 { PRAGMA integrity_check } } {ok}
}
#-------------------------------------------------------------------------
# When an CONCURRENT transaction is opened on a database, the nFree and
# iTrunk header fields of the cached version of page 1 are both set
# to 0. This allows an CONCURRENT transaction to use its own private
# free-page-list, which is merged with the main database free-list when
# the transaction is committed.
#
# The following tests check that nFree/iTrunk are correctly restored if
# an CONCURRENT transaction is rolled back, and that savepoint rollbacks
# that occur within CONCURRENT transactions do not incorrectly restore
# these fields to their on-disk values.
#
reset_db
do_execsql_test 3.0 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(x, y);
INSERT INTO t1 VALUES(randomblob(1500), randomblob(1500));
DELETE FROM t1;
} {wal}
do_execsql_test 3.1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(1, 2);
ROLLBACK;
}
do_execsql_test 3.2 { PRAGMA integrity_check } {ok}
do_execsql_test 3.3 { PRAGMA freelist_count } {2}
do_execsql_test 3.4.1 {
BEGIN CONCURRENT;
PRAGMA freelist_count;
} {2}
do_execsql_test 3.4.2 {
SAVEPOINT xyz;
INSERT INTO t1 VALUES(randomblob(1500), NULL);
PRAGMA freelist_count;
} {0}
do_execsql_test 3.4.3 {
ROLLBACK TO xyz;
} {}
do_execsql_test 3.4.4 { PRAGMA freelist_count } {0}
do_execsql_test 3.4.5 { COMMIT; PRAGMA freelist_count } {2}
do_execsql_test 3.4.6 { PRAGMA integrity_check } {ok}
do_execsql_test 3.5.1 {
BEGIN CONCURRENT;
UPDATE t1 SET x=randomblob(10) WHERE y=555;
PRAGMA freelist_count;
} {0}
do_execsql_test 3.5.2 {
ROLLBACK;
PRAGMA freelist_count;
} {2}
do_execsql_test 3.5.3 { PRAGMA integrity_check } {ok}
#-------------------------------------------------------------------------
# Test that nothing goes wrong if an CONCURRENT transaction allocates a
# page at the end of the file, frees it within the same transaction, and
# then has to move the same page to avoid a conflict on COMMIT.
#
do_multiclient_test tn {
do_test 4.$tn.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(x);
CREATE TABLE t2(x);
}
} {wal}
do_test 4.$tn.2 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(randomblob(1500));
INSERT INTO t1 VALUES(randomblob(1500));
DELETE FROM t1 WHERE rowid = 1;
}
sql2 {
INSERT INTO t2 VALUES(randomblob(1500));
INSERT INTO t2 VALUES(randomblob(1500));
INSERT INTO t2 VALUES(randomblob(1500));
INSERT INTO t2 VALUES(randomblob(1500));
DELETE FROM t2 WHERE rowid IN (1, 2);
}
sql1 COMMIT
} {}
}
#-------------------------------------------------------------------------
#
do_multiclient_test tn {
do_test 5.$tn.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(x);
CREATE TABLE t2(x);
INSERT INTO t1 VALUES(randomblob(1500));
PRAGMA page_count;
}
} {wal 4}
do_test 5.$tn.2 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO t2 VALUES(randomblob(1500));
PRAGMA page_count;
}
} {5}
do_test 5.$tn.3 {
sql2 {
DELETE FROM t1;
PRAGMA freelist_count;
PRAGMA page_count;
}
} {1 4}
do_test 5.$tn.4 { sql1 COMMIT } {}
do_test 5.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
}
#-------------------------------------------------------------------------
#
do_multiclient_test tn {
do_test 6.$tn.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(x);
INSERT INTO t1 VALUES(randomblob(1500));
PRAGMA wal_checkpoint;
}
} {wal 0 5 5}
do_test 6.$tn.2 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(randomblob(1500));
INSERT INTO t1 VALUES(randomblob(1500));
}
} {}
do_test 6.$tn.3 {
sql2 {
BEGIN;
INSERT INTO t1 VALUES(randomblob(1500));
INSERT INTO t1 VALUES(randomblob(1500));
COMMIT;
}
} {}
do_test 6.$tn.4 {
list [catch { sql1 COMMIT } msg] $msg
} {1 {database is locked}}
do_test 6.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
do_test 6.$tn.5 { sql3 { SELECT count(*) from t1 } } {3}
}
#-------------------------------------------------------------------------
# Test that if a corrupt wal-index-header is encountered when attempting
# to commit a CONCURRENT transaction, the transaction is not committed
# (or rolled back) and that SQLITE_BUSY_SNAPSHOT is returned to the user.
#
catch { db close }
forcedelete test.db
testvfs tvfs
sqlite3 db test.db -vfs tvfs
do_execsql_test 7.1 {
PRAGMA journal_mode = wal;
BEGIN;
CREATE TABLE t1(a, b, PRIMARY KEY(a));
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(3, 4);
COMMIT;
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(5, 6);
INSERT INTO t1 VALUES(7, 8);
SELECT * FROM t1;
} {wal 1 2 3 4 5 6 7 8}
# Corrupt the wal-index header
incr_tvfs_hdr test.db 11 1
do_catchsql_test 7.2.1 { COMMIT } {1 {database is locked}}
do_test 7.2.2 { sqlite3_extended_errcode db } SQLITE_BUSY_SNAPSHOT
do_execsql_test 7.3.1 {
SELECT * FROM t1;
ROLLBACK;
} {1 2 3 4 5 6 7 8}
do_execsql_test 7.3.2 {
SELECT * FROM t1;
} {1 2 3 4}
#-------------------------------------------------------------------------
# Test that "PRAGMA integrity_check" works within a concurrent
# transaction. Within a concurrent transaction, "PRAGMA integrity_check"
# is unable to detect unused database pages, but can detect other types
# of corruption.
#
reset_db
do_test 8.1 {
execsql {
PRAGMA journal_mode = wal;
CREATE TABLE kv(k INTEGER PRIMARY KEY, v UNIQUE);
INSERT INTO kv VALUES(NULL, randomblob(750));
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
DELETE FROM kv WHERE rowid%2;
}
set v [db one {PRAGMA freelist_count}]
expr $v==33 || $v==34
} {1}
do_execsql_test 8.2 { PRAGMA integrity_check } ok
do_execsql_test 8.3 {
BEGIN CONCURRENT;
PRAGMA integrity_check;
} {ok}
do_execsql_test 8.4 {
INSERT INTO kv VALUES(1100, 1100);
PRAGMA integrity_check;
} {ok}
do_execsql_test 8.5 {
COMMIT;
PRAGMA integrity_check;
} {ok}
#-------------------------------------------------------------------------
# Test that concurrent transactions do not allow foreign-key constraints
# to be bypassed.
#
do_multiclient_test tn {
do_test 9.$tn.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE pp(i INTEGER PRIMARY KEY, j);
CREATE TABLE cc(a, b REFERENCES pp);
WITH seq(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM seq WHERE i<100)
INSERT INTO pp SELECT i, randomblob(1000) FROM seq;
PRAGMA foreign_keys = 1;
}
} {wal}
do_test 9.$tn.2.1 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO cc VALUES(42, 42);
}
} {}
do_test 9.$tn.2.2 {
sql2 { DELETE FROM pp WHERE i=42 }
list [catch { sql1 COMMIT } msg] $msg
} {1 {database is locked}}
do_test 9.$tn.2.3 {
sql1 ROLLBACK
} {}
do_test 9.$tn.3.1 {
sql1 {
PRAGMA foreign_keys = 0;
BEGIN CONCURRENT;
INSERT INTO cc VALUES(43, 43);
}
} {}
do_test 9.$tn.3.2 {
sql2 { DELETE FROM pp WHERE i=43 }
list [catch { sql1 COMMIT } msg] $msg
} {0 {}}
do_test 9.$tn.4.1 {
sql1 {
PRAGMA foreign_keys = on;
BEGIN CONCURRENT;
INSERT INTO cc VALUES(44, 44);
}
} {}
do_test 9.$tn.4.2 {
sql2 { DELETE FROM pp WHERE i=1 }
list [catch { sql1 COMMIT } msg] $msg
} {0 {}}
}
#-------------------------------------------------------------------------
# Test that even if a SELECT statement appears before all writes within
# a CONCURRENT transaction, the pages it reads are still considered when
# considering whether or not the transaction may be committed.
#
do_multiclient_test tn {
do_test 10.$tn.1.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(a);
CREATE TABLE t2(b);
CREATE TABLE t3(c);
INSERT INTO t1 VALUES(1), (2), (3);
INSERT INTO t2 VALUES(1), (2), (3);
INSERT INTO t3 VALUES(1), (2), (3);
}
} {wal}
do_test 10.$tn.1.2 {
sql1 {
BEGIN CONCURRENT;
SELECT * FROM t1;
INSERT INTO t2 VALUES(4);
}
} {1 2 3}
do_test 10.$tn.1.3 {
sql2 { INSERT INTO t1 VALUES(4) }
list [catch {sql1 COMMIT} msg] $msg
} {1 {database is locked}}
sql1 ROLLBACK
# In this case, because the "SELECT * FROM t1" is first stepped before
# the "BEGIN CONCURRENT", the pages it reads are not recorded by the
# pager object. And so the transaction can be committed. Technically
# this behaviour (the effect of an ongoing SELECT on a BEGIN CONCURRENT
# transacation) is undefined.
#
do_test 10.$tn.2.1 {
code1 {
set ::stmt [sqlite3_prepare db "SELECT * FROM t1" -1 dummy]
sqlite3_step $::stmt
}
} {SQLITE_ROW}
do_test 10.$tn.2.2 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO t2 VALUES(4);
}
code1 {
set res [list]
lappend res [sqlite3_column_int $::stmt 0]
while {[sqlite3_step $::stmt]=="SQLITE_ROW"} {
lappend res [sqlite3_column_int $::stmt 0]
}
sqlite3_finalize $::stmt
set res
}
} {1 2 3 4}
do_test 10.$tn.2.3 {
sql2 { INSERT INTO t1 VALUES(5) }
sql1 COMMIT
} {}
# More tests surrounding long-lived prepared statements and concurrent
# transactions.
do_test 10.$tn.3.1 {
sql1 {
BEGIN CONCURRENT;
SELECT * FROM t1;
COMMIT;
}
sql1 {
BEGIN CONCURRENT;
INSERT INTO t2 VALUES(5);
}
sql2 {
INSERT INTO t1 VALUES(5);
}
sql1 COMMIT
sql3 {
SELECT * FROM t2;
}
} {1 2 3 4 5}
do_test 10.$tn.3.2 {
sql1 {
BEGIN CONCURRENT;
SELECT * FROM t1;
ROLLBACK;
}
sql1 {
BEGIN CONCURRENT;
INSERT INTO t2 VALUES(6);
}
sql2 {
INSERT INTO t1 VALUES(6);
}
sql1 COMMIT
sql3 { SELECT * FROM t2 }
} {1 2 3 4 5 6}
do_test 10.$tn.3.3 {
sql1 { BEGIN CONCURRENT }
code1 {
set ::stmt [sqlite3_prepare db "SELECT * FROM t1" -1 dummy]
sqlite3_step $::stmt
}
sql1 {
INSERT INTO t2 VALUES(7);
SELECT * FROM t3;
ROLLBACK;
BEGIN CONCURRENT;
}
sql2 { INSERT INTO t3 VALUES(5) }
code1 { sqlite3_finalize $::stmt }
sql1 {
INSERT INTO t2 VALUES(8);
COMMIT;
}
} {}
}
do_multiclient_test tn {
do_test 11.$tn.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(a);
}
} {wal}
do_test 11.$tn.2 {
code1 { sqlite3_wal_info db main }
} {0 2}
do_test 11.$tn.3 {
sql1 { INSERT INTO t1 VALUES(1) }
code1 { sqlite3_wal_info db main }
} {2 3}
do_test 11.$tn.4 {
sql2 { INSERT INTO t1 VALUES(2) }
code2 { sqlite3_wal_info db2 main }
} {3 4}
do_test 11.$tn.5 {
sql1 { PRAGMA wal_checkpoint }
sql2 { INSERT INTO t1 VALUES(3) }
code2 { sqlite3_wal_info db2 main }
} {0 1}
}
reset_db
do_execsql_test 12.0 {
PRAGMA journal_mode = wal;
CREATE TABLE tx(a INTEGER PRIMARY KEY, b);
} {wal}
do_test 12.1 {
for {set i 0} {$i < 50} {incr i} {
execsql {
BEGIN CONCURRENT;
INSERT INTO tx(b) VALUES( randomblob( 1200 ) );
COMMIT;
}
}
execsql { PRAGMA page_size }
} {1024}
do_execsql_test 12.2 {
DELETE FROM tx;
}
do_test 12.3 {
for {set i 0} {$i < 50} {incr i} {
execsql {
BEGIN CONCURRENT;
INSERT INTO tx(b) VALUES( randomblob( 1200 ) );
COMMIT;
}
}
execsql { PRAGMA page_size }
} {1024}
do_execsql_test 12.4 {
DELETE FROM tx;
}
do_test 12.5 {
execsql { BEGIN CONCURRENT }
for {set i 0} {$i < 5000} {incr i} {
execsql {
INSERT INTO tx(b) VALUES( randomblob( 1200 ) );
}
}
execsql { COMMIT }
execsql { PRAGMA page_size }
} {1024}
finish_test
+231
View File
@@ -0,0 +1,231 @@
# 2015 July 26
#
# 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.
#
#***********************************************************************
#
# Tests for transactions started with BEGIN CONCURRENT. The tests in this
# file focus on testing that deferred page allocation works properly.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl
set ::testprefix concurrent3
if {$AUTOVACUUM} { finish_test ; return }
ifcapable !concurrent {
finish_test
return
}
db close
sqlite3_shutdown
test_sqlite3_log xLog
proc xLog {error_code msg} {
# puts "$error_code: $msg"
# Enable the previous for debugging
}
reset_db
proc create_schema {} {
db eval {
PRAGMA journal_mode = wal;
CREATE TABLE t1(x, y);
CREATE TABLE t2(x, y);
CREATE TABLE t3(x, y);
CREATE TABLE t4(x, y);
CREATE INDEX i1 ON t1(y, x);
CREATE INDEX i2 ON t2(y, x);
CREATE INDEX i3 ON t3(y, x);
CREATE INDEX i4 ON t4(y, x);
}
}
proc do_sql_op {iTbl iOp} {
set db "db$iTbl"
switch $iOp {
"i" {
set sql "
WITH cnt(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM cnt WHERE i<10)
INSERT INTO t$iTbl SELECT randomblob(800), randomblob(800) FROM cnt;
"
}
"d" {
set sql "
DELETE FROM t$iTbl WHERE rowid IN (
SELECT rowid FROM t$iTbl ORDER BY 1 ASC LIMIT 10
)
"
}
"D" {
set sql "
DELETE FROM t$iTbl WHERE rowid IN (
SELECT rowid FROM t$iTbl o WHERE (
SELECT count(*) FROM t$iTbl i WHERE i.rowid<o.rowid
) % 2
)
"
}
"I" {
set sql "
INSERT INTO t$iTbl SELECT randomblob(800), randomblob(800) FROM t$iTbl;
"
}
default {
error "bad iOp parameter: $iOp"
}
}
$db eval $sql
}
set DBLIST {db1 db2 db3 db4}
create_schema
foreach {tn oplist} {
1 {1i 2i 3i 4i}
2 {1iii 2iii 3iii 4iii}
3 {1d 2d 3d 4d}
. -----------------------
4 {1i}
5 {1d 2i}
. -----------------------
6 {1iii 2iii 3iii 4iii}
7 {1di 2id 3iii 4ddd}
8 {1iii 2iii 3iii 4iii}
9 {1D 2II}
10 {1I 2D 3I 4D}
11 {1III 3dddddd 4III}
} {
if {[string range $oplist 0 0]=="-"} {
reset_db
create_schema
continue
}
foreach db $DBLIST { sqlite3 $db test.db }
do_test 1.$tn {
foreach db $DBLIST { $db eval "BEGIN CONCURRENT" }
foreach op $oplist {
set iTbl [string range $op 0 0]
foreach char [split [string range $op 1 end] {}] {
do_sql_op $iTbl $char
}
}
foreach db $DBLIST { $db eval "COMMIT" }
db eval {PRAGMA integrity_check}
} {ok}
foreach db $DBLIST {
$db close
}
}
#-------------------------------------------------------------------------
#
proc create_schema2 {} {
db eval {
PRAGMA journal_mode = wal;
CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
CREATE INDEX i1 ON t1(y);
}
}
proc randint {nMax} {
db eval {SELECT abs(random() % $nMax)}
}
proc do_sql_op2 {db iOp} {
switch -- $iOp {
i {
# Insert 1 rows.
set r [randint 1000000000]
set ::rows($r) 1
#puts "insert row $r"
$db eval { INSERT OR IGNORE INTO t1 VALUES($r, randomblob(50)); }
}
d {
# Insert 1 row
set keys [array names ::rows]
set r [randint [llength $keys]]
set rowid [lindex $keys $r]
$db eval { DELETE FROM t1 WHERE x=$rowid }
unset ::rows($rowid)
}
}
}
foreach {tn nRepeat oplist} {
- - ----------------------------
1 100 { 1iiiiiiiiii }
2 100 { 1i 2d }
3 100 { 1d 2i }
4 50 { 1d 2i 3d }
5 500 { 1i 2i 3i 4i }
6 500 { 1i 2d 3d 4d }
} {
if {[string range $oplist 0 0]=="-"} {
array unset rows
reset_db
create_schema2
continue
}
foreach db $DBLIST {
sqlite3 $db test.db
set stats($db,0) 0
set stats($db,1) 0
}
array unset used
do_test 2.$tn {
for {set i 0} {$i < $nRepeat} {incr i} {
foreach db $DBLIST { $db eval "BEGIN CONCURRENT" }
foreach op $oplist {
set iDb [string range $op 0 0]
set used(db$iDb) 1
foreach char [split [string range $op 1 end] {}] {
do_sql_op2 "db$iDb" $char
}
}
foreach db $DBLIST {
set rc [catch { $db eval COMMIT } msg]
if {$rc} { $db eval ROLLBACK }
incr stats($db,$rc)
}
set res [db eval {PRAGMA integrity_check}]
if {$res != "ok"} { puts "after $db $rc: $res" ; after 1000000 }
}
} {}
foreach db $DBLIST {
$db close
}
# foreach k [lsort [array names used]] {
# puts "$k: $stats($k,0) committed, $stats($k,1) rolled back"
# }
}
finish_test
+194
View File
@@ -0,0 +1,194 @@
# 2017 May 26
#
# 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.
#
#***********************************************************************
#
# Miscellaneous tests for transactions started with BEGIN CONCURRENT.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl
source $testdir/wal_common.tcl
set ::testprefix concurrent4
ifcapable !concurrent {
finish_test
return
}
do_execsql_test 1.0 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(x PRIMARY KEY, y UNIQUE);
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM s;
DELETE FROM t1 WHERE rowid<2;
} {wal}
do_execsql_test 1.1 {
BEGIN CONCURRENT;
INSERT INTO t1(rowid, x, y) VALUES(1000, randomblob(3000), randomblob(3000));
SAVEPOINT abc;
DELETE FROM t1 WHERE rowid = 1000;
}
do_execsql_test 1.2 { ROLLBACK TO abc }
do_execsql_test 1.3 { COMMIT }
do_execsql_test 1.4 { PRAGMA integrity_check } {ok}
do_multiclient_test tn {
do_test 2.$tn.1 {
sql1 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(a, b);
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM s;
CREATE TABLE t2(a, b);
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
INSERT INTO t2 SELECT randomblob(400), randomblob(400) FROM s;
}
sql1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(randomblob(3000), randomblob(3000));
}
} {}
do_test 2.$tn.2 {
sql2 {
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<10)
INSERT INTO t2 SELECT randomblob(400), randomblob(400) FROM s;
}
sql2 {
DELETE FROM t2 WHERE rowid<10;
}
} {}
do_test 2.$tn.3 {
sql1 {
COMMIT;
PRAGMA integrity_check;
}
} {ok}
do_test 2.$tn.4 {
sql2 {
PRAGMA integrity_check;
}
} {ok}
}
reset_db
do_execsql_test 3.1 {
PRAGMA page_size = 1024;
PRAGMA journal_mode = wal;
CREATE TABLE t2(x);
INSERT INTO t2 VALUES(randomblob(5000));
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
INSERT INTO t1 VALUES(25, randomblob(104));
DELETE FROM t2;
} {wal}
do_execsql_test 3.2 {
BEGIN CONCURRENT;
REPLACE INTO t1 VALUES(25, randomblob(1117));
COMMIT;
} {}
#-------------------------------------------------------------------------
# Test the effect of BEGIN CONCURRENT transactions that consist entirely
# of read-only statements.
#
reset_db
do_execsql_test 4.0 {
PRAGMA page_size = 1024;
PRAGMA journal_mode = wal;
CREATE TABLE t4(a, b);
INSERT INTO t4 VALUES(1, 2);
INSERT INTO t4 VALUES(3, 4);
} {wal}
sqlite3 db2 test.db
do_test 4.1.1 {
db eval {
BEGIN CONCURRENT;
INSERT INTO t4 VALUES(5, 6);
}
db2 eval {
BEGIN CONCURRENT;
SELECT * FROM t4;
ROLLBACK;
}
} {1 2 3 4}
do_test 4.1.2 {
db eval { COMMIT }
db2 eval { SELECT * FROM t4 }
} {1 2 3 4 5 6}
do_test 4.2.1 {
db eval {
BEGIN CONCURRENT;
INSERT INTO t4 VALUES(7, 8);
}
db2 eval {
BEGIN CONCURRENT;
SELECT * FROM t4;
COMMIT;
}
} {1 2 3 4 5 6}
do_test 4.2.2 {
db eval { COMMIT }
db2 eval { SELECT * FROM t4 }
} {1 2 3 4 5 6 7 8}
do_test 4.3 {
db2 eval {
BEGIN CONCURRENT;
SELECT * FROM t4;
}
db eval {
BEGIN CONCURRENT;
INSERT INTO t4 VALUES(9, 10);
COMMIT;
}
db2 eval {
SELECT * FROM t4;
COMMIT;
}
} {1 2 3 4 5 6 7 8}
set sz [file size test.db-wal]
do_test 4.4.1 {
db eval {
BEGIN CONCURRENT;
SELECT * FROM t4;
SELECT * FROM sqlite_master;
}
db eval COMMIT
file size test.db-wal
} $sz
do_test 4.4.2 {
db eval {
BEGIN CONCURRENT;
SELECT * FROM t4;
SELECT * FROM sqlite_master;
ROLLBACK;
}
file size test.db-wal
} $sz
finish_test
+133
View File
@@ -0,0 +1,133 @@
# 2017 May 26
#
# 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/wal_common.tcl
set ::testprefix concurrent5
ifcapable !concurrent {
finish_test
return
}
db close
sqlite3_shutdown
test_sqlite3_log [list lappend ::log]
set ::log [list]
sqlite3 db test.db
proc do_test_conflict_msg {tn msg} {
set msg "cannot commit CONCURRENT transaction - [string trim $msg]"
uplevel [list do_test $tn {lindex $::log end} $msg]
}
do_execsql_test 1.0 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(x, y);
CREATE TABLE t2(c);
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM s;
} {wal}
sqlite3 db2 test.db
do_test 1.1.1 {
set ::log [list]
db2 eval {
BEGIN CONCURRENT;
SELECT count(*) FROM t1;
INSERT INTO t2 VALUES(10);
}
db eval {
INSERT INTO t1 VALUES(randomblob(200), randomblob(200));
}
catchsql COMMIT db2
} {1 {database is locked}}
do_test_conflict_msg 1.1.2 {
conflict at page 2 (read-only page; part of db table t1; content=0500000063021100...)
}
do_test 1.2.1 {
set ::log [list]
db2 eval {
ROLLBACK;
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(11, 12);
}
db eval {
INSERT INTO t1 VALUES(12, 11);
}
catchsql COMMIT db2
} {1 {database is locked}}
do_test_conflict_msg 1.2.2 {
conflict at page 105 (read/write page; part of db table t1; content=0D00000002026100...)
}
do_test 1.3.1 {
set ::log [list]
db2 eval {
ROLLBACK;
BEGIN CONCURRENT;
INSERT INTO t2 VALUES('x');
}
db eval {
INSERT INTO t2 VALUES('y');
}
catchsql COMMIT db2
} {1 {database is locked}}
do_test_conflict_msg 1.3.2 {
conflict at page 3 (read/write page; part of db table t2; content=0D0000000103FB00...)
}
do_test 1.4.1 {
set ::log [list]
execsql {
ROLLBACK;
CREATE TABLE t3(a INTEGER PRIMARY KEY, b INTEGER);
CREATE INDEX i3 ON t3(b);
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<5000
) INSERT INTO t3 SELECT i, i FROM s;
BEGIN CONCURRENT;
INSERT INTO t3 VALUES(0, 5001);
} db2
execsql { INSERT INTO t3 VALUES(NULL, 5002) } db
catchsql COMMIT db2
} {1 {database is locked}}
do_test_conflict_msg 1.3.2 {
conflict at page 211 (read/write page; part of db index t3.i3; content=0A0310006300D800...)
}
db close
db2 close
sqlite3_shutdown
test_sqlite3_log
sqlite3_initialize
finish_test
+60
View File
@@ -0,0 +1,60 @@
# 2017 May 26
#
# 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/wal_common.tcl
set ::testprefix concurrent6
ifcapable !concurrent {
finish_test
return
}
sqlite3 db2 test.db
do_execsql_test 1.0 {
PRAGMA page_size = 1024;
PRAGMA journal_mode = wal;
CREATE TABLE t1(x);
CREATE TABLE t2(x);
CREATE TABLE t3(x);
CREATE TABLE t4(x);
INSERT INTO t1 VALUES(zeroblob(1500));
} {wal}
do_execsql_test -db db2 1.1 {
BEGIN CONCURRENT;
INSERT INTO t3 VALUES(zeroblob(4000));
DELETE FROM t1;
}
do_execsql_test 1.2 {
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
INSERT INTO t2 SELECT zeroblob(1000) FROM s;
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
INSERT INTO t4 SELECT zeroblob(1000) FROM s;
DELETE FROM t4;
}
do_execsql_test -db db2 1.3 {
COMMIT;
}
finish_test
+52
View File
@@ -0,0 +1,52 @@
# 2018 Jan 5
#
# 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
set ::testprefix concurrent7
sqlite3 db2 test.db
do_execsql_test 1 {
PRAGMA journal_mode = wal;
CREATE TABLE t1(x);
CREATE TABLE t2(x);
} {wal}
do_execsql_test -db db2 2 {
SELECT * FROM t1;
}
do_execsql_test 3 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(randomblob(1500));
INSERT INTO t1 VALUES(randomblob(1500));
DELETE FROM t1 WHERE rowid = 1;
}
do_execsql_test -db db2 4 {
INSERT INTO t2 VALUES(randomblob(1500));
INSERT INTO t2 VALUES(randomblob(1500));
INSERT INTO t2 VALUES(randomblob(1500));
INSERT INTO t2 VALUES(randomblob(1500));
DELETE FROM t2 WHERE rowid IN (1, 2);
}
do_execsql_test 5 {
COMMIT;
PRAGMA integrity_check;
} {ok}
finish_test
+109
View File
@@ -0,0 +1,109 @@
# 2020 July 17
#
# 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
set ::testprefix concurrent8
source $testdir/lock_common.tcl
ifcapable !concurrent {
finish_test
return
}
do_multiclient_test tn {
do_test 1.$tn.0 {
sql1 {
CREATE TABLE t1(x, y);
PRAGMA journal_mode = wal;
}
} {wal}
do_test 1.$tn.1 {
sql1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(1, 1);
}
} {}
do_test 1.$tn.2 {
sql2 {
CREATE TABLE t2(a, b);
}
} {}
do_test 1.$tn.3 {
list [catch { sql1 { COMMIT } } msg] $msg
} {1 {database is locked}}
do_test 1.$tn.4 {
code1 { db errorcode }
} {517} ;# SQLITE_BUSY_SNAPSHOT
do_test 1.$tn.5 {
sql1 {
ROLLBACK;
BEGIN CONCURRENT;
CREATE TABLE t3(a, b);
COMMIT;
}
} {}
do_test 1.$tn.6 {
set nPg [sql1 {PRAGMA page_count}]
sql1 "BEGIN CONCURRENT"
for {set i 0} {$i<250} {incr i} {
sql1 "CREATE TABLE z$i (a, b, c)"
}
sql1 "COMMIT"
set nPg2 [sql1 {PRAGMA page_count}]
expr $nPg2>$nPg
} {1}
do_test 1.$tn.7 {
sql2 { PRAGMA integrity_check }
} {ok}
do_test 1.$tn.8 {
sql1 {
BEGIN CONCURRENT;
CREATE TABLE t4(a, b);
}
sql2 {
INSERT INTO t1 VALUES(2, 2);
}
list [catch { sql1 COMMIT } msg] $msg
} {1 {database is locked}}
sql1 ROLLBACK
do_test 1.$tn.9 {
sql1 {
BEGIN CONCURRENT;
CREATE TEMP TABLE t5(a, b);
INSERT INTO t2 VALUES('x', 'x');
}
sql2 {
INSERT INTO t1 VALUES(3, 3);
CREATE TEMP TABLE t1(x, y);
}
sql1 COMMIT
} {}
}
finish_test
+36
View File
@@ -0,0 +1,36 @@
# 2020 September 01
#
# 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
set testprefix noop_update
if {[db eval {PRAGMA noop_update}]==""} {
finish_test
return
}
do_execsql_test 1.0 {
CREATE TABLE t1(x, y);
INSERT INTO t1 VALUES('a', 111);
}
do_execsql_test 1.1 {
UPDATE t1 SET y=222 WHERE x='a';
SELECT * FROM t1;
} {a 222}
do_execsql_test 1.2 {
PRAGMA noop_update = 1;
UPDATE t1 SET y=333 WHERE x='a';
SELECT * FROM t1;
} {a 222}
finish_test
-103
View File
@@ -1,103 +0,0 @@
# 2014-08-24
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library.
# The focus of this script is testing details of the SQL language parser.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
do_catchsql_test parser1-1.1 {
CREATE TABLE t1(
a TEXT PRIMARY KEY,
b TEXT,
FOREIGN KEY(b COLLATE nocase DESC) REFERENCES t1(a COLLATE binary ASC)
);
} {1 {syntax error after column name "b"}}
# Verify that a legacy schema in the sqlite_master file is allowed to have
# COLLATE, ASC, and DESC keywords on the id list of a FK constraint, and that
# those keywords are silently ignored.
#
sqlite3_db_config db DEFENSIVE 0
do_execsql_test parser1-1.2 {
CREATE TABLE t1(
a TEXT PRIMARY KEY,
b TEXT,
FOREIGN KEY(b) REFERENCES t1(a)
);
INSERT INTO t1 VALUES('abc',NULL),('xyz','abc');
PRAGMA writable_schema=on;
UPDATE sqlite_master SET sql='CREATE TABLE t1(
a TEXT PRIMARY KEY,
b TEXT,
FOREIGN KEY(b COLLATE nocase) REFERENCES t1(a)
)' WHERE name='t1';
SELECT name FROM sqlite_master WHERE sql LIKE '%collate%';
} {t1}
sqlite3 db2 test.db
do_test parser1-1.3 {
sqlite3 db2 test.db
db2 eval {SELECT * FROM t1 ORDER BY 1}
} {abc {} xyz abc}
db2 close
do_execsql_test parser1-1.4 {
UPDATE sqlite_master SET sql='CREATE TABLE t1(
a TEXT PRIMARY KEY,
b TEXT,
FOREIGN KEY(b ASC) REFERENCES t1(a)
)' WHERE name='t1';
SELECT name FROM sqlite_master WHERE sql LIKE '%ASC%';
} {t1}
sqlite3 db2 test.db
do_test parser1-1.5 {
sqlite3 db2 test.db
db2 eval {SELECT * FROM t1 ORDER BY 1}
} {abc {} xyz abc}
db2 close
do_catchsql_test parser1-2.1 {
WITH RECURSIVE
c(x COLLATE binary) AS (VALUES(1) UNION SELECT x+1 FROM c WHERE x<5)
SELECT x FROM c;
} {1 {syntax error after column name "x"}}
do_catchsql_test parser1-2.2 {
WITH RECURSIVE
c(x ASC) AS (VALUES(1) UNION SELECT x+1 FROM c WHERE x<5)
SELECT x FROM c;
} {1 {syntax error after column name "x"}}
# Verify that the comma between multiple table constraints is
# optional.
#
# The missing comma is technically a syntax error. But we have to support
# it because there might be legacy databases that omit the commas in their
# sqlite_master tables.
#
do_execsql_test parser1-3.1 {
CREATE TABLE t300(id INTEGER PRIMARY KEY);
CREATE TABLE t301(
id INTEGER PRIMARY KEY,
c1 INTEGER NOT NULL,
c2 INTEGER NOT NULL,
c3 BOOLEAN NOT NULL DEFAULT 0,
FOREIGN KEY(c1) REFERENCES t300(id) ON DELETE CASCADE ON UPDATE RESTRICT
/* no comma */
FOREIGN KEY(c2) REFERENCES t300(id) ON DELETE CASCADE ON UPDATE RESTRICT
/* no comma */
UNIQUE(c1, c2)
);
PRAGMA foreign_key_list(t301);
} {0 0 t300 c2 id RESTRICT CASCADE NONE 1 0 t300 c1 id RESTRICT CASCADE NONE}
finish_test
+1 -866
View File
@@ -32,885 +32,20 @@
** The exit status is non-zero if any test fails.
*/
/*
** The "Set Error Line" macro.
*/
#define SEL(e) ((e)->iLine = ((e)->rc ? (e)->iLine : __LINE__))
/* Database functions */
#define opendb(w,x,y,z) (SEL(w), opendb_x(w,x,y,z))
#define closedb(y,z) (SEL(y), closedb_x(y,z))
/* Functions to execute SQL */
#define sql_script(x,y,z) (SEL(x), sql_script_x(x,y,z))
#define integrity_check(x,y) (SEL(x), integrity_check_x(x,y))
#define execsql_i64(x,y,...) (SEL(x), execsql_i64_x(x,y,__VA_ARGS__))
#define execsql_text(x,y,z,...) (SEL(x), execsql_text_x(x,y,z,__VA_ARGS__))
#define execsql(x,y,...) (SEL(x), (void)execsql_i64_x(x,y,__VA_ARGS__))
#define sql_script_printf(x,y,z,...) ( \
SEL(x), sql_script_printf_x(x,y,z,__VA_ARGS__) \
)
/* Thread functions */
#define launch_thread(w,x,y,z) (SEL(w), launch_thread_x(w,x,y,z))
#define join_all_threads(y,z) (SEL(y), join_all_threads_x(y,z))
/* Timer functions */
#define setstoptime(y,z) (SEL(y), setstoptime_x(y,z))
#define timetostop(z) (SEL(z), timetostop_x(z))
/* Report/clear errors. */
#define test_error(z, ...) test_error_x(z, sqlite3_mprintf(__VA_ARGS__))
#define clear_error(y,z) clear_error_x(y, z)
/* File-system operations */
#define filesize(y,z) (SEL(y), filesize_x(y,z))
#define filecopy(x,y,z) (SEL(x), filecopy_x(x,y,z))
#define PTR2INT(x) ((int)((intptr_t)x))
#define INT2PTR(x) ((void*)((intptr_t)x))
/*
** End of test code/infrastructure interface macros.
*************************************************************************/
#include <sqlite3.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include "test_multiplex.h"
#include "tt3_core.c"
/* Required to link test_multiplex.c */
#ifndef SQLITE_OMIT_WSD
int sqlite3PendingByte = 0x40000000;
#endif
/*
* This code implements the MD5 message-digest algorithm.
* The algorithm is due to Ron Rivest. This code was
* written by Colin Plumb in 1993, no copyright is claimed.
* This code is in the public domain; do with it what you wish.
*
* Equivalent code is available from RSA Data Security, Inc.
* This code has been tested against that, and is equivalent,
* except that you don't need to include two pages of legalese
* with every copy.
*
* To compute the message digest of a chunk of bytes, declare an
* MD5Context structure, pass it to MD5Init, call MD5Update as
* needed on buffers full of bytes, and then call MD5Final, which
* will fill a supplied 16-byte array with the digest.
*/
/*
* If compiled on a machine that doesn't have a 32-bit integer,
* you just set "uint32" to the appropriate datatype for an
* unsigned 32-bit integer. For example:
*
* cc -Duint32='unsigned long' md5.c
*
*/
#ifndef uint32
# define uint32 unsigned int
#endif
struct MD5Context {
int isInit;
uint32 buf[4];
uint32 bits[2];
union {
unsigned char in[64];
uint32 in32[16];
} u;
};
typedef struct MD5Context MD5Context;
/*
* Note: this code is harmless on little-endian machines.
*/
static void byteReverse (unsigned char *buf, unsigned longs){
uint32 t;
do {
t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 |
((unsigned)buf[1]<<8 | buf[0]);
*(uint32 *)buf = t;
buf += 4;
} while (--longs);
}
/* The four core functions - F1 is optimized somewhat */
/* #define F1(x, y, z) (x & y | ~x & z) */
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
/* This is the central step in the MD5 algorithm. */
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
/*
* The core of the MD5 algorithm, this alters an existing MD5 hash to
* reflect the addition of 16 longwords of new data. MD5Update blocks
* the data and converts bytes into longwords for this routine.
*/
static void MD5Transform(uint32 buf[4], const uint32 in[16]){
register uint32 a, b, c, d;
a = buf[0];
b = buf[1];
c = buf[2];
d = buf[3];
MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7);
MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7);
MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7);
MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7);
MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);
MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5);
MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9);
MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5);
MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9);
MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5);
MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9);
MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5);
MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9);
MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);
MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4);
MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4);
MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4);
MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4);
MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);
MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6);
MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6);
MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6);
MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6);
MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);
buf[0] += a;
buf[1] += b;
buf[2] += c;
buf[3] += d;
}
/*
* Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
* initialization constants.
*/
static void MD5Init(MD5Context *ctx){
ctx->isInit = 1;
ctx->buf[0] = 0x67452301;
ctx->buf[1] = 0xefcdab89;
ctx->buf[2] = 0x98badcfe;
ctx->buf[3] = 0x10325476;
ctx->bits[0] = 0;
ctx->bits[1] = 0;
}
/*
* Update context to reflect the concatenation of another buffer full
* of bytes.
*/
static
void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len){
uint32 t;
/* Update bitcount */
t = ctx->bits[0];
if ((ctx->bits[0] = t + ((uint32)len << 3)) < t)
ctx->bits[1]++; /* Carry from low to high */
ctx->bits[1] += len >> 29;
t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
/* Handle any leading odd-sized chunks */
if ( t ) {
unsigned char *p = (unsigned char *)ctx->u.in + t;
t = 64-t;
if (len < t) {
memcpy(p, buf, len);
return;
}
memcpy(p, buf, t);
byteReverse(ctx->u.in, 16);
MD5Transform(ctx->buf, (uint32 *)ctx->u.in);
buf += t;
len -= t;
}
/* Process data in 64-byte chunks */
while (len >= 64) {
memcpy(ctx->u.in, buf, 64);
byteReverse(ctx->u.in, 16);
MD5Transform(ctx->buf, (uint32 *)ctx->u.in);
buf += 64;
len -= 64;
}
/* Handle any remaining bytes of data. */
memcpy(ctx->u.in, buf, len);
}
/*
* Final wrapup - pad to 64-byte boundary with the bit pattern
* 1 0* (64-bit count of bits processed, MSB-first)
*/
static void MD5Final(unsigned char digest[16], MD5Context *ctx){
unsigned count;
unsigned char *p;
/* Compute number of bytes mod 64 */
count = (ctx->bits[0] >> 3) & 0x3F;
/* Set the first char of padding to 0x80. This is safe since there is
always at least one byte free */
p = ctx->u.in + count;
*p++ = 0x80;
/* Bytes of padding needed to make 64 bytes */
count = 64 - 1 - count;
/* Pad out to 56 mod 64 */
if (count < 8) {
/* Two lots of padding: Pad the first block to 64 bytes */
memset(p, 0, count);
byteReverse(ctx->u.in, 16);
MD5Transform(ctx->buf, (uint32 *)ctx->u.in);
/* Now fill the next block with 56 bytes */
memset(ctx->u.in, 0, 56);
} else {
/* Pad block to 56 bytes */
memset(p, 0, count-8);
}
byteReverse(ctx->u.in, 14);
/* Append length in bits and transform */
ctx->u.in32[14] = ctx->bits[0];
ctx->u.in32[15] = ctx->bits[1];
MD5Transform(ctx->buf, (uint32 *)ctx->u.in);
byteReverse((unsigned char *)ctx->buf, 4);
memcpy(digest, ctx->buf, 16);
memset(ctx, 0, sizeof(*ctx)); /* In case it is sensitive */
}
/*
** Convert a 128-bit MD5 digest into a 32-digit base-16 number.
*/
static void MD5DigestToBase16(unsigned char *digest, char *zBuf){
static char const zEncode[] = "0123456789abcdef";
int i, j;
for(j=i=0; i<16; i++){
int a = digest[i];
zBuf[j++] = zEncode[(a>>4)&0xf];
zBuf[j++] = zEncode[a & 0xf];
}
zBuf[j] = 0;
}
/*
** During testing, the special md5sum() aggregate function is available.
** inside SQLite. The following routines implement that function.
*/
static void md5step(sqlite3_context *context, int argc, sqlite3_value **argv){
MD5Context *p;
int i;
if( argc<1 ) return;
p = sqlite3_aggregate_context(context, sizeof(*p));
if( p==0 ) return;
if( !p->isInit ){
MD5Init(p);
}
for(i=0; i<argc; i++){
const char *zData = (char*)sqlite3_value_text(argv[i]);
if( zData ){
MD5Update(p, (unsigned char*)zData, strlen(zData));
}
}
}
static void md5finalize(sqlite3_context *context){
MD5Context *p;
unsigned char digest[16];
char zBuf[33];
p = sqlite3_aggregate_context(context, sizeof(*p));
MD5Final(digest,p);
MD5DigestToBase16(digest, zBuf);
sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
}
/*
** End of copied md5sum() code.
**************************************************************************/
typedef sqlite3_int64 i64;
typedef struct Error Error;
typedef struct Sqlite Sqlite;
typedef struct Statement Statement;
typedef struct Threadset Threadset;
typedef struct Thread Thread;
/* Total number of errors in this process so far. */
static int nGlobalErr = 0;
struct Error {
int rc;
int iLine;
char *zErr;
};
struct Sqlite {
sqlite3 *db; /* Database handle */
Statement *pCache; /* Linked list of cached statements */
int nText; /* Size of array at aText[] */
char **aText; /* Stored text results */
};
struct Statement {
sqlite3_stmt *pStmt; /* Pre-compiled statement handle */
Statement *pNext; /* Next statement in linked-list */
};
struct Thread {
int iTid; /* Thread number within test */
void* pArg; /* Pointer argument passed by caller */
pthread_t tid; /* Thread id */
char *(*xProc)(int, void*); /* Thread main proc */
Thread *pNext; /* Next in this list of threads */
};
struct Threadset {
int iMaxTid; /* Largest iTid value allocated so far */
Thread *pThread; /* Linked list of threads */
};
static void free_err(Error *p){
sqlite3_free(p->zErr);
p->zErr = 0;
p->rc = 0;
}
static void print_err(Error *p){
if( p->rc!=SQLITE_OK ){
int isWarn = 0;
if( p->rc==SQLITE_SCHEMA ) isWarn = 1;
if( sqlite3_strglob("* - no such table: *",p->zErr)==0 ) isWarn = 1;
printf("%s: (%d) \"%s\" at line %d\n", isWarn ? "Warning" : "Error",
p->rc, p->zErr, p->iLine);
if( !isWarn ) nGlobalErr++;
fflush(stdout);
}
}
static void print_and_free_err(Error *p){
print_err(p);
free_err(p);
}
static void system_error(Error *pErr, int iSys){
pErr->rc = iSys;
pErr->zErr = (char *)sqlite3_malloc(512);
strerror_r(iSys, pErr->zErr, 512);
pErr->zErr[511] = '\0';
}
static void sqlite_error(
Error *pErr,
Sqlite *pDb,
const char *zFunc
){
pErr->rc = sqlite3_errcode(pDb->db);
pErr->zErr = sqlite3_mprintf(
"sqlite3_%s() - %s (%d)", zFunc, sqlite3_errmsg(pDb->db),
sqlite3_extended_errcode(pDb->db)
);
}
static void test_error_x(
Error *pErr,
char *zErr
){
if( pErr->rc==SQLITE_OK ){
pErr->rc = 1;
pErr->zErr = zErr;
}else{
sqlite3_free(zErr);
}
}
static void clear_error_x(
Error *pErr,
int rc
){
if( pErr->rc==rc ){
pErr->rc = SQLITE_OK;
sqlite3_free(pErr->zErr);
pErr->zErr = 0;
}
}
static int busyhandler(void *pArg, int n){
usleep(10*1000);
return 1;
}
static void opendb_x(
Error *pErr, /* IN/OUT: Error code */
Sqlite *pDb, /* OUT: Database handle */
const char *zFile, /* Database file name */
int bDelete /* True to delete db file before opening */
){
if( pErr->rc==SQLITE_OK ){
int rc;
int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI;
if( bDelete ) unlink(zFile);
rc = sqlite3_open_v2(zFile, &pDb->db, flags, 0);
if( rc ){
sqlite_error(pErr, pDb, "open");
sqlite3_close(pDb->db);
pDb->db = 0;
}else{
sqlite3_create_function(
pDb->db, "md5sum", -1, SQLITE_UTF8, 0, 0, md5step, md5finalize
);
sqlite3_busy_handler(pDb->db, busyhandler, 0);
sqlite3_exec(pDb->db, "PRAGMA synchronous=OFF", 0, 0, 0);
}
}
}
static void closedb_x(
Error *pErr, /* IN/OUT: Error code */
Sqlite *pDb /* OUT: Database handle */
){
int rc;
int i;
Statement *pIter;
Statement *pNext;
for(pIter=pDb->pCache; pIter; pIter=pNext){
pNext = pIter->pNext;
sqlite3_finalize(pIter->pStmt);
sqlite3_free(pIter);
}
for(i=0; i<pDb->nText; i++){
sqlite3_free(pDb->aText[i]);
}
sqlite3_free(pDb->aText);
rc = sqlite3_close(pDb->db);
if( rc && pErr->rc==SQLITE_OK ){
pErr->zErr = sqlite3_mprintf("%s", sqlite3_errmsg(pDb->db));
}
memset(pDb, 0, sizeof(Sqlite));
}
static void sql_script_x(
Error *pErr, /* IN/OUT: Error code */
Sqlite *pDb, /* Database handle */
const char *zSql /* SQL script to execute */
){
if( pErr->rc==SQLITE_OK ){
pErr->rc = sqlite3_exec(pDb->db, zSql, 0, 0, &pErr->zErr);
}
}
static void sql_script_printf_x(
Error *pErr, /* IN/OUT: Error code */
Sqlite *pDb, /* Database handle */
const char *zFormat, /* SQL printf format string */
... /* Printf args */
){
va_list ap; /* ... printf arguments */
va_start(ap, zFormat);
if( pErr->rc==SQLITE_OK ){
char *zSql = sqlite3_vmprintf(zFormat, ap);
pErr->rc = sqlite3_exec(pDb->db, zSql, 0, 0, &pErr->zErr);
sqlite3_free(zSql);
}
va_end(ap);
}
static Statement *getSqlStatement(
Error *pErr, /* IN/OUT: Error code */
Sqlite *pDb, /* Database handle */
const char *zSql /* SQL statement */
){
Statement *pRet;
int rc;
for(pRet=pDb->pCache; pRet; pRet=pRet->pNext){
if( 0==strcmp(sqlite3_sql(pRet->pStmt), zSql) ){
return pRet;
}
}
pRet = sqlite3_malloc(sizeof(Statement));
rc = sqlite3_prepare_v2(pDb->db, zSql, -1, &pRet->pStmt, 0);
if( rc!=SQLITE_OK ){
sqlite_error(pErr, pDb, "prepare_v2");
return 0;
}
assert( 0==strcmp(sqlite3_sql(pRet->pStmt), zSql) );
pRet->pNext = pDb->pCache;
pDb->pCache = pRet;
return pRet;
}
static sqlite3_stmt *getAndBindSqlStatement(
Error *pErr, /* IN/OUT: Error code */
Sqlite *pDb, /* Database handle */
va_list ap /* SQL followed by parameters */
){
Statement *pStatement; /* The SQLite statement wrapper */
sqlite3_stmt *pStmt; /* The SQLite statement to return */
int i; /* Used to iterate through parameters */
pStatement = getSqlStatement(pErr, pDb, va_arg(ap, const char *));
if( !pStatement ) return 0;
pStmt = pStatement->pStmt;
for(i=1; i<=sqlite3_bind_parameter_count(pStmt); i++){
const char *zName = sqlite3_bind_parameter_name(pStmt, i);
void * pArg = va_arg(ap, void*);
switch( zName[1] ){
case 'i':
sqlite3_bind_int64(pStmt, i, *(i64 *)pArg);
break;
default:
pErr->rc = 1;
pErr->zErr = sqlite3_mprintf("Cannot discern type: \"%s\"", zName);
pStmt = 0;
break;
}
}
return pStmt;
}
static i64 execsql_i64_x(
Error *pErr, /* IN/OUT: Error code */
Sqlite *pDb, /* Database handle */
... /* SQL and pointers to parameter values */
){
i64 iRet = 0;
if( pErr->rc==SQLITE_OK ){
sqlite3_stmt *pStmt; /* SQL statement to execute */
va_list ap; /* ... arguments */
va_start(ap, pDb);
pStmt = getAndBindSqlStatement(pErr, pDb, ap);
if( pStmt ){
int first = 1;
while( SQLITE_ROW==sqlite3_step(pStmt) ){
if( first && sqlite3_column_count(pStmt)>0 ){
iRet = sqlite3_column_int64(pStmt, 0);
}
first = 0;
}
if( SQLITE_OK!=sqlite3_reset(pStmt) ){
sqlite_error(pErr, pDb, "reset");
}
}
va_end(ap);
}
return iRet;
}
static char * execsql_text_x(
Error *pErr, /* IN/OUT: Error code */
Sqlite *pDb, /* Database handle */
int iSlot, /* Db handle slot to store text in */
... /* SQL and pointers to parameter values */
){
char *zRet = 0;
if( iSlot>=pDb->nText ){
int nByte = sizeof(char *)*(iSlot+1);
pDb->aText = (char **)sqlite3_realloc(pDb->aText, nByte);
memset(&pDb->aText[pDb->nText], 0, sizeof(char*)*(iSlot+1-pDb->nText));
pDb->nText = iSlot+1;
}
if( pErr->rc==SQLITE_OK ){
sqlite3_stmt *pStmt; /* SQL statement to execute */
va_list ap; /* ... arguments */
va_start(ap, iSlot);
pStmt = getAndBindSqlStatement(pErr, pDb, ap);
if( pStmt ){
int first = 1;
while( SQLITE_ROW==sqlite3_step(pStmt) ){
if( first && sqlite3_column_count(pStmt)>0 ){
zRet = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
sqlite3_free(pDb->aText[iSlot]);
pDb->aText[iSlot] = zRet;
}
first = 0;
}
if( SQLITE_OK!=sqlite3_reset(pStmt) ){
sqlite_error(pErr, pDb, "reset");
}
}
va_end(ap);
}
return zRet;
}
static void integrity_check_x(
Error *pErr, /* IN/OUT: Error code */
Sqlite *pDb /* Database handle */
){
if( pErr->rc==SQLITE_OK ){
Statement *pStatement; /* Statement to execute */
char *zErr = 0; /* Integrity check error */
pStatement = getSqlStatement(pErr, pDb, "PRAGMA integrity_check");
if( pStatement ){
sqlite3_stmt *pStmt = pStatement->pStmt;
while( SQLITE_ROW==sqlite3_step(pStmt) ){
const char *z = (const char*)sqlite3_column_text(pStmt, 0);
if( strcmp(z, "ok") ){
if( zErr==0 ){
zErr = sqlite3_mprintf("%s", z);
}else{
zErr = sqlite3_mprintf("%z\n%s", zErr, z);
}
}
}
sqlite3_reset(pStmt);
if( zErr ){
pErr->zErr = zErr;
pErr->rc = 1;
}
}
}
}
static void *launch_thread_main(void *pArg){
Thread *p = (Thread *)pArg;
return (void *)p->xProc(p->iTid, p->pArg);
}
static void launch_thread_x(
Error *pErr, /* IN/OUT: Error code */
Threadset *pThreads, /* Thread set */
char *(*xProc)(int, void*), /* Proc to run */
void *pArg /* Argument passed to thread proc */
){
if( pErr->rc==SQLITE_OK ){
int iTid = ++pThreads->iMaxTid;
Thread *p;
int rc;
p = (Thread *)sqlite3_malloc(sizeof(Thread));
memset(p, 0, sizeof(Thread));
p->iTid = iTid;
p->pArg = pArg;
p->xProc = xProc;
rc = pthread_create(&p->tid, NULL, launch_thread_main, (void *)p);
if( rc!=0 ){
system_error(pErr, rc);
sqlite3_free(p);
}else{
p->pNext = pThreads->pThread;
pThreads->pThread = p;
}
}
}
static void join_all_threads_x(
Error *pErr, /* IN/OUT: Error code */
Threadset *pThreads /* Thread set */
){
Thread *p;
Thread *pNext;
for(p=pThreads->pThread; p; p=pNext){
void *ret;
pNext = p->pNext;
int rc;
rc = pthread_join(p->tid, &ret);
if( rc!=0 ){
if( pErr->rc==SQLITE_OK ) system_error(pErr, rc);
}else{
printf("Thread %d says: %s\n", p->iTid, (ret==0 ? "..." : (char *)ret));
fflush(stdout);
}
sqlite3_free(p);
}
pThreads->pThread = 0;
}
static i64 filesize_x(
Error *pErr,
const char *zFile
){
i64 iRet = 0;
if( pErr->rc==SQLITE_OK ){
struct stat sStat;
if( stat(zFile, &sStat) ){
iRet = -1;
}else{
iRet = sStat.st_size;
}
}
return iRet;
}
static void filecopy_x(
Error *pErr,
const char *zFrom,
const char *zTo
){
if( pErr->rc==SQLITE_OK ){
i64 nByte = filesize_x(pErr, zFrom);
if( nByte<0 ){
test_error_x(pErr, sqlite3_mprintf("no such file: %s", zFrom));
}else{
i64 iOff;
char aBuf[1024];
int fd1;
int fd2;
unlink(zTo);
fd1 = open(zFrom, O_RDONLY);
if( fd1<0 ){
system_error(pErr, errno);
return;
}
fd2 = open(zTo, O_RDWR|O_CREAT|O_EXCL, 0644);
if( fd2<0 ){
system_error(pErr, errno);
close(fd1);
return;
}
iOff = 0;
while( iOff<nByte ){
int nCopy = sizeof(aBuf);
if( nCopy+iOff>nByte ){
nCopy = nByte - iOff;
}
if( nCopy!=read(fd1, aBuf, nCopy) ){
system_error(pErr, errno);
break;
}
if( nCopy!=write(fd2, aBuf, nCopy) ){
system_error(pErr, errno);
break;
}
iOff += nCopy;
}
close(fd1);
close(fd2);
}
}
}
/*
** Used by setstoptime() and timetostop().
*/
static double timelimit = 0.0;
static double currentTime(void){
double t;
static sqlite3_vfs *pTimelimitVfs = 0;
if( pTimelimitVfs==0 ) pTimelimitVfs = sqlite3_vfs_find(0);
if( pTimelimitVfs->iVersion>=2 && pTimelimitVfs->xCurrentTimeInt64!=0 ){
sqlite3_int64 tm;
pTimelimitVfs->xCurrentTimeInt64(pTimelimitVfs, &tm);
t = tm/86400000.0;
}else{
pTimelimitVfs->xCurrentTime(pTimelimitVfs, &t);
}
return t;
}
static void setstoptime_x(
Error *pErr, /* IN/OUT: Error code */
int nMs /* Milliseconds until "stop time" */
){
if( pErr->rc==SQLITE_OK ){
double t = currentTime();
timelimit = t + ((double)nMs)/(1000.0*60.0*60.0*24.0);
}
}
static int timetostop_x(
Error *pErr /* IN/OUT: Error code */
){
int ret = 1;
if( pErr->rc==SQLITE_OK ){
double t = currentTime();
ret = (t >= timelimit);
}
return ret;
}
/*************************************************************************
**************************************************************************
+1035
View File
File diff suppressed because it is too large Load Diff
-37
View File
@@ -35,43 +35,6 @@ proc cond_incr_sync_count {adj} {
}
}
proc set_tvfs_hdr {file args} {
# Set $nHdr to the number of bytes in the wal-index header:
set nHdr 48
set nInt [expr {$nHdr/4}]
if {[llength $args]>2} {
error {wrong # args: should be "set_tvfs_hdr fileName ?val1? ?val2?"}
}
set blob [tvfs shm $file]
if {$::tcl_platform(byteOrder)=="bigEndian"} {set fmt I} {set fmt i}
if {[llength $args]} {
set ia [lindex $args 0]
set ib $ia
if {[llength $args]==2} {
set ib [lindex $args 1]
}
binary scan $blob a[expr $nHdr*2]a* dummy tail
set blob [binary format ${fmt}${nInt}${fmt}${nInt}a* $ia $ib $tail]
tvfs shm $file $blob
}
binary scan $blob ${fmt}${nInt} ints
return $ints
}
proc incr_tvfs_hdr {file idx incrval} {
set ints [set_tvfs_hdr $file]
set v [lindex $ints $idx]
incr v $incrval
lset ints $idx $v
set_tvfs_hdr $file $ints
}
#-------------------------------------------------------------------------
# Test case wal2-1.*:
#
+40
View File
@@ -90,4 +90,44 @@ proc wal_fix_walindex_cksum {hdrvar} {
lset hdr 11 $c2
}
# This command assumes that $file is the name of a database file opened
# in wal mode using a [testvfs] VFS. It returns a list of the 12 32-bit
# integers that make up the wal-index-header for the named file.
#
proc set_tvfs_hdr {file args} {
# Set $nHdr to the number of bytes in the wal-index header:
set nHdr 48
set nInt [expr {$nHdr/4}]
if {[llength $args]>2} {
error {wrong # args: should be "set_tvfs_hdr fileName ?val1? ?val2?"}
}
set blob [tvfs shm $file]
if {$::tcl_platform(byteOrder)=="bigEndian"} {set fmt I} {set fmt i}
if {[llength $args]} {
set ia [lindex $args 0]
set ib $ia
if {[llength $args]==2} {
set ib [lindex $args 1]
}
binary scan $blob a[expr $nHdr*2]a* dummy tail
set blob [binary format ${fmt}${nInt}${fmt}${nInt}a* $ia $ib $tail]
tvfs shm $file $blob
}
binary scan $blob ${fmt}${nInt} ints
return $ints
}
proc incr_tvfs_hdr {file idx incrval} {
set ints [set_tvfs_hdr $file]
set v [lindex $ints $idx]
incr v $incrval
lset ints $idx $v
set_tvfs_hdr $file $ints
}
+6
View File
@@ -107,6 +107,12 @@ set pragma_def {
IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
IF: defined(SQLITE_DEBUG)
NAME: noop_update
TYPE: FLAG
ARG: SQLITE_NoopUpdate
IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
IF: defined(SQLITE_ENABLE_NOOP_UPDATE)
NAME: ignore_check_constraints
TYPE: FLAG
ARG: SQLITE_IgnoreChecks