Compare commits
363 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a89d43e5db | |||
| 646931b83e | |||
| 9be4c57ca1 | |||
| d7e055d41c | |||
| 97467fa8fa | |||
| fb41f57de4 | |||
| 190034d268 | |||
| ae3c30e318 | |||
| 7644162026 | |||
| fa8e52021f | |||
| ec4f18fed8 | |||
| 9487f6d441 | |||
| 7b386a77ff | |||
| 3a697a26de | |||
| 593b55cb78 | |||
| 8c4a4acc02 | |||
| 978b0abe4d | |||
| e070f37183 | |||
| b5d0d980be | |||
| 3dbf444538 | |||
| 3f7bf4ef2f | |||
| c9bffdb35e | |||
| ffc14a20ad | |||
| aa1579de80 | |||
| 8227408b6f | |||
| 9fb225190c | |||
| b7c32f2f29 | |||
| 09c84c70ee | |||
| 9a5801af3a | |||
| a0dc22438d | |||
| f0075cd2e4 | |||
| b348459070 | |||
| 458dfb94d0 | |||
| 91a3aa3646 | |||
| 1b5bd0edf1 | |||
| a2fd301a6a | |||
| 53f419e2c8 | |||
| 0cdc6432f7 | |||
| 667d1b15c1 | |||
| b1bbff20a0 | |||
| 23e50585eb | |||
| b273dc8de1 | |||
| 9c2ac0b6e3 | |||
| a5e5116228 | |||
| 5d59375ac8 | |||
| 1b828b8770 | |||
| 36e9e015de | |||
| d8f6cc7ea4 | |||
| 2884421c05 | |||
| 7459c01689 | |||
| 053048a40f | |||
| 2ca10782d2 | |||
| 2429af2e0c | |||
| 865a8f3072 | |||
| 124f449319 | |||
| 76ad3be617 | |||
| 23d0a08176 | |||
| b14202f86e | |||
| 0b9f4ee89c | |||
| d1b9001a7f | |||
| d57abdb017 | |||
| 970b3bc4c4 | |||
| d688d1551a | |||
| dacabb4a33 | |||
| 2bd7ac6716 | |||
| 49edbe2150 | |||
| 062016c2fd | |||
| 84ead09d94 | |||
| 2cb57d9d4a | |||
| 2f5d281796 | |||
| 6a1efec20a | |||
| 082e41f17b | |||
| 19a2ba1f5b | |||
| b677c5afd4 | |||
| 7340b032d1 | |||
| 2bc3763ec5 | |||
| a6812a4242 | |||
| 6bf7825ac5 | |||
| 192b3a539b | |||
| 002d33d8c5 | |||
| ee181f479a | |||
| 076db92d85 | |||
| 2dacda40a6 | |||
| 62a3734e78 | |||
| 95d072cc81 | |||
| 0120735de1 | |||
| d80528ad4d | |||
| c2d29194a8 | |||
| 61523e22f9 | |||
| 51b2c3863b | |||
| 6feeafc7de | |||
| a735462db6 | |||
| d2240cc988 | |||
| 942b7c555a | |||
| 069e5acd63 | |||
| 1c0a370472 | |||
| 2acb57c1be | |||
| 2a28b15748 | |||
| b1637e5408 | |||
| b48ca02d64 | |||
| c28f2d22b6 | |||
| 48a591fa6b | |||
| 2c351bbf82 | |||
| ddea119f82 | |||
| 03aa9a7b50 | |||
| 62d17739bc | |||
| f55434ab30 | |||
| 0a04687373 | |||
| c079bd08a2 | |||
| 5cae890219 | |||
| f3eea1efa4 | |||
| 3040da55a8 | |||
| 1a760748a6 | |||
| 5481274912 | |||
| 6c218ba0ad | |||
| ca8593ce68 | |||
| af6fdd2673 | |||
| 1cd9eb3811 | |||
| 7437c19a01 | |||
| d076d98ec8 | |||
| 72f0bd609c | |||
| 7aad2942c2 | |||
| 07ded24fbb | |||
| 5b98467d28 | |||
| 3d006d0d00 | |||
| 991cbd78b3 | |||
| 6a8e887a64 | |||
| 43ab3c2a84 | |||
| 9b7ea5700b | |||
| 720d3afa7e | |||
| 4cc2dbd54d | |||
| b79ffe60b6 | |||
| 6e3e557c81 | |||
| 18bee1617f | |||
| 2315f282be | |||
| ce2f503c1c | |||
| 5554aa27e6 | |||
| 5cfa63e6c8 | |||
| 0ca827a590 | |||
| 7e20b624db | |||
| 94c8755a95 | |||
| 8c9071ae4e | |||
| e1233bb609 | |||
| 00ba6a6635 | |||
| 3020203708 | |||
| db7fe319ed | |||
| 3eb6a5ee33 | |||
| f380599476 | |||
| a1e5778889 | |||
| 8f70ec615f | |||
| de739c3450 | |||
| c597ed79d1 | |||
| f305e1921b | |||
| 3dbab77bf1 | |||
| da18b56b25 | |||
| ecd9748590 | |||
| 6ae9949399 | |||
| 37e253208f | |||
| b869ed6b06 | |||
| ccf23dadea | |||
| d3dc8e5ae5 | |||
| badc42fd74 | |||
| fd9eba554a | |||
| 8154476ff8 | |||
| d258b93356 | |||
| 3ef3ecdf8a | |||
| 632b618063 | |||
| d58b463ffb | |||
| 1a0555610d | |||
| 079f8dc682 | |||
| 4da2ddf50e | |||
| fdf746967c | |||
| c7ddaf2479 | |||
| 45b4213160 | |||
| cbbeddbd67 | |||
| db5aebce43 | |||
| 4d3f3c7070 | |||
| bc94e5dbb6 | |||
| d0fd0cc2fa | |||
| 1192d6f5b1 | |||
| a20f63e0ef | |||
| a497743e24 | |||
| a2d992a000 | |||
| 62cd986418 | |||
| 4dd11e33e4 | |||
| de94ec87fc | |||
| e087d0f266 | |||
| b6ea7f0550 | |||
| e4ba14bc40 | |||
| b969f8c2f1 | |||
| c1bf93ecb6 | |||
| fd88eed8fa | |||
| f77a5af2b3 | |||
| 6df919b723 | |||
| cb388ec9a3 | |||
| 0e30396fe2 | |||
| fc12461f5c | |||
| 3cfe5c3dad | |||
| d3265d1473 | |||
| 61d9f8f881 | |||
| 397a3c4a04 | |||
| 0c3b3f93d1 | |||
| 9f3208e1f8 | |||
| 672b87d0d5 | |||
| 02dc8d0c5f | |||
| 13b63ed930 | |||
| 6166c2ee48 | |||
| a3e7c597b5 | |||
| cf632ddb7e | |||
| a8fdc0e0c0 | |||
| 8b803c716c | |||
| 077be44cc2 | |||
| 1f9c2ed674 | |||
| 887fa2fe83 | |||
| 73da727480 | |||
| 94f56f5800 | |||
| 93d7dc266b | |||
| 1d2f71ce4a | |||
| 431b85963f | |||
| 054aff6272 | |||
| 657721fd32 | |||
| e14c16189e | |||
| 88be32c9d3 | |||
| cf420c9910 | |||
| 5f6a6fa6f0 | |||
| eb83e32cef | |||
| 79db323ce1 | |||
| 219acc9f4f | |||
| 8bee9cd3cf | |||
| f8ca671997 | |||
| 15670b1b3f | |||
| df300e8e51 | |||
| 2c8f97159c | |||
| 993bc6b171 | |||
| 86d1ef8bf7 | |||
| e707e68420 | |||
| 1788b7b719 | |||
| d4c4e91674 | |||
| 4f95a67b0c | |||
| 31c19f587d | |||
| bfc0414b9a | |||
| f9db8b2350 | |||
| ad37ec3aa2 | |||
| 4b77e36dec | |||
| 3ece90420d | |||
| 8ff6523afd | |||
| 821323938d | |||
| e572525306 | |||
| 082aebfa0d | |||
| 6f842b8da4 | |||
| 3d84988efe | |||
| 128f65ca6d | |||
| 79a4591478 | |||
| 14fa0e9b86 | |||
| b4d42c6702 | |||
| 3ed7926e2f | |||
| 8923a68c5f | |||
| c94c21d0a6 | |||
| fb7c642990 | |||
| 2bc658c387 | |||
| f02745088c | |||
| 596008f078 | |||
| 1c9e8ca88b | |||
| c70fbd39f9 | |||
| 9db9f18985 | |||
| f107c52d4c | |||
| 7509c6351e | |||
| 57e46429fe | |||
| a9c2e08087 | |||
| 7f1f4c803d | |||
| bd13573a96 | |||
| 0cadeecf6f | |||
| ad52288e0d | |||
| d5f0e01593 | |||
| 3e663bd8c7 | |||
| 97901bbd69 | |||
| c549e28c67 | |||
| 90dae0d01d | |||
| 0a0a52ae93 | |||
| e1d4680fdd | |||
| 532e506d18 | |||
| c9bed48c3c | |||
| 6b4eb6725f | |||
| 6176034151 | |||
| 7471b5d6c0 | |||
| 1fc7341ad2 | |||
| 6af10b0efd | |||
| 70d1e3e75e | |||
| 74006fdcdb | |||
| 4b9f9d6794 | |||
| 39b2fe7e5b | |||
| 01ec60bb87 | |||
| e2dbd0a578 | |||
| df7b22790e | |||
| 50811f74e0 | |||
| d7bf8233ff | |||
| 455486bbc4 | |||
| ba9092e228 | |||
| 67d4e0226f | |||
| 323b3fc121 | |||
| 78279e2550 | |||
| 4e4962f6b3 | |||
| ed95a9d103 | |||
| e791e7c7a0 | |||
| 2545d9c201 | |||
| 2a07c6de2c | |||
| 68b07ddd41 | |||
| 4b69e7704b | |||
| e17ab401f0 | |||
| 57ee077c5e | |||
| c7edd22456 | |||
| 8606a52934 | |||
| 5c6404aa77 | |||
| 429369f4aa | |||
| 9529894a29 | |||
| c4c47a49a8 | |||
| ee7f6f29fc | |||
| c3b52dff53 | |||
| 30af54043e | |||
| 4ebed2a4e5 | |||
| 532ed30ab2 | |||
| 5e99eeb3d8 | |||
| d3abfd77f5 | |||
| 63984b6ab7 | |||
| 93082e5589 | |||
| f6b807a022 | |||
| 8c6a6b85db | |||
| 9f76fb019e | |||
| 29997b4731 | |||
| f0c8e27670 | |||
| d6c985ad0b | |||
| 1696740fdc | |||
| 2cefd5954a | |||
| 8895c50403 | |||
| a2116f86b0 | |||
| 1e472f2d32 | |||
| e3dbf75160 | |||
| ade9289540 | |||
| dfba4eb34a | |||
| b57d2a5a51 | |||
| 84c1079220 | |||
| c8d866294b | |||
| f0baddb205 | |||
| ec2ea68d1f | |||
| c71e6dd054 | |||
| 9efde40a5d | |||
| afef1f2923 | |||
| 09f37099a7 | |||
| 846fbd6653 | |||
| 526acc1834 | |||
| d962afef2d | |||
| 395ac6c1bc | |||
| e9d0bc6b68 | |||
| 483f0ef13c | |||
| 34c2c8dc47 | |||
| 3f9df1f5fb | |||
| 2eeac123c8 | |||
| 64a123a216 | |||
| 6e93d28950 | |||
| 008395614a | |||
| dd1e2c39bc | |||
| 0523cfda5a | |||
| bc68ec78de |
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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 ){
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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(¤tTime);
|
||||
SystemTimeToFileTime(¤tTime, &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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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
@@ -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
@@ -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, "<", 4); break;
|
||||
case '>': sqlite3_str_append(pOut, ">", 4); break;
|
||||
case '&': sqlite3_str_append(pOut, "&", 5); break;
|
||||
case '<': sqlite3_str_append(pOut, "<", 4); break;
|
||||
case '"': sqlite3_str_append(pOut, """, 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
@@ -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
@@ -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{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Executable
+298
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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*/
|
||||
|
||||
@@ -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
|
||||
#
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
branch corrupt-changeset_fix
|
||||
tag corrupt-changeset_fix
|
||||
branch trunk
|
||||
tag trunk
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0de91ff0d798cff21289d893cc441b89fc37b051d5cdf611d92d3ae2bc41cdf2
|
||||
c9171221160f241b450d425f41bb53b42362dccfad4c3fdf64dd5c3562ed4096
|
||||
|
||||
+25
-14
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user