Compare commits

...

363 Commits

Author SHA1 Message Date
stephan a89d43e5db Correct test from [96a48a15760a] - that journal was indeed corrupt, but not in the intended manner. Add a verification that the bad journal gets replaced after a write.
FossilOrigin-Name: c9171221160f241b450d425f41bb53b42362dccfad4c3fdf64dd5c3562ed4096
2026-06-21 11:38:05 +00:00
stephan 646931b83e Extend the test from the previous check-in a bit.
FossilOrigin-Name: bd6bbd0e84226c90d864c28ac4d6acd908c4f06bfe63dcfd6fa6b8d17105b338
2026-06-21 11:10:17 +00:00
stephan 9be4c57ca1 Add a JS test demonstrating that [7c4db1cd2d06] recovers gracefully from journal corruption.
FossilOrigin-Name: 96a48a15760a2eee2642926874e9dfd4082518c6669f930fcc367ffd16669f39
2026-06-21 10:55:09 +00:00
stephan d7e055d41c Fix a result value semantics bug which broke application of a journal in kvvfs. Fix a memory-reset bug in hypothetical non-WASM builds of kvvfs. Bug report [bugs:7fcd3ea30fa4fcfc|2026-06-20T18:22:59Z], and this resolves the journaling issue mentioned in the parent post.
FossilOrigin-Name: 7c4db1cd2d06d9dc6c4b5eed5c4fc0fc5466128a169a5d4ba261e0cb17020ae1
2026-06-21 08:38:07 +00:00
stephan 97467fa8fa Fix a potential integer overflow when decoding a corrupt kvvfs block. Bug report [bugs:76acc88b57|2026-06-20T18:35:54Z].
FossilOrigin-Name: c36fc5df62c7eb8fca6a43cb0b3154a030b39a4cfade8fb04496120d4d339b97
2026-06-21 08:13:22 +00:00
drh fb41f57de4 Fix an incorrect assert() in check-in [984c9b181801c1de]
FossilOrigin-Name: 1d41c93b3636de63cc4b9ee49f73319429944f2255ab56d7556595f56434c17c
2026-06-20 21:54:18 +00:00
stephan 190034d268 Correct misuse of a loop control variable in writing of a kvvfs journal. Bug report [bugs:20e208fe17|2026-06-20T08:49:41Z]. Remove a snippet of dead code in the JS kvvfs side.
FossilOrigin-Name: a396d7c54c5f251daaaac1e68321c2a54d3b6969599027c29b7ca7abe7fb8c4e
2026-06-20 11:07:04 +00:00
drh ae3c30e318 Add the sqlite3_result_str() interface and use it to simplify the code.
FossilOrigin-Name: 8f8361edf82233d6cd53bf8cd2f7fbf0cb3552228c9202395ef1e3383ed559ff
2026-06-19 23:53:29 +00:00
drh 7644162026 Use SQLITE_FINISH in a few places in the core, to help ensure that path is
well tested.

FossilOrigin-Name: 7356bf3a813bcd9082a7fdef5e2cc38c7a0f142f0ef4c948f78e4dafa216c039
2026-06-19 22:57:44 +00:00
drh fa8e52021f Put the sqlite3OomStr singleton in "const" space to ensure that it is
never modified.  Doing so identified a long-standing bug.

FossilOrigin-Name: 2fe53e6d68c3f9f1c4b064fa40fe8efe12025179a973182f0bb256acb9b5d58d
2026-06-19 22:17:09 +00:00
drh ec4f18fed8 Additional changes based on Claude analysis.
FossilOrigin-Name: d4691a0db1e96b646d3094f183b1fc330411a2d2472b09f9b5fe0fb268b5ca09
2026-06-19 21:43:04 +00:00
drh 9487f6d441 Further refinements and uses for sqlite3_result_str().
FossilOrigin-Name: 974253a6058a7120354261b2134ed4803373031996957c7334161370b80bf428
2026-06-19 20:01:47 +00:00
drh 7b386a77ff Prototype implementation for a new sqlite3_result_str() API.
FossilOrigin-Name: cdbc62c36a37ad687589917c6f1e92ded6d99959c54eb8d79eaaef6fa57dacd3
2026-06-19 17:03:21 +00:00
drh 3a697a26de Fix the format() SQL function so that it returns an empty string not a
NULL if the first argument is an empty string.
[bugs:/info/2026-06-19T16:40:04Z|Bug 2026-06-19T16:40:04Z]

FossilOrigin-Name: ece7af98a8bf982567b489cf82a52519f46a1c9360c82a486dbe1cd0664b1555
2026-06-19 16:55:08 +00:00
dan 593b55cb78 Fix a cast in fts3_write so that it is applied as intended. Bug [bugs:/info/2026-06-19T07:56:23Z | 2026-06-19T07:56:23Z].
FossilOrigin-Name: d6a3272e5592bcfeecf8a06dd8330cce57ff6603b24994b533d1fdcd48b5dc66
2026-06-19 11:46:05 +00:00
dan 8c4a4acc02 Fix an fts5 problem causing attempts to create locale=1 tables with more than 115 columns to fail. Bug [bugs:/info/2026-06-19T05:19:08Z | 2026-06-19T05:19:08Z].
FossilOrigin-Name: 979229f720e63d0294be14dae3f9fad68f76e4126a0ad63e32573cdfecd0151e
2026-06-19 11:14:16 +00:00
drh 978b0abe4d Fix a memory size computation error on 32-bit platforms caused by
check-in [9a2a230464e43140].

FossilOrigin-Name: dc4763b14c421710f2a19c19c1cac455c1d0244b27303b6f328c778ac188bb02
2026-06-18 17:09:20 +00:00
drh e070f37183 Small parsing performance improvement by reducing the amount of the
Parse object that needs to be initialized.

FossilOrigin-Name: 0901b7c2dfb87707fa159f24ada5abb0a2914043c6f0571de4ce55c80edb663b
2026-06-17 20:11:26 +00:00
drh b5d0d980be Remove the unnecessary Parse.szOpAlloc field, for small size reduction
and performance improvement.

FossilOrigin-Name: 9a2a230464e4314033e67e602dbd9f554f50ec2eefc7b1a5f05b85249591f7b8
2026-06-17 19:28:17 +00:00
drh 3dbf444538 Small performance boost in the sqlite3VdbeResolveLabel() routine of the
code generator.

FossilOrigin-Name: f786f3643e410e862568777ea021afa47c924348e511a83e7e9000cf14bec534
2026-06-17 18:39:05 +00:00
drh 3f7bf4ef2f Safer conversion from double to 64-bit integer in two places.
FossilOrigin-Name: 17c444382979b70cdca78e51418366e802b2d41d1156774e5e9693b52542b84c
2026-06-17 16:57:19 +00:00
drh c9bffdb35e Additional refinements to the large floating-point handling in
generate_series().  Follow-up to [5aab8c2cde63f9db].

FossilOrigin-Name: 4247c5d9afb162a3e9f334b5121f0df2bfddb916054fa1e6fadb65b1983a7ed8
2026-06-17 14:53:14 +00:00
drh ffc14a20ad Fix CLI CSV test cases so that they suppress the CR on windows and thus
work cross-platform.

FossilOrigin-Name: 7a2c96405e365dd2efb3593e0973536d1f87f7183d5442f3bc98ca23310e5530
2026-06-17 13:52:55 +00:00
drh aa1579de80 Fix the OR-to-IN optimization so that it works in cases where there are
conflicting collation sequences on the equality constraints within the OR.
[bugs:/info/2026-06-17T05:04:48Z|Bug 2026-06-17T05:04:48Z].

FossilOrigin-Name: 984c9b181801c1de06782335eb049f32d1dc1d5739a7fb9c94630c22159a4f8c
2026-06-17 13:31:01 +00:00
drh 8227408b6f Typo fix in a comment used to generate documentation. No changes to code.
[forum:/info/2026-06-16T23:18:50Z|Forum 2026-06-16T23:18:50Z].

FossilOrigin-Name: 7386faef618acb006086d688fcea854a7c555ac1bcfcaa294ea86e964e4ce75f
2026-06-16 23:59:58 +00:00
drh 9fb225190c Limit VIEW recursion depth to SQLITE_LIMIT_EXPR_DEPTH to prevent
static overflow in malicious schemas with tens of thousands of
levels of recursive views.
[bugs:/info/2026-06-16T04:21:51Z|Bug 2026-06-16T04:21:51Z]

FossilOrigin-Name: 3f3fb9b638f59ad982beafb7c117f24ddd3da612e62c862510805fa672ffae06
2026-06-16 13:43:08 +00:00
dan b7c32f2f29 Fix instances of potential buffer overflow caused by loading the contents of a stat4 table with an absurd number of samples in a 32-bit build. Bug [bugs:/info/2026-06-16T11:30:52Z | 2026-06-16T11:30:52Z].
FossilOrigin-Name: 0b72246732fecd7909add28f2d95f1727e1c4dc6c8f3b8e6e3482f6d17c92d34
2026-06-16 11:53:19 +00:00
drh 09c84c70ee Fix a possible call to memcpy() with a NULL source pointer when the size
parameter is zero (which is technically undefined behavior but in practice
is completely harmless) in the sha1_query() extension function.
[bugs:/info/2026-06-16T07:42:23Z|Bug 2026-06-16T07:42:23Z].

FossilOrigin-Name: 2063926473adaf9619ca10fd47b273ef59b1f77136836d575e5db3cfd68987da
2026-06-16 09:53:23 +00:00
drh 9a5801af3a Consistent error messages for excess trigger nesting.
FossilOrigin-Name: 4fb9f2ffb4b45d81c70f561f45cf29a0d5872a612b948882c4a2a14629bcbe28
2026-06-15 17:39:46 +00:00
dan a0dc22438d Speed up SQL aggregate functions percentile() and median() by using quickselect to find the required values instead of fully sorting the array of values with quicksort.
FossilOrigin-Name: 0796d337f6a0ef02a460a24eadda20e33e8fb9ec64b696ae1addee69c1bfddeb
2026-06-15 17:27:05 +00:00
drh f0075cd2e4 New Windows makefile targets for common EXEs that omit the ".exe" suffix, so
that when muscle memory kicks in and we type "make sqlite3" on windows, it
still works.

FossilOrigin-Name: 1152463a66b47eed27b71e87533e8361b6077dc54ff55b8cd2ccde5cfa8199bd
2026-06-15 17:14:12 +00:00
dan b348459070 Improve comments in percentSort().
FossilOrigin-Name: 9b43500f51ae7b43f0e2cc805bb8c93907eddbd8caf21ee5b5a3d2e795962088
2026-06-15 17:13:40 +00:00
drh 458dfb94d0 Enhance SQLITE_LIMIT_TRIGGER_DEPTH so that it also limits a chain of
distinct triggers during code generation.
[bugs:/info/2026-06-15T06:44:07Z|Bug 2026-06-15T06:44:07Z].

FossilOrigin-Name: 0b5378678e3d095ef982bb3b5d5ad05dfb15ce4ee12170b8d0a83fa4b75404c0
2026-06-15 16:45:35 +00:00
stephan 91a3aa3646 Change the PACKAGE_BUGREPORT config option point to /bugs instead of /forum.
FossilOrigin-Name: ca15fedede9873b5f72f29f7cd33cba294cd49593999fc952670e2d5ce7ec86e
2026-06-15 15:48:40 +00:00
dan 1b5bd0edf1 Fix a problem sometimes causing an UPSERT to incorrectly convert an excluded.* column from type REAL to INT in cases where doing so does not cause data loss. Bug [bugs:/info/2026-06-15T09:08:12Z | 2026-06-15T09:08:12Z].
FossilOrigin-Name: 71334f7ec95edc91138b51ef6fe455a4a8c1db130484237eb241956bc549aa5c
2026-06-15 15:38:16 +00:00
drh a2fd301a6a Improvements to handling of inequality constraints on generate_series().value
against very large floating-point values.
[bugs:/info/2026-06-15T04:37:48Z|Bug 2026-06-15T04:37:48Z].

FossilOrigin-Name: 5aab8c2cde63f9db6b2346c86512efd6297937cad4e1306759b0f448815ad2e0
2026-06-15 15:27:26 +00:00
drh 53f419e2c8 Additional tweaks and test cases for generate_series() constraints
using very large floating-point numbers.

FossilOrigin-Name: 50d611404a444f23368c684dfa6dc34b057f8adb45aa058bcf3b2768e908eb3e
2026-06-15 15:24:37 +00:00
drh 0cdc6432f7 Better behavior for generate_series() in cases where constraints involve
floating point number that are near the minimum and maximum 64-bit integer
values.

FossilOrigin-Name: 236ef21789c4ebb67a8cf0a841341fab1477a6fe8e88b575e9f450a8f868e618
2026-06-15 14:16:13 +00:00
dan 667d1b15c1 Avoid a possible buffer overrun in 32-bit builds of fts5 that could occur when processing corrupt records. Bug [bugs:/info/2026-06-14T12:21:15Z | 2026-06-14T12:21:15Z].
FossilOrigin-Name: 0fa3345fe4464804f6846c2598774b9e700df39570e81e020de21e06e2ff044d
2026-06-15 13:39:20 +00:00
stephan b1bbff20a0 Revert the native impl of kvvfs's xOpen() (as distinct from the JS impl which the wasm build uses) to the historical db name restrictions of 'local' or 'session', failing with SQLITE_CANTOPEN if passed another name. [ec866b04d088e53b] overhauled support for kvvfs db names in JS but it turns out that the native impl still relies on those names in order to match journals to databases. Correct a related too-lenient assert() in the WASM pieces.
FossilOrigin-Name: 4b3ec30c63e3824163a2e6cacceb9630301b61f3ffd2b80f8a7b234f09bc251b
2026-06-15 12:36:24 +00:00
dan 23e50585eb Fix a problem that could cause fts5 to return incorrect answers when performing prefix queries on tokens with synonyms. Bug [bugs:/info/2026-06-14T11:11:06Z | 2026-06-14T11:11:06Z].
FossilOrigin-Name: 323d87541a63f0b2c64271c78ad6ebd5d04220da59aebfdfbc07caa691af816a
2026-06-15 11:47:52 +00:00
dan b273dc8de1 Regardless of whether the platform is 32 or 64-bits, Use 64-bit arithmetic to calculate buffer sizes in the fts3_snippet(). Bug [bugs:/info/2026-06-14T09:19:52Z | 2026-06-14T09:19:52Z].
FossilOrigin-Name: 79dc1949b2171c46f6649743ed7dbbf6b83046901ec18ffcbc37a2a47544b100
2026-06-15 11:03:13 +00:00
drh 9c2ac0b6e3 Fix rounding behavior in the decimal extension when all leading digits
are 9 and there is not fractional component.
[bugs:/info/2026-06-15T03:49:42Z|Bug 2026-06-15T03:49:42Z].

FossilOrigin-Name: c6a3b18ee9eb4b8dda57fab94aea9c61aed5e1ffe600f0bcf2c90ca752f2512b
2026-06-15 10:13:31 +00:00
drh a5e5116228 Prevent array overflow when decoding a corrupted value from the
underlying key/value store.
[bugs:/info/2026-06-14T21:58:12Z|Bug 2026-06-14T21:58:12Z]

FossilOrigin-Name: 732c8f81b5a914831c36610ddaed3da15a7f9fdbf54f44872e181b941368d702
2026-06-15 01:24:06 +00:00
drh 5d59375ac8 Remove the unused, untested, experiemental "blobio.c" extension, to
avoid confusing AIs.
[bugs:/info/2026-06-14T15:12:50Z|Bug 2026-06-14T15:12:50Z]

FossilOrigin-Name: 45918d5e97a0c29c6fa03d09ac8e131dcca3bf4268bf4f5c46c221b1f900651a
2026-06-14 22:01:56 +00:00
drh 1b828b8770 Do not allow ridiculous "columns=N" values in the (unused) csv virtual
table in the extensions folder.
[bugs:/info/2026-06-14T15:22:47Z|Bug 2026-06-14T15:22:47Z]

FossilOrigin-Name: ecae4a2ddf103fe575c6864d201e83a9bb084b511685397db41b3349c91b38e6
2026-06-14 20:10:28 +00:00
dan 36e9e015de Speed up SQL aggregate functions percentile() and median() by avoiding a full sort of the array of arguments.
FossilOrigin-Name: 9232b5f21d76dc702ccda5c689d61bcd0057fb1f2a4db6a70c607c25ad03a027
2026-06-13 20:16:54 +00:00
drh d8f6cc7ea4 Make the --header option to the CLI sticky, so that it does not get
turned off by subsequence --csv or similar mode change options.
[bugs:/info/2026-06-13T11:49:18Z|Bug 2026-06-13T11:49:18Z]

FossilOrigin-Name: b19cef1862444ee694eeea2254aa9a8d74d2343da5a8257c22e3068f388aa2fd
2026-06-13 18:04:18 +00:00
dan 2884421c05 When reading a super-journal name from a journal file, allocate a new buffer rather than using Pager.pTmpSpace. This prevents a buffer overrun that could occur when using a VFS with a large sqlite3_vfs.mxPathname value with a database with a small page size.
FossilOrigin-Name: 7f71859841af7cb0806f58e9c8013a990fcca72b807a0513156d7127ce5c7b62
2026-06-12 18:35:24 +00:00
dan 7459c01689 Fix errors in comments on this branch. Also prefer SQLITE_NOMEM_BKPT to SQLITE_NOMEM.
FossilOrigin-Name: ac17669e840dbac5c7c75e9b6014cca2d289aba8de36f9d1d0d61186e8cc6207
2026-06-12 17:32:44 +00:00
dan 053048a40f Fix a memory leak that could follow an OOM error in the new code on this branch.
FossilOrigin-Name: d4774a898730d9ef7564bba0a8531748e31e6517ab29564c2f5a0196c1584451
2026-06-12 17:16:12 +00:00
drh 2ca10782d2 Improved documentation for sqlite3_deserialize().
FossilOrigin-Name: f15d076820f4a0d9e7fa32d99575bce27bde5e987bb415e6cfb69b06f0da7e1e
2026-06-12 16:47:45 +00:00
drh 2429af2e0c Do not use shared locks on SHM files when the filename is a long DOS-device
path.  Only used shared locks for UNC paths.  Do not confuse the
long-DOS-device syntax with UNC paths.

FossilOrigin-Name: a4691489f21bb1645bb8de649268dd457af53c5f613f2a7fd2e5569f029efa87
2026-06-12 16:26:39 +00:00
dan 865a8f3072 Fix a buffer overwrite in fts3 that could occur while processing NEAR queries against corrupt records. Bug [bugs:/info/2026-06-11T23:11:26Z | 2026-06-11T23:11:26Z].
FossilOrigin-Name: b0f773e99eb45cde0953e4870a0a7436c35277c95146fdab3aa06ebbc076c79e
2026-06-12 15:36:26 +00:00
drh 124f449319 When using a read-only WAL/SHM, handle a corrupt page size in the WAL
file correctly.
[bugs:/info/2026-06-11T22:20:03Z|Bug 2026-06-11T22:20:03Z].

FossilOrigin-Name: 09ab0a1bac4eb7de651f78cfd3027b452308da7e04c4ccd7f46c402c82fd6ca5
2026-06-12 12:15:42 +00:00
drh 76ad3be617 Add the --raw option to the dbtotxt command-line tool, for debugging.
FossilOrigin-Name: abfecc08209c9377b6762125b2372e4eb1ab56b4d0399467c8fbe5cfc11c806e
2026-06-12 11:55:39 +00:00
dan 23d0a08176 Fix a signed integer overflow that could occur in fts3 when processing corrupt database records. Bug [bugs:/info/2026-06-11T23:12:25Z | 2026-06-11T23:12:25Z].
FossilOrigin-Name: 978d04f051c06aff798f915b0774da19a0b4f89f9daee124f7e62b12afaaced8
2026-06-12 11:24:30 +00:00
drh b14202f86e Comment improvements on the ".ar -x" command of the CLI. No changes to code.
FossilOrigin-Name: 5b939fb1a284088c4bd46adf517cf598816e2262cd77ee2d9caaab1cef2ce9a1
2026-06-11 23:11:14 +00:00
drh 0b9f4ee89c Fix harmless compiler warnings.
FossilOrigin-Name: dd0c161fcd1619518cd4671d64afb6afeec44c140ec176ccb8616d381a88f42f
2026-06-11 22:48:16 +00:00
drh d1b9001a7f Further improvements to the ".ar" command of the CLI.
FossilOrigin-Name: 6149b938c7a1e95a0fb8600a81557dd0e3efb7b312f3806e2c09d03acf9f4b17
2026-06-11 16:39:41 +00:00
drh d57abdb017 Three-pass algorithm for the ".ar x" command in the CLI, so that symlinks
are created after all regular files are created.

FossilOrigin-Name: 37a80d20fe1948db15ac72432ed7bf3d573c80807fe0a58c58440647e3c34ab8
2026-06-11 14:28:48 +00:00
drh 970b3bc4c4 Add the (undocumented) --debug option to the ".ar" command in the CLI
FossilOrigin-Name: 3a040ffc47fa3e38b36716587c56d9b071d9c69d9daaa9cf0ef6baa654419ab5
2026-06-11 14:14:37 +00:00
drh d688d1551a Improved detection of corrupt node IDs in RTree shadow tables.
Test case in TH3.

FossilOrigin-Name: e01a185a699d0c4ad2341ce2114bc826f02768735cef67179a1127229a073d03
2026-06-11 01:31:19 +00:00
dan dacabb4a33 When reading a super-journal name from a journal file, allocate a new buffer rather than using Pager.pTmpSpace. This prevents a buffer overrun that could occur when using a VFS with a large sqlite3_vfs.mxPathname value with a database with a small page size.
FossilOrigin-Name: 37aa82a50a0a1c5edb4c4f2de1cb00fa009ebbfeb420df3de72231c5d883518a
2026-06-10 20:10:19 +00:00
dan 2bd7ac6716 Fix a signed-integer overflow in fts5 that might occur when dealing with strategicly corrupted records. Bug [bugs:/info/2026-06-10T03:56:42Z | 2026-06-10T03:56:42Z].
FossilOrigin-Name: fc6442ee54795fbeb746539193716238aa653d80170523bc327ae3ce0d945ebf
2026-06-10 16:51:20 +00:00
drh 49edbe2150 Detect an OOM condition in the realpath() function of the fileio.c extension
and cause that function to return NULL.  To Do:  we should go back in and
fix realpath() to raise an SQLITE_NOMEM error on OOM rather than returning
NULL.  But we will delay that, in as much as OOMs are all but impossible
on modern OSes.
[bug:/info/2026-06-10T07:46:32Z|Bug 2026-06-10T07:46:32Z].

FossilOrigin-Name: 8b961dc3d27c5aa62a5dc7c2e44f8b505817e184f8499f3bb903e06b5aec1b72
2026-06-10 10:40:29 +00:00
drh 062016c2fd Fix a possible signed integer overflow in the RBU extension given a
maliciously crafted delta.
[bugs:/info/2026-06-10T06:41:54Z|Bug 2026-06-10T06:41:54Z].

FossilOrigin-Name: 8531c0c3b61771592b055b0c22e903b8301a4161c7bcb7f9fc54d730b080d095
2026-06-10 10:13:11 +00:00
drh 84ead09d94 Harden code that processes Fossil Deltas against OOM and maliciously
malformed delta blobs.
[bugs:/info/2026-06-10T07:01:00Z|Bug 2026-06-10T07:01:00Z] and
[bugs:/info/2026-06-10T07:06:43Z|Bug 2026-06-10T07:06:43Z].

FossilOrigin-Name: 67271c31292bc1bddbb5e144c881c85c9f91b3963a1db4bae1f738adab50f7c0
2026-06-10 09:51:33 +00:00
dan 2cb57d9d4a Avoid a possible integer overflow when fts5 tokenizes a very large document. Only possible with non-standard builds that use large values of SQLITE_MAX_LENGTH. Bug [bugs:/info/2026-06-09T05:27:16Z | 2026-06-09T05:27:16Z].
FossilOrigin-Name: d562e91374e2bebcf75a00776b4def532bb71914a07e37c8507f7a5918db1d3b
2026-06-09 10:43:37 +00:00
drh 2f5d281796 Change loop counter variables from int to i64 to avoid a potential
integer overflow inside an assert() statement when SQLite is compiled 
with SQLITE_DEBUG and an extra-large SQLITE_MAX_LENGTH.  Does not
affect production builds.
[bugs:/info/2026-06-09T08:53:14Z|Bug 2026-06-09T08:53:14Z].

FossilOrigin-Name: 77f615d9833b1f5eaabcb1a6b59af3a6698752abcd16592091f1630beec5969e
2026-06-09 10:16:40 +00:00
drh 6a1efec20a Fix builds on i486 CPUs. [forum:/info/2025-01-25T12:58:44Z|Forum 2025-01-25T12:58:44Z].
FossilOrigin-Name: 4cb349370daab17123770c814c71872a3e4c616a3f984569b3d7f97f9c3f5ea0
2026-06-08 18:15:27 +00:00
dan 082e41f17b Fix another buffer overread in fts5 when processing corrupt records. Bug [bugs:/info/2026-06-08T16:03:39Z | 2026-06-08T16:03:39Z ].
FossilOrigin-Name: 8ba5acabf9a0273ffbc2ba0bacc7f0b000690db91081858e4e563d6cbeeb4e7a
2026-06-08 17:48:00 +00:00
dan 19a2ba1f5b Fix a buffer overread that could occur in fts5 when processing corrupt records. Bug [bugs:/info/2026-06-08T11:15:52Z | 2026-06-08T11:15:52Z]
FossilOrigin-Name: b07441cfc06b8e6b47a4f4a6e0f5c261da580d5afe82444cc7f42a9f39ea7026
2026-06-08 15:20:49 +00:00
dan b677c5afd4 Avoid a potential use-after-free in fts5. Report [bugs:/info/2026-06-08T08:45:27Z | 2026-06-08T08:45:27Z].
FossilOrigin-Name: 9c018b02dbfb071c748d540ad679a4dbdc0fb88a62988e02cb51a3403509febe
2026-06-08 12:03:52 +00:00
dan 7340b032d1 Clamp the nToken parameter to the fts5 snippet() function between 0 and 64. It has always been documented this way, but not previously implemented. Report [bugs:/info/2026-06-08T08:29:00Z | 2026-06-08T08:29:00Z].
FossilOrigin-Name: 4af1d9b3e54a7c42552e61284456bbd7089e525d4aa55e580f7518956d8521bb
2026-06-08 11:24:05 +00:00
dan 2bc3763ec5 Add extra tests to check that non-deterministic functions may not be used in indexes.
FossilOrigin-Name: beeef9dc2b1d778ca628c5e3adc097778646933233ea5ea4f03d2cace0199c17
2026-06-08 10:47:55 +00:00
drh a6812a4242 Improvements to the GCC compiler bug work-around of check-in [7fae321095ebec77].
FossilOrigin-Name: 247894f70d8616cb16468025809513a7db70086d24d2a289b50b4e8d9e073eb4
2026-06-06 18:32:53 +00:00
drh 6bf7825ac5 Minor performance enhancement and improved comments on the quicksort
algorithm used by the median() implementation in func.c.

FossilOrigin-Name: 2800f6a75bbb6ddc86f2e029a7d36ff08febe03812302fb7931eb68da316f74d
2026-06-06 17:20:35 +00:00
drh 192b3a539b A new approach to working around the GCC bug.
FossilOrigin-Name: 2f95186ea568b27d45bd19fca1e03b878afb0b1ea0938875897c7323bdeab8a2
2026-06-05 00:11:56 +00:00
drh 002d33d8c5 Disable the vector-IN-SELECT optimization if the number of columns is so
large that it would cause the WhereTerm.nChild column to wrap.
[bugs:/info/2026-06-04T10:00:49Z|Bug 2026-06-04T10:00:49Z].

FossilOrigin-Name: bb49dfc948048779a30deff9a3d1bb39846f6f72c26a2bdcdd0e386251720fe6
2026-06-04 16:56:11 +00:00
dan ee181f479a Fix a crash that could be caused by configuring the pager-cache with a bulk allocation too small to fit even one page. Bug [bugs:/info/2026-06-04T07:03:12Z | 2026-06-04T07:03:12Z].
FossilOrigin-Name: b4b9dc632b06f932759bc2fceeb6fa1dd6e0de329106ae1d34be874ea1695859
2026-06-04 11:48:18 +00:00
drh 076db92d85 Fix a possible NULL pointer derefence in the (experimental and untested)
uuid.c extension.
[bugs:/info/2026-06-04T09:50:59Z|Bug 2026-06-04T09:50:59Z].

FossilOrigin-Name: fa6374fe3ae1530f0b5ba10b7e6fb703ffe5dd592c532a965e7d50b7d2d70a5f
2026-06-04 11:42:23 +00:00
drh 2dacda40a6 Extra testcase() macros to ensure adequate testing of the previous
check-in.

FossilOrigin-Name: 003013ccabaaa7aea1e78844474ed5032ee9c9824f98c5d36687ac5256a5e128
2026-06-04 11:39:13 +00:00
dan 62a3734e78 Fix problems with expressions like "(?,?,?) IN (SELECT c,b,a FROM ...)" when there is an index on columns "c", "b" and "a", but not in that order. Bug [bugs:/info/2026-06-04T07:02:53Z | 2026-06-04T07:02:53Z].
FossilOrigin-Name: 07667afd0f17b73b58df8ace6a2a5c3a593276f9b7804cb14d4705051258dd75
2026-06-04 11:19:43 +00:00
drh 95d072cc81 Clearly mark the ext/misc/wholenumber.c extension as "testing and
debugging use only".  Fix a potential integer overflow that can occur
if the above warning is ignored.
[bugs:/info/2026-06-04T09:40:28Z|Bug 2026-06-04T09:40:28Z].

FossilOrigin-Name: 44154ea9ae99d532d5c7268d70bd8b32751f7a82c367c7bd1ec0e6948d71d1b8
2026-06-04 11:18:11 +00:00
dan 0120735de1 Update the utf-8 decoder in the ICU extension to treat invalid sequences as codepoint 0xFFFD, matching the core. Report [bugs:/info/2026-06-03T04:04:46Z | 2026-06-03T04:04:46Z].
FossilOrigin-Name: 840be0ee62c0ba34f4916c382e2d07071bebd8ce7bca31deb1616869722bfa4d
2026-06-03 17:21:12 +00:00
dan d80528ad4d Fix a faulty assert() in rtree that could fire when processing an unusually large record. Bug [bugs:/info/2026-06-03T14:44:46Z | 2026-06-03T14:44:46Z].
FossilOrigin-Name: e7335dd377a73fc2b913076ee659ed2b0c4a131b16e04b1b55fae677d6b56e8a
2026-06-03 16:46:15 +00:00
drh c2d29194a8 Use tail recursion on the larger of the two partitions when doing a
quicksort as part of the implementation of the median() function,
to avoid excess stack usage on pathological inputs.
[bugs:/info/2026-06-03T07:26:42Z|Bug 2026-06-03T07:26:42Z].

FossilOrigin-Name: 7dc987165c030c260ba90a8878291b5ceb037c48c2f437a8fb3f05b6c4f84fde
2026-06-03 16:21:17 +00:00
dan 61523e22f9 Fix an integer overflow problem in fts3 that could lead to a buffer overwrite on platforms where size_t is 32-bits. Bug [bugs:/info/2026-06-03T04:28:51Z | 2026-06-03T04:28:51Z].
FossilOrigin-Name: ef0c66d2a2e0b8f523633efca66a97236bb5a502525eb6a00a28a350ec7c76e6
2026-06-03 15:29:59 +00:00
dan 51b2c3863b Fix another buffer overread in fts5 that could occur when processing corrupt records. Bug [bugs:/info/2026-06-03T03:54:40Z | 2026-06-03T03:54:40Z].
FossilOrigin-Name: b5337c87cc314e6830615e4efe2d4723fa7cedf87ce404f60d6e520aeab77cbc
2026-06-03 14:58:13 +00:00
dan 6feeafc7de Fix another potential buffer overrun that could occur in fts5 when processing corrupt records.
FossilOrigin-Name: 6ee44b199512b8cac604bf062f893a9047af4b5bfc881bb7cb69ae42d0a0adb4
2026-06-03 13:49:33 +00:00
drh a735462db6 Fix a potential UAF bug in the zipfile extension.
[bugs:/info/2026-06-03T10:58:51Z|Bug 2026-06-03T10:58:51Z].

FossilOrigin-Name: 1fb5e9169ace6bea2bdf9013f39002c1ce5dc9ce51d6007bec22d91f456c15f0
2026-06-03 13:12:20 +00:00
drh d2240cc988 Fix negative array index in "testing and debugging uses only" extension
qpvtab.c.
[bugs:/info/2026-06-03T07:26:26Z|Bug 2026-06-03T07:26:26Z].

FossilOrigin-Name: d066699fcacd87496645f3aa3c4049935410ae2451879a033102304c80273deb
2026-06-03 12:43:50 +00:00
dan 942b7c555a Avoid excessive recursion and stack overflow in fts3 when processing a corrupt
database.

FossilOrigin-Name: 21369378769195f20f839231f625582552eaa3f79044ca3e3efb7f9476b515a8
2026-06-03 11:37:45 +00:00
drh 069e5acd63 The prefix_length() function should stop at the first NUL terminator.
[bugs:/info/2026-06-03T07:42:00Z|Bug 2026-06-03T07:42:00Z].

FossilOrigin-Name: a9b8bdea0683bb03015ea1ee38f75636c80c189eed4b786b6d3d654a831cfdfb
2026-06-03 11:07:47 +00:00
drh 1c0a370472 Make the (unused, untested, and unsupported) ext/misc/compress.c routines
responsive to OOM conditions.
[bugs:/info/2026-06-03T08:28:36Z|Bug 2026-06-03T08:28:36Z].

FossilOrigin-Name: e3120e2a4339d51210645b14d075abba27dd97bd6bd6d42f445dd5baf3d337e3
2026-06-03 10:55:08 +00:00
dan 2acb57c1be Fix a case where sqlite3expert could be tricked into executing arbitrary SQL by a corrupt database schema.
FossilOrigin-Name: 8a633070e62bdc83a7cf895fd1a22c04b13579659df7cee9584d95096bfffab1
2026-06-02 15:20:37 +00:00
dan 2a28b15748 Add extra test case to zipfile.test. No code changes.
FossilOrigin-Name: 83fe72bcdf866bdaf3043ae5b0c1eb45a9d50a01b24b7a531858271dd746baab
2026-06-02 11:11:02 +00:00
drh b1637e5408 Fix the CLI so that it works when compiled using STDCALL on
Microsoft x86.  Change should not affect other platforms.
[forum:/info/2026-06-02T09:44:12Z|Forum 2026-06-02T09:44:12Z].

FossilOrigin-Name: 83adece349aed73b8d0a3aec141213ea329150eb529f4fd9774ca157b49d02fc
2026-06-02 10:08:30 +00:00
drh b48ca02d64 Enhance PRAGMA integrity_check so that it better detects incorrect
header sizes at the beginning of cells.

FossilOrigin-Name: 08bb2878e7575ab069f8f132537880a47a4d4543a1ac8bffc55e32488223245c
2026-06-01 20:02:17 +00:00
dan c28f2d22b6 Disable transitive WHERE constraints when there are explicit COLLATE operators. Fix for report [bugs:/info/ffddcd3617 | 2026-05-31T08:33:10Z].
FossilOrigin-Name: 18ee3a17c589cfd34500b6a6547d0cb51aea4dd14b4e1e4b2d0cf0dbfb32378e
2026-06-01 18:01:13 +00:00
dan 48a591fa6b Minor performance improvement for the patch on this branch.
FossilOrigin-Name: 2702fecc5c47704bdae535459e59260b55c571ff3b7d09617541e36829d4cf9c
2026-06-01 17:56:11 +00:00
dan 2c351bbf82 Fix minor problems surrounding transitive WHERE constraints and explicit COLLATE terms.
FossilOrigin-Name: 9500238a1d603a3f08a8fc0954725d5d217aab339720146ea0bc700e47b81c3c
2026-06-01 17:40:23 +00:00
drh ddea119f82 Make sure the authenticator is called for columns that are part of
a USING in a FULL JOIN.
[bugs:/info/2026-05-31T02:00:37Z|Bug 2026-05-31T02:00:37Z].

FossilOrigin-Name: 4ef88a36f9d9669b0d74c58f79ad497803885ff6078e5f9685f13c96eab397cc
2026-06-01 16:49:39 +00:00
dan 03aa9a7b50 For expressions of the form "(x, y...) IN (SELECT a, b ...)" where the result is not true, consider the collation sequences of columns "a" and "b" when determinining if the result should be false or NULL. Bug report [bugs:/info/0785f45e67 | 2026-05-31T02:10:44Z].
FossilOrigin-Name: 04d7a9d788cf0bb811483baceb9142f67d4a2380d4af409d5300a92f7972472e
2026-06-01 16:20:40 +00:00
dan 62d17739bc Better handle a (possibly) potential buffer overrun in fts5. Based on the report in [bugs:/info/38e75aa4f9 | 2026-05-31T09:06:13Z].
FossilOrigin-Name: 9d0e39408075f22dd56ad5fb50ef458c0cd6a4ec234dd43b3a5f5e0ac4447b61
2026-06-01 15:03:53 +00:00
stephan f55434ab30 Remove the data type ANY added to the shell's CSV exports in [aff74e71ea734e1a], as discussed in [forum:2ea4c50f69fc9829|forum post 2026-06-01T12:01:59Z].
FossilOrigin-Name: a6a347aec5ff33b24d877ac0f04018f4e93748361da62ed27b1abec77840963e
2026-06-01 13:14:48 +00:00
drh 0a04687373 Do not allow the argument to a table-valued function to reference a table
to its right, even if the SELECT is inside a common table expression.
This is a replacement for the fix at [3c0a277e6741c722].  Fix
for multiple dbsqlfuzz reports and probably also a fix
for [bugs:/info/b6c6fad96c|Bug 2026-06-01T07:23:11Z].

FossilOrigin-Name: d96271db6a3a44e50d1a977250c4cc14178612a6c838e7c51c5f79fda45ae19f
2026-06-01 12:36:28 +00:00
drh c079bd08a2 Test case to show that the change fixes the original carray problem.
FossilOrigin-Name: 8df6671ae8211b706b7d4f073c5166f0f41f3c11339bc4d28997e8a205a39545
2026-06-01 12:30:13 +00:00
drh 5cae890219 Improvements to sqlite3SelectCheckOnClauses() to be more accurate in
identifying table-valued functions that references tables to the right.

FossilOrigin-Name: e5bae3687dbb470ead7e8d6bf428b1fbb1e9f10c214bc2a91e93e68fb2ae6ff7
2026-06-01 12:19:30 +00:00
drh f3eea1efa4 Back out the recent change to carray. The intent is to fix this problem
in a different way so that it works for all table-valued functions.

FossilOrigin-Name: 43e02dbb0dbe7192304125cd3298b3f4bbac02395d4d03f7f8d5f96b465eb43e
2026-06-01 12:07:21 +00:00
dan 3040da55a8 Fix an integer overflow that could lead to a buffer overrun in the zipfile extension. Bug [bugs:/info/2026-06-01T10:16:47Z | 2026-06-01T10:16:47Z].
FossilOrigin-Name: a8dac6af353c02aed8eaaba5921e036d3f3a6639367ae70e8c75d759c7b4ab52
2026-06-01 11:52:23 +00:00
dan 1a760748a6 Update new test in zipfile.test so that it does not run if SQLITE_MAX_LENGTH is too small.
FossilOrigin-Name: cc86820e198db6ee4e62f455d3dd9877d73873aaccad6ceffd0b34452d35c89c
2026-06-01 11:50:55 +00:00
dan 5481274912 Fix an integer overflow that could lead to a buffer overrun in the zipfile extension. Bug [bugs:/info/2026-06-01T10:16:47Z | 2026-06-01T10:16:47Z].
FossilOrigin-Name: 7c740d2cfd22a89258be2221860ca10985f0fc16dc7a17a99fc282d7105dc38e
2026-06-01 10:56:11 +00:00
drh 6c218ba0ad Extra defenses against integer overflows in the untested, unused, and
unsupported transliterate() SQL extension function found in the
ext/misc/spellfix.c extension.
[bugs:/info/2026-06-01T10:34:57Z|Bug 2026-06-01T10:34:57Z].

FossilOrigin-Name: 2b073519b6080abc8872b0728c64827cc088d1b43f132cd2aeb396f06de3d36f
2026-06-01 10:53:28 +00:00
drh ca8593ce68 Fix the carray virtual table so that it gives no solution if it cannot
find a usable first parameter.  dbsqlfuzz find.

FossilOrigin-Name: 3c0a277e6741c72281e12c44d85902aa6780890a7f59bacc3ac2b35ba27f7211
2026-05-31 19:41:16 +00:00
drh af6fdd2673 Test case to cover the bug fix in the previous check-in.
FossilOrigin-Name: 5b28d49b61d5edc9fef896e685bf227b7e1716c0cc666fd2ebe0d5ea0d11af06
2026-05-31 17:01:44 +00:00
drh 1cd9eb3811 Fix the zipfile extension so that ZIP archives containing files whose names
contain embedded \000 bytes do not cause problems.
[bugs:/info/2026-05-31T11:43:05Z|Bug 2026-05-31T11:43:05Z].

FossilOrigin-Name: c12ff342a90c61a0a82c8e63d2d94fecec10dff498da666873ff6aaa15c23dfd
2026-05-31 15:49:57 +00:00
drh 7437c19a01 Fiddle should default to using NO_COLOR since the JS console does not
support ANSI X3.64.

FossilOrigin-Name: bde6190fdeb530bfb92524ab5542f3f4b64adf31c31de1e00fe4430a8e00411f
2026-05-31 09:38:28 +00:00
drh d076d98ec8 Fix the format() SQL function so that it reports TOOBIG and NOMEM errors.
Fix a possible integer overflow on %#Q formatting.
[bugs:/info/2026-05-31T02:00:07Z|Bug 2026-05-31T02:00:07Z].

FossilOrigin-Name: 3bfe0510aecccf113b9d008c308fca3096e9c45c59b919c0b91bb4703415988f
2026-05-31 09:18:31 +00:00
drh 72f0bd609c Add a test to ensure an application does not try to create a geopoly
virtual table with too many columns.
[bugs:/info/2026-05-30T12:47:27Z|Bug 2026-05-30T12:47:27Z].

FossilOrigin-Name: 2c605bfb1562d7a3609ad6ffd7446def12f1ac7084e41b9c6723e998c156501d
2026-05-30 13:23:25 +00:00
drh 7aad2942c2 Harden the diskused.c extension against NULL pointer deferences that
might have previously occurred when given a corrupt database file.
[bug:/info/2026-05-30T07:08:55Z|Bug 2026-05-30T07:08:55Z].

FossilOrigin-Name: 7487a1c59d3aaea9f8b2569dca76bbccf21948b1e7bd8a1d841e04382db696f4
2026-05-30 10:24:03 +00:00
drh 07ded24fbb Remove the untested and undocumented "scrub" utility program. It was written
in 2016, but made obsolete by the [VACUUM INTO] command in 2019.

FossilOrigin-Name: 27d67fb175e839f6e7450f54166b52e0a6137c075758511da32fd82c57c310c4
2026-05-30 10:09:33 +00:00
drh 5b98467d28 Be sure to do floating-point to text conversions in JSON to the full
17-digits.

FossilOrigin-Name: 61a776650cdc3fe632bdb1dc39f50f542b00a9e5b468180eb54b69750d0cd70c
2026-05-29 23:14:22 +00:00
drh 3d006d0d00 Rename the ext/misc/analyze.c extension to ext/misc/diskused.c, to avoid
confusion with the src/analyze.c file.  The function is now called
"diskused(X)" instead of "analyze(X)".  The CLI command is renamed
from ".dbstat" to ".diskused".

FossilOrigin-Name: c7839f7a1749b38f8a36c06d93a7b59095e91ce753e8dd020de273ca8f73a239
2026-05-29 14:57:38 +00:00
drh 991cbd78b3 The -csv option to the CLI also sets "-limits off", for legacy
compatibility, and because that seems to make sense.
[forum:/info/2026-05-28T16:23:36Z|Forum thread 2026-05-28T16:23:36Z].

FossilOrigin-Name: d15cd64160192119a5f12afec14dbfbb30fb38a8557cb9dc559b3f061c15687a
2026-05-29 12:23:38 +00:00
dan 6a8e887a64 Avoid a resource leak in an error path in the icu extension.
FossilOrigin-Name: 87c37dab7e53d1bd891f3fed624963b35ab15a785706d0964b5d07ab70421c10
2026-05-28 11:29:05 +00:00
drh 43ab3c2a84 Fix an infinite loop that can occur in the unused, untested, and
unsupported ext/misc/btreeinfo.c extension if it is presented with
a corrupt database file.
[bugs:/forumpost/b43c44cc1c|Bug 2026-05-28T05:41:01Z].

FossilOrigin-Name: 646e9b49153ebd7e09e3ef72cc4f64fb685d0cfd8b158ef7d11d37c50c3f0b7e
2026-05-28 11:16:55 +00:00
dan 9b7ea5700b Fix a problem with shell tool function analyze() and schema objects with name containing "%" characters.
FossilOrigin-Name: c6d5793d5d2212c979e2fe5eb12c3844bfc1f2029bd8d076fec030a04f6d4bb5
2026-05-28 11:12:29 +00:00
drh 720d3afa7e Fix potential integer overflow in btree overflow page cache computation,
reported by Project Fortify.  Test cases in TH3.

FossilOrigin-Name: dfa674d6e6bffdb930dbefa767831db7862c322b6d3c7a6322f0fa0f087aaaf9
2026-05-28 10:14:25 +00:00
stephan 4cc2dbd54d Patch test/indexexpr1.test to work when built with SQLITE_DQS=0. Addresses [forum:2026-05-26T18:08:20Z|forum post 2026-05-26T18:08:20Z].
FossilOrigin-Name: b470a5d69e70d3440467e7792231f8556111d2c1126cf62879bbfd214ac0a9e0
2026-05-26 19:25:36 +00:00
stephan b79ffe60b6 Fix an OOM check in shell.c.in. Addresses [forum:2026-05-26T15:02:35Z|forum post 2026-05-26T15:02:35Z].
FossilOrigin-Name: e78198e6aec57c28e33a5b1c5ae9943115a35d2fbfa04c8428318567f17eefbe
2026-05-26 17:15:54 +00:00
dan 6e3e557c81 Fix a potential 1-byte overread in sqlite3changeset_invert() when processing a corrupt buffer.
FossilOrigin-Name: 78eaa605cb6c14e5bd49a898b4c737957bd60c8714913cc2341f4ffe3bfe81fe
2026-05-26 15:03:30 +00:00
dan 18bee1617f Fix a 32-bit integer overflow in sqlite3changegroup_change_blob() that could lead to a buffer overwrite.
FossilOrigin-Name: 8a289158e2baeee8aa5e601bde46b0482361064ede09e4108f519270efdd5f69
2026-05-26 14:18:50 +00:00
drh 2315f282be Fix QRF so that it works sensibly with "--wrap 1"
FossilOrigin-Name: 48f950b2a1ef841d915ca733baf324a1af98e644b660f238dd5018015340a6c6
2026-05-26 13:54:57 +00:00
drh ce2f503c1c Strength the defense against malicious command-line arguments in the
sqlite3_rsync utility program.

FossilOrigin-Name: c84d596b6da22061627282d444913c88dc2f9bd82e86957183f7e732f2713b33
2026-05-26 11:23:00 +00:00
drh 5554aa27e6 Fix HTML ">" quoting in QRF.
[bugs:/forumpost/f72dfbdeff|Bug f72dfbdeff].

FossilOrigin-Name: 5cee1097b47d850989ebecdb9d2c814e6968240fe3bacb5598ca85067cb95692
2026-05-25 23:44:37 +00:00
drh 5cfa63e6c8 Fix a minor problem with test case mjournal-3.3.
FossilOrigin-Name: 80093dc0610438cd506d230eca12e699e05395aecb8048c5bc066379b927e8bb
2026-05-25 20:45:43 +00:00
drh 0ca827a590 Early malformed JSON errors in json_each() and json_tree() to prevent
excess output.

FossilOrigin-Name: eb4d7e1b86b1d1bece01f621c9991a11f4812bf515a973b5036b09a1392134e2
2026-05-25 19:47:58 +00:00
drh 7e20b624db Fix to testrunner.tcl so that it will run on FreeBSD.
[forum:/info/2026-05-25T18:50:36Z|Forum post 2026-05-25T18:50:36Z]

FossilOrigin-Name: 97b66687b3f772e225e1792194a7f54a2ae187c84de1d76227846503631b5c2f
2026-05-25 19:32:52 +00:00
drh 94c8755a95 Enhance the defenses against malformed JSONB in the jsonbPayloadSize()
routine.

FossilOrigin-Name: 32c9f71a989fa4c81a613398ca5c1e68eb88b2a90ac4a4a7bf39e755717f43b1
2026-05-25 18:46:42 +00:00
dan 8c9071ae4e Use "-encoding iso8859-1" instead of "-encoding binary" in mjournal.test so that the script works with both Tcl 8 and 9.
FossilOrigin-Name: 897b443fb35d550891315890a5af473d347af3b6ecea11fcafafb5b06a1b50a5
2026-05-25 18:14:58 +00:00
dan e1233bb609 Fix a potential 1 byte overwrite that could occur when processing a carefully crafted super-journal file.
FossilOrigin-Name: b3766c3afd0ac4d31f158ee5938f19d72a047872e422b5f19b1567c60640f54d
2026-05-25 17:58:07 +00:00
dan 00ba6a6635 Fix a potential buffer overwrite that could occur when in fts5 when handling corrupt records.
FossilOrigin-Name: de009593f692251c4a033742b1e79c4ddb5ddcb174209d58c4d1bea19ceb360c
2026-05-25 17:09:37 +00:00
drh 3020203708 Make the /A substitution in the CLI prompt expansion responsive to the
SQLITE_CLI_APPNAME compile-time option.

FossilOrigin-Name: 624dc12e9e6dece22f7d247b66f0889755295cb2154502ade6553c13b8fecd83
2026-05-23 12:14:05 +00:00
drh db7fe319ed Strengthen the statement about not accepting agentic code.
FossilOrigin-Name: 64b707f4b3177384b611df5f5e4ca7da44d86313f74ff943a00581a6dd924b08
2026-05-22 17:51:15 +00:00
dan 3eb6a5ee33 Update the session module so that it can apply changesets containing two or more UPDATE changes that form a dependency loop - so that no single UPDATE can be applied independently without violating a constraint.
FossilOrigin-Name: 32c762bbb187e3bc964dcaad0949ebeb4da21331be842a5868feeac6088790ca
2026-05-22 14:29:33 +00:00
dan f380599476 Add the SQLITE_CHANGESETAPPLY_NOUPDATELOOP option to the sqlite3_changeset_apply_v2/3() method. To disable the extra processing to commit changesets that swap two or more values subject to a UNIQUE constraint between rows.
FossilOrigin-Name: eba02092bcf1713a646ecf8febd53a83bc803cf1b17e2e4fc590c14d886a8d27
2026-05-22 14:22:14 +00:00
drh a1e5778889 Add a prototype AGENTS.md file
FossilOrigin-Name: 3dd1181120b9910a96090b5b3cad5f0829fadeffeb6ffaad17031540a5749513
2026-05-22 11:15:25 +00:00
drh 8f70ec615f Fix the VFSes so that there is no 32-bit integer overflow in the
xShmMap method if the WAL file size exceeds about 25 terabytes, or
if the header is corrupted to make SQLite think that the WAL file size
is that big.
[bugs:/info/2026-05-21T03:53:03Z|Bug 2026-05-21T03:53:03Z].

FossilOrigin-Name: 9ac4a33a2932d353c4871fd8e09c10addf827f1fc3fc9380037d738cf2cd0353
2026-05-21 15:14:35 +00:00
dan de739c3450 Merge latest trunk changes with this branch.
FossilOrigin-Name: c6226d508d6af28fcd42fecd482014c3e284923871d3d8f5177f728cbf3457c5
2026-05-21 15:13:17 +00:00
dan c597ed79d1 Fix a buffer overread in the session module that could occur when processing a corrupt changeset.
FossilOrigin-Name: 869a51ae84dfaaf824c872e4b3024f35eea7fa67bb584759a2d42ebf8404ef6e
2026-05-21 14:58:36 +00:00
drh f305e1921b Performance optimization to the sqlite3ExprCollSeqMatch() routine.
FossilOrigin-Name: 035f1d2f284a081e6aef01cc773a3784458ec8e6297f3fbf4063670a9e65278c
2026-05-21 14:48:43 +00:00
drh 3dbab77bf1 Remove dead code created by [20c2f8ce9242c3db].
FossilOrigin-Name: 68c5fd5fa0f986b782519472398b068ba7c295c56a0e0199b8e0509d0d1685fb
2026-05-21 14:16:47 +00:00
drh da18b56b25 Limit the length of inputs to the spellfix1_editdist() extension
function (not part of the standard SQLite build and rarely used)
to 99999 characters, to avoid any possibility of integer overflow.
[bugs:/forumpost/5da728247a|Bug 5da728247a].

FossilOrigin-Name: 51ebf0e04efcbe982e62f2228553700b8c1147a37c70fdfaa0e377184b3ac936
2026-05-21 13:37:15 +00:00
drh ecd9748590 A join constraint is not an equivalence unless both operands have the
same collation.
[bugs:/info/2026-05-21T03:39:28Z|Bug 2026-05-21T03:39:28Z].
Error goes back to version 3.7.17, 13 years ago.

FossilOrigin-Name: 20c2f8ce9242c3dbb8054f01c6c7338763c830b7c1491a99c8e1de98ce8f0a5b
2026-05-21 13:03:18 +00:00
drh 6ae9949399 Enhance the xfer-optimization to verify access to the source table using
the authorizer, if an authorizer is registered.
[bugs:/info/2026-05-21T03:31:22Z|Bug 2026-05-21T03:31:22Z].

FossilOrigin-Name: d1cdb817cafd03a4081b254a71672ba07e5b637994d3f2f925532f17af0a67dc
2026-05-21 12:04:20 +00:00
drh 37e253208f Improved entropy collection on Windows.
FossilOrigin-Name: b734b76af3f732e0db8a002781bae57aa7a01c4a7f5151605144a6aeef993a21
2026-05-20 20:03:07 +00:00
dan b869ed6b06 Fix a bug causing the session module to dereference a NULL pointer when applying a corrupt changeset.
FossilOrigin-Name: e807d4e3798efd532b3d78d1dfe513ed4fbd3cb793dd0ae5c30cae6031422b10
2026-05-20 15:06:34 +00:00
drh ccf23dadea Check-in [7cf841f3613c8302] was incomplete in that it did not remove
the incorrect assert() statement.  Fixed here.

FossilOrigin-Name: c19bacca13f699953bbf50afb867035a94080b8a48111cf3d87bced880a3e620
2026-05-20 11:53:15 +00:00
dan d3dc8e5ae5 Avoid returning an SQLITE_SCHEMA error from within sqlite3session_diff() if the schema has been recently modified. First <a href=https://sqlite.org/bugs/forumpost/269f1e9cef>reported here</a>.
FossilOrigin-Name: 20110547a117912be68d91bc006e92c45beffde16c6bcdf062bf8971c9a9a864
2026-05-20 11:41:24 +00:00
drh badc42fd74 Fix a test case added by [1249eeb1b9759038] that does not work for
the config=prepare permutation.

FossilOrigin-Name: 6aaeeffc2c7abbe32050da40bcbffc5f8d596e6df4603b8f8f23294f2465f25e
2026-05-20 01:32:30 +00:00
drh fd9eba554a Improvements to testrunner.tcl: Make C-program tests responsive to the
pattern list.  Do not remove build steps until the very end when we are
certain that the build step is not required.  The pattern list matches
the exact text of the displayname column of the jobs table.

FossilOrigin-Name: 3f409c0e1be6b71eca92eccf0b9de3ef5923a6a57be49540ebe190fc6a6d06d4
2026-05-20 00:16:20 +00:00
dan 8154476ff8 Fix a potential buffer overread in fts5 that could occur when handling corrupt records.
FossilOrigin-Name: 70021c3291b38192832c99fa4d8155249dd39f5b26334595c71f5cee66d13ebb
2026-05-19 20:27:54 +00:00
drh d258b93356 Silently ignore nested porter tokenizers in FTS5. Having nested porter
tokenizers is pointless, but it does use stack space unnecessarily.
[bugs:/forumpost/a7766198f1|Bug report a7766198f1].

FossilOrigin-Name: 0bdeedf56c9d7209d1ea8f950d0ef03c78cbf677528d9d30c5f4ec48c4e1a571
2026-05-19 19:33:49 +00:00
dan 3ef3ecdf8a Remove new unreachable code from this branch.
FossilOrigin-Name: 07f81e256fde5796c0d8bcedff8fad4368639386f40881c0844e9c86a69b6fad
2026-05-19 18:58:59 +00:00
drh 632b618063 Prevent integer overflow in the ICU extension.
[bugs:/forumpost/844bdd7d5c|Bug report 844bdd7d5c].

FossilOrigin-Name: c20cb75ca07d0553d7a847c65a40efb2e5c587579ec32b02121a8963c70db12f
2026-05-19 18:56:11 +00:00
drh d58b463ffb Fix an assertion fault that can occur in RTree given a carefully
corrupted database.
[bugs:/forumpost/2026-05-18T06:46:01Z|Bug report 2026-05-18T06:46:01Z].

FossilOrigin-Name: 7cf841f3613c8302a419638bdec83b1b9799f00cfedbfe40dca0a1a005c196b5
2026-05-19 18:45:05 +00:00
dan 1a0555610d Fix problems with error handling when applying changesets with update loops.
FossilOrigin-Name: f797925df05f1a53392dba416aa72067716bb6e38ebc679d313f8a98f89c5913
2026-05-19 18:28:03 +00:00
dan 079f8dc682 Merge trunk changes into this branch.
FossilOrigin-Name: e480e126fc1550948bd1a3635f0e3a862353dcd8b0f1921ab6e66d699110b477
2026-05-19 17:15:05 +00:00
dan 4da2ddf50e Avoid a potential 1 byte overread in sqlite3changegroup_add() when processing a corrupt changeset buffer.
FossilOrigin-Name: 4d8c3a2919dc942a0a044ec7582a688e0f93e91f4a465bb94390fbe3ad1b50b4
2026-05-19 17:12:39 +00:00
drh fdf746967c Add an sqlite3FaultSim() call to the changes at [6193e4105b6a58ea] to
make them testable.

FossilOrigin-Name: 0de3d95500b7ecd41a09d81f176c1510896e811b3e468e1cf50f752305fdb06f
2026-05-19 17:05:28 +00:00
dan c7ddaf2479 Merge latest trunk changes into this branch.
FossilOrigin-Name: 1f71b530c9e8ddce5b25ed0971447cd925f9194afc7a2f929429318a5fcb5a72
2026-05-19 16:07:22 +00:00
drh 45b4213160 Fix a incorrect assert() from [52dd437d51d3c1af] reported by OSSFuzz.
FossilOrigin-Name: 64b49e4d431d35db0920c7fc8a89d38ce2d2d2dbf42a7caf76aaa9685d66601b
2026-05-19 16:07:08 +00:00
drh cbbeddbd67 Add the "make" subcommand to testrunner.tcl
FossilOrigin-Name: 00d546812810252a4d51ff796931dc2ff5bdd5173ddb8f95fb5efccd897087ff
2026-05-19 15:13:03 +00:00
drh db5aebce43 Early detection of attempts to overwrite an in-use cache page due
to database corruption.
[https://issues.chromium.org/issues/513858286|Chromium 513858286].

FossilOrigin-Name: 6193e4105b6a58eac2bc17c5b2d55fdae332816b59beed1fe24c15dff1372322
2026-05-19 12:40:00 +00:00
drh 4d3f3c7070 Security enhancements to the untested and unused fossildelta.c extension.
Bug reports [bugs:/forumpost/3ac3fe3d71|3ac3fe3d71] and
[bugs:/forumpost/e7e470b760|e7e470b760].

FossilOrigin-Name: 2d3fbbe421d3b0ad8fa08255fd30af7f2d947919ebb90fa9c9c4ee72ffd880b4
2026-05-19 11:15:33 +00:00
drh bc94e5dbb6 Limit the size of input strings to the (disused) spellfix extension
to avoid excessive runtime and integer overflows.
[bugs:/forumpost/24a33e5d10|Bugs report 24a33e5d10].

FossilOrigin-Name: 4b16b80cf2e26c41f0828d65883145dc81c0987110c3f04a864cec43e7c418e5
2026-05-19 10:30:13 +00:00
drh d0fd0cc2fa Fix a NULL pointer dereference in the obscure (and unused) prefixes
extension, which I had forgotten even existed.
[bugs:/forumpost/24a33e5d10|Bug report 24a33e5d10].

FossilOrigin-Name: 4880adc053b0f21633936347fd5610e4338b15da39607d8ba5a6b32d1516e8cb
2026-05-18 23:58:14 +00:00
drh 1192d6f5b1 Fix an off-by-one error in matchinfo('b') for FTS3 when there are a large
number of columns.
[bugs:/forumpost/42d5f799d1|Bug report 42d5f799d1].

FossilOrigin-Name: c0f2cd6d292bb3554539a309a2ecfa0a12f0248cfcbb5594391607b107a62cd2
2026-05-18 21:45:06 +00:00
drh a20f63e0ef Limit the NEAR span in FTS3 to 1 billion, to avoid integer overflow.
[bugs:/forumpost/4cfcf164d2|Bug report 4cfcf164d2].

FossilOrigin-Name: adcb44f9dcfd34df4cf4f5cc418ecac9f2ca09723442379dfedcba532c45de77
2026-05-18 20:43:15 +00:00
dan a497743e24 Fix a problem applying changesets that contain multiple UPDATE constraint loops.
FossilOrigin-Name: 8e89aafab1b8fa7d59ac98c39b7f1c1cc23a98ba868a5dac83dee63688786a3f
2026-05-18 20:34:56 +00:00
drh a2d992a000 Prevent integer overflow due to database corruption in.
fts3auxNextMethod().
[bugs:/forumpost/bce19272cf|Bug report bce19272cf].

FossilOrigin-Name: d903795e3bdf91607f23dace5a15a070139b32e34aed8540a6c2e19c39e997be
2026-05-18 20:27:57 +00:00
drh 62cd986418 When a subquery is an argument to an SQLITE_SUBTYPE function, then set
the EP_SubtArg flag on the result-set expressions of that subquery.
[bugs:/forumpost/8de44412fd|Bug report 8de44412fd].

FossilOrigin-Name: d75c08c8416bde3f510a135ea07cc217ba6183eacda6ba895bccbf57efe9284f
2026-05-18 19:44:04 +00:00
dan 4dd11e33e4 Update the session module so that it can apply changesets containing two or more UPDATE changes that form a dependency loop - so that no single UPDATE can be applied independently without violating a constraint.
FossilOrigin-Name: 5f8010599fb33fbe1dadc894a679056a5d38040ab4f09772cd59827941e9348b
2026-05-18 18:59:06 +00:00
drh de94ec87fc Fix the window-function variant of the json_group_object() function so
that it correctly handles NULL entries.
[bugs:/forumpost/0de87b23b3|Bug report 0de87b23b3].

FossilOrigin-Name: ac3a958b0ab7766544bb406aa990668d2235ab26fb68c75ded3f71273d97b18c
2026-05-18 17:55:40 +00:00
drh e087d0f266 Do not attempt the OR-optimization on conjuncts that contain COLLATE
operators because (1) such cases are rare and (2) they are tricky to
get right and are thus prone to bugs and hard to test.
[bugs:/forumpost/329521b269|Bugs report 329521b269] is one
such bug that goes back about 20 years and thus demonstrates both
points of the previous sentence.

FossilOrigin-Name: 622882529558b4779dfb7246bd5a9de776555c8f940bb941397fb56fb9f97e43
2026-05-18 14:28:53 +00:00
drh b6ea7f0550 Fix a harmless compiler warning caused by [460243ee4c28873d].
FossilOrigin-Name: e076ac7b648988b48b546e19b5ef2e50061b44c85bc2645362409e295e3b4b11
2026-05-18 11:44:09 +00:00
drh e4ba14bc40 Correction to the bugfix at [212c68249cc0e890] so that it works
for 2nd and subsequent terms of a row value.
[bugs:/forumpost/ba8084256b|Bugs report ba8084256b].

FossilOrigin-Name: 49a3c6ade45ea31187ac8dbba0452fcad487e76f2219bfa6435feab545b5f632
2026-05-18 11:35:44 +00:00
drh b969f8c2f1 Raise an error in the dbpage virtual table if the page number on an insert
is too large. [bugs:/forumpost/f363b34881|Bug report f363b34881].

FossilOrigin-Name: 2bbc8f1404e3cbaa8fe24e2ea2d774daf2bf3b232fbd8978a1a37e4f8f0f629d
2026-05-18 10:19:14 +00:00
stephan c1bf93ecb6 Add a reference to the new sqlite.org/bugs forum.
FossilOrigin-Name: f2fe7e7a184e482411ddf792a1964e6ce30a28185da9fa79488d097382c352fd
2026-05-18 10:00:35 +00:00
drh fd88eed8fa Fix the ".prompt" command so that it honors -bail.
FossilOrigin-Name: 5b58bdd33f0410624523fc8adc230db4e5f57283d234a5f0d26a2790629bc0f9
2026-05-17 19:21:30 +00:00
drh f77a5af2b3 Fix the new %J and %j formats so they work correctly with a zero precision.
FossilOrigin-Name: 428b924dd6e228c879338c5c5edbe68bdc4d8d8e80c37e3d070a8a34b57300c1
2026-05-17 17:50:32 +00:00
drh 6df919b723 Fix the sqlite3_value_numeric_type() interface so that it works even if
the argument is an sqlite3_value object that came from sqlite3_value_dup().
[forum:/forumpost/15c4a323bb|Forum post 15c4a323bb].

FossilOrigin-Name: 460243ee4c28873d5c484a91af7ec18d03d0e726b8bb4e1e680b588e5f371400
2026-05-17 17:18:12 +00:00
drh cb388ec9a3 In the CLI, make the NO_COLOR prompt the same as the default prompt, only
without any color.

FossilOrigin-Name: cc05e9749c14d6ff4e396ad387c42d53b696680ce74ce6a92f6fb71ed9a53ae5
2026-05-17 16:35:36 +00:00
drh 0e30396fe2 When two tables are joined by USING, the affinity and collating sequence of
the join column will be the same as one or the other of the columns in the
two tables (which are hopefully the same).

FossilOrigin-Name: cbb6f72c10b1224088b9d604a0e014e7020e8d79a317ebb4af420ba3b72f40cc
2026-05-16 20:21:55 +00:00
drh fc12461f5c When two tables are joined by USING, the affinity of the join column can
be the affinity of the corresponding column in either table.  But it must
be one or the other.

FossilOrigin-Name: 8cc0b52cca2d6b155b4b169e6f9de9e7d2dc61c40f89920c2b6bf86e549ab6f7
2026-05-16 19:40:55 +00:00
drh 3cfe5c3dad Fix false-negatives in the sqlite3ExprCanReturnSubtype() routine having
to do with CASE, CAST, and COLLATE expressions and the "+" no-op operator.
[forum:/forumpost/1f0168a120:Forum post 1f0168a120].

FossilOrigin-Name: 9be94e961457967408cbd0acf11877fbb323a48eee405fff53755770f1b3261a
2026-05-16 17:28:56 +00:00
drh d3265d1473 Improved comment on sqlite3ExprCanReturnSubtype() as well as one very
minor efficiency improvement.

FossilOrigin-Name: b57d6b76b0f9f4f154997af83f14d545bf2b6b8f7194746835e4c34885db227a
2026-05-16 16:02:08 +00:00
dan 61d9f8f881 When using an index on a CASE expression that may return a value with a subtype, avoid discarding any subtype.
FossilOrigin-Name: 69c9a82cbaea7b6faa6a357dd884b1e0d2bc684d847efb31a2d64c75fabf5428
2026-05-15 19:44:24 +00:00
dan 397a3c4a04 Have window functions sum(), total() and avg() correctly handle the obscure case where integer overflow occurs as part of the same step that adds the first real value to the running total.
FossilOrigin-Name: 614f8cbc374c7a2d39826706f16ab1c17e75abb420a70be356c7eefa95b271a5
2026-05-15 18:36:11 +00:00
dan 0c3b3f93d1 Have the sqlite_dbpage module correctly handle constraints of the form "WHERE pgno=$pgno" when $pgno is an integer that cannot be represented as a signed, 32-bit value.
FossilOrigin-Name: 350152d5124b71b3dd640c109dbac5f05a3a9583a335fbe25f707e1b754c1c74
2026-05-15 17:24:34 +00:00
drh 9f3208e1f8 If there is a WHERE clause constraint of the form "a=$var", then
automatically reprepare the query if the binding of $var changes, as
that could affect the query plan.

FossilOrigin-Name: 1249eeb1b9759038b6d10dcd8d65f64f81e8529b28e5e24f9fc5759e9a1c88a5
2026-05-14 22:39:41 +00:00
dan 672b87d0d5 Handle constraints like "a=$var" where the value bound to $var is -1, 0 or 1 in the same way as a literal -1, 0, 1. This might cause excessive reprepares.
FossilOrigin-Name: 224e827e87ac8aa4b18ff9b2193decbdcd832579bf6b6e0883c03db90ebd5319
2026-05-14 17:54:23 +00:00
drh 02dc8d0c5f Harden the QRF extension against integer overflow problems.
FossilOrigin-Name: 83d3a0d39b0b36a47003206d390935d4c94f7a906ada02f69e0ad1eef2f22d59
2026-05-14 13:29:59 +00:00
drh 13b63ed930 In QRF, relax the constraint on the screen width so that it can be as
wide as a 32-bit signed integer can count.
[forum:/forumpost/2570051b60|Forum post 2570051b60].

FossilOrigin-Name: 91439d95dc81adebf8226f2b7c33ca62a4b7163cfc14e03993524aa17b5767f3
2026-05-14 12:46:41 +00:00
drh 6166c2ee48 Raise an error if an ALTER TABLE ADD COLUMN statement tries to add
a CHECK constraint that contains an internal-use-only SQL function.

FossilOrigin-Name: 52dd437d51d3c1af0777350daeebf1159033eede3f58fcc5ebaeaec8869ffaff
2026-05-14 11:35:17 +00:00
drh a3e7c597b5 Correction to the documentation for sqlite3_uri_int64().
FossilOrigin-Name: 58472fdab0691f7f03f15201542f15148ed7734bfeb495370386d666ae4beded
2026-05-14 10:36:50 +00:00
dan cf632ddb7e Fix the sqlite3changegroup_change_finish() API so that it correctly handles NULL being passed for the pzErr parameter.
FossilOrigin-Name: f81d6d7bc8943729f678a3b62921a96764b15b9cc11d8a5753e48210a1b59617
2026-05-13 20:37:30 +00:00
drh a8fdc0e0c0 Additional changes to the Windows VFS to try to get the system to a point
where it will compile for UWP.

FossilOrigin-Name: 3ddc07110dacef98c14b852dcdc4f11d1c0f4061008bf3a61e2df707b0dc6c6b
2026-05-13 20:06:08 +00:00
drh 8b803c716c Change to compile-time option that omits non-UWP system calls to
SQLITE_UWP.  Takes steps to eliminate all non-UWP system calls when that
compile-time option is used.

FossilOrigin-Name: b167c4c96e6a753952eb4be95d6283b50a9c59f4ecf59d60cf10d8de9cb115d8
2026-05-13 18:07:01 +00:00
drh 077be44cc2 In the Windows OS interface, use CreateFileMappingFromApp() and
MapViewOfFilFromApp() if SQLITE_OS_WINRT is defined.

FossilOrigin-Name: 3c51436b43ab77b88134851a2773147b49a3c4fb5b0416db5e318b6e12d3b708
2026-05-13 15:11:04 +00:00
drh 1f9c2ed674 On Windows, use only GetSystemTimeAsFileTime() and not GetSystemTime().
Also add a TCL script that will renumber the systemcall thunk for Windows.

FossilOrigin-Name: 4b7f0130d68c7a0b98e87d09d0a661c85554215cb111478305307960deab3921
2026-05-13 13:55:31 +00:00
stephan 887fa2fe83 Modify [2990dd27dd] to use setlocale(LC_CTYPE) instead of setlocale(LC_ALL). Same effect but hypothetically a smaller scope.
FossilOrigin-Name: 8be57f96f6a72753fd983d7b6b9924c3b60f57c5664eeb7dfe7a0799739597c0
2026-05-13 12:12:54 +00:00
stephan 73da727480 Slightly rework the --editline configure flag for purposes of distinguishing between the two conventional libedit headers. When using libedit, invoke setlocale(), which appears to resolve [forum:aad7a634916ff050|forum post aad7a63491]'s report of non-ASCII input failng to work when linking against libedit.
FossilOrigin-Name: 2990dd27dda4333e8ccf9225ff5af690d4062276dd2ad60ee7df922aa6be99ee
2026-05-13 12:06:27 +00:00
drh 94f56f5800 Use GetTickCount64() instead of GetTickCount() in Windows, for portability
to UWP.

FossilOrigin-Name: ea65ae964e21541d8f7c00ec679a608d34bc94408d9cc479b2d7fb08bf0e3cdf
2026-05-13 10:47:40 +00:00
stephan 93d7dc266b In Haiku OS builds, fix the --editline configure flag to actually link against libedit rather than libreadline.
FossilOrigin-Name: 642168d386074d1398b52c2d952e4cb153617f7bed82fb99d76b6d28dc8242bc
2026-05-13 07:33:31 +00:00
drh 1d2f71ce4a Do not allow internal-use-only functions to be used in CHECK constraints
that are added as part of ALTER TABLE ADD COLUMN.

FossilOrigin-Name: 28272746fb13dcdbe2a4f21ef30446c316dfc843aa3d48f135f490abaee34198
2026-05-12 19:33:04 +00:00
drh 431b85963f Fix a copy/paste error in a comment. No changes to code.
FossilOrigin-Name: 2741193e81750512cae8bb14ebc03fa11dde100da144488b4da1c81a351e4ff6
2026-05-12 18:06:44 +00:00
drh 054aff6272 Fix the insert4.test test module so that it works when SQLite is
compiled with SQLITE_OMIT_VIRTUALTABLE.

FossilOrigin-Name: bff9db9b5eb05ec3f99e35cb4c198d24bcb9ee7ceb2401bf55627788ac2b96db
2026-05-12 12:34:16 +00:00
drh 657721fd32 Attempt to disable false-positive GCC compiler warning.
FossilOrigin-Name: e377b4d375f2ec64085d953e87739904bbbccf09d82db99fe0579368c985ca84
2026-05-12 11:47:01 +00:00
drh e14c16189e Fix the xfer-optimization such that if the destination table has
constraints on the hidden rowid, the optimization is not attempted.
[forum:/forumpost/6dcc95e3ca|Forum post 6dcc95e3ca].

FossilOrigin-Name: da9ca357fb24f39a4a3292ca447773c6233a3084721abfbd5665a45ee8a4bed4
2026-05-11 16:58:05 +00:00
drh 88be32c9d3 Prevent internal-use-only SQL functions from being used inside of a CHECK
constraint added using ALTER TABLE ADD CONSTRAINT.
[forum:/forumpost/6256ee34ed|Forum post 6256ee34ed].

FossilOrigin-Name: 07beb966daddd0dfea36265eb38ebfcfbb10b0cbccda458dea501eda330a49f0
2026-05-11 14:22:45 +00:00
drh cf420c9910 Fix comment typos. Add test cases.
FossilOrigin-Name: 7a85921ae585f948f084bd8fccc1cc002539415e629aa0021d58afc17e5471a3
2026-05-11 14:14:25 +00:00
drh 5f6a6fa6f0 An alternative fix to ALTER TABLE ADD CONSTRAINT.
FossilOrigin-Name: a231fc6d58abb3b76d0e669babdb377e41b5a348d155ab5f2c1fe49eefbbf3a9
2026-05-11 12:38:18 +00:00
dan eb83e32cef Improve detection of corrupt database records in fts5.
FossilOrigin-Name: 87b653731171a26396f9796b0a5c436593571d39e991d8d040ef4473cf459b67
2026-05-11 11:53:07 +00:00
dan 79db323ce1 Fix potential buffer overwrite that could occur in fts5 when processing corrupt records.
FossilOrigin-Name: 4a5ad516ea93926c0d5206b4d72c3675905d2bf666b27a649256b93eb95c671b
2026-05-11 11:12:06 +00:00
drh 219acc9f4f Fix harmless bug in testing logic. No changes to deliverables.
FossilOrigin-Name: c4a6e62b5f36f9df988d2ec12a92a614e9e813cdcd50198ec2827c33745a362a
2026-05-11 11:05:17 +00:00
drh 8bee9cd3cf Hush-up a false-positive compiler warning from GCC. See
[forum:/forumpost/281ace196961082c|forum thread 281ace196961082c].

FossilOrigin-Name: 45a215290896eee9776c2b1727363f6e1c1aad5b521f4f92b13981a5da5d0f54
2026-05-11 10:25:58 +00:00
drh f8ca671997 Fix the xfer optimization so that it does not work if the destination is
a STRICT table that is incompatible with the source.
[forum:/forumpost/4955d2235c22ef4e|Forum post 4955d2235c22ef4e].

FossilOrigin-Name: 5e916b2a8fd41ffc42c29bfc9b5333b7a579f37fe094bd0b6b00e2f176c4e3fe
2026-05-11 00:53:44 +00:00
stephan 15670b1b3f Minor config script doc tweaks.
FossilOrigin-Name: 678954553e123de9eb0bbe7c3101039218edd27d282b015f51e69a2eba683feb
2026-05-09 14:32:16 +00:00
stephan df300e8e51 In sqlite-config.tcl remove a reference to the platform name apple when checking for Mac platforms, relying on a check for darwin instead, analog to [b5c6cb13cff53f]. Intended to resolve [forum:c26b856db7dd5caf|forum post c26b856db7dd5caf].
FossilOrigin-Name: 7ccffe38b4b99c61e4bfc703dc4b3516d7c2a72d73680b699886ee43eea9bd21
2026-05-09 14:01:57 +00:00
drh 2c8f97159c Fix upward propagation of expression properties into row-value expressions.
[forum:/info/8387cc287240643e|Forum post 8387cc287240643e].

FossilOrigin-Name: 0372dd717b852da8dc2c6b0ff2da02a2ac25bc0486a38e4c2a970694d897b6c9
2026-05-09 13:54:40 +00:00
drh 993bc6b171 Improved detection of OOM in the regexp_bytecode() SQLite function
(available in debug builds only).
[forum:/forumpost/a3a10e1c7b5dd1d2|Forum post a3a10e1c7b5dd1d2].

FossilOrigin-Name: 172e8c35badadc3c5eb5065da8ed2161093a0adff0ecfb5b162f78a9fb55bb34
2026-05-09 13:16:18 +00:00
drh 86d1ef8bf7 Fix an integer truncation problem due to oversized inputs to the
approximate_match virtual table extension.
[forum:/info/625f687e4d25c392|Forum post 625f687e4d25c392].

FossilOrigin-Name: 2881e17cc030814b350ca371fd2fd2089d3ff2fcc4ea52654de8bc5c2fc95222
2026-05-09 12:41:23 +00:00
drh e707e68420 Fix a corner-case floating-point rounding problem in the generate_series()
virtual table, introduced by [c113e31b818d1677] and reported by
[forum:/info/6e6cf9054bea2b1d|forum post 6e6cf9054bea2b1d].

FossilOrigin-Name: ff56785674ebeb1d4bab66ba0827956a2728c8c14419f933d40dc0152efbd4f5
2026-05-09 12:03:14 +00:00
drh 1788b7b719 Remove unnecessary and incorrect assert() statements in the new %J code.
FossilOrigin-Name: 5a8497dbec80b55a2dedc8a6f9ee823243e01a08d37ceeed1f87214dfe527d63
2026-05-07 20:45:57 +00:00
drh d4c4e91674 Add the %J and %j conversions to printf() - rendering JSON string
literals.

FossilOrigin-Name: 7e6fd838a15f08bf15d57f1887ed1019b58aa6be9eb7b162e67e0d9412082c6d
2026-05-07 15:04:30 +00:00
drh 4f95a67b0c Fix harmless UB by declaring a constant 1 to be unsigned.
FossilOrigin-Name: c5235734a6787b9c59a356f4e0ccbb3f262b8d9a6407ebd415afc87e859b0498
2026-05-07 14:47:21 +00:00
drh 31c19f587d Fix another off-by-one error.
FossilOrigin-Name: 8ae724a647dfcb986c4037caaab07e5d0188e66b66039a4758937c963f1fcbf7
2026-05-07 14:30:34 +00:00
drh bfc0414b9a Bug fixes in the new %J and %j logic.
FossilOrigin-Name: d1b55572c76569cb1e999028e79bcf2e76069f8778f0c8e933ace606d7b6a8b2
2026-05-07 14:14:05 +00:00
drh f9db8b2350 Add support for width to %J and %j.
FossilOrigin-Name: 0b102f2076586c08b134c78cc18acdd05555fa2b534c895535e96a0466661910
2026-05-07 10:46:39 +00:00
drh ad37ec3aa2 Separate conversion letters %J and %j. %J includes surrounding quotes and
%j omits them.  Some test cases added.

FossilOrigin-Name: ea7c2c1a6bd32695fc3a1f2295b4c4719e3d9dd0a8f6480e7c95e17b564c3391
2026-05-07 08:58:10 +00:00
drh 4b77e36dec Get precision working on %J, both with and with the ! flag. Width is
still a no-op, and there are no test cases.  Incremental check-in.

FossilOrigin-Name: 18166bce208c64b7f3afaa88c27a10e0295a89f9c72942dcaa949cf7efb479e6
2026-05-07 00:02:55 +00:00
drh 3ece90420d Experimental prototype of the %J conversion in printf().
FossilOrigin-Name: 7f6b1bae0849f0a840b95ae95aa6fdc6d51b72fdd50493649f584ad7829a3060
2026-05-06 20:48:34 +00:00
drh 8ff6523afd Fix a minor typo in a comment in the json module.
FossilOrigin-Name: 5c6d3e9d83af94234f67b06bd23c4eb0178a80c74c39be4945b3282244630020
2026-05-06 19:13:21 +00:00
stephan 821323938d Minor internal doc typo fixes.
FossilOrigin-Name: d588680240a7f4395f2ef52dd78635de66120a677a41a28ceb96921b3333973d
2026-05-06 06:57:42 +00:00
stephan e572525306 Delete the OPFS SEE test dbs when done with them.
FossilOrigin-Name: cbb9f7bcd04c586d523192f32c0ce7dcec72a76bb9a9c3b67fcde1f9e79f7218
2026-05-05 15:31:41 +00:00
stephan 082aebfa0d Add importDb() tests of SEE databases for the opfs/opfs-wl VFSes, analog to the opfs-sahpool test in [a5a9ac7ad6622b].
FossilOrigin-Name: 534d388031dfacf83cb40c712150a376db10393c98936a33ea80da0166b3dbeb
2026-05-05 15:26:24 +00:00
stephan 6f842b8da4 For the various OPFS VFS db-import routines, in SEE builds (only) relax the is-this-a-db check to allow for SEE dbs not having the usual header string in cleartext. Addresses [https://sqlite.org/see/forumpost/f84bef3552|SEE forum post f84bef3552].
FossilOrigin-Name: a5a9ac7ad6622b5b07193aa279deefde7d59ca17f81aba9dd78ee90718446993
2026-05-05 08:51:10 +00:00
stephan 3d84988efe Fix a predicate which causes a false error in the OPFS SAHPool tests when building with SEE.
FossilOrigin-Name: 97ab0a2fd56501d08bc2ab2adbe70a4130f6c5b5db343941fa16c7b34c550bd3
2026-05-05 08:11:55 +00:00
stephan 128f65ca6d Add #include time.h to shell.c.in to get fiddle building with emcc 5.x.
FossilOrigin-Name: ae675dfe7946d37f224f22ad34b6ebe049fb1426cb3d49d88ebc10d0b7634e40
2026-05-05 07:57:54 +00:00
drh 79a4591478 Fix a test case on CLI ".help" output due to the output changing because
of check-in [1590b401ff0670b6].

FossilOrigin-Name: b6ad35096d48de29fd8da498ac23cd7eba9194a70cad05ec49c318f11fe77199
2026-05-04 18:36:04 +00:00
dan 14fa0e9b86 When using the OR-optimization on a loop created by exists-to-join, do the early-exit logic for the whole loop, not for each individual OR branch. Possible fix for forum post [forum:c8b205720c | c8b205720c].
FossilOrigin-Name: 70a5d091241748aea63aa13bb218b573d0b84615a0682c2ec693addbfe9bc2f4
2026-05-04 18:16:09 +00:00
dan b4d42c6702 When using the OR-optimization on a loop created by exists-to-join, do the early-exit logic for the whole loop, not for each individual OR branch. Possible fix for forum post [forum:c8b205720c | c8b205720c].
FossilOrigin-Name: 3d197dd03c47a0fb687064131dbf519b8c587a47f9c2bccfdb83d7eae8418336
2026-05-04 17:16:43 +00:00
stephan 3ed7926e2f Add a missing tcl quote which breaks the build on msys2.
FossilOrigin-Name: 9380be51c76392eca9544f1fd56373406d2ce001e77a8d476deb772968f2aacc
2026-05-04 16:18:25 +00:00
drh 8923a68c5f Add the --color and --no-color options to the ".prompt" command of the CLI.
FossilOrigin-Name: 1590b401ff0670b68910504c281e67ecae7ae357ba3ed65f96dabaabd7aaeb7f
2026-05-04 12:58:05 +00:00
drh c94c21d0a6 Honor the NO_COLOR environment variable. If NO_COLOR is set and
SQLITE_PS1 and SQLITE_PS2 are not set, then the default prompt is
monochrome and ANSI escapes are omitted.  Also change the name of
the SQLITE_PS_NOANSI compile-time option to SQLITE_NO_COLOR.

FossilOrigin-Name: 7e4134e3ff1ca8712f5fc78fadae665549450988dc43af27c7fe0c77f10ce3fb
2026-05-04 10:14:13 +00:00
drh fb7c642990 New test cases for the shell prompt
FossilOrigin-Name: 2340cbbd230bdadedbb5d3eb9cfd287fd934e69ce5a2c989e141b0fc87b947aa
2026-05-03 18:31:49 +00:00
drh 2bc658c387 Improvements to the non-printing character delimiter logic in the CLI
prompt, for improved accuracy and for improved testability.

FossilOrigin-Name: f5649795cd8d7a1f270037ef0b212ea605c1665ec05a75a6da0b250f2aeab070
2026-05-03 15:53:52 +00:00
drh f02745088c Change the non-printing character escapes for editline to U+0001 and
U+0002.  Also insert those escapes for readline.  Readline does not
need it (because it can figure them out on its own) but they are needed
when editline is being used in readline-compatibility mode, and we have
no way of distinguishing between editline and readline in that case, so
we might as well include them.

FossilOrigin-Name: b9545b0f0b6fa0a777a064e1410b17f52cb49d853c188f9b8b3a772bd810ece3
2026-05-02 23:40:40 +00:00
drh 596008f078 Attempt to automatically insert U+0001 escape bytes before and after
ANSI X3.64 escape sequences when using editline, to work around the
limitations of editline.

FossilOrigin-Name: 63050bee6e5ed46cead14bd5a02b0cb6a7dbcdcb6a56601b90c10cf1c8d84efc
2026-05-02 23:03:42 +00:00
drh 1c9e8ca88b Fix a harmless compiler warning on some MSVC versions.
FossilOrigin-Name: bb8f6deeff31e9cc9b5a88b17b29dc6cd4fb4515134fa8e9758e33e42bbe89b0
2026-05-02 20:19:31 +00:00
drh c70fbd39f9 Minor optimization to the authorizer.
FossilOrigin-Name: 625f78a630bbf897f945065f1b85a92fe26e426302c8f6edc4d26a817ff62358
2026-05-02 17:30:33 +00:00
drh 9db9f18985 Fix harmless compiler warnings.
FossilOrigin-Name: a0d0b5aff51088699e5ad0a726c350d10744b1779acd9ee54232659fb4c352a4
2026-05-02 15:58:28 +00:00
drh f107c52d4c Close database connections in a timely manner to fix test script problems
on Windows.

FossilOrigin-Name: 068a53865a17d4c2b4ce227fc08a2c019d1dbf31d833842ec08ab5665a3565a3
2026-05-02 11:47:22 +00:00
drh 7509c6351e Fix an SQLITE_MISUSE memory error in check-ini [1f940357f7bb160b].
FossilOrigin-Name: fc64113e8f9aee0161e682db7c244a7849f2db86551a557b276deb1785941476
2026-05-01 17:42:15 +00:00
drh 57e46429fe Harden the recovery extension against SQL injections coming from the
sqlite_schema table of the database being recovered.

FossilOrigin-Name: 9190f1b9b8889e4d80c370db36b916744d5779de0ec51da0369bbe74adb7b931
2026-05-01 16:06:21 +00:00
drh a9c2e08087 Check-in [1786fcd5b4ee6cd9] works great and generates correct code, but it
upset UBSAN.  This check-in fixes the UBSAN complaint.

FossilOrigin-Name: 7cd76847e8c9b683e39c1063a343288f11b4aa5e9302394fe0c4244d361ee4f1
2026-05-01 16:03:51 +00:00
drh 7f1f4c803d The value for /f and other filename substitutions in SQLITE_PS1 is now
"memory" if open on an in-memory database.

FossilOrigin-Name: 4aac1057eeaf6c29a4893e9c080497c780b0963e810c501532d79eba1b457f27
2026-04-30 18:23:58 +00:00
drh bd13573a96 Remove lots of code used to support legacy Windows version (WindowsXP and
before).  Use Slim Reader/Writer Locks on Windows for non-recursive mutexes,
since SRWLs are now available on all supported Windows platforms.

FossilOrigin-Name: 6f7a5ff22db10f889596239aae5f6a1130cbdfe72612ceee68cbeb3b86e40227
2026-04-30 16:52:56 +00:00
drh 0cadeecf6f Fix a harmless compiler warning in src/mutex_w32.c.
FossilOrigin-Name: 8de796ca459f2c996937fe1107838b217572636110c8c09e358343385bf17a1b
2026-04-30 15:41:01 +00:00
drh ad52288e0d Fix the memdb1.test test module so that it runs correctly on Windows.
FossilOrigin-Name: 9012f8ea7454bbb792b4361ed74d778e02b98ef44a443f9a23a27a4222fda747
2026-04-30 15:33:44 +00:00
drh d5f0e01593 Change the CLI warning that appears in the header about opening a
in-memory database, to be more readable.

FossilOrigin-Name: cc64cafdfe0b0965fabaf9d561d9a108c20a3643f71b36e6a6c51fc8f30b82cb
2026-04-30 15:19:09 +00:00
drh 3e663bd8c7 Add the /m escape to the CLI prompt string. Use it to show "memory"
in italic for the default prompt for in-memory databases.

FossilOrigin-Name: 1e3c95393044279997833145cb610b98926fb0c8f53ae4fb35e9fcc6c1ded1a2
2026-04-30 15:07:25 +00:00
dan 97901bbd69 Fix a crash that could occur if the destination database of a backup is
replaced using sqlite3_deserialize() between the call to sqlite3_backup_init()
and the first call to sqlite3_backup_step(). Forum post [forum:15d82885e2 | 15d82885e2].

FossilOrigin-Name: 1f940357f7bb160b583ac5b08ff4e32a9fef353255d032c5a18bcb04416c0f0b
2026-04-29 19:14:54 +00:00
drh c549e28c67 Reduce the number of system calls in the os_win.c thunk from 81 down
to 58, as well as other simplifications.

FossilOrigin-Name: c49521739c961ed5e38424bf26b2e34d8017a85122622ceac5af7398233fe343
2026-04-29 16:09:47 +00:00
drh 90dae0d01d Further simplifications to the interface to the Windows family of OSes.
An incremental check-in.

FossilOrigin-Name: f2aa6b66d2a1b5bb2dc8a44788a061a549a846244354df19b44398839578461e
2026-04-29 15:03:01 +00:00
drh 0a0a52ae93 Simplify the Windows interface by removing support for various WindowsXP
and WindowsCE.  This is a work-in-progress.  More to follow.

FossilOrigin-Name: 4dbc4d19c4f3b58d0bedd766cfba65d9624903a5ab0a340eb83ddeec058607ea
2026-04-29 14:02:23 +00:00
drh e1d4680fdd Updates and simplifications to the mutex implementation on Windows. Uses
Slim Reader/Writer Locks for performance, for non-reentrant mutexes.
Requires WindowsVista or later.

FossilOrigin-Name: a75aa5400f94531b8ffd00be4580b0578d8d81ba9681fd06b1d9d96a6180994c
2026-04-28 20:01:03 +00:00
drh 532e506d18 Attempt to align instances of the sqlite3_mutex object at 128-byte boundaries
to prevent false-sharing in multi-core machines. See the discussion at and
around [forum:/forumpost/2026-03-25T23:15:03Z|forum post 2026-03-25T23:15:03Z].

FossilOrigin-Name: 1786fcd5b4ee6cd9b4780f3687dfaec5b90ef0476e0da266a94e069b98e70514
2026-04-28 15:12:40 +00:00
drh c9bed48c3c Fix comment typo
FossilOrigin-Name: d64a1dbe0fb2d9286806d833a3146b21d5bf1636a650d5a64cf163c7f2356e98
2026-04-28 12:49:10 +00:00
drh 6b4eb6725f Improved detection of when the alternative windows-compatible main() is
needed in the sqldiff.exe utility.

FossilOrigin-Name: e25b849a603cb08ab888641ff324e9aa129391c4792de86a62c55e9caf84c16e
2026-04-27 14:01:23 +00:00
drh 6176034151 Relax arbitrary length restrictions on some error messages in the CLI.
FossilOrigin-Name: 52f5531ded8e0d3da3dc4d5c9b3d14573041b9446e2c45b5093c958b6c74e0de
2026-04-27 12:43:09 +00:00
stephan 7471b5d6c0 Increase a stack-local buffer size to resolve a test regression introduced in [ff084ae341eab5c], in which some tests fail a pattern match check if their absolute file names are too long. Resolves [forum:205f73db1b2806f5|forum post 205f73db1b2806f5].
FossilOrigin-Name: 286fc22c7ca882b0b09de2f1b2a7e0b382af6c498f2f8ad237fe064feaa70d7f
2026-04-27 12:05:56 +00:00
dan 1fc7341ad2 Fix a crash that could occur if sqlite3_deserialize() was used to overwrite a database with an open transaction on it. Bug introduced by [fc42d31d6fca21ab] on 2018-03-07 and first released in version 3.23.0. This is not a vulnerability as an attacker that can exploit this already has the ability to execute arbitrary code. Forum post [forum:39134ba029 | 39134ba029].
FossilOrigin-Name: 77662cce9aa452ba5820e5f1a864a802763bd83b937cdb16e7025219eff7bd08
2026-04-27 11:32:43 +00:00
stephan 6af10b0efd Refactor the uint128 type check to consolidate duplicated rules.
FossilOrigin-Name: 362ef7bc00eea180fb3458bd8887021aed54e321901483edc00ad6b23a33d8e5
2026-04-27 06:48:07 +00:00
stephan 70d1e3e75e Feature detection fix for uint128 on RISCV 32-bit, reported in [forum:f8d1417ce8eb2f22|forum post 2026-04-26T11:41:01Z] and validated by the OP.
FossilOrigin-Name: c4a2c20839ef75534d71d928a03ad276bdc488b0baa76c1505f48dc1ff0bcdd1
2026-04-27 05:55:38 +00:00
drh 74006fdcdb Improvements to temp-file cleanup on close in the CLI.
FossilOrigin-Name: 048c969c34eaaf8c203b996e999a7dbc94c47b4959719af9e6625052520f7135
2026-04-26 18:35:26 +00:00
drh 4b9f9d6794 In the CLI, eliminate the 2-second delay before getting the prompt back
after running a ".www" or ".excel".  Instead, remember the name of the
temporary file that is to be deleted and delete it later.

FossilOrigin-Name: 4e9ee211ca661fe8978b7f676f33ef962ff5c8177bc7c5ad42b19a2fa4482d90
2026-04-25 16:17:23 +00:00
drh 39b2fe7e5b Simplify the modePush()/modePop() implementation in the CLI.
FossilOrigin-Name: 92cad2cd7284d3a969b713336865007da2b0b2a8eef367d4185802174cbd027a
2026-04-25 15:05:46 +00:00
dan 01ec60bb87 Do not run expridx2.test as part of the "journaltest" permutation, as it requires wal mode.
FossilOrigin-Name: 11cfbae735669776c967522294b1606af90b4bd5379e2e1ed722520887b57757
2026-04-25 14:32:05 +00:00
dan e2dbd0a578 Fix a single-byte OOB read that could occur in the session module when concatenating patchsets.
FossilOrigin-Name: 60d7cd625a6160ba1bc60fd00fab2e91e0deff42034c6864107c19330b35ea7a
2026-04-25 14:00:29 +00:00
drh df7b22790e Minor code simplification in the CLI.
FossilOrigin-Name: 59795c71e5745e8a27dc596b9f1fc8f137df58a6ec8d8d0dab2b31cf8562796d
2026-04-25 10:45:17 +00:00
drh 50811f74e0 Fix a problem in the CLI in which the ".www" command followed by
".dbstat" does not restore the mode to its original setting afterwards.

FossilOrigin-Name: 4a5cac1d00e1fa287ab8ce3437c0152a9f362d72bdb9976889c93f6368b3fd66
2026-04-25 00:29:40 +00:00
drh d7bf8233ff Fix test cases due to recent CLI default prompt changes. Make sure ANSI
escape codes are recognized on Windows terminals.

FossilOrigin-Name: 22b791e404f0895b0f343b2ce0e169a7ada8578e46a39a9f4bd3a31f81c9853c
2026-04-24 20:27:42 +00:00
drh 455486bbc4 Improvements to the default prompt for the CLI.
FossilOrigin-Name: 9ab0bb487f78ba5a51cd3b4efcee23b11096e90ebf3add4da18aea657e8de0b6
2026-04-24 09:21:25 +00:00
drh ba9092e228 Improved default prompt in the CLI.
FossilOrigin-Name: 78193b60346dba304485241dd65f0eb6e971714397b76191b3bf7a5fdb44916b
2026-04-23 22:15:18 +00:00
drh 67d4e0226f Fix harmless compiler warnings. Enhance the CLI prompt mechanism to make
it more easily reusable by other projects, and to add the /h, /H, and /u
escape sequences.

FossilOrigin-Name: 354ce4f5e2a86b621b2058a6372495a5919cf3dc88ce1ed3637642f720c48002
2026-04-23 20:13:14 +00:00
drh 323b3fc121 Fix a bug in [dbc1d6f0a3452607] that disrupts the display when running
on a Windows console (not redirecting to a file).

FossilOrigin-Name: 75b5df2b0046827e985712b5725488b59667e2cfd171cde9f1194f2917165205
2026-04-23 20:06:16 +00:00
drh 78279e2550 Improvements to the #define mechanism in the CLI that allows other programs
that incorporate the shell.c source file to customize some of the
prompt expansions.

FossilOrigin-Name: e5f8840d11c1863cfc5c0b4a3d16c9ec9ab6ca6ecb7ff1a3a9a237715eed5d30
2026-04-23 16:40:34 +00:00
dan 4e4962f6b3 Have testrunner.tcl return a non-zero exit status if one or more tests have failed.
FossilOrigin-Name: 50ddc36d32540b6d98990b5c9a465beb5b68cc31ae3f0ed162f6fe07e7c74e2d
2026-04-23 15:54:00 +00:00
drh ed95a9d103 New CLI prompt escape sequences: /r, /A, /v, /V, and /D../D.
FossilOrigin-Name: e7c7acd1a4e5cb0f8e68f119352480c1aa9dd3c2b289c92f88e9e697a2e440e0
2026-04-23 15:05:46 +00:00
drh e791e7c7a0 Fix harmless compiler warning.
FossilOrigin-Name: 706373dcbe43752cf21604f278f96fb3015a36089ad64f069bb3a64bc526d137
2026-04-23 14:49:21 +00:00
stephan 2545d9c201 CLI shell: handle the special case of .prompt "" to not incorrectly report that as an OOM. Reported [https://fossil-scm.org/forum/forumpost/ca277be73a|in the Fossil SCM forum].
FossilOrigin-Name: 3fa0500b964869c45f7b49717680b791049f7cd8c47a06a337dd22351d2002f9
2026-04-23 14:05:36 +00:00
dan 2a07c6de2c When updating indexes on expressions, omit the update if the new indexed values are the same as the old.
FossilOrigin-Name: f0fc4e5ec334da3a1db8b0334e23d1911874c96675690da94210b2afb45f7307
2026-04-22 19:38:04 +00:00
dan 68b07ddd41 Add OOM test for the new code on this branch.
FossilOrigin-Name: 685be705a2a69e79de0d581e5bbe9d942133817342121786a551d95d59a4af6c
2026-04-22 19:25:12 +00:00
drh 4b69e7704b Mark branches that are believed to be unreachable.
FossilOrigin-Name: 18ed9736fbbea762e04b8ad156e83e577e60c5234a5e233540ad8664fe3b379c
2026-04-22 19:12:48 +00:00
dan e17ab401f0 Instead of adding a branch to OP_IdxInsert, use an extra OP_IfNull when updating indexes on expressions.
FossilOrigin-Name: 5112f96cb1428693614b91bcca78326fccf7289e395a920339676ceeba871e3f
2026-04-22 18:19:24 +00:00
dan 57ee077c5e Enable the optimization on this branch for indexes on expressions only.
FossilOrigin-Name: 3576b6930436b3149c5033476f70518e9b4adeaeec2538a68b7dc599eb854c47
2026-04-22 17:50:50 +00:00
dan c7edd22456 Fix P3/P5 confusion in the comment above OP_IdxDelete.
FossilOrigin-Name: 0fb6ca8e22f355838a02223dd24b34c0669a6cc0b7ac80931c721db72cf5c1c8
2026-04-22 17:31:19 +00:00
dan 8606a52934 Detect and omit cases where an index key is being replaced with an identical key.
FossilOrigin-Name: 7a01caf0d0864a462eb81b7643dcf89048f4ae56a29d762f0cfc38066218504f
2026-04-22 16:58:36 +00:00
drh 5c6404aa77 Fix a integer type bug in the previous check-in.
FossilOrigin-Name: 5e1b5ff1c170e94956bf77bd38afcad6369cb9f9333864711212af045281ad0e
2026-04-22 13:41:20 +00:00
drh 429369f4aa QRF and the CLI preserve zero bytes when outputing unformatted BLOB values.
FossilOrigin-Name: dbc1d6f0a3452607e4e92155df0fdb1c5ea4ea5ad1654664f86383faf6c105ef
2026-04-22 12:22:32 +00:00
drh 9529894a29 Fix harmless compiler warning in QRF.
FossilOrigin-Name: dcfb505ac7762f7a3102e1c3853a83dca1d1ef436cc8981949b63c7d7992cc5b
2026-04-21 22:19:38 +00:00
dan c4c47a49a8 Disable optimization of nested EXIST loops. Possible fix for [forum:/forumpost/077c02e8ed | forum post 77c02e8ed].
FossilOrigin-Name: d0d95a39bc211034071746395ee6dbcfbea9d573d27480655391ba9698f7e214
2026-04-21 20:40:22 +00:00
drh ee7f6f29fc Fix the BETWEEN operator so that it propagates the EP_Subquery,
EP_Collate, and EP_HasFunc flags in the expression tree, as it ought.
Fix for the problem reported at
[forum:/forumpost/2026-04-21T19:10:55Z|forum post 2026-04-21T19:10:55Z]
and probably a bunch of other problems too.  This has been incorrect
for a long time.

FossilOrigin-Name: 2d1d2947b28f5dcbf4c66b594e176038c4e71e61948a88db565c1cb1212657ce
2026-04-21 20:28:38 +00:00
dan c3b52dff53 Correctly dequote the constraint name passed to ALTER TABLE DROP CONSTRAINT. This improves upon the partial fix in [7f5afb12f4]. Forum post [forum:/forumpost/a746b08b87 | a746b08b87].
FossilOrigin-Name: 9387238cb0bb2651c64ecb229072df039577d71fd91546a2f0c94ca9755bf25d
2026-04-21 18:07:04 +00:00
dan 30af54043e Disable optimization of nested EXIST loops. Possible fix for [forum:/forumpost/077c02e8ed | forum post 77c02e8ed].
FossilOrigin-Name: 64f0d9eb63a351de12bb9643f1f6390a89f6d95dc81c07f1dc409ee31bae6f83
2026-04-21 16:40:08 +00:00
drh 4ebed2a4e5 Fix an off-by-one buffer size calculation in the decimal.c extension.
FossilOrigin-Name: 24df42104d5fd1cf974f2bf539fa7515808e21e68d64899c39f5fcf3bee49a48
2026-04-21 16:07:15 +00:00
drh 532ed30ab2 Do not unnecessarily delay opening the database file in the CLI.
FossilOrigin-Name: ab6ee76c1266b753d0749c63890f40db875536ceb4dd3252cc7a3386e386a0c1
2026-04-21 14:39:23 +00:00
drh 5e99eeb3d8 Fix another bug in [/info/e33da6d5dc964db8|EXISTS-to-JOIN] - this one
having to do with an OFFSET clause of a LIMIT.  Reported by
[forum:/forumpost/2026-04-21T10:57:09Z|forum post 2026-04-21T10:57:09Z].

FossilOrigin-Name: 1dd3c6a5e50ac3f617683678223e42f48636c17e58905506c5c74cc4bf05845c
2026-04-21 12:59:43 +00:00
stephan d3abfd77f5 Reformulate how the OPFS async proxy Worker is loaded for the bundler-friendly builds to work around a bug in Vite in which it incompatibly rewrites the Worker's URL arguments. Addresses the discussion at [https://github.com/sqlite/sqlite-wasm/pull/159|npm package PR 159] and patch validated by Jure Rotar.
FossilOrigin-Name: ec877a30f5c5db30b9154745140744c5cc80d89673d79e50901d7377599cf2b4
2026-04-21 10:19:38 +00:00
drh 63984b6ab7 Fix the fileio extension for Windows builds so that it does not depend on
the sqlite3_win32_utf8_to_unicode() and sqlite3_win32_unicode_to_utf()
routines that are found in the SQLite core.
[forum:/forumpost/2026-04-20T02:02:56Z|Forum post 2026-04-20T02:02:56Z].

FossilOrigin-Name: fe0414a9a3caf6af67f53a5f3534efd5d4bf4978ebce1c591ef62d6961e55701
2026-04-20 10:28:19 +00:00
drh 93082e5589 Fix a harmless compiler warning in test/speedtest1.c.
FossilOrigin-Name: 13f0fd1daaf787bff6eded4a01c2cf47c79e52cf8812bb344995e28c015a4ed1
2026-04-18 21:51:32 +00:00
drh f6b807a022 Performance improvement in the substr() and length() SQL functions.
Caution: The new code may give different results for malformed UTF8.
I do not consider that to be a problem by the GI/GO principle.

FossilOrigin-Name: 6124d27a33f4562f40777c2c6318d61709f7b481f23f9ade45064d8ad0700752
2026-04-18 12:31:53 +00:00
drh 8c6a6b85db Fix a test case that started failing due to a change in the help text
for the ".prompt" command of the CLI.

FossilOrigin-Name: bfe9df1bf4660ce6bdfd11a4f06d32694c93750c7bc0a6432459d9f1089eace4
2026-04-17 19:12:27 +00:00
dan 9f76fb019e Update interrupt2.test to avoid using Tcl_GetCommandInfo() from within calls to Tcl_DeleteCommand().
FossilOrigin-Name: 1979aa0902a43f20d4e396c5f9b9a49aaf0094d8520bf53ce058bb379a7720ab
2026-04-17 15:09:50 +00:00
drh 29997b4731 Help-text and comment fixes in the CLI. Add the output-redir-test.txt
testing script.

FossilOrigin-Name: ec58a133b4716d4f5a53c38853f8ecdb7a1275b931663ce07c8dc700d845e62e
2026-04-17 11:41:33 +00:00
drh f0c8e27670 Less error-prone parsing of ".once" options. Fix for the problem reported
by [forum:/info/2026-04-17T01:26:56Z|forum post 2026-04-17T01:26:56Z].

FossilOrigin-Name: b235a8093734f457c2731af017c99027510d04e5cd1060f5559afbec5409a472
2026-04-17 01:50:40 +00:00
drh d6c985ad0b Fix testrunner.tcl so that it does not create directories with names
ending in "sqlite3.o" or "sqlite3.lo".  Use "sqlite3obj" for those
directory names instead.  Otherwise "make clean" gets confused.

FossilOrigin-Name: a4bf9289c9cdb12b22bfb24e7e2b0243384e9edece12ae3fed14afbfe9b93260
2026-04-16 19:50:52 +00:00
drh 1696740fdc Improved comments explaining the logic, and improved diagnostic messages
when testrunner.tcl encounters trouble loading the sqlite3 extension.

FossilOrigin-Name: 8d820b0ccfccff94ed5b7297fa587ac27a74e990f233cfe229da7409ab2e3af8
2026-04-16 19:33:04 +00:00
drh 2cefd5954a Minor updates to testrunner documentation.
FossilOrigin-Name: f8507bd5e221783175be2bb7b907c098e0970b1cbcb01817df7217005eab9ab4
2026-04-16 17:07:23 +00:00
dan 8895c50403 Run C-language tests in tests/c/ as part of release test.
FossilOrigin-Name: e6929022b71c6bff7aa9cfbdb8a5e80499ca856b8c06d4211c55311b48704a08
2026-04-16 16:43:32 +00:00
drh a2116f86b0 The sqlite3_str_new() interface never returns NULL, even after an OOM.
Code that uses that interface should invoke sqlite3_str_errcode() to see
if an OOM has occurred.

FossilOrigin-Name: cb910047edc591102dd4ed0d57c863b366981b8aa1d3085d5f95b1fbbabf6c31
2026-04-15 14:41:25 +00:00
drh 1e472f2d32 Bug fix in sqlite3_str_free(): Do not crash if called on an sqlite3_str
object returned from sqlite3_str_new() following an OOM.

FossilOrigin-Name: 92d8ac0c4938e43b00111b43630ecfa91c30e5200ca03937a84f4b50bd1a13b3
2026-04-15 14:17:58 +00:00
drh e3dbf75160 Add support for the analyze() extension function to the fuzzcheck test
program.

FossilOrigin-Name: abd94554d24122f0e0c3b7b36e5846e8c7aa0dddbddd9aae35b3f6ae6ab283c0
2026-04-15 13:05:43 +00:00
drh ade9289540 Improve the analyze() extension so that it returns a simple error message
as text (it does not throw an error) if the database is empty or is "temp"
or does not exist.

FossilOrigin-Name: 35379f60922f14d4f6d231ae627df066ad4e4510208bcc244505b9afb279f2fa
2026-04-15 12:47:37 +00:00
drh dfba4eb34a Fix harmless compiler warnings seen by MSVC in the previous check-in.
Also add ext/misc/analyze.c to the makefile dependencies for the CLI.

FossilOrigin-Name: 5a0ee6e227042e41ac64869c75f961798b252458670c18662175fe5bfa8b87d3
2026-04-15 12:35:23 +00:00
drh b57d2a5a51 Add the analyze() SQL function implemented the ext/misc/analyze.c extension
(but not part of the amalgamation).  Make that function available in the
CLI and add a new ".dbinfo" command to the CLI using that function.

FossilOrigin-Name: 4c4988e56df3c668b8891e2a55887a6618f5b84e9111e562b93059ddb9a662dc
2026-04-15 12:20:11 +00:00
drh 84c1079220 Add a missing open_db() call in the new ".dbstat" command of the CLI.
FossilOrigin-Name: a138e44a243466f8679e9652421f8c893a4a1bc0addc86736588d9aee51cf090
2026-04-15 11:58:54 +00:00
drh c8d866294b Add the .dbstat command to the CLI.
FossilOrigin-Name: fdba76df2b3a5b4d56ba79f80fd8b16d5faebca1fb07a266262be2ea635e6f94
2026-04-15 11:41:50 +00:00
stephan f0baddb205 On platforms which support ./configure --with-icu-config, ensure that 'make tclextension' includes the ICU pieces in the tcl extension. Intended to resolve [forum:92a88de4b88929fe|forum post 92a88de4b88929fe].
FossilOrigin-Name: 404fde8bacc3f05254a2fcdb1531a7f53125a7ad45dc6b68a5065c1d0c73b670
2026-04-15 11:40:13 +00:00
dan ec2ea68d1f Avoid a potentially undefined left-shift in the fts5 porter tokenizer.
FossilOrigin-Name: 46a288e43e4c48fca6610bce6a2db418aaa3c8a1dcb115b7a0bfad2063355e58
2026-04-15 11:23:30 +00:00
dan c71e6dd054 Handle the case where the name of the constraint in an "ALTER TABLE DROP CONSTRAINT" command is quoted. [forum:8bfbaec404 | Forum post 8bfbaec404].
FossilOrigin-Name: 7f5afb12f4a5d35cfe6b95f17d85ce85ce62159515bfe26fb9c67d3ce26b6d4a
2026-04-15 11:07:58 +00:00
drh 9efde40a5d Compute the average fanout for b-trees of depth 2 or more.
FossilOrigin-Name: 2f8598bb09f32100f22dd1d9465f3fa0f7f8d81f5d67c34de9919708345f090b
2026-04-15 10:49:14 +00:00
stephan afef1f2923 Minor doc typo fixes contributed by BrickViking.
FossilOrigin-Name: c4660ce205919ce0d9262e7fd7879375a72ef65d9e5ac97f19caf5dc36e355aa
2026-04-14 23:53:38 +00:00
drh 09f37099a7 Code simplifications. Return NULL is the specified SCHEMA does not exist.
FossilOrigin-Name: 953969c88149ea2f589942b98a0a96d907956319e5aa09e2d5a3ae76d3e7d70a
2026-04-14 23:19:02 +00:00
dan 846fbd6653 Handle a special case of a corrupt changeset in sqlite3changegroup_add().
FossilOrigin-Name: eba625f917935437e3f366197df1c6717c7120ce0418a0cbac1d2024b2235091
2026-04-14 20:18:54 +00:00
drh 526acc1834 Merge trunk enhancements and fixes into the analyze-sql-func branch.
FossilOrigin-Name: bb4a9beac87796b4186efce416bf03f840041bc588be6702c5a444d643df684e
2026-04-14 20:17:03 +00:00
drh d962afef2d Various minor tweaks and refinements to the analyze() function.
FossilOrigin-Name: 48d94e72eaec443011dd1f7a7264e5222c05bb515c84deaf20ed4195b74d45ff
2026-04-14 20:07:29 +00:00
drh 395ac6c1bc Implementation of the analyze() SQL function is now mostly complete.
FossilOrigin-Name: 1ca0386ab2df0d88ed52941469d459114f483ecf6b7e4691bcc32e909636cb7d
2026-04-14 19:36:33 +00:00
drh e9d0bc6b68 Much, but not all, of the report generation is added. An incremental check-in.
FossilOrigin-Name: 289fefc3edfeecb06dc11897d3ff24763fd9e25db44e369eeec29b9ed3de5ccc
2026-04-14 19:02:40 +00:00
dan 483f0ef13c Ensure c-tests use the locally built sqlite3.h file, not the system copy.
FossilOrigin-Name: 2d81ee65ffbed30fd98bdda96dc79c1929c73f806cea3c9e4c244b618980b202
2026-04-14 18:12:09 +00:00
drh 34c2c8dc47 Further fixes and clarifications. Now ready to start generating the
actual report.

FossilOrigin-Name: 037a0e586c4aa81a53aea164080e739f9d5b87197d04fdd68feb0ab5eb01020b
2026-04-14 13:18:04 +00:00
drh 3f9df1f5fb Further improvements to error messages and robustness.
FossilOrigin-Name: 45917c1124310fd77daca47865959d3261154f7b0c6771d3689e6d345a3fd153
2026-04-14 12:46:12 +00:00
drh 2eeac123c8 Improved error messages and robustness.
FossilOrigin-Name: c09ed9eb914bde96cdc5157e29b6384ce488262a2dadc7321c44941a1d08a187
2026-04-14 12:23:11 +00:00
drh 64a123a216 Initial prototype implementation of a new run-time loadable SQL function
to do approximately the same work as sqlite3_analyze.

FossilOrigin-Name: 03934b5a6c88a97117a4743a68ee36d30bcbdcebcf11342d30ac42daf9f10874
2026-04-14 11:25:22 +00:00
stephan 6e93d28950 Pass the ICU-related build flags through the 'tclextension' target, not just 'tclextension-install'.
FossilOrigin-Name: 499c89f8745fac7ee93ff79a340abd69b4f06b07983b4d50057fa5d0893b2f3b
2026-04-14 00:48:34 +00:00
stephan 008395614a Tweak 'make tclextension' to pass on any --with-icu-config build flags so that it can link with the various ICU libs. This works for me but needs testing on a wider selection of systems before being moved to trunk. Intended to resolve [forum:92a88de4b88929fe|forum post 92a88de4b88929fe].
FossilOrigin-Name: a8c022d7946e8a29114400c1321f36f03979a2277253f1dcd769d8f36e944ece
2026-04-14 00:11:23 +00:00
dan dd1e2c39bc Update test/c/snprintf1.c to test the result of the sqlite3_snprintf() call. Also add an SQLITE_OMIT_AUTOINIT configuration and update C tests to account for it.
FossilOrigin-Name: 30b597d797e737a2907b755706a37d63c37c6a06c4e037098a6d9c482bcde887
2026-04-13 11:20:51 +00:00
dan 0523cfda5a Fixes for running C tests on windows.
FossilOrigin-Name: 1fcacdc41ab1bb66a628acdac29412e66decdc2578dd9d084baaffb74679f984
2026-04-11 18:05:49 +00:00
dan bc68ec78de Enhance testrunner.tcl to run individual tests written in C from the test/c/ directory.
FossilOrigin-Name: 6f140f76f47aadb10d3f35358503d91adb43e0c1802326f6e599effe1b08e7aa
2026-04-11 17:03:27 +00:00
202 changed files with 8836 additions and 4054 deletions
+125
View File
@@ -0,0 +1,125 @@
# AGENTS.md
Guidance for AI coding agents working in this repository.
## Project nature
SQLite is a self-contained, serverless SQL database engine written in C. The
source is **public domain** — no copyright or license header should ever be
added to any file. The blessing comment that appears at the top of each source
file is intentional and should be preserved unchanged.
SQLite does not accept pull requests without prior agreement and/or
accompanying legal paperwork that places the pull request in the public domain.
However, the human SQLite developers will review a concise and well-written
pull request as a proof-of-concept prior to reimplementing the changes
themselves.
SQLite does not accept agentic code. However the project
will accept agentic bug reports that include a reproducible test case.
Patches or pull requests demonstrating a possible fix, for documentation
purposes, are welcomed.
SQLite uses the [Fossil](https://fossil-scm.org/) for version control,
not Git. The canonical repository is at <https://sqlite.org/src>.
## Build
The configure script uses [autosetup](https://msteveb.github.io/autosetup/),
not GNU Autoconf.
```bash
apt install gcc make tcl-dev # prerequisites (Debian/Ubuntu)
./configure --dev # debug build
make sqlite3 # CLI shell
make sqlite3d # Debugging variant of the CLI shell
make sqlite3.c # amalgamation (single-file distribution form)
make testfixture # test runner binary (requires tcl-dev)
make tclextension-install # install TCL extension before running tests
```
## Testing
Tests are TCL scripts run through the `testfixture` enhanced interpreter.
```bash
# From the build directory:
./testfixture test/main.test # single test file
test/testrunner.tcl # quick suite
test/testrunner.tcl full # full suite
test/testrunner.tcl fts5% # pattern match
# Check for failures:
grep '!' testrunner.log
```
`make devtest` is the fastest way to run a representative subset. Always run
at least `devtest` after any change to `src/`.
## Architecture
SQL text → **tokenizer** (`tokenize.c`) → **parser** (`parse.y` / Lemon) →
**code generator** (`build.c`, `select.c`, `insert.c`, `update.c`, `delete.c`,
`expr.c`) → **optimizer** (`where*.c`) → **VDBE** (`vdbe.c`) → **B-Tree**
(`btree.c`) → **Pager** (`pager.c`) → **WAL** (`wal.c`) → **VFS**
(`os_unix.c`, `os_win.c`).
The master internal header is `src/sqliteInt.h`. Major subsystems have private
headers: `vdbeInt.h`, `btreeInt.h`, `whereInt.h`. The public API is defined in
`src/sqlite.h.in` (template) which generates `sqlite3.h`.
## Do not edit generated files
These files are produced by scripts and must not be edited by hand:
| File | Regenerate with |
|---|---|
| `sqlite3.h` | `tool/mksqlite3h.tcl` |
| `parse.c`, `parse.h` | build lemon (`tool/lemon.c`) then run on `src/parse.y` |
| `opcodes.h` | `tool/mkopcodeh.tcl` (reads `src/vdbe.c`) |
| `opcodes.c` | `tool/mkopcodec.tcl` (reads `opcodes.h`) |
| `keywordhash.h` | `tool/mkkeywordhash.c` |
| `pragma.h` | `tool/mkpragmatab.tcl` |
| `sqlite3.c` | `tool/mksqlite3c.tcl` (the amalgamation) |
Editing rules:
- To add a PRAGMA: edit `tool/mkpragmatab.tcl`, then regenerate `pragma.h`.
- To add a VDBE opcode: add the `case OP_Xxx:` handler in `src/vdbe.c`; the
opcode number and name are extracted automatically by `mkopcodeh.tcl`.
- To change the SQL grammar: edit `src/parse.y`, not `parse.c`.
## Coding conventions
- C89/C99 compatible C only. No C++, no STL, no exceptions, no VLAs.
- All memory allocation goes through `sqlite3Malloc` / `sqlite3_malloc64`
(never raw `malloc`). The `sqlite3MallocZero` variant zero-initializes.
- Integer widths: use `i64` (`sqlite3_int64`) for 64-bit values, `u32`/`u64`
for unsigned. Avoid bare `long` or `int` for values that could exceed 2G.
- Error propagation: functions return `SQLITE_OK` (0) on success and a
`SQLITE_*` error code on failure. Many routines also set `db->mallocFailed`
on OOM, allowing deferred error checking.
- Assert liberally for invariants that must hold in correct code; use
`ALWAYS(x)` / `NEVER(x)` for conditions that are logically always
true/false but that the compiler cannot prove.
## Extensions (`ext/`)
Extensions are compiled separately from the core and are not part of the
amalgamation by default (except where explicitly included). Major subsystems:
- `ext/fts5/` — Full-Text Search 5 (current)
- `ext/rtree/` — R-Tree spatial indexing
- `ext/session/` — Changesets and sessions
- `ext/recover/` — Database file recovery
- `ext/misc/` — Single-file utility extensions (many included in the CLI)
- `ext/qrf/` — Query Result Formatter utility library
## Useful references
- Architecture: <https://sqlite.org/arch.html>
- File format: <https://sqlite.org/fileformat2.html>
- VDBE opcodes: <https://sqlite.org/opcode.html>
- Query planner: <https://sqlite.org/optoverview.html>
- Lemon parser generator: <https://sqlite.org/doc/trunk/doc/lemon.html>
- Compile-time options: <https://sqlite.org/compile.html>
+20 -3
View File
@@ -1770,6 +1770,7 @@ FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\base64.c
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\base85.c
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\completion.c
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\decimal.c
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\diskused.c
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\ieee754.c
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\randomjson.c
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\regexp.c
@@ -1896,9 +1897,6 @@ RSYNC_OPT = \
sqlite3_rsync.exe: $(RSYNC_SRC) $(LIBRESOBJS)
$(LTLINK) $(RSYNC_OPT) $(NO_WARN) $(RSYNC_SRC) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS)
scrub.exe: $(TOP)\ext\misc\scrub.c $(SQLITE3C) $(SQLITE3H)
$(LTLINK) $(NO_WARN) -DSCRUB_STANDALONE=1 $(TOP)\ext\misc\scrub.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
srcck1.exe: $(TOP)\tool\srcck1.c
$(BCC) $(NO_WARN) -Fe$@ $(TOP)\tool\srcck1.c
@@ -2347,6 +2345,7 @@ SHELL_DEP = \
$(TOP)\ext\misc\base85.c \
$(TOP)\ext\misc\completion.c \
$(TOP)\ext\misc\decimal.c \
$(TOP)\ext\misc\diskused.c \
$(TOP)\ext\misc\fileio.c \
$(TOP)\ext\misc\ieee754.c \
$(TOP)\ext\misc\memtrace.c \
@@ -2741,6 +2740,9 @@ rbu.exe: $(TOP)\ext\rbu\rbu.c $(TOP)\ext\rbu\sqlite3rbu.c $(SQLITE3C) $(SQLITE3H
$(LTLINK) $(NO_WARN) -DSQLITE_ENABLE_RBU \
$(TOP)\ext\rbu\rbu.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
$(AUXTEST).exe: $(TOP)\test\c\$(AUXTEST).c
$(LTLINK) $(NO_WARN) $(TOP)\test\c\$(AUXTEST).c sqlite3.lo /link $(LDFLAGS) $(LTLINKOPTS)
THREADTEST3_SRC = \
$(TOP)\test\threadtest3.c \
$(TOP)\test\tt3_checkpoint.c \
@@ -2752,6 +2754,21 @@ THREADTEST3_SRC = \
threadtest3.exe: $(THREADTEST3_SRC) $(TOP)\src\test_multiplex.c $(SQLITE3C) $(SQLITE3H)
$(LTLINK) $(NO_WARN) $(TOP)\test\threadtest3.c $(TOP)\src\test_multiplex.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
# Convenienced targets for common executables that omit
# the .exe suffix
#
sqldiff: sqldiff.exe
sqlite3_rsync: sqlite3_rsync.exe
fuzzcheck: fuzzcheck.exe
testfixture: testfixture.exe
sqlite3: $(SQLITE3EXE)
showdb: showdb.exe
LogEst: LogEst.exe
wordcount: wordcount.exe
speedtest1: speedtest1.exe
kvtest: kvtest.exe
jimsh0: jimsh0.exe
# Display key variables that control which version of TCL is to be used.
#
tcl-env:
+5 -4
View File
@@ -33,10 +33,11 @@ verify its integrity, there are hints on how to do that in the
## Contacting The SQLite Developers
The preferred way to ask questions or make comments about SQLite or to
report bugs against SQLite is to visit the
[SQLite Forum](https://sqlite.org/forum) at <https://sqlite.org/forum/>.
Anonymous postings are permitted.
The preferred way to ask questions or make comments about SQLite is to
visit the [SQLite Forum](https://sqlite.org/forum) at
<https://sqlite.org/forum/>. Anonymous postings are permitted. For
bug reports, please use the [SQLite bugs
forum](https://sqlite.org/bugs) at <https://sqlite.org/bugs>.
If you think you have found a bug that has security implications and
you do not want to report it on the public forum, you can send a private
+2 -2
View File
@@ -2028,7 +2028,7 @@ array set proj__Cache {}
# Returns a cache key for the given argument:
#
# integer: relative call stack levels to get the scope name of for
# use as a key. [proj-scope [expr {1 + $arg + addLevel}]] is
# use as a key. [proj-scope [expr {1 + $arg + $addLevel}]] is
# then used to generate the key. i.e. the default of 0 uses the
# calling scope's name as the key.
#
@@ -2226,7 +2226,7 @@ proc proj-coalesce {args} {
#
# Pragmas:
#
# Passing :PRAGMAS to this function may modify how it works. The
# The prototype list may contain :PRAGMAS to modify how it works. The
# following pragmas are supported (note the leading ":"):
#
# :all-flags indicates that the whole input list should be scanned,
+41 -19
View File
@@ -466,7 +466,7 @@ proc sqlite-configure {buildMode configScript} {
proc sqlite-configure-phase1 {buildMode} {
define PACKAGE_NAME sqlite
define PACKAGE_URL {https://sqlite.org}
define PACKAGE_BUGREPORT [get-define PACKAGE_URL]/forum
define PACKAGE_BUGREPORT [get-define PACKAGE_URL]/bugs
define PACKAGE_STRING "[get-define PACKAGE_NAME] [get-define PACKAGE_VERSION]"
proj-xfer-options-aliases {
# Carry values from hidden --flag aliases over to their canonical
@@ -1067,7 +1067,7 @@ proc sqlite-get-readline-dir-list {} {
}
*-haiku {
lappend dirs /boot/system/develop/headers
if {[opt-val with-readline-ldflags] in {auto ""}} {
if {![opt-bool editline] && [opt-val with-readline-ldflags] in {auto ""}} {
# If the user did not supply their own --with-readline-ldflags
# value, hijack that flag to inject options which are known to
# work on Haiku OS installations.
@@ -1094,16 +1094,30 @@ proc sqlite-get-readline-dir-list {} {
# - HAVE_LINENOISE to 0, 1, or 2
# - HAVE_EDITLINE to 0 or 1
#
# Only one of ^^^ those will be set to non-0.
# Those will be set as follows:
#
# HAVE_LINENOISE=0, HAVE_EDITLINE=0, HAVE_READLINE=0: no line editing
#
# HAVE_LINENOISE>0: 1=antirez flavor, 2=msteveb flavor
#
# HAVE_EDITLINE=0, HAVE_READLINE=1: use GNU readline
#
# HAVE_EDITLINE=1, HAVE_READLINE=1: use BSD editline header
# <readline/readline.h>.
#
# HAVE_EDITLINE=1, HAVE_READLINE=0: use BSD editline header
# <editline/readline.h>.
#
# Other defines it sets:
#
# - LDFLAGS_READLINE = linker flags or empty string
#
# - CFLAGS_READLINE = compilation flags for clients or empty string.
#
# Note that LDFLAGS_READLINE and CFLAGS_READLINE may refer to
# linenoise or editline, not necessarily libreadline. In some cases
# it will set HAVE_READLINE=1 when it's really using editline, for
# reasons described in this function's comments.
# Both LDFLAGS_READLINE and CFLAGS_READLINE may refer to linenoise or
# editline, not necessarily libreadline. In some cases it will set
# HAVE_READLINE=1 when it's really using editline, for reasons
# described in this function's comments.
#
# Returns a string describing which line-editing approach to use, or
# "none" if no option is available.
@@ -1141,7 +1155,7 @@ proc sqlite-check-line-editing {} {
# If none of --with-linenoise, --enable-readline, or --enable-editline
# are provided, but there exists a directory "linenoise" at $HOME or
# a sibling of the build or source directory, then try to use that linenoise
# direcctory.
# directory.
#
if {"" eq $dirLn
&& ![proj-opt-was-provided readline]
@@ -1305,14 +1319,18 @@ proc sqlite-check-line-editing {} {
# If we found a library, configure the build to use it...
if {"" ne $rlLib} {
if {"editline" eq $editLibName && "HAVE_READLINE" eq $editLibDef} {
# Alert the user that, despite outward appearances, we won't be
# linking to the GPL'd libreadline. Presumably that distinction is
# significant for those using --editline.
proj-indented-notice {
NOTE: the local libedit uses <readline/readline.h> so we
will compile with -DHAVE_READLINE=1 but will link with
libedit.
if {"editline" eq $editLibName} {
# sqlite-add-shell-opt -DEL_WIDECHAR=1;
if {"HAVE_READLINE" eq $editLibDef} {
sqlite-add-shell-opt -DHAVE_EDITLINE=1
# Alert the user that, despite outward appearances, we won't be
# linking to the GPL'd libreadline. Presumably that distinction is
# significant for those using --editline.
proj-indented-notice {
NOTE: the local libedit uses <readline/readline.h> so we
will compile with -DHAVE_READLINE=1 but will link with
libedit.
}
}
}
set rlLib [join $rlLib]
@@ -1745,8 +1763,12 @@ proc sqlite-handle-env-quirks {} {
set autoDll 0; # true if --out-implib/--dll-basename should be implied
set host [get-define host]
switch -glob -- $host {
*apple* -
*darwin* { set instName darwin }
*-*-darwin* {
set instName darwin
# We don't look for *apple* because:
# https://sqlite.org/forum/forumpost/7b218c3c9f207646
# There's at least one Linux out there which matches *apple*.
}
default {
set x [sqlite-env-is-unix-on-windows $host]
if {"" ne $x} {
@@ -2042,7 +2064,7 @@ proc sqlite-check-tcl {} {
}
}
if {![file-isexec $with_tclsh]} {
proj-warn "Cannot find a usable tclsh (tried: $tryThese)
proj-warn "Cannot find a usable tclsh (tried: $tryThese)"
}
}
define TCLSH_CMD $with_tclsh
+6 -1
View File
@@ -33,7 +33,7 @@
The testrunner.tcl program is a Tcl script used to run multiple SQLite
tests in parallel, thus reducing testing time on multi-core machines.
The testrunner.tcl supports running tests that based on `testfixture`,
`sqlite3`, and `fuzzcheck`.
`sqlite3`, `fuzzcheck`, and C programs found in test/c.
<a name="runtr"></a>
## 1.1 Running testrunner.tcl
@@ -59,11 +59,16 @@ up front.
The standard Makefiles for SQLite include targets that invoke
testrunner.tcl. So the following commands also run testrunner.tcl:
* `make test`
* `make devtest`
* `make releasetest`
* `make sdevtest`
* `make testrunner`
Since SQLite 3.53.0, there is a "<tt>make.bat</tt>" script in the root
of the source tree that causes the "make" command to work on Windows.
There is no need to do something different using "nmake" on Windows.
<a name="outputs"></a>
## 1.3 Outputs from testrunner.tcl
+36
View File
@@ -606,4 +606,40 @@ ifcapable fts5 {
}
}
#-------------------------------------------------------------------------
reset_db
set ci {CREATE INDEX i1 ON t1(a COLLATE "binary,sqlite_expert_rem(999,0)");}
do_execsql_test 8.0 {
BEGIN TRANSACTION;
CREATE TABLE t1(a TEXT, b TEXT);
INSERT INTO t1 VALUES('v0','d0');
INSERT INTO t1 VALUES('v1','d1');
INSERT INTO t1 VALUES('v2','d2');
INSERT INTO t1 VALUES('v3','d3');
INSERT INTO t1 VALUES('v4','d4');
INSERT INTO t1 VALUES('v5','d5');
INSERT INTO t1 VALUES('v6','d6');
INSERT INTO t1 VALUES('v7','d7');
INSERT INTO t1 VALUES('v8','d8');
INSERT INTO t1 VALUES('v9','d9');
CREATE INDEX i1 ON t1(a);
COMMIT;
PRAGMA writable_schema = ON;
UPDATE sqlite_schema SET sql = $ci WHERE name = 'i1';
}
db close
sqlite3 db test.db
do_test 8.1 {
set expert [sqlite3_expert_new db]
$expert sql { SELECT 1234 }
list [catch { $expert analyze } msg] $msg
} {1 {no such collation sequence: binary,sqlite_expert_rem(999,0)}}
$expert destroy
finish_test
+2 -2
View File
@@ -1501,7 +1501,7 @@ static int idxCreateVtabSchema(sqlite3expert *p, char **pzErrmsg){
/* The statement the vtab will pass to sqlite3_declare_vtab() */
zInner = idxAppendText(&rc, 0, "CREATE TABLE x(");
for(i=0; i<pTab->nCol; i++){
zInner = idxAppendText(&rc, zInner, "%s%Q COLLATE %s",
zInner = idxAppendText(&rc, zInner, "%s%Q COLLATE %Q",
(i==0 ? "" : ", "), pTab->aCol[i].zName, pTab->aCol[i].zColl
);
}
@@ -1701,7 +1701,7 @@ static int idxPopulateOneStat1(
return sqlite3_reset(pIndexXInfo);
}
zCols = idxAppendText(&rc, zCols,
"%sx.%Q IS sqlite_expert_rem(%d, x.%Q) COLLATE %s",
"%sx.%Q IS sqlite_expert_rem(%d, x.%Q) COLLATE %Q",
zComma, zName, nCol, zName, zColl
);
zOrder = idxAppendText(&rc, zOrder, "%s%d", zComma, ++nCol);
+18 -3
View File
@@ -302,6 +302,12 @@
SQLITE_EXTENSION_INIT1
#endif
/*
** Assume any b-tree layer with more levels than this is corrupt.
*/
#define FTS3_MAX_BTREE_HEIGHT 48
typedef struct Fts3HashWrapper Fts3HashWrapper;
struct Fts3HashWrapper {
Fts3Hash hash; /* Hash table */
@@ -2018,7 +2024,11 @@ static int fts3SelectLeaf(
assert( piLeaf || piLeaf2 );
fts3GetVarint32(zNode, &iHeight);
rc = fts3ScanInteriorNode(zTerm, nTerm, zNode, nNode, piLeaf, piLeaf2);
if( iHeight>FTS3_MAX_BTREE_HEIGHT ){
rc = FTS_CORRUPT_VTAB;
}else{
rc = fts3ScanInteriorNode(zTerm, nTerm, zNode, nNode, piLeaf, piLeaf2);
}
assert_fts3_nc( !piLeaf2 || !piLeaf || rc!=SQLITE_OK || (*piLeaf<=*piLeaf2) );
if( rc==SQLITE_OK && iHeight>1 ){
@@ -2063,8 +2073,13 @@ static void fts3PutDeltaVarint(
sqlite3_int64 iVal /* Write this value to the list */
){
assert_fts3_nc( iVal-*piPrev > 0 || (*piPrev==0 && iVal==0) );
*pp += sqlite3Fts3PutVarint(*pp, iVal-*piPrev);
*piPrev = iVal;
if( iVal-(*piPrev)>=0 ){
/* Refuse to write a negative delta integer. This only happens with a
** corrupt db (see the assert above) and can cause buffer overwrites
** in some cases. */
*pp += sqlite3Fts3PutVarint(*pp, iVal-*piPrev);
*piPrev = iVal;
}
}
/*
+1 -1
View File
@@ -341,7 +341,7 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){
/* State 3. The integer just read is a column number. */
default: assert( eState==3 );
iCol = (int)v;
if( iCol<1 ){
if( iCol<1 || iCol>(pFts3->nColumn+1) ){
rc = SQLITE_CORRUPT_VTAB;
break;
}
+1
View File
@@ -471,6 +471,7 @@ static int getNextNode(
assert( nKey==4 );
if( zInput[4]=='/' && zInput[5]>='0' && zInput[5]<='9' ){
nKey += 1+sqlite3Fts3ReadInt(&zInput[nKey+1], &nNear);
if( nNear>=1000000000 ) nNear = 1000000000;
}
}
+10 -11
View File
@@ -132,10 +132,9 @@ struct StrBuffer {
/*
** Allocate a two-slot MatchinfoBuffer object.
*/
static MatchinfoBuffer *fts3MIBufferNew(size_t nElem, const char *zMatchinfo){
static MatchinfoBuffer *fts3MIBufferNew(i64 nElem, const char *zMatchinfo){
MatchinfoBuffer *pRet;
sqlite3_int64 nByte = sizeof(u32) * (2*(sqlite3_int64)nElem + 1)
+ SZ_MATCHINFOBUFFER(1);
sqlite3_int64 nByte = sizeof(u32) * (2*(i64)nElem+1) + SZ_MATCHINFOBUFFER(1);
sqlite3_int64 nStr = strlen(zMatchinfo);
pRet = sqlite3Fts3MallocZero(nByte + nStr+1);
@@ -887,7 +886,7 @@ static int fts3ExprLHits(
if( p->flag==FTS3_MATCHINFO_LHITS ){
p->aMatchinfo[iStart + iCol] = (u32)nHit;
}else if( nHit ){
p->aMatchinfo[iStart + (iCol+1)/32] |= (1 << (iCol&0x1F));
p->aMatchinfo[iStart + iCol/32] |= (1U << (iCol&0x1F));
}
}
assert( *pIter==0x00 || *pIter==0x01 );
@@ -1006,8 +1005,8 @@ static int fts3MatchinfoCheck(
return SQLITE_ERROR;
}
static size_t fts3MatchinfoSize(MatchInfo *pInfo, char cArg){
size_t nVal; /* Number of integers output by cArg */
static i64 fts3MatchinfoSize(MatchInfo *pInfo, char cArg){
i64 nVal; /* Number of integers output by cArg */
switch( cArg ){
case FTS3_MATCHINFO_NDOC:
@@ -1023,16 +1022,16 @@ static size_t fts3MatchinfoSize(MatchInfo *pInfo, char cArg){
break;
case FTS3_MATCHINFO_LHITS:
nVal = (size_t)pInfo->nCol * pInfo->nPhrase;
nVal = (i64)pInfo->nCol * pInfo->nPhrase;
break;
case FTS3_MATCHINFO_LHITS_BM:
nVal = (size_t)pInfo->nPhrase * ((pInfo->nCol + 31) / 32);
nVal = (i64)pInfo->nPhrase * ((pInfo->nCol + 31) / 32);
break;
default:
assert( cArg==FTS3_MATCHINFO_HITS );
nVal = (size_t)pInfo->nCol * pInfo->nPhrase * 3;
nVal = (i64)pInfo->nCol * pInfo->nPhrase * 3;
break;
}
@@ -1314,7 +1313,7 @@ static int fts3MatchinfoValues(
case FTS3_MATCHINFO_LHITS_BM:
case FTS3_MATCHINFO_LHITS: {
size_t nZero = fts3MatchinfoSize(pInfo, zArg[i]) * sizeof(u32);
i64 nZero = fts3MatchinfoSize(pInfo, zArg[i]) * sizeof(u32);
memset(pInfo->aMatchinfo, 0, nZero);
rc = fts3ExprLHitGather(pCsr->pExpr, pInfo);
break;
@@ -1383,7 +1382,7 @@ static void fts3GetMatchinfo(
** initialize those elements that are constant for every row.
*/
if( pCsr->pMIBuffer==0 ){
size_t nMatchinfo = 0; /* Number of u32 elements in match-info */
i64 nMatchinfo = 0; /* Number of u32 elements in match-info */
int i; /* Used to iterate through zArg */
/* Determine the number of phrases in the query */
+5 -1
View File
@@ -3129,6 +3129,10 @@ static void fts3ReadEndBlockField(
for(/* no-op */; zText[i]>='0' && zText[i]<='9'; i++){
iVal = iVal*10 + (zText[i] - '0');
}
/* This if() clause is just to avoid an integer overflow. The record is
** corrupt in this case. */
if( (i64)iVal==SMALLEST_INT64 ) iMul = 1;
*pnByte = ((i64)iVal * (i64)iMul);
}
}
@@ -4355,7 +4359,7 @@ static int fts3IncrmergeLoad(
return FTS_CORRUPT_VTAB;
}
pWriter->nLeafEst = (int)((iEnd - iStart) + 1)/FTS_MAX_APPENDABLE_HEIGHT;
pWriter->nLeafEst = (int)(((iEnd - iStart)+1)/FTS_MAX_APPENDABLE_HEIGHT);
pWriter->iStart = iStart;
pWriter->iEnd = iEnd;
pWriter->iAbsLevel = iAbsLevel;
+2 -2
View File
@@ -426,7 +426,7 @@ static void fts5SnippetFunction(
int rc = SQLITE_OK; /* Return code */
int iCol; /* 1st argument to snippet() */
const char *zEllips; /* 4th argument to snippet() */
int nToken; /* 5th argument to snippet() */
i64 nToken; /* 5th argument to snippet() */
int nInst = 0; /* Number of instance matches this row */
int i; /* Used to iterate through instances */
int nPhrase; /* Number of phrases in query */
@@ -451,7 +451,7 @@ static void fts5SnippetFunction(
ctx.zClose = fts5ValueToText(apVal[2]);
ctx.iRangeEnd = -1;
zEllips = fts5ValueToText(apVal[3]);
nToken = sqlite3_value_int(apVal[4]);
nToken = (int)(MIN( MAX(sqlite3_value_int64(apVal[4]), 0), 64));
iBestCol = (iCol>=0 ? iCol : 0);
nPhrase = pApi->xPhraseCount(pFts);
+2 -2
View File
@@ -807,7 +807,7 @@ static int fts5ExprNearIsMatch(int *pRc, Fts5ExprNearset *pNear){
i64 iPos = a[i].reader.iPos;
Fts5PoslistWriter *pWriter = &a[i].writer;
if( a[i].pOut->n==0 || iPos!=pWriter->iPrev ){
sqlite3Fts5PoslistWriterAppend(a[i].pOut, pWriter, iPos);
sqlite3Fts5PoslistSafeAppend(a[i].pOut, &pWriter->iPrev, iPos);
}
}
@@ -1758,10 +1758,10 @@ static int fts5ParseTokenize(
memset(pSyn, 0, (size_t)nByte);
pSyn->pTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer);
pSyn->nFullTerm = pSyn->nQueryTerm = nToken;
memcpy(pSyn->pTerm, pToken, nToken);
if( pCtx->pConfig->bTokendata ){
pSyn->nQueryTerm = (int)strlen(pSyn->pTerm);
}
memcpy(pSyn->pTerm, pToken, nToken);
pSyn->pSynonym = pPhrase->aTerm[pPhrase->nTerm-1].pSynonym;
pPhrase->aTerm[pPhrase->nTerm-1].pSynonym = pSyn;
}
+1 -1
View File
@@ -323,7 +323,7 @@ int sqlite3Fts5HashWrite(
** + 5 bytes for the new position offset (32-bit max).
*/
if( (p->nAlloc - p->nData) < (9 + 4 + 1 + 3 + 5) ){
sqlite3_int64 nNew = p->nAlloc * 2;
sqlite3_int64 nNew = (i64)p->nAlloc * 2;
Fts5HashEntry *pNew;
Fts5HashEntry **pp;
pNew = (Fts5HashEntry*)sqlite3_realloc64(p, nNew);
+34 -19
View File
@@ -922,7 +922,7 @@ static void fts5DataRelease(Fts5Data *pData){
static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){
Fts5Data *pRet = fts5DataRead(p, iRowid);
if( pRet ){
if( pRet->nn<4 || pRet->szLeaf>pRet->nn ){
if( pRet->szLeaf<4 || pRet->szLeaf>pRet->nn ){
FTS5_CORRUPT_ROWID(p, iRowid);
fts5DataRelease(pRet);
pRet = 0;
@@ -1166,7 +1166,7 @@ static int fts5StructureDecode(
i += fts5GetVarint32(&pData[i], nTotal);
if( nTotal<pLvl->nMerge ) rc = FTS5_CORRUPT;
pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&rc,
nTotal * sizeof(Fts5StructureSegment)
(i64)nTotal * sizeof(Fts5StructureSegment)
);
nSegment -= nTotal;
}
@@ -1695,6 +1695,7 @@ static int fts5DlidxLvlPrev(Fts5DlidxLvl *pLvl){
pLvl->bEof = 1;
}else{
u8 *a = pLvl->pData->p;
int nn = pLvl->pData->nn;
pLvl->iOff = 0;
fts5DlidxLvlNext(pLvl);
@@ -1703,7 +1704,7 @@ static int fts5DlidxLvlPrev(Fts5DlidxLvl *pLvl){
int ii = pLvl->iOff;
u64 delta = 0;
while( a[ii]==0 ){
while( ii<nn && a[ii]==0 ){
nZero++;
ii++;
}
@@ -2122,7 +2123,7 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){
while( p->rc==SQLITE_OK && pIter->iLeafPgno>pIter->iTermLeafPgno ){
Fts5Data *pNew;
pIter->iLeafPgno--;
pNew = fts5DataRead(p, FTS5_SEGMENT_ROWID(
pNew = fts5LeafRead(p, FTS5_SEGMENT_ROWID(
pIter->pSeg->iSegid, pIter->iLeafPgno
));
if( pNew ){
@@ -2576,6 +2577,10 @@ static void fts5LeafSeek(
if( nKeep<nMatch ){
goto search_failed;
}
if( (iOff+nNew)>n ){
FTS5_CORRUPT_ITER(p, pIter);
return;
}
assert( nKeep>=nMatch );
if( nKeep==nMatch ){
@@ -3552,8 +3557,7 @@ static void fts5PoslistFilterCallback(
do {
while( i<nChunk && pChunk[i]!=0x01 ){
while( pChunk[i] & 0x80 ) i++;
i++;
fts5IndexSkipVarint(pChunk, i);
}
if( pCtx->eState ){
fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart);
@@ -3702,7 +3706,7 @@ static void fts5IndexExtractColset(
/* Advance pointer p until it points to pEnd or an 0x01 byte that is
** not part of a varint */
while( p<pEnd && *p!=0x01 ){
while( *p++ & 0x80 );
while( p<pEnd && (*p++ & 0x80) );
}
if( pColset->aiCol[i]==iCurrent ){
@@ -3799,8 +3803,11 @@ static void fts5IterSetOutputs_Col100(Fts5Iter *pIter, Fts5SegIter *pSeg){
assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_COLUMNS );
assert( pIter->pColset );
assert( pIter->poslist.nSpace>=pIter->pIndex->pConfig->nCol );
if( pSeg->iLeafOffset+pSeg->nPos>pSeg->pLeaf->szLeaf ){
if( pSeg->iLeafOffset+pSeg->nPos>pSeg->pLeaf->szLeaf
|| pSeg->nPos>pIter->pIndex->pConfig->nCol
){
fts5IterSetOutputs_Col(pIter, pSeg);
}else{
u8 *a = (u8*)&pSeg->pLeaf->p[pSeg->iLeafOffset];
@@ -5294,6 +5301,11 @@ static void fts5DoSecureDelete(
}else{
iStart = fts5GetU16(&aPg[0]);
}
if( iStart>nPg ){
FTS5_CORRUPT_IDX(p);
sqlite3_free(aIdx);
return;
}
iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta);
assert_nc( iSOP<=pSeg->iLeafOffset );
@@ -7979,8 +7991,8 @@ static void fts5IndexTombstoneRebuild(
){
const int MINSLOT = 32;
int nSlotPerPage = MAX(MINSLOT, (p->pConfig->pgsz - 8) / szKey);
int nSlot = 0; /* Number of slots in each output page */
int nOut = 0;
i64 nSlot = 0; /* Number of slots in each output page */
i64 nOut = 0;
/* Figure out how many output pages (nOut) and how many slots per
** page (nSlot). There are three possibilities:
@@ -8005,23 +8017,26 @@ static void fts5IndexTombstoneRebuild(
nSlot = MINSLOT;
}else if( pSeg->nPgTombstone==1 ){
/* Case 2. */
int nElem = (int)fts5GetU32(&pData1->p[4]);
u32 nElem = fts5GetU32(&pData1->p[4]);
assert( pData1 && iPg1==0 );
nOut = 1;
nSlot = MAX(nElem*4, MINSLOT);
if( nSlot>nSlotPerPage ) nOut = 0;
if( nElem>((u32)nSlotPerPage/4) ){
nOut = 0;
}else{
nOut = 1;
nSlot = MAX((i64)nElem*4, MINSLOT);
}
}
if( nOut==0 ){
/* Case 3. */
nOut = (pSeg->nPgTombstone * 2 + 1);
nOut = ((i64)pSeg->nPgTombstone * 2 + 1);
nSlot = nSlotPerPage;
}
/* Allocate the required array and output pages */
while( 1 ){
int res = 0;
int ii = 0;
int szPage = 0;
i64 ii = 0;
i64 szPage = 0;
Fts5Data **apOut = 0;
/* Allocate space for the new hash table */
@@ -8559,7 +8574,7 @@ static void fts5IndexIntegrityCheckSegment(
/* Check any rowid-less pages that occur before the current leaf. */
for(iPg=iPrevLeaf+1; iPg<fts5DlidxIterPgno(pDlidx); iPg++){
iKey = FTS5_SEGMENT_ROWID(iSegid, iPg);
pLeaf = fts5DataRead(p, iKey);
pLeaf = fts5LeafRead(p, iKey);
if( pLeaf ){
if( fts5LeafFirstRowidOff(pLeaf)!=0 ) FTS5_CORRUPT_ROWID(p, iKey);
fts5DataRelease(pLeaf);
@@ -8570,7 +8585,7 @@ static void fts5IndexIntegrityCheckSegment(
/* Check that the leaf page indicated by the iterator really does
** contain the rowid suggested by the same. */
iKey = FTS5_SEGMENT_ROWID(iSegid, iPrevLeaf);
pLeaf = fts5DataRead(p, iKey);
pLeaf = fts5LeafRead(p, iKey);
if( pLeaf ){
i64 iRowid;
int iRowidOff = fts5LeafFirstRowidOff(pLeaf);
+23 -26
View File
@@ -365,34 +365,31 @@ int sqlite3Fts5StorageOpen(
if( pConfig->eContent==FTS5_CONTENT_NORMAL
|| pConfig->eContent==FTS5_CONTENT_UNINDEXED
){
int nDefn = 32 + pConfig->nCol*10;
char *zDefn = sqlite3_malloc64(32 + (sqlite3_int64)pConfig->nCol * 20);
if( zDefn==0 ){
rc = SQLITE_NOMEM;
}else{
int i;
int iOff;
sqlite3_snprintf(nDefn, zDefn, "id INTEGER PRIMARY KEY");
iOff = (int)strlen(zDefn);
for(i=0; i<pConfig->nCol; i++){
if( pConfig->eContent==FTS5_CONTENT_NORMAL
|| pConfig->abUnindexed[i]
){
sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i);
iOff += (int)strlen(&zDefn[iOff]);
}
int i = 0;
char *zDefn = 0;
sqlite3_str *pDefn = sqlite3_str_new(pConfig->db);
sqlite3_str_appendf(pDefn, "id INTEGER PRIMARY KEY");
for(i=0; i<pConfig->nCol; i++){
if( pConfig->eContent==FTS5_CONTENT_NORMAL || pConfig->abUnindexed[i] ){
sqlite3_str_appendf(pDefn, ", c%d", i);
}
if( pConfig->bLocale ){
for(i=0; i<pConfig->nCol; i++){
if( pConfig->abUnindexed[i]==0 ){
sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", l%d", i);
iOff += (int)strlen(&zDefn[iOff]);
}
}
}
rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr);
}
sqlite3_free(zDefn);
if( pConfig->bLocale ){
for(i=0; i<pConfig->nCol; i++){
if( pConfig->abUnindexed[i]==0 ){
sqlite3_str_appendf(pDefn, ", l%d", i);
}
}
}
zDefn = sqlite3_str_finish(pDefn);
if( zDefn ){
rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr);
sqlite3_free(zDefn);
}else{
rc = SQLITE_NOMEM;
}
}
if( rc==SQLITE_OK && pConfig->bColumnsize ){
+10 -4
View File
@@ -586,8 +586,14 @@ static int fts5PorterCreate(
const char *zBase = "unicode61";
fts5_tokenizer_v2 *pV2 = 0;
if( nArg>0 ){
zBase = azArg[0];
while( nArg>0 ){
if( sqlite3_stricmp(azArg[0],"porter")==0 ){
nArg--;
azArg++;
}else{
zBase = azArg[0];
break;
}
}
pRet = (PorterTokenizer*)sqlite3_malloc64(sizeof(PorterTokenizer));
@@ -712,9 +718,9 @@ static int fts5Porter_Ostar(char *zStem, int nStem){
for(i=0; i<nStem; i++){
bCons = !fts5PorterIsVowel(zStem[i], bCons);
assert( bCons==0 || bCons==1 );
mask = (mask << 1) + bCons;
mask = ((mask << 1) + bCons) & 0x0007;
}
return ((mask & 0x0007)==0x0005);
return (mask==0x0005);
}
}
+23
View File
@@ -191,6 +191,29 @@ do_execsql_test 5.6 {
SELECT snippet(p1, 0, '[', NULL, '...', 6) FROM p1('x OR ' || x'DB');
} {{[x a a a a a...}}
do_execsql_test 5.7.1 {
SELECT snippet(p1, 0, '[', NULL, '...', -2147483647) FROM p1('x OR ' || x'DB')
} {{[x...}}
do_execsql_test 5.7.2 {
SELECT snippet(p1, 0, '[', NULL, '...', -2147483648) FROM p1('x OR ' || x'DB')
} {{[x...}}
do_execsql_test 5.7.3 {
SELECT snippet(p1, 0, '[', NULL, '...', -2147483649) FROM p1('x OR ' || x'DB')
} {{[x...}}
do_execsql_test 5.7.4 {
SELECT snippet(p1, 0, '[', NULL, '...', 0) FROM p1('x OR ' || x'DB')
} {{[x...}}
do_execsql_test 5.8.1 {
SELECT snippet(p1, 0, '[', NULL, '...', 2147483647) FROM p1('x OR ' || x'DB')
} {{[x a a a a a a a a a a}}
do_execsql_test 5.8.2 {
SELECT snippet(p1, 0, '[', NULL, '...', 2147483648) FROM p1('x OR ' || x'DB')
} {{[x a a a a a a a a a a}}
do_execsql_test 5.8.3 {
SELECT snippet(p1, 0, '[', NULL, '...', 2147483649) FROM p1('x OR ' || x'DB')
} {{[x a a a a a a a a a a}}
} ;# foreach_detail_mode
reset_db
+28
View File
@@ -0,0 +1,28 @@
# 2026 June 19
#
# 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 the FTS5 module.
#
source [file join [file dirname [info script]] fts5_common.tcl]
set testprefix fts5bigtok2
return_if_no_fts5
set big [string repeat a 1080000000]
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE t USING fts5(x);
}
do_catchsql_test 1.1 { INSERT INTO t VALUES($big); } {1 {out of memory}}
set big {}
finish_test
+2 -2
View File
@@ -15153,9 +15153,9 @@ do_execsql_test 78.1 {
CREATE VIRTUAL TABLE t3 USING fts5vocab('t1','col');
}
do_execsql_test 78.2 {
do_catchsql_test 78.2 {
SELECT count(rowid) FROM t3 WHERE term>='nsocse';
} 2
} {1 {database disk image is malformed}}
#-------------------------------------------------------------------------
reset_db
+294 -1
View File
@@ -1220,7 +1220,7 @@ do_test 8.0 {
do_catchsql_test 8.1 {
SELECT rowid FROM t1('a* NOT ý‘') ;
} {0 {1 2 3 4 5 6 7}}
} {1 {fts5: corruption on page 1, segment 1, table "t1"}}
#-------------------------------------------------------------------------
reset_db
@@ -1917,6 +1917,299 @@ do_catchsql_test 11.3 {
REPLACE INTO t1(rowid,b,a,rowid) VALUES(x'44023b9eb002d28b0ee90c',1,2,3);
} {1 {database disk image is malformed}}
#-------------------------------------------------------------------------
reset_db
do_test 12.0 {
sqlite3 db {}
db deserialize [decode_hexdb {
.open --hexdb
| size 24576 pagesize 4096 filename p.db
| page 1 offset 0
| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
| 16: 10 00 01 01 00 40 20 20 00 00 00 03 00 00 00 06 .....@ ........
| 32: 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 04 ................
| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 ................
| 96: 00 2e 76 89 0d 00 00 00 06 0d ed 00 0f bb 0f 64 ..v............d
| 112: 0e f9 0e a2 0e 45 0d ed 00 00 00 00 00 00 00 00 .....E..........
| 3552: 00 00 00 00 00 00 00 00 00 00 00 00 00 56 06 06 .............V..
| 3568: 17 1f 1f 01 7d 74 61 62 6c 65 66 74 5f 63 6f 6e .....tableft_con
| 3584: 66 69 67 66 74 5f 63 6f 6e 66 69 67 06 43 52 45 figft_config.CRE
| 3600: 41 54 45 20 54 41 42 4c 45 20 27 66 74 5f 63 6f ATE TABLE 'ft_co
| 3616: 6e 66 69 67 27 28 6b 20 50 52 49 4d 41 52 59 20 nfig'(k PRIMARY
| 3632: 4b 45 59 2c 20 76 29 20 57 49 54 48 4f 55 54 20 KEY, v) WITHOUT
| 3648: 52 4f 57 49 44 5b 05 07 17 21 21 01 81 01 74 61 ROWID[...!!...ta
| 3664: 62 6c 65 66 74 5f 64 6f 63 73 69 7a 65 66 74 5f bleft_docsizeft_
| 3680: 64 6f 63 73 69 7a 65 05 43 52 45 41 54 45 20 54 docsize.CREATE T
| 3696: 41 42 4c 45 20 27 66 74 5f 64 6f 63 73 69 7a 65 ABLE 'ft_docsize
| 3712: 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 '(id INTEGER PRI
| 3728: 4d 41 52 59 20 4b 45 59 2c 20 73 7a 20 42 4c 4f MARY KEY, sz BLO
| 3744: 42 29 55 04 06 17 21 21 01 77 74 61 62 6c 65 66 B)U...!!.wtablef
| 3760: 74 5f 63 6f 6e 74 65 6e 74 66 74 5f 63 6f 6e 74 t_contentft_cont
| 3776: 65 6e 74 04 43 52 45 41 54 45 20 54 41 42 4c 45 ent.CREATE TABLE
| 3792: 20 27 66 74 5f 63 6f 6e 74 65 6e 74 27 28 69 64 'ft_content'(id
| 3808: 20 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 INTEGER PRIMARY
| 3824: 20 4b 45 59 2c 20 63 30 29 69 03 07 17 19 19 01 KEY, c0)i......
| 3840: 81 2d 74 61 62 6c 65 66 74 5f 69 64 78 66 74 5f .-tableft_idxft_
| 3856: 69 64 78 03 43 52 45 41 54 45 20 54 41 42 4c 45 idx.CREATE TABLE
| 3872: 20 27 66 74 5f 69 64 78 27 28 73 65 67 69 64 2c 'ft_idx'(segid,
| 3888: 20 74 65 72 6d 2c 20 70 67 6e 6f 2c 20 50 52 49 term, pgno, PRI
| 3904: 4d 41 52 59 20 4b 45 59 28 73 65 67 69 64 2c 20 MARY KEY(segid,
| 3920: 74 65 72 6d 29 29 20 57 49 54 48 4f 55 54 20 52 term)) WITHOUT R
| 3936: 4f 57 49 44 55 02 07 17 1b 1b 01 81 01 74 61 62 OWIDU........tab
| 3952: 6c 65 66 74 5f 64 61 74 61 66 74 5f 64 61 74 61 left_dataft_data
| 3968: 02 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 66 .CREATE TABLE 'f
| 3984: 74 5f 64 61 74 61 27 28 69 64 20 49 4e 54 45 47 t_data'(id INTEG
| 4000: 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 ER PRIMARY KEY,
| 4016: 62 6c 6f 63 6b 20 42 4c 4f 42 29 43 01 06 17 11 block BLOB)C....
| 4032: 11 08 75 74 61 62 6c 65 66 74 66 74 43 52 45 41 ..utableftftCREA
| 4048: 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 4c 45 TE VIRTUAL TABLE
| 4064: 20 66 74 20 55 53 49 4e 47 20 66 74 73 35 28 61 ft USING fts5(a
| 4080: 2c 20 64 65 74 61 69 6c 3d 63 6f 6c 75 6d 6e 29 , detail=column)
| page 2 offset 4096
| 0: 0d 00 00 00 03 0f 7f 00 0f e8 0f ef 0f 7f 00 00 ................
| 3952: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 62 ...............b
| 3968: 84 80 80 80 80 01 04 00 81 48 00 00 00 54 04 30 .........H...T.0
| 3984: 61 61 61 01 81 02 02 02 02 02 02 02 02 02 02 02 aaa.............
| 4000: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 4016: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 4032: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 4048: 02 02 02 02 02 02 02 03 6a 6a 6a 01 02 02 04 08 ........jjj.....
| 4064: 08 08 08 08 08 08 08 08 05 01 03 00 10 01 0a 0f ................
| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 01 ...$............
| page 3 offset 8192
| 0: 0a 00 00 00 01 0f fa 00 0f fa 00 00 00 00 00 00 ................
| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................
| page 4 offset 12288
| 0: 0d 00 00 00 01 0f d4 00 0f d4 00 00 00 00 00 00 ................
| 4048: 00 00 00 00 2a 01 03 00 5b 61 61 61 20 62 62 62 ....*...[aaa bbb
| 4064: 20 63 63 63 20 64 64 64 20 65 65 65 20 66 66 66 ccc ddd eee fff
| 4080: 20 67 67 67 20 68 68 68 20 69 69 69 20 6a 6a 6a ggg hhh iii jjj
| page 5 offset 16384
| 0: 0d 00 00 00 01 0f fa 00 0f fa 00 00 00 00 00 00 ................
| 4080: 00 00 00 00 00 00 00 00 00 00 04 01 03 00 0e 0a ................
| page 6 offset 20480
| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................
| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version.
| end p.db
}]} {}
do_catchsql_test 12.1 {
SELECT rowid FROM ft('a:aaa')
} {0 1}
#-------------------------------------------------------------------------
reset_db
do_test 13.0 {
sqlite3 db {}
db deserialize [decode_hexdb {
.open --hexdb
| size 24576 pagesize 4096 filename vuln_001.db
| page 1 offset 0
| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
| 16: 10 00 01 01 00 40 20 20 00 00 00 03 00 00 00 06 .....@ ........
| 32: 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 04 ................
| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 ................
| 96: 00 2e 76 89 0d 00 00 00 06 0e 09 00 0f ca 0f 77 ..v............w
| 112: 0f 0f 0e b7 0e 5e 0e 09 00 00 00 00 00 00 00 00 .....^..........
| 3584: 00 00 00 00 00 00 00 00 00 53 06 06 17 1d 1d 01 .........S......
| 3600: 7b 74 61 62 6c 65 74 5f 63 6f 6e 66 69 67 74 5f .tablet_configt_
| 3616: 63 6f 6e 66 69 67 06 43 52 45 41 54 45 20 54 41 config.CREATE TA
| 3632: 42 4c 45 20 27 74 5f 63 6f 6e 66 69 67 27 28 6b BLE 't_config'(k
| 3648: 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 PRIMARY KEY, v)
| 3664: 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 57 05 WITHOUT ROWIDW.
| 3680: 06 17 1f 1f 01 7f 74 61 62 6c 65 74 5f 64 6f 63 ......tablet_doc
| 3696: 73 69 7a 65 74 5f 64 6f 63 73 69 7a 65 05 43 52 sizet_docsize.CR
| 3712: 45 41 54 45 20 54 41 42 4c 45 20 27 74 5f 64 6f EATE TABLE 't_do
| 3728: 63 73 69 7a 65 27 28 69 64 20 49 4e 54 45 47 45 csize'(id INTEGE
| 3744: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 73 R PRIMARY KEY, s
| 3760: 7a 20 42 4c 4f 42 29 56 04 06 17 1f 1f 01 7d 74 z BLOB)V.......t
| 3776: 61 62 6c 65 74 5f 63 6f 6e 74 65 6e 74 74 5f 63 ablet_contentt_c
| 3792: 6f 6e 74 65 6e 74 04 43 52 45 41 54 45 20 54 41 ontent.CREATE TA
| 3808: 42 4c 45 20 27 74 5f 63 6f 6e 74 65 6e 74 27 28 BLE 't_content'(
| 3824: 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d 41 id INTEGER PRIMA
| 3840: 52 59 20 4b 45 59 2c 20 63 30 2c 20 63 31 29 66 RY KEY, c0, c1)f
| 3856: 03 07 17 17 17 01 81 2b 74 61 62 6c 65 74 5f 69 .......+tablet_i
| 3872: 64 78 74 5f 69 64 78 03 43 52 45 41 54 45 20 54 dxt_idx.CREATE T
| 3888: 41 42 4c 45 20 27 74 5f 69 64 78 27 28 73 65 67 ABLE 't_idx'(seg
| 3904: 69 64 2c 20 74 65 72 6d 2c 20 70 67 6e 6f 2c 20 id, term, pgno,
| 3920: 50 52 49 4d 41 52 59 20 4b 45 59 28 73 65 67 69 PRIMARY KEY(segi
| 3936: 64 2c 20 74 65 72 6d 29 29 20 57 49 54 48 4f 55 d, term)) WITHOU
| 3952: 54 20 52 4f 57 49 44 51 02 06 17 19 19 01 7f 74 T ROWIDQ.......t
| 3968: 61 62 6c 65 74 5f 64 61 74 61 74 5f 64 61 74 61 ablet_datat_data
| 3984: 02 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 .CREATE TABLE 't
| 4000: 5f 64 61 74 61 27 28 69 64 20 49 4e 54 45 47 45 _data'(id INTEGE
| 4016: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 R PRIMARY KEY, b
| 4032: 6c 6f 63 6b 20 42 4c 4f 42 29 34 01 06 17 0f 0f lock BLOB)4.....
| 4048: 08 5b 74 61 62 6c 65 74 74 43 52 45 41 54 45 20 .[tablettCREATE
| 4064: 56 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 20 VIRTUAL TABLE t
| 4080: 55 53 49 4e 47 20 66 74 73 35 28 61 2c 20 62 29 USING fts5(a, b)
| page 2 offset 4096
| 0: 0d 00 00 00 04 0b da 00 0f e7 0f ef 0f ce 0b da ................
| 3024: 00 00 00 00 00 00 00 00 00 00 87 6c 84 80 80 80 ...........l....
| 3040: 80 02 04 00 8f 5c 00 00 03 e8 80 80 80 80 80 80 ................
| 3056: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3072: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3088: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3104: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3120: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3136: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3152: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3168: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3184: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3200: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3216: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3232: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3248: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3264: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3280: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3296: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3312: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3328: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3344: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3360: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3376: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3392: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3408: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3424: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3440: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3456: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3472: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3488: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3504: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3520: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3536: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3552: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3568: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3584: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3600: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3616: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3632: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3648: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3664: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3680: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3696: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3712: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3728: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3744: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3760: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3776: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3792: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3808: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3824: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3840: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3856: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3872: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3888: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3904: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3920: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3936: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3952: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3968: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 3984: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 4000: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 4016: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 4032: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 12 84 ................
| 4048: 80 80 80 80 01 03 00 2a 00 0b 00 0e 06 30 68 65 .......*.....0he
| 4064: 6c 6c 6f 01 04 04 04 06 01 03 00 12 01 02 02 0f llo.............
| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 02 ...$............
| page 3 offset 8192
| 0: 0a 00 00 00 01 0f fa 00 0f fa 00 00 00 00 00 00 ................
| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................
| page 4 offset 12288
| 0: 0d 00 00 00 01 0f e8 00 0f e8 00 00 00 00 00 00 ................
| 4064: 00 00 00 00 00 00 00 00 16 01 04 00 23 1b 68 65 ............#.he
| 4080: 6c 6c 6f 20 77 6f 72 6c 64 66 6f 6f 20 62 61 72 llo worldfoo bar
| page 5 offset 16384
| 0: 0d 00 00 00 01 0f f9 00 0f f9 00 00 00 00 00 00 ................
| 4080: 00 00 00 00 00 00 00 00 00 05 01 03 00 10 02 02 ................
| page 6 offset 20480
| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................
| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version.
| end vuln_001.db
}]} {}
do_catchsql_test 13.1 {
SELECT * FROM t('a:hello')
} {0 {{hello world} {foo bar}}}
#-------------------------------------------------------------------------
reset_db
#-------------------------------------------------------------------------
reset_db
do_test 14.0 {
sqlite3 db {}
db deserialize [decode_hexdb {
.open --hexdb
| size 24576 pagesize 4096 filename vuln_001.db
| page 1 offset 0
| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
| 16: 10 00 01 01 00 40 20 20 00 00 00 03 00 00 00 06 .....@ ........
| 32: 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 04 ................
| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 ................
| 96: 00 2e 76 89 0d 00 00 00 06 0e 0a 00 0f c7 0f 74 ..v............t
| 112: 0f 0c 0e b8 0e 5f 0e 0a 00 00 00 00 00 00 00 00 ....._..........
| 3584: 00 00 00 00 00 00 00 00 00 00 53 06 06 17 1d 1d ..........S.....
| 3600: 01 7b 74 61 62 6c 65 74 5f 63 6f 6e 66 69 67 74 ..tablet_configt
| 3616: 5f 63 6f 6e 66 69 67 06 43 52 45 41 54 45 20 54 _config.CREATE T
| 3632: 41 42 4c 45 20 27 74 5f 63 6f 6e 66 69 67 27 28 ABLE 't_config'(
| 3648: 6b 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 k PRIMARY KEY, v
| 3664: 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 57 ) WITHOUT ROWIDW
| 3680: 05 06 17 1f 1f 01 7f 74 61 62 6c 65 74 5f 64 6f .......tablet_do
| 3696: 63 73 69 7a 65 74 5f 64 6f 63 73 69 7a 65 05 43 csizet_docsize.C
| 3712: 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 5f 64 REATE TABLE 't_d
| 3728: 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 54 45 47 ocsize'(id INTEG
| 3744: 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 ER PRIMARY KEY,
| 3760: 73 7a 20 42 4c 4f 42 29 52 04 06 17 1f 1f 01 75 sz BLOB)R......u
| 3776: 74 61 62 6c 65 74 5f 63 6f 6e 74 65 6e 74 74 5f tablet_contentt_
| 3792: 63 6f 6e 74 65 6e 74 04 43 52 45 41 54 45 20 54 content.CREATE T
| 3808: 41 42 4c 45 20 27 74 5f 63 6f 6e 74 65 6e 74 27 ABLE 't_content'
| 3824: 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d (id INTEGER PRIM
| 3840: 41 52 59 20 4b 45 59 2c 20 63 30 29 66 03 07 17 ARY KEY, c0)f...
| 3856: 17 17 01 81 2b 74 61 62 6c 65 74 5f 69 64 78 74 ....+tablet_idxt
| 3872: 5f 69 64 78 03 43 52 45 41 54 45 20 54 41 42 4c _idx.CREATE TABL
| 3888: 45 20 27 74 5f 69 64 78 27 28 73 65 67 69 64 2c E 't_idx'(segid,
| 3904: 20 74 65 72 6d 2c 20 70 67 6e 6f 2c 20 50 52 49 term, pgno, PRI
| 3920: 4d 41 52 59 20 4b 45 59 28 73 65 67 69 64 2c 20 MARY KEY(segid,
| 3936: 74 65 72 6d 29 29 20 57 49 54 48 4f 55 54 20 52 term)) WITHOUT R
| 3952: 4f 57 49 44 51 02 06 17 19 19 01 7f 74 61 62 6c OWIDQ.......tabl
| 3968: 65 74 5f 64 61 74 61 74 5f 64 61 74 61 02 43 52 et_datat_data.CR
| 3984: 45 41 54 45 20 54 41 42 4c 45 20 27 74 5f 64 61 EATE TABLE 't_da
| 4000: 74 61 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 ta'(id INTEGER P
| 4016: 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 6c 6f 63 RIMARY KEY, bloc
| 4032: 6b 20 42 4c 4f 42 29 37 01 06 17 0f 0f 08 61 74 k BLOB)7......at
| 4048: 61 62 6c 65 74 74 43 52 45 41 54 45 20 56 49 52 ablettCREATE VIR
| 4064: 54 55 41 4c 20 54 41 42 4c 45 20 74 20 55 53 49 TUAL TABLE t USI
| 4080: 4e 47 20 66 74 73 35 28 63 6f 6e 74 65 6e 74 29 NG fts5(content)
| page 2 offset 4096
| 0: 0d 0f ef 00 03 0f ac 00 0f e8 0f ac 0f c4 00 00 ................
| 4000: 00 00 00 00 00 00 00 00 00 00 00 00 16 0a 03 00 ................
| 4016: 32 01 00 00 00 01 01 01 00 d5 aa d5 2b 01 01 01 2...........+...
| 4032: 83 74 01 01 1d 84 80 80 80 80 01 03 00 40 00 00 .t...........@..
| 4048: 00 18 06 30 68 65 6c 6c 6f 01 02 02 01 05 77 6f ...0hello.....wo
| 4064: 72 6c 64 01 02 03 04 0a 05 01 03 00 10 01 02 00 rld.............
| 4080: 00 00 11 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
| page 3 offset 8192
| 0: 0a 00 00 00 01 0f fa 00 0f fa 00 00 00 00 00 00 ................
| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................
| page 4 offset 12288
| 0: 0d 00 00 00 01 0f f0 00 0f f0 00 00 00 00 00 00 ................
| 4080: 0e 01 03 00 23 68 65 6c 6c 6f 20 77 6f 72 6c 64 ....#hello world
| page 5 offset 16384
| 0: 0d 00 00 00 01 0f fa 00 0f fa 00 00 00 00 00 00 ................
| 4080: 00 00 00 00 00 00 00 00 00 00 04 01 03 00 0e 02 ................
| page 6 offset 20480
| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................
| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version.
| end vuln_001.db
}]} {}
do_catchsql_test 14.1 {
SELECT * FROM t('hello');
} {1 {out of memory}}
sqlite3_fts5_may_be_corrupt 0
finish_test
+36
View File
@@ -125,4 +125,40 @@ do_catchsql_test 2.3 {
DELETE FROM t1 WHERE rowid = 1
} {/.*fts5: corrupt.*/}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
PRAGMA page_size=4096;
PRAGMA journal_mode=DELETE;
CREATE VIRTUAL TABLE t USING fts5(x, detail=none);
WITH s(i) AS (
VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<5000
)
INSERT INTO t(rowid, x) SELECT i, 'vulnerabilitytest' FROM s;
INSERT INTO t(t) VALUES('optimize');
INSERT INTO t(t, rank) VALUES('secure-delete', 1);
} {delete}
do_test 3.1 {
db eval { SELECT rowid AS rowid, block FROM t_data ORDER BY rowid } {
if {$rowid>=10 && [string length $block]>=4} {
binary scan $block Su first_rowid_off
set pgno [expr ($rowid & 0x7FFFFFFF)]
if {$pgno>=2 && $first_rowid_off>0} break
}
}
set bad [binary format a*a* "\xFF\xFF" [string range $block 2 end]]
db eval {
UPDATE t_data SET block = $bad WHERE rowid=$rowid
}
} {}
do_catchsql_test 3.2 {
DELETE FROM t WHERE rowid=4500;
} {1 {fts5: corruption in table "t"}}
finish_test
+273
View File
@@ -0,0 +1,273 @@
# 2026 May 11
#
# 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.
#
#***********************************************************************
#
source [file join [file dirname [info script]] fts5_common.tcl]
set testprefix fts5corruptA
# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
ifcapable !fts5 {
finish_test
return
}
sqlite3_fts5_may_be_corrupt 1
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE t USING fts5(x, detail='full');
INSERT INTO t(t, rank) VALUES('pgsz', 32);
}
set big [string repeat "a " 200]
do_execsql_test 1.1 {
INSERT INTO t(rowid, x) VALUES(1, $big)
}
do_test 1.2 {
db eval {
SELECT min(rowid) AS base_rowid, count(*) AS page_count FROM t_data
WHERE rowid>1000
} {}
} {}
do_test 1.3 {
for {set ii 0} {$ii < 5} {incr ii} {
db eval {
INSERT INTO t_data(rowid, block)
VALUES( $base_rowid + $page_count + $ii, zeroblob(4) );
}
}
db eval {
INSERT INTO t_data(rowid, block)
VALUES( $base_rowid + $page_count + 5,
unhex('00000080' || 'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC') );
}
set {} {}
} {}
db close
do_test 1.4 {
set hex [hexio_read test.db 0 [file size test.db]]
set off [string first "023061018310" $hex]
set hex [string replace $hex $off [expr $off+11] 023061018370]
hexio_write test.db 0 $hex
} {6144}
sqlite3 db test.db
do_catchsql_test 1.5 {
SELECT rowid FROM t WHERE t MATCH 'a'
} {1 {fts5: corruption found reading blob 137438953481 from table "t"}}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
CREATE VIRTUAL TABLE t1 USING fts5(a);
BEGIN;
INSERT INTO t1(rowid, a) VALUES(1, 'aaaa');
INSERT INTO t1(rowid, a) VALUES(2, 'bbbb');
INSERT INTO t1(rowid, a) VALUES(3, 'cccc');
COMMIT;
}
db close
do_test 2.1 {
set hex [hexio_read test.db 0 [file size test.db]]
set off [string first "010463636363" $hex]
set hex [string replace $hex $off [expr $off+11] 017F63636363]
hexio_write test.db 0 $hex
set {} {}
} {}
sqlite3 db test.db
do_catchsql_test 2.2 {
SELECT rowid FROM t1('cccccccccccccccccccccccccccccccccccccccccccccccc');
} {1 {fts5: corruption on page 1, segment 1, table "t1"}}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
CREATE VIRTUAL TABLE t1 USING fts5(a, b, c);
INSERT INTO t1 VALUES(
'hello',
'alpha bravo charlie delta echo foxtrot golf hotel india juliet kilo lima mike november oscar papa quebec romeo sierra tango uniform victor whiskey xray yankee zulu',
'ant bear cat dog elephant fox gorilla hippo iguana jaguar kangaroo lion monkey newt octopus panda quail rabbit snake turtle unicorn vulture walrus xerus yak zebra'
);
UPDATE t1_data SET block = unhex(
-- Preserve bytes 0-168: header through hello's nSz field
hex(substr(block, 1, 169))
-- Inject 448 bytes of 0xFF: corrupted poslist + subsequent term data
|| replace(hex(zeroblob(448)), '00', 'FF')
-- Preserve 16 bytes of page index needed for hello lookup
|| hex(substr(block, 618, 16))
-- Corrupt remaining 37 footer bytes (not needed for hello lookup)
|| replace(hex(zeroblob(37)), '00', 'FF')
)
WHERE id = 137438953473;
}
do_test 3.1 {
execsql { SELECT * FROM t1 WHERE t1 MATCH '{a b}: hello' }
set {} {}
} {}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 4.0 {
CREATE VIRTUAL TABLE t1 USING fts5(content);
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
BEGIN;
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<5000
)
INSERT INTO t1(content) SELECT 'xyzterm' FROM s;
COMMIT;
INSERT INTO t1(t1) VALUES('optimize');
}
do_test 4.1 {
db eval {
SELECT rowid AS a, hex(block) AS block FROM t1_data
WHERE ((rowid>>36) & 0x01)==1
AND ((rowid>>31) & 0x1F)==0
} {
set n [string length $block]
set block [string range $block 0 11]
append block [string repeat "00" [expr ($n/2)-7-2]]
append block "8080"
db eval {
UPDATE t1_data SET block = unhex($block) WHERE rowid = $a
}
}
} {}
do_catchsql_test 4.2 {
SELECT count(*) FROM (
SELECT rowid FROM t1 WHERE t1 MATCH 'xyzterm' ORDER BY rowid ASC
)
} {0 5000}
do_catchsql_test 4.3 {
SELECT count(*) FROM (
SELECT rowid FROM t1 WHERE t1 MATCH 'xyzterm' ORDER BY rowid DESC
)
} {0 4987}
do_test 4.4 {
db eval {
SELECT rowid AS a, hex(block) AS block FROM t1_data
WHERE ((rowid>>36) & 0x01)==1
AND ((rowid>>31) & 0x1F)==0
} {
set n [string length $block]
set block [string range $block 0 $n-5]
append block [string repeat "00" 2]
db eval {
UPDATE t1_data SET block = unhex($block) WHERE rowid = $a
}
}
} {}
do_catchsql_test 4.5 {
SELECT count(*) FROM (
SELECT rowid FROM t1 WHERE t1 MATCH 'xyzterm' ORDER BY rowid ASC
)
} {0 5000}
do_catchsql_test 4.6 {
SELECT count(*) FROM (
SELECT rowid FROM t1 WHERE t1 MATCH 'xyzterm' ORDER BY rowid DESC
)
} {0 4879}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 5.0 {
CREATE VIRTUAL TABLE t USING fts5(x);
INSERT INTO t(t,rank) VALUES('pgsz', 64);
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<80
)
INSERT INTO t SELECT 'shared word' || (i%10) || ' shared shared' FROM s;
INSERT INTO t(t) VALUES('optimize');
}
set lLeaf [db eval { SELECT id FROM t_data }]
db_save_and_close
foreach leaf $lLeaf {
db_restore_and_reopen
set leaf [expr $leaf]
set hex [db one {
SELECT hex(block) FROM t_data WHERE id=$leaf
}]
# Replace the first 4 bytes of each leaf page with the size of the leaf in
# bytes plus 50 as a 2 byte integer, followed by 0x7FFF.
#
set nn [expr [string length $hex]/2]
set first "[format %.4x [expr $nn+50]]7FFF"
set hex [string replace $hex 0 7 $first]
db eval { UPDATE t_data SET block=unhex($hex) WHERE id=$leaf }
do_test 5.1.$leaf {
catchsql {
SELECT rowid FROM t WHERE t MATCH 'shared' ORDER BY rowid DESC;
}
set {} {}
} {}
do_test 5.1.$leaf.2 {
catchsql {
PRAGMA integrity_check;
}
set {} {}
} {}
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 6.0 {
CREATE VIRTUAL TABLE t USING fts5(x, content='', contentless_delete=1);
INSERT INTO t(rowid,x)
VALUES(1,'a b'), (2,'c d'), (3,'e f'), (4,'g h'), (5,'i j');
INSERT INTO t(t) VALUES('optimize');
DELETE FROM t WHERE rowid=2;
}
do_test 6.1 {
db eval {
SELECT rowid AS rid, hex(block) AS blk
FROM t_data WHERE rowid>1_000_000_000_000
} {}
set blk [string replace $blk 8 15 20000000]
execsql {
UPDATE t_data SET block = unhex($blk) WHERE rowid=$rid
}
} {}
do_execsql_test 6.2 {
DELETE FROM t WHERE rowid=3;
}
sqlite3_fts5_may_be_corrupt 0
finish_test
+13
View File
@@ -743,6 +743,19 @@ do_execsql_test 15.4 {
SELECT c, fts5_columnlocale(ft, 2) FROM ft
} {three {} three loc}
#-------------------------------------------------------------------------
#
set nMaxCol 500
set nStep 20
for {set ii $nStep} {$ii <= $nMaxCol} {incr ii $nStep} {
set C [list]
for {set iCol 0} {$iCol<$ii} {incr iCol} {
lappend C col$iCol
}
set ct "CREATE VIRTUAL TABLE t$ii USING fts5([join $C ,], locale=1)"
do_execsql_test 16.$ii $ct
}
finish_test
+13
View File
@@ -66,5 +66,18 @@ do_near_test 1.23 "a b c d e f g h i" { NEAR(a+b+c+d i b+c, 4) } 0
do_near_test 1.24 "a b c d e f g h i" { NEAR(i a+b+c+d b+c, 5) } 1
do_near_test 1.25 "a b c d e f g h i" { NEAR(i a+b+c+d b+c, 4) } 0
#-------------------------------------------------------------------------
# Check that https://sqlite.org/bugs/forumpost/16bb36f7e8 is fixed.
#
reset_db
do_execsql_test 2.0 {
CREATE VIRTUAL TABLE t USING fts5(x, tokenize=trigram);
INSERT INTO t
VALUES('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
}
do_execsql_test 2.1 {
SELECT count(*) FROM t WHERE t MATCH 'NEAR(aaa aaa, 4)';
} {1}
finish_test
+8
View File
@@ -65,5 +65,13 @@ foreach {in out} $test_vocab {
incr i
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
CREATE VIRTUAL TABLE tbl USING fts5(t, tokenize='porter unicode61');
INSERT INTO tbl VALUES(' andeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee');
}
finish_test
+28
View File
@@ -418,6 +418,34 @@ do_execsql_test 7.1.2 {
INSERT INTO t2(t2) VALUES('integrity-check');
}
#-------------------------------------------------------------------------
reset_db
fts5_aux_test_functions db
proc tcl_create {args} { return "tcl_tokenize" }
sqlite3_fts5_create_tokenizer db tcl tcl_create
proc tcl_tokenize {tflags text} {
foreach {w iStart iEnd} [fts5_tokenize_split $text] {
sqlite3_fts5_token $w $iStart $iEnd
if {$w=="usa"} {
sqlite3_fts5_token -colo "united" $iStart $iEnd
}
}
}
do_execsql_test 8.0 {
CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize='tcl', tokendata=1);
INSERT INTO t1 VALUES('usa is great');
INSERT INTO t1 VALUES('canada is cool');
INSERT INTO t1 VALUES('australia is what matters');
}
do_execsql_test 8.1 {
SELECT * FROM t1 WHERE t1 MATCH 'usa*';
} {{usa is great}}
} ;# foreach_detail_mode
finish_test
+10
View File
@@ -371,4 +371,14 @@ do_execsql_test 10.10 {
a 2 2 b 2 2 c 2 2 d 1 1 e 1 1 f 1 1
}
# Bug report 2026-05-18T05:19:01Z
#
set manyporters {porter Porter}
for {set i 0} {$i<18} {incr i} {
set manyporters "$manyporters $manyporters"
}
do_execsql_test 11.1 "
CREATE VIRTUAL TABLE t11 USING fts5(a,b,tokenize=\"$manyporters\");
" {}
finish_test
+8 -4
View File
@@ -105,6 +105,9 @@ static const unsigned char icuUtf8Trans1[] = {
while( (*zIn & 0xc0)==0x80 ){ \
c = (c<<6) + (0x3f & *(zIn++)); \
} \
if( c<0x80 \
|| (c&0xFFFFF800)==0xD800 \
|| (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \
}
#define SQLITE_ICU_SKIP_UTF8(zIn) \
@@ -362,7 +365,7 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){
const UChar *zInput; /* Pointer to input string */
UChar *zOutput = 0; /* Pointer to output buffer */
int nInput; /* Size of utf-16 input string in bytes */
int nOut; /* Size of output buffer in bytes */
sqlite3_int64 nOut; /* Size of output buffer in bytes */
int cnt;
int bToUpper; /* True for toupper(), false for tolower() */
UErrorCode status;
@@ -385,7 +388,7 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){
}
for(cnt=0; cnt<2; cnt++){
UChar *zNew = sqlite3_realloc(zOutput, nOut);
UChar *zNew = sqlite3_realloc64(zOutput, nOut);
if( zNew==0 ){
sqlite3_free(zOutput);
sqlite3_result_error_nomem(p);
@@ -394,9 +397,9 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){
zOutput = zNew;
status = U_ZERO_ERROR;
if( bToUpper ){
nOut = 2*u_strToUpper(zOutput,nOut/2,zInput,nInput/2,zLocale,&status);
nOut = 2LL*u_strToUpper(zOutput,nOut/2,zInput,nInput/2,zLocale,&status);
}else{
nOut = 2*u_strToLower(zOutput,nOut/2,zInput,nInput/2,zLocale,&status);
nOut = 2LL*u_strToLower(zOutput,nOut/2,zInput,nInput/2,zLocale,&status);
}
if( U_SUCCESS(status) ){
@@ -516,6 +519,7 @@ static void icuLoadCollation(
}
sqlite3_result_error(p, sqlite3_str_value(pStr), -1);
sqlite3_free(sqlite3_str_finish(pStr));
ucol_close(pUCollator);
return;
}
}
+1 -1
View File
@@ -1157,7 +1157,7 @@ static int amatchNext(sqlite3_vtab_cursor *cur){
#endif
nWord = (int)strlen(pWord->zWord+2);
if( nWord+20>nBuf ){
nBuf = (char)(nWord+100);
nBuf = nWord+100;
zBuf = sqlite3_realloc64(zBuf, nBuf);
if( zBuf==0 ) return SQLITE_NOMEM;
}
-152
View File
@@ -1,152 +0,0 @@
/*
** 2019-03-30
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
******************************************************************************
**
** An SQL function that uses the incremental BLOB I/O mechanism of SQLite
** to read or write part of a blob. This is intended for debugging use
** in the CLI.
**
** readblob(SCHEMA,TABLE,COLUMN,ROWID,OFFSET,N)
**
** Returns N bytes of the blob starting at OFFSET.
**
** writeblob(SCHEMA,TABLE,COLUMN,ROWID,OFFSET,NEWDATA)
**
** NEWDATA must be a blob. The content of NEWDATA overwrites the
** existing BLOB data at SCHEMA.TABLE.COLUMN for row ROWID beginning
** at OFFSET bytes into the blob.
*/
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#include <assert.h>
#include <string.h>
static void readblobFunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
sqlite3_blob *pBlob = 0;
const char *zSchema;
const char *zTable;
const char *zColumn;
sqlite3_int64 iRowid;
int iOfst;
unsigned char *aData;
int nData;
sqlite3 *db;
int rc;
zSchema = (const char*)sqlite3_value_text(argv[0]);
zTable = (const char*)sqlite3_value_text(argv[1]);
if( zTable==0 ){
sqlite3_result_error(context, "bad table name", -1);
return;
}
zColumn = (const char*)sqlite3_value_text(argv[2]);
if( zTable==0 ){
sqlite3_result_error(context, "bad column name", -1);
return;
}
iRowid = sqlite3_value_int64(argv[3]);
iOfst = sqlite3_value_int(argv[4]);
nData = sqlite3_value_int(argv[5]);
if( nData<=0 ) return;
aData = sqlite3_malloc64( nData+1 );
if( aData==0 ){
sqlite3_result_error_nomem(context);
return;
}
db = sqlite3_context_db_handle(context);
rc = sqlite3_blob_open(db, zSchema, zTable, zColumn, iRowid, 0, &pBlob);
if( rc ){
sqlite3_free(aData);
sqlite3_result_error(context, "cannot open BLOB pointer", -1);
return;
}
rc = sqlite3_blob_read(pBlob, aData, nData, iOfst);
sqlite3_blob_close(pBlob);
if( rc ){
sqlite3_free(aData);
sqlite3_result_error(context, "BLOB read failed", -1);
}else{
sqlite3_result_blob(context, aData, nData, sqlite3_free);
}
}
static void writeblobFunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
sqlite3_blob *pBlob = 0;
const char *zSchema;
const char *zTable;
const char *zColumn;
sqlite3_int64 iRowid;
int iOfst;
unsigned char *aData;
int nData;
sqlite3 *db;
int rc;
zSchema = (const char*)sqlite3_value_text(argv[0]);
zTable = (const char*)sqlite3_value_text(argv[1]);
if( zTable==0 ){
sqlite3_result_error(context, "bad table name", -1);
return;
}
zColumn = (const char*)sqlite3_value_text(argv[2]);
if( zTable==0 ){
sqlite3_result_error(context, "bad column name", -1);
return;
}
iRowid = sqlite3_value_int64(argv[3]);
iOfst = sqlite3_value_int(argv[4]);
if( sqlite3_value_type(argv[5])!=SQLITE_BLOB ){
sqlite3_result_error(context, "6th argument must be a BLOB", -1);
return;
}
nData = sqlite3_value_bytes(argv[5]);
aData = (unsigned char *)sqlite3_value_blob(argv[5]);
db = sqlite3_context_db_handle(context);
rc = sqlite3_blob_open(db, zSchema, zTable, zColumn, iRowid, 1, &pBlob);
if( rc ){
sqlite3_result_error(context, "cannot open BLOB pointer", -1);
return;
}
rc = sqlite3_blob_write(pBlob, aData, nData, iOfst);
sqlite3_blob_close(pBlob);
if( rc ){
sqlite3_result_error(context, "BLOB write failed", -1);
}
}
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_blobio_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
rc = sqlite3_create_function(db, "readblob", 6, SQLITE_UTF8, 0,
readblobFunc, 0, 0);
if( rc==SQLITE_OK ){
rc = sqlite3_create_function(db, "writeblob", 6, SQLITE_UTF8, 0,
writeblobFunc, 0, 0);
}
return rc;
}
+12
View File
@@ -60,6 +60,13 @@
**
** SELECT name FROM sqlite_btreeinfo
** WHERE type='table' AND NOT hasRowid;
**
** UNUSED, UNTESTED, UNSUPPORTED
**
** This extension exists for information and demonstration purposes
** only. The developers are not aware of any real-world use of this
** extension. The extension has no formal test cases. The developers
** do not actively support it.
*/
#if !defined(SQLITEINT_H)
#include "sqlite3ext.h"
@@ -286,6 +293,11 @@ static int binfoCompute(sqlite3 *db, int pgno, BinfoCursor *pCsr){
pCsr->depth = 1;
while(1){
sqlite3_bind_int(pStmt, 1, pgno);
if( pCsr->depth>25 ){
sqlite3_set_errmsg(db, SQLITE_CORRUPT, "btree nested too deep");
rc = SQLITE_ERROR;
break;
}
rc = sqlite3_step(pStmt);
if( rc!=SQLITE_ROW ){
rc = SQLITE_ERROR;
+8
View File
@@ -60,6 +60,10 @@ static void compressFunc(
nIn = sqlite3_value_bytes(argv[0]);
nOut = 13 + nIn + (nIn+999)/1000;
pOut = sqlite3_malloc64( nOut+5 );
if( pOut==0 ){
sqlite3_result_error_nomem(context);
return;
}
for(i=4; i>=0; i--){
x[i] = (nIn >> (7*(4-i)))&0x7f;
}
@@ -99,6 +103,10 @@ static void uncompressFunc(
if( (pIn[i]&0x80)!=0 ){ i++; break; }
}
pOut = sqlite3_malloc64( nOut+1 );
if( pOut==0 ){
sqlite3_result_error_nomem(context);
return;
}
rc = uncompress(pOut, &nOut, &pIn[i], nIn-i);
if( rc==Z_OK ){
sqlite3_result_blob(context, pOut, nOut, sqlite3_free);
+7 -2
View File
@@ -547,6 +547,10 @@ static int csvtabConnect(
if( nCol<=0 ){
csv_errmsg(&sRdr, "column= value must be positive");
goto csvtab_connect_error;
}else if( nCol>sqlite3_limit(db,SQLITE_LIMIT_COLUMN,-1) ){
csv_errmsg(&sRdr, "column= value too big, max %d",
sqlite3_limit(db, SQLITE_LIMIT_COLUMN,-1));
goto csvtab_connect_error;
}
}else
{
@@ -709,8 +713,9 @@ static int csvtabClose(sqlite3_vtab_cursor *cur){
static int csvtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
CsvTable *pTab = (CsvTable*)p;
CsvCursor *pCur;
size_t nByte;
nByte = sizeof(*pCur) + (sizeof(char*)+sizeof(i64))*pTab->nCol;
sqlite3_int64 nByte = pTab->nCol;
assert( pTab->nCol<32768 );
nByte = sizeof(*pCur) + (sizeof(char*)+sizeof(i64))*nByte;
pCur = sqlite3_malloc64( nByte );
if( pCur==0 ) return SQLITE_NOMEM;
memset(pCur, 0, nByte);
+16 -7
View File
@@ -261,7 +261,7 @@ static void decimal_result(sqlite3_context *pCtx, Decimal *p){
sqlite3_result_null(pCtx);
return;
}
z = sqlite3_malloc64( (sqlite3_int64)p->nDigit+4 );
z = sqlite3_malloc64( (sqlite3_int64)p->nDigit+8 );
if( z==0 ){
sqlite3_result_error_nomem(pCtx);
return;
@@ -299,28 +299,37 @@ static void decimal_result(sqlite3_context *pCtx, Decimal *p){
sqlite3_result_text(pCtx, z, i, sqlite3_free);
}
/* Forward declaration */
static void decimal_expand(Decimal *p, int nDigit, int nFrac);
/*
** Round a decimal value to N significant digits. N must be positive.
*/
static void decimal_round(Decimal *p, int N){
int i;
int nZero;
int nZero; /* Number of leading zeros */
if( N<1 ) return;
if( p==0 ) return;
if( p->nDigit<=N ) return;
for(nZero=0; nZero<p->nDigit && p->a[nZero]==0; nZero++){}
N += nZero;
if( p->nDigit<=N ) return;
if( p->a[N]>4 ){
if( p->a[N]>=5 ){
/* If all leading digits are 9, increase the number of digits
** by adding a new 0 to the front */
for(i=0; i<N && p->a[i]==9; i++){}
if( i==N ){
decimal_expand(p, p->nDigit+1, 0);
if( p->oom ) return;
}
/* Do the rounding */
p->a[N-1]++;
for(i=N-1; i>0 && p->a[i]>9; i--){
p->a[i] = 0;
p->a[i-1]++;
}
if( p->a[0]>9 ){
p->a[0] = 1;
p->nFrac--;
}
assert( p->a[0]<=9 );
}
memset(&p->a[N], 0, p->nDigit - N);
}
+847
View File
@@ -0,0 +1,847 @@
/*
** 2026-04-13
**
** 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 extension implements an SQL function:
**
** diskused(X)
**
** Where X is the schema name (typically 'main'). The output is text
** that describes how much filesystem space the various tables and indexes
** of the database consume.
**
** This function is a replacement for the (now deprecated)
** "sqlite3_analyzer" utility program. This function is built
** into the CLI and is used to implement the ".diskused" command there.
*/
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
/*
** State information for the analysis
*/
typedef struct DiskUsed DiskUsed;
struct DiskUsed {
sqlite3 *db; /* Database connection */
sqlite3_context *context; /* SQL function context */
sqlite3_str *pOut; /* Write output here */
char *zSU; /* Name of the temp.space_used table */
const char *zSchema; /* Schema to be analyzed */
};
/*
** Free all resources that the DiskUsed object references and
** reset the DiskUsed object.
**
** Call this routine multiple times on the same DiskUsed object
** is a harmless no-op, as long as the memory for the object itself
** has not been freed.
*/
static void diskusedReset(DiskUsed *p){
if( p->zSU ){
char *zSql = sqlite3_mprintf("DROP TABLE temp.%s;", p->zSU);
if( zSql ){
sqlite3_exec(p->db, zSql, 0, 0, 0);
sqlite3_free(zSql);
}
}
sqlite3_str_free(p->pOut);
sqlite3_free(p->zSU);
memset(p, 0, sizeof(*p));
}
/*
** Report an error using formatted text. If zFormat==NULL then report
** an OOM error.
*/
static void diskusedError(DiskUsed *p, const char *zFormat, ...){
char *zErr;
if( zFormat ){
va_list ap;
va_start(ap, zFormat);
zErr = sqlite3_vmprintf(zFormat, ap);
va_end(ap);
}else{
zErr = 0;
}
if( zErr==0 ){
sqlite3_result_error_nomem(p->context);
}else{
sqlite3_result_error(p->context, zErr, -1);
sqlite3_free(zErr);
}
diskusedReset(p);
}
/*
** Prepare and return an SQL statement.
*/
static sqlite3_stmt *diskusedVPrep(DiskUsed *p, const char *zFmt, va_list ap){
char *zSql;
int rc;
sqlite3_stmt *pStmt = 0;
zSql = sqlite3_vmprintf(zFmt, ap);
if( zSql==0 ){ diskusedError(p,0); return 0; }
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
if( rc ){
diskusedError(p, "SQL parse error: %s\nOriginal SQL: %s",
sqlite3_errmsg(p->db), zSql);
sqlite3_finalize(pStmt);
diskusedReset(p);
pStmt = 0;
}
sqlite3_free(zSql);
return pStmt;
}
static sqlite3_stmt *diskusedPrepare(DiskUsed *p, const char *zFormat, ...){
va_list ap;
sqlite3_stmt *pStmt = 0;
va_start(ap, zFormat);
pStmt = diskusedVPrep(p,zFormat,ap);
va_end(ap);
return pStmt;
}
/*
** If rc is something other than SQLITE_DONE or SQLITE_OK, then report
** an error and return true.
**
** If rc is SQLITE_DONE or SQLITE_OK, then return false.
**
** The prepared statement is closed in either case.
*/
static int diskusedStmtFinish(DiskUsed *p, int rc, sqlite3_stmt *pStmt){
if( rc==SQLITE_DONE ){
rc = SQLITE_OK;
}
if( rc!=SQLITE_OK || (rc = sqlite3_reset(pStmt))!=SQLITE_OK ){
diskusedError(p, "SQL run-time error: %s\nOriginal SQL: %s",
sqlite3_errmsg(p->db), sqlite3_sql(pStmt));
diskusedReset(p);
}
sqlite3_finalize(pStmt);
return rc;
}
/*
** Run SQL. Return the number of errors.
*/
static int diskusedSql(DiskUsed *p, const char *zFormat, ...){
va_list ap;
int rc;
sqlite3_stmt *pStmt = 0;
va_start(ap, zFormat);
pStmt = diskusedVPrep(p,zFormat,ap);
va_end(ap);
if( pStmt==0 ) return 1;
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){}
if( rc==SQLITE_DONE ){
rc = SQLITE_OK;
}else{
diskusedError(p, "SQL run-time error: %s\nOriginal SQL: %s",
sqlite3_errmsg(p->db), sqlite3_sql(pStmt));
diskusedReset(p);
}
sqlite3_finalize(pStmt);
return rc;
}
/*
** Run an SQL query that returns an integer. Write that integer
** into *piRes. Return the number of errors.
*/
static int diskusedSqlInt(
DiskUsed *p,
sqlite3_int64 *piRes,
const char *zFormat, ...
){
va_list ap;
int rc;
sqlite3_stmt *pStmt = 0;
va_start(ap, zFormat);
pStmt = diskusedVPrep(p,zFormat,ap);
va_end(ap);
if( pStmt==0 ) return 1;
rc = sqlite3_step(pStmt);
if( rc==SQLITE_ROW ){
*piRes = sqlite3_column_int64(pStmt, 0);
rc = SQLITE_OK;
}else if( rc==SQLITE_DONE ){
rc = SQLITE_OK;
}else{
if( p->db ){
/* p->db is NULL if there was some prior error */
diskusedError(p, "SQL run-time error: %s\nOriginal SQL: %s",
sqlite3_errmsg(p->db), sqlite3_sql(pStmt));
}
diskusedReset(p);
}
sqlite3_finalize(pStmt);
return rc;
}
/*
** Add to the output a title line that contains the text determined
** by the format string. If the output is initially empty, begin
** the title line with "/" so that it forms the beginning of a C-style
** comment. Otherwise begin with a new-line. Always finish with a
** newline.
*/
static void diskusedTitle(DiskUsed *p, const char *zFormat, ...){
char *zFirst;
char *zTitle;
size_t nTitle;
va_list ap;
va_start(ap, zFormat);
zTitle = sqlite3_vmprintf(zFormat, ap);
va_end(ap);
if( zTitle==0 ){
diskusedError(p, 0);
return;
}
zFirst = sqlite3_str_length(p->pOut)==0 ? "/" : "\n*";
nTitle = strlen(zTitle);
if( nTitle>=75 ){
sqlite3_str_appendf(p->pOut, "%s** %z\n\n", zFirst, zTitle);
}else{
int nExtra = 74 - (int)nTitle;
sqlite3_str_appendf(p->pOut, "%s** %z %.*c\n\n", zFirst, zTitle,
nExtra, '*');
}
}
/*
** Add an output line that begins with the zDesc text extended out to
** 50 columns with "." characters, and followed by whatever text is
** described by zFormat.
*/
static void diskusedLine(
DiskUsed *p, /* DiskUsed context */
const char *zDesc, /* Description */
const char *zFormat, /* Argument to the description */
...
){
char *zTxt;
size_t nDesc;
va_list ap;
va_start(ap, zFormat);
zTxt = sqlite3_vmprintf(zFormat, ap);
va_end(ap);
if( zTxt==0 ){
diskusedError(p, 0);
return;
}
nDesc = zDesc ? strlen(zDesc) : 0;
if( nDesc>=50 ){
sqlite3_str_appendf(p->pOut, "%s %z", zDesc, zTxt);
}else{
int nExtra = 50 - (int)nDesc;
sqlite3_str_appendf(p->pOut, "%s%.*c %z", zDesc, nExtra, '.', zTxt);
}
}
/*
** Write a percentage into the output. The number written should show
** two or three significant digits, with the decimal point being the fourth
** character.
*/
static void diskusedPercent(DiskUsed *p, double r){
char zNum[100];
char *zDP;
int nLeadingDigit;
int sz;
sqlite3_snprintf(sizeof(zNum)-5, zNum, r>=10.0 ? "%.3g" :"%.2g", r);
sz = (int)strlen(zNum);
zDP = strchr(zNum, '.');
if( zDP==0 ){
memcpy(zNum+sz,".0",3);
nLeadingDigit = sz;
sz += 2;
}else{
nLeadingDigit = (int)(zDP - zNum);
}
if( nLeadingDigit<3 ){
sqlite3_str_appendchar(p->pOut, 3-nLeadingDigit, ' ');
}
sqlite3_str_append(p->pOut, zNum, sz);
sqlite3_str_append(p->pOut, "%\n", 2);
}
/*
** Create a subreport on a subset of tables and/or indexes.
**
** The title if the subreport is given by zTitle. zWhere is
** a boolean expression that can go in the WHERE clause to select
** the relevant rows of the s.zSU table.
*/
static int diskusedSubreport(
DiskUsed *p, /* DiskUsed context */
char *zTitle, /* Title for this subreport */
char *zWhere, /* WHERE clause for this subreport */
sqlite3_int64 pgsz, /* Database page size */
sqlite3_int64 nPage /* Number of pages in entire database */
){
sqlite3_stmt *pStmt; /* Statement to query p->zSU */
sqlite3_int64 nentry; /* Number of btree entires */
sqlite3_int64 payload; /* Payload in bytes */
sqlite3_int64 ovfl_payload; /* overflow payload in bytes */
sqlite3_int64 mx_payload; /* largest individual payload */
sqlite3_int64 ovfl_cnt; /* Number entries using overflow */
sqlite3_int64 leaf_pages; /* Leaf pages */
sqlite3_int64 int_pages; /* internal pages */
sqlite3_int64 ovfl_pages; /* overflow pages */
sqlite3_int64 leaf_unused; /* unused bytes on leaf pages */
sqlite3_int64 int_unused; /* unused bytes on internal pages */
sqlite3_int64 ovfl_unused; /* unused bytes on overflow pages */
sqlite3_int64 int_cell; /* B-tree entries on internal pages */
sqlite3_int64 depth; /* btree depth */
sqlite3_int64 cnt; /* Number of s.zSU entries that match */
sqlite3_int64 storage; /* Total bytes */
sqlite3_int64 total_pages; /* Total page count */
sqlite3_int64 total_unused; /* Total unused bytes */
sqlite3_int64 total_meta; /* Total metadata */
int rc;
if( zTitle==0 || zWhere==0 ){
diskusedError(p, 0);
return SQLITE_NOMEM;
}
pStmt = diskusedPrepare(p,
"SELECT\n"
" sum(if(is_without_rowid OR is_index,nentry,leaf_entries)),\n" /* 0 */
" sum(payload),\n" /* 1 */
" sum(ovfl_payload),\n" /* 2 */
" max(mx_payload),\n" /* 3 */
" sum(ovfl_cnt),\n" /* 4 */
" sum(leaf_pages),\n" /* 5 */
" sum(int_pages),\n" /* 6 */
" sum(ovfl_pages),\n" /* 7 */
" sum(leaf_unused),\n" /* 8 */
" sum(int_unused),\n" /* 9 */
" sum(ovfl_unused),\n" /* 10 */
" max(depth),\n" /* 11 */
" count(*),\n" /* 12 */
" sum(int_entries)\n" /* 13 */
" FROM temp.%s WHERE %s",
p->zSU, zWhere);
if( pStmt==0 ) return 1;
rc = sqlite3_step(pStmt);
if( rc==SQLITE_ROW ){
diskusedTitle(p, "%s", zTitle);
nentry = sqlite3_column_int64(pStmt, 0);
payload = sqlite3_column_int64(pStmt, 1);
ovfl_payload = sqlite3_column_int64(pStmt, 2);
mx_payload = sqlite3_column_int64(pStmt, 3);
ovfl_cnt = sqlite3_column_int64(pStmt, 4);
leaf_pages = sqlite3_column_int64(pStmt, 5);
int_pages = sqlite3_column_int64(pStmt, 6);
ovfl_pages = sqlite3_column_int64(pStmt, 7);
leaf_unused = sqlite3_column_int64(pStmt, 8);
int_unused = sqlite3_column_int64(pStmt, 9);
ovfl_unused = sqlite3_column_int64(pStmt, 10);
depth = sqlite3_column_int64(pStmt, 11);
cnt = sqlite3_column_int64(pStmt, 12);
int_cell = sqlite3_column_int64(pStmt, 13);
rc = SQLITE_DONE;
total_pages = leaf_pages + int_pages + ovfl_pages;
diskusedLine(p, "Percentage of total database", "%.3g%%\n",
(total_pages*100.0)/(double)nPage);
diskusedLine(p, "Number of entries", "%lld\n", nentry);
storage = total_pages*pgsz;
diskusedLine(p, "Bytes of storage consumed", "%lld\n", storage);
diskusedLine(p, "Bytes of payload", "%-11lld ", payload);
diskusedPercent(p, payload*100.0/(double)storage);
if( ovfl_cnt>0 ){
diskusedLine(p, "Bytes of payload in overflow","%-11lld ",ovfl_payload);
diskusedPercent(p, ovfl_payload*100.0/(double)payload);
}
total_unused = leaf_unused + int_unused + ovfl_unused;
total_meta = storage - payload - total_unused;
diskusedLine(p, "Bytes of metadata","%-11lld ", total_meta);
diskusedPercent(p, total_meta*100.0/(double)storage);
if( cnt==1 ){
diskusedLine(p, "B-tree depth", "%lld\n", depth);
if( int_cell>1 ){
diskusedLine(p, "Average fanout", "%.1f\n",
(double)(int_cell+int_pages)/(double)int_pages);
}
}
if( nentry>0 ){
diskusedLine(p, "Average payload per entry", "%.1f\n",
(double)payload/(double)nentry);
diskusedLine(p, "Average unused bytes per entry", "%.1f\n",
(double)total_unused/(double)nentry);
diskusedLine(p, "Average metadata per entry", "%.1f\n",
(double)total_meta/(double)nentry);
}
diskusedLine(p, "Maximum single-entry payload", "%lld\n", mx_payload);
if( nentry>0 ){
diskusedLine(p, "Entries that use overflow", "%-11lld ", ovfl_cnt);
diskusedPercent(p, ovfl_cnt*100.0/(double)nentry);
}
if( int_pages>0 ){
diskusedLine(p, "Index pages used", "%lld\n", int_pages);
}
diskusedLine(p, "Primary pages used", "%lld\n", leaf_pages);
if( ovfl_cnt ){
diskusedLine(p, "Overflow pages used", "%lld\n", ovfl_pages);
}
diskusedLine(p, "Total pages used", "%lld\n", total_pages);
if( int_pages>0 ){
diskusedLine(p, "Unused bytes on index pages", "%lld\n", int_unused);
}
diskusedLine(p, "Unused bytes on primary pages", "%lld\n", leaf_unused);
if( ovfl_cnt ){
diskusedLine(p, "Unused bytes on overflow pages", "%lld\n", ovfl_unused);
}
diskusedLine(p, "Unused bytes on all pages", "%-11lld ", total_unused);
diskusedPercent(p, total_unused*100.0/(double)storage);
}
return diskusedStmtFinish(p, rc, pStmt);
}
/*
** SQL Function: diskused(SCHEMA)
**
** Analyze the database schema named in the argument. Return text
** containing the space utilization stats.
*/
static void diskusedFunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
int rc;
sqlite3_stmt *pStmt;
int n;
sqlite3_int64 ii;
sqlite3_int64 pgsz;
sqlite3_int64 nPage;
sqlite3_int64 nPageInUse;
sqlite3_int64 nFreeList;
sqlite3_int64 nIndex;
sqlite3_int64 nWORowid;
DiskUsed s;
sqlite3_uint64 r[2];
(void)argc;
memset(&s, 0, sizeof(s));
s.db = sqlite3_context_db_handle(context);
s.context = context;
s.pOut = sqlite3_str_new(0);
if( sqlite3_str_errcode(s.pOut) ){
diskusedError(&s, 0);
return;
}
s.zSchema = (const char*)sqlite3_value_text(argv[0]);
if( s.zSchema==0 ){
s.zSchema = "main";
}else if( sqlite3_strlike("temp",s.zSchema,0)==0 ){
diskusedReset(&s);
sqlite3_result_text(context, "cannot analyze \"temp\"",-1,SQLITE_STATIC);
return;
}
ii = 0;
rc = diskusedSqlInt(&s,&ii,"SELECT 1 FROM pragma_database_list"
" WHERE name=%Q COLLATE nocase",s.zSchema);
if( rc || ii==0 ){
diskusedReset(&s);
sqlite3_result_text(context,"no such database",-1,SQLITE_STATIC);
return;
}
sqlite3_randomness(sizeof(r), &r);
s.zSU = sqlite3_mprintf("diskused%016llx%016llx", r[0], r[1]);
if( s.zSU==0 ){ diskusedError(&s, 0); return; }
/* The s.zSU table contains the data used for the analysis.
** The table name contains 128-bits of randomness to avoid
** collisions with preexisting tables in temp.
*/
rc = diskusedSql(&s,
"CREATE TABLE temp.%s(\n"
" name text, -- A table or index\n"
" tblname text, -- Table that owns name\n"
" is_index boolean, -- TRUE if it is an index\n"
" is_without_rowid boolean, -- TRUE if WITHOUT ROWID table\n"
" nentry int, -- Number of entries in the BTree\n"
" leaf_entries int, -- Number of leaf entries\n"
" depth int, -- Depth of the b-tree\n"
" payload int, -- Total data stored in this table/index\n"
" ovfl_payload int, -- Total data stored on overflow pages\n"
" ovfl_cnt int, -- Number of entries that use overflow\n"
" mx_payload int, -- Maximum payload size\n"
" int_pages int, -- Interior pages used\n"
" leaf_pages int, -- Leaf pages used\n"
" ovfl_pages int, -- Overflow pages used\n"
" int_unused int, -- Unused bytes on interior pages\n"
" leaf_unused int, -- Unused bytes on primary pages\n"
" ovfl_unused int, -- Unused bytes on overflow pages\n"
" int_entries int -- Btree cells on internal pages\n"
");",
s.zSU
);
if( rc ) return;
/* Populate the s.zSU table
*/
rc = diskusedSql(&s,
"WITH\n"
" allidx(idxname) AS (\n"
" SELECT name FROM \"%w\".sqlite_schema WHERE type='index'\n"
" ),\n"
" allobj(allname,tblname,isidx,isworowid) AS (\n"
" SELECT 'sqlite_schema',\n"
" 'sqlite_schema',\n"
" 0,\n"
" 0\n"
" UNION ALL\n"
" SELECT name,\n"
" tbl_name,\n"
" type='index',\n"
" EXISTS(SELECT 1\n"
" FROM pragma_index_list(sqlite_schema.name,%Q)\n"
" WHERE pragma_index_list.origin='pk'\n"
" AND pragma_index_list.name NOT IN allidx)\n"
" FROM \"%w\".sqlite_schema\n"
" )\n"
"INSERT INTO temp.%s\n"
" SELECT\n"
" allname,\n"
" tblname,\n"
" isidx,\n"
" isworowid,\n"
" sum(ncell),\n"
" sum((pagetype='leaf')*ncell),\n"
" max((length(if(path GLOB '*+*','',path))+3)/4),\n"
" sum(payload),\n"
" sum((pagetype='overflow')*payload),\n"
" sum(path GLOB '*+000000'),\n"
" max(mx_payload),\n"
" sum(pagetype='internal'),\n"
" sum(pagetype='leaf'),\n"
" sum(pagetype='overflow'),\n"
" sum((pagetype='internal')*unused),\n"
" sum((pagetype='leaf')*unused),\n"
" sum((pagetype='overflow')*unused),\n"
" sum(if(pagetype='internal',ncell))\n"
" FROM allobj CROSS JOIN dbstat(%Q) \n"
" WHERE dbstat.name=allobj.allname\n"
" GROUP BY allname;\n",
s.zSchema, /* %w.sqlite_schema -- in allidx */
s.zSchema, /* pragma_index_list(...,%Q) */
s.zSchema, /* %w.sqlite_schema */
s.zSU, /* INTO temp.%s */
s.zSchema /* JOIN dbstat(%Q) */
);
if( rc ) return;
nPage = 0;
rc = diskusedSqlInt(&s, &nPage, "PRAGMA \"%w\".page_count", s.zSchema);
if( rc ) return;
if( nPage<=0 ){
/* Very brief reply for an empty database */
diskusedReset(&s);
sqlite3_result_text(context, "empty database", -1, SQLITE_STATIC);
return;
}
/* Begin generating the report */
diskusedTitle(&s, "Database storage utilization report");
pgsz = 0;
rc = diskusedSqlInt(&s, &pgsz, "PRAGMA \"%w\".page_size", s.zSchema);
if( rc ) return;
diskusedLine(&s, "Page size in bytes","%lld\n",pgsz);
diskusedLine(&s, "Pages in the database", "%lld\n", nPage);
nPageInUse = 0;
rc = diskusedSqlInt(&s, &nPageInUse,
"SELECT sum(leaf_pages+int_pages+ovfl_pages) FROM temp.%s", s.zSU);
if( rc ) return;
diskusedLine(&s, "Pages that store data", "%-11lld ", nPageInUse);
diskusedPercent(&s, (nPageInUse*100.0)/(double)nPage);
nFreeList = 0;
rc = diskusedSqlInt(&s, &nFreeList, "PRAGMA \"%w\".freelist_count",s.zSchema);
if( rc ) return;
diskusedLine(&s, "Pages on the freelist", "%-11lld ", nFreeList);
diskusedPercent(&s, (nFreeList*100.0)/(double)nPage);
ii = 0;
rc = diskusedSqlInt(&s, &ii, "PRAGMA \"%w\".auto_vacuum", s.zSchema);
if( rc ) return;
if( ii==0 || nPage<=1 ){
ii = 0;
}else{
double rPtrsPerPage = pgsz/5;
double rAvPage = (nPage-1.0)/(rPtrsPerPage+1.0);
ii = (sqlite3_int64)ceil(rAvPage);
}
diskusedLine(&s, "Pages of auto-vacuum overhead", "%-11lld ", ii);
diskusedPercent(&s, (ii*100.0)/(double)nPage);
ii = 0;
rc = diskusedSqlInt(&s, &ii,
"SELECT count(*)+1 FROM \"%w\".sqlite_schema WHERE type='table'",
s.zSchema);
if( rc ) return;
diskusedLine(&s, "Number of tables", "%lld\n", ii);
nWORowid = 0;
rc = diskusedSqlInt(&s, &nWORowid,
"SELECT count(*) FROM \"%w\".pragma_table_list WHERE wr",
s.zSchema);
if( rc ) return;
if( nWORowid>0 ){
diskusedLine(&s, "Number of WITHOUT ROWID tables", "%lld\n", nWORowid);
diskusedLine(&s, "Number of rowid tables", "%lld\n", ii - nWORowid);
}
nIndex = 0;
rc = diskusedSqlInt(&s, &nIndex,
"SELECT count(*) FROM \"%w\".sqlite_schema WHERE type='index'",
s.zSchema);
if( rc ) return;
diskusedLine(&s, "Number of indexes", "%lld\n", nIndex);
ii = 0;
rc = diskusedSqlInt(&s, &ii,
"SELECT count(*) FROM \"%w\".sqlite_schema"
" WHERE name GLOB 'sqlite_autoindex_*' AND type='index'",
s.zSchema);
if( rc ) return;
diskusedLine(&s, "Number of defined indexes", "%lld\n", nIndex - ii);
diskusedLine(&s, "Number of implied indexes", "%lld\n", ii);
diskusedLine(&s, "Size of the database in bytes", "%lld\n", pgsz*nPage);
ii = 0;
rc = diskusedSqlInt(&s, &ii,
"SELECT sum(payload) FROM temp.%s"
" WHERE NOT is_index AND name NOT LIKE 'sqlite_schema'",
s.zSU);
if( rc ) return;
diskusedLine(&s, "Bytes of payload", "%-11lld ", ii);
diskusedPercent(&s, ii*100.0/(double)(pgsz*nPage));
diskusedTitle(&s, "Page counts for all tables with their indexes");
pStmt = diskusedPrepare(&s,
"SELECT upper(tblname),\n"
" sum(int_pages+leaf_pages+ovfl_pages)\n"
" FROM temp.%s\n"
" WHERE tblname IS NOT NULL\n"
" GROUP BY 1\n"
" ORDER BY 2 DESC, 1;",
s.zSU);
if( pStmt==0 ) return;
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
sqlite3_int64 nn = sqlite3_column_int64(pStmt,1);
diskusedLine(&s, (const char*)sqlite3_column_text(pStmt,0), "%-11lld ", nn);
diskusedPercent(&s, (nn*100.0)/(double)nPage);
}
if( diskusedStmtFinish(&s, rc, pStmt) ) return;
diskusedTitle(&s, "Page counts for all tables and indexes separately");
pStmt = diskusedPrepare(&s,
"SELECT upper(name),\n"
" sum(int_pages+leaf_pages+ovfl_pages)\n"
" FROM temp.%s\n"
" WHERE name IS NOT NULL\n"
" GROUP BY 1\n"
" ORDER BY 2 DESC, 1;",
s.zSU);
if( pStmt==0 ) return;
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
sqlite3_int64 nn = sqlite3_column_int64(pStmt,1);
diskusedLine(&s, (const char*)sqlite3_column_text(pStmt,0), "%-11lld ", nn);
diskusedPercent(&s, (nn*100.0)/(double)nPage);
}
if( diskusedStmtFinish(&s, rc, pStmt) ) return;
rc = diskusedSubreport(&s, "All tables and indexes", "1", pgsz, nPage);
if( rc ) return;
rc = diskusedSubreport(&s, "All tables", "NOT is_index", pgsz, nPage);
if( rc ) return;
if( nWORowid>0 ){
rc = diskusedSubreport(&s, "All WITHOUT ROWID tables", "is_without_rowid",
pgsz, nPage);
if( rc ) return;
rc = diskusedSubreport(&s, "All rowid tables",
"NOT is_without_rowid AND NOT is_index",
pgsz, nPage);
if( rc ) return;
}
rc = diskusedSubreport(&s, "All indexes", "is_index", pgsz, nPage);
if( rc ) return;
pStmt = diskusedPrepare(&s,
"SELECT upper(tblname), tblname, sum(is_index) FROM temp.%s"
" GROUP BY 1 ORDER BY 1",
s.zSU);
if( pStmt==0 ) return;
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
const char *zUpper = (const char*)sqlite3_column_text(pStmt, 0);
const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
int nSubIndex = sqlite3_column_int(pStmt, 2);
if( nSubIndex==0 ){
char *zTitle = sqlite3_mprintf("Table %s", zUpper);
char *zWhere = sqlite3_mprintf("name=%Q", zName);
rc = diskusedSubreport(&s, zTitle, zWhere, pgsz, nPage);
sqlite3_free(zTitle);
sqlite3_free(zWhere);
if( rc ) break;
}else{
sqlite3_stmt *pS2;
char *zTitle = sqlite3_mprintf("Table %s and all its indexes", zUpper);
char *zWhere = sqlite3_mprintf("tblname=%Q", zName);
rc = diskusedSubreport(&s, zTitle, zWhere, pgsz, nPage);
sqlite3_free(zTitle);
sqlite3_free(zWhere);
if( rc ) break;
zTitle = sqlite3_mprintf("Table %s w/o any indexes", zUpper);
zWhere = sqlite3_mprintf("name=%Q", zName);
rc = diskusedSubreport(&s, zTitle, zWhere, pgsz, nPage);
sqlite3_free(zTitle);
sqlite3_free(zWhere);
if( rc ) break;
if( nSubIndex>1 ){
zTitle = sqlite3_mprintf("All indexes of table %s", zUpper);
zWhere = sqlite3_mprintf("tblname=%Q AND is_index", zName);
rc = diskusedSubreport(&s, zTitle, zWhere, pgsz, nPage);
sqlite3_free(zTitle);
sqlite3_free(zWhere);
if( rc ) break;
}
pS2 = diskusedPrepare(&s,
"SELECT name, upper(name) FROM temp.%s"
" WHERE is_index AND tblname=%Q",
s.zSU, zName);
if( pS2==0 ){
rc = SQLITE_NOMEM;
break;
}
while( (rc = sqlite3_step(pS2))==SQLITE_ROW ){
const char *zU = (const char*)sqlite3_column_text(pS2, 1);
const char *zN = (const char*)sqlite3_column_text(pS2, 0);
zTitle = sqlite3_mprintf("Index %s", zU);
zWhere = sqlite3_mprintf("name=%Q", zN);
rc = diskusedSubreport(&s, zTitle, zWhere, pgsz, nPage);
sqlite3_free(zTitle);
sqlite3_free(zWhere);
if( rc ) break;
}
rc = diskusedStmtFinish(&s, rc, pS2);
if( rc ) break;
}
}
if( diskusedStmtFinish(&s, rc, pStmt) ) return;
/* Append SQL statements that will recreate the raw data used for
** the analysis.
*/
diskusedTitle(&s, "Raw data used to generate this report");
sqlite3_str_appendf(s.pOut,
"The following SQL will create a table named \"space_used\" which\n"
"contains most of the information used to generate the report above.\n"
"*/\n"
);
sqlite3_str_appendf(s.pOut,
"BEGIN;\n"
"CREATE TABLE space_used(\n"
" name text, -- A table or index\n" /* 0 */
" tblname text, -- Table that owns name\n" /* 1 */
" is_index boolean, -- TRUE if it is an index\n" /* 2 */
" is_without_rowid boolean, -- TRUE if WITHOUT ROWID table\n" /* 3 */
" nentry int, -- Number of entries in the BTree\n" /* 4 */
" leaf_entries int, -- Number of leaf entries\n" /* 5 */
" depth int, -- Depth of the b-tree\n" /* 6 */
" payload int, -- Total data in this table/index\n" /* 7 */
" ovfl_payload int, -- Total data on overflow pages\n" /* 8 */
" ovfl_cnt int, -- Entries that use overflow\n" /* 9 */
" mx_payload int, -- Maximum payload size\n" /* 10 */
" int_pages int, -- Interior pages used\n" /* 11 */
" leaf_pages int, -- Leaf pages used\n" /* 12 */
" ovfl_pages int, -- Overflow pages used\n" /* 13 */
" int_unused int, -- Unused bytes on interior pages\n" /* 14 */
" leaf_unused int, -- Unused bytes on primary pages\n" /* 15 */
" ovfl_unused int, -- Unused bytes on overflow pages\n" /* 16 */
" int_entries int -- B-tree entries on internal pages\n"/* 17 */
");\n"
"INSERT INTO space_used VALUES\n"
);
pStmt = diskusedPrepare(&s,
"SELECT quote(name), quote(tblname),\n" /* 0..1 */
" is_index, is_without_rowid, nentry, leaf_entries,\n" /* 2..5 */
" depth, payload, ovfl_payload, ovfl_cnt, mx_payload,\n" /* 6..10 */
" int_pages, leaf_pages, ovfl_pages, int_unused,\n" /* 11..14 */
" leaf_unused, ovfl_unused, int_entries\n" /* 15..17 */
" FROM temp.%s;",
s.zSU);
if( pStmt==0 ) return;
n = 0;
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
if( n++ ) sqlite3_str_appendf(s.pOut,",\n");
sqlite3_str_appendf(s.pOut,
" (%s,%s,%lld,%lld,%lld,%lld,%lld,%lld,%lld,"
"%lld,%lld,%lld,%lld,%lld,%lld,%lld,%lld,%lld)",
sqlite3_column_text(pStmt, 0),
sqlite3_column_text(pStmt, 1),
sqlite3_column_int64(pStmt, 2),
sqlite3_column_int64(pStmt, 3),
sqlite3_column_int64(pStmt, 4),
sqlite3_column_int64(pStmt, 5),
sqlite3_column_int64(pStmt, 6),
sqlite3_column_int64(pStmt, 7),
sqlite3_column_int64(pStmt, 8),
sqlite3_column_int64(pStmt, 9),
sqlite3_column_int64(pStmt, 10),
sqlite3_column_int64(pStmt, 11),
sqlite3_column_int64(pStmt, 12),
sqlite3_column_int64(pStmt, 13),
sqlite3_column_int64(pStmt, 14),
sqlite3_column_int64(pStmt, 15),
sqlite3_column_int64(pStmt, 16),
sqlite3_column_int64(pStmt, 17));
}
if( rc!=SQLITE_DONE ){
diskusedError(&s, "SQL run-time error: %s\nSQL: %s",
sqlite3_errmsg(s.db), sqlite3_sql(pStmt));
sqlite3_finalize(pStmt);
return;
}
sqlite3_str_appendf(s.pOut,";\nCOMMIT;");
sqlite3_finalize(pStmt);
if( sqlite3_str_length(s.pOut) ){
sqlite3_result_text(context, sqlite3_str_finish(s.pOut), -1,
sqlite3_free);
s.pOut = 0;
}
diskusedReset(&s);
}
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_diskused_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
rc = sqlite3_create_function(db, "diskused", 1,
SQLITE_UTF8|SQLITE_INNOCUOUS,
0, diskusedFunc, 0, 0);
return rc;
}
+66 -9
View File
@@ -97,8 +97,6 @@ SQLITE_EXTENSION_INIT1
# define STRUCT_STAT struct _stat
# define chmod(path,mode) fileio_chmod(path,mode)
# define mkdir(path,mode) fileio_mkdir(path)
extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*);
extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR);
#endif
#include <time.h>
#include <errno.h>
@@ -125,13 +123,69 @@ SQLITE_EXTENSION_INIT1
#define FSDIR_COLUMN_PATH 5 /* Path to top of search */
#define FSDIR_COLUMN_DIR 6 /* Path is relative to this directory */
#ifdef _WIN32
/*
** Convert a UTF-8 string to Microsoft Unicode.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static wchar_t *winUtf8To16(const char *zText){
int nChar;
wchar_t *zWideText;
nChar = MultiByteToWideChar(CP_UTF8, 0, zText, -1, NULL, 0);
if( nChar==0 ){
return 0;
}
zWideText = sqlite3_malloc64(nChar*sizeof(WCHAR) );
if( zWideText==0 ){
return 0;
}
nChar = MultiByteToWideChar(CP_UTF8, 0, zText, -1, zWideText,
nChar);
if( nChar==0 ){
sqlite3_free(zWideText);
zWideText = 0;
}
return zWideText;
}
#endif /* _WIN32 */
#ifdef _WIN32
/*
** Convert a Microsoft Unicode string to UTF-8.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static char *winUtf16To8(wchar_t *zWideText){
int nByte;
char *zText;
nByte = WideCharToMultiByte(CP_UTF8, 0, zWideText, -1, 0, 0, 0, 0);
if( nByte == 0 ){
return 0;
}
zText = sqlite3_malloc64( nByte );
if( zText==0 ){
return 0;
}
nByte = WideCharToMultiByte(CP_UTF8, 0, zWideText, -1, zText, nByte,
0, 0);
if( nByte == 0 ){
sqlite3_free(zText);
zText = 0;
}
return zText;
}
#endif /* _WIN32 */
/*
** UTF8 chmod() function for Windows
*/
#if defined(_WIN32) || defined(WIN32)
static int fileio_chmod(const char *zPath, int pmode){
int rc;
wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath);
wchar_t *b1 = winUtf8To16(zPath);
if( b1==0 ) return -1;
rc = _wchmod(b1, pmode);
sqlite3_free(b1);
@@ -145,7 +199,7 @@ static int fileio_chmod(const char *zPath, int pmode){
#if defined(_WIN32) || defined(WIN32)
static int fileio_mkdir(const char *zPath){
int rc;
wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath);
wchar_t *b1 = winUtf8To16(zPath);
if( b1==0 ) return -1;
rc = _wmkdir(b1);
sqlite3_free(b1);
@@ -272,7 +326,7 @@ static int fileStat(
){
#if defined(_WIN32)
int rc;
wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath);
wchar_t *b1 = winUtf8To16(zPath);
if( b1==0 ) return 1;
rc = _wstat(b1, pStatBuf);
if( rc==0 ){
@@ -425,14 +479,13 @@ static int writeFile(
LONGLONG intervals;
HANDLE hFile;
LPWSTR zUnicodeName;
extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*);
GetSystemTime(&currentTime);
SystemTimeToFileTime(&currentTime, &lastAccess);
intervals = (mtime*10000000) + 116444736000000000;
lastWrite.dwLowDateTime = (DWORD)intervals;
lastWrite.dwHighDateTime = intervals >> 32;
zUnicodeName = sqlite3_win32_utf8_to_unicode(zFile);
zUnicodeName = winUtf8To16(zFile);
if( zUnicodeName==0 ){
return 1;
}
@@ -1090,12 +1143,12 @@ static char *portable_realpath(const char *zPath){
if( zPath==0 ) return 0;
zPath16 = sqlite3_win32_utf8_to_unicode(zPath);
zPath16 = winUtf8To16(zPath);
if( zPath16==0 ) return 0;
z = _wfullpath(NULL, zPath16, 0);
sqlite3_free(zPath16);
if( z ){
zOut = sqlite3_win32_unicode_to_utf8(z);
zOut = winUtf16To8(z);
free(z);
}
return zOut;
@@ -1112,6 +1165,9 @@ static char *portable_realpath(const char *zPath){
** The file or directory X is not required to exist. The answer is formed
** by calling system realpath() on the prefix of X that does exist and
** appending the tail of X that does not (yet) exist.
**
** FIXME: This routine sometimes returns NULL rather than raising
** an SQLITE_NOMEM error if an OOM is encountered.
*/
static void realpathFunc(
sqlite3_context *context,
@@ -1134,6 +1190,7 @@ static void realpathFunc(
if( zPath==0 ) return;
if( zPath[0]==0 ) zPath = ".";
zCopy = sqlite3_mprintf("%s",zPath);
if( zCopy==0 ) return;
len = strlen(zCopy);
while( len>1 && (zCopy[len-1]=='/' || (isWin && zCopy[len-1]=='\\')) ){
len--;
+42 -17
View File
@@ -29,6 +29,17 @@
** RBU does not use this extension directly. Rather, this extension is
** provided as a convenience to developers who want to analyze RBU files
** that contain deltas.
**
** Typical build commands assuming
**
** DIR=ext/misc
* NAME=fossildelta
**
** First run "make sqlite3ext.h" then:
**
** linux: gcc -shared -I. -fPIC -o $NAME.so $DIR/$NAME.c
** OS-X: gcc -dynamiclib -fPIC -I. -o $NAME.dylib $DIR/$NAME.c
** Win11: cl -I. $DIR/$NAME.c -link -dll -out:$NAME.dll
*/
#include <string.h>
#include <assert.h>
@@ -43,6 +54,7 @@ SQLITE_EXTENSION_INIT1
*/
typedef unsigned int u32;
typedef sqlite3_uint64 u64;
typedef sqlite3_int64 i64;
/*
** Must be a 16-bit value
@@ -167,16 +179,26 @@ static unsigned int deltaGetInt(const char **pz, int *pLen){
25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, 36,
-1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, -1, -1, -1, 63, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
};
unsigned int v = 0;
int c;
unsigned char *z = (unsigned char*)*pz;
unsigned char *zStart = z;
while( (c = zValue[0x7f&*(z++)])>=0 ){
v = (v<<6) + c;
unsigned char *zEnd = z + (*pLen);
while( z<zEnd && (c = zValue[*z])>=0 ){
v = (v<<6) + c;
z++;
}
z--;
*pLen -= z - zStart;
*pLen -= (int)(z - (unsigned char*)*pz);
*pz = (char*)z;
return v;
}
@@ -266,7 +288,8 @@ static unsigned int checksum(const char *zIn, size_t N){
** The delta string will be NUL-terminated, but it might also contain
** embedded NUL characters if either the zSrc or zOut files are
** binary. This function returns the length of the delta string
** in bytes, excluding the final NUL terminator character.
** in bytes, excluding the final NUL terminator character. It returns
** 0 if an OOM occurs.
**
** Output Format:
**
@@ -358,6 +381,7 @@ static int delta_create(
*/
nHash = lenSrc/NHASH;
collide = sqlite3_malloc64( (sqlite3_int64)nHash*2*sizeof(int) );
if( collide==0 ) return 0;
memset(collide, -1, nHash*2*sizeof(int));
landmark = &collide[nHash];
for(i=0; i<lenSrc-NHASH; i+=NHASH){
@@ -508,7 +532,7 @@ static int delta_create(
static int delta_output_size(const char *zDelta, int lenDelta){
int size;
size = deltaGetInt(&zDelta, &lenDelta);
if( *zDelta!='\n' ){
if( lenDelta<=0 || *zDelta!='\n' ){
/* ERROR: size integer not terminated by "\n" */
return -1;
}
@@ -550,7 +574,7 @@ static int delta_apply(
#endif
limit = deltaGetInt(&zDelta, &lenDelta);
if( *zDelta!='\n' ){
if( lenDelta<=0 || *zDelta!='\n' ){
/* ERROR: size integer not terminated by "\n" */
return -1;
}
@@ -558,6 +582,7 @@ static int delta_apply(
while( *zDelta && lenDelta>0 ){
unsigned int cnt, ofst;
cnt = deltaGetInt(&zDelta, &lenDelta);
if( lenDelta<=0 ) return -1;
switch( zDelta[0] ){
case '@': {
zDelta++; lenDelta--;
@@ -769,11 +794,11 @@ struct deltaparsevtab_vtab {
struct deltaparsevtab_cursor {
sqlite3_vtab_cursor base; /* Base class - must be first */
char *aDelta; /* The delta being parsed */
int nDelta; /* Number of bytes in the delta */
int iCursor; /* Current cursor location */
i64 iCursor; /* Current cursor location */
i64 iNext; /* Next cursor value */
i64 nDelta; /* Number of bytes in the delta */
int eOp; /* Name of current operator */
unsigned int a1, a2; /* Arguments to current operator */
int iNext; /* Next cursor value */
};
/* Operator names:
@@ -877,7 +902,7 @@ static int deltaparsevtabNext(sqlite3_vtab_cursor *cur){
}
z = pCur->aDelta + pCur->iCursor;
pCur->a1 = deltaGetInt(&z, &i);
switch( z[0] ){
switch( i>0 ? z[0] : 0 ){
case '@': {
z++;
if( pCur->iNext>=pCur->nDelta ){
@@ -887,14 +912,14 @@ static int deltaparsevtabNext(sqlite3_vtab_cursor *cur){
}
pCur->a2 = deltaGetInt(&z, &i);
pCur->eOp = DELTAPARSE_OP_COPY;
pCur->iNext = (int)(&z[1] - pCur->aDelta);
pCur->iNext = (i64)(&z[1] - pCur->aDelta);
break;
}
case ':': {
z++;
pCur->a2 = (unsigned int)(z - pCur->aDelta);
pCur->eOp = DELTAPARSE_OP_INSERT;
pCur->iNext = (int)(&z[pCur->a1] - pCur->aDelta);
pCur->iNext = (i64)(&z[pCur->a1] - pCur->aDelta);
break;
}
case ';': {
@@ -938,7 +963,7 @@ static int deltaparsevtabColumn(
if( pCur->eOp==DELTAPARSE_OP_COPY ){
sqlite3_result_int(ctx, pCur->a2);
}else if( pCur->eOp==DELTAPARSE_OP_INSERT ){
if( pCur->a2 + pCur->a1 > pCur->nDelta ){
if( (i64)pCur->a2 + (i64)pCur->a1 > pCur->nDelta ){
sqlite3_result_zeroblob(ctx, pCur->a1);
}else{
sqlite3_result_blob(ctx, pCur->aDelta+pCur->a2, pCur->a1,
@@ -1007,14 +1032,14 @@ static int deltaparsevtabFilter(
a = pCur->aDelta;
pCur->eOp = DELTAPARSE_OP_SIZE;
pCur->a1 = deltaGetInt(&a, &i);
if( a[0]!='\n' ){
if( i<=0 || a[0]!='\n' ){
pCur->eOp = DELTAPARSE_OP_ERROR;
pCur->a1 = pCur->a2 = 0;
pCur->iNext = pCur->nDelta;
return SQLITE_OK;
}
a++;
pCur->iNext = (unsigned int)(a - pCur->aDelta);
pCur->iNext = (i64)(a - pCur->aDelta);
return SQLITE_OK;
}
+37 -37
View File
@@ -340,45 +340,45 @@ static void percentSort(double *a, unsigned int n){
int iGt; /* Entries at or after a[iGt] are greater than rPivot */
int i; /* Loop counter */
double rPivot; /* The pivot value */
assert( n>=2 );
if( a[0]>a[n-1] ){
SWAP_DOUBLE(a[0],a[n-1])
}
if( n==2 ) return;
iGt = n-1;
i = n/2;
if( a[0]>a[i] ){
SWAP_DOUBLE(a[0],a[i])
}else if( a[i]>a[iGt] ){
SWAP_DOUBLE(a[i],a[iGt])
}
if( n==3 ) return;
rPivot = a[i];
iLt = i = 1;
do{
if( a[i]<rPivot ){
if( i>iLt ) SWAP_DOUBLE(a[i],a[iLt])
iLt++;
i++;
}else if( a[i]>rPivot ){
do{
iGt--;
}while( iGt>i && a[iGt]>rPivot );
SWAP_DOUBLE(a[i],a[iGt])
}else{
i++;
while( n>=2 ){
if( a[0]>a[n-1] ){
SWAP_DOUBLE(a[0],a[n-1])
}
if( n==2 ) return;
iGt = n-1;
i = n/2;
if( a[0]>a[i] ){
SWAP_DOUBLE(a[0],a[i])
}else if( a[i]>a[iGt] ){
SWAP_DOUBLE(a[i],a[iGt])
}
if( n==3 ) return;
rPivot = a[i];
iLt = i = 1;
do{
if( a[i]<rPivot ){
if( i>iLt ) SWAP_DOUBLE(a[i],a[iLt])
iLt++;
i++;
}else if( a[i]>rPivot ){
do{
iGt--;
}while( iGt>i && a[iGt]>rPivot );
SWAP_DOUBLE(a[i],a[iGt])
}else{
i++;
}
}while( i<iGt );
if( iLt>n/2 ){
if( n-iGt>=2 ) percentSort(a+iGt, n-iGt);
n = iLt;
}else{
if( iLt>=2 ) percentSort(a, iLt);
a += iGt;
n -= iGt;
}
}while( i<iGt );
if( iLt>=2 ) percentSort(a, iLt);
if( n-iGt>=2 ) percentSort(a+iGt, n-iGt);
/* Uncomment for testing */
#if 0
for(i=0; i<n-1; i++){
assert( a[i]<=a[i+1] );
}
#endif
}
+5 -1
View File
@@ -289,10 +289,14 @@ static void prefixLengthFunc(
int nL = sqlite3_value_bytes(apVal[0]);
int nR = sqlite3_value_bytes(apVal[1]);
int i;
if( zL==0 || zR==0 ){
sqlite3_result_int(ctx, 0);
return;
}
nByte = (nL > nR ? nL : nR);
for(i=0; i<nByte; i++){
if( zL[i]!=zR[i] ) break;
if( zL[i]==0 || zL[i]!=zR[i] ) break;
if( (zL[i] & 0xC0)!=0x80 ) nRet++;
}
+1 -1
View File
@@ -363,7 +363,7 @@ static int qpvtabBestIndex(
}
sqlite3_str_appendf(pStr,"aConstraint,%d,%s,%d,%d,",
i,
azColname[iCol],
iCol>=0 ? azColname[iCol] : "rowid",
op,
pIdxInfo->aConstraint[i].usable);
pVal = 0;
+7 -8
View File
@@ -865,7 +865,6 @@ static void re_bytecode_func(
return;
}
pStr = sqlite3_str_new(0);
if( pStr==0 ) goto re_bytecode_func_err;
if( pRe->nInit>0 ){
sqlite3_str_appendf(pStr, "INIT ");
for(i=0; i<pRe->nInit; i++){
@@ -877,15 +876,15 @@ static void re_bytecode_func(
sqlite3_str_appendf(pStr, "%-8s %4d\n",
ReOpName[(unsigned char)pRe->aOp[i]], pRe->aArg[i]);
}
if( sqlite3_str_errcode(pStr)==SQLITE_NOMEM ){
sqlite3_str_finish(pStr);
re_free(pRe);
sqlite3_result_error_nomem(context);
return;
}
n = sqlite3_str_length(pStr);
z = sqlite3_str_finish(pStr);
if( n==0 ){
sqlite3_free(z);
}else{
sqlite3_result_text(context, z, n-1, sqlite3_free);
}
re_bytecode_func_err:
sqlite3_result_text(context, z, n-1, sqlite3_free);
re_free(pRe);
}
-610
View File
@@ -1,610 +0,0 @@
/*
** 2016-05-05
**
** 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 a utility function (and a utility program) that
** makes a copy of an SQLite database while simultaneously zeroing out all
** deleted content.
**
** Normally (when PRAGMA secure_delete=OFF, which is the default) when SQLite
** deletes content, it does not overwrite the deleted content but rather marks
** the region of the file that held that content as being reusable. This can
** cause deleted content to recoverable from the database file. This stale
** content is removed by the VACUUM command, but VACUUM can be expensive for
** large databases. When in PRAGMA secure_delete=ON mode, the deleted content
** is zeroed, but secure_delete=ON has overhead as well.
**
** This utility attempts to make a copy of a complete SQLite database where
** all of the deleted content is zeroed out in the copy, and it attempts to
** do so while being faster than running VACUUM.
**
** Usage:
**
** int sqlite3_scrub_backup(
** const char *zSourceFile, // Source database filename
** const char *zDestFile, // Destination database filename
** char **pzErrMsg // Write error message here
** );
**
** Simply call the API above specifying the filename of the source database
** and the name of the backup copy. The source database must already exist
** and can be in active use. (A read lock is held during the backup.) The
** destination file should not previously exist. If the pzErrMsg parameter
** is non-NULL and if an error occurs, then an error message might be written
** into memory obtained from sqlite3_malloc() and *pzErrMsg made to point to
** that error message. But if the error is an OOM, the error might not be
** reported. The routine always returns non-zero if there is an error.
**
** If compiled with -DSCRUB_STANDALONE then a main() procedure is added and
** this file becomes a standalone program that can be run as follows:
**
** ./sqlite3scrub SOURCE DEST
*/
#include "sqlite3.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
typedef struct ScrubState ScrubState;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
/* State information for a scrub-and-backup operation */
struct ScrubState {
const char *zSrcFile; /* Name of the source file */
const char *zDestFile; /* Name of the destination file */
int rcErr; /* Error code */
char *zErr; /* Error message text */
sqlite3 *dbSrc; /* Source database connection */
sqlite3_file *pSrc; /* Source file handle */
sqlite3 *dbDest; /* Destination database connection */
sqlite3_file *pDest; /* Destination file handle */
u32 szPage; /* Page size */
u32 szUsable; /* Usable bytes on each page */
u32 nPage; /* Number of pages */
u32 iLastPage; /* Page number of last page written so far*/
u8 *page1; /* Content of page 1 */
};
/* Store an error message */
static void scrubBackupErr(ScrubState *p, const char *zFormat, ...){
va_list ap;
sqlite3_free(p->zErr);
va_start(ap, zFormat);
p->zErr = sqlite3_vmprintf(zFormat, ap);
va_end(ap);
if( p->rcErr==0 ) p->rcErr = SQLITE_ERROR;
}
/* Allocate memory to hold a single page of content */
static u8 *scrubBackupAllocPage(ScrubState *p){
u8 *pPage;
if( p->rcErr ) return 0;
pPage = sqlite3_malloc64( p->szPage );
if( pPage==0 ) p->rcErr = SQLITE_NOMEM;
return pPage;
}
/* Read a page from the source database into memory. Use the memory
** provided by pBuf if not NULL or allocate a new page if pBuf==NULL.
*/
static u8 *scrubBackupRead(ScrubState *p, int pgno, u8 *pBuf){
int rc;
sqlite3_int64 iOff;
u8 *pOut = pBuf;
if( p->rcErr ) return 0;
if( pOut==0 ){
pOut = scrubBackupAllocPage(p);
if( pOut==0 ) return 0;
}
iOff = (pgno-1)*(sqlite3_int64)p->szPage;
rc = p->pSrc->pMethods->xRead(p->pSrc, pOut, p->szPage, iOff);
if( rc!=SQLITE_OK ){
if( pBuf==0 ) sqlite3_free(pOut);
pOut = 0;
scrubBackupErr(p, "read failed for page %d", pgno);
p->rcErr = SQLITE_IOERR;
}
return pOut;
}
/* Write a page to the destination database */
static void scrubBackupWrite(ScrubState *p, int pgno, const u8 *pData){
int rc;
sqlite3_int64 iOff;
if( p->rcErr ) return;
iOff = (pgno-1)*(sqlite3_int64)p->szPage;
rc = p->pDest->pMethods->xWrite(p->pDest, pData, p->szPage, iOff);
if( rc!=SQLITE_OK ){
scrubBackupErr(p, "write failed for page %d", pgno);
p->rcErr = SQLITE_IOERR;
}
if( (u32)pgno>p->iLastPage ) p->iLastPage = pgno;
}
/* Prepare a statement against the "db" database. */
static sqlite3_stmt *scrubBackupPrepare(
ScrubState *p, /* Backup context */
sqlite3 *db, /* Database to prepare against */
const char *zSql /* SQL statement */
){
sqlite3_stmt *pStmt;
if( p->rcErr ) return 0;
p->rcErr = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
if( p->rcErr ){
scrubBackupErr(p, "SQL error \"%s\" on \"%s\"",
sqlite3_errmsg(db), zSql);
sqlite3_finalize(pStmt);
return 0;
}
return pStmt;
}
/* Open the source database file */
static void scrubBackupOpenSrc(ScrubState *p){
sqlite3_stmt *pStmt;
int rc;
/* Open the source database file */
p->rcErr = sqlite3_open_v2(p->zSrcFile, &p->dbSrc,
SQLITE_OPEN_READWRITE |
SQLITE_OPEN_URI | SQLITE_OPEN_PRIVATECACHE, 0);
if( p->rcErr ){
scrubBackupErr(p, "cannot open source database: %s",
sqlite3_errmsg(p->dbSrc));
return;
}
p->rcErr = sqlite3_exec(p->dbSrc, "SELECT 1 FROM sqlite_schema; BEGIN;",
0, 0, 0);
if( p->rcErr ){
scrubBackupErr(p,
"cannot start a read transaction on the source database: %s",
sqlite3_errmsg(p->dbSrc));
return;
}
rc = sqlite3_wal_checkpoint_v2(p->dbSrc, "main", SQLITE_CHECKPOINT_FULL,
0, 0);
if( rc ){
scrubBackupErr(p, "cannot checkpoint the source database");
return;
}
pStmt = scrubBackupPrepare(p, p->dbSrc, "PRAGMA page_size");
if( pStmt==0 ) return;
rc = sqlite3_step(pStmt);
if( rc==SQLITE_ROW ){
p->szPage = sqlite3_column_int(pStmt, 0);
}else{
scrubBackupErr(p, "unable to determine the page size");
}
sqlite3_finalize(pStmt);
if( p->rcErr ) return;
pStmt = scrubBackupPrepare(p, p->dbSrc, "PRAGMA page_count");
if( pStmt==0 ) return;
rc = sqlite3_step(pStmt);
if( rc==SQLITE_ROW ){
p->nPage = sqlite3_column_int(pStmt, 0);
}else{
scrubBackupErr(p, "unable to determine the size of the source database");
}
sqlite3_finalize(pStmt);
sqlite3_file_control(p->dbSrc, "main", SQLITE_FCNTL_FILE_POINTER, &p->pSrc);
if( p->pSrc==0 || p->pSrc->pMethods==0 ){
scrubBackupErr(p, "cannot get the source file handle");
p->rcErr = SQLITE_ERROR;
}
}
/* Create and open the destination file */
static void scrubBackupOpenDest(ScrubState *p){
sqlite3_stmt *pStmt;
int rc;
char *zSql;
if( p->rcErr ) return;
p->rcErr = sqlite3_open_v2(p->zDestFile, &p->dbDest,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
SQLITE_OPEN_URI | SQLITE_OPEN_PRIVATECACHE, 0);
if( p->rcErr ){
scrubBackupErr(p, "cannot open destination database: %s",
sqlite3_errmsg(p->dbDest));
return;
}
zSql = sqlite3_mprintf("PRAGMA page_size(%u);", p->szPage);
if( zSql==0 ){
p->rcErr = SQLITE_NOMEM;
return;
}
p->rcErr = sqlite3_exec(p->dbDest, zSql, 0, 0, 0);
sqlite3_free(zSql);
if( p->rcErr ){
scrubBackupErr(p,
"cannot set the page size on the destination database: %s",
sqlite3_errmsg(p->dbDest));
return;
}
sqlite3_exec(p->dbDest, "PRAGMA journal_mode=OFF;", 0, 0, 0);
p->rcErr = sqlite3_exec(p->dbDest, "BEGIN EXCLUSIVE;", 0, 0, 0);
if( p->rcErr ){
scrubBackupErr(p,
"cannot start a write transaction on the destination database: %s",
sqlite3_errmsg(p->dbDest));
return;
}
pStmt = scrubBackupPrepare(p, p->dbDest, "PRAGMA page_count;");
if( pStmt==0 ) return;
rc = sqlite3_step(pStmt);
if( rc!=SQLITE_ROW ){
scrubBackupErr(p, "cannot measure the size of the destination");
}else if( sqlite3_column_int(pStmt, 0)>1 ){
scrubBackupErr(p, "destination database is not empty - holds %d pages",
sqlite3_column_int(pStmt, 0));
}
sqlite3_finalize(pStmt);
sqlite3_file_control(p->dbDest, "main", SQLITE_FCNTL_FILE_POINTER, &p->pDest);
if( p->pDest==0 || p->pDest->pMethods==0 ){
scrubBackupErr(p, "cannot get the destination file handle");
p->rcErr = SQLITE_ERROR;
}
}
/* Read a 32-bit big-endian integer */
static u32 scrubBackupInt32(const u8 *a){
u32 v = a[3];
v += ((u32)a[2])<<8;
v += ((u32)a[1])<<16;
v += ((u32)a[0])<<24;
return v;
}
/* Read a 16-bit big-endian integer */
static u32 scrubBackupInt16(const u8 *a){
return (a[0]<<8) + a[1];
}
/*
** Read a varint. Put the value in *pVal and return the number of bytes.
*/
static int scrubBackupVarint(const u8 *z, sqlite3_int64 *pVal){
sqlite3_int64 v = 0;
int i;
for(i=0; i<8; i++){
v = (v<<7) + (z[i]&0x7f);
if( (z[i]&0x80)==0 ){ *pVal = v; return i+1; }
}
v = (v<<8) + (z[i]&0xff);
*pVal = v;
return 9;
}
/*
** Return the number of bytes in a varint.
*/
static int scrubBackupVarintSize(const u8 *z){
int i;
for(i=0; i<8; i++){
if( (z[i]&0x80)==0 ){ return i+1; }
}
return 9;
}
/*
** Copy the freelist trunk page given, and all its descendents,
** zeroing out as much as possible in the process.
*/
static void scrubBackupFreelist(ScrubState *p, int pgno, u32 nFree){
u8 *a, *aBuf;
u32 n, mx;
if( p->rcErr ) return;
aBuf = scrubBackupAllocPage(p);
if( aBuf==0 ) return;
while( pgno && nFree){
a = scrubBackupRead(p, pgno, aBuf);
if( a==0 ) break;
n = scrubBackupInt32(&a[4]);
mx = p->szUsable/4 - 2;
if( n<mx ){
memset(&a[n*4+8], 0, 4*(mx-n));
}
scrubBackupWrite(p, pgno, a);
pgno = scrubBackupInt32(a);
#if 0
/* There is really no point in copying the freelist leaf pages.
** Simply leave them uninitialized in the destination database. The
** OS filesystem should zero those pages for us automatically.
*/
for(i=0; i<n && nFree; i++){
u32 iLeaf = scrubBackupInt32(&a[i*4+8]);
if( aZero==0 ){
aZero = scrubBackupAllocPage(p);
if( aZero==0 ){ pgno = 0; break; }
memset(aZero, 0, p->szPage);
}
scrubBackupWrite(p, iLeaf, aZero);
nFree--;
}
#endif
}
sqlite3_free(aBuf);
}
/*
** Copy an overflow chain from source to destination. Zero out any
** unused tail at the end of the overflow chain.
*/
static void scrubBackupOverflow(ScrubState *p, int pgno, u32 nByte){
u8 *a, *aBuf;
aBuf = scrubBackupAllocPage(p);
if( aBuf==0 ) return;
while( nByte>0 && pgno!=0 ){
a = scrubBackupRead(p, pgno, aBuf);
if( a==0 ) break;
if( nByte >= (p->szUsable)-4 ){
nByte -= (p->szUsable) - 4;
}else{
u32 x = (p->szUsable - 4) - nByte;
u32 i = p->szUsable - x;
memset(&a[i], 0, x);
nByte = 0;
}
scrubBackupWrite(p, pgno, a);
pgno = scrubBackupInt32(a);
}
sqlite3_free(aBuf);
}
/*
** Copy B-Tree page pgno, and all of its children, from source to destination.
** Zero out deleted content during the copy.
*/
static void scrubBackupBtree(ScrubState *p, int pgno, int iDepth){
u8 *a;
u32 i, n, pc;
u32 nCell;
u32 nPrefix;
u32 szHdr;
u32 iChild;
u8 *aTop;
u8 *aCell;
u32 x, y;
int ln = 0;
if( p->rcErr ) return;
if( iDepth>50 ){
scrubBackupErr(p, "corrupt: b-tree too deep at page %d", pgno);
return;
}
if( pgno==1 ){
a = p->page1;
}else{
a = scrubBackupRead(p, pgno, 0);
if( a==0 ) return;
}
nPrefix = pgno==1 ? 100 : 0;
aTop = &a[nPrefix];
szHdr = 8 + 4*(aTop[0]==0x02 || aTop[0]==0x05);
aCell = aTop + szHdr;
nCell = scrubBackupInt16(&aTop[3]);
/* Zero out the gap between the cell index and the start of the
** cell content area */
x = scrubBackupInt16(&aTop[5]); /* First byte of cell content area */
if( x>p->szUsable ){ ln=__LINE__; goto btree_corrupt; }
y = szHdr + nPrefix + nCell*2;
if( y>x ){ ln=__LINE__; goto btree_corrupt; }
if( y<x ) memset(a+y, 0, x-y); /* Zero the gap */
/* Zero out all the free blocks */
pc = scrubBackupInt16(&aTop[1]);
if( pc>0 && pc<x ){ ln=__LINE__; goto btree_corrupt; }
while( pc ){
if( pc>(p->szUsable)-4 ){ ln=__LINE__; goto btree_corrupt; }
n = scrubBackupInt16(&a[pc+2]);
if( pc+n>(p->szUsable) ){ ln=__LINE__; goto btree_corrupt; }
if( n>4 ) memset(&a[pc+4], 0, n-4);
x = scrubBackupInt16(&a[pc]);
if( x<pc+4 && x>0 ){ ln=__LINE__; goto btree_corrupt; }
pc = x;
}
/* Write this one page */
scrubBackupWrite(p, pgno, a);
/* Walk the tree and process child pages */
for(i=0; i<nCell; i++){
u32 X, M, K, nLocal;
sqlite3_int64 P;
pc = scrubBackupInt16(&aCell[i*2]);
if( pc <= szHdr ){ ln=__LINE__; goto btree_corrupt; }
if( pc > p->szUsable-3 ){ ln=__LINE__; goto btree_corrupt; }
if( aTop[0]==0x05 || aTop[0]==0x02 ){
if( pc+4 > p->szUsable ){ ln=__LINE__; goto btree_corrupt; }
iChild = scrubBackupInt32(&a[pc]);
pc += 4;
scrubBackupBtree(p, iChild, iDepth+1);
if( aTop[0]==0x05 ) continue;
}
pc += scrubBackupVarint(&a[pc], &P);
if( pc >= p->szUsable ){ ln=__LINE__; goto btree_corrupt; }
if( aTop[0]==0x0d ){
X = p->szUsable - 35;
}else{
X = ((p->szUsable - 12)*64/255) - 23;
}
if( P<=X ){
/* All content is local. No overflow */
continue;
}
M = ((p->szUsable - 12)*32/255)-23;
K = M + ((P-M)%(p->szUsable-4));
if( aTop[0]==0x0d ){
pc += scrubBackupVarintSize(&a[pc]);
if( pc > (p->szUsable-4) ){ ln=__LINE__; goto btree_corrupt; }
}
nLocal = K<=X ? K : M;
if( pc+nLocal > p->szUsable-4 ){ ln=__LINE__; goto btree_corrupt; }
iChild = scrubBackupInt32(&a[pc+nLocal]);
scrubBackupOverflow(p, iChild, (u32)(P-nLocal));
}
/* Walk the right-most tree */
if( aTop[0]==0x05 || aTop[0]==0x02 ){
iChild = scrubBackupInt32(&aTop[8]);
scrubBackupBtree(p, iChild, iDepth+1);
}
/* All done */
if( pgno>1 ) sqlite3_free(a);
return;
btree_corrupt:
scrubBackupErr(p, "corruption on page %d of source database (errid=%d)",
pgno, ln);
if( pgno>1 ) sqlite3_free(a);
}
/*
** Copy all ptrmap pages from source to destination.
** This routine is only called if the source database is in autovacuum
** or incremental vacuum mode.
*/
static void scrubBackupPtrmap(ScrubState *p){
u32 pgno = 2;
u32 J = p->szUsable/5;
u32 iLock = (1073742335/p->szPage)+1;
u8 *a, *pBuf;
if( p->rcErr ) return;
pBuf = scrubBackupAllocPage(p);
if( pBuf==0 ) return;
while( pgno<=p->nPage ){
a = scrubBackupRead(p, pgno, pBuf);
if( a==0 ) break;
scrubBackupWrite(p, pgno, a);
pgno += J+1;
if( pgno==iLock ) pgno++;
}
sqlite3_free(pBuf);
}
int sqlite3_scrub_backup(
const char *zSrcFile, /* Source file */
const char *zDestFile, /* Destination file */
char **pzErr /* Write error here if non-NULL */
){
ScrubState s;
u32 n, i;
sqlite3_stmt *pStmt;
memset(&s, 0, sizeof(s));
s.zSrcFile = zSrcFile;
s.zDestFile = zDestFile;
/* Open both source and destination databases */
scrubBackupOpenSrc(&s);
scrubBackupOpenDest(&s);
/* Read in page 1 */
s.page1 = scrubBackupRead(&s, 1, 0);
if( s.page1==0 ) goto scrub_abort;
s.szUsable = s.szPage - s.page1[20];
/* Copy the freelist */
n = scrubBackupInt32(&s.page1[36]);
i = scrubBackupInt32(&s.page1[32]);
if( n ) scrubBackupFreelist(&s, i, n);
/* Copy ptrmap pages */
n = scrubBackupInt32(&s.page1[52]);
if( n ) scrubBackupPtrmap(&s);
/* Copy all of the btrees */
scrubBackupBtree(&s, 1, 0);
pStmt = scrubBackupPrepare(&s, s.dbSrc,
"SELECT rootpage FROM sqlite_schema WHERE coalesce(rootpage,0)>0");
if( pStmt==0 ) goto scrub_abort;
while( sqlite3_step(pStmt)==SQLITE_ROW ){
i = (u32)sqlite3_column_int(pStmt, 0);
scrubBackupBtree(&s, i, 0);
}
sqlite3_finalize(pStmt);
/* If the last page of the input db file is a free-list leaf, then the
** backup file on disk is still smaller than the size indicated within
** the database header. In this case, write a page of zeroes to the
** last page of the backup database so that SQLite does not mistakenly
** think the db is corrupt. */
if( s.iLastPage<s.nPage ){
u8 *aZero = scrubBackupAllocPage(&s);
if( aZero ){
memset(aZero, 0, s.szPage);
scrubBackupWrite(&s, s.nPage, aZero);
sqlite3_free(aZero);
}
}
scrub_abort:
/* Close the destination database without closing the transaction. If we
** commit, page zero will be overwritten. */
sqlite3_close(s.dbDest);
/* But do close out the read-transaction on the source database */
sqlite3_exec(s.dbSrc, "COMMIT;", 0, 0, 0);
sqlite3_close(s.dbSrc);
sqlite3_free(s.page1);
if( pzErr ){
*pzErr = s.zErr;
}else{
sqlite3_free(s.zErr);
}
return s.rcErr;
}
#ifdef SCRUB_STANDALONE
/* Error and warning log */
static void errorLogCallback(void *pNotUsed, int iErr, const char *zMsg){
const char *zType;
switch( iErr&0xff ){
case SQLITE_WARNING: zType = "WARNING"; break;
case SQLITE_NOTICE: zType = "NOTICE"; break;
default: zType = "ERROR"; break;
}
fprintf(stderr, "%s: %s\n", zType, zMsg);
}
/* The main() routine when this utility is run as a stand-alone program */
int main(int argc, char **argv){
char *zErr = 0;
int rc;
if( argc!=3 ){
fprintf(stderr,"Usage: %s SOURCE DESTINATION\n", argv[0]);
exit(1);
}
sqlite3_config(SQLITE_CONFIG_LOG, errorLogCallback, 0);
rc = sqlite3_scrub_backup(argv[1], argv[2], &zErr);
if( rc==SQLITE_NOMEM ){
fprintf(stderr, "%s: out of memory\n", argv[0]);
exit(1);
}
if( zErr ){
fprintf(stderr, "%s: %s\n", argv[0], zErr);
sqlite3_free(zErr);
exit(1);
}
return 0;
}
#endif
+29 -11
View File
@@ -400,6 +400,16 @@ static double seriesFloor(double r){
}
#endif
/* Convert a floating point value to its closest integer. Do so in
** a way that avoids 'outside the range of representable values' warnings
** from UBSAN.
*/
sqlite3_int64 seriesRealToI64(double r){
if( r<-9223372036854774784.0 ) return SMALLEST_INT64;
if( r>+9223372036854774784.0 ) return LARGEST_INT64;
return (sqlite3_int64)r;
}
/*
** This method is called to "rewind" the series_cursor object back
** to the first row of output. This method is always called at least
@@ -522,7 +532,7 @@ static int seriesFilter(
&& r>=(double)SMALLEST_INT64
&& r<=(double)LARGEST_INT64
){
iMin = iMax = (sqlite3_int64)r;
iMin = iMax = seriesRealToI64(r);
}else{
goto series_no_rows;
}
@@ -530,15 +540,19 @@ static int seriesFilter(
iMin = iMax = sqlite3_value_int64(argv[iArg++]);
}
}else{
if( idxNum & 0x0300 ){ /* value>X or value>=X */
if( idxNum & 0x0300 ){ /* value>X (0x200) or value>=X (0x100) */
if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){
double r = sqlite3_value_double(argv[iArg++]);
if( r<(double)SMALLEST_INT64 ){
if( r<=(double)SMALLEST_INT64 ){
iMin = SMALLEST_INT64;
}else if( (idxNum & 0x0200)!=0 && r==seriesCeil(r) ){
iMin = (sqlite3_int64)seriesCeil(r+1.0);
}else if( r>(double)LARGEST_INT64 ){
goto series_no_rows;
}else{
iMin = (sqlite3_int64)seriesCeil(r);
iMin = seriesRealToI64(seriesCeil(r));
if( (idxNum & 0x0200)!=0 && r==seriesCeil(r) ){
if( iMin==LARGEST_INT64 ) goto series_no_rows;
iMin++;
}
}
}else{
iMin = sqlite3_value_int64(argv[iArg++]);
@@ -551,15 +565,19 @@ static int seriesFilter(
}
}
}
if( idxNum & 0x3000 ){ /* value<X or value<=X */
if( idxNum & 0x3000 ){ /* value<X (0x2000) or value<=X (0x1000) */
if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){
double r = sqlite3_value_double(argv[iArg++]);
if( r>(double)LARGEST_INT64 ){
if( r>=(double)LARGEST_INT64 ){
iMax = LARGEST_INT64;
}else if( (idxNum & 0x2000)!=0 && r==seriesFloor(r) ){
iMax = (sqlite3_int64)(r-1.0);
}else if( r<=(double)SMALLEST_INT64 ){
goto series_no_rows;
}else{
iMax = (sqlite3_int64)seriesFloor(r);
iMax = seriesRealToI64(seriesFloor(r));
if( (idxNum & 0x2000)!=0 && r==seriesFloor(r) ){
if( iMax==SMALLEST_INT64 ) goto series_no_rows;
iMax--;
}
}
}else{
iMax = sqlite3_value_int64(argv[iArg++]);
+8 -1
View File
@@ -31,6 +31,10 @@ SQLITE_EXTENSION_INIT1
/******************************************************************************
** The Hash Engine
*/
#if defined(__GNUC__) && __GNUC__>=11
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wstringop-overread"
#endif
/* Context for the SHA1 hash */
typedef struct SHA1Context SHA1Context;
struct SHA1Context {
@@ -171,7 +175,7 @@ static void hash_step(
}else{
i = 0;
}
(void)memcpy(&p->buffer[j], &data[i], len - i);
if( len-i>0 ) (void)memcpy(&p->buffer[j], &data[i], len - i);
}
/* Compute a string using sqlite3_vsnprintf() and hash it */
@@ -226,6 +230,9 @@ static void hash_finish(
zOut[i*2]= 0;
}
}
#if defined(__GNUC__) && __GNUC__>=11
# pragma GCC diagnostic pop
#endif
/* End of the hashing logic
*****************************************************************************/
+21 -11
View File
@@ -32,6 +32,7 @@ SQLITE_EXTENSION_INIT1
# define NEVER(X) 0
typedef unsigned char u8;
typedef unsigned short u16;
typedef sqlite3_int64 i64;
#endif
#include <ctype.h>
@@ -192,7 +193,7 @@ static const unsigned char className[] = ".ABCDHLRMY9 ?";
** Return NULL if memory allocation fails.
*/
static unsigned char *phoneticHash(const unsigned char *zIn, int nIn){
unsigned char *zOut = sqlite3_malloc64( nIn + 1 );
unsigned char *zOut = sqlite3_malloc64( (i64)nIn + 1 );
int i;
int nOut = 0;
char cPrev = 0x77;
@@ -352,6 +353,7 @@ static int substituteCost(char cPrev, char cFrom, char cTo){
** -1 One of the inputs is NULL
** -2 Non-ASCII characters on input
** -3 Unable to allocate memory
** -4 Inputs too large
**
** If pnMatch is not NULL, then *pnMatch is set to the number of bytes
** of zB that matched the pattern in zA. If zA does not end with a '*',
@@ -390,9 +392,11 @@ static int editdist1(const char *zA, const char *zB, int *pnMatch){
for(nA=0; zA[nA]; nA++){
if( zA[nA]&0x80 ) return -2;
}
if( nA>=100000 ) return -4;
for(nB=0; zB[nB]; nB++){
if( zB[nB]&0x80 ) return -2;
}
if( nB>=100000 ) return -4;
/* Special processing if either string is empty */
if( nA==0 ){
@@ -419,7 +423,7 @@ static int editdist1(const char *zA, const char *zB, int *pnMatch){
if( nB<(sizeof(mStack)*4)/(sizeof(mStack[0])*5) ){
m = mStack;
}else{
m = toFree = sqlite3_malloc64( (nB+1)*5*sizeof(m[0])/4 );
m = toFree = sqlite3_malloc64( ((i64)nB+1)*5LL*sizeof(m[0])/4 );
if( m==0 ) return -3;
}
cx = (char*)&m[nB+1];
@@ -527,6 +531,8 @@ static void editdistSqlFunc(
if( res<0 ){
if( res==(-3) ){
sqlite3_result_error_nomem(context);
}else if( res==(-4) ){
sqlite3_result_error_toobig(context);
}else if( res==(-2) ){
sqlite3_result_error(context, "non-ASCII input to editdist()", -1);
}else{
@@ -767,7 +773,7 @@ static int editDist3ConfigLoad(
if( iCost>=10000 ) continue; /* Costs above 10K are considered infinite */
if( pLang==0 || iLang!=iLangPrev ){
EditDist3Lang *pNew;
pNew = sqlite3_realloc64(p->a, (p->nLang+1)*sizeof(p->a[0]));
pNew = sqlite3_realloc64(p->a, ((i64)p->nLang+1)*sizeof(p->a[0]));
if( pNew==0 ){ rc = SQLITE_NOMEM; break; }
p->a = pNew;
pLang = &p->a[p->nLang];
@@ -901,7 +907,7 @@ static EditDist3FromString *editDist3FromStringNew(
if( z==0 ) return 0;
if( n<0 ) n = (int)strlen(z);
pStr = sqlite3_malloc64( sizeof(*pStr) + sizeof(pStr->a[0])*n + n + 1 );
pStr = sqlite3_malloc64( sizeof(*pStr) + sizeof(pStr->a[0])*n + (i64)n + 1 );
if( pStr==0 ) return 0;
pStr->a = (EditDist3From*)&pStr[1];
memset(pStr->a, 0, sizeof(pStr->a[0])*n);
@@ -927,13 +933,13 @@ static EditDist3FromString *editDist3FromStringNew(
if( matchFrom(p, z+i, n-i)==0 ) continue;
if( p->nTo==0 ){
apNew = sqlite3_realloc64(pFrom->apDel,
sizeof(*apNew)*(pFrom->nDel+1));
sizeof(*apNew)*((i64)pFrom->nDel+1));
if( apNew==0 ) break;
pFrom->apDel = apNew;
apNew[pFrom->nDel++] = p;
}else{
apNew = sqlite3_realloc64(pFrom->apSubst,
sizeof(*apNew)*(pFrom->nSubst+1));
sizeof(*apNew)*((i64)pFrom->nSubst+1));
if( apNew==0 ) break;
pFrom->apSubst = apNew;
apNew[pFrom->nSubst++] = p;
@@ -1008,6 +1014,8 @@ static int editDist3Core(
unsigned int stackSpace[SQLITE_SPELLFIX_STACKALLOC_SZ/sizeof(unsigned int)];
/* allocate the Wagner matrix and the aTo[] array for the TO string */
if( n2>10000 ) return -2;
if( f.n>10000 ) return -2;
n = (f.n+1)*(n2+1);
n = (n+1)&~1;
nByte = n*sizeof(m[0]) + sizeof(a2[0])*n2;
@@ -1198,6 +1206,8 @@ static void editDist3SqlFunc(
editDist3FromStringDelete(pFrom);
if( dist==(-1) ){
sqlite3_result_error_nomem(context);
}else if( dist==(-2) ){
sqlite3_result_error_toobig(context);
}else{
sqlite3_result_int(context, dist);
}
@@ -1712,9 +1722,9 @@ static const Transliteration *spellfixFindTranslit(int c, int *pxTop){
*/
static unsigned char *transliterate(const unsigned char *zIn, int nIn){
#ifdef SQLITE_SPELLFIX_5BYTE_MAPPINGS
unsigned char *zOut = sqlite3_malloc64( nIn*5 + 1 );
unsigned char *zOut = sqlite3_malloc64( (i64)nIn*5 + 1 );
#else
unsigned char *zOut = sqlite3_malloc64( nIn*4 + 1 );
unsigned char *zOut = sqlite3_malloc64( (i64)nIn*4 + 1 );
#endif
int c, sz, nOut;
if( zOut==0 ) return 0;
@@ -2057,7 +2067,7 @@ static int spellfix1Init(
int i;
nDbName = (int)strlen(zDbName);
pNew = sqlite3_malloc64( sizeof(*pNew) + nDbName + 1);
pNew = sqlite3_malloc64( sizeof(*pNew) + (i64)nDbName + 1);
if( pNew==0 ){
rc = SQLITE_NOMEM;
}else{
@@ -2461,7 +2471,7 @@ static void spellfix1RunQuery(MatchQuery *p, const char *zQuery, int nQuery){
iDist = editdist1(p->zPattern, zK1, 0);
}
if( iDist<0 ){
p->rc = SQLITE_NOMEM;
p->rc = iDist==(-4) ? SQLITE_TOOBIG : SQLITE_NOMEM;
break;
}
pCur->nSearch++;
@@ -2773,7 +2783,7 @@ static int spellfix1Column(
if( !zTranslit ) return SQLITE_NOMEM;
res = editdist1(pCur->zPattern, zTranslit, &iMatchlen);
sqlite3_free(zTranslit);
if( res<0 ) return SQLITE_NOMEM;
if( res<0 ) return res==(-4) ? SQLITE_TOOBIG : SQLITE_NOMEM;
iMatchlen = translen_to_charlen(zWord, nWord, iMatchlen);
}else{
iMatchlen = utf8Charlen(zWord, nWord);
+1
View File
@@ -120,6 +120,7 @@ static int sqlite3UuidStrToBlob(
unsigned char *aBlob /* Write results here */
){
int i;
if( zStr==0 ) return 1;
if( zStr[0]=='{' ) zStr++;
for(i=0; i<16; i++){
if( zStr[0]=='-' ) zStr++;
+9 -2
View File
@@ -13,6 +13,11 @@
** This file implements a virtual table that returns the whole numbers
** between 1 and 4294967295, inclusive.
**
** TESTING AND DEBUG USE ONLY -> This is not production code. This is
** not a deliverable. Use the generate_series() virtual table for
** real-world applications instead of this extension. THIS EXTENSION
** MAY CONTAIN BUGS.
**
** Example:
**
** CREATE VIRTUAL TABLE nums USING wholenumber;
@@ -154,12 +159,14 @@ static int wholenumberFilter(
pCur->iValue = 1;
pCur->mxValue = 0xffffffff; /* 4294967295 */
if( idxNum & 3 ){
v = sqlite3_value_int64(argv[0]) + (idxNum&1);
v = sqlite3_value_int64(argv[0]);
if( v<=pCur->mxValue && (idxNum&1)!=0 ) v++;
if( v>pCur->iValue && v<=pCur->mxValue ) pCur->iValue = v;
i++;
}
if( idxNum & 12 ){
v = sqlite3_value_int64(argv[i]) - ((idxNum>>2)&1);
v = sqlite3_value_int64(argv[i]);
if( v>0 && ((idxNum>>2)&1)!=0 ) v--;
if( v>=pCur->iValue && v<pCur->mxValue ) pCur->mxValue = v;
}
return SQLITE_OK;
+11 -4
View File
@@ -488,6 +488,7 @@ static void zipfileResetCursor(ZipfileCsr *pCsr){
pNext = p->pNext;
zipfileEntryFree(p);
}
pCsr->pFreeEntry = 0;
}
/*
@@ -890,7 +891,13 @@ static int zipfileGetEntry(
if( rc==SQLITE_OK ){
u32 *pt = &pNew->mUnixTime;
pNew->cds.zFile = sqlite3_mprintf("%.*s", nFile, aRead);
/* aRead[0..nFile-1] might contain embedded \000 characters
** See Bug 2026-05-31T11:43:05Z */
pNew->cds.zFile = sqlite3_malloc64(nFile+1);
if( pNew->cds.zFile!=0 ){
memcpy(pNew->cds.zFile, aRead, nFile);
pNew->cds.zFile[nFile] = 0;
}
pNew->aExtra = (u8*)&pNew[1];
memcpy(pNew->aExtra, &aRead[nFile], nExtra);
if( pNew->cds.zFile==0 ){
@@ -1994,10 +2001,10 @@ struct ZipfileCtx {
};
static int zipfileBufferGrow(ZipfileBuffer *pBuf, i64 nByte){
if( pBuf->n+nByte>pBuf->nAlloc ){
if( (pBuf->nAlloc-pBuf->n)<nByte ){
u8 *aNew;
sqlite3_int64 nNew = pBuf->n ? pBuf->n*2 : 512;
int nReq = pBuf->n + nByte;
i64 nNew = pBuf->n ? (i64)pBuf->n*2 : 512;
i64 nReq = pBuf->n + nByte;
while( nNew<nReq ) nNew = nNew*2;
aNew = sqlite3_realloc64(pBuf->a, nNew);
+50 -31
View File
@@ -252,6 +252,10 @@ static void qrfApproxInt64(sqlite3_str *pOut, i64 N){
sqlite3_str_appendf(pOut, "%4lld ", N);
return;
}
if( N>=9223372036854775800LL ){
sqlite3_str_appendf(pOut, "%.2fE", 1e-18*(double)N);
return;
}
for(i=1; i<=18; i++){
N = (N+5)/10;
if( N<10000 ){
@@ -411,8 +415,8 @@ static void qrfEqpStats(Qrf *p){
sqlite3_str_reset(pStats);
if( nCycle>=0 && nTotal>0 ){
qrfApproxInt64(pStats, nCycle);
sqlite3_str_appendf(pStats, " %3d%%",
((nCycle*100)+nTotal/2) / nTotal
sqlite3_str_appendf(pStats, " %3.0f%%",
((100.0*(double)nCycle)+nTotal/2.0) / (double)nTotal
);
nSp = 2;
}
@@ -428,7 +432,6 @@ static void qrfEqpStats(Qrf *p){
if( nRow>=0 ){
if( nSp ) sqlite3_str_appendchar(pStats, nSp, ' ');
qrfApproxInt64(pStats, nRow);
nSp = 2;
if( p->spec.eStyle==QRF_STYLE_StatsEst ){
sqlite3_str_appendf(pStats, " ");
qrfApproxInt64(pStats, (i64)rEstCum);
@@ -718,19 +721,16 @@ static int qrfDisplayWidth(const char *zIn, sqlite3_int64 nByte, int *pnNL){
}
/*
** Escape the input string if it is needed and in accordance with
** eEsc, which is either QRF_ESC_Ascii or QRF_ESC_Symbol.
** Escape the text starting at byte iStart of pStr, if needed, using the
** escape encoding of eEsc, which is either QRF_ESC_Ascii or QRF_ESC_Symbol.
** The pStr string is modified appropriately.
**
** Escaping is needed if the string contains any control characters
** other than \t, \n, and \r\n
**
** If no escaping is needed (the common case) then set *ppOut to NULL
** and return 0. If escaping is needed, write the escaped string into
** memory obtained from sqlite3_malloc64() and make *ppOut point to that
** memory and return 0. If an error occurs, return non-zero.
**
** The caller is responsible for freeing *ppFree if it is non-NULL in order
** to reclaim memory.
** If no escaping is needed (the common case) then pStr is unchanged.
** If escaping is required, then pStr is expanded and modified to hold
** an escaped representation of the text.
*/
static void qrfEscape(
int eEsc, /* QRF_ESC_Ascii or QRF_ESC_Symbol */
@@ -738,20 +738,22 @@ static void qrfEscape(
int iStart /* Begin escapding on this byte of pStr */
){
sqlite3_int64 i, j; /* Loop counters */
sqlite3_int64 sz; /* Size of the string prior to escaping */
sqlite3_int64 nCtrl = 0;/* Number of control characters to escape */
unsigned char *zIn; /* Text to be escaped */
unsigned int nIn; /* Bytes of text to be escaped */
unsigned char c; /* A single character of the text */
unsigned char *zOut; /* Where to write the results */
/* Find the text to be escaped */
zIn = (unsigned char*)sqlite3_str_value(pStr);
nIn = sqlite3_str_length(pStr);
if( zIn==0 ) return;
zIn += iStart;
nIn -= iStart;
/* Count the control characters */
for(i=0; (c = zIn[i])!=0; i++){
if( c<=0x1f
for(i=0; i<nIn; i++){
if( (c = zIn[i])<=0x1f
&& c!='\t'
&& c!='\n'
&& (c!='\r' || zIn[i+1]!='\n')
@@ -763,18 +765,17 @@ static void qrfEscape(
/* Make space to hold the escapes. Copy the original text to the end
** of the available space. */
sz = sqlite3_str_length(pStr) - iStart;
if( eEsc==QRF_ESC_Symbol ) nCtrl *= 2;
sqlite3_str_appendchar(pStr, nCtrl, ' ');
zOut = (unsigned char*)sqlite3_str_value(pStr);
if( zOut==0 ) return;
zOut += iStart;
zIn = zOut + nCtrl;
memmove(zIn,zOut,sz);
memmove(zIn,zOut,nIn);
/* Convert the control characters */
for(i=j=0; (c = zIn[i])!=0; i++){
if( c>0x1f
for(i=j=0; i<nIn; i++){
if( (c = zIn[i])>0x1f
|| c=='\t'
|| c=='\n'
|| (c=='\r' && zIn[i+1]=='\n')
@@ -786,6 +787,7 @@ static void qrfEscape(
j += i;
}
zIn += i+1;
nIn -= i+1;
i = -1;
if( eEsc==QRF_ESC_Symbol ){
zOut[j++] = 0xe2;
@@ -911,7 +913,7 @@ static void qrfEncodeText(Qrf *p, sqlite3_str *pOut, const char *zTxt){
sqlite3_str_append(pOut, (const char*)z, i);
}
switch( z[i] ){
case '>': sqlite3_str_append(pOut, "&lt;", 4); break;
case '>': sqlite3_str_append(pOut, "&gt;", 4); break;
case '&': sqlite3_str_append(pOut, "&amp;", 5); break;
case '<': sqlite3_str_append(pOut, "&lt;", 4); break;
case '"': sqlite3_str_append(pOut, "&quot;", 6); break;
@@ -1188,8 +1190,22 @@ static void qrfRenderValue(Qrf *p, sqlite3_str *pOut, int iCol){
break;
}
default: {
const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
qrfEncodeText(p, pOut, zTxt);
const void *pBlob = sqlite3_column_blob(p->pStmt,iCol);
int nBlob = sqlite3_column_bytes(p->pStmt,iCol);
int rc;
qrfWrite(p);
if( nBlob==0 ){
/* no-op */
}else if( p->spec.eEsc==QRF_ESC_Off ){
rc = p->spec.xWrite(p->spec.pWriteArg,pBlob,nBlob);
if( rc ){
qrfError(p, rc, "Failed to write %d bytes of BLOB output", nBlob);
}
}else{
sqlite3_str_append(pOut, pBlob, nBlob);
qrfEscape(p->spec.eEsc, pOut, 0);
qrfWrite(p);
}
}
}
break;
@@ -1391,7 +1407,7 @@ static void qrfWrapLine(
for(k=i-1; k>=i/2; k--){
if( qrfSpace(z[k]) ) break;
}
if( k<i/2 ){
if( k<i/2 && i/2>0 ){
for(k=i; k>=i/2; k--){
if( qrfAlnum(z[k-1])!=qrfAlnum(z[k]) && (z[k]&0xc0)!=0x80 ) break;
}
@@ -1642,12 +1658,12 @@ static void qrfBoxLine(sqlite3_str *pOut, int N, int bDbl){
DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24
};/* 0 1 2 3 4 5 6 7 8 9 */
const int nDash = 30;
N *= 3;
while( N>nDash ){
i64 nn = 3*(i64)N;
while( nn>nDash ){
sqlite3_str_append(pOut, azDash[bDbl], nDash);
N -= nDash;
nn -= nDash;
}
sqlite3_str_append(pOut, azDash[bDbl], N);
sqlite3_str_append(pOut, azDash[bDbl], (int)nn);
}
/*
@@ -1720,7 +1736,7 @@ static int *qrfValidLayout(
int i; /* Loop counter */
int nr; /* Number of rows */
int w = 0; /* Width of the current column */
int t; /* Total width of all columns */
i64 t; /* Total width of all columns */
int *aw; /* Array of individual column widths */
aw = sqlite3_malloc64( sizeof(int)*nCol );
@@ -1858,8 +1874,11 @@ static void qrfRestrictScreenWidth(qrfColData *pData, Qrf *p){
if( p->spec.bBorder==QRF_No ) sepW -= 2;
}
nCol = pData->nCol;
for(i=sumW=0; i<nCol; i++) sumW += pData->a[i].w;
if( p->spec.nScreenWidth >= sumW+sepW ) return;
for(i=0, sumW=0; i<nCol; i++){
if( sumW > 2147483647 - pData->a[i].w ) return;
sumW += pData->a[i].w;
}
if( p->spec.nScreenWidth >= (i64)sumW + sepW ) return;
/* First thing to do is reduce the separation between columns */
pData->nMargin = 0;
@@ -2734,7 +2753,7 @@ static void qrfInitialize(
memcpy(&p->spec, pSpec, sz);
if( p->spec.zNull==0 ) p->spec.zNull = "";
p->mxWidth = p->spec.nScreenWidth;
if( p->mxWidth<=0 ) p->mxWidth = QRF_MAX_WIDTH;
if( p->mxWidth<=0 ) p->mxWidth = 2147483647;
p->mxHeight = p->spec.nLineLimit;
if( p->mxHeight<=0 ) p->mxHeight = 2147483647;
if( p->spec.eStyle>QRF_STYLE_Table ) p->spec.eStyle = QRF_Auto;
+1 -2
View File
@@ -70,8 +70,7 @@ int sqlite3_format_query_result(
);
/*
** Range of values for sqlite3_qrf_spec.aWidth[] entries and for
** sqlite3_qrf_spec.mxColWidth and .nScreenWidth
** Range of values for sqlite3_qrf_spec.aWidth[] entries.
*/
#define QRF_MAX_WIDTH 10000
#define QRF_MIN_WIDTH 0
+21 -10
View File
@@ -516,16 +516,26 @@ static unsigned int rbuDeltaGetInt(const char **pz, int *pLen){
25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, 36,
-1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, -1, -1, -1, 63, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
};
unsigned int v = 0;
int c;
unsigned char *z = (unsigned char*)*pz;
unsigned char *zStart = z;
while( (c = zValue[0x7f&*(z++)])>=0 ){
v = (v<<6) + c;
unsigned char *zEnd = z + (*pLen);
while( z<zEnd && (c = zValue[*z])>=0 ){
v = (v<<6) + c;
z++;
}
z--;
*pLen -= (int)(z - zStart);
*pLen -= (int)(z - (unsigned char*)*pz);
*pz = (char*)z;
return v;
}
@@ -601,7 +611,7 @@ static int rbuDeltaApply(
#endif
limit = rbuDeltaGetInt(&zDelta, &lenDelta);
if( *zDelta!='\n' ){
if( lenDelta<=0 || *zDelta!='\n' ){
/* ERROR: size integer not terminated by "\n" */
return -1;
}
@@ -609,11 +619,12 @@ static int rbuDeltaApply(
while( *zDelta && lenDelta>0 ){
unsigned int cnt, ofst;
cnt = rbuDeltaGetInt(&zDelta, &lenDelta);
if( lenDelta<=0 ) return -1;
switch( zDelta[0] ){
case '@': {
zDelta++; lenDelta--;
ofst = rbuDeltaGetInt(&zDelta, &lenDelta);
if( lenDelta>0 && zDelta[0]!=',' ){
if( lenDelta>0 || zDelta[0]!=',' ){
/* ERROR: copy command not terminated by ',' */
return -1;
}
@@ -638,7 +649,7 @@ static int rbuDeltaApply(
/* ERROR: insert command gives an output larger than predicted */
return -1;
}
if( (int)cnt>lenDelta ){
if( (i64)cnt>(i64)lenDelta ){
/* ERROR: insert count exceeds size of delta */
return -1;
}
@@ -676,7 +687,7 @@ static int rbuDeltaApply(
static int rbuDeltaOutputSize(const char *zDelta, int lenDelta){
int size;
size = rbuDeltaGetInt(&zDelta, &lenDelta);
if( *zDelta!='\n' ){
if( lenDelta<=0 || *zDelta!='\n' ){
/* ERROR: size integer not terminated by "\n" */
return -1;
}
@@ -724,7 +735,7 @@ static void rbuFossilDeltaFunc(
return;
}
aOut = sqlite3_malloc(nOut+1);
aOut = sqlite3_malloc64((i64)nOut+1);
if( aOut==0 ){
sqlite3_result_error_nomem(context);
}else{
+40 -16
View File
@@ -532,18 +532,39 @@ static void recoverFinalize(sqlite3_recover *p, sqlite3_stmt *pStmt){
}
}
/*
** Run a single SQL statement in zSql. If zSql contains two or more
** SQL statements separated by ';', only the first is run.
**
** Return the sqlite3_finalizer() or sqlite3_prepare() result code
** from running the zSql statement.
*/
static int recoverOneStmt(sqlite3 *db, const char *zSql){
sqlite3_stmt *pStmt = 0;
int rc;
if( zSql==0 ) return SQLITE_OK;
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
if( rc ){
sqlite3_finalize(pStmt);
return rc;
}
while( SQLITE_ROW==sqlite3_step(pStmt) ){}
return sqlite3_finalize(pStmt);
}
/*
** This function is a no-op if recover handle p already contains an error
** (if p->errCode!=SQLITE_OK). A copy of p->errCode is returned in this
** case.
**
** Otherwise, execute SQL script zSql. If successful, return SQLITE_OK.
** Or, if an error occurs, leave an error code and message in the recover
** handle and return a copy of the error code.
** Otherwise, execute a single SQL statment in zSql. Even if zSql contains
** two or more SQL statements separated by ';', only execute the first one.
** If successful, return SQLITE_OK. Or, if an error occurs, leave an error
** code and message in the recover handle and return a copy of the error code.
*/
static int recoverExec(sqlite3_recover *p, sqlite3 *db, const char *zSql){
if( p->errCode==SQLITE_OK ){
int rc = sqlite3_exec(db, zSql, 0, 0, 0);
int rc = recoverOneStmt(db, zSql);
if( rc ){
recoverDbError(p, db);
}
@@ -943,7 +964,8 @@ static void recoverTransferSettings(sqlite3_recover *p){
}
recoverFinalize(p, p1);
}
recoverExec(p, db2, "CREATE TABLE t1(a); DROP TABLE t1;");
recoverExec(p, db2, "CREATE TABLE t1(a)");
recoverExec(p, db2, "DROP TABLE t1");
if( p->errCode==SQLITE_OK ){
sqlite3 *db = p->dbOut;
@@ -1025,12 +1047,12 @@ static int recoverOpenOutput(sqlite3_recover *p){
static void recoverOpenRecovery(sqlite3_recover *p){
char *zSql = recoverMPrintf(p, "ATTACH %Q AS recovery;", p->zStateDb);
recoverExec(p, p->dbOut, zSql);
recoverExec(p, p->dbOut,
"PRAGMA writable_schema = 1;"
"CREATE TABLE recovery.map(pgno INTEGER PRIMARY KEY, parent INT);"
"CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
);
sqlite3_free(zSql);
recoverExec(p, p->dbOut, "PRAGMA writable_schema = 1");
recoverExec(p, p->dbOut,
"CREATE TABLE recovery.map(pgno INTEGER PRIMARY KEY, parent INT)");
recoverExec(p, p->dbOut,
"CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql)");
}
@@ -1170,7 +1192,7 @@ static int recoverWriteSchema1(sqlite3_recover *p){
")"
"SELECT rootpage, tbl, isVirtual, name, sql"
" FROM dbschema "
" WHERE tbl OR isIndex"
" WHERE (tbl OR isIndex) AND sql GLOB 'CREATE *'"
" ORDER BY tbl DESC, name=='sqlite_sequence' DESC"
);
@@ -1196,7 +1218,7 @@ static int recoverWriteSchema1(sqlite3_recover *p){
zName, zName, zSql
));
}
rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0);
rc = recoverOneStmt(p->dbOut, zSql);
if( rc==SQLITE_OK ){
recoverSqlCallback(p, zSql);
if( bTable && !bVirtual ){
@@ -1238,15 +1260,17 @@ static int recoverWriteSchema2(sqlite3_recover *p){
p->bSlowIndexes ?
"SELECT rootpage, sql FROM recovery.schema "
" WHERE type!='table' AND type!='index'"
" AND sql GLOB 'CREATE *'"
:
"SELECT rootpage, sql FROM recovery.schema "
" WHERE type!='table' AND (type!='index' OR sql NOT LIKE '%unique%')"
" AND sql GLOB 'CREATE *'"
);
if( pSelect ){
while( sqlite3_step(pSelect)==SQLITE_ROW ){
const char *zSql = (const char*)sqlite3_column_text(pSelect, 1);
int rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0);
int rc = recoverOneStmt(p->dbOut, zSql);
if( rc==SQLITE_OK ){
recoverSqlCallback(p, zSql);
}else if( rc!=SQLITE_ERROR ){
@@ -2624,7 +2648,7 @@ static void recoverStep(sqlite3_recover *p){
if( bUseWrapper ) recoverUninstallWrapper(p);
}while( p->errCode==SQLITE_NOTADB
&& (bUseWrapper--)
&& SQLITE_OK==sqlite3_exec(p->dbIn, "ROLLBACK", 0, 0, 0)
&& SQLITE_OK==recoverOneStmt(p->dbIn, "ROLLBACK")
);
}
@@ -2689,7 +2713,7 @@ static void recoverStep(sqlite3_recover *p){
** database. Regardless of whether or not an error has occurred, make
** an attempt to end the read transaction on the input database. */
recoverExec(p, p->dbOut, "COMMIT");
rc = sqlite3_exec(p->dbIn, "END", 0, 0, 0);
rc = recoverOneStmt(p->dbIn, "END");
if( p->errCode==SQLITE_OK ) p->errCode = rc;
recoverSqlCallback(p, "PRAGMA writable_schema = off");
@@ -2885,7 +2909,7 @@ int sqlite3_recover_finish(sqlite3_recover *p){
}else{
recoverFinalCleanup(p);
if( p->bCloseTransaction && sqlite3_get_autocommit(p->dbIn)==0 ){
rc = sqlite3_exec(p->dbIn, "END", 0, 0, 0);
rc = recoverOneStmt(p->dbIn, "END");
if( p->errCode==SQLITE_OK ) p->errCode = rc;
}
rc = p->errCode;
+5
View File
@@ -1251,6 +1251,11 @@ static int geopolyInit(
int ii;
(void)pAux;
if( argc>=RTREE_MAX_AUX_COLUMN+4 ){
*pzErr = sqlite3_mprintf("Too many columns for a geopoly table");
return SQLITE_ERROR;
}
sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
+10 -4
View File
@@ -166,7 +166,7 @@ struct Rtree {
u8 eCoordType; /* RTREE_COORD_REAL32 or RTREE_COORD_INT32 */
u8 nBytesPerCell; /* Bytes consumed per cell */
u8 inWrTrans; /* True if inside write transaction */
u8 nAux; /* # of auxiliary columns in %_rowid */
u16 nAux; /* # of auxiliary columns in %_rowid */
#ifdef SQLITE_ENABLE_GEOPOLY
u8 nAuxNotNull; /* Number of initial not-null aux columns */
#endif
@@ -761,6 +761,9 @@ static int nodeAcquire(
rc = SQLITE_CORRUPT_VTAB;
RTREE_IS_CORRUPT(pRtree);
}
}else if( iNode<=0 ){
RTREE_IS_CORRUPT(pRtree);
rc = SQLITE_CORRUPT_VTAB;
}else if( pRtree->iNodeSize==sqlite3_blob_bytes(pRtree->pNodeBlob) ){
pNode = (RtreeNode *)sqlite3_malloc64(sizeof(RtreeNode)+pRtree->iNodeSize);
if( !pNode ){
@@ -1402,7 +1405,7 @@ static int nodeRowidIndex(
){
int ii;
int nCell = NCELL(pNode);
assert( nCell<200 );
assert( nCell<65536 && nCell>=0 );
for(ii=0; ii<nCell; ii++){
if( nodeGetRowid(pRtree, pNode, ii)==iRowid ){
*piIndex = ii;
@@ -1664,7 +1667,10 @@ static int rtreeStepToLeaf(RtreeCursor *pCur){
pNode = rtreeNodeOfFirstSearchPoint(pCur, &rc);
if( rc ) return rc;
nCell = NCELL(pNode);
assert( nCell<200 );
if( nCell>RTREE_MAXCELLS ){
RTREE_IS_CORRUPT(pRtree);
return SQLITE_CORRUPT_VTAB;
}
pCellData = pNode->zData + (4+pRtree->nBytesPerCell*p->iCell);
while( p->iCell<nCell ){
sqlite3_rtree_dbl rScore = (sqlite3_rtree_dbl)-1;
@@ -3641,7 +3647,7 @@ static int rtreeInit(
"Auxiliary rtree columns must be last" /* 4 */
};
assert( RTREE_MAX_AUX_COLUMN<256 ); /* Aux columns counted by a u8 */
assert( RTREE_MAX_AUX_COLUMN<256 );
if( argc<6 || argc>RTREE_MAX_AUX_COLUMN+3 ){
*pzErr = sqlite3_mprintf("%s", aErrMsg[2 + (argc>=6)]);
return SQLITE_ERROR;
+29
View File
@@ -18,6 +18,7 @@ if {![info exists testdir]} {
}
source $testdir/tester.tcl
ifcapable !rtree { finish_test ; return }
set testprefix rtreeA
proc create_t1 {} {
db close
@@ -258,5 +259,33 @@ do_test rtreeA-7.120 {
sqlite3_extended_errcode db
} {SQLITE_CORRUPT_VTAB}
#-------------------------------------------------------------------------
reset_db
set N 300
set rec [binary format SS 0 $N]
for {set ii 1} {$ii<=$N} {incr ii} {
set cell [binary format Wff $ii $ii [expr $ii+1]]
set rec [binary format a*a* $rec $cell]
}
do_execsql_test 8.1 {
CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2);
INSERT INTO rt VALUES(1, 0.0, 1.0);
UPDATE rt_node SET data = $rec WHERE nodeno = 1;
DELETE FROM rt_rowid;
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<300
)
INSERT INTO rt_rowid SELECT i, 1 FROM s;
DELETE FROM rt_parent;
}
db close
sqlite3 db test.db
do_execsql_test 8.2 {
SELECT count(*) FROM rt WHERE id = 1
} {1}
finish_test
+42
View File
@@ -324,5 +324,47 @@ do_test 6.3.2 { sqlite3_errcode db2 } {SQLITE_ERROR}
do_test 6.4 {
catchsql { INSERT INTO c1 VALUES(100, 200) } db2
} {1 {no such table: main.p1}}
db2 close
#-------------------------------------------------------------------------
reset_db
do_execsql_test 7.0 {
PRAGMA trusted_schema=OFF;
PRAGMA foreign_keys=ON;
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, d);
CREATE TABLE t2(e TEXT PRIMARY KEY NOT NULL, f, g);
CREATE TABLE t3(w REAL PRIMARY KEY NOT NULL, x, y);
CREATE TABLE t4(z PRIMARY KEY) WITHOUT ROWID;
CREATE TABLE tc(a INTEGER, b INTEGER, c BLOB, d TEXT, PRIMARY KEY(a,b));
CREATE TABLE wr(a TEXT, b INT, c, PRIMARY KEY(a,b)) WITHOUT ROWID;
CREATE TABLE child(x INTEGER PRIMARY KEY, y INTEGER REFERENCES t1(a) ON DELETE CASCADE, z TEXT);
INSERT INTO t1 VALUES(1,2,3,4),(2,3.5,'four',x'556677'),(3,NULL,'xyz',15),(4,'bubba',2147483648,0.0);
INSERT INTO t1 SELECT a+4,c,d,b FROM t1;
INSERT INTO t2 VALUES('x1y',2,3),('x2y','four',x'556677'),('x3y',NULL,'xyz');
INSERT INTO t3 VALUES(1.1,'a','b'),(2.2,x'00ff','c');
INSERT INTO t4 VALUES('alpha'),('beta'),('gamma');
INSERT INTO tc VALUES(1,1,x'0102','one'),(1,2,x'0304','two'),(2,1,x'0506','three');
INSERT INTO wr VALUES('a',1,'wa'),('b',2,'wb');
INSERT INTO child VALUES(1,1,'c1'),(2,2,'c2');
}
set C [db one "SELECT unhex('
54 0401 0200 0074 6300 1700 0100 0000
0000 0000 0101 0000 0000 0069 0001 0402
0102 0303 6f6e 6500 0004 2008 40a1 4c0b
0d3f 3730 5d92 3f18 f2c9 66a6 4220 a873
04cc 5281 ce5c d9d4 dc8b 7003 056f 6e65
2d75 1700 0100 0002 5004 0100 0000 6d69
7800 1700 0302 6b31 0409 1100
', ' \n')"]
proc conflict_handler {args} {
return "OMIT"
}
do_test 7.1 {
list [catch {sqlite3changeset_apply_v2 db $::C conflict_handler} msg] $msg
} {1 SQLITE_CORRUPT}
finish_test
+23
View File
@@ -273,4 +273,27 @@ do_test 7.0 {
} {1 SQLITE_CORRUPT}
grp delete
#-------------------------------------------------------------------------
#
reset_db
set CS 54014f300009ff
do_test 8.0 {
sqlite3changegroup grp
list [catch { grp add [db one {SELECT unhex($CS)}] } msg] $msg
} {1 SQLITE_CORRUPT}
grp delete
#-------------------------------------------------------------------------
#
reset_db
set CS 540101740017000003ffffffff
do_test 9.0 {
set C [db one {SELECT unhex($CS)}]
list [catch { sqlite3changeset_concat $C $C } msg] $msg
} {1 SQLITE_CORRUPT}
finish_test
+4 -1
View File
@@ -82,6 +82,9 @@ do_test 2.2.1 {
# It is not possible to apply the changeset generated by the following
# SQL, as none of the three updated rows may be updated as part of the
# first pass.
#
# UPDATE 19/05/2026 - it is now possible to apply such an update.
#
do_then_apply_sql -ignorenoop {
UPDATE t1 SET b=0 WHERE a=1;
UPDATE t1 SET b=1 WHERE a=2;
@@ -89,7 +92,7 @@ do_test 2.2.1 {
UPDATE t1 SET b=3 WHERE a=1;
}
db2 eval { SELECT a, b FROM t1 }
} {1 1 2 2 3 3}
} {1 3 2 1 3 2}
do_test 2.2.2 { db eval { SELECT a, b FROM t1 } } {1 3 2 1 3 2}
#-------------------------------------------------------------------------
+9 -1
View File
@@ -184,10 +184,18 @@ foreach {tn script error} {
sqlite3changegroup grp
grp schema db main
eval $script
do_test 3.1.$tn {
do_test 3.1.$tn.1 {
list [catch { grp change_finish false } msg] $msg
} [list 1 $error]
grp delete
sqlite3changegroup grp
grp schema db main
eval $script
do_test 3.1.$tn.2 {
list [catch { grp change_finishne false } msg] $msg
} [list 1 [lindex $error 0]]
grp delete
}
do_test 3.2.1 {
+298
View File
@@ -0,0 +1,298 @@
# 2026 May 18
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library.
#
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 sessionconflict2
forcedelete test.db2
sqlite3 db2 test.db2
do_test 1.0 {
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b, c UNIQUE);
INSERT INTO t1 VALUES(1, 1, 1);
INSERT INTO t1 VALUES(2, 2, 2);
INSERT INTO t1 VALUES(3, 3, 3);
}
} {}
do_test 1.1 {
do_then_apply_sql {
UPDATE t1 SET c=NULL WHERE a=1;
UPDATE t1 SET c=1 WHERE a=3;
UPDATE t1 SET c=3 WHERE a=1;
}
} {}
do_execsql_test -db db 1.2 {
SELECT rowid, * FROM t1
} {
1 1 1 3
2 2 2 2
3 3 3 1
}
do_execsql_test -db db2 1.3 {
SELECT rowid, * FROM t1
} {
1 1 1 3
2 2 2 2
3 3 3 1
}
#--------------------------------------------------------------------------
reset_db
db2 close
forcedelete test.db2
sqlite3 db2 test.db2
do_test 2.0 {
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b, c UNIQUE) WITHOUT ROWID;
INSERT INTO t1 VALUES(1, 1, 1);
INSERT INTO t1 VALUES(2, 2, 2);
INSERT INTO t1 VALUES(3, 3, 3);
}
} {}
do_test 2.1 {
do_then_apply_sql {
UPDATE t1 SET c=NULL WHERE a=1;
UPDATE t1 SET c=1 WHERE a=3;
UPDATE t1 SET c=3 WHERE a=1;
}
} {}
do_execsql_test -db db 2.2 {
SELECT * FROM t1
} {
1 1 3
2 2 2
3 3 1
}
do_execsql_test -db db2 2.3 {
SELECT * FROM t1
} {
1 1 3
2 2 2
3 3 1
}
#--------------------------------------------------------------------------
reset_db
db2 close
forcedelete test.db2
sqlite3 db2 test.db2
do_test 3.0 {
do_common_sql {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c UNIQUE);
INSERT INTO t1 VALUES(1, 1, 1);
INSERT INTO t1 VALUES(2, 2, 2);
INSERT INTO t1 VALUES(3, 3, 3);
}
} {}
do_test 3.1 {
do_then_apply_sql {
UPDATE t1 SET c=NULL WHERE a=1;
UPDATE t1 SET c=1 WHERE a=3;
UPDATE t1 SET c=3 WHERE a=1;
}
} {}
do_execsql_test -db db 3.2 {
SELECT rowid, * FROM t1
} {
1 1 1 3
2 2 2 2
3 3 3 1
}
do_execsql_test -db db2 3.3 {
SELECT rowid, * FROM t1
} {
1 1 1 3
2 2 2 2
3 3 3 1
}
#-------------------------------------------------------------------------
db2 close
reset_db
forcedelete test.db2
sqlite3 db2 test.db2
set ::conflict_list [list]
proc xConflict {args} {
lappend ::conflict_list $args
return "OMIT"
}
proc do_conflict_test {tn bNoUpdateLoop script clist} {
uplevel [list do_test $tn.1 [subst -nocommands {
sqlite3session S db "main"
S attach *
eval {$script}
set ::changeset [S changeset]
S delete
}] {}]
if {$bNoUpdateLoop} {
uplevel [list do_test $tn.2.no {
set ::conflict_list [list]
sqlite3changeset_apply_v2 -noupdateloop db2 $::changeset xConflict
set ::conflict_list
} [list {*}$clist]]
} else {
uplevel [list do_test $tn.2 {
set ::conflict_list [list]
sqlite3changeset_apply_v2 db2 $::changeset xConflict
set ::conflict_list
} [list {*}$clist]]
}
}
do_test 4.0 {
do_common_sql {
CREATE TABLE t1(a INT PRIMARY KEY, b, c UNIQUE, d UNIQUE);
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<10
)
INSERT INTO t1 SELECT i, i, i, i FROM s;
}
} {}
proc swap {tbl pkcol valcol pk1 pk2} {
set val1 [db one "SELECT $valcol FROM $tbl WHERE $pkcol = \$pk1"]
set val2 [db one "SELECT $valcol FROM $tbl WHERE $pkcol = \$pk2"]
db eval "
UPDATE $tbl SET $valcol = NULL WHERE $pkcol IN (\$pk1, \$pk2);
UPDATE $tbl SET $valcol = \$val2 WHERE $pkcol = \$pk1;
UPDATE $tbl SET $valcol = \$val1 WHERE $pkcol = \$pk2;
"
}
do_conflict_test 4.1.1 0 {
swap t1 a c 4 5
swap t1 a c 2 3
swap t1 a c 8 1
} {
}
do_execsql_test -db db 4.1.2 {
SELECT a, c FROM t1
} {
1 8 2 3 3 2 4 5 5 4 6 6 7 7 8 1 9 9 10 10
}
do_execsql_test -db db2 4.1.3 {
SELECT a, c FROM t1
} {
1 8 2 3 3 2 4 5 5 4 6 6 7 7 8 1 9 9 10 10
}
do_conflict_test 4.2.1 0 {
swap t1 a d 10 9
swap t1 a d 8 7
swap t1 a d 7 6
swap t1 a d 5 4
swap t1 a d 4 8
} {
}
do_execsql_test -db db 4.2.2 {
SELECT a, d FROM t1
} {
1 1 2 2 3 3 4 7 5 4 6 8 7 6 8 5 9 10 10 9
}
do_execsql_test -db db2 4.2.3 {
SELECT a, d FROM t1
} {
1 1 2 2 3 3 4 7 5 4 6 8 7 6 8 5 9 10 10 9
}
do_execsql_test -db db2 4.3 {
INSERT INTO t1(a, b, c, d) VALUES(11, 11, 11, 11);
}
do_conflict_test 4.3.1 0 {
swap t1 a c 3 6
db eval { UPDATE t1 SET d=11 WHERE a=10; }
swap t1 a d 4 8
} {
{UPDATE t1 CONSTRAINT {i 10 {} {} {} {} i 9} {{} {} {} {} {} {} i 11}}
}
do_conflict_test 4.3.2 0 {
swap t1 a c 1 2
swap t1 a c 3 4
db eval { UPDATE t1 SET c=11 WHERE a=10; }
swap t1 a d 5 6
swap t1 a d 4 5
} {
{UPDATE t1 CONSTRAINT {i 10 {} {} i 10 {} {}} {{} {} {} {} i 11 {} {}}}
}
do_conflict_test 4.3.3 0 {
swap t1 a c 1 2
swap t1 a c 2 3
swap t1 a c 3 4
swap t1 a c 4 1
} {
}
db2 close
#-------------------------------------------------------------------------
reset_db
forcedelete test.db2
sqlite3 db2 test.db2
do_test 5.0 {
do_common_sql {
CREATE TABLE t1(a INT PRIMARY KEY, b UNIQUE);
INSERT INTO t1 VALUES('one', 'one');
INSERT INTO t1 VALUES('two', 'two');
INSERT INTO t1 VALUES('three', 'three');
INSERT INTO t1 VALUES('four', 'four');
INSERT INTO t1 VALUES('five', 'five');
}
} {}
do_conflict_test 5.1 1 {
} {
}
do_conflict_test 5.2 1 {
swap t1 a b one two
} {
{UPDATE t1 CONSTRAINT {t two t two} {{} {} t one}}
{UPDATE t1 CONSTRAINT {t one t one} {{} {} t two}}
}
do_conflict_test 5.2 0 {
swap t1 a b four five
} {
}
finish_test
+41
View File
@@ -94,4 +94,45 @@ do_faultsim_test 2 -faults oom-t* -prep {
catch { S delete }
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE);
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
INSERT INTO t1 VALUES(3, 'three');
INSERT INTO t1 VALUES(4, 'four');
INSERT INTO t1 VALUES(5, 'five');
}
faultsim_save_and_close
faultsim_restore_and_reopen
set C [changeset_from_sql {
UPDATE t1 SET b=NULL WHERE a IN (1, 3, 5);
UPDATE t1 SET b='three' WHERE a=1;
UPDATE t1 SET b='five' WHERE a=3;
UPDATE t1 SET b='one' WHERE a=5;
}]
do_execsql_test 3.1 {
SELECT * FROM t1
} {
1 three 2 two 3 five 4 four 5 one
}
proc xConflict {args} {
lappend ::conflict_list $args
return "OMIT"
}
do_faultsim_test 3 -faults oom* -prep {
faultsim_restore_and_reopen
db eval {SELECT * FROM sqlite_schema}
} -body {
sqlite3changeset_apply_v2 db $::C xConflict
set {} {}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
}
finish_test
+6
View File
@@ -181,5 +181,11 @@ do_invert_test 4.1 {
{UPDATE t1 0 X. {i 4 t three} {{} {} t four}}
}
#-------------------------------------------------------------------------
#
do_test 5.0 {
set C [db one {SELECT unhex('54000009')}]
list [catch { sqlite3changeset_invert $C } msg] $msg
} {1 SQLITE_CORRUPT}
finish_test
+25
View File
@@ -228,5 +228,30 @@ do_execsql_test 3.8 {
SELECT * FROM c1;
} {two}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 4.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c INTEGER);
ATTACH ':memory:' AS aux;
CREATE TABLE aux.t1(a INTEGER PRIMARY KEY, b TEXT, c INTEGER);
}
do_test 4.1 {
sqlite3session S db main
S diff aux t1
} {}
do_test 4.2 {
sqlite3changeset_apply_v2 -ignorenoop -noaction db "" conflict
} {}
breakpoint
do_test 4.3 {
S diff aux t1
} {}
S delete
finish_test
+407 -61
View File
@@ -346,7 +346,9 @@ static int sessionVarintLen(int iVal){
** bytes read.
*/
static int sessionVarintGet(const u8 *aBuf, int *piVal){
return getVarint32(aBuf, *piVal);
int ret = getVarint32(aBuf, *piVal);
*piVal = (*piVal & 0x7FFFFFFF);
return ret;
}
/*
@@ -361,7 +363,7 @@ static int sessionVarintGetSafe(const u8 *aBuf, int nBuf, int *piVal){
memcpy(aCopy, aBuf, nBuf);
aRead = aCopy;
}
return getVarint32(aRead, *piVal);
return sessionVarintGet(aRead, piVal);
}
/* Load an unaligned and unsigned 32-bit integer */
@@ -665,17 +667,17 @@ static unsigned int sessionChangeHash(
u8 *a = aRecord; /* Used to iterate through change record */
for(i=0; i<pTab->nCol; i++){
int eType = *a;
int isPK = pTab->abPK[i];
if( bPkOnly && isPK==0 ) continue;
assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT
|| eType==SQLITE_TEXT || eType==SQLITE_BLOB
|| eType==SQLITE_NULL || eType==0
);
if( isPK ){
a++;
int eType = *a++;
assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT
|| eType==SQLITE_TEXT || eType==SQLITE_BLOB
|| eType==SQLITE_NULL || eType==0
);
h = sessionHashAppendType(h, eType);
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
h = sessionHashAppendI64(h, sessionGetI64(a));
@@ -1545,6 +1547,16 @@ static int sessionPrepareDfltStmt(
return rc;
}
/*
** Finalize statement pStmt. If (*pRc) is SQLITE_OK when this function is
** called, set it to the results of the sqlite3_finalize() call. Or, if
** it is already set to an error code, leave it as is.
*/
static void sessionFinalizeStmt(sqlite3_stmt *pStmt, int *pRc){
int rc = sqlite3_finalize(pStmt);
if( *pRc==SQLITE_OK ) *pRc = rc;
}
/*
** Table pTab has one or more existing change-records with old.* records
** with fewer than pTab->nCol columns. This function updates all such
@@ -1567,9 +1579,8 @@ static int sessionUpdateChanges(sqlite3_session *pSession, SessionTable *pTab){
}
}
sessionFinalizeStmt(pStmt, &rc);
pSession->rc = rc;
rc = sqlite3_finalize(pStmt);
if( pSession->rc==SQLITE_OK ) pSession->rc = rc;
return pSession->rc;
}
@@ -2137,7 +2148,7 @@ static int sessionDiffFindNew(
rc = SQLITE_NOMEM;
}else{
sqlite3_stmt *pStmt;
rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0);
rc = sqlite3_prepare_v2(pSession->db, zStmt, -1, &pStmt, 0);
if( rc==SQLITE_OK ){
SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx;
pDiffCtx->pStmt = pStmt;
@@ -2200,7 +2211,7 @@ static int sessionDiffFindModified(
rc = SQLITE_NOMEM;
}else{
sqlite3_stmt *pStmt;
rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0);
rc = sqlite3_prepare_v2(pSession->db, zStmt, -1, &pStmt, 0);
if( rc==SQLITE_OK ){
SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx;
@@ -2895,11 +2906,11 @@ static int sessionSelectStmt(
);
sessionAppendStr(&cols, "tbl, ?2, stat", &rc);
}else{
#if 0
#if 0
if( bRowid ){
sessionAppendStr(&cols, SESSIONS_ROWID, &rc);
}
#endif
#endif
for(i=0; i<nCol; i++){
if( cols.nBuf ) sessionAppendStr(&cols, ", ", &rc);
sessionAppendIdent(&cols, azCol[i], &rc);
@@ -3694,17 +3705,21 @@ static int sessionChangesetBufferRecord(
int eType;
rc = sessionInputBuffer(pIn, nByte + 10);
if( rc==SQLITE_OK ){
eType = pIn->aData[pIn->iNext + nByte++];
if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
int n;
int nRem = pIn->nData - (pIn->iNext + nByte);
nByte += sessionVarintGetSafe(&pIn->aData[pIn->iNext+nByte], nRem, &n);
nByte += n;
rc = sessionInputBuffer(pIn, nByte);
}else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
nByte += 8;
}else if( eType!=0 && eType!=SQLITE_NULL ){
if( pIn->iNext+nByte>=pIn->nData ){
rc = SQLITE_CORRUPT_BKPT;
}else{
eType = pIn->aData[pIn->iNext + nByte++];
if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
int n;
int nRem = pIn->nData - (pIn->iNext + nByte);
nByte += sessionVarintGetSafe(&pIn->aData[pIn->iNext+nByte], nRem,&n);
nByte += n;
rc = sessionInputBuffer(pIn, nByte);
}else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
nByte += 8;
}else if( eType!=0 && eType!=SQLITE_NULL ){
rc = SQLITE_CORRUPT_BKPT;
}
}
}
if( rc==SQLITE_OK && (pIn->iNext+nByte)>pIn->nData ){
@@ -4153,7 +4168,13 @@ static int sessionChangesetInvert(
/* Test for EOF. */
if( (rc = sessionInputBuffer(pInput, 2)) ) goto finished_invert;
if( pInput->iNext>=pInput->nData ) break;
if( pInput->iNext+1>=pInput->nData ){
if( pInput->iNext!=pInput->nData ){
rc = SQLITE_CORRUPT_BKPT;
goto finished_invert;
}
break;
}
eType = pInput->aData[pInput->iNext];
switch( eType ){
@@ -4349,6 +4370,7 @@ struct SessionApplyCtx {
u8 bRebaseStarted; /* If table header is already in rebase */
u8 bRebase; /* True to collect rebase information */
u8 bIgnoreNoop; /* True to ignore no-op conflicts */
u8 bNoUpdateLoop; /* No update-loop processing */
int bRowid;
char *zErr; /* Error message, if any */
};
@@ -4922,7 +4944,7 @@ static int sessionConflictHandler(
u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent];
int nBlob = pIter->in.iNext - pIter->in.iCurrent;
sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc);
return SQLITE_OK;
return rc;
}else if( p->bIgnoreNoop==0 || op!=SQLITE_DELETE
|| eType==SQLITE_CHANGESET_CONFLICT
){
@@ -5044,7 +5066,7 @@ static int sessionApplyOneOp(
for(i=0; rc==SQLITE_OK && i<nCol; i++){
sqlite3_value *pOld = sessionChangesetOld(pIter, i);
sqlite3_value *pNew = sessionChangesetNew(pIter, i);
if( p->abPK[i] || (bPatchset==0 && pOld) ){
if( pOld && (p->abPK[i] || bPatchset==0) ){
rc = sessionBindValue(pUp, i*2+2, pOld);
}
if( rc==SQLITE_OK && pNew ){
@@ -5170,7 +5192,264 @@ static int sessionApplyOneWithRetry(
}
/*
** Retry the changes accumulated in the pApply->constraints buffer.
** Create an iterator to iterate through the retry buffer pRetry.
*/
static int sessionRetryIterInit(
SessionBuffer *pRetry, /* Buffer to iterate through */
int bPatchset, /* True for patchset, false for changeset */
const char *zTab, /* Table name */
SessionApplyCtx *pApply, /* Session apply context */
sqlite3_changeset_iter **ppIter /* OUT: New iterator */
){
sqlite3_changeset_iter *pRet = 0;
int rc = SQLITE_OK;
rc = sessionChangesetStart(
&pRet, 0, 0, pRetry->nBuf, pRetry->aBuf, pApply->bInvertConstraints, 1
);
if( rc==SQLITE_OK ){
size_t nByte = 2*pApply->nCol*sizeof(sqlite3_value*);
pRet->bPatchset = bPatchset;
pRet->zTab = (char*)zTab;
pRet->nCol = pApply->nCol;
pRet->abPK = pApply->abPK;
sessionBufferGrow(&pRet->tblhdr, nByte, &rc);
pRet->apValue = (sqlite3_value**)pRet->tblhdr.aBuf;
if( rc==SQLITE_OK ){
memset(pRet->apValue, 0, nByte);
}else{
sqlite3changeset_finalize(pRet);
pRet = 0;
}
}
*ppIter = pRet;
return rc;
}
/*
** Attempt to apply all the changes in retry buffer pRetry to the database.
** Except, if parameter iSkip is greater than or equal to 0, skip change
** iSkip.
*/
static int sessionApplyRetryBuffer(
SessionBuffer *pRetry, /* Buffer to apply changes from */
int iSkip, /* If >=0, index of change to omit */
sqlite3 *db, /* Database handle */
int bPatchset, /* True for patchset, false for changeset */
const char *zTab, /* Name of table to write to */
SessionApplyCtx *pApply, /* Apply context */
int(*xConflict)(void*, int, sqlite3_changeset_iter*),
void *pCtx /* First argument passed to xConflict */
){
int rc = SQLITE_OK;
int rc2 = SQLITE_OK;
int ii = 0;
sqlite3_changeset_iter *pIter = 0;
assert( pApply->constraints.nBuf==0 );
rc = sessionRetryIterInit(pRetry, bPatchset, zTab, pApply, &pIter);
for(ii=0; rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter); ii++){
if( ii!=iSkip ){
rc = sessionApplyOneWithRetry(db, pIter, pApply, xConflict, pCtx);
}
}
rc2 = sqlite3changeset_finalize(pIter);
if( rc==SQLITE_OK ) rc = rc2;
assert( pApply->bDeferConstraints || pApply->constraints.nBuf==0 );
return rc;
}
/*
** Check if table zTab in the "main" database of db is a WITHOUT ROWID
** table.
**
** If no error occurs, return SQLITE_OK and set output variable (*pbWR) to
** true if zTab is a WITHOUT ROWID table, or false otherwise. Or, if an
** error does occur, return an SQLite error code. The final value of (*pbWR)
** is undefined in this case.
*/
static int sessionTableIsWithoutRowid(sqlite3 *db, const char *zTab, int *pbWR){
sqlite3_stmt *pList = 0;
char *zSql = 0;
int rc = SQLITE_OK;
zSql = sqlite3_mprintf("PRAGMA table_list = %Q", zTab);
if( zSql==0 ){
rc = SQLITE_NOMEM;
}else{
rc = sqlite3_prepare_v2(db, zSql, -1, &pList, 0);
sqlite3_free(zSql);
}
if( rc==SQLITE_OK ){
sqlite3_step(pList);
*pbWR = sqlite3_column_int(pList, 4);
rc = sqlite3_finalize(pList);
}
return rc;
}
/*
** Iterator pUp points to an UPDATE change. This function deletes the
** affected row from the database and creates an INSERT statement that
** may be used to reinsert the row as it is after the UPDATE change
** has been applied.
**
** If successful, SQLITE_OK is returned and output variable (*ppInsert)
** is left pointing to a prepared INSERT statement. It is the responsibility
** of the caller to eventually free this statement using sqlite3_finalize().
** Or, if an error occurs, an SQLite error code is returned and (*ppInsert)
** set to NULL. pApply->zErr may be set to an error message in this case.
*/
static int sessionUpdateToDeleteInsert(
sqlite3 *db, /* Database to write to */
const char *zTab, /* Table name */
SessionApplyCtx *pApply, /* Apply context */
sqlite3_changeset_iter *pUp, /* Iterator pointing to UPDATE change */
sqlite3_stmt **ppInsert /* OUT: INSERT statement */
){
sqlite3_stmt *pRet = 0; /* The INSERT statement */
sqlite3_stmt *pSelect = 0; /* SELECT to read current values of row */
int rc = SQLITE_OK;
int bWR = 0;
rc = sessionTableIsWithoutRowid(db, zTab, &bWR);
if( rc==SQLITE_OK ){
char *zSelect = 0;
char *zInsert = 0;
SessionBuffer cols = {0, 0, 0};
SessionBuffer insbind = {0, 0, 0};
SessionBuffer pkcols = {0, 0, 0};
SessionBuffer selbind = {0, 0, 0};
const char *zComma = "";
const char *zComma2 = "";
int ii;
for(ii=0; ii<pApply->nCol; ii++){
sessionAppendStr(&cols, zComma, &rc);
sessionAppendIdent(&cols, pApply->azCol[ii], &rc);
sessionAppendStr(&insbind, zComma, &rc);
sessionAppendStr(&insbind, "?", &rc);
zComma = ", ";
if( pApply->abPK[ii] ){
sessionAppendStr(&pkcols, zComma2, &rc);
sessionAppendIdent(&pkcols, pApply->azCol[ii], &rc);
sessionAppendStr(&selbind, zComma2, &rc);
sessionAppendPrintf(&selbind, &rc, "?%d", ii+1);
zComma2 = ", ";
}
}
if( bWR==0 ){
sessionAppendStr(&cols, zComma, &rc);
sessionAppendStr(&cols, SESSIONS_ROWID, &rc);
sessionAppendStr(&insbind, zComma, &rc);
sessionAppendStr(&insbind, "?", &rc);
}
if( rc==SQLITE_OK ){
zSelect = sqlite3_mprintf("SELECT %s FROM %Q WHERE (%s) IS (%s)",
cols.aBuf, zTab, pkcols.aBuf, selbind.aBuf
);
if( zSelect==0 ) rc = SQLITE_NOMEM;
}
if( rc==SQLITE_OK ){
zInsert = sqlite3_mprintf("INSERT INTO %Q(%s) VALUES(%s)",
zTab, cols.aBuf, insbind.aBuf
);
if( zInsert==0 ) rc = SQLITE_NOMEM;
}
if( rc==SQLITE_OK ){
rc = sessionPrepare(db, &pSelect, &pApply->zErr, zSelect);
}
if( rc==SQLITE_OK ){
rc = sessionPrepare(db, &pRet, &pApply->zErr, zInsert);
}
sqlite3_free(zSelect);
sqlite3_free(zInsert);
sqlite3_free(cols.aBuf);
sqlite3_free(insbind.aBuf);
sqlite3_free(pkcols.aBuf);
sqlite3_free(selbind.aBuf);
}
if( rc==SQLITE_OK ){
rc = sessionBindRow(
pUp, sqlite3changeset_old, pApply->nCol, pApply->abPK, pSelect
);
}
if( rc==SQLITE_OK && sqlite3_step(pSelect)==SQLITE_ROW ){
int iCol;
for(iCol=0; iCol<pApply->nCol; iCol++){
sqlite3_value *pVal = pUp->apValue[iCol+pApply->nCol];
if( pVal==0 ){
pVal = sqlite3_column_value(pSelect, iCol);
}
rc = sqlite3_bind_value(pRet, iCol+1, pVal);
}
if( bWR==0 ){
sqlite3_bind_int64(pRet, iCol+1, sqlite3_column_int64(pSelect, iCol));
}
}
sessionFinalizeStmt(pSelect, &rc);
/* Delete the row from the database. */
if( rc==SQLITE_OK ){
rc = sessionBindRow(
pUp, sqlite3changeset_old, pApply->nCol, pApply->abPK, pApply->pDelete
);
sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1);
}
if( rc==SQLITE_OK ){
sqlite3_step(pApply->pDelete);
rc = sqlite3_reset(pApply->pDelete);
}
if( rc!=SQLITE_OK ){
sqlite3_finalize(pRet);
pRet = 0;
}
*ppInsert = pRet;
return rc;
}
/*
** Retry the changes accumulated in the pApply->constraints buffer. The
** pApply->constraints buffer contains all changes to table zTab that
** could not be applied due to SQLITE_CONSTRAINT errors. This function
** attempts to apply them as follows:
**
** 1) It runs through the buffer and attempts to retry each change,
** removing any that are successfully applied from the buffer. This
** is repeated until no further progress can be made.
**
** 2) For each UPDATE change in the buffer, try the following in a
** savepoint transaction:
**
** a) DELETE the affected row,
** b) Attempt step (1) with remaining changes,
** c) Attempt to INSERT a row equivalent to the one that would be
** created by applying this UPDATE change.
**
** If the INSERT in (c) succeeds, the savepoint is committed and all
** successfully applied changes are removed from the buffer. Step (2)
** is then repeated.
**
** 3) Once step (2) has been attempted for each UPDATE in the change,
** a final attempt is made to apply each remaining change. This time,
** if an SQLITE_CONSTRAINT error is encountered, the conflict handler
** is invoked and the user has to decide whether to omit the change
** or rollback the entire _apply() operation.
*/
static int sessionRetryConstraints(
sqlite3 *db,
@@ -5181,41 +5460,101 @@ static int sessionRetryConstraints(
void *pCtx /* First argument passed to xConflict */
){
int rc = SQLITE_OK;
int iUpdate = 0;
/* Step (1) */
while( pApply->constraints.nBuf ){
sqlite3_changeset_iter *pIter2 = 0;
SessionBuffer cons = pApply->constraints;
memset(&pApply->constraints, 0, sizeof(SessionBuffer));
rc = sessionChangesetStart(
&pIter2, 0, 0, cons.nBuf, cons.aBuf, pApply->bInvertConstraints, 1
rc = sessionApplyRetryBuffer(
&cons, -1, db, bPatchset, zTab, pApply, xConflict, pCtx
);
if( rc==SQLITE_OK ){
size_t nByte = 2*pApply->nCol*sizeof(sqlite3_value*);
int rc2;
pIter2->bPatchset = bPatchset;
pIter2->zTab = (char*)zTab;
pIter2->nCol = pApply->nCol;
pIter2->abPK = pApply->abPK;
sessionBufferGrow(&pIter2->tblhdr, nByte, &rc);
pIter2->apValue = (sqlite3_value**)pIter2->tblhdr.aBuf;
if( rc==SQLITE_OK ) memset(pIter2->apValue, 0, nByte);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter2) ){
rc = sessionApplyOneWithRetry(db, pIter2, pApply, xConflict, pCtx);
}
rc2 = sqlite3changeset_finalize(pIter2);
if( rc==SQLITE_OK ) rc = rc2;
}
assert( pApply->bDeferConstraints || pApply->constraints.nBuf==0 );
sqlite3_free(cons.aBuf);
if( rc!=SQLITE_OK ) break;
if( pApply->constraints.nBuf>=cons.nBuf ){
/* No progress was made on the last round. */
pApply->bDeferConstraints = 0;
/* If no progress has been made this round, break out of the loop. */
if( pApply->constraints.nBuf>=cons.nBuf ) break;
}
/* Step (2) */
while( rc==SQLITE_OK && pApply->constraints.nBuf && !pApply->bNoUpdateLoop ){
SessionBuffer cons = {0, 0, 0};
sqlite3_changeset_iter *pUp = 0;
sqlite3_stmt *pInsert = 0;
int iSkip = 0;
rc = sessionRetryIterInit(
&pApply->constraints, bPatchset, zTab, pApply, &pUp
);
if( rc==SQLITE_OK ){
int iThis = -1;
while( SQLITE_ROW==sqlite3changeset_next(pUp) ){
if( pUp->op==SQLITE_UPDATE ) iThis++;
if( iThis==iUpdate ) break;
iSkip++;
}
if( iThis==iUpdate ){
rc = sqlite3_exec(db, "SAVEPOINT update_op", 0, 0, 0);
if( rc==SQLITE_OK ){
rc = sessionUpdateToDeleteInsert(db, zTab, pApply, pUp, &pInsert);
}
}
sqlite3changeset_finalize(pUp);
if( iThis!=iUpdate ) break;
}
if( rc==SQLITE_OK ){
cons = pApply->constraints;
while( rc==SQLITE_OK && pApply->constraints.nBuf>0 ){
SessionBuffer app = pApply->constraints;
memset(&pApply->constraints, 0, sizeof(SessionBuffer));
rc = sessionApplyRetryBuffer(
&app, iSkip, db, bPatchset, zTab, pApply, xConflict, pCtx
);
if( app.aBuf!=cons.aBuf ){
sqlite3_free(app.aBuf);
}
if( pApply->constraints.nBuf>=app.nBuf ){
break;
}
iSkip = -1;
}
}
iUpdate++;
if( rc==SQLITE_OK ){
sqlite3_step(pInsert);
rc = sqlite3_finalize(pInsert);
if( rc==SQLITE_CONSTRAINT ){
rc = sqlite3_exec(db, "ROLLBACK TO update_op", 0, 0, 0);
sqlite3_free(pApply->constraints.aBuf);
pApply->constraints = cons;
memset(&cons, 0, sizeof(cons));
}else if( rc==SQLITE_OK ){
iUpdate = 0;
}
if( rc==SQLITE_OK ){
rc = sqlite3_exec(db, "RELEASE update_op", 0, 0, 0);
}
}else{
sqlite3_finalize(pInsert);
}
sqlite3_free(cons.aBuf);
}
/* Step (3) */
if( rc==SQLITE_OK && pApply->constraints.nBuf ){
SessionBuffer cons = pApply->constraints;
memset(&pApply->constraints, 0, sizeof(SessionBuffer));
pApply->bDeferConstraints = 0;
rc = sessionApplyRetryBuffer(
&cons, -1, db, bPatchset, zTab, pApply, xConflict, pCtx
);
sqlite3_free(cons.aBuf);
}
return rc;
@@ -5269,6 +5608,7 @@ static int sessionChangesetApply(
sApply.bRebase = (ppRebase && pnRebase);
sApply.bInvertConstraints = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
sApply.bIgnoreNoop = !!(flags & SQLITE_CHANGESETAPPLY_IGNORENOOP);
sApply.bNoUpdateLoop = !!(flags & SQLITE_CHANGESETAPPLY_NOUPDATELOOP);
if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){
rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
}
@@ -7053,7 +7393,7 @@ int sqlite3changegroup_change_text(
const char *pVal,
int nVal
){
int nText = nVal>=0 ? nVal : strlen(pVal);
sqlite3_int64 nText = nVal>=0 ? nVal : strlen(pVal);
sqlite3_int64 nByte = 1 + sessionVarintLen(nText) + nText;
int rc = SQLITE_OK;
SessionBuffer *pBuf = 0;
@@ -7080,7 +7420,7 @@ int sqlite3changegroup_change_blob(
const void *pVal,
int nVal
){
sqlite3_int64 nByte = 1 + sessionVarintLen(nVal) + nVal;
sqlite3_int64 nByte = 1 + sessionVarintLen(nVal) + (i64)nVal;
int rc = SQLITE_OK;
SessionBuffer *pBuf = 0;
@@ -7105,6 +7445,7 @@ int sqlite3changegroup_change_finish(
char **pzErr
){
int rc = SQLITE_OK;
char *zErr = 0;
if( pGrp->cd.pTab ){
SessionBuffer *aBuf = pGrp->cd.aBuf;
int ii;
@@ -7116,14 +7457,14 @@ int sqlite3changegroup_change_finish(
for(ii=0; ii<nBuf; ii++){
if( pGrp->cd.pTab->abPK[ii] ){
if( aBuf[ii].nBuf<=1 ){
*pzErr = sqlite3_mprintf(
zErr = sqlite3_mprintf(
"invalid change: %s value in PK of old.* record",
aBuf[ii].nBuf==1 ? "null" : "undefined"
);
rc = SQLITE_ERROR;
break;
}else if( aBuf[ii + nBuf].nBuf>0 ){
*pzErr = sqlite3_mprintf(
zErr = sqlite3_mprintf(
"invalid change: defined value in PK of new.* record"
);
rc = SQLITE_ERROR;
@@ -7131,7 +7472,7 @@ int sqlite3changegroup_change_finish(
}
}else
if( pGrp->bPatch==0 && (aBuf[ii].nBuf>0)!=(aBuf[ii+nBuf].nBuf>0) ){
*pzErr = sqlite3_mprintf(
zErr = sqlite3_mprintf(
"invalid change: column %d "
"- old.* value is %sdefined but new.* is %sdefined",
ii, aBuf[ii].nBuf ? "" : "un", aBuf[ii+nBuf].nBuf ? "" : "un"
@@ -7148,14 +7489,14 @@ int sqlite3changegroup_change_finish(
if( (pGrp->cd.eOp==SQLITE_INSERT || pGrp->bPatch==0 || isPK)
&& aBuf[ii].nBuf==0
){
*pzErr = sqlite3_mprintf(
zErr = sqlite3_mprintf(
"invalid change: column %d is undefined", ii
);
rc = SQLITE_ERROR;
break;
}
if( aBuf[ii].nBuf==1 && isPK ){
*pzErr = sqlite3_mprintf(
zErr = sqlite3_mprintf(
"invalid change: null value in PK"
);
rc = SQLITE_ERROR;
@@ -7205,6 +7546,11 @@ int sqlite3changegroup_change_finish(
pGrp->cd.pTab = 0;
}
if( pzErr ){
*pzErr = zErr;
}else{
sqlite3_free(zErr);
}
return rc;
}
+12
View File
@@ -1366,11 +1366,23 @@ int sqlite3changeset_apply_v3(
** database behave as if they were declared with "ON UPDATE NO ACTION ON
** DELETE NO ACTION", even if they are actually CASCADE, RESTRICT, SET NULL
** or SET DEFAULT.
**
** <dt>SQLITE_CHANGESETAPPLY_NOUPDATELOOP <dd>
** Sometimes, a changeset contains two or more update statements such that
** although after applying all updates the database will contain no
** constraint violations, no single update can be applied before the others.
** The simplest example of this is a pair of UPDATEs that have "swapped"
** two column values with a UNIQUE constraint.
** <p>
** Usually, sqlite3changeset_apply() and similar functions work hard to try
** to find a way to apply such a changeset. However, if this flag is set,
** then all such updates are considered CONSTRAINT conflicts.
*/
#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001
#define SQLITE_CHANGESETAPPLY_INVERT 0x0002
#define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004
#define SQLITE_CHANGESETAPPLY_FKNOACTION 0x0008
#define SQLITE_CHANGESETAPPLY_NOUPDATELOOP 0x0010
/*
** CAPI3REF: Constants Passed To The Conflict Handler
+19 -10
View File
@@ -914,6 +914,9 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
}
else if( n>2 && n<=11 && 0==sqlite3_strnicmp("-ignorenoop", z1, n) ){
flags |= SQLITE_CHANGESETAPPLY_IGNORENOOP;
}
else if( n>3 && n<=13 && 0==sqlite3_strnicmp("-noupdateloop", z1, n) ){
flags |= SQLITE_CHANGESETAPPLY_NOUPDATELOOP;
}else{
break;
}
@@ -1095,7 +1098,7 @@ static int SQLITE_TCLAPI test_sqlite3changeset_invert(
memset(&sIn, 0, sizeof(sIn));
memset(&sOut, 0, sizeof(sOut));
sIn.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
sIn.aData = Tcl_GetByteArrayFromObj(objv[1], &nn);
sIn.aData = testGetByteArrayFromObj(objv[1], &nn);
sIn.nData = (int)nn;
if( sIn.nStream ){
@@ -1112,6 +1115,7 @@ static int SQLITE_TCLAPI test_sqlite3changeset_invert(
Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n));
}
sqlite3_free(sOut.p);
free(sIn.aData);
return rc;
}
@@ -1615,10 +1619,11 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
{ "change_text", 3, "[new|old] ICOL VALUE" }, /* 9 */
{ "change_blob", 3, "[new|old] ICOL VALUE" }, /* 10 */
{ "change_finish", 1, "BDISCARD" }, /* 11 */
{ "change_finishne", 1, "BDISCARD" }, /* 12 */
{ "config", 2, "OPTION INTVAL" }, /* 12 */
{ "change_text-1", 3, "[new|old] ICOL VALUE" }, /* 13 */
{ "change_begin_ne", 3, "TYPE TABLE INDIRECT" }, /* 14 */
{ "config", 2, "OPTION INTVAL" }, /* 13 */
{ "change_text-1", 3, "[new|old] ICOL VALUE" }, /* 14 */
{ "change_begin_ne", 3, "TYPE TABLE INDIRECT" }, /* 15 */
{ 0, 0, 0 }
};
int rc = TCL_OK;
@@ -1651,9 +1656,10 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
case 1: { /* add */
Tcl_Size nByte = 0;
const u8 *aByte = Tcl_GetByteArrayFromObj(objv[2], &nByte);
rc = sqlite3changegroup_add(p->pGrp, (int)nByte, (void*)aByte);
void *aByte = testGetByteArrayFromObj(objv[2], &nByte);
rc = sqlite3changegroup_add(p->pGrp, (int)nByte, aByte);
if( rc!=SQLITE_OK ) rc = test_session_error(interp, rc, 0);
free(aByte);
break;
};
@@ -1688,7 +1694,7 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
break;
};
case 14: /* change_beginne */
case 15: /* change_beginne */
case 5: { /* change_begin */
struct ChangeType {
const char *zType;
@@ -1815,13 +1821,16 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
break;
}
case 12: /* change_finishne */
case 11: { /* change_finish */
int bDiscard = 0;
if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[2], &bDiscard) ){
rc = TCL_ERROR;
}else{
char *zErr = 0;
rc = sqlite3changegroup_change_finish(p->pGrp, bDiscard, &zErr);
char **pz = &zErr;
if( iSub==12 ) pz = 0;
rc = sqlite3changegroup_change_finish(p->pGrp, bDiscard, pz);
if( rc!=SQLITE_OK ){
rc = test_session_error(interp, rc, zErr);
}
@@ -1829,7 +1838,7 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
break;
}
case 12: { /* config */
case 13: { /* config */
struct OptionName {
const char *zOpt;
int op;
@@ -1858,7 +1867,7 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
break;
}
case 13: { /* change_text-1 */
case 14: { /* change_text-1 */
int bNew = 0;
int iCol = 0;
if( TCL_OK!=testGetNewOrOld(interp, objv[2], &bNew)
+1 -1
View File
@@ -352,7 +352,7 @@ else
SQLITE_C_IS_SEE = 0
else
SQLITE_C_IS_SEE = 1
$(info This is an SEE build)
$(info $(emo.lock)$(emo.lock)$(emo.lock) This is an SEE build $(emo.lock)$(emo.lock)$(emo.lock))
endif
endif
+17 -12
View File
@@ -363,11 +363,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
called this way, the resolved value of the returned Promise is
the number of bytes written to the target file.
It very specifically requires the input to be an SQLite3
database and throws if that's not the case. It does so in
order to prevent this function from taking on a larger scope
than it is specifically intended to. i.e. we do not want it to
become a convenience for importing arbitrary files into OPFS.
It very specifically requires the input to be an SQLite3 database
and throws if that's not the case. It does so in order to
prevent this function from taking on a larger scope than it is
specifically intended to. i.e. we do not want it to become a
convenience for importing arbitrary files into OPFS. Caveat: as
of 3.54 in SEE builds, the "is this a db?" check is necessarily
lax to account for SEE-encrypted databases not having a legible
header. In such builds, only the db size is relevant for the
check.
This routine rewrites the database header bytes in the output
file (not the input array) to force disabling of WAL mode.
@@ -1099,20 +1103,21 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return promiseResolve_(sqlite3);
};
const options = opfsUtil.options;
let proxyUri = options.proxyUri +(
//#if not target:es6-bundler-friendly
const proxyUri = options.proxyUri +(
(options.proxyUri.indexOf('?')<0) ? '?' : '&'
)+'vfs='+vfsName;
//sqlite3.config.error("proxyUri",options.proxyUri, (new Error()));
//#/if
const W = opfsVfs.worker =
//#if target:es6-bundler-friendly
(()=>{
/* _Sigh_... */
switch(vfsName){
case 'opfs':
return new Worker(new URL("sqlite3-opfs-async-proxy.js?vfs=opfs", import.meta.url));
case 'opfs-wl':
return new Worker(new URL("sqlite3-opfs-async-proxy.js?vfs=opfs-wl", import.meta.url));
}
/* Discussion explaining this formulation:
https://github.com/sqlite/sqlite-wasm/pull/159 */
const url = new URL('sqlite3-opfs-async-proxy.js', import.meta.url);
url.searchParams.set('vfs', vfsName);
return new Worker(url.toString());
})();
//#elif target:es6-module
new Worker(new URL(proxyUri, import.meta.url));
+12 -2
View File
@@ -771,7 +771,11 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
lead bytes of that buffer do not hold a SQLite3 database header,
else it returns without side effects.
Added in 3.44.
Added in 3.44. As of 3.54 in SEE builds it performs only a
rudimentary length check and does not fail for invalid header
bytes, under the assumption that the input may be an encrypted
db. We cannot unambiguously distinguish an encrypted db from
garbage bytes.
*/
affirmDbHeader: function(bytes){
if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
@@ -781,7 +785,12 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
}
for(let i = 0; i < header.length; ++i){
if( header.charCodeAt(i) !== bytes[i] ){
//#if enable-see
break /* assume this might be an encrypted db.
https://sqlite.org/see/forumpost/f84bef3552 */;
//#else
toss3("Input does not contain an SQLite3 database header.");
//#/if
}
}
},
@@ -791,7 +800,8 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
database. It only examines the size and header, but further
checks may be added in the future.
Added in 3.44.
Added in 3.44. See notes in affirmDbHeader() regarding SEE
builds.
*/
affirmIsDb: function(bytes){
if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
-4
View File
@@ -811,10 +811,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
xOpen: function(pProtoVfs,zName,pProtoFile,flags,pOutFlags){
cache.popError();
let zToFree /* alloc()'d memory for temp db name */;
if( 0 ){
/* tester1.js makes it a lot further if we do this. */
flags |= capi.SQLITE_OPEN_CREATE;
}
try{
if( !zName ){
zToFree = wasm.allocCString(""+pProtoFile+"."
@@ -1040,19 +1040,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
importDb(name, bytes){
if( bytes instanceof ArrayBuffer ) bytes = new Uint8Array(bytes);
else if( bytes instanceof Function ) return this.importDbChunked(name, bytes);
util.affirmIsDb(bytes);
const sah = this.#mapFilenameToSAH.get(name)
|| this.nextAvailableSAH()
|| toss("No available handles to import to.");
const n = bytes.byteLength;
if(n<512 || n%512!=0){
toss("Byte array size is invalid for an SQLite db.");
}
const header = "SQLite format 3";
for(let i = 0; i < header.length; ++i){
if( header.charCodeAt(i) !== bytes[i] ){
toss("Input does not contain an SQLite database header.");
}
}
const nWrote = sah.write(bytes, {at: HEADER_OFFSET_DATA});
if(nWrote != n){
this.setAssociatedPath(sah, '', 0);
+1 -1
View File
@@ -1557,7 +1557,7 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_posix_create_file,
SQLITE_WASM_EXPORT2(const char *,sqlite3__wasm_kvvfsMakeKey,
(const char *zClass, const char *zKeyIn)){
static char buf[SQLITE_KVOS_SZ+1] = {0};
assert(sqlite3KvvfsMethods.nKeySize>24);
assert(sqlite3KvvfsMethods.nKeySize>32);
if( zClass && *zClass ){
kvrecordMakeKey(zClass, zKeyIn, buf);
return buf;
+94 -27
View File
@@ -380,16 +380,19 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
function which must return a constructor args object for ctor. It
is passed true if the db needs to be cleaned up/unlinked before
opening it (OPFS) and false if not (how that is done is
VFS-dependent). dbUnlink is a function which is expected to
unlink() the db file if the ctorOpfFunc does not do so when
passed true (kvvfs).
VFS-dependent). dbUnlink is a _synchronous_ function which is
expected to unlink() the db file if the ctorOpfFunc does not do
so when passed true (namely for kvvfs).
This function initializes the db described by ctorOptFunc(...),
writes some secret info into it, and re-opens it twice to
confirmi that it can be read with an SEE key and cannot be read
without one.
This function initializes the db described by the result of
ctorOptFunc(bool), writes some secret info into it, and re-opens
it twice to confirm that it can be read with an SEE key and
cannot be read without one.
If unlinkDbAtEnd is truthy then dbUnlink() is (on success)
called before returning.
*/
T.seeBaseCheck = function(ctor, ctorOptFunc, dbUnlink){
T.seeBaseCheck = function(ctor, ctorOptFunc, dbUnlink, unlinkDbAtEnd=true){
let initDb = true;
const tryKey = function(keyKey, key, expectCount){
let db;
@@ -450,7 +453,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
tryKey('hexkey', hexFoo, 3);
T.assert( !initDb );
tryKey('hexkey', hexFoo, 6);
dbUnlink();
if( unlinkDbAtEnd ) dbUnlink();
};
//#/if enable-see
@@ -3061,10 +3064,10 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
const JDb = sqlite3.oo1.JsStorageDb;
const pVfs = capi.sqlite3_vfs_find('kvvfs');
T.assert(looksLikePtr(pVfs));
let x = sqlite3.kvvfs.internal.storageForZClass('session');
T.assert( 0 === x.files.length )
.assert( globalThis.sessionStorage===x.storage )
.assert( 'kvvfs-session-' === x.keyPrefix );
const pStorage = sqlite3.kvvfs.internal.storageForZClass('session');
T.assert( 0 === pStorage.files.length )
.assert( globalThis.sessionStorage===pStorage.storage )
.assert( 'kvvfs-session-' === pStorage.keyPrefix );
const filename = this.kvvfsDbFile = 'session';
const unlink = this.kvvfsUnlink = ()=>sqlite3.kvvfs.clear(filename);
unlink();
@@ -3082,9 +3085,30 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
db.exec('insert into kvvfs(a) values(4),(5),(6)');
T.assert(6 === db.selectValue('select count(*) from kvvfs'));
}finally{
if( db ) db.close();
if( db ){
db.close();
db = null;
}
}
//console.debug("sessionStorage",globalThis.sessionStorage);
const corruptJrnl = [
/* Test that it recovers properly from a bad journal:
https://sqlite.org/bugs/forumpost/20e208fe172cae4f */
pStorage.keyPrefix+'jrnl',
'cb d9d505f920a163d7ffffffffdeadbeef000000010000020000001000'
];
sessionStorage.setItem(...corruptJrnl);
//console.debug("sessionStorage",globalThis.sessionStorage);
T.assert( corruptJrnl[1] === sessionStorage.getItem(corruptJrnl[0]) );
try{
db = new JDb(filename);
T.assert(6 === db.selectValue('select count(*) from kvvfs'));
db.exec('insert into kvvfs(a) values(7),(8),(9)');
T.assert(9 === db.selectValue('select count(*) from kvvfs'));
T.assert( corruptJrnl[1] !== sessionStorage.getItem(corruptJrnl[0]) );
}finally{
if( db ) db.close();
}
}
}/*kvvfs sanity checks*/)
.t({
@@ -3596,7 +3620,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
name: 'kvvfs SEE encryption in sessionStorage',
predicate: ()=>(!!globalThis.sessionStorage
|| "sessionStorage is not available"),
test: function(sqlite3){
test: async function(sqlite3){
const JDb = sqlite3.oo1.JsStorageDb;
T.seeBaseCheck(JDb,
(isInit)=>{
@@ -3923,16 +3947,39 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
.t({
name: '@vfsName@ with SEE encryption',
predicate: (sqlite3)=>!!sqlite3.oo1.@oo1Ctor@,
test: function(sqlite3){
T.seeBaseCheck(
sqlite3.oo1.@oo1Ctor@,
function(isInit){
const opt = {filename: 'file:///sqlite3-see.edb'};
if( isInit ) opt.filename += '?delete-before-open=1';
return opt;
},
()=>{}
);
test: async function(sqlite3){
const ctor = sqlite3.oo1.@oo1Ctor@;
const filename = 'sqlite3-see.edb';
const ctorOpt = {filename: 'file:///'+filename};
const opfs = sqlite3.opfs;
const initer = function(isInit){
const opt = {...ctorOpt};
if( isInit ) opt.filename += '?delete-before-open=1';
return opt;
};
T.seeBaseCheck(ctor, initer, ()=>{}, false);
/*
Ensure that ctor.importDb() correctly handles SEE-encrypted
dbs. For more details search below for forumpost/f84bef3552.
*/
let exp = await opfs.getRootDir()
.then(d=>d.getFileHandle(filename))
.then(fh=>fh.getFile())
.then(f=>f.bytes());
await opfs.unlink(filename);
T.assert( exp.byteLength > 100 && 0===(exp.byteLength % 512) );
const got = await ctor.importDb(filename, exp);
T.assert(exp.byteLength === got);
exp = null;
const db = new ctor({...initer(false), key: 'foo'});
try{
T.assert( 4 === db.selectValue("select count(*) from t") )
.assert( 6 === db.selectValue("select sum(a) from t") );
}finally{
db.close();
await opfs.unlink(filename);
}
}
})
//#/if enable-see
@@ -3940,7 +3987,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
//#/query
////////////////////////////////////////////////////////////////////////
T.g('OPFS SyncAccessHandle Pool VFS',
(sqlite3)=>(!!sqlite3.installOpfsSAHPoolVfs || "requires OPFS SAH Pool APIs"))
(sqlite3)=>((isWorker() && !!sqlite3.installOpfsSAHPoolVfs) || "requires OPFS SAH Pool APIs"))
.t({
name: 'SAH sanity checks',
test: async function(sqlite3){
@@ -4136,12 +4183,32 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
}
let poolUtil;
const P1 = await inst(poolConfig).then(u=>poolUtil = u).catch(catcher);
if( !poolUtil ) return;
const dbFile = '/sqlite3-see.edb';
T.seeBaseCheck(
poolUtil.OpfsSAHPoolDb,
(isInit)=>{return {filename: dbFile}},
()=>poolUtil.unlink(dbFile)
()=>poolUtil.unlink(dbFile),
false
);
/* Ensure that importDb() does the right thing for an
SEE-encrypted DB: https://sqlite.org/see/forumpost/f84bef3552 */
let exp = poolUtil.exportFile(dbFile);
T.assert( exp.byteLength > 100 && 0===(exp.byteLength % 512) );
poolUtil.unlink(dbFile);
const got = poolUtil.importDb(dbFile, exp);
T.assert(exp.byteLength === got);
exp = null;
const db = new poolUtil.OpfsSAHPoolDb({
filename: dbFile,
key: 'foo'
});
try{
T.assert( 4 === db.selectValue("select count(*) from t") )
.assert( 6 === db.selectValue("select sum(a) from t") );
}finally{
db.close();
}
poolUtil.removeVfs();
}
})/*opfs-sahpool with SEE*/
+12 -8
View File
@@ -986,6 +986,7 @@ FUZZCHECK_SRC = sqlite3.c \
$(TOP)/ext/misc/base85.c \
$(TOP)/ext/misc/completion.c \
$(TOP)/ext/misc/decimal.c \
$(TOP)/ext/misc/diskused.c \
$(TOP)/ext/misc/ieee754.c \
$(TOP)/ext/misc/randomjson.c \
$(TOP)/ext/misc/regexp.c \
@@ -1678,7 +1679,8 @@ tclsqlite3.c: sqlite3.c tclsqlite-ex.c
#
# $(CFLAGS.tclextension) = CFLAGS for the tclextension* targets.
#
CFLAGS.tclextension = $(CFLAGS.intree_includes) $(CFLAGS.env) $(OPT_FEATURE_FLAGS) $(OPTS)
CFLAGS.tclextension = $(CFLAGS.intree_includes) $(CFLAGS.env) \
$(OPT_FEATURE_FLAGS) $(OPTS) $(CFLAGS.icu)
#
# Build the SQLite TCL extension in a way that make it compatible
# with whatever version of TCL is running as $TCLSH_CMD, possibly defined
@@ -1686,7 +1688,8 @@ CFLAGS.tclextension = $(CFLAGS.intree_includes) $(CFLAGS.env) $(OPT_FEATURE_FLAG
#
tclextension: tclsqlite3.c
$(TCLSH_CMD) $(TOP)/tool/buildtclext.tcl --build-only \
--tclConfig.sh $(TCL_CONFIG_SH) --cc "$(T.cc)" $(CFLAGS.tclextension)
--tclConfig.sh $(TCL_CONFIG_SH) --cc "$(T.cc)" \
--extlibs "$(LDFLAGS.icu)" $(CFLAGS.tclextension)
#
# Install the SQLite TCL extension in a way that is appropriate for $TCLSH_CMD
@@ -1694,7 +1697,8 @@ tclextension: tclsqlite3.c
#
tclextension-install: tclsqlite3.c
$(TCLSH_CMD) $(TOP)/tool/buildtclext.tcl --destdir "$(DESTDIR)" \
--tclConfig.sh $(TCL_CONFIG_SH) --cc "$(T.cc)" $(CFLAGS.tclextension)
--tclConfig.sh $(TCL_CONFIG_SH) --cc "$(T.cc)" \
--extlibs "$(LDFLAGS.icu)" $(CFLAGS.tclextension)
#
# Uninstall the SQLite TCL extension that is used by $TCLSH_CMD.
@@ -2232,11 +2236,6 @@ install: install-man1
install-pc: sqlite3.pc $(install-dir.pkgconfig)
$(INSTALL.noexec) sqlite3.pc "$(install-dir.pkgconfig)"
scrub$(T.exe): $(TOP)/ext/misc/scrub.c sqlite3.o
$(T.link) -o $@ -I. -DSCRUB_STANDALONE \
$(TOP)/ext/misc/scrub.c sqlite3.o $(LDFLAGS.libsqlite3)
xbin: scrub$(T.exe)
srcck1$(B.exe): $(TOP)/tool/srcck1.c
$(B.cc) -o srcck1$(B.exe) $(TOP)/tool/srcck1.c
xbin: srcck1$(B.exe)
@@ -2346,6 +2345,7 @@ SHELL_DEP = \
$(TOP)/ext/misc/base85.c \
$(TOP)/ext/misc/completion.c \
$(TOP)/ext/misc/decimal.c \
$(TOP)/ext/misc/diskused.c \
$(TOP)/ext/misc/fileio.c \
$(TOP)/ext/misc/ieee754.c \
$(TOP)/ext/misc/memtrace.c \
@@ -2422,6 +2422,10 @@ sqlite3session.o: $(TOP)/ext/session/sqlite3session.c $(DEPS_EXT_COMMON)
stmt.o: $(TOP)/ext/misc/stmt.c $(DEPS_EXT_COMMON)
$(T.cc.extension) -c $(TOP)/ext/misc/stmt.c
$(AUXTEST): $(TOP)/test/c/$(AUXTEST).c
$(T.cc.sqlite) -o $@ $(TOP)/test/c/$(AUXTEST).c sqlite3.o $(LDFLAGS.libsqlite3)
#
# Windows section
#
+203 -193
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,2 +1,2 @@
branch corrupt-changeset_fix
tag corrupt-changeset_fix
branch trunk
tag trunk
+1 -1
View File
@@ -1 +1 @@
0de91ff0d798cff21289d893cc441b89fc37b051d5cdf611d92d3ae2bc41cdf2
c9171221160f241b450d425f41bb53b42362dccfad4c3fdf64dd5c3562ed4096
+25 -14
View File
@@ -2661,9 +2661,8 @@ static void addConstraintFunc(
int iCol = sqlite3_value_int(argv[2]);
int iOff = 0;
int ii;
char *zNew = 0;
sqlite3_str *pNew;
int t = 0;
sqlite3 *db;
UNUSED_PARAMETER(NotUsed);
if( skipCreateTable(ctx, zSql, &iOff) ) return;
@@ -2683,13 +2682,11 @@ static void addConstraintFunc(
iOff += getWhitespace(&zSql[iOff]);
db = sqlite3_context_db_handle(ctx);
if( iCol<0 ){
zNew = sqlite3MPrintf(db, "%.*s, %s%s", iOff, zSql, zCons, &zSql[iOff]);
}else{
zNew = sqlite3MPrintf(db, "%.*s %s%s", iOff, zSql, zCons, &zSql[iOff]);
}
sqlite3_result_text(ctx, zNew, -1, SQLITE_DYNAMIC);
pNew = sqlite3_str_new(sqlite3_context_db_handle(ctx));
sqlite3_str_append(pNew, (const char*)zSql, iOff);
if( iCol<0 ) sqlite3_str_append(pNew, ",", 1);
sqlite3_str_appendf(pNew, " %s%s", zCons, &zSql[iOff]);
sqlite3_result_str(ctx, pNew, SQLITE_FINISH);
}
/*
@@ -2798,7 +2795,9 @@ void sqlite3AlterDropConstraint(
if( !pTab ) return;
if( pCons ){
zArg = sqlite3MPrintf(db, "%.*Q", pCons->n, pCons->z);
char *z = sqlite3NameFromToken(db, pCons);
zArg = sqlite3MPrintf(db, "%Q", z);
sqlite3DbFree(db, z);
}else{
int iCol;
if( alterFindCol(pParse, pTab, pCol, &iCol) ) return;
@@ -2985,19 +2984,31 @@ void sqlite3AlterAddConstraint(
SrcList *pSrc, /* Table to add constraint to */
Token *pFirst, /* First token of new constraint */
Token *pName, /* Name of new constraint. NULL if name omitted. */
const char *pExpr, /* Text of CHECK expression */
int nExpr /* Size of pExpr in bytes */
const char *zExpr, /* Text of CHECK expression */
int nExpr, /* Size of pExpr in bytes */
Expr *pExpr /* The parsed CHECK expression */
){
Table *pTab = 0; /* Table identified by pSrc */
int iDb = 0; /* Which schema does pTab live in */
const char *zDb = 0; /* Name of the schema in which pTab lives */
const char *pCons = 0; /* Text of the constraint */
int nCons; /* Bytes of text to use from pCons[] */
int rc; /* Result from error checking pExpr */
/* Look up the table being altered. */
assert( pSrc->nSrc==1 );
pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, 1);
if( !pTab ) return;
if( !pTab ){
sqlite3ExprDelete(pParse->db, pExpr);
return;
}
/* Verify that the new CHECK constraint does not contain any
** internal-use-only function. Forum post 2026-05-10T01:11:28Z
*/
rc = sqlite3ResolveSelfReference(pParse, pTab, NC_IsCheck, pExpr, 0);
sqlite3ExprDelete(pParse->db, pExpr);
if( rc ) return;
/* If this new constraint has a name, check that it is not a duplicate of
** an existing constraint. It is an error if it is. */
@@ -3018,7 +3029,7 @@ void sqlite3AlterAddConstraint(
sqlite3NestedParse(pParse,
"SELECT sqlite_fail('constraint failed', %d) "
"FROM %Q.%Q WHERE (%.*s) IS NOT TRUE",
SQLITE_CONSTRAINT, zDb, pTab->zName, nExpr, pExpr
SQLITE_CONSTRAINT, zDb, pTab->zName, nExpr, zExpr
);
/* Edit the SQL for the named table. */
+6 -6
View File
@@ -876,7 +876,7 @@ static void statGet(
assert( p->current.anEq[i] || p->nRow==0 );
#endif
}
sqlite3ResultStrAccum(context, &sStat);
sqlite3_result_str(context, &sStat, SQLITE_XFER);
}
#ifdef SQLITE_ENABLE_STAT4
else if( eCall==STAT_GET_ROWID ){
@@ -913,7 +913,7 @@ static void statGet(
sqlite3_str_appendf(&sStat, "%llu ", (u64)aCnt[i]);
}
if( sStat.nChar ) sStat.nChar--;
sqlite3ResultStrAccum(context, &sStat);
sqlite3_result_str(context, &sStat, SQLITE_XFER);
}
#endif /* SQLITE_ENABLE_STAT4 */
#ifndef SQLITE_DEBUG
@@ -1814,9 +1814,9 @@ static int loadStatTbl(
}
pIdx->nSampleCol = nIdxCol;
pIdx->mxSample = nSample;
nByte = ROUND8(sizeof(IndexSample) * nSample);
nByte += sizeof(tRowcnt) * nIdxCol * 3 * nSample;
nByte += nIdxCol * sizeof(tRowcnt); /* Space for Index.aAvgEq[] */
nByte = ROUND8(sizeof64(IndexSample) * nSample);
nByte += sizeof64(tRowcnt) * nIdxCol * 3 * nSample;
nByte += nIdxCol * sizeof64(tRowcnt); /* Space for Index.aAvgEq[] */
pIdx->aSample = sqlite3DbMallocZero(db, nByte);
if( pIdx->aSample==0 ){
@@ -1824,7 +1824,7 @@ static int loadStatTbl(
return SQLITE_NOMEM_BKPT;
}
pPtr = (u8*)pIdx->aSample;
pPtr += ROUND8(nSample*sizeof(pIdx->aSample[0]));
pPtr += ROUND8(nSample*sizeof64(pIdx->aSample[0]));
pSpace = (tRowcnt*)pPtr;
assert( EIGHT_BYTE_ALIGNMENT( pSpace ) );
pIdx->aAvgEq = pSpace; pSpace += nIdxCol;
+11 -2
View File
@@ -106,6 +106,16 @@ static void attachFunc(
** from sqlite3_deserialize() to close database db->init.iDb and
** reopen it as a MemDB */
Btree *pNewBt = 0;
pNew = &db->aDb[db->init.iDb];
assert( pNew->pBt!=0 );
if( sqlite3BtreeTxnState(pNew->pBt)!=SQLITE_TXN_NONE
|| sqlite3BtreeIsInBackup(pNew->pBt)
){
rc = SQLITE_BUSY;
goto attach_error;
}
pVfs = sqlite3_vfs_find("memdb");
if( pVfs==0 ) return;
rc = sqlite3BtreeOpen(pVfs, "x\0", db, &pNewBt, 0, SQLITE_OPEN_MAIN_DB);
@@ -115,8 +125,7 @@ static void attachFunc(
/* Both the Btree and the new Schema were allocated successfully.
** Close the old db and update the aDb[] slot with the new memdb
** values. */
pNew = &db->aDb[db->init.iDb];
if( ALWAYS(pNew->pBt) ) sqlite3BtreeClose(pNew->pBt);
sqlite3BtreeClose(pNew->pBt);
pNew->pBt = pNewBt;
pNew->pSchema = pNewSchema;
}else{
+21 -2
View File
@@ -189,8 +189,14 @@ void sqlite3AuthRead(
** either SQLITE_OK (zero) or SQLITE_IGNORE or SQLITE_DENY. If SQLITE_DENY
** is returned, then the error count and error message in pParse are
** modified appropriately.
**
** Divided into two routines. realAuthCheck() does the work. The
** sqlite3AuthCheck() routine is usually a fast no-op but invokes
** realAuthCheck() (and spends time doing some stack pushes and pops
** as a result) in the uncommon case where an authorization check is
** actually needed.
*/
int sqlite3AuthCheck(
static int SQLITE_NOINLINE realAuthCheck(
Parse *pParse,
int code,
const char *zArg1,
@@ -204,7 +210,7 @@ int sqlite3AuthCheck(
** or if the parser is being invoked from within sqlite3_declare_vtab.
*/
assert( !IN_RENAME_OBJECT || db->xAuth==0 );
if( db->xAuth==0 || db->init.busy || IN_SPECIAL_PARSE ){
if( IN_SPECIAL_PARSE ){
return SQLITE_OK;
}
@@ -229,6 +235,19 @@ int sqlite3AuthCheck(
}
return rc;
}
int sqlite3AuthCheck(
Parse *pParse,
int code,
const char *zArg1,
const char *zArg2,
const char *zArg3
){
if( pParse->db->xAuth!=0 && pParse->db->init.busy==0 ){
return realAuthCheck(pParse,code,zArg1,zArg2,zArg3);
}else{
return SQLITE_OK;
}
}
/*
** Push an authorization context. After this routine is called, the
+66 -54
View File
@@ -20,13 +20,13 @@
*/
struct sqlite3_backup {
sqlite3* pDestDb; /* Destination database handle */
Btree *pDest; /* Destination b-tree file */
Db *pDest; /* Destination db file */
u32 iDestSchema; /* Original schema cookie in destination */
int bDestLocked; /* True once a write-transaction is open on pDest */
Pgno iNext; /* Page number of the next source page to copy */
sqlite3* pSrcDb; /* Source database handle */
Btree *pSrc; /* Source b-tree file */
Db *pSrc; /* Source db file */
int rc; /* Backup process error code */
@@ -79,7 +79,7 @@ struct sqlite3_backup {
** function. If an error occurs while doing so, return 0 and write an
** error message to pErrorDb.
*/
static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){
static Db *findDatabase(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){
int i = sqlite3FindDbName(pDb, zDb);
if( i==1 ){
@@ -102,7 +102,7 @@ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){
return 0;
}
return pDb->aDb[i].pBt;
return &pDb->aDb[i];
}
/*
@@ -110,9 +110,9 @@ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){
** of the source.
*/
static int setDestPgsz(sqlite3_backup *p){
int rc;
rc = sqlite3BtreeSetPageSize(p->pDest,sqlite3BtreeGetPageSize(p->pSrc),0,0);
return rc;
return sqlite3BtreeSetPageSize(p->pDest->pBt,
sqlite3BtreeGetPageSize(p->pSrc->pBt), 0, 0
);
}
/*
@@ -181,15 +181,15 @@ sqlite3_backup *sqlite3_backup_init(
/* If the allocation succeeded, populate the new object. */
if( p ){
p->pSrc = findBtree(pDestDb, pSrcDb, zSrcDb);
p->pDest = findBtree(pDestDb, pDestDb, zDestDb);
p->pSrc = findDatabase(pDestDb, pSrcDb, zSrcDb);
p->pDest = findDatabase(pDestDb, pDestDb, zDestDb);
p->pDestDb = pDestDb;
p->pSrcDb = pSrcDb;
p->iNext = 1;
p->isAttached = 0;
if( 0==p->pSrc || 0==p->pDest
|| checkReadTransaction(pDestDb, p->pDest)!=SQLITE_OK
|| checkReadTransaction(pDestDb, p->pDest->pBt)!=SQLITE_OK
){
/* One (or both) of the named databases did not exist or an OOM
** error was hit. Or there is a transaction open on the destination
@@ -201,7 +201,7 @@ sqlite3_backup *sqlite3_backup_init(
}
}
if( p ){
p->pSrc->nBackup++;
p->pSrc->pBt->nBackup++;
}
sqlite3_mutex_leave(pDestDb->mutex);
@@ -229,18 +229,18 @@ static int backupOnePage(
const u8 *zSrcData, /* Source database page data */
int bUpdate /* True for an update, false otherwise */
){
Pager * const pDestPager = sqlite3BtreePager(p->pDest);
const int nSrcPgsz = sqlite3BtreeGetPageSize(p->pSrc);
int nDestPgsz = sqlite3BtreeGetPageSize(p->pDest);
Pager * const pDestPager = sqlite3BtreePager(p->pDest->pBt);
const int nSrcPgsz = sqlite3BtreeGetPageSize(p->pSrc->pBt);
int nDestPgsz = sqlite3BtreeGetPageSize(p->pDest->pBt);
const int nCopy = MIN(nSrcPgsz, nDestPgsz);
const i64 iEnd = (i64)iSrcPg*(i64)nSrcPgsz;
int rc = SQLITE_OK;
i64 iOff;
assert( sqlite3BtreeGetReserveNoMutex(p->pSrc)>=0 );
assert( sqlite3BtreeGetReserveNoMutex(p->pSrc->pBt)>=0 );
assert( p->bDestLocked );
assert( !isFatalError(p->rc) );
assert( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) );
assert( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt->pBt) );
assert( zSrcData );
assert( nSrcPgsz==nDestPgsz || sqlite3PagerIsMemdb(pDestPager)==0 );
@@ -251,7 +251,7 @@ static int backupOnePage(
for(iOff=iEnd-(i64)nSrcPgsz; rc==SQLITE_OK && iOff<iEnd; iOff+=nDestPgsz){
DbPage *pDestPg = 0;
Pgno iDest = (Pgno)(iOff/nDestPgsz)+1;
if( iDest==PENDING_BYTE_PAGE(p->pDest->pBt) ) continue;
if( iDest==PENDING_BYTE_PAGE(p->pDest->pBt->pBt) ) continue;
if( SQLITE_OK==(rc = sqlite3PagerGet(pDestPager, iDest, &pDestPg, 0))
&& SQLITE_OK==(rc = sqlite3PagerWrite(pDestPg))
){
@@ -269,7 +269,7 @@ static int backupOnePage(
memcpy(zOut, zIn, nCopy);
((u8 *)sqlite3PagerGetExtra(pDestPg))[0] = 0;
if( iOff==0 && bUpdate==0 ){
sqlite3Put4byte(&zOut[28], sqlite3BtreeLastPage(p->pSrc));
sqlite3Put4byte(&zOut[28], sqlite3BtreeLastPage(p->pSrc->pBt));
}
}
sqlite3PagerUnref(pDestPg);
@@ -301,8 +301,8 @@ static int backupTruncateFile(sqlite3_file *pFile, i64 iSize){
*/
static void attachBackupObject(sqlite3_backup *p){
sqlite3_backup **pp;
assert( sqlite3BtreeHoldsMutex(p->pSrc) );
pp = sqlite3PagerBackupPtr(sqlite3BtreePager(p->pSrc));
assert( sqlite3BtreeHoldsMutex(p->pSrc->pBt) );
pp = sqlite3PagerBackupPtr(sqlite3BtreePager(p->pSrc->pBt));
p->pNext = *pp;
*pp = p;
p->isAttached = 1;
@@ -316,20 +316,26 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
int destMode; /* Destination journal mode */
int pgszSrc = 0; /* Source page size */
int pgszDest = 0; /* Destination page size */
Btree *pDest;
Btree *pSrc;
#ifdef SQLITE_ENABLE_API_ARMOR
if( p==0 ) return SQLITE_MISUSE_BKPT;
#endif
assert( p->pDest );
assert( p->pSrc );
pDest = p->pDest->pBt;
pSrc = p->pSrc->pBt;
sqlite3_mutex_enter(p->pSrcDb->mutex);
sqlite3BtreeEnter(p->pSrc);
sqlite3BtreeEnter(pSrc);
if( p->pDestDb ){
sqlite3_mutex_enter(p->pDestDb->mutex);
}
rc = p->rc;
if( !isFatalError(rc) ){
Pager * const pSrcPager = sqlite3BtreePager(p->pSrc); /* Source pager */
Pager * const pDestPager = sqlite3BtreePager(p->pDest); /* Dest pager */
Pager * const pSrcPager = sqlite3BtreePager(pSrc); /* Source pager */
Pager * const pDestPager = sqlite3BtreePager(pDest); /* Dest pager */
int ii; /* Iterator variable */
int nSrcPage = -1; /* Size of source db in pages */
int bCloseTrans = 0; /* True if src db requires unlocking */
@@ -337,7 +343,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
/* If the source pager is currently in a write-transaction, return
** SQLITE_BUSY immediately.
*/
if( p->pDestDb && p->pSrc->pBt->inTransaction==TRANS_WRITE ){
if( p->pDestDb && pSrc->pBt->inTransaction==TRANS_WRITE ){
rc = SQLITE_BUSY;
}else{
rc = SQLITE_OK;
@@ -347,8 +353,8 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
** one now. If a transaction is opened here, then it will be closed
** before this function exits.
*/
if( rc==SQLITE_OK && SQLITE_TXN_NONE==sqlite3BtreeTxnState(p->pSrc) ){
rc = sqlite3BtreeBeginTrans(p->pSrc, 0, 0);
if( rc==SQLITE_OK && SQLITE_TXN_NONE==sqlite3BtreeTxnState(pSrc) ){
rc = sqlite3BtreeBeginTrans(pSrc, 0, 0);
bCloseTrans = 1;
}
@@ -364,7 +370,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
/* Lock the destination database, if it is not locked already. */
if( SQLITE_OK==rc && p->bDestLocked==0
&& SQLITE_OK==(rc = sqlite3BtreeBeginTrans(p->pDest, 2,
&& SQLITE_OK==(rc = sqlite3BtreeBeginTrans(pDest, 2,
(int*)&p->iDestSchema))
){
p->bDestLocked = 1;
@@ -372,9 +378,9 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
/* Do not allow backup if the destination database is in WAL mode
** and the page sizes are different between source and destination */
pgszSrc = sqlite3BtreeGetPageSize(p->pSrc);
pgszDest = sqlite3BtreeGetPageSize(p->pDest);
destMode = sqlite3PagerGetJournalMode(sqlite3BtreePager(p->pDest));
pgszSrc = sqlite3BtreeGetPageSize(pSrc);
pgszDest = sqlite3BtreeGetPageSize(pDest);
destMode = sqlite3PagerGetJournalMode(sqlite3BtreePager(pDest));
if( SQLITE_OK==rc
&& (destMode==PAGER_JOURNALMODE_WAL || sqlite3PagerIsMemdb(pDestPager))
&& pgszSrc!=pgszDest
@@ -385,11 +391,11 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
/* Now that there is a read-lock on the source database, query the
** source pager for the number of pages in the database.
*/
nSrcPage = (int)sqlite3BtreeLastPage(p->pSrc);
nSrcPage = (int)sqlite3BtreeLastPage(pSrc);
assert( nSrcPage>=0 );
for(ii=0; (nPage<0 || ii<nPage) && p->iNext<=(Pgno)nSrcPage && !rc; ii++){
const Pgno iSrcPg = p->iNext; /* Source page number */
if( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) ){
if( iSrcPg!=PENDING_BYTE_PAGE(pSrc->pBt) ){
DbPage *pSrcPg; /* Source page object */
rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg,PAGER_GET_READONLY);
if( rc==SQLITE_OK ){
@@ -416,18 +422,18 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
*/
if( rc==SQLITE_DONE ){
if( nSrcPage==0 ){
rc = sqlite3BtreeNewDb(p->pDest);
rc = sqlite3BtreeNewDb(pDest);
nSrcPage = 1;
}
if( rc==SQLITE_OK || rc==SQLITE_DONE ){
rc = sqlite3BtreeUpdateMeta(p->pDest,1,p->iDestSchema+1);
rc = sqlite3BtreeUpdateMeta(pDest,1,p->iDestSchema+1);
}
if( rc==SQLITE_OK ){
if( p->pDestDb ){
sqlite3ResetAllSchemasOfConnection(p->pDestDb);
}
if( destMode==PAGER_JOURNALMODE_WAL ){
rc = sqlite3BtreeSetVersion(p->pDest, 2);
rc = sqlite3BtreeSetVersion(pDest, 2);
}
}
if( rc==SQLITE_OK ){
@@ -444,12 +450,12 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
** journalled by PagerCommitPhaseOne() before they are destroyed
** by the file truncation.
*/
assert( pgszSrc==sqlite3BtreeGetPageSize(p->pSrc) );
assert( pgszDest==sqlite3BtreeGetPageSize(p->pDest) );
assert( pgszSrc==sqlite3BtreeGetPageSize(pSrc) );
assert( pgszDest==sqlite3BtreeGetPageSize(pDest) );
if( pgszSrc<pgszDest ){
int ratio = pgszDest/pgszSrc;
nDestTruncate = (nSrcPage+ratio-1)/ratio;
if( nDestTruncate==(int)PENDING_BYTE_PAGE(p->pDest->pBt) ){
if( nDestTruncate==(int)PENDING_BYTE_PAGE(pDest->pBt) ){
nDestTruncate--;
}
}else{
@@ -477,7 +483,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
assert( pFile );
assert( nDestTruncate==0
|| (i64)nDestTruncate*(i64)pgszDest >= iSize || (
nDestTruncate==(int)(PENDING_BYTE_PAGE(p->pDest->pBt)-1)
nDestTruncate==(int)(PENDING_BYTE_PAGE(pDest->pBt)-1)
&& iSize>=PENDING_BYTE && iSize<=PENDING_BYTE+pgszDest
));
@@ -489,7 +495,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
** journal file. */
sqlite3PagerPagecount(pDestPager, &nDstPage);
for(iPg=nDestTruncate; rc==SQLITE_OK && iPg<=(Pgno)nDstPage; iPg++){
if( iPg!=PENDING_BYTE_PAGE(p->pDest->pBt) ){
if( iPg!=PENDING_BYTE_PAGE(pDest->pBt) ){
DbPage *pPg;
rc = sqlite3PagerGet(pDestPager, iPg, &pPg, 0);
if( rc==SQLITE_OK ){
@@ -533,7 +539,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
/* Finish committing the transaction to the destination database. */
if( SQLITE_OK==rc
&& SQLITE_OK==(rc = sqlite3BtreeCommitPhaseTwo(p->pDest, 0))
&& SQLITE_OK==(rc = sqlite3BtreeCommitPhaseTwo(pDest, 0))
){
rc = SQLITE_DONE;
}
@@ -547,8 +553,8 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
*/
if( bCloseTrans ){
TESTONLY( int rc2 );
TESTONLY( rc2 = ) sqlite3BtreeCommitPhaseOne(p->pSrc, 0);
TESTONLY( rc2 |= ) sqlite3BtreeCommitPhaseTwo(p->pSrc, 0);
TESTONLY( rc2 = ) sqlite3BtreeCommitPhaseOne(pSrc, 0);
TESTONLY( rc2 |= ) sqlite3BtreeCommitPhaseTwo(pSrc, 0);
assert( rc2==SQLITE_OK );
}
@@ -560,7 +566,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
if( p->pDestDb ){
sqlite3_mutex_leave(p->pDestDb->mutex);
}
sqlite3BtreeLeave(p->pSrc);
sqlite3BtreeLeave(pSrc);
sqlite3_mutex_leave(p->pSrcDb->mutex);
return rc;
}
@@ -577,17 +583,17 @@ int sqlite3_backup_finish(sqlite3_backup *p){
if( p==0 ) return SQLITE_OK;
pSrcDb = p->pSrcDb;
sqlite3_mutex_enter(pSrcDb->mutex);
sqlite3BtreeEnter(p->pSrc);
sqlite3BtreeEnter(p->pSrc->pBt);
if( p->pDestDb ){
sqlite3_mutex_enter(p->pDestDb->mutex);
}
/* Detach this backup from the source pager. */
if( p->pDestDb ){
p->pSrc->nBackup--;
p->pSrc->pBt->nBackup--;
}
if( p->isAttached ){
pp = sqlite3PagerBackupPtr(sqlite3BtreePager(p->pSrc));
pp = sqlite3PagerBackupPtr(sqlite3BtreePager(p->pSrc->pBt));
assert( pp!=0 );
while( *pp!=p ){
pp = &(*pp)->pNext;
@@ -597,7 +603,7 @@ int sqlite3_backup_finish(sqlite3_backup *p){
}
/* If a transaction is still open on the Btree, roll it back. */
sqlite3BtreeRollback(p->pDest, SQLITE_OK, 0);
sqlite3BtreeRollback(p->pDest->pBt, SQLITE_OK, 0);
/* Set the error code of the destination database handle. */
rc = (p->rc==SQLITE_DONE) ? SQLITE_OK : p->rc;
@@ -607,7 +613,7 @@ int sqlite3_backup_finish(sqlite3_backup *p){
/* Exit the mutexes and free the backup context structure. */
sqlite3LeaveMutexAndCloseZombie(p->pDestDb);
}
sqlite3BtreeLeave(p->pSrc);
sqlite3BtreeLeave(p->pSrc->pBt);
if( p->pDestDb ){
/* EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a
** call to sqlite3_backup_init() and is destroyed by a call to
@@ -665,7 +671,7 @@ static SQLITE_NOINLINE void backupUpdate(
){
assert( p!=0 );
do{
assert( sqlite3_mutex_held(p->pSrc->pBt->mutex) );
assert( sqlite3_mutex_held(p->pSrc->pBt->pBt->mutex) );
if( !isFatalError(p->rc) && iPage<p->iNext ){
/* The backup process p has already copied page iPage. But now it
** has been modified by a transaction on the source pager. Copy
@@ -701,7 +707,7 @@ void sqlite3BackupUpdate(sqlite3_backup *pBackup, Pgno iPage, const u8 *aData){
void sqlite3BackupRestart(sqlite3_backup *pBackup){
sqlite3_backup *p; /* Iterator variable */
for(p=pBackup; p; p=p->pNext){
assert( sqlite3_mutex_held(p->pSrc->pBt->mutex) );
assert( sqlite3_mutex_held(p->pSrc->pBt->pBt->mutex) );
p->iNext = 1;
}
}
@@ -719,6 +725,8 @@ int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
int rc;
sqlite3_file *pFd; /* File descriptor for database pTo */
sqlite3_backup b;
Db dbDest;
Db dbSrc;
sqlite3BtreeEnter(pTo);
sqlite3BtreeEnter(pFrom);
@@ -737,9 +745,13 @@ int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
** from this function, not directly by the user.
*/
memset(&b, 0, sizeof(b));
memset(&dbDest, 0, sizeof(dbDest));
memset(&dbSrc, 0, sizeof(dbSrc));
dbDest.pBt = pTo;
dbSrc.pBt = pFrom;
b.pSrcDb = pFrom->db;
b.pSrc = pFrom;
b.pDest = pTo;
b.pSrc = &dbSrc;
b.pDest = &dbDest;
b.iNext = 1;
/* 0x7FFFFFFF is the hard limit for the number of pages in a database
@@ -755,7 +767,7 @@ int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
if( rc==SQLITE_OK ){
pTo->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED;
}else{
sqlite3PagerClearCache(sqlite3BtreePager(b.pDest));
sqlite3PagerClearCache(sqlite3BtreePager(pTo));
}
assert( sqlite3BtreeTxnState(pTo)!=SQLITE_TXN_WRITE );
+33 -1
View File
@@ -15,6 +15,16 @@
*/
#include "btreeInt.h"
/*
** Suppress false-positive compiler warnings from GCC. Warnings are
** re-enabled at the bottom of this source file.
*/
#if defined(__GNUC__) && __GNUC__>=11
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wstringop-overread"
# pragma GCC diagnostic ignored "-Wstringop-overflow"
#endif
/*
** The header string that appears at the beginning of every
** SQLite database.
@@ -5185,7 +5195,9 @@ static int accessPayload(
** means "not yet known" (the cache is lazily populated).
*/
if( (pCur->curFlags & BTCF_ValidOvfl)==0 ){
int nOvfl = (pCur->info.nPayload-pCur->info.nLocal+ovflSize-1)/ovflSize;
i64 nOvfl = pCur->info.nPayload;
testcase( nOvfl - pCur->info.nLocal + ovflSize - 1 > 0xffffffffU );
nOvfl = (nOvfl - pCur->info.nLocal + ovflSize-1)/ovflSize;
if( pCur->aOverflow==0
|| nOvfl*(int)sizeof(Pgno) > sqlite3MallocSize(pCur->aOverflow)
){
@@ -5290,6 +5302,14 @@ static int accessPayload(
(eOp==0 ? PAGER_GET_READONLY : 0)
);
if( rc==SQLITE_OK ){
if( eOp!=0
&& (sqlite3PagerPageRefcount(pDbPage)!=1
|| NEVER(((MemPage*)sqlite3PagerGetExtra(pDbPage))->isInit))
&& sqlite3FaultSim(411)==SQLITE_OK
){
sqlite3PagerUnref(pDbPage);
return SQLITE_CORRUPT_PAGE(pPage);
}
aPayload = sqlite3PagerGetData(pDbPage);
nextPage = get4byte(aPayload);
rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage);
@@ -10964,6 +10984,11 @@ static int checkTreePage(
doCoverageCheck = 0;
continue;
}
if( info.nPayload && info.pPayload[0]<2 ){
checkAppendMsg(pCheck, "Bad cell header size");
doCoverageCheck = 0;
continue;
}
/* Check for integer primary key out of range */
if( pPage->intKey ){
@@ -11566,3 +11591,10 @@ int sqlite3BtreeConnectionCount(Btree *p){
return p->pBt->nRef;
}
#endif
/* Re-enable GCC compiler warnings that were suppressed at the top
** of this source file to prevent annoying false-positives.
*/
#if defined(__GNUC__) && __GNUC__>=11
# pragma GCC diagnostic pop
#endif
+21 -2
View File
@@ -70,6 +70,7 @@ static SQLITE_NOINLINE void lockTable(
assert( pToplevel->nTableLock < 0x7fff0000 );
nBytes = sizeof(TableLock) * (pToplevel->nTableLock+1);
if( pToplevel->nTableLock==0 ) pToplevel->aTableLock = 0;
pToplevel->aTableLock =
sqlite3DbReallocOrFree(pToplevel->db, pToplevel->aTableLock, nBytes);
if( pToplevel->aTableLock ){
@@ -236,7 +237,7 @@ void sqlite3FinishCoding(Parse *pParse){
/* Initialize any AUTOINCREMENT data structures required.
*/
if( pParse->pAinc ) sqlite3AutoincrementBegin(pParse);
if( pParse->usesAinc ) sqlite3AutoincrementBegin(pParse);
/* Code constant expressions that were factored out of inner loops.
*/
@@ -269,7 +270,7 @@ void sqlite3FinishCoding(Parse *pParse){
if( pParse->nErr==0 ){
/* A minimum of one cursor is required if autoincrement is used
* See ticket [a696379c1f08866] */
assert( pParse->pAinc==0 || pParse->nTab>0 );
assert( pParse->usesAinc==0 || pParse->nTab>0 );
sqlite3VdbeMakeReady(v, pParse);
pParse->rc = SQLITE_DONE;
}else{
@@ -714,6 +715,19 @@ Expr *sqlite3ColumnExpr(Table *pTab, Column *pCol){
return pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr;
}
/*
** Suppress false-positive warning message generated with -O3 in GCC
** on the second call to sqlite3Strlen30() in the sqlite3ColumnSetColl()
** function below. See the forum thread beginning on 2026-05-10T01:11:22Z.
**
** See also the "pop" pragma to undo this warning suppression immediately
** after the function.
*/
#if defined(__GNUC__) && __GNUC__>=11
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wstringop-overread"
#endif
/*
** Set the collating sequence name for a column.
*/
@@ -739,6 +753,11 @@ void sqlite3ColumnSetColl(
}
}
/* Undo the false-positive warning suppression above. */
#if defined(__GNUC__) && __GNUC__>=11
# pragma GCC diagnostic pop
#endif
/*
** Return the collating sequence name for a column
*/
+31 -31
View File
@@ -1416,38 +1416,38 @@ static void strftimeFunc(
size_t i,j;
sqlite3 *db;
const char *zFmt;
sqlite3_str sRes;
sqlite3_str *pRes;
if( argc==0 ) return;
zFmt = (const char*)sqlite3_value_text(argv[0]);
if( zFmt==0 || isDate(context, argc-1, argv+1, &x) ) return;
db = sqlite3_context_db_handle(context);
sqlite3StrAccumInit(&sRes, 0, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]);
pRes = sqlite3_str_new(db);
computeJD(&x);
computeYMD_HMS(&x);
for(i=j=0; zFmt[i]; i++){
char cf;
if( zFmt[i]!='%' ) continue;
if( j<i ) sqlite3_str_append(&sRes, zFmt+j, (int)(i-j));
if( j<i ) sqlite3_str_append(pRes, zFmt+j, (int)(i-j));
i++;
j = i + 1;
cf = zFmt[i];
switch( cf ){
case 'd': /* Fall thru */
case 'e': {
sqlite3_str_appendf(&sRes, cf=='d' ? "%02d" : "%2d", x.D);
sqlite3_str_appendf(pRes, cf=='d' ? "%02d" : "%2d", x.D);
break;
}
case 'f': { /* Fractional seconds. (Non-standard) */
double s = x.s;
if( NEVER(s>59.999) ) s = 59.999;
sqlite3_str_appendf(&sRes, "%06.3f", s);
sqlite3_str_appendf(pRes, "%06.3f", s);
break;
}
case 'F': {
sqlite3_str_appendf(&sRes, "%04d-%02d-%02d", x.Y, x.M, x.D);
sqlite3_str_appendf(pRes, "%04d-%02d-%02d", x.Y, x.M, x.D);
break;
}
case 'G': /* Fall thru */
@@ -1459,15 +1459,15 @@ static void strftimeFunc(
y.validYMD = 0;
computeYMD(&y);
if( cf=='g' ){
sqlite3_str_appendf(&sRes, "%02d", y.Y%100);
sqlite3_str_appendf(pRes, "%02d", y.Y%100);
}else{
sqlite3_str_appendf(&sRes, "%04d", y.Y);
sqlite3_str_appendf(pRes, "%04d", y.Y);
}
break;
}
case 'H':
case 'k': {
sqlite3_str_appendf(&sRes, cf=='H' ? "%02d" : "%2d", x.h);
sqlite3_str_appendf(pRes, cf=='H' ? "%02d" : "%2d", x.h);
break;
}
case 'I': /* Fall thru */
@@ -1475,65 +1475,65 @@ static void strftimeFunc(
int h = x.h;
if( h>12 ) h -= 12;
if( h==0 ) h = 12;
sqlite3_str_appendf(&sRes, cf=='I' ? "%02d" : "%2d", h);
sqlite3_str_appendf(pRes, cf=='I' ? "%02d" : "%2d", h);
break;
}
case 'j': { /* Day of year. Jan01==1, Jan02==2, and so forth */
sqlite3_str_appendf(&sRes,"%03d",daysAfterJan01(&x)+1);
sqlite3_str_appendf(pRes,"%03d",daysAfterJan01(&x)+1);
break;
}
case 'J': { /* Julian day number. (Non-standard) */
sqlite3_str_appendf(&sRes,"%.16g",x.iJD/86400000.0);
sqlite3_str_appendf(pRes,"%.16g",x.iJD/86400000.0);
break;
}
case 'm': {
sqlite3_str_appendf(&sRes,"%02d",x.M);
sqlite3_str_appendf(pRes,"%02d",x.M);
break;
}
case 'M': {
sqlite3_str_appendf(&sRes,"%02d",x.m);
sqlite3_str_appendf(pRes,"%02d",x.m);
break;
}
case 'p': /* Fall thru */
case 'P': {
if( x.h>=12 ){
sqlite3_str_append(&sRes, cf=='p' ? "PM" : "pm", 2);
sqlite3_str_append(pRes, cf=='p' ? "PM" : "pm", 2);
}else{
sqlite3_str_append(&sRes, cf=='p' ? "AM" : "am", 2);
sqlite3_str_append(pRes, cf=='p' ? "AM" : "am", 2);
}
break;
}
case 'R': {
sqlite3_str_appendf(&sRes, "%02d:%02d", x.h, x.m);
sqlite3_str_appendf(pRes, "%02d:%02d", x.h, x.m);
break;
}
case 's': {
if( x.useSubsec ){
sqlite3_str_appendf(&sRes,"%.3f",
sqlite3_str_appendf(pRes,"%.3f",
(x.iJD - 21086676*(i64)10000000)/1000.0);
}else{
i64 iS = (i64)(x.iJD/1000 - 21086676*(i64)10000);
sqlite3_str_appendf(&sRes,"%lld",iS);
sqlite3_str_appendf(pRes,"%lld",iS);
}
break;
}
case 'S': {
sqlite3_str_appendf(&sRes,"%02d",(int)x.s);
sqlite3_str_appendf(pRes,"%02d",(int)x.s);
break;
}
case 'T': {
sqlite3_str_appendf(&sRes,"%02d:%02d:%02d", x.h, x.m, (int)x.s);
sqlite3_str_appendf(pRes,"%02d:%02d:%02d", x.h, x.m, (int)x.s);
break;
}
case 'u': /* Day of week. 1 to 7. Monday==1, Sunday==7 */
case 'w': { /* Day of week. 0 to 6. Sunday==0, Monday==1 */
char c = (char)daysAfterSunday(&x) + '0';
if( c=='0' && cf=='u' ) c = '7';
sqlite3_str_appendchar(&sRes, 1, c);
sqlite3_str_appendchar(pRes, 1, c);
break;
}
case 'U': { /* Week num. 00-53. First Sun of the year is week 01 */
sqlite3_str_appendf(&sRes,"%02d",
sqlite3_str_appendf(pRes,"%02d",
(daysAfterJan01(&x)-daysAfterSunday(&x)+7)/7);
break;
}
@@ -1544,30 +1544,30 @@ static void strftimeFunc(
y.iJD += (3 - daysAfterMonday(&x))*86400000;
y.validYMD = 0;
computeYMD(&y);
sqlite3_str_appendf(&sRes,"%02d", daysAfterJan01(&y)/7+1);
sqlite3_str_appendf(pRes,"%02d", daysAfterJan01(&y)/7+1);
break;
}
case 'W': { /* Week num. 00-53. First Mon of the year is week 01 */
sqlite3_str_appendf(&sRes,"%02d",
sqlite3_str_appendf(pRes,"%02d",
(daysAfterJan01(&x)-daysAfterMonday(&x)+7)/7);
break;
}
case 'Y': {
sqlite3_str_appendf(&sRes,"%04d",x.Y);
sqlite3_str_appendf(pRes,"%04d",x.Y);
break;
}
case '%': {
sqlite3_str_appendchar(&sRes, 1, '%');
sqlite3_str_appendchar(pRes, 1, '%');
break;
}
default: {
sqlite3_str_reset(&sRes);
sqlite3_str_free(pRes);
return;
}
}
}
if( j<i ) sqlite3_str_append(&sRes, zFmt+j, (int)(i-j));
sqlite3ResultStrAccum(context, &sRes);
if( j<i ) sqlite3_str_append(pRes, zFmt+j, (int)(i-j));
sqlite3_result_str(context, pRes, SQLITE_FINISH);
}
/*
@@ -1703,7 +1703,7 @@ static void timediffFunc(
sqlite3StrAccumInit(&sRes, 0, 0, 0, 100);
sqlite3_str_appendf(&sRes, "%c%04d-%02d-%02d %02d:%02d:%06.3f",
sign, Y, M, d1.D-1, d1.h, d1.m, d1.s);
sqlite3ResultStrAccum(context, &sRes);
sqlite3_result_str(context, &sRes, SQLITE_XFER);
}
+9 -6
View File
@@ -259,12 +259,13 @@ static int dbpageFilter(
pCsr->szPage = sqlite3BtreeGetPageSize(pBt);
pCsr->mxPgno = sqlite3BtreeLastPage(pBt);
if( idxNum & 1 ){
i64 iPg = sqlite3_value_int64(argv[idxNum>>1]);
assert( argc>(idxNum>>1) );
pCsr->pgno = sqlite3_value_int(argv[idxNum>>1]);
if( pCsr->pgno<1 || pCsr->pgno>pCsr->mxPgno ){
if( iPg<1 || iPg>pCsr->mxPgno ){
pCsr->pgno = 1;
pCsr->mxPgno = 0;
}else{
pCsr->pgno = (Pgno)iPg;
pCsr->mxPgno = pCsr->pgno;
}
}else{
@@ -344,6 +345,7 @@ static int dbpageUpdate(
){
DbpageTable *pTab = (DbpageTable *)pVtab;
Pgno pgno;
sqlite3_int64 pgno64;
DbPage *pDbPage = 0;
int rc = SQLITE_OK;
char *zErr = 0;
@@ -363,11 +365,11 @@ static int dbpageUpdate(
goto update_fail;
}
if( sqlite3_value_type(argv[0])==SQLITE_NULL ){
pgno = (Pgno)sqlite3_value_int64(argv[2]);
pgno64 = sqlite3_value_int64(argv[2]);
isInsert = 1;
}else{
pgno = (Pgno)sqlite3_value_int64(argv[0]);
if( (Pgno)sqlite3_value_int(argv[1])!=pgno ){
pgno64 = (Pgno)sqlite3_value_int64(argv[0]);
if( sqlite3_value_int64(argv[1])!=pgno64 ){
zErr = "cannot insert";
goto update_fail;
}
@@ -384,10 +386,11 @@ static int dbpageUpdate(
}
}
pBt = pTab->db->aDb[iDb].pBt;
if( pgno<1 || NEVER(pBt==0) ){
if( pgno64<1 || pgno64>4294967294 || NEVER(pBt==0) ){
zErr = "bad page number";
goto update_fail;
}
pgno = (Pgno)pgno64;
szPage = sqlite3BtreeGetPageSize(pBt);
if( sqlite3_value_type(argv[3])!=SQLITE_BLOB
|| sqlite3_value_bytes(argv[3])!=szPage
+6 -3
View File
@@ -915,6 +915,7 @@ void sqlite3GenerateRowIndexDelete(
v = pParse->pVdbe;
pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab);
for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
int p3 = 0;
assert( iIdxCur+i!=iDataCur || pPk==pIdx );
if( aRegIdx!=0 && aRegIdx[i]==0 ) continue;
if( pIdx==pPk ) continue;
@@ -922,10 +923,12 @@ void sqlite3GenerateRowIndexDelete(
VdbeModuleComment((v, "GenRowIdxDel for %s", pIdx->zName));
r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 1,
&iPartIdxLabel, pPrior, r1);
sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur+i, r1,
pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn
);
if( pIdx->bHasExpr && aRegIdx ){
p3 = aRegIdx[i];
}
sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur+i, r1, p3);
sqlite3VdbeChangeP4(v, -1, (const char*)pIdx, P4_INDEX);
sqlite3VdbeChangeP5(v, pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn);
sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
pPrior = pIdx;
}
+43 -18
View File
@@ -331,7 +331,9 @@ CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr){
int sqlite3ExprCollSeqMatch(Parse *pParse, const Expr *pE1, const Expr *pE2){
CollSeq *pColl1 = sqlite3ExprNNCollSeq(pParse, pE1);
CollSeq *pColl2 = sqlite3ExprNNCollSeq(pParse, pE2);
return sqlite3StrICmp(pColl1->zName, pColl2->zName)==0;
assert( (pColl1==pColl2) ==
(sqlite3_stricmp(pColl1->zName,pColl2->zName)==0) );
return pColl1==pColl2;
}
/*
@@ -4168,6 +4170,7 @@ static void sqlite3ExprCodeIN(
int rLhsOrig = rLhs;
rLhs = sqlite3GetTempRange(pParse, nVector);
for(i=0; i<nVector; i++){
testcase( aiMap[i]!=i );
sqlite3VdbeAddOp3(v, OP_Copy, rLhsOrig+i, rLhs+aiMap[i], 0);
}
sqlite3ReleaseTempReg(pParse, rLhsOrig);
@@ -4188,7 +4191,8 @@ static void sqlite3ExprCodeIN(
Expr *p = sqlite3VectorFieldSubexpr(pExpr->pLeft, i);
if( pParse->nErr ) goto sqlite3ExprCodeIN_oom_error;
if( sqlite3ExprCanBeNull(p) ){
sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+i, destStep2);
testcase( aiMap[i]!=i );
sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+aiMap[i], destStep2);
VdbeCoverage(v);
}
}
@@ -4262,9 +4266,19 @@ static void sqlite3ExprCodeIN(
CollSeq *pColl;
int r3 = sqlite3GetTempReg(pParse);
p = sqlite3VectorFieldSubexpr(pLeft, i);
pColl = sqlite3ExprCollSeq(pParse, p);
sqlite3VdbeAddOp3(v, OP_Column, iTab, i, r3);
sqlite3VdbeAddOp4(v, OP_Ne, rLhs+i, destNotNull, r3,
if( ExprUseXSelect(pExpr) ){
Expr *pRhs = pExpr->x.pSelect->pEList->a[i].pExpr;
pColl = sqlite3BinaryCompareCollSeq(pParse, p, pRhs);
}else{
/* If the RHS of the IN(...) expression are scalar expressions, do
** not consider their collation sequences. The documentation says
** "The collating sequence used for expressions of the form "x IN (y, z,
** ...)" is the collating sequence of x.". */
pColl = sqlite3ExprCollSeq(pParse, p);
}
testcase( aiMap[i]!=i );
sqlite3VdbeAddOp3(v, OP_Column, iTab, aiMap[i], r3);
sqlite3VdbeAddOp4(v, OP_Ne, rLhs+aiMap[i], destNotNull, r3,
(void*)pColl, P4_COLLSEQ);
VdbeCoverage(v);
sqlite3ReleaseTempReg(pParse, r3);
@@ -4685,26 +4699,37 @@ static int exprCodeInlineFunction(
}
/*
** Expression Node callback for sqlite3ExprCanReturnSubtype().
** Expression Node callback for sqlite3ExprCanReturnSubtype(). If
** pExpr is able to return a subtype, set pWalker->eCode and abort
** the search. If pExpr can never return a subtype, prune search.
**
** Only a function call is able to return a subtype. So if the node
** is not a function call, return WRC_Prune immediately.
** The only expressions that can return a subtype are:
**
** A function call is able to return a subtype if it has the
** SQLITE_RESULT_SUBTYPE property.
** 1. A function
** 2. The no-op "+" operator
** 3. A CASE...END expression
** 4. A CAST() expression
** 5. A "expr COLLATE colseq" expression.
**
** Assume that every function is able to pass-through a subtype from
** one of its argument (using sqlite3_result_value()). Most functions
** are not this way, but we don't have a mechanism to distinguish those
** that are from those that are not, so assume they all work this way.
** That means that if one of its arguments is another function and that
** other function is able to return a subtype, then this function is
** able to return a subtype.
** For any other kind of expression, prune the search.
**
** For case 1, the expression can yield a subtype if the function has
** the SQLITE_RESULT_SUBTYPE property. Functions can also return
** a subtype (via sqlite3_result_value()) if any of the arguments can
** return a subtype.
**
** In all cases 1 through 5, the expression might also return a subtype
** if any operand can return a subtype.
*/
static int exprNodeCanReturnSubtype(Walker *pWalker, Expr *pExpr){
int n;
FuncDef *pDef;
sqlite3 *db;
if( pExpr->op==TK_CASE || pExpr->op==TK_UPLUS
|| pExpr->op==TK_COLLATE || pExpr->op==TK_CAST
){
return WRC_Continue;
}
if( pExpr->op!=TK_FUNCTION ){
return WRC_Prune;
}
@@ -4714,7 +4739,7 @@ static int exprNodeCanReturnSubtype(Walker *pWalker, Expr *pExpr){
pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0);
if( NEVER(pDef==0) || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){
pWalker->eCode = 1;
return WRC_Prune;
return WRC_Abort;
}
return WRC_Continue;
}
+135 -90
View File
@@ -130,13 +130,17 @@ static void lengthFunc(
case SQLITE_TEXT: {
const unsigned char *z = sqlite3_value_text(argv[0]);
const unsigned char *z0;
unsigned char c;
if( z==0 ) return;
z0 = z;
while( (c = *z)!=0 ){
z++;
if( c>=0xc0 ){
while( (*z & 0xc0)==0x80 ){ z++; z0++; }
while( 1 /*exit-by-break*/ ){
/* vvvvvv---- See tag-20260418-01 */
if( (u8)(z[0]-1)<(0x80-1) ){
z++;
}else if( z[0]==0 ){
break;
}else{
z++;
while( (z[0]&0xc0)==0x80 ){ z++; z0++; }
}
}
sqlite3_result_int(context, (int)(z-z0));
@@ -316,7 +320,6 @@ static void printfFunc(
PrintfArguments x;
StrAccum str;
const char *zFormat;
int n;
sqlite3 *db = sqlite3_context_db_handle(context);
if( argc>=1 && (zFormat = (const char*)sqlite3_value_text(argv[0]))!=0 ){
@@ -326,9 +329,7 @@ static void printfFunc(
sqlite3StrAccumInit(&str, db, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]);
str.printfFlags = SQLITE_PRINTF_SQLFUNC;
sqlite3_str_appendf(&str, zFormat, &x);
n = str.nChar;
sqlite3_result_text(context, sqlite3StrAccumFinish(&str), n,
SQLITE_DYNAMIC);
sqlite3_result_str(context, &str, SQLITE_XFER);
}
}
@@ -415,12 +416,25 @@ static void substrFunc(
}
assert( p1>=0 && p2>=0 );
if( p0type!=SQLITE_BLOB ){
while( *z && p1 ){
SQLITE_SKIP_UTF8(z);
p1--;
for( ; p1>0; p1--){
/* vvvvvv---- See tag-20260418-01 */
if( (u8)(z[0]-1)<(0x80-1) ){
z++;
}else if( z[0]==0 ){
break;
}else{
do{ z++; }while( (z[0]&0xc0)==0x80 );
}
}
for(z2=z; *z2 && p2; p2--){
SQLITE_SKIP_UTF8(z2);
for(z2=z; p2>0; p2--){
/* vvvvvv---- See tag-20260418-01 */
if( (u8)(z2[0]-1)<(0x80-1) ){
z2++;
}else if( z2[0]==0 ){
break;
}else{
do{ z2++; }while( (z2[0]&0xc0)==0x80 );
}
}
sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT,
SQLITE_UTF8);
@@ -1262,12 +1276,7 @@ static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
UNUSED_PARAMETER(argc);
sqlite3StrAccumInit(&str, db, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]);
sqlite3QuoteValue(&str,argv[0],SQLITE_PTR_TO_INT(sqlite3_user_data(context)));
sqlite3_result_text(context, sqlite3StrAccumFinish(&str), str.nChar,
SQLITE_DYNAMIC);
if( str.accError!=SQLITE_OK ){
sqlite3_result_null(context);
sqlite3_result_error_code(context, str.accError);
}
sqlite3_result_str(context, &str, SQLITE_XFER);
}
/*
@@ -1487,7 +1496,7 @@ static void replaceFunc(
int nRep; /* Size of zRep */
i64 nOut; /* Maximum size of zOut */
int loopLimit; /* Last zStr[] that might match zPattern[] */
int i, j; /* Loop counters */
i64 i, j; /* Loop counters */
unsigned cntExpand; /* Number zOut expansions */
sqlite3 *db = sqlite3_context_db_handle(context);
@@ -1966,11 +1975,16 @@ static void sumInverse(sqlite3_context *context, int argc, sqlite3_value**argv){
assert( p->cnt>0 );
p->cnt--;
if( !p->approx ){
if( sqlite3SubInt64(&p->iSum, sqlite3_value_int64(argv[0])) ){
p->ovrfl = 1;
p->approx = 1;
i64 x = p->iSum;
if( sqlite3SubInt64(&x, sqlite3_value_int64(argv[0]))==0 ){
p->iSum = x;
return;
}
}else if( type==SQLITE_INTEGER ){
p->ovrfl = 1;
p->approx = 1;
kahanBabuskaNeumaierInit(p, p->iSum);
}
if( type==SQLITE_INTEGER ){
i64 iVal = sqlite3_value_int64(argv[0]);
if( iVal!=SMALLEST_INT64 ){
kahanBabuskaNeumaierStepInt64(p, -iVal);
@@ -2295,7 +2309,7 @@ static void groupConcatFinalize(sqlite3_context *context){
GroupConcatCtx *pGCC
= (GroupConcatCtx*)sqlite3_aggregate_context(context, 0);
if( pGCC ){
sqlite3ResultStrAccum(context, &pGCC->str);
sqlite3_result_str(context, &pGCC->str, SQLITE_XFER);
#ifndef SQLITE_OMIT_WINDOWFUNC
sqlite3_free(pGCC->pnSepLengths);
#endif
@@ -2305,18 +2319,8 @@ static void groupConcatFinalize(sqlite3_context *context){
static void groupConcatValue(sqlite3_context *context){
GroupConcatCtx *pGCC
= (GroupConcatCtx*)sqlite3_aggregate_context(context, 0);
if( pGCC ){
StrAccum *pAccum = &pGCC->str;
if( pAccum->accError==SQLITE_TOOBIG ){
sqlite3_result_error_toobig(context);
}else if( pAccum->accError==SQLITE_NOMEM ){
sqlite3_result_error_nomem(context);
}else if( pGCC->nAccum>0 && pAccum->nChar==0 ){
sqlite3_result_text(context, "", 1, SQLITE_STATIC);
}else{
const char *zText = sqlite3_str_value(pAccum);
sqlite3_result_text(context, zText, pAccum->nChar, SQLITE_TRANSIENT);
}
if( pGCC && pGCC->nAccum>0 ){
sqlite3_result_str(context, &pGCC->str, SQLITE_COPY);
}
}
#else
@@ -2936,53 +2940,91 @@ static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){
** (1) To avoid a dependency on qsort()
** (2) To avoid the function call to the comparison routine for each
** comparison.
**
** If parameter iReq is non-negative, then the caller will only access
** elements a[iReq] and a[iReq+1] (if it exists) of the sorted array and
** so it is not necessary to position any other elements. Or if iReq is
** negative, then the final array must be fully sorted.
*/
static void percentSort(double *a, unsigned int n){
static void percentSort(
double *a, /* Array to sort */
unsigned int n, /* Number of elements in array a[] */
int iReq /* Element caller cares about (or -ve) */
){
int iLt; /* Entries before a[iLt] are less than rPivot */
int iGt; /* Entries at or after a[iGt] are greater than rPivot */
int i; /* Loop counter */
double rPivot; /* The pivot value */
assert( n>=2 );
if( a[0]>a[n-1] ){
SWAP_DOUBLE(a[0],a[n-1])
}
if( n==2 ) return;
iGt = n-1;
i = n/2;
if( a[0]>a[i] ){
SWAP_DOUBLE(a[0],a[i])
}else if( a[i]>a[iGt] ){
SWAP_DOUBLE(a[i],a[iGt])
}
if( n==3 ) return;
rPivot = a[i];
iLt = i = 1;
do{
if( a[i]<rPivot ){
if( i>iLt ) SWAP_DOUBLE(a[i],a[iLt])
iLt++;
i++;
}else if( a[i]>rPivot ){
do{
iGt--;
}while( iGt>i && a[iGt]>rPivot );
SWAP_DOUBLE(a[i],a[iGt])
}else{
i++;
}
}while( i<iGt );
if( iLt>=2 ) percentSort(a, iLt);
if( n-iGt>=2 ) percentSort(a+iGt, n-iGt);
/* Uncomment for testing */
#if 0
for(i=0; i<n-1; i++){
assert( a[i]<=a[i+1] );
}
#endif
}
assert( n>=2 );
do{
if( a[0]>a[n-1] ){
SWAP_DOUBLE(a[0],a[n-1])
}
if( n==2 ) return;
iGt = n-1;
i = n/2;
if( a[0]>a[i] ){
SWAP_DOUBLE(a[0],a[i])
}else if( a[i]>a[iGt] ){
SWAP_DOUBLE(a[i],a[iGt])
}
if( n==3 ) return;
rPivot = a[i];
iLt = i = 1;
do{
if( a[i]<rPivot ){
if( i>iLt ) SWAP_DOUBLE(a[i],a[iLt])
iLt++;
i++;
}else if( a[i]>rPivot ){
do{
iGt--;
}while( iGt>i && a[iGt]>rPivot );
SWAP_DOUBLE(a[i],a[iGt])
}else{
i++;
}
}while( i<iGt );
assert( a[iLt]==rPivot );
assert( iGt>iLt );
if( iReq>=0 ){
/* In this case, the only elements that the caller requires sorted into
** the correct positions are elements a[iReq] and a[iReq+1]. At this
** point we know that element a[iLt] is in the correct position and
** all elements smaller than a[iLt] are in the left-hand partition.
** So if (iReq<iLt), then it is only necessary to sort the left
** partition.
**
** If (iReq>=iLt), then elements iReq and iReq+1 are either in the
** right partition or the equal partition (elements for which
** iLt<=iElem<iGt). Therefore it is always sufficient to sort only
** the right partition in this case. */
if( iReq<iLt ){
n = iLt;
}else{
a += iGt;
n -= iGt;
iReq = MAX(0, iReq-iGt);
}
}else{
/* Recurse on the smaller partition only. The smaller partition
** will hold n/2 or fewer entries, which assures that the stack
** depth will not exceed O(log(n)), even for pathological cases.
** Loop without recursion for the larger partition. */
if( iLt>(int)(n/2) ){
if( n-iGt>=2 ) percentSort(a+iGt, n-iGt, -1);
n = iLt;
}else{
if( iLt>=2 ) percentSort(a, iLt, -1);
a += iGt;
n -= iGt;
}
}
}while( n>=2 );
}
/*
** The "inverse" function for percentile(Y,P) is called to remove a
@@ -3016,7 +3058,7 @@ static void percentInverse(sqlite3_context *pCtx,int argc,sqlite3_value **argv){
}
if( p->bSorted==0 ){
assert( p->nUsed>1 );
percentSort(p->a, p->nUsed);
percentSort(p->a, p->nUsed, -1);
p->bSorted = 1;
}
p->bKeepSorted = 1;
@@ -3045,13 +3087,17 @@ static void percentCompute(sqlite3_context *pCtx, int bIsFinal){
if( p==0 ) return;
if( p->a==0 ) return;
if( p->nUsed ){
if( p->bSorted==0 ){
assert( p->nUsed>1 );
percentSort(p->a, p->nUsed);
p->bSorted = 1;
}
ix = p->rPct*(p->nUsed-1);
i1 = (unsigned)ix;
if( p->bSorted==0 ){
/* In cases where bIsFinal is non-zero, setting Percentile.bSorted
** after the percentSort() call here is not technically correct, as
** the array is not fully sorted. But in this case the object will be
** freed below anyway, so it doesn't matter. */
assert( p->nUsed>1 );
percentSort(p->a, p->nUsed, (bIsFinal ? (int)i1 : -1));
p->bSorted = 1;
}
if( settings & 1 ){
vx = p->a[i1];
}else{
@@ -3106,7 +3152,7 @@ static void filestatFunc(
assert( pPager!=0 );
fd = sqlite3PagerFile(pPager);
pStr = sqlite3_str_new(db);
if( pStr==0 ){
if( sqlite3_str_errcode(pStr) ){
sqlite3_result_error_nomem(context);
}else{
sqlite3_str_append(pStr, "{\"db\":", 6);
@@ -3119,8 +3165,7 @@ static void filestatFunc(
if( rc ) sqlite3_str_append(pStr, "null", 4);
}
sqlite3_str_append(pStr, "}", 1);
sqlite3_result_text(context, sqlite3_str_finish(pStr), -1,
sqlite3_free);
sqlite3_result_str(context, pStr, SQLITE_FINISH);
}
sqlite3BtreeLeave(pBtree);
}else{
@@ -3209,7 +3254,7 @@ static void parseuriFunc(
flgs = (unsigned int)sqlite3_value_int(argv[1]);
rc = sqlite3ParseUri(zVfs, zUri, &flgs, &pVfs, &zFile, &zErr);
pResult = sqlite3_str_new(0);
if( pResult ){
if( !sqlite3_str_errcode(pResult) ){
int i;
sqlite3_str_appendf(pResult, "rc=%d", rc);
sqlite3_str_appendf(pResult, ", flags=0x%x", flgs);
@@ -3236,8 +3281,8 @@ static void parseuriFunc(
}
}
}
sqlite3_result_text(ctx, sqlite3_str_finish(pResult), -1, sqlite3_free);
}
sqlite3_result_str(ctx, pResult, SQLITE_FINISH);
sqlite3_free_filename(zFile);
sqlite3_free(zErr);
}
+10
View File
@@ -308,6 +308,16 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = {
*/
FuncDefHash sqlite3BuiltinFunctions;
/*
** This singleton is an sqlite3_str object that is returned if
** sqlite3_malloc() fails to provide space for a real one. This
** sqlite3_str object accepts no new text and always returns
** an SQLITE_NOMEM error.
*/
const sqlite3_str sqlite3OomStr = {
0, 0, 0, 0, 0, SQLITE_NOMEM, 0
};
#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG)
/*
** Counter used for coverage testing. Does not come into play for
+1 -1
View File
@@ -28,7 +28,7 @@
}
#elif !defined(__STRICT_ANSI__) && defined(__GNUC__) && \
(defined(i386) || defined(__i386__) || defined(_M_IX86))
(defined(i586) || defined(__i586__) || defined(_M_IX86))
__inline__ sqlite_uint64 sqlite3Hwtime(void){
unsigned int lo, hi;
+75 -5
View File
@@ -433,6 +433,9 @@ static int autoIncBegin(
return 0;
}
if( pToplevel->usesAinc==0 ){
pToplevel->pAinc = 0;
}
pInfo = pToplevel->pAinc;
while( pInfo && pInfo->pTab!=pTab ){ pInfo = pInfo->pNext; }
if( pInfo==0 ){
@@ -442,6 +445,7 @@ static int autoIncBegin(
if( pParse->db->mallocFailed ) return 0;
pInfo->pNext = pToplevel->pAinc;
pToplevel->pAinc = pInfo;
pToplevel->usesAinc = 1;
pInfo->pTab = pTab;
pInfo->iDb = iDb;
pToplevel->nMem++; /* Register to hold name of table */
@@ -470,6 +474,7 @@ void sqlite3AutoincrementBegin(Parse *pParse){
assert( sqlite3IsToplevel(pParse) );
assert( v ); /* We failed long ago if this is not so */
assert( pParse->usesAinc );
for(p = pParse->pAinc; p; p = p->pNext){
static const int iLn = VDBE_OFFSET_LINENO(2);
static const VdbeOpList autoInc[] = {
@@ -537,6 +542,7 @@ static SQLITE_NOINLINE void autoIncrementEnd(Parse *pParse){
sqlite3 *db = pParse->db;
assert( v );
assert( pParse->usesAinc );
for(p = pParse->pAinc; p; p = p->pNext){
static const int iLn = VDBE_OFFSET_LINENO(2);
static const VdbeOpList autoIncEnd[] = {
@@ -569,7 +575,7 @@ static SQLITE_NOINLINE void autoIncrementEnd(Parse *pParse){
}
}
void sqlite3AutoincrementEnd(Parse *pParse){
if( pParse->pAinc ) autoIncrementEnd(pParse);
if( pParse->usesAinc ) autoIncrementEnd(pParse);
}
#else
/*
@@ -2809,7 +2815,10 @@ void sqlite3CompleteInsertion(
|| pIdx->pNext==0
|| pIdx->pNext->onError==OE_Replace );
if( aRegIdx[i]==0 ) continue;
if( pIdx->pPartIdxWhere ){
if( pIdx->pPartIdxWhere || (update_flags && pIdx->bHasExpr) ){
/* If this is a partial index, or an UPDATE of an index on an
** expression, then the record register may be set to NULL to indicate
** that no record should be inserted into this index. */
sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2);
VdbeCoverage(v);
}
@@ -2984,6 +2993,44 @@ static int xferCompatibleIndex(Index *pDest, Index *pSrc){
return 1;
}
/*
** Examine an expression node and abort if it references the ROWID.
** This is a Walker callback used by xferCompatibleCheck()
*/
static int xferCheckRowid(Walker *pWalk, Expr *pExpr){
if( pExpr->op==TK_COLUMN && pExpr->iColumn<0 ){
pWalk->eCode = 1;
return WRC_Abort;
}else{
return WRC_Continue;
}
}
/*
** Analyze CHECK constraints on the source and destination tables and
** return true if those CHECK constraints are compatible with the
** xfer-optimization.
**
** * The pDest and pSrc tables must have identical CHECK constraints.
**
** * If the destination table, pDest, does not have an
** INTEGER PRIMARY KEY column, then no CHECK constraint may
** referenced the ROWID. (See forum post 2026-05-11T13:15:57Z)
*/
static int xferCompatibleCheck(Table *pDest, Table *pSrc){
if( sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) ){
return 0;
}
if( pDest->iPKey<0 ){
Walker w;
memset(&w, 0, sizeof(w));
w.xExprCallback = xferCheckRowid;
sqlite3WalkExprList(&w,pDest->pCheck);
if( w.eCode ) return 0;
}
return 1;
}
/*
** Attempt the transfer optimization on INSERTs of the form
**
@@ -3110,8 +3157,19 @@ static int xferOptimization(
if( pDest->iPKey!=pSrc->iPKey ){
return 0; /* Both tables must have the same INTEGER PRIMARY KEY */
}
if( (pDest->tabFlags & TF_Strict)!=0 && (pSrc->tabFlags & TF_Strict)==0 ){
return 0; /* Cannot feed from a non-strict into a strict table */
if( (pDest->tabFlags & TF_Strict)!=0 ){
if( (pSrc->tabFlags & TF_Strict)==0 ){
return 0; /* Cannot feed from a non-strict into a strict table */
}
for(i=0; i<pDest->nCol; i++){
unsigned eDestType = pDest->aCol[i].eCType;
unsigned eSrcType = pSrc->aCol[i].eCType;
if( eDestType==COLTYPE_ANY ) continue;
if( eDestType==eSrcType ) continue;
if( eDestType==COLTYPE_INT && eSrcType==COLTYPE_INTEGER ) continue;
if( eDestType==COLTYPE_INTEGER && eSrcType==COLTYPE_INT ) continue;
return 0; /* Incompatible types in source and destination */
}
}
for(i=0; i<pDest->nCol; i++){
Column *pDestCol = &pDest->aCol[i];
@@ -3205,7 +3263,7 @@ static int xferOptimization(
#ifndef SQLITE_OMIT_CHECK
if( pDest->pCheck
&& (db->mDbFlags & DBFLAG_Vacuum)==0
&& sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1)
&& !xferCompatibleCheck(pDest,pSrc)
){
return 0; /* Tables have different CHECK constraints. Ticket #2252 */
}
@@ -3226,6 +3284,18 @@ static int xferOptimization(
if( (db->flags & SQLITE_CountRows)!=0 ){
return 0; /* xfer opt does not play well with PRAGMA count_changes */
}
#ifndef SQLITE_OMIT_AUTHORIZATION
if( db->xAuth ){
int iDb = sqlite3SchemaToIndex(db, pSrc->pSchema);
if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 0;
for(i=0; i<pSrc->nCol; i++){
Column *pSrcCol = &pSrc->aCol[i];
if( sqlite3AuthReadCol(pParse, pSrc->zName, pSrcCol->zCnName, iDb) ){
return 0;
}
}
}
#endif
/* If we get this far, it means that the xfer optimization is at
** least a possibility, though it might only work if the destination
+80 -21
View File
@@ -810,7 +810,7 @@ static void jsonAppendSqlValue(
break;
}
case SQLITE_FLOAT: {
jsonPrintf(100, p, "%!0.15g", sqlite3_value_double(pValue));
jsonPrintf(100, p, "%!0.17g", sqlite3_value_double(pValue));
break;
}
case SQLITE_INTEGER: {
@@ -2124,9 +2124,10 @@ static u32 jsonbPayloadSize(const JsonParse *pParse, u32 i, u32 *pSz){
u8 x;
u32 sz;
u32 n;
assert( i<=pParse->nBlob );
x = pParse->aBlob[i]>>4;
if( x<=11 ){
if( i>=pParse->nBlob ){
*pSz = 0;
return 0;
}else if( (x = pParse->aBlob[i]>>4)<=11 ){
sz = x;
n = 1;
}else if( x==12 ){
@@ -2340,7 +2341,7 @@ static u32 jsonTranslateBlobToText(
break;
case 0xe2:
/* '\' followed by either U+2028 or U+2029 is ignored as
** whitespace. Not that in UTF8, U+2028 is 0xe2 0x80 0x29.
** whitespace. Note that in UTF8, U+2028 is 0xe2 0x80 0x29.
** U+2029 is the same except for the last byte */
if( sz2<4
|| 0x80!=(u8)zIn[2]
@@ -4909,11 +4910,9 @@ static void jsonGroupInverse(
UNUSED_PARAMETER(argc);
UNUSED_PARAMETER(argv);
pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0);
#ifdef NEVER
/* pStr is always non-NULL since jsonArrayStep() or jsonObjectStep() will
** always have been called to initialize it */
if( NEVER(!pStr) ) return;
#endif
z = pStr->zBuf;
for(i=1; i<pStr->nUsed && ((c = z[i])!=',' || inStr || nNest); i++){
if( c=='"' ){
@@ -4942,6 +4941,13 @@ static void jsonGroupInverse(
** json_group_obj(NAME,VALUE)
**
** Return a JSON object composed of all names and values in the aggregate.
**
** Rows for which NAME is NULL do not result in a new entry. However, we
** do initially insert a "@" entry into the growing string for each null entry
** and change the first character of the string to "@" to signal that the
** string contains null entries. The "@" markers are needed in order to
** correctly process xInverse() requests. The initial "@" is converted
** back into "{" and the "@" null values are removed by jsonObjectCompute().
*/
static void jsonObjectStep(
sqlite3_context *ctx,
@@ -4959,7 +4965,7 @@ static void jsonObjectStep(
if( pStr->zBuf==0 ){
jsonStringInit(pStr, ctx);
jsonAppendChar(pStr, '{');
}else if( pStr->nUsed>1 && z!=0 ){
}else if( pStr->nUsed>1 ){
jsonAppendChar(pStr, ',');
}
pStr->pCtx = ctx;
@@ -4967,6 +4973,9 @@ static void jsonObjectStep(
jsonAppendString(pStr, z, n);
jsonAppendChar(pStr, ':');
jsonAppendSqlValue(pStr, argv[1]);
}else{
pStr->zBuf[0] = '@';
jsonAppendRawNZ(pStr, "@", 1);
}
}
}
@@ -4975,20 +4984,64 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){
int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx));
pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0);
if( pStr ){
jsonAppendRawNZ(pStr, "}", 2);
jsonStringTrimOneChar(pStr);
JsonString *pOgStr = pStr;
JsonString tmpStr;
jsonAppendRawNZ(pOgStr, "}", 2); /* Ensure it is zero-terminated */
jsonStringTrimOneChar(pOgStr); /* Remove the zero terminator */
pStr->pCtx = ctx;
if( pStr->eErr ){
jsonReturnString(pStr, 0, 0);
return;
}else if( flags & JSON_BLOB ){
}
if( pStr->zBuf[0]!='{' ){
/* The string contains null entries that need to be removed */
u64 i, j;
int inStr = 0;
if( !isFinal ){
/* Work with a temporary copy of the string if this is not the
** final result */
jsonStringInit(&tmpStr, ctx);
jsonAppendRawNZ(&tmpStr, pStr->zBuf, pStr->nUsed+1);
pStr = &tmpStr;
if( pStr->eErr ){
jsonReturnString(pStr, 0, 0);
return;
}
jsonStringTrimOneChar(pStr); /* Remove zero terminator */
}
/* Fix up the string by changing the initial "@" flag back to
** to "{" and removing all subsequence "@" entries, with their
** associated comma delimeters. */
pStr->zBuf[0] = '{';
for(i=j=1; i<pStr->nUsed; i++){
char c = pStr->zBuf[i];
if( c=='"' ){
inStr = !inStr;
pStr->zBuf[j++] = '"';
}else if( c=='\\' ){
pStr->zBuf[j++] = '\\';
pStr->zBuf[j++] = pStr->zBuf[++i];
}else if( c=='@' && !inStr ){
assert( i+1<pStr->nUsed );
if( pStr->zBuf[i+1]==',' ){
i++;
}else if( pStr->zBuf[j-1]==',' ){
j--;
}
}else{
pStr->zBuf[j++] = c;
}
}
pStr->zBuf[j] = 0; /* Restore zero terminator */
pStr->nUsed = j; /* Truncate the string */
}
if( flags & JSON_BLOB ){
jsonReturnStringAsBlob(pStr);
if( isFinal ){
if( !pStr->bStatic ) sqlite3RCStrUnref(pStr->zBuf);
}else{
jsonStringTrimOneChar(pStr);
jsonStringTrimOneChar(pOgStr);
}
return;
}else if( isFinal ){
sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed,
pStr->bStatic ? SQLITE_TRANSIENT :
@@ -4996,8 +5049,9 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){
pStr->bStatic = 1;
}else{
sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT);
jsonStringTrimOneChar(pStr);
jsonStringTrimOneChar(pOgStr);
}
if( pStr!=pOgStr ) jsonStringReset(pStr);
}else if( flags & JSON_BLOB ){
static const unsigned char emptyObject = 0x0c;
sqlite3_result_blob(ctx, &emptyObject, 1, SQLITE_STATIC);
@@ -5201,6 +5255,15 @@ static void jsonAppendPathName(JsonEachCursor *p){
}
}
/* Report a "malformed JSON" or OOM error against the cursor.
*/
static int jsonEachMalformedInput(sqlite3_vtab_cursor *cur){
sqlite3_free(cur->pVtab->zErrMsg);
cur->pVtab->zErrMsg = sqlite3_mprintf("malformed JSON");
jsonEachCursorReset((JsonEachCursor*)cur);
return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM;
}
/* Advance the cursor to the next element for json_tree() */
static int jsonEachNext(sqlite3_vtab_cursor *cur){
JsonEachCursor *p = (JsonEachCursor*)cur;
@@ -5212,6 +5275,7 @@ static int jsonEachNext(sqlite3_vtab_cursor *cur){
u32 i = jsonSkipLabel(p);
x = p->sParse.aBlob[i] & 0x0f;
n = jsonbPayloadSize(&p->sParse, i, &sz);
if( n==0 )return jsonEachMalformedInput(cur);
if( x==JSONB_OBJECT || x==JSONB_ARRAY ){
JsonParent *pParent;
if( p->nParent>=p->nParentAlloc ){
@@ -5257,6 +5321,7 @@ static int jsonEachNext(sqlite3_vtab_cursor *cur){
u32 n, sz = 0;
u32 i = jsonSkipLabel(p);
n = jsonbPayloadSize(&p->sParse, i, &sz);
if( n==0 )return jsonEachMalformedInput(cur);
p->i = i + n + sz;
}
if( p->eType==JSONB_ARRAY && p->nParent ){
@@ -5493,7 +5558,7 @@ static int jsonEachFilter(
if( p->sParse.oom ){
return SQLITE_NOMEM;
}
goto json_each_malformed_input;
return jsonEachMalformedInput(cur);
}
}
if( idxNum==3 ){
@@ -5554,12 +5619,6 @@ static int jsonEachFilter(
p->aParent[0].iValue = i;
}
return SQLITE_OK;
json_each_malformed_input:
sqlite3_free(cur->pVtab->zErrMsg);
cur->pVtab->zErrMsg = sqlite3_mprintf("malformed JSON");
jsonEachCursorReset(p);
return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM;
}
/* The methods of the json_each virtual table */
+3 -1
View File
@@ -533,7 +533,9 @@ static const sqlite3_api_routines sqlite3Apis = {
0,
0,
#endif
sqlite3_incomplete
/* Version 3.54.0 and later */
sqlite3_incomplete,
sqlite3_result_str
};
/* True if x is the directory separator character
+1 -1
View File
@@ -518,7 +518,7 @@ int sqlite3_config(int op, ...){
break;
}
case SQLITE_CONFIG_SMALL_MALLOC: {
sqlite3GlobalConfig.bSmallMalloc = va_arg(ap, int);
sqlite3GlobalConfig.bSmallMalloc = va_arg(ap, int)!=0;
break;
}
case SQLITE_CONFIG_PAGECACHE: {
+3 -4
View File
@@ -876,10 +876,10 @@ int sqlite3_deserialize(
if( rc ) goto end_deserialize;
db->init.iDb = (u8)iDb;
db->init.reopenMemdb = 1;
rc = sqlite3_step(pStmt);
sqlite3_step(pStmt);
db->init.reopenMemdb = 0;
if( rc!=SQLITE_DONE ){
rc = SQLITE_ERROR;
rc = sqlite3_finalize(pStmt);
if( rc!=SQLITE_OK ){
goto end_deserialize;
}
p = memdbFromDbSchema(db, zSchema);
@@ -900,7 +900,6 @@ int sqlite3_deserialize(
}
end_deserialize:
sqlite3_finalize(pStmt);
if( pData && (mFlags & SQLITE_DESERIALIZE_FREEONCLOSE)!=0 ){
sqlite3_free(pData);
}
+35 -16
View File
@@ -35,8 +35,15 @@
# define SQLITE_MUTEX_NREF 0
#endif
#if GCC_VERSION>0
# define ALIGN128 __attribute__((aligned(128)))
#else
# define ALIGN128
#endif
/*
** Each recursive mutex is an instance of the following structure.
** Each SQLite mutex is an instance of the following structure.
**
*/
struct sqlite3_mutex {
pthread_mutex_t mutex; /* Mutex controlling the lock */
@@ -151,19 +158,31 @@ static int pthreadMutexEnd(void){ return SQLITE_OK; }
** the same type number.
*/
static sqlite3_mutex *pthreadMutexAlloc(int iType){
static sqlite3_mutex staticMutexes[] = {
SQLITE3_MUTEX_INITIALIZER(2),
SQLITE3_MUTEX_INITIALIZER(3),
SQLITE3_MUTEX_INITIALIZER(4),
SQLITE3_MUTEX_INITIALIZER(5),
SQLITE3_MUTEX_INITIALIZER(6),
SQLITE3_MUTEX_INITIALIZER(7),
SQLITE3_MUTEX_INITIALIZER(8),
SQLITE3_MUTEX_INITIALIZER(9),
SQLITE3_MUTEX_INITIALIZER(10),
SQLITE3_MUTEX_INITIALIZER(11),
SQLITE3_MUTEX_INITIALIZER(12),
SQLITE3_MUTEX_INITIALIZER(13)
/* Static mutexes - those with IDs of 2 or more...
**
** The ALIGN128 macro attempts to force 128-byte alignment on mutexes,
** so that adjacent mutex objects are always on different cache lines
** in the CPU. This is CPU-dependent, of course, but 128-byte alignment
** seems to work well for all contemporary processors. Experiments show
** that 64 works just as well most of the time, but the internet says
** that 128-byte alignment works better.
*/
static ALIGN128 union staticMutex {
sqlite3_mutex m;
char aSpacer[128];
} aMutex[] = {
{ .m = SQLITE3_MUTEX_INITIALIZER(2) },
{ .m = SQLITE3_MUTEX_INITIALIZER(3) },
{ .m = SQLITE3_MUTEX_INITIALIZER(4) },
{ .m = SQLITE3_MUTEX_INITIALIZER(5) },
{ .m = SQLITE3_MUTEX_INITIALIZER(6) },
{ .m = SQLITE3_MUTEX_INITIALIZER(7) },
{ .m = SQLITE3_MUTEX_INITIALIZER(8) },
{ .m = SQLITE3_MUTEX_INITIALIZER(9) },
{ .m = SQLITE3_MUTEX_INITIALIZER(10) },
{ .m = SQLITE3_MUTEX_INITIALIZER(11) },
{ .m = SQLITE3_MUTEX_INITIALIZER(12) },
{ .m = SQLITE3_MUTEX_INITIALIZER(13) },
};
sqlite3_mutex *p;
switch( iType ){
@@ -200,12 +219,12 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){
}
default: {
#ifdef SQLITE_ENABLE_API_ARMOR
if( iType-2<0 || iType-2>=ArraySize(staticMutexes) ){
if( iType-2<0 || iType-2>=ArraySize(aMutex) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
p = &staticMutexes[iType-2];
p = &aMutex[iType-2].m;
break;
}
}

Some files were not shown because too many files have changed in this diff Show More