Compare commits

...

30 Commits

Author SHA1 Message Date
drh
cded2fc025 Make use of F_BARRIERFSYNC as an alternative to F_FULLFSYNC when it is available.
FossilOrigin-Name: 3c517ba5dc386c7782c90536ce67562d7d6d2f155203a09b57b1f8f655c1b958
2024-12-11 13:55:19 -06:00
Evan Hahn
c00c9c8238 Hack in F_BARRIERFSYNC support
`FULLFSYNC` is replaced with `BARRIERFSYNC`.

`git grep F_FULLFSYNC` still returns some results, but two are in
comments and one is in an `#ifdef` that should always be defined on
Apple devices.
2024-12-11 13:55:19 -06:00
Stephen Lombardo
c5bd336ece Fixes default log output to console for macOS 2024-08-13 17:15:26 -04:00
Stephen Lombardo
c579558bd1 Updates changelog to reflect upstream 3.46.1 2024-08-13 16:43:33 -04:00
Stephen Lombardo
495260331d Merge sqlite-release(3.46.1) into prerelease-integration 2024-08-13 16:34:00 -04:00
Stephen Lombardo
686e3b8340 Snapshot of upstream SQLite 3.46.1 2024-08-13 16:24:48 -04:00
Stephen Lombardo
7b5ca787a5 Updates changelog 2024-08-07 11:19:57 -04:00
Stephen Lombardo
61bf74fdf6 Updates minimum working set size on windows to increase lockable pages
Under certain conditions the number of VirtualLock-able pages could be exceeded
on windows due to the low default working set size. SQLCipher will now check
whether the process is using default working set sizes, and if so, it
will adjust the minimum working set size to allow additional locked
pages. The maximum working set size will not be changed.
2024-08-01 10:53:16 -04:00
Stephen Lombardo
5c90435c8e Replaces use of deprecated WINAPI_FAMILY_APP macro with WINAPI_FAMILY_PC_APP 2024-08-01 10:39:04 -04:00
Stephen Lombardo
bfa3141e2e Fixes minor compiler warnings related to size_t conversions 2024-08-01 10:23:27 -04:00
Stephen Lombardo
8e3f8079d1 Corrects Podspec license element format 2024-07-24 12:05:53 -04:00
Stephen Lombardo
7dfc063b83 Specifies BSD-3-Clause license in Podspec (Issue #522) 2024-07-24 11:49:21 -04:00
Stephen Lombardo
1f5940b805 Merge branch 'prerelease' of ssh://git.zetetic.net/sqlcipher into prerelease 2024-07-23 15:31:09 -04:00
Stephen Lombardo
d170d89943 Restores default log level and corrects comment in test 2024-07-23 15:30:30 -04:00
Stephen Lombardo
2e01826a45 Converts to __android_log_write for device log operations 2024-07-19 16:18:28 -04:00
Stephen Lombardo
29497ee559 Resolve a remaining reference to log source ALL 2024-07-19 15:25:35 -04:00
Stephen Lombardo
3ac7bde485 Allows multiple cipher_log_source values to be selected
Calling cipher_log_source = NONE will reset the log source list,
after which, calling cipher_log_source for individual sources
(e.g. CORE, PROVIDER, MEMORY, MUTEX) will enable each respectively.
This allows for the fine-grained selection of log sources (e.g.
CORE and PROVIDER, but not MEMORY OR MUTEX). Note that the default
log source is now ANY intead of ALL.

When calling cipher_log_source with or without arguments, the
PRAGMA returns a result set of the enabled levels separated
by spaces.

Tests for the cipher_log_source and cipher_log_level pragmas
are also included.
2024-07-19 14:48:25 -04:00
Stephen Lombardo
b12328cd21 Corrects device logging for android and apple using preformatted messages 2024-07-19 13:57:13 -04:00
Stephen Lombardo
aef55108fb Removes unnecessary non-community extension points 2024-07-19 12:07:32 -04:00
Stephen Lombardo
64f5c4e54a Relocates and defines extension hooks 2024-07-17 14:56:32 -04:00
Stephen Lombardo
4ac2399728 Merges crypto.h, crypto.c, and crypto_impl.c into sqlcipher.c
- Makes internal sqlcipher functions static
- Cleans up unecessary getter/setter wrapper functions
- Simplifies management of default page size,
  HMAC algorithm, KDF algorithm, plaintext header
  size, log settings, memory security
2024-07-17 11:06:16 -04:00
Stephen Lombardo
abcdba2f18 Renames log subsystem to source (e.g. PRAGMA cipher_log_source) 2024-07-15 13:40:45 -04:00
Stephen Lombardo
451db24c6e Emits level and subsystem in log output 2024-07-15 13:27:39 -04:00
Stephen Lombardo
58ee07587e Extends PRAGMAs cipher_log_level and cipher_log_subsystem to print current setting
Calling PRAGMA cipher_log_level or cipher_log_subsystem without an assignment
will now print a friendly string of the current setting. When providing an
assignment the PRAGMAs will echo back the setting applied.
2024-07-08 14:02:59 -04:00
Stephen Lombardo
acbf140b4f Improves error logging in cipher_migrate
Several potential error conditions would only be logged at DEBUG level
during database version migration. These have been adjusted to log at
ERROR or WARN level instead.
2024-07-08 13:29:54 -04:00
Stephen Lombardo
44fb467932 Adds PRAGMA cipher_log_subsystem to restrict output of log messages
This change categorizes all sqlcipher_log calls with a subsystem of
either CORE, MEMORY, MUTEX, or PROVIDER. The pragma allows an application
to restrict logging of messages to a specific subsystem to reduce
the verbosity of log messages. This is most useful in the context of
TRACE and DEBUG level logging where the volume of log messages
is very high.
2024-07-08 12:58:28 -04:00
Stephen Lombardo
c50f84f591 Avoids overwriting log level and target if they've already been set
This commit corrects an issue what could occur if cipher_log_level
and/or cipher_log are set prior to activating sqlcipher, i.e. if
those pragmas are called before PRAGMA key or sqlite3_key. In that
case the specific requested log settings would be ovewritten by
different defaults.
2024-07-03 15:18:44 -04:00
Stephen Lombardo
addaa4c047 Merge sqlite-release(3.46.0) into prerelease-integration 2024-05-23 14:14:12 -04:00
Stephen Lombardo
58e90d6b29 Snapshot of upstream SQLite 3.46.0 2024-05-23 13:52:30 -04:00
Stephen Lombardo
91520d4174 version bump to 4.6.1 2024-05-23 13:34:01 -04:00
226 changed files with 14959 additions and 6040 deletions

View File

@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file.
## [unreleased] - (? 2024 - [unreleased changes])
## [4.6.1] - (August 2024 - [4.6.1 changes])
- Updates baseline to upstream SQLite 3.46.1
- Significant refactor to merge `crypto.h`, `crypto.c`, and `crypto_impl.c` into a single `sqlcipher.c` source file for simplicity.
- Updates minimum working set size on windows to increase lockable pages
- Adds new `PRAGMA cipher_log_source` for filtering log output on higher verbosity levels
- Improves log output by including the log level and source prior to message
- Improves error logging in `PRAGMA cipher_migrate`
- Fixes issue where log level and target would be overwritten if set prior to initialization
- Corrects Podspec license element to use specific BSD 3 Clause
- Fixes default log output to console for macOS
## [4.6.0] - (May 2024 - [4.6.0 changes])
- Sets default log level to WARN
- Sends default log output to: logcat for Android; Console for iOS and macOS; and stderr for all other platforms
@ -249,7 +260,9 @@ All notable changes to this project will be documented in this file.
- Change KDF iteration length from 4,000 to 64,000
[unreleased]: https://github.com/sqlcipher/sqlcipher/tree/prerelease
[unreleased changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.6.0...prerelease
[unreleased changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.6.1...prerelease
[4.6.1]: https://github.com/sqlcipher/sqlcipher/tree/v4.6.1
[4.6.1 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.6.0...v4.6.1
[4.6.0]: https://github.com/sqlcipher/sqlcipher/tree/v4.6.0
[4.6.0 changes]: https://github.com/sqlcipher/sqlcipher/compare/v4.5.7...v4.6.0
[4.5.7]: https://github.com/sqlcipher/sqlcipher/tree/v4.5.7

View File

@ -145,18 +145,15 @@ LTLINK_EXTRAS += $(GCOV_LDFLAGS$(USE_GCOV))
# BEGIN CRYPTO
CRYPTOLIBOBJ = \
crypto.lo \
crypto_impl.lo \
sqlcipher.lo \
crypto_openssl.lo \
crypto_libtomcrypt.lo \
crypto_nss.lo \
crypto_cc.lo
CRYPTOSRC = \
$(TOP)/src/crypto.h \
$(TOP)/src/sqlcipher.h \
$(TOP)/src/crypto.c \
$(TOP)/src/crypto_impl.c \
$(TOP)/src/sqlcipher.c \
$(TOP)/src/crypto_libtomcrypt.c \
$(TOP)/src/crypto_nss.c \
$(TOP)/src/crypto_openssl.c \
@ -440,6 +437,8 @@ TESTSRC = \
$(TOP)/ext/recover/sqlite3recover.c \
$(TOP)/ext/recover/dbdata.c \
$(TOP)/ext/recover/test_recover.c \
$(TOP)/ext/intck/test_intck.c \
$(TOP)/ext/intck/sqlite3intck.c \
$(TOP)/ext/rbu/test_rbu.c
# Statically linked extensions
@ -837,7 +836,7 @@ has_tclsh85:
touch .target_source
sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl src-verify has_tclsh84
$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS)
$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) $(EXTRA_SRC)
cp tsrc/sqlite3ext.h .
cp $(TOP)/ext/session/sqlite3session.h .
@ -848,7 +847,7 @@ sqlite3r.c: sqlite3.c sqlite3r.h has_tclsh84
cp $(TOP)/ext/recover/sqlite3recover.c tsrc/
cp $(TOP)/ext/recover/sqlite3recover.h tsrc/
cp $(TOP)/ext/recover/dbdata.c tsrc/
$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl --enable-recover $(AMALGAMATION_LINE_MACROS)
$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl --enable-recover $(AMALGAMATION_LINE_MACROS) $(EXTRA_SRC)
sqlite3ext.h: .target_source
cp tsrc/sqlite3ext.h .
@ -891,10 +890,8 @@ opcodes.lo: opcodes.c
$(LTCOMPILE) $(TEMP_STORE) -c opcodes.c
# BEGIN CRYPTO
crypto.lo: $(TOP)/src/crypto.c $(HDR)
$(LTCOMPILE) -c $(TOP)/src/crypto.c
crypto_impl.lo: $(TOP)/src/crypto_impl.c $(HDR)
$(LTCOMPILE) -c $(TOP)/src/crypto_impl.c
sqlcipher.lo: $(TOP)/src/sqlcipher.c $(HDR)
$(LTCOMPILE) -c $(TOP)/src/sqlcipher.c
crypto_openssl.lo: $(TOP)/src/crypto_openssl.c $(HDR)
$(LTCOMPILE) -c $(TOP)/src/crypto_openssl.c
crypto_nss.lo: $(TOP)/src/crypto_nss.c $(HDR)
@ -1189,35 +1186,37 @@ keywordhash.h: $(TOP)/tool/mkkeywordhash.c
$(BCC) -o mkkeywordhash$(BEXE) $(OPT_FEATURE_FLAGS) $(OPTS) $(TOP)/tool/mkkeywordhash.c
./mkkeywordhash$(BEXE) >keywordhash.h
# Source files that go into making shell.c
SHELL_SRC = \
$(TOP)/src/shell.c.in \
$(TOP)/ext/consio/console_io.c \
$(TOP)/ext/consio/console_io.h \
$(TOP)/ext/misc/appendvfs.c \
$(TOP)/ext/misc/completion.c \
$(TOP)/ext/misc/decimal.c \
$(TOP)/ext/misc/basexx.c \
$(TOP)/ext/misc/base64.c \
$(TOP)/ext/misc/base85.c \
$(TOP)/ext/misc/fileio.c \
$(TOP)/ext/misc/ieee754.c \
$(TOP)/ext/misc/regexp.c \
$(TOP)/ext/misc/series.c \
$(TOP)/ext/misc/shathree.c \
$(TOP)/ext/misc/sqlar.c \
$(TOP)/ext/misc/uint.c \
$(TOP)/ext/expert/sqlite3expert.c \
$(TOP)/ext/expert/sqlite3expert.h \
$(TOP)/ext/misc/zipfile.c \
$(TOP)/ext/misc/memtrace.c \
$(TOP)/ext/misc/pcachetrace.c \
$(TOP)/ext/recover/dbdata.c \
$(TOP)/ext/recover/sqlite3recover.c \
$(TOP)/ext/recover/sqlite3recover.h \
$(TOP)/src/test_windirent.c
# Source and header files that shell.c depends on
SHELL_DEP = \
$(TOP)/src/shell.c.in \
$(TOP)/ext/consio/console_io.c \
$(TOP)/ext/consio/console_io.h \
$(TOP)/ext/expert/sqlite3expert.c \
$(TOP)/ext/expert/sqlite3expert.h \
$(TOP)/ext/intck/sqlite3intck.c \
$(TOP)/ext/intck/sqlite3intck.h \
$(TOP)/ext/misc/appendvfs.c \
$(TOP)/ext/misc/base64.c \
$(TOP)/ext/misc/base85.c \
$(TOP)/ext/misc/completion.c \
$(TOP)/ext/misc/decimal.c \
$(TOP)/ext/misc/fileio.c \
$(TOP)/ext/misc/ieee754.c \
$(TOP)/ext/misc/memtrace.c \
$(TOP)/ext/misc/pcachetrace.c \
$(TOP)/ext/misc/regexp.c \
$(TOP)/ext/misc/series.c \
$(TOP)/ext/misc/shathree.c \
$(TOP)/ext/misc/sqlar.c \
$(TOP)/ext/misc/uint.c \
$(TOP)/ext/misc/zipfile.c \
$(TOP)/ext/recover/dbdata.c \
$(TOP)/ext/recover/sqlite3recover.c \
$(TOP)/ext/recover/sqlite3recover.h \
$(TOP)/src/test_windirent.c \
$(TOP)/src/test_windirent.h
shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl has_tclsh84
shell.c: $(SHELL_DEP) $(TOP)/tool/mkshellc.tcl has_tclsh84
$(TCLSH_CMD) $(TOP)/tool/mkshellc.tcl >shell.c
@ -1345,9 +1344,9 @@ testfixture$(TEXE): has_tclsh85 $(TESTFIXTURE_SRC)
$(LTLINK) -DSQLITE_NO_SYNC=1 $(TEMP_STORE) $(TESTFIXTURE_FLAGS) \
-o $@ $(TESTFIXTURE_SRC) $(LIBTCL) $(TLIBS)
coretestprogs: $(TESTPROGS)
coretestprogs: testfixture$(BEXE) sqlite3$(BEXE)
testprogs: coretestprogs srcck1$(BEXE) fuzzcheck$(TEXE) sessionfuzz$(TEXE)
testprogs: $(TESTPROGS) srcck1$(BEXE) fuzzcheck$(TEXE) sessionfuzz$(TEXE)
# A very detailed test running most or all test cases
fulltest: alltest fuzztest

View File

@ -18,6 +18,13 @@ USE_AMALGAMATION = 1
!ENDIF
# <</mark>>
# Optionally set EXTRA_SRC to a list of C files to append to
# the generated sqlite3.c.
#
!IFNDEF EXTRA_SRC
EXTRA_SRC =
!ENDIF
# Set this non-0 to enable full warnings (-W4, etc) when compiling.
#
!IFNDEF USE_FULLWARN
@ -1320,13 +1327,11 @@ LIBRESOBJS =
# Core source code files, part 1.
#
SRC00 = \
$(TOP)\src\crypto.c \
$(TOP)\src\sqlcipher.c \
$(TOP)\src\crypto_cc.c \
$(TOP)\src\crypto_impl.c \
$(TOP)\src\crypto_libtomcrypt.c \
$(TOP)\src\crypto_nss.c \
$(TOP)\src\crypto_openssl.c \
$(TOP)\src\crypto.h \
$(TOP)\src\sqlcipher.h \
$(TOP)\src\alter.c \
$(TOP)\src\analyze.c \
@ -1603,6 +1608,8 @@ TESTEXT = \
$(TOP)\ext\rtree\test_rtreedoc.c \
$(TOP)\ext\recover\sqlite3recover.c \
$(TOP)\ext\recover\test_recover.c \
$(TOP)\ext\intck\test_intck.c \
$(TOP)\ext\intck\sqlite3intck.c \
$(TOP)\ext\recover\dbdata.c
# If use of zlib is enabled, add the "zipfile.c" source file.
@ -1921,7 +1928,7 @@ mptest: mptester.exe
echo > .target_source
sqlite3.c: .target_source sqlite3ext.h sqlite3session.h $(MKSQLITE3C_TOOL) src-verify.exe
$(TCLSH_CMD) $(MKSQLITE3C_TOOL) $(MKSQLITE3C_ARGS)
$(TCLSH_CMD) $(MKSQLITE3C_TOOL) $(MKSQLITE3C_ARGS) $(EXTRA_SRC)
sqlite3-all.c: sqlite3.c $(TOP)\tool\split-sqlite3c.tcl
$(TCLSH_CMD) $(TOP)\tool\split-sqlite3c.tcl
@ -2271,39 +2278,44 @@ mkkeywordhash.exe: $(TOP)\tool\mkkeywordhash.c
keywordhash.h: $(TOP)\tool\mkkeywordhash.c mkkeywordhash.exe
.\mkkeywordhash.exe > keywordhash.h
# Source files that go into making shell.c
SHELL_SRC = \
$(TOP)\src\shell.c.in \
$(TOP)\ext\consio\console_io.c \
$(TOP)\ext\consio\console_io.h \
$(TOP)\ext\misc\appendvfs.c \
$(TOP)\ext\misc\completion.c \
$(TOP)\ext\misc\base64.c \
$(TOP)\ext\misc\base85.c \
$(TOP)\ext\misc\decimal.c \
$(TOP)\ext\misc\fileio.c \
$(TOP)\ext\misc\ieee754.c \
$(TOP)\ext\misc\regexp.c \
$(TOP)\ext\misc\series.c \
$(TOP)\ext\misc\shathree.c \
$(TOP)\ext\misc\uint.c \
$(TOP)\ext\expert\sqlite3expert.c \
$(TOP)\ext\expert\sqlite3expert.h \
$(TOP)\ext\misc\memtrace.c \
$(TOP)\ext\misc\pcachetrace.c \
$(TOP)\ext\recover\dbdata.c \
$(TOP)\ext\recover\sqlite3recover.c \
$(TOP)\ext\recover\sqlite3recover.h \
$(TOP)\src\test_windirent.c
# Source and header files that shell.c depends on
SHELL_DEP = \
$(TOP)\src\shell.c.in \
$(TOP)\ext\consio\console_io.c \
$(TOP)\ext\consio\console_io.h \
$(TOP)\ext\expert\sqlite3expert.c \
$(TOP)\ext\expert\sqlite3expert.h \
$(TOP)\ext\intck\sqlite3intck.c \
$(TOP)\ext\intck\sqlite3intck.h \
$(TOP)\ext\misc\appendvfs.c \
$(TOP)\ext\misc\base64.c \
$(TOP)\ext\misc\base85.c \
$(TOP)\ext\misc\completion.c \
$(TOP)\ext\misc\decimal.c \
$(TOP)\ext\misc\fileio.c \
$(TOP)\ext\misc\ieee754.c \
$(TOP)\ext\misc\memtrace.c \
$(TOP)\ext\misc\pcachetrace.c \
$(TOP)\ext\misc\regexp.c \
$(TOP)\ext\misc\series.c \
$(TOP)\ext\misc\shathree.c \
$(TOP)\ext\misc\sqlar.c \
$(TOP)\ext\misc\uint.c \
$(TOP)\ext\misc\zipfile.c \
$(TOP)\ext\recover\dbdata.c \
$(TOP)\ext\recover\sqlite3recover.c \
$(TOP)\ext\recover\sqlite3recover.h \
$(TOP)\src\test_windirent.c \
$(TOP)\src\test_windirent.h
# If use of zlib is enabled, add the "zipfile.c" source file.
#
!IF $(USE_ZLIB)!=0
SHELL_SRC = $(SHELL_SRC) $(TOP)\ext\misc\sqlar.c
SHELL_SRC = $(SHELL_SRC) $(TOP)\ext\misc\zipfile.c
SHELL_DEP = $(SHELL_DEP) $(TOP)\ext\misc\sqlar.c
SHELL_DEP = $(SHELL_DEP) $(TOP)\ext\misc\zipfile.c
!ENDIF
shell.c: $(SHELL_SRC) $(TOP)\tool\mkshellc.tcl
shell.c: $(SHELL_DEP) $(TOP)\tool\mkshellc.tcl
$(TCLSH_CMD) $(TOP)\tool\mkshellc.tcl > shell.c
zlib:
@ -2490,9 +2502,9 @@ extensiontest: testfixture.exe testloadext.dll
tool-zip: testfixture.exe sqlite3.exe sqldiff.exe sqlite3_analyzer.exe $(TOP)\tool\mktoolzip.tcl
.\testfixture.exe $(TOP)\tool\mktoolzip.tcl
coretestprogs: $(TESTPROGS)
coretestprogs: testfixture.exe sqlite3.exe
testprogs: coretestprogs srcck1.exe fuzzcheck.exe sessionfuzz.exe
testprogs: $(TESTPROGS) srcck1.exe fuzzcheck.exe sessionfuzz.exe
fulltest: alltest fuzztest
@ -2547,7 +2559,7 @@ mdevtest:
# Testing for a release
#
releasetest: testfixture.exe fuzztest
releasetest: testfixture.exe
testfixture.exe $(TOP)\test\testrunner.tcl release

View File

@ -3,7 +3,10 @@
"default_subspecs": "standard",
"description": "SQLCipher is an open source extension to SQLite that provides transparent 256-bit AES encryption of database files.",
"homepage": "https://www.zetetic.net/sqlcipher/",
"license": "BSD",
"license": {
"type": "BSD-3-Clause",
"file": "LICENSE.md"
},
"name": "SQLCipher",
"platforms": {
"ios": "12.0",
@ -15,10 +18,10 @@
"requires_arc": false,
"source": {
"git": "https://github.com/sqlcipher/sqlcipher.git",
"tag": "v4.6.0"
"tag": "v4.6.1"
},
"summary": "Full Database Encryption for SQLite.",
"version": "4.6.0",
"version": "4.6.1",
"subspecs": [
{
"compiler_flags": [

View File

@ -1 +1 @@
3.45.3
3.46.1

BIN
art/icon-243x273.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
art/icon-80x90.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -18,6 +18,13 @@
TOP = .
# Optionally set EXTRA_SRC to a list of C files to append to
# the generated sqlite3.c.
#
!IFNDEF EXTRA_SRC
EXTRA_SRC =
!ENDIF
# Set this non-0 to enable full warnings (-W4, etc) when compiling.
#
!IFNDEF USE_FULLWARN

View File

@ -19,7 +19,7 @@ dnl to configure the system for the local environment.
# so that we create the export library with the dll.
#-----------------------------------------------------------------------
AC_INIT([sqlite],[3.45.3])
AC_INIT([sqlite],[3.46.1])
#--------------------------------------------------------------------
# Call TEA_INIT as the first TEA_ macro to set up initial vars.

18
configure vendored
View File

@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.71 for sqlcipher 3.45.3.
# Generated by GNU Autoconf 2.71 for sqlcipher 3.46.1.
#
#
# Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation,
@ -618,8 +618,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='sqlcipher'
PACKAGE_TARNAME='sqlcipher'
PACKAGE_VERSION='3.45.3'
PACKAGE_STRING='sqlcipher 3.45.3'
PACKAGE_VERSION='3.46.1'
PACKAGE_STRING='sqlcipher 3.46.1'
PACKAGE_BUGREPORT=''
PACKAGE_URL=''
@ -1379,7 +1379,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures sqlcipher 3.45.3 to adapt to many kinds of systems.
\`configure' configures sqlcipher 3.46.1 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@ -1445,7 +1445,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of sqlcipher 3.45.3:";;
short | recursive ) echo "Configuration of sqlcipher 3.46.1:";;
esac
cat <<\_ACEOF
@ -1588,7 +1588,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
sqlcipher configure 3.45.3
sqlcipher configure 3.46.1
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@ -1863,7 +1863,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by sqlcipher $as_me 3.45.3, which was
It was created by sqlcipher $as_me 3.46.1, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@ -14684,7 +14684,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by sqlcipher $as_me 3.45.3, which was
This file was extended by sqlcipher $as_me 3.46.1, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@ -14752,7 +14752,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
sqlcipher config.status 3.45.3
sqlcipher config.status 3.46.1
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"

View File

@ -683,6 +683,7 @@ other than that, the order of directives in Lemon is arbitrary.</p>
<li><tt><a href='#pifdef'>%endif</a></tt>
<li><tt><a href='#extraarg'>%extra_argument</a></tt>
<li><tt><a href='#pfallback'>%fallback</a></tt>
<li><tt><a href='#reallc'>%free</a></tt>
<li><tt><a href='#pifdef'>%if</a></tt>
<li><tt><a href='#pifdef'>%ifdef</a></tt>
<li><tt><a href='#pifdef'>%ifndef</a></tt>
@ -693,6 +694,7 @@ other than that, the order of directives in Lemon is arbitrary.</p>
<li><tt><a href='#parse_accept'>%parse_accept</a></tt>
<li><tt><a href='#parse_failure'>%parse_failure</a></tt>
<li><tt><a href='#pright'>%right</a></tt>
<li><tt><a href='#reallc'>%realloc</a></tt>
<li><tt><a href='#stack_overflow'>%stack_overflow</a></tt>
<li><tt><a href='#stack_size'>%stack_size</a></tt>
<li><tt><a href='#start_symbol'>%start_symbol</a></tt>
@ -1200,6 +1202,21 @@ match any input token.</p>
the wildcard token and some other token, the other token is always used.
The wildcard token is only matched if there are no alternatives.</p>
<a id='reallc'></a>
<h4>4.4.26 The <tt>%realloc</tt> and <tt>%free</tt> directives</h4>
<p>The <tt>%realloc</tt> and <tt>%free</tt> directives defines function
that allocate and free heap memory. The signatures of these functions
should be the same as the realloc() and free() functions from the standard
C library.
<p>If both of these functions are defined
then these functions are used to allocate and free
memory for supplemental parser stack space, if the initial
parse stack space is exceeded. The initial parser stack size
is specified by either <tt>%stack_size</tt> or the
-DYYSTACKDEPTH compile-time flag.
<a id='errors'></a>
<h2>5.0 Error Processing</h2>
@ -1224,6 +1241,7 @@ to begin parsing a new file. This is what will happen at the very
first syntax error, of course, if there are no instances of the
"error" non-terminal in your grammar.</p>
<a id='history'></a>
<h2>6.0 History of Lemon</h2>

View File

@ -17,7 +17,6 @@
<li> 3.3. <a href=#source_code_test_failures>Investigating Source Code Test Failures</a>
</ul>
<li> 4. <a href=#testrunner_options>Extra testrunner.tcl Options</a>
# 4. Extra testrunner.tcl Options
<li> 5. <a href=#cpu_cores>Controlling CPU Core Utilization</a>
</ul>
@ -29,12 +28,18 @@ multiple jobs. It supports the following types of tests:
* Tcl test scripts.
* Tests run with [make] commands. Specifically, at time of writing,
[make fuzztest], [make mptest], [make sourcetest] and [make threadtest].
* Tests run with `make` commands. Examples:
- `make mdevtest`
- `make releasetest`
- `make sdevtest`
- `make testrunner`
testrunner.tcl pipes the output of all tests and builds run into log file
**testrunner.log**, created in the cwd directory. Searching this file for
"failed" is a good way to find the output of a failed test.
**testrunner.log**, created in the current working directory. Search this
file to find details of errors. Suggested search commands:
* `grep "^!" testrunner.log`
* `grep failed testrunner.log`
testrunner.tcl also populates SQLite database **testrunner.db**. This database
contains details of all tests run, running and to be run. A useful query
@ -60,7 +65,7 @@ Running:
in another terminal is a good way to keep an eye on a long running test.
Sometimes testrunner.tcl uses the [testfixture] binary that it is run with
Sometimes testrunner.tcl uses the `testfixture` binary that it is run with
to run tests (see "Binary Tests" below). Sometimes it builds testfixture and
other binaries in specific configurations to test (see "Source Tests").
@ -68,9 +73,9 @@ other binaries in specific configurations to test (see "Source Tests").
# 2. Binary Tests
The commands described in this section all run various combinations of the Tcl
test scripts using the [testfixture] binary used to run the testrunner.tcl
test scripts using the `testfixture` binary used to run the testrunner.tcl
script (i.e. they do not invoke the compiler to build new binaries, or the
[make] command to run tests that are not Tcl scripts). The procedure to run
`make` command to run tests that are not Tcl scripts). The procedure to run
these tests is therefore:
1. Build the "testfixture" (or "testfixture.exe" for windows) binary using
@ -193,7 +198,7 @@ TODO: ./configure + Makefile.msc build systems.
## 3.1. Commands to Run SQLite Tests
The **mdevtest** command is equivalent to running the veryquick tests and
the [make fuzztest] target once for each of two --enable-all builds - one
the `make fuzztest` target once for each of two --enable-all builds - one
with debugging enabled and one without:
```
@ -283,7 +288,7 @@ a dos \*.bat file on windows. For example:
```
The generated bash or \*.bat file script accepts a single argument - a makefile
target to build. This may be used either to run a [make] command test directly,
target to build. This may be used either to run a `make` command test directly,
or else to build a testfixture (or testfixture.exe) binary with which to
run a Tcl test script, as <a href=#binary_test_failures>described above</a>.
@ -310,6 +315,16 @@ would normally execute into the testrunner.log file. Example:
tclsh $TESTDIR/testrunner.tcl --dryrun mdevtest"
```
The **--explain** option is similar to --dryrun in that it prevents testrunner.tcl
from building any binaries or running any tests. The difference is that --explain
prints on standard output a human-readable summary of all the builds and tests that
would have been run.
```
# Show what builds and tests would have been run
tclsh $TESTDIR/testrunner.tcl --explain mdevtest
```
<a name=cpu_cores></a>
# 5. Controlling CPU Core Utilization
@ -339,6 +354,3 @@ testrunner.log and testrunner.db files:
```
$ ./testfixture $TESTDIR/testrunner.tcl njob $NEW_NUMBER_OF_JOBS
```

View File

@ -53,11 +53,6 @@
# define CIO_WIN_WC_XLATE 0 /* Not exposing translation routines at all */
#endif
#if CIO_WIN_WC_XLATE
/* Character used to represent a known-incomplete UTF-8 char group (<28>) */
static WCHAR cBadGroup = 0xfffd;
#endif
#if CIO_WIN_WC_XLATE
static HANDLE handleOfFile(FILE *pf){
int fileDesc = _fileno(pf);

View File

@ -8,6 +8,7 @@
# May you share freely, never taking more than you give.
#
#***********************************************************************
# TESTRUNNER: shell
#
# The focus of this file is testing the CLI shell tool. Specifically,
# the ".recommend" command.
@ -464,6 +465,13 @@ do_execsql_test 5.3 {
t2 t2_idx_0001295b {100 20 5}
}
do_catchsql_test 5.4 {
SELECT sqlite_expert_rem(123, 123);
} {1 {no such function: sqlite_expert_rem}}
do_catchsql_test 5.5 {
SELECT sqlite_expert_sample();
} {1 {no such function: sqlite_expert_sample}}
if 0 {
do_test expert1-6.0 {
catchcmd :memory: {

View File

@ -626,7 +626,7 @@ static int expertFilter(
pCsr->pData = 0;
if( rc==SQLITE_OK ){
rc = idxPrintfPrepareStmt(pExpert->db, &pCsr->pData, &pVtab->base.zErrMsg,
"SELECT * FROM main.%Q WHERE sample()", pVtab->pTab->zName
"SELECT * FROM main.%Q WHERE sqlite_expert_sample()", pVtab->pTab->zName
);
}
@ -1500,7 +1500,7 @@ struct IdxRemCtx {
};
/*
** Implementation of scalar function rem().
** Implementation of scalar function sqlite_expert_rem().
*/
static void idxRemFunc(
sqlite3_context *pCtx,
@ -1513,7 +1513,7 @@ static void idxRemFunc(
assert( argc==2 );
iSlot = sqlite3_value_int(argv[0]);
assert( iSlot<=p->nSlot );
assert( iSlot<p->nSlot );
pSlot = &p->aSlot[iSlot];
switch( pSlot->eType ){
@ -1624,7 +1624,8 @@ static int idxPopulateOneStat1(
const char *zName = (const char*)sqlite3_column_text(pIndexXInfo, 0);
const char *zColl = (const char*)sqlite3_column_text(pIndexXInfo, 1);
zCols = idxAppendText(&rc, zCols,
"%sx.%Q IS rem(%d, x.%Q) COLLATE %s", zComma, zName, nCol, zName, zColl
"%sx.%Q IS sqlite_expert_rem(%d, x.%Q) COLLATE %s",
zComma, zName, nCol, zName, zColl
);
zOrder = idxAppendText(&rc, zOrder, "%s%d", zComma, ++nCol);
}
@ -1757,13 +1758,13 @@ static int idxPopulateStat1(sqlite3expert *p, char **pzErr){
if( rc==SQLITE_OK ){
sqlite3 *dbrem = (p->iSample==100 ? p->db : p->dbv);
rc = sqlite3_create_function(
dbrem, "rem", 2, SQLITE_UTF8, (void*)pCtx, idxRemFunc, 0, 0
rc = sqlite3_create_function(dbrem, "sqlite_expert_rem",
2, SQLITE_UTF8, (void*)pCtx, idxRemFunc, 0, 0
);
}
if( rc==SQLITE_OK ){
rc = sqlite3_create_function(
p->db, "sample", 0, SQLITE_UTF8, (void*)&samplectx, idxSampleFunc, 0, 0
rc = sqlite3_create_function(p->db, "sqlite_expert_sample",
0, SQLITE_UTF8, (void*)&samplectx, idxSampleFunc, 0, 0
);
}
@ -1815,6 +1816,9 @@ static int idxPopulateStat1(sqlite3expert *p, char **pzErr){
rc = sqlite3_exec(p->dbm, "ANALYZE sqlite_schema", 0, 0, 0);
}
sqlite3_create_function(p->db, "sqlite_expert_rem", 2, SQLITE_UTF8, 0,0,0,0);
sqlite3_create_function(p->db, "sqlite_expert_sample", 0,SQLITE_UTF8,0,0,0,0);
sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp."UNIQUE_TABLE_NAME,0,0,0);
return rc;
}
@ -1948,7 +1952,7 @@ sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErrmsg){
sqlite3_stmt *pSql = 0;
rc = idxPrintfPrepareStmt(pNew->db, &pSql, pzErrmsg,
"SELECT sql FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%%'"
" AND sql NOT LIKE 'CREATE VIRTUAL %%'"
" AND sql NOT LIKE 'CREATE VIRTUAL %%' ORDER BY rowid"
);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
const char *zSql = (const char*)sqlite3_column_text(pSql, 0);

View File

@ -4014,22 +4014,24 @@ static int fts3IntegrityMethod(
char **pzErr /* Write error message here */
){
Fts3Table *p = (Fts3Table*)pVtab;
int rc;
int rc = SQLITE_OK;
int bOk = 0;
UNUSED_PARAMETER(isQuick);
rc = sqlite3Fts3IntegrityCheck(p, &bOk);
assert( rc!=SQLITE_CORRUPT_VTAB || bOk==0 );
if( rc!=SQLITE_OK && rc!=SQLITE_CORRUPT_VTAB ){
assert( rc!=SQLITE_CORRUPT_VTAB );
if( rc==SQLITE_ERROR || (rc&0xFF)==SQLITE_CORRUPT ){
*pzErr = sqlite3_mprintf("unable to validate the inverted index for"
" FTS%d table %s.%s: %s",
p->bFts4 ? 4 : 3, zSchema, zTabname, sqlite3_errstr(rc));
}else if( bOk==0 ){
if( *pzErr ) rc = SQLITE_OK;
}else if( rc==SQLITE_OK && bOk==0 ){
*pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s",
p->bFts4 ? 4 : 3, zSchema, zTabname);
if( *pzErr==0 ) rc = SQLITE_NOMEM;
}
sqlite3Fts3SegmentsClose(p);
return SQLITE_OK;
return rc;
}

View File

@ -446,7 +446,7 @@ static void fts3SnippetDetails(
}
mCover |= mPhrase;
for(j=0; j<pPhrase->nToken; j++){
for(j=0; j<pPhrase->nToken && j<pIter->nSnippet; j++){
mHighlight |= (mPos>>j);
}

View File

@ -5372,7 +5372,12 @@ int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk){
sqlite3_finalize(pStmt);
}
*pbOk = (rc==SQLITE_OK && cksum1==cksum2);
if( rc==SQLITE_CORRUPT_VTAB ){
rc = SQLITE_OK;
*pbOk = 0;
}else{
*pbOk = (rc==SQLITE_OK && cksum1==cksum2);
}
return rc;
}

View File

@ -55,8 +55,8 @@ struct Fts5PhraseIter {
** EXTENSION API FUNCTIONS
**
** xUserData(pFts):
** Return a copy of the context pointer the extension function was
** registered with.
** Return a copy of the pUserData pointer passed to the xCreateFunction()
** API when the extension function was registered.
**
** xColumnTotalSize(pFts, iCol, pnToken):
** If parameter iCol is less than zero, set output variable *pnToken

View File

@ -324,7 +324,11 @@ int sqlite3Fts5ExprNew(
}
sqlite3_free(sParse.apPhrase);
*pzErr = sParse.zErr;
if( 0==*pzErr ){
*pzErr = sParse.zErr;
}else{
sqlite3_free(sParse.zErr);
}
return sParse.rc;
}
@ -2452,6 +2456,7 @@ Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
assert( pRight->eType==FTS5_STRING
|| pRight->eType==FTS5_TERM
|| pRight->eType==FTS5_EOF
|| (pRight->eType==FTS5_AND && pParse->bPhraseToAnd)
);
if( pLeft->eType==FTS5_AND ){

View File

@ -1700,6 +1700,7 @@ static int fts5UpdateMethod(
rc = SQLITE_ERROR;
}else{
rc = fts5SpecialDelete(pTab, apVal);
bUpdateOrDelete = 1;
}
}else{
rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]);
@ -2874,14 +2875,16 @@ int sqlite3Fts5GetTokenizer(
if( pMod==0 ){
assert( nArg>0 );
rc = SQLITE_ERROR;
*pzErr = sqlite3_mprintf("no such tokenizer: %s", azArg[0]);
if( pzErr ) *pzErr = sqlite3_mprintf("no such tokenizer: %s", azArg[0]);
}else{
rc = pMod->x.xCreate(
pMod->pUserData, (azArg?&azArg[1]:0), (nArg?nArg-1:0), &pConfig->pTok
);
pConfig->pTokApi = &pMod->x;
if( rc!=SQLITE_OK ){
if( pzErr ) *pzErr = sqlite3_mprintf("error in tokenizer constructor");
if( pzErr && rc!=SQLITE_NOMEM ){
*pzErr = sqlite3_mprintf("error in tokenizer constructor");
}
}else{
pConfig->ePattern = sqlite3Fts5TokenizerPattern(
pMod->x.xCreate, pConfig->pTok
@ -2975,18 +2978,25 @@ static int fts5IntegrityMethod(
assert( pzErr!=0 && *pzErr==0 );
UNUSED_PARAM(isQuick);
assert( pTab->p.pConfig->pzErrmsg==0 );
pTab->p.pConfig->pzErrmsg = pzErr;
rc = sqlite3Fts5StorageIntegrity(pTab->pStorage, 0);
if( (rc&0xff)==SQLITE_CORRUPT ){
*pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s",
zSchema, zTabname);
}else if( rc!=SQLITE_OK ){
*pzErr = sqlite3_mprintf("unable to validate the inverted index for"
" FTS5 table %s.%s: %s",
zSchema, zTabname, sqlite3_errstr(rc));
if( *pzErr==0 && rc!=SQLITE_OK ){
if( (rc&0xff)==SQLITE_CORRUPT ){
*pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s",
zSchema, zTabname);
rc = (*pzErr) ? SQLITE_OK : SQLITE_NOMEM;
}else{
*pzErr = sqlite3_mprintf("unable to validate the inverted index for"
" FTS5 table %s.%s: %s",
zSchema, zTabname, sqlite3_errstr(rc));
}
}
sqlite3Fts5IndexCloseReader(pTab->p.pIndex);
return SQLITE_OK;
sqlite3Fts5IndexCloseReader(pTab->p.pIndex);
pTab->p.pConfig->pzErrmsg = 0;
return rc;
}
static int fts5Init(sqlite3 *db){

View File

@ -79,7 +79,7 @@ static int fts5AsciiCreate(
int i;
memset(p, 0, sizeof(AsciiTokenizer));
memcpy(p->aTokenChar, aAsciiTokenChar, sizeof(aAsciiTokenChar));
for(i=0; rc==SQLITE_OK && i<nArg; i+=2){
for(i=0; rc==SQLITE_OK && i<nArg-1; i+=2){
const char *zArg = azArg[i+1];
if( 0==sqlite3_stricmp(azArg[i], "tokenchars") ){
fts5AsciiAddExceptions(p, zArg, 1);
@ -90,6 +90,7 @@ static int fts5AsciiCreate(
rc = SQLITE_ERROR;
}
}
if( rc==SQLITE_OK && i<nArg ) rc = SQLITE_ERROR;
if( rc!=SQLITE_OK ){
fts5AsciiDelete((Fts5Tokenizer*)p);
p = 0;
@ -381,17 +382,16 @@ static int fts5UnicodeCreate(
}
/* Search for a "categories" argument */
for(i=0; rc==SQLITE_OK && i<nArg; i+=2){
for(i=0; rc==SQLITE_OK && i<nArg-1; i+=2){
if( 0==sqlite3_stricmp(azArg[i], "categories") ){
zCat = azArg[i+1];
}
}
if( rc==SQLITE_OK ){
rc = unicodeSetCategories(p, zCat);
}
for(i=0; rc==SQLITE_OK && i<nArg; i+=2){
for(i=0; rc==SQLITE_OK && i<nArg-1; i+=2){
const char *zArg = azArg[i+1];
if( 0==sqlite3_stricmp(azArg[i], "remove_diacritics") ){
if( (zArg[0]!='0' && zArg[0]!='1' && zArg[0]!='2') || zArg[1] ){
@ -416,6 +416,7 @@ static int fts5UnicodeCreate(
rc = SQLITE_ERROR;
}
}
if( i<nArg && rc==SQLITE_OK ) rc = SQLITE_ERROR;
}else{
rc = SQLITE_NOMEM;
@ -1298,7 +1299,7 @@ static int fts5TriCreate(
int i;
pNew->bFold = 1;
pNew->iFoldParam = 0;
for(i=0; rc==SQLITE_OK && i<nArg; i+=2){
for(i=0; rc==SQLITE_OK && i<nArg-1; i+=2){
const char *zArg = azArg[i+1];
if( 0==sqlite3_stricmp(azArg[i], "case_sensitive") ){
if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1] ){
@ -1316,6 +1317,7 @@ static int fts5TriCreate(
rc = SQLITE_ERROR;
}
}
if( i<nArg && rc==SQLITE_OK ) rc = SQLITE_ERROR;
if( pNew->iFoldParam!=0 && pNew->bFold==0 ){
rc = SQLITE_ERROR;

View File

@ -377,4 +377,28 @@ do_catchsql_test 12.3.3 {
SELECT fts5_collist(t1, 1) FROM t1('one AND two');
} {0 1}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 13.1 {
CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=ascii);
INSERT INTO t1 VALUES('a b c'), ('d e f');
PRAGMA integrity_check;
} {ok}
do_catchsql_test 13.2 {
SELECT highlight(t1, 0, '[', ']') FROM t1
} {0 {{a b c} {d e f}}}
do_execsql_test 13.3 {
PRAGMA writable_schema = 1;
UPDATE sqlite_schema SET sql = 'CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=blah)'
WHERE name = 't1';
}
db close
sqlite3 db test.db
do_catchsql_test 13.4 {
SELECT highlight(t1, 0, '[', ']') FROM t1
} {1 {no such tokenizer: blah}}
finish_test

View File

@ -57,7 +57,6 @@ foreach_detail_mode $testprefix {
} ;# foreach_detail_mode...
do_execsql_test 4.0 {
CREATE VIRTUAL TABLE x2 USING fts5(a);
INSERT INTO x2(x2, rank) VALUES('crisismerge', 2);
@ -80,5 +79,18 @@ do_faultsim_test 4 -faults oom-* -prep {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
}
set TMPDBERROR {1 {unable to open a temporary database file for storing temporary tables}}
do_faultsim_test 5 -faults oom-t* -prep {
faultsim_restore_and_reopen
execsql { PRAGMA temp_store = memory }
} -body {
execsql { PRAGMA integrity_check }
} -test {
if {[string match {*error code=7*} $testresult]==0} {
faultsim_test_result {0 ok} {1 SQLITE_NOMEM} $::TMPDBERROR
}
}
finish_test

View File

@ -380,5 +380,32 @@ do_execsql_test 12.3 {
} {ok}
#-------------------------------------------------------------------
reset_db
do_execsql_test 13.1 {
CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=ascii);
INSERT INTO t1 VALUES('a b c'), ('d e f');
PRAGMA integrity_check;
} {ok}
db close
sqlite3 db test.db
do_catchsql_test 13.2 {
PRAGMA integrity_check;
} {0 ok}
do_execsql_test 13.3 {
PRAGMA writable_schema = 1;
UPDATE sqlite_schema SET sql = 'CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=blah)'
WHERE name = 't1';
}
db close
sqlite3 db test.db
breakpoint
do_catchsql_test 13.4 {
PRAGMA integrity_check;
} {1 {no such tokenizer: blah}}
finish_test

View File

@ -42,6 +42,22 @@ do_execsql_test 1.2 {
PRAGMA integrity_check;
} {ok}
do_execsql_test 2.0 {
CREATE VIRTUAL TABLE xyz USING fts5 (
name,
content=''
);
INSERT INTO xyz(xyz, rank) VALUES('secure-delete', 1);
INSERT INTO xyz (rowid, name) VALUES(1, 'A');
INSERT INTO xyz (rowid, name) VALUES(2, 'A');
INSERT INTO xyz(xyz, rowid, name) VALUES('delete', 2, 'A');
}
do_execsql_test 2.1 {
pragma quick_check;
} {ok}

View File

@ -85,5 +85,25 @@ do_execsql_test 1.7 {
SELECT highlight(t1, 0, '>', '<') FROM t1('BB mess');
} {AAdont>BBmess<}
# 2024-08-06 https://sqlite.org/forum/forumpost/171bcc2bcd
# Error handling of tokenize= arguments.
#
foreach {n tkz} {
1 {ascii none}
2 {unicode61 none}
3 {porter none}
4 {trigram none}
5 {ascii none 0}
6 {unicode61 none 0}
7 {porter none 0}
8 {trigram none 0}
} {
db eval {DROP TABLE IF EXISTS t2;}
do_catchsql_test 2.$n "
DROP TABLE IF EXISTS t2;
CREATE VIRTUAL TABLE t2 USING fts5(a,b,c,tokenize='$tkz');
" {1 {error in tokenizer constructor}}
}
finish_test

View File

@ -69,6 +69,9 @@ do_execsql_test 2.0 {
INSERT INTO t1 VALUES('abcdefghijklm');
INSERT INTO t1 VALUES('กรุงเทพมหานคร');
}
do_catchsql_test 2.0.1 {
CREATE VIRTUAL TABLE t2 USING fts5(z, tokenize='trigram case_sensitive');
} {1 {error in tokenizer constructor}}
foreach {tn s res} {
1 abc "(abc)defghijklm"
@ -206,7 +209,7 @@ do_execsql_test 7.0 {
(20, "жираф.png"),
(30, "cat.png"),
(40, "кот.png"),
(50, "misic-🎵-.mp3");
(50, "misic-🎵-.mp3");
}
do_execsql_test 7.1 {
SELECT rowid FROM f WHERE +filename GLOB '*ир*';

View File

@ -21,6 +21,9 @@ do_execsql_test 1.0 "
INSERT INTO t1 VALUES('abc\u0303defghijklm');
INSERT INTO t1 VALUES('a\u0303b\u0303c\u0303defghijklm');
"
do_catchsql_test 1.0.1 {
CREATE VIRTUAL TABLE t2 USING fts5(z, tokenize='trigram remove_diacritics');
} {1 {error in tokenizer constructor}}
do_execsql_test 1.1 {
SELECT highlight(t1, 0, '(', ')') FROM t1('abc');

View File

@ -471,7 +471,7 @@ static void icuLoadCollation(
UCollator *pUCollator; /* ICU library collation object */
int rc; /* Return code from sqlite3_create_collation_x() */
assert(nArg==2);
assert(nArg==2 || nArg==3);
(void)nArg; /* Unused parameter */
zLocale = (const char *)sqlite3_value_text(apArg[0]);
zName = (const char *)sqlite3_value_text(apArg[1]);
@ -486,7 +486,39 @@ static void icuLoadCollation(
return;
}
assert(p);
if(nArg==3){
const char *zOption = (const char*)sqlite3_value_text(apArg[2]);
static const struct {
const char *zName;
UColAttributeValue val;
} aStrength[] = {
{ "PRIMARY", UCOL_PRIMARY },
{ "SECONDARY", UCOL_SECONDARY },
{ "TERTIARY", UCOL_TERTIARY },
{ "DEFAULT", UCOL_DEFAULT_STRENGTH },
{ "QUARTERNARY", UCOL_QUATERNARY },
{ "IDENTICAL", UCOL_IDENTICAL },
};
unsigned int i;
for(i=0; i<sizeof(aStrength)/sizeof(aStrength[0]); i++){
if( sqlite3_stricmp(zOption,aStrength[i].zName)==0 ){
ucol_setStrength(pUCollator, aStrength[i].val);
break;
}
}
if( i>=sizeof(aStrength)/sizeof(aStrength[0]) ){
sqlite3_str *pStr = sqlite3_str_new(sqlite3_context_db_handle(p));
sqlite3_str_appendf(pStr,
"unknown collation strength \"%s\" - should be one of:",
zOption);
for(i=0; i<sizeof(aStrength)/sizeof(aStrength[0]); i++){
sqlite3_str_appendf(pStr, " %s", aStrength[i].zName);
}
sqlite3_result_error(p, sqlite3_str_value(pStr), -1);
sqlite3_free(sqlite3_str_finish(pStr));
return;
}
}
rc = sqlite3_create_collation_v2(db, zName, SQLITE_UTF16, (void *)pUCollator,
icuCollationColl, icuCollationDel
);
@ -509,6 +541,7 @@ int sqlite3IcuInit(sqlite3 *db){
void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
} scalars[] = {
{"icu_load_collation",2,SQLITE_UTF8|SQLITE_DIRECTONLY,1, icuLoadCollation},
{"icu_load_collation",3,SQLITE_UTF8|SQLITE_DIRECTONLY,1, icuLoadCollation},
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU)
{"regexp", 2, SQLITE_ANY|SQLITEICU_EXTRAFLAGS, 0, icuRegexpFunc},
{"lower", 1, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS, 0, icuCaseFunc16},

332
ext/intck/intck1.test Normal file
View File

@ -0,0 +1,332 @@
# 2008 Feb 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.
#
#***********************************************************************
#
# The focus of this file is testing the incremental integrity check
# (intck) extension.
#
source [file join [file dirname [info script]] intck_common.tcl]
set testprefix intck1
return_if_no_intck
foreach {tn sql} {
1 "CREATE TABLE t1(a PRIMARY KEY, b)"
2 "CREATE TABLE t2(a PRIMARY KEY, b) WITHOUT ROWID "
3 "CREATE TABLE t3(a PRIMARY KEY, b) WITHOUT rowID;"
4 "CREATE TABLE t4(a PRIMARY KEY, ROWID)"
5 {CREATE TABLE t5(a PRIMARY KEY, ROWID) WITHOUT ROWID
}
} {
do_test 1.1.$tn {
db eval $sql
set {} {}
} {}
}
set space " \n\v\t\r\f"
do_execsql_test 1.2 {
SELECT name, (rtrim(sql, $space) LIKE '%rowid')
FROM sqlite_schema WHERE type='table'
ORDER BY 1
} {
t1 0
t2 1
t3 1
t4 0
t5 1
}
do_execsql_test 1.3 {
CREATE TABLE x1(a COLLATE nocase, b INTEGER, c BLOB);
INSERT INTO x1 VALUES('lEtTeRs', 1234, 1234);
}
do_execsql_test 1.3.1 {
WITH wrapper(c1, c2, c3) AS (
SELECT a, b, c FROM x1
)
SELECT * FROM wrapper WHERE c1='letters';
} {lEtTeRs 1234 1234}
do_execsql_test 1.3.2 {
WITH wrapper(c1, c2, c3) AS (
SELECT a, b, c FROM x1
)
SELECT * FROM wrapper WHERE c2='1234';
} {lEtTeRs 1234 1234}
do_execsql_test 1.3.2 {
WITH wrapper(c1, c2, c3) AS (
SELECT a, b, c FROM x1
)
SELECT * FROM wrapper WHERE c3='1234';
} {}
do_execsql_test 1.4 {
CREATE TABLE z1(a, b);
CREATE INDEX z1ab ON z1(a+b COLLATE nocase);
}
do_execsql_test 1.4.1 {
SELECT * FROM z1 INDEXED BY z1ab
}
do_catchsql_test 1.5.1 {
CREATE INDEX z1b ON z1(b ASC NULLS LAST);
} {1 {unsupported use of NULLS LAST}}
do_catchsql_test 1.5.2 {
CREATE INDEX z1b ON z1(b DESC NULLS LAST);
} {1 {unsupported use of NULLS LAST}}
do_catchsql_test 1.5.3 {
CREATE INDEX z1b ON z1(b ASC NULLS FIRST);
} {1 {unsupported use of NULLS FIRST}}
do_catchsql_test 1.5.4 {
CREATE INDEX z1b ON z1(b DESC NULLS FIRST);
} {1 {unsupported use of NULLS FIRST}}
reset_db
do_execsql_test 1.6.1 {
CREATE TABLE t1(i INTEGER PRIMARY KEY, b, c);
CREATE INDEX i1 ON t1(b);
ANALYZE;
INSERT INTO sqlite_stat1 VALUES('t1', 'i1', '10000 10000');
ANALYZE sqlite_schema;
} {}
do_eqp_test 1.6.2 {
SELECT 1 FROM t1 INDEXED BY i1 WHERE (b, i) IS (?, ?);
} {SEARCH}
#-------------------------------------------------------------------------
reset_db
do_test 2.0 {
set ic [sqlite3_intck db main]
$ic close
} {}
do_execsql_test 2.1 {
CREATE TABLE t1(a, b);
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(3, 4);
CREATE INDEX i1 ON t1(a COLLATE nocase);
CREATE INDEX i2 ON t1(b, a);
CREATE INDEX i3 ON t1(b + a COLLATE nocase) WHERE a!=1;
}
do_intck_test 2.2 {
}
# Delete a row from each of the i1 and i2 indexes using the imposter
# table interface.
#
do_test 2.3 {
db eval {SELECT name, rootpage FROM sqlite_schema} {
set R($name) $rootpage
}
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(i1)
db eval { CREATE TABLE imp1(a PRIMARY KEY, rowid) WITHOUT ROWID; }
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(i2)
db eval { CREATE TABLE imp2(b, a, rowid, PRIMARY KEY(b, a)) WITHOUT ROWID; }
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0
db eval {
DELETE FROM imp1 WHERE rowid=1;
DELETE FROM imp2 WHERE rowid=2;
}
db close
sqlite3 db test.db
} {}
do_intck_test 2.4 {
{entry (1,1) missing from index i1}
{entry (4,3,2) missing from index i2}
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
CREATE TABLE x1(a, b, c, PRIMARY KEY(c, b)) WITHOUT ROWID;
CREATE INDEX x1a ON x1(a COLLATE nocase);
INSERT INTO x1 VALUES(1, 2, 'three');
INSERT INTO x1 VALUES(4, 5, 'six');
INSERT INTO x1 VALUES(7, 8, 'nine');
}
do_intck_test 3.1 { }
do_test 3.2 {
db eval {SELECT name, rootpage FROM sqlite_schema} {
set R($name) $rootpage
}
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(x1a)
db eval { CREATE TABLE imp1(c, b, a, PRIMARY KEY(c, b)) WITHOUT ROWID }
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0
db eval {
DELETE FROM imp1 WHERE a=5;
}
execsql_pp {
}
db close
sqlite3 db test.db
} {}
do_intck_test 3.3 {
{entry (4,'six',5) missing from index x1a}
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 4.0 {
CREATE TABLE www(x, y, z);
CREATE INDEX w1 ON www( (x+1), z );
INSERT INTO www VALUES(1, 1, 1), (2, 2, 2);
}
do_intck_test 4.1 { }
#-------------------------------------------------------------------------
reset_db
do_execsql_test 5.0 {
CREATE TABLE t1(a, b);
CREATE INDEX i1 ON t1(a COLLATE NOCASE);
INSERT INTO t1 VALUES(1, 1);
INSERT INTO t1 VALUES(2, 2);
}
do_test 5.1 {
set ic [sqlite3_intck db nosuchdb]
$ic step
} {SQLITE_ERROR}
do_test 5.2 {
$ic close
set ic [sqlite3_intck db {}]
while {[$ic step]=="SQLITE_OK"} {}
set res [$ic error]
$ic close
set res
} {SQLITE_OK {}}
do_test 5.3 { test_do_intck db "main" } {}
do_test 5.4 {
set ret {}
set ic [sqlite3_intck db main]
db eval [$ic test_sql t1] {
if {$error_message!=""} { lappend ret $error_message }
}
$ic close
set ret
} {}
do_test 5.5 {
set ret {}
set ic [sqlite3_intck db main]
db eval [$ic test_sql {}] {
if {$error_message!=""} { lappend ret $error_message }
}
$ic close
set ret
} {}
db cache flush
do_test 5.6 {
set ret {}
set ic [sqlite3_intck db main]
$ic step
db eval [$ic test_sql {}] {
if {$error_message!=""} { lappend ret $error_message }
}
$ic close
set ret
} {}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 6.0 {
CREATE TABLE t1(x, y, PRIMARY KEY(x)) WITHOUT ROWID;
CREATE INDEX i1 ON t1(y, x);
INSERT INTO t1 VALUES(X'0000', X'1111');
}
do_intck_test 6.1 {}
do_execsql_test 6.2.1 {
PRAGMA writable_schema = 1;
UPDATE sqlite_schema SET sql = 'CREATE INDEX i1' WHERE name='i1';
} {}
do_intck_test 6.2.2 {}
do_execsql_test 6.3.1 {
UPDATE sqlite_schema SET sql = 'CREATE INDEX i1(y' WHERE name='i1';
} {}
do_intck_test 6.3.2 {}
do_execsql_test 6.4.1 {
UPDATE sqlite_schema
SET sql = 'CREATE INDEX i1(y) hello world'
WHERE name='i1';
} {}
do_intck_test 6.4.2 {}
do_execsql_test 6.5.1 {
UPDATE sqlite_schema
SET sql = 'CREATE INDEX i1(y, x) WHERE 1 '
WHERE name='i1';
} {}
do_intck_test 6.5.2 {}
do_execsql_test 6.6.1 {
UPDATE sqlite_schema
SET sql = 'CREATE INDEX i1( , ) WHERE 1 '
WHERE name='i1';
} {}
do_test 6.7.2 {
set ic [sqlite3_intck db main]
$ic step
} {SQLITE_ERROR}
do_test 6.5.3 {
$ic error
} {SQLITE_ERROR {near "AS": syntax error}}
$ic close
do_execsql_test 6.6.1 {
UPDATE sqlite_schema
SET sql = 'CREATE INDEX i1([y'
WHERE name='i1';
} {}
do_intck_test 6.6.2 {}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 7.0 {
CREATE TABLE x1("1", "22", "3333", four);
CREATE INDEX i1 ON x1( "1" , "22", NULL);
INSERT INTO x1 VALUES(1, 22, 3333, NULL);
INSERT INTO x1 VALUES(1, 22, 3333, NULL);
}
do_execsql_test 7.1 " CREATE INDEX i2 ON x1( \"1\"\r\n\t ) "
do_execsql_test 7.2 { CREATE INDEX i3 ON x1( "22" || 'abc''def' || `1` ) }
do_execsql_test 7.3 { CREATE INDEX i4 ON x1( [22] + [1] ) }
do_execsql_test 7.4 { CREATE INDEX i5 ON x1( four||'hello' ) }
do_intck_test 7.5 {}
finish_test

177
ext/intck/intck2.test Normal file
View File

@ -0,0 +1,177 @@
# 2024 Feb 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.
#
#***********************************************************************
#
# The focus of this file is testing the incremental integrity check
# (intck) extension.
#
source [file join [file dirname [info script]] intck_common.tcl]
set testprefix intck2
return_if_no_intck
do_execsql_test 1.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
INSERT INTO t1 VALUES(3, 'three');
CREATE INDEX i1 ON t1(b);
}
proc imposter_edit {obj create sql} {
sqlite3 xdb test.db
set pgno [xdb one {SELECT rootpage FROM sqlite_schema WHERE name=$obj}]
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER xdb main 1 $pgno
xdb eval $create
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER xdb main 0 0
xdb eval $sql
xdb close
}
imposter_edit i1 {
CREATE TABLE imp(b, a, PRIMARY KEY(b)) WITHOUT ROWID;
} {
DELETE FROM imp WHERE b='two';
INSERT INTO imp(b, a) VALUES('four', 4);
}
do_intck_test 1.1 {
{surplus entry ('four',4) in index i1}
{entry ('two',2) missing from index i1}
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
CREATE TABLE x1(a, b, "c d");
CREATE INDEX x1a ON x1(a COLLATE nocase DESC , b ASC);
CREATE INDEX x1b ON x1( a || b || ' "''" ' COLLATE binary ASC );
CREATE INDEX x1c ON x1( format('%s', a)ASC, format('%d', "c d" ) );
INSERT INTO x1 VALUES('one', 2, 3);
INSERT INTO x1 VALUES('One', 4, 5);
INSERT INTO x1 VALUES('ONE', 6, 7);
INSERT INTO x1 VALUES(NULL, NULL, NULL);
}
do_intck_test 2.1 {}
imposter_edit x1 {
CREATE TABLE imp(a, b, c);
} {
DELETE FROM imp WHERE c=7;
}
do_intck_test 2.2 {
{surplus entry ('ONE',6,3) in index x1a}
{surplus entry ('ONE6 "''" ',3) in index x1b}
{surplus entry ('ONE','7',3) in index x1c}
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
CREATE TABLE x1(a, b, c);
CREATE INDEX x1all ON x1(a DESC, b ASC, c DESC);
INSERT INTO x1 VALUES(2, 1, 2);
INSERT INTO x1 VALUES(2, 1, 1);
INSERT INTO x1 VALUES(2, 2, 2);
INSERT INTO x1 VALUES(2, 2, 1);
INSERT INTO x1 VALUES(1, 1, 2);
INSERT INTO x1 VALUES(1, 1, 1);
INSERT INTO x1 VALUES(1, 2, 2);
INSERT INTO x1 VALUES(1, 2, 1);
}
do_intck_test 3.1 {
}
imposter_edit x1 {
CREATE TABLE imp(a, b, c);
} {
DELETE FROM imp WHERE 1;
}
db close
sqlite3 db test.db
do_intck_test 3.2 {
{surplus entry (2,1,2,1) in index x1all}
{surplus entry (2,1,1,2) in index x1all}
{surplus entry (2,2,2,3) in index x1all}
{surplus entry (2,2,1,4) in index x1all}
{surplus entry (1,1,2,5) in index x1all}
{surplus entry (1,1,1,6) in index x1all}
{surplus entry (1,2,2,7) in index x1all}
{surplus entry (1,2,1,8) in index x1all}
}
do_execsql_test 3.3 {
DELETE FROM x1;
INSERT INTO x1 VALUES(NULL, NULL, NULL);
INSERT INTO x1 VALUES(NULL, NULL, NULL);
INSERT INTO x1 VALUES(NULL, NULL, NULL);
INSERT INTO x1 VALUES(NULL, NULL, NULL);
}
do_intck_test 3.4 {
}
imposter_edit x1 {
CREATE TABLE imp(a, b, c);
} {
DELETE FROM imp WHERE 1;
INSERT INTO imp(rowid) VALUES(-123);
INSERT INTO imp(rowid) VALUES(456);
}
db close
sqlite3 db test.db
do_intck_test 3.5 {
{entry (NULL,NULL,NULL,-123) missing from index x1all}
{entry (NULL,NULL,NULL,456) missing from index x1all}
{surplus entry (NULL,NULL,NULL,1) in index x1all}
{surplus entry (NULL,NULL,NULL,2) in index x1all}
{surplus entry (NULL,NULL,NULL,3) in index x1all}
{surplus entry (NULL,NULL,NULL,4) in index x1all}
}
reset_db
do_execsql_test 3.6 {
CREATE TABLE w1(a PRIMARY KEY, b, c);
INSERT INTO w1 VALUES(1.0, NULL, NULL);
INSERT INTO w1 VALUES(33.0, NULL, NULL);
INSERT INTO w1 VALUES(100.0, NULL, NULL);
CREATE INDEX w1bc ON w1(b, c);
}
do_intck_test 3.7 {
}
imposter_edit w1 {
CREATE TABLE imp(a, b, c);
} {
DELETE FROM imp WHERE a=33;
INSERT INTO imp(a) VALUES(1234.5);
INSERT INTO imp(a) VALUES(-1234.5);
}
do_intck_test 3.8 {
{surplus entry (33.0,2) in index sqlite_autoindex_w1_1}
{entry (1234.5,4) missing from index sqlite_autoindex_w1_1}
{entry (NULL,NULL,4) missing from index w1bc}
{entry (-1234.5,5) missing from index sqlite_autoindex_w1_1}
{entry (NULL,NULL,5) missing from index w1bc}
{surplus entry (NULL,NULL,2) in index w1bc}
}
finish_test

View File

@ -0,0 +1,66 @@
# 2024 Feb 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.
#
#***********************************************************************
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source $testdir/tester.tcl
ifcapable !vtab||!pragma {
proc return_if_no_intck {} {
finish_test
return -code return
}
return
} else {
proc return_if_no_intck {} {}
}
proc do_intck {db {bSuspend 0}} {
set ic [sqlite3_intck $db main]
set ret [list]
while {"SQLITE_OK"==[$ic step]} {
set msg [$ic message]
if {$msg!=""} {
lappend ret $msg
}
if {$bSuspend} {
$ic unlock
#puts "SQL: [$ic test_sql {}]"
#execsql_pp "EXPLAIN query plan [$ic test_sql {}]"
#explain_i [$ic test_sql {}]
}
}
set err [$ic error]
if {[lindex $err 0]!="SQLITE_OK"} {
error $err
}
$ic close
return $ret
}
proc intck_sql {db tbl} {
set ic [sqlite3_intck $db main]
set sql [$ic test_sql $tbl]
$ic close
return $sql
}
proc do_intck_test {tn expect} {
uplevel [list do_test $tn.a [list do_intck db] [list {*}$expect]]
uplevel [list do_test $tn.b [list do_intck db 1] [list {*}$expect]]
}

49
ext/intck/intckbusy.test Normal file
View File

@ -0,0 +1,49 @@
# 2024 February 24
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
source [file join [file dirname [info script]] intck_common.tcl]
set testprefix intckbusy
return_if_no_intck
do_execsql_test 1.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t1 VALUES(2, 'two', 'three');
INSERT INTO t1 VALUES(3, NULL, NULL);
CREATE INDEX i1 ON t1(b, c);
}
sqlite3 db2 test.db
do_execsql_test -db db2 1.1 {
BEGIN EXCLUSIVE;
INSERT INTO t1 VALUES(4, 5, 6);
}
do_test 1.2 {
set ic [sqlite3_intck db main]
$ic step
} {SQLITE_BUSY}
do_test 1.3 {
$ic unlock
} {SQLITE_BUSY}
do_test 1.4 {
$ic error
} {SQLITE_BUSY {database is locked}}
do_test 1.4 {
$ic close
} {}
finish_test

236
ext/intck/intckcorrupt.test Normal file
View File

@ -0,0 +1,236 @@
# 2024 Feb 21
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
# The focus of this file is testing the intck extensions response
# to corruption at the b-tree level.
#
source [file join [file dirname [info script]] intck_common.tcl]
set testprefix intckcorrupt
return_if_no_intck
#-------------------------------------------------------------------------
reset_db
do_test 1.0 {
sqlite3 db {}
db deserialize [decode_hexdb {
| size 356352 pagesize 4096 filename crash-acaae0347204ae.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 00 d0 00 00 00 .....@ ........
| 32: 40 00 ea 00 00 00 00 00 00 40 00 00 00 40 00 00 @........@...@..
| 96: 00 00 00 00 0d 00 00 00 04 0e 9c 00 0f ad 0f 4f ...............O
| 112: 0e fc 0e 9c 00 00 00 00 00 00 00 00 00 00 00 00 ................
| 3728: 00 00 00 00 00 00 00 00 00 00 00 00 5e 04 07 17 ............^...
| 3744: 1f 1f 01 81 0b 74 61 62 6c 65 74 31 5f 70 61 72 .....tablet1_par
| 3760: 65 6e 74 74 31 5f 70 61 72 65 6e 74 04 43 52 45 entt1_parent.CRE
| 3776: 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 70 61 ATE TABLE .t1_pa
| 3792: 72 65 6e 74 22 28 6e 6f 64 65 6e 6f 20 49 4e 54 rent.(nodeno INT
| 3808: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY
| 3824: 2c 70 61 72 65 6e 74 6e 6f 64 65 29 51 03 06 17 ,parentnode)Q...
| 3840: 1b 1b 01 7b 74 61 62 6c 65 74 31 5f 6e 6f 64 65 ....tablet1_node
| 3856: 74 31 5f 6e 6f 64 65 03 43 52 45 41 54 45 20 54 t1_node.CREATE T
| 3872: 41 42 4c 45 20 22 74 31 5f 6e 6f 64 65 22 28 6e ABLE .t1_node.(n
| 3888: 6f 64 65 6e 6f 20 49 4e 54 45 47 45 52 20 50 52 odeno INTEGER PR
| 3904: 49 4d 41 52 59 20 4b 45 59 2c 64 61 74 61 29 5c IMARY KEY,data).
| 3920: 02 07 17 1d 1d 01 81 0b 74 61 62 6c 65 74 31 5f ........tablet1_
| 3936: 72 6f 77 69 64 74 31 5f 72 6f 77 69 64 02 43 52 rowidt1_rowid.CR
| 3952: 45 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 72 EATE TABLE .t1_r
| 3968: 6f 77 69 64 22 28 72 6f 77 69 64 20 49 4e 54 45 owid.(rowid INTE
| 3984: 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c GER PRIMARY KEY,
| 4000: 6e 6f 64 65 6e 6f 2c 61 30 2c 61 31 29 51 01 07 nodeno,a0,a1)Q..
| 4016: 17 11 11 08 81 0f 74 61 62 6c 65 74 31 74 31 43 ......tablet1t1C
| 4032: 52 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 REATE VIRTUAL TA
| 4048: 42 4c 45 20 74 31 20 55 53 49 4e 47 20 72 74 72 BLE t1 USING rtr
| 4064: 65 65 28 69 64 2c 78 30 20 50 52 49 4d 41 52 59 ee(id,x0 PRIMARY
| 4080: 20 4b 45 59 2c 70 61 72 65 6e 74 6e 6f 64 65 29 KEY,parentnode)
| page 2 offset 4096
| 0: 51 03 06 17 1b 1b 01 7b 74 61 62 6c 65 74 31 5f Q.......tablet1_
| 16: 6e 6f 64 65 74 31 5f 6e 6f 64 65 03 43 52 45 41 nodet1_node.CREA
| 32: 54 45 20 54 41 42 4c 45 20 22 74 31 5f 6e 6f 64 TE TABLE .t1_nod
| 48: 65 22 28 6e 6f 64 65 6e 6f 20 49 4e 54 45 47 45 e.(nodeno INTEGE
| 64: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 64 61 R PRIMARY KEY,da
| 80: 74 61 29 5c 02 07 17 1d 1d 01 81 0b 74 61 62 6c ta).........tabl
| 96: 65 74 31 5f 72 6f 77 69 64 74 31 5f 72 6f 77 69 et1_rowidt1_rowi
| 112: 64 02 43 52 45 41 54 45 20 54 41 42 4c 45 00 00 d.CREATE TABLE..
| 128: 01 0a 02 00 00 00 01 0e 0d 00 00 00 00 24 0e 0d .............$..
| 144: 0c 1a 06 85 50 46 60 27 70 08 00 00 00 00 00 00 ....PF`'p.......
| 3824: 00 00 00 00 00 00 00 0d 0e 05 00 09 1d 00 74 6f ..............to
| 3840: 79 20 68 61 6c 66 10 0d 05 00 09 23 00 62 6f 74 y half.....#.bot
| 3856: 74 6f 6d 20 68 61 6c 66 0f 0c 05 00 09 21 00 72 tom half.....!.r
| 3872: 69 67 68 74 20 68 61 6c 66 0e 0b 05 00 09 1f 00 ight half.......
| 3888: 6c 65 66 74 20 43 15 f6 e6 f6 46 50 34 35 24 54 left C....FP45$T
| 3904: 15 44 52 05 44 14 24 c4 52 02 27 43 15 f6 e6 f6 .DR.D.$.R.'C....
| 3920: 46 52 22 8e 6f 64 65 6e 6f 20 49 4e 54 45 47 45 FR..odeno INTEGE
| 3936: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 64 61 R PRIMARY KEY,da
| 3952: 74 61 29 5c 02 07 17 1d 1d 01 81 0b 74 61 62 6c ta).........tabl
| 3968: 65 74 31 5f 72 6f 74 74 6f 6d 20 65 64 67 65 0f et1_rottom edge.
| 3984: 07 05 00 09 21 00 72 69 67 68 74 20 65 64 67 65 ....!.right edge
| 4000: 0e 06 05 00 09 1f 00 6c 65 66 74 20 65 64 67 65 .......left edge
| 4016: 0b 05 05 00 09 19 00 63 65 6e 74 65 72 17 04 05 .......center...
| 4032: 00 09 31 00 75 70 70 65 72 2d 72 69 67 68 74 20 ..1.upper-right
| 4048: 63 6f 72 6e 65 72 17 03 05 00 09 31 00 6c 6f 77 corner.....1.low
| 4064: 65 72 2d 72 69 67 68 74 20 63 6f 72 6e 65 72 16 er-right corner.
| 4080: 02 05 00 09 2f 00 75 70 70 65 72 2d 6c 65 66 74 ..../.upper-left
| page 3 offset 8192
| 0: 20 63 6f 72 6e 65 72 16 01 05 00 09 2f 01 8c 6f corner...../..o
| 16: 77 65 72 2d 6c 53 51 4c 69 74 65 20 66 6f 72 6d wer-lSQLite form
| 32: 61 74 20 33 00 10 00 01 01 00 40 20 20 00 00 00 at 3......@ ...
| 48: 00 00 00 00 2f 00 00 0d eb 13 00 00 00 03 00 00 ..../...........
| 64: 00 04 00 00 00 00 00 00 00 06 00 00 00 01 00 00 ................
| 80: 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 ................
| page 6 offset 20480
| 128: 00 00 00 00 00 00 00 00 97 3d 04 ae 7c 01 00 00 .........=..|...
| 624: 00 00 00 00 00 00 21 97 3d 04 ae 7c 01 00 00 00 ......!.=..|....
| 1120: 00 00 00 00 00 20 97 3d 04 ae 7c 01 00 00 00 00 ..... .=..|.....
| 1616: 00 00 00 00 1f 97 3d 04 ae 7c 01 00 00 00 00 00 ......=..|......
| 2112: 00 00 00 1e 97 3d 04 ae 7c 01 00 00 00 00 00 00 .....=..|.......
| 2608: 00 00 1d 97 d3 d0 4a e7 c0 00 00 00 00 00 00 00 ......J.........
| 3088: 00 00 00 00 00 00 00 00 00 00 00 00 01 f3 00 00 ................
| 3600: 23 97 3d 04 ae 7c 01 00 00 00 00 00 00 00 00 00 #.=..|..........
| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 26 ...............&
| page 8 offset 28672
| 0: 0d 00 00 00 01 04 30 00 04 30 00 00 00 00 00 00 ......0..0......
| 1072: 97 4d 1e 14 00 ae 7c 00 00 00 00 00 00 00 00 00 .M....|.........
| 1088: 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 ................
| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 ................
| page 10 offset 36864
| 0: 0d 00 00 00 01 04 30 00 04 30 00 00 00 00 00 00 ......0..0......
| 1072: 9a ee c1 80 fd 78 1f ce 1b ae eb b4 00 00 00 00 .....x..........
| 1088: 13 20 ff 20 00 70 00 00 00 60 50 00 00 00 11 e0 . . .p...`P.....
| 1104: 00 00 00 70 00 00 00 60 50 05 35 14 c6 97 46 52 ...p...`P.5...FR
| 1120: 06 66 f7 26 d6 17 42 03 30 01 00 00 10 10 04 02 .f.&..B.0.......
| 1136: 02 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 .........@......
| 1152: 00 00 00 00 00 40 00 00 00 40 00 00 00 00 00 00 .....@...@......
| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05 ................
| page 12 offset 45056
| 0: 0d 00 00 00 01 04 30 00 04 30 e1 b4 30 97 4d 46 ......0..0..0.MF
| 16: 14 00 ae 7c 00 00 00 00 00 00 00 03 00 00 43 00 ...|..........C.
| page 47 offset 188416
| 2512: 00 00 00 00 00 00 00 00 be 00 00 00 00 00 00 00 ................
| page 87 offset 352256
| 2512: 00 00 00 00 00 00 00 00 aa 00 00 00 00 00 00 00 ................
| end crash-acaae0347204ae.db
}]} {}
do_intck_test 1.1 {
{corruption found while reading database schema}
}
#-------------------------------------------------------------------------
reset_db
do_test 2.0 {
sqlite3 db {}
db deserialize [decode_hexdb {
| size 28672 pagesize 4096 filename crash-3afa1ca9e9c1bd.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 00 00 00 00 07 .....@ ........
| 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 ................
| 96: 00 00 00 00 0d 00 00 00 06 0e 88 00 0f b8 0f 6d ...............m
| 112: 0f 3a 0f 0b 0e d5 0e 88 01 00 00 00 00 00 00 00 .:..............
| 3712: 00 00 00 00 00 00 00 00 4b 06 06 17 25 25 01 5b ........K...%%.[
| 3728: 74 61 62 6c 65 73 71 6c 69 74 65 5f 73 74 61 74 tablesqlite_stat
| 3744: 31 73 71 6c 69 74 65 5f 73 74 61 74 31 07 43 52 1sqlite_stat1.CR
| 3760: 45 41 54 45 20 54 41 42 4c 45 20 73 71 6c 69 74 EATE TABLE sqlit
| 3776: 65 5f 73 74 61 74 31 28 74 62 6c 2c 69 64 78 2c e_stat1(tbl,idx,
| 3792: 73 74 61 74 29 34 05 06 17 13 11 01 53 69 6e 64 stat)4......Sind
| 3808: 65 78 63 31 63 63 31 06 43 52 45 41 54 45 20 55 exc1cc1.CREATE U
| 3824: 4e 49 51 55 45 20 49 4e 44 45 58 20 63 31 63 20 NIQUE INDEX c1c
| 3840: 4f 4e 20 63 31 28 63 2c 20 62 29 2d 04 06 17 13 ON c1(c, b)-....
| 3856: 11 01 45 69 6e 64 65 78 63 31 64 63 31 05 43 52 ..Eindexc1dc1.CR
| 3872: 45 41 54 45 20 49 4e 44 45 58 20 63 31 64 20 4f EATE INDEX c1d O
| 3888: 4e 20 63 31 28 64 2c 20 62 29 31 03 06 17 13 11 N c1(d, b)1.....
| 3904: 01 4d 69 6e 64 65 78 62 31 63 62 31 05 43 52 45 .Mindexb1cb1.CRE
| 3920: 41 54 45 20 55 4e 49 51 55 45 20 49 4e 44 45 58 ATE UNIQUE INDEX
| 3936: 20 62 31 63 20 4f 4e 20 62 31 28 63 29 49 02 06 b1c ON b1(c)I..
| 3952: 17 11 11 0f 7f 74 61 62 6c 65 63 31 63 31 03 43 .....tablec1c1.C
| 3968: 52 45 41 54 45 20 54 41 42 4c 45 20 63 31 28 61 REATE TABLE c1(a
| 3984: 20 49 4e 54 20 50 52 49 4d 41 52 59 20 4b 45 59 INT PRIMARY KEY
| 4000: 2c 20 62 2c 20 63 2c 20 64 29 20 57 49 54 48 4f , b, c, d) WITHO
| 4016: 55 54 20 52 4f 57 49 44 46 01 06 17 11 11 01 79 UT ROWIDF......y
| 4032: 74 61 62 6c 65 62 31 62 31 02 43 52 45 41 54 45 tableb1b1.CREATE
| 4048: 20 54 41 42 4c 45 20 62 31 28 61 20 49 4e 54 20 TABLE b1(a INT
| 4064: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 2c 20 PRIMARY KEY, b,
| 4080: 63 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 c) WITHOUT ROWID
| page 2 offset 4096
| 0: 0a 00 00 00 07 0f ca 00 0f fa 0f f2 0f ea 0f e2 ................
| 16: 0f da 00 00 00 01 00 00 00 00 00 00 00 00 00 00 ................
| 4032: 00 00 00 00 00 00 00 00 00 00 07 04 01 0f 01 06 ................
| 4048: 67 07 07 04 01 0f 01 06 66 06 07 04 01 0f 01 05 g.......f.......
| 4064: 65 05 07 04 01 0f 01 04 64 04 07 04 01 0f 01 03 e.......d.......
| 4080: 63 03 07 04 01 0f 01 02 62 0f 05 04 09 0f 09 61 c.......b......a
| page 3 offset 8192
| 0: 0a 00 00 00 07 0f bd 00 0f f9 0f ef 0f e5 0f db ................
| 16: 0f d1 0f c7 0f bd 00 00 00 00 01 00 00 00 00 00 ................
| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 09 05 01 ................
| 4032: 0f 01 01 07 61 07 07 09 05 01 0f 01 01 06 61 06 ....a.........a.
| 4048: 06 09 05 01 0f 01 01 05 61 05 05 09 05 01 0f 01 ........a.......
| 4064: 01 04 61 04 04 09 05 01 0f 01 01 03 61 03 03 09 ..a.........a...
| 4080: 05 01 0f 01 01 02 61 0f 02 06 05 09 0f 09 09 61 ......a........a
| page 4 offset 12288
| 0: 0a 00 00 00 07 0f d8 00 0f fc 0f f0 0f ea 0f e4 ................
| 16: 0f de 0f d8 0f f6 00 00 00 00 00 00 00 00 00 00 ................
| 4048: 00 00 00 00 00 00 00 00 05 03 01 01 07 07 05 03 ................
| 4064: 01 01 06 06 05 03 01 01 05 05 05 03 01 01 04 04 ................
| 4080: 05 03 01 01 03 03 05 03 01 01 0f 02 03 03 09 09 ................
| page 5 offset 16384
| 0: 0a 00 00 00 07 0f ca 00 0f fa 0f f2 0f ea 0f 00 ................
| 4032: 00 00 00 00 00 00 00 00 00 00 07 04 01 0f 01 07 ................
| 4048: 61 07 07 04 01 0f 01 06 61 06 07 04 01 0f 01 05 a.......a.......
| 4064: 61 05 07 04 01 1f 01 04 61 04 07 04 01 0f 01 03 a.......a.......
| 4080: 61 03 07 04 01 0f 01 02 61 02 05 04 09 0f 09 61 a.......a......a
| page 6 offset 20480
| 0: 0a 00 00 00 07 0f ca 00 0f fa 0f ea 0f e2 00 00 ................
| 4032: 00 00 00 00 00 00 00 00 00 00 07 04 01 0f 01 07 ................
| 4048: 61 07 07 04 01 0f 01 06 61 06 07 04 01 0f 01 05 a.......a.......
| 4064: 61 05 07 04 01 0f 01 04 61 04 07 04 01 0f 01 03 a.......a.......
| 4080: 61 03 07 04 01 0f 01 0f 61 02 05 04 09 0f 09 61 a.......a......a
| page 7 offset 24576
| 0: 0d 00 00 00 05 0f 1c 00 0f f0 0f e0 0f d3 0f c5 ................
| 16: 0f b8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
| 4016: 00 00 00 00 00 00 00 00 0b 05 04 11 11 13 62 31 ..............b1
| 4032: 62 31 37 20 31 0c 04 04 11 13 13 62 31 62 31 63 b17 1......b1b1c
| 4048: 37 20 31 0b 03 04 11 11 13 63 31 63 31 37 20 31 7 1......c1c17 1
| 4064: 0e 02 04 11 13 07 63 31 63 31 64 37 20 31 20 31 ......c1c1d7 1 1
| 4080: 0e 01 04 11 13 17 63 31 63 31 63 37 20 31 00 00 ......c1c1c7 1..
| end crash-3afa1ca9e9c1bd.db
}]} {}
do_intck_test 2.1 {
{corruption found while reading database schema}
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
PRAGMA page_size = 1024;
CREATE TABLE t1(a, b);
CREATE INDEX i1 ON t1(a);
INSERT INTO t1 VALUES(1, 1), (2, 2), (3, 3);
}
do_test 3.1 {
set pgno [db one {SELECT rootpage FROM sqlite_schema WHERE name='t1'}]
db close
hexio_write test.db [expr ($pgno-1)*1024] 0000
} {2}
sqlite3 db test.db
do_intck_test 3.2 {
{corruption found while scanning database object i1}
{corruption found while scanning database object t1}
}
finish_test

42
ext/intck/intckfault.test Normal file
View File

@ -0,0 +1,42 @@
# 2024 February 24
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
source [file join [file dirname [info script]] intck_common.tcl]
set testprefix intckfault
return_if_no_intck
do_execsql_test 1.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t1 VALUES(2, 'two', 'three');
INSERT INTO t1 VALUES(3, NULL, NULL);
CREATE INDEX i1 ON t1(b, c);
}
do_faultsim_test 1 -faults oom-t* -prep {
} -body {
set ::ic [sqlite3_intck db main]
set nStep 0
while {"SQLITE_OK"==[$::ic step]} {
incr nStep
if {$nStep==3} { $::ic unlock }
}
set res [$::ic error]
$::ic close
set res
} -test {
catch { $::ic close }
faultsim_test_result {0 {SQLITE_OK {}}} {0 {SQLITE_NOMEM {}}} {0 {SQLITE_NOMEM {out of memory}}}
}
finish_test

940
ext/intck/sqlite3intck.c Normal file
View File

@ -0,0 +1,940 @@
/*
** 2024-02-08
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
*/
#include "sqlite3intck.h"
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
/*
** nKeyVal:
** The number of values that make up the 'key' for the current pCheck
** statement.
**
** rc:
** Error code returned by most recent sqlite3_intck_step() or
** sqlite3_intck_unlock() call. This is set to SQLITE_DONE when
** the integrity-check operation is finished.
**
** zErr:
** If the object has entered the error state, this is the error message.
** Is freed using sqlite3_free() when the object is deleted.
**
** zTestSql:
** The value returned by the most recent call to sqlite3_intck_testsql().
** Each call to testsql() frees the previous zTestSql value (using
** sqlite3_free()) and replaces it with the new value it will return.
*/
struct sqlite3_intck {
sqlite3 *db;
const char *zDb; /* Copy of zDb parameter to _open() */
char *zObj; /* Current object. Or NULL. */
sqlite3_stmt *pCheck; /* Current check statement */
char *zKey;
int nKeyVal;
char *zMessage;
int bCorruptSchema;
int rc; /* Error code */
char *zErr; /* Error message */
char *zTestSql; /* Returned by sqlite3_intck_test_sql() */
};
/*
** Some error has occurred while using database p->db. Save the error message
** and error code currently held by the database handle in p->rc and p->zErr.
*/
static void intckSaveErrmsg(sqlite3_intck *p){
p->rc = sqlite3_errcode(p->db);
sqlite3_free(p->zErr);
p->zErr = sqlite3_mprintf("%s", sqlite3_errmsg(p->db));
}
/*
** If the handle passed as the first argument is already in the error state,
** then this function is a no-op (returns NULL immediately). Otherwise, if an
** error occurs within this function, it leaves an error in said handle.
**
** Otherwise, this function attempts to prepare SQL statement zSql and
** return the resulting statement handle to the user.
*/
static sqlite3_stmt *intckPrepare(sqlite3_intck *p, const char *zSql){
sqlite3_stmt *pRet = 0;
if( p->rc==SQLITE_OK ){
p->rc = sqlite3_prepare_v2(p->db, zSql, -1, &pRet, 0);
if( p->rc!=SQLITE_OK ){
intckSaveErrmsg(p);
assert( pRet==0 );
}
}
return pRet;
}
/*
** If the handle passed as the first argument is already in the error state,
** then this function is a no-op (returns NULL immediately). Otherwise, if an
** error occurs within this function, it leaves an error in said handle.
**
** Otherwise, this function treats argument zFmt as a printf() style format
** string. It formats it according to the trailing arguments and then
** attempts to prepare the results and return the resulting prepared
** statement.
*/
static sqlite3_stmt *intckPrepareFmt(sqlite3_intck *p, const char *zFmt, ...){
sqlite3_stmt *pRet = 0;
va_list ap;
char *zSql = 0;
va_start(ap, zFmt);
zSql = sqlite3_vmprintf(zFmt, ap);
if( p->rc==SQLITE_OK && zSql==0 ){
p->rc = SQLITE_NOMEM;
}
pRet = intckPrepare(p, zSql);
sqlite3_free(zSql);
va_end(ap);
return pRet;
}
/*
** Finalize SQL statement pStmt. If an error occurs and the handle passed
** as the first argument does not already contain an error, store the
** error in the handle.
*/
static void intckFinalize(sqlite3_intck *p, sqlite3_stmt *pStmt){
int rc = sqlite3_finalize(pStmt);
if( p->rc==SQLITE_OK && rc!=SQLITE_OK ){
intckSaveErrmsg(p);
}
}
/*
** If there is already an error in handle p, return it. Otherwise, call
** sqlite3_step() on the statement handle and return that value.
*/
static int intckStep(sqlite3_intck *p, sqlite3_stmt *pStmt){
if( p->rc ) return p->rc;
return sqlite3_step(pStmt);
}
/*
** Execute SQL statement zSql. There is no way to obtain any results
** returned by the statement. This function uses the sqlite3_intck error
** code convention.
*/
static void intckExec(sqlite3_intck *p, const char *zSql){
sqlite3_stmt *pStmt = 0;
pStmt = intckPrepare(p, zSql);
intckStep(p, pStmt);
intckFinalize(p, pStmt);
}
/*
** A wrapper around sqlite3_mprintf() that uses the sqlite3_intck error
** code convention.
*/
static char *intckMprintf(sqlite3_intck *p, const char *zFmt, ...){
va_list ap;
char *zRet = 0;
va_start(ap, zFmt);
zRet = sqlite3_vmprintf(zFmt, ap);
if( p->rc==SQLITE_OK ){
if( zRet==0 ){
p->rc = SQLITE_NOMEM;
}
}else{
sqlite3_free(zRet);
zRet = 0;
}
return zRet;
}
/*
** This is used by sqlite3_intck_unlock() to save the vector key value
** required to restart the current pCheck query as a nul-terminated string
** in p->zKey.
*/
static void intckSaveKey(sqlite3_intck *p){
int ii;
char *zSql = 0;
sqlite3_stmt *pStmt = 0;
sqlite3_stmt *pXinfo = 0;
const char *zDir = 0;
assert( p->pCheck );
assert( p->zKey==0 );
pXinfo = intckPrepareFmt(p,
"SELECT group_concat(desc, '') FROM %Q.sqlite_schema s, "
"pragma_index_xinfo(%Q, %Q) "
"WHERE s.type='index' AND s.name=%Q",
p->zDb, p->zObj, p->zDb, p->zObj
);
if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXinfo) ){
zDir = (const char*)sqlite3_column_text(pXinfo, 0);
}
if( zDir==0 ){
/* Object is a table, not an index. This is the easy case,as there are
** no DESC columns or NULL values in a primary key. */
const char *zSep = "SELECT '(' || ";
for(ii=0; ii<p->nKeyVal; ii++){
zSql = intckMprintf(p, "%z%squote(?)", zSql, zSep);
zSep = " || ', ' || ";
}
zSql = intckMprintf(p, "%z || ')'", zSql);
}else{
/* Object is an index. */
assert( p->nKeyVal>1 );
for(ii=p->nKeyVal; ii>0; ii--){
int bLastIsDesc = zDir[ii-1]=='1';
int bLastIsNull = sqlite3_column_type(p->pCheck, ii)==SQLITE_NULL;
const char *zLast = sqlite3_column_name(p->pCheck, ii);
char *zLhs = 0;
char *zRhs = 0;
char *zWhere = 0;
if( bLastIsNull ){
if( bLastIsDesc ) continue;
zWhere = intckMprintf(p, "'%s IS NOT NULL'", zLast);
}else{
const char *zOp = bLastIsDesc ? "<" : ">";
zWhere = intckMprintf(p, "'%s %s ' || quote(?%d)", zLast, zOp, ii);
}
if( ii>1 ){
const char *zLhsSep = "";
const char *zRhsSep = "";
int jj;
for(jj=0; jj<ii-1; jj++){
const char *zAlias = (const char*)sqlite3_column_name(p->pCheck,jj+1);
zLhs = intckMprintf(p, "%z%s%s", zLhs, zLhsSep, zAlias);
zRhs = intckMprintf(p, "%z%squote(?%d)", zRhs, zRhsSep, jj+1);
zLhsSep = ",";
zRhsSep = " || ',' || ";
}
zWhere = intckMprintf(p,
"'(%z) IS (' || %z || ') AND ' || %z",
zLhs, zRhs, zWhere);
}
zWhere = intckMprintf(p, "'WHERE ' || %z", zWhere);
zSql = intckMprintf(p, "%z%s(quote( %z ) )",
zSql,
(zSql==0 ? "VALUES" : ",\n "),
zWhere
);
}
zSql = intckMprintf(p,
"WITH wc(q) AS (\n%z\n)"
"SELECT 'VALUES' || group_concat('(' || q || ')', ',\n ') FROM wc"
, zSql
);
}
pStmt = intckPrepare(p, zSql);
if( p->rc==SQLITE_OK ){
for(ii=0; ii<p->nKeyVal; ii++){
sqlite3_bind_value(pStmt, ii+1, sqlite3_column_value(p->pCheck, ii+1));
}
if( SQLITE_ROW==sqlite3_step(pStmt) ){
p->zKey = intckMprintf(p,"%s",(const char*)sqlite3_column_text(pStmt, 0));
}
intckFinalize(p, pStmt);
}
sqlite3_free(zSql);
intckFinalize(p, pXinfo);
}
/*
** Find the next database object (table or index) to check. If successful,
** set sqlite3_intck.zObj to point to a nul-terminated buffer containing
** the object's name before returning.
*/
static void intckFindObject(sqlite3_intck *p){
sqlite3_stmt *pStmt = 0;
char *zPrev = p->zObj;
p->zObj = 0;
assert( p->rc==SQLITE_OK );
assert( p->pCheck==0 );
pStmt = intckPrepareFmt(p,
"WITH tables(table_name) AS ("
" SELECT name"
" FROM %Q.sqlite_schema WHERE (type='table' OR type='index') AND rootpage"
" UNION ALL "
" SELECT 'sqlite_schema'"
")"
"SELECT table_name FROM tables "
"WHERE ?1 IS NULL OR table_name%s?1 "
"ORDER BY 1"
, p->zDb, (p->zKey ? ">=" : ">")
);
if( p->rc==SQLITE_OK ){
sqlite3_bind_text(pStmt, 1, zPrev, -1, SQLITE_TRANSIENT);
if( sqlite3_step(pStmt)==SQLITE_ROW ){
p->zObj = intckMprintf(p,"%s",(const char*)sqlite3_column_text(pStmt, 0));
}
}
intckFinalize(p, pStmt);
/* If this is a new object, ensure the previous key value is cleared. */
if( sqlite3_stricmp(p->zObj, zPrev) ){
sqlite3_free(p->zKey);
p->zKey = 0;
}
sqlite3_free(zPrev);
}
/*
** Return the size in bytes of the first token in nul-terminated buffer z.
** For the purposes of this call, a token is either:
**
** * a quoted SQL string,
* * a contiguous series of ascii alphabet characters, or
* * any other single byte.
*/
static int intckGetToken(const char *z){
char c = z[0];
int iRet = 1;
if( c=='\'' || c=='"' || c=='`' ){
while( 1 ){
if( z[iRet]==c ){
iRet++;
if( z[iRet]!=c ) break;
}
iRet++;
}
}
else if( c=='[' ){
while( z[iRet++]!=']' && z[iRet] );
}
else if( (c>='A' && c<='Z') || (c>='a' && c<='z') ){
while( (z[iRet]>='A' && z[iRet]<='Z') || (z[iRet]>='a' && z[iRet]<='z') ){
iRet++;
}
}
return iRet;
}
/*
** Return true if argument c is an ascii whitespace character.
*/
static int intckIsSpace(char c){
return (c==' ' || c=='\t' || c=='\n' || c=='\r');
}
/*
** Argument z points to the text of a CREATE INDEX statement. This function
** identifies the part of the text that contains either the index WHERE
** clause (if iCol<0) or the iCol'th column of the index.
**
** If (iCol<0), the identified fragment does not include the "WHERE" keyword,
** only the expression that follows it. If (iCol>=0) then the identified
** fragment does not include any trailing sort-order keywords - "ASC" or
** "DESC".
**
** If the CREATE INDEX statement does not contain the requested field or
** clause, NULL is returned and (*pnByte) is set to 0. Otherwise, a pointer to
** the identified fragment is returned and output parameter (*pnByte) set
** to its size in bytes.
*/
static const char *intckParseCreateIndex(const char *z, int iCol, int *pnByte){
int iOff = 0;
int iThisCol = 0;
int iStart = 0;
int nOpen = 0;
const char *zRet = 0;
int nRet = 0;
int iEndOfCol = 0;
/* Skip forward until the first "(" token */
while( z[iOff]!='(' ){
iOff += intckGetToken(&z[iOff]);
if( z[iOff]=='\0' ) return 0;
}
assert( z[iOff]=='(' );
nOpen = 1;
iOff++;
iStart = iOff;
while( z[iOff] ){
const char *zToken = &z[iOff];
int nToken = 0;
/* Check if this is the end of the current column - either a "," or ")"
** when nOpen==1. */
if( nOpen==1 ){
if( z[iOff]==',' || z[iOff]==')' ){
if( iCol==iThisCol ){
int iEnd = iEndOfCol ? iEndOfCol : iOff;
nRet = (iEnd - iStart);
zRet = &z[iStart];
break;
}
iStart = iOff+1;
while( intckIsSpace(z[iStart]) ) iStart++;
iThisCol++;
}
if( z[iOff]==')' ) break;
}
if( z[iOff]=='(' ) nOpen++;
if( z[iOff]==')' ) nOpen--;
nToken = intckGetToken(zToken);
if( (nToken==3 && 0==sqlite3_strnicmp(zToken, "ASC", nToken))
|| (nToken==4 && 0==sqlite3_strnicmp(zToken, "DESC", nToken))
){
iEndOfCol = iOff;
}else if( 0==intckIsSpace(zToken[0]) ){
iEndOfCol = 0;
}
iOff += nToken;
}
/* iStart is now the byte offset of 1 byte passed the final ')' in the
** CREATE INDEX statement. Try to find a WHERE clause to return. */
while( zRet==0 && z[iOff] ){
int n = intckGetToken(&z[iOff]);
if( n==5 && 0==sqlite3_strnicmp(&z[iOff], "where", 5) ){
zRet = &z[iOff+5];
nRet = (int)strlen(zRet);
}
iOff += n;
}
/* Trim any whitespace from the start and end of the returned string. */
if( zRet ){
while( intckIsSpace(zRet[0]) ){
nRet--;
zRet++;
}
while( nRet>0 && intckIsSpace(zRet[nRet-1]) ) nRet--;
}
*pnByte = nRet;
return zRet;
}
/*
** User-defined SQL function wrapper for intckParseCreateIndex():
**
** SELECT parse_create_index(<sql>, <icol>);
*/
static void intckParseCreateIndexFunc(
sqlite3_context *pCtx,
int nVal,
sqlite3_value **apVal
){
const char *zSql = (const char*)sqlite3_value_text(apVal[0]);
int idx = sqlite3_value_int(apVal[1]);
const char *zRes = 0;
int nRes = 0;
assert( nVal==2 );
if( zSql ){
zRes = intckParseCreateIndex(zSql, idx, &nRes);
}
sqlite3_result_text(pCtx, zRes, nRes, SQLITE_TRANSIENT);
}
/*
** Return true if sqlite3_intck.db has automatic indexes enabled, false
** otherwise.
*/
static int intckGetAutoIndex(sqlite3_intck *p){
int bRet = 0;
sqlite3_stmt *pStmt = 0;
pStmt = intckPrepare(p, "PRAGMA automatic_index");
if( SQLITE_ROW==intckStep(p, pStmt) ){
bRet = sqlite3_column_int(pStmt, 0);
}
intckFinalize(p, pStmt);
return bRet;
}
/*
** Return true if zObj is an index, or false otherwise.
*/
static int intckIsIndex(sqlite3_intck *p, const char *zObj){
int bRet = 0;
sqlite3_stmt *pStmt = 0;
pStmt = intckPrepareFmt(p,
"SELECT 1 FROM %Q.sqlite_schema WHERE name=%Q AND type='index'",
p->zDb, zObj
);
if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
bRet = 1;
}
intckFinalize(p, pStmt);
return bRet;
}
/*
** Return a pointer to a nul-terminated buffer containing the SQL statement
** used to check database object zObj (a table or index) for corruption.
** If parameter zPrev is not NULL, then it must be a string containing the
** vector key required to restart the check where it left off last time.
** If pnKeyVal is not NULL, then (*pnKeyVal) is set to the number of
** columns in the vector key value for the specified object.
**
** This function uses the sqlite3_intck error code convention.
*/
static char *intckCheckObjectSql(
sqlite3_intck *p, /* Integrity check object */
const char *zObj, /* Object (table or index) to scan */
const char *zPrev, /* Restart key vector, if any */
int *pnKeyVal /* OUT: Number of key-values for this scan */
){
char *zRet = 0;
sqlite3_stmt *pStmt = 0;
int bAutoIndex = 0;
int bIsIndex = 0;
const char *zCommon =
/* Relation without_rowid also contains just one row. Column "b" is
** set to true if the table being examined is a WITHOUT ROWID table,
** or false otherwise. */
", without_rowid(b) AS ("
" SELECT EXISTS ("
" SELECT 1 FROM tabname, pragma_index_list(tab, db) AS l"
" WHERE origin='pk' "
" AND NOT EXISTS (SELECT 1 FROM sqlite_schema WHERE name=l.name)"
" )"
")"
""
/* Table idx_cols contains 1 row for each column in each index on the
** table being checked. Columns are:
**
** idx_name: Name of the index.
** idx_ispk: True if this index is the PK of a WITHOUT ROWID table.
** col_name: Name of indexed column, or NULL for index on expression.
** col_expr: Indexed expression, including COLLATE clause.
** col_alias: Alias used for column in 'intck_wrapper' table.
*/
", idx_cols(idx_name, idx_ispk, col_name, col_expr, col_alias) AS ("
" SELECT l.name, (l.origin=='pk' AND w.b), i.name, COALESCE(("
" SELECT parse_create_index(sql, i.seqno) FROM "
" sqlite_schema WHERE name = l.name"
" ), format('\"%w\"', i.name) || ' COLLATE ' || quote(i.coll)),"
" 'c' || row_number() OVER ()"
" FROM "
" tabname t,"
" without_rowid w,"
" pragma_index_list(t.tab, t.db) l,"
" pragma_index_xinfo(l.name) i"
" WHERE i.key"
" UNION ALL"
" SELECT '', 1, '_rowid_', '_rowid_', 'r1' FROM without_rowid WHERE b=0"
")"
""
""
/*
** For a PK declared as "PRIMARY KEY(a, b) ... WITHOUT ROWID", where
** the intck_wrapper aliases of "a" and "b" are "c1" and "c2":
**
** o_pk: "o.c1, o.c2"
** i_pk: "i.'a', i.'b'"
** ...
** n_pk: 2
*/
", tabpk(db, tab, idx, o_pk, i_pk, q_pk, eq_pk, ps_pk, pk_pk, n_pk) AS ("
" WITH pkfields(f, a) AS ("
" SELECT i.col_name, i.col_alias FROM idx_cols i WHERE i.idx_ispk"
" )"
" SELECT t.db, t.tab, t.idx, "
" group_concat(a, ', '), "
" group_concat('i.'||quote(f), ', '), "
" group_concat('quote(o.'||a||')', ' || '','' || '), "
" format('(%s)==(%s)',"
" group_concat('o.'||a, ', '), "
" group_concat(format('\"%w\"', f), ', ')"
" ),"
" group_concat('%s', ','),"
" group_concat('quote('||a||')', ', '), "
" count(*)"
" FROM tabname t, pkfields"
")"
""
", idx(name, match_expr, partial, partial_alias, idx_ps, idx_idx) AS ("
" SELECT idx_name,"
" format('(%s,%s) IS (%s,%s)', "
" group_concat(i.col_expr, ', '), i_pk,"
" group_concat('o.'||i.col_alias, ', '), o_pk"
" ), "
" parse_create_index("
" (SELECT sql FROM sqlite_schema WHERE name=idx_name), -1"
" ),"
" 'cond' || row_number() OVER ()"
" , group_concat('%s', ',')"
" , group_concat('quote('||i.col_alias||')', ', ')"
" FROM tabpk t, "
" without_rowid w,"
" idx_cols i"
" WHERE i.idx_ispk==0 "
" GROUP BY idx_name"
")"
""
", wrapper_with(s) AS ("
" SELECT 'intck_wrapper AS (\n SELECT\n ' || ("
" WITH f(a, b) AS ("
" SELECT col_expr, col_alias FROM idx_cols"
" UNION ALL "
" SELECT partial, partial_alias FROM idx WHERE partial IS NOT NULL"
" )"
" SELECT group_concat(format('%s AS %s', a, b), ',\n ') FROM f"
" )"
" || format('\n FROM %Q.%Q ', t.db, t.tab)"
/* If the object being checked is a table, append "NOT INDEXED".
** Otherwise, append "INDEXED BY <index>", and then, if the index
** is a partial index " WHERE <condition>". */
" || CASE WHEN t.idx IS NULL THEN "
" 'NOT INDEXED'"
" ELSE"
" format('INDEXED BY %Q%s', t.idx, ' WHERE '||i.partial)"
" END"
" || '\n)'"
" FROM tabname t LEFT JOIN idx i ON (i.name=t.idx)"
")"
""
;
bAutoIndex = intckGetAutoIndex(p);
if( bAutoIndex ) intckExec(p, "PRAGMA automatic_index = 0");
bIsIndex = intckIsIndex(p, zObj);
if( bIsIndex ){
pStmt = intckPrepareFmt(p,
/* Table idxname contains a single row. The first column, "db", contains
** the name of the db containing the table (e.g. "main") and the second,
** "tab", the name of the table itself. */
"WITH tabname(db, tab, idx) AS ("
" SELECT %Q, (SELECT tbl_name FROM %Q.sqlite_schema WHERE name=%Q), %Q "
")"
""
", whereclause(w_c) AS (%s)"
""
"%s" /* zCommon */
""
", case_statement(c) AS ("
" SELECT "
" 'CASE WHEN (' || group_concat(col_alias, ', ') || ', 1) IS (\n' "
" || ' SELECT ' || group_concat(col_expr, ', ') || ', 1 FROM '"
" || format('%%Q.%%Q NOT INDEXED WHERE %%s\n', t.db, t.tab, p.eq_pk)"
" || ' )\n THEN NULL\n '"
" || 'ELSE format(''surplus entry ('"
" || group_concat('%%s', ',') || ',' || p.ps_pk"
" || ') in index ' || t.idx || ''', ' "
" || group_concat('quote('||i.col_alias||')', ', ') || ', ' || p.pk_pk"
" || ')'"
" || '\n END AS error_message'"
" FROM tabname t, tabpk p, idx_cols i WHERE i.idx_name=t.idx"
")"
""
", thiskey(k, n) AS ("
" SELECT group_concat(i.col_alias, ', ') || ', ' || p.o_pk, "
" count(*) + p.n_pk "
" FROM tabpk p, idx_cols i WHERE i.idx_name=p.idx"
")"
""
", main_select(m, n) AS ("
" SELECT format("
" 'WITH %%s\n' ||"
" ', idx_checker AS (\n' ||"
" ' SELECT %%s,\n' ||"
" ' %%s\n' || "
" ' FROM intck_wrapper AS o\n' ||"
" ')\n',"
" ww.s, c, t.k"
" ), t.n"
" FROM case_statement, wrapper_with ww, thiskey t"
")"
"SELECT m || "
" group_concat('SELECT * FROM idx_checker ' || w_c, ' UNION ALL '), n"
" FROM "
"main_select, whereclause "
, p->zDb, p->zDb, zObj, zObj
, zPrev ? zPrev : "VALUES('')", zCommon
);
}else{
pStmt = intckPrepareFmt(p,
/* Table tabname contains a single row. The first column, "db", contains
** the name of the db containing the table (e.g. "main") and the second,
** "tab", the name of the table itself. */
"WITH tabname(db, tab, idx, prev) AS (SELECT %Q, %Q, NULL, %Q)"
""
"%s" /* zCommon */
/* expr(e) contains one row for each index on table zObj. Value e
** is set to an expression that evaluates to NULL if the required
** entry is present in the index, or an error message otherwise. */
", expr(e, p) AS ("
" SELECT format('CASE WHEN EXISTS \n"
" (SELECT 1 FROM %%Q.%%Q AS i INDEXED BY %%Q WHERE %%s%%s)\n"
" THEN NULL\n"
" ELSE format(''entry (%%s,%%s) missing from index %%s'', %%s, %%s)\n"
" END\n'"
" , t.db, t.tab, i.name, i.match_expr, ' AND (' || partial || ')',"
" i.idx_ps, t.ps_pk, i.name, i.idx_idx, t.pk_pk),"
" CASE WHEN partial IS NULL THEN NULL ELSE i.partial_alias END"
" FROM tabpk t, idx i"
")"
", numbered(ii, cond, e) AS ("
" SELECT 0, 'n.ii=0', 'NULL'"
" UNION ALL "
" SELECT row_number() OVER (),"
" '(n.ii='||row_number() OVER ()||COALESCE(' AND '||p||')', ')'), e"
" FROM expr"
")"
", counter_with(w) AS ("
" SELECT 'WITH intck_counter(ii) AS (\n ' || "
" group_concat('SELECT '||ii, ' UNION ALL\n ') "
" || '\n)' FROM numbered"
")"
""
", case_statement(c) AS ("
" SELECT 'CASE ' || "
" group_concat(format('\n WHEN %%s THEN (%%s)', cond, e), '') ||"
" '\nEND AS error_message'"
" FROM numbered"
")"
""
/* This table contains a single row consisting of a single value -
** the text of an SQL expression that may be used by the main SQL
** statement to output an SQL literal that can be used to resume
** the scan if it is suspended. e.g. for a rowid table, an expression
** like:
**
** format('(%d,%d)', _rowid_, n.ii)
*/
", thiskey(k, n) AS ("
" SELECT o_pk || ', ii', n_pk+1 FROM tabpk"
")"
""
", whereclause(w_c) AS ("
" SELECT CASE WHEN prev!='' THEN "
" '\nWHERE (' || o_pk ||', n.ii) > ' || prev"
" ELSE ''"
" END"
" FROM tabpk, tabname"
")"
""
", main_select(m, n) AS ("
" SELECT format("
" '%%s, %%s\nSELECT %%s,\n%%s\nFROM intck_wrapper AS o"
", intck_counter AS n%%s\nORDER BY %%s', "
" w, ww.s, c, thiskey.k, whereclause.w_c, t.o_pk"
" ), thiskey.n"
" FROM case_statement, tabpk t, counter_with, "
" wrapper_with ww, thiskey, whereclause"
")"
"SELECT m, n FROM main_select",
p->zDb, zObj, zPrev, zCommon
);
}
while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
zRet = intckMprintf(p, "%s", (const char*)sqlite3_column_text(pStmt, 0));
if( pnKeyVal ){
*pnKeyVal = sqlite3_column_int(pStmt, 1);
}
}
intckFinalize(p, pStmt);
if( bAutoIndex ) intckExec(p, "PRAGMA automatic_index = 1");
return zRet;
}
/*
** Open a new integrity-check object.
*/
int sqlite3_intck_open(
sqlite3 *db, /* Database handle to operate on */
const char *zDbArg, /* "main", "temp" etc. */
sqlite3_intck **ppOut /* OUT: New integrity-check handle */
){
sqlite3_intck *pNew = 0;
int rc = SQLITE_OK;
const char *zDb = zDbArg ? zDbArg : "main";
int nDb = (int)strlen(zDb);
pNew = (sqlite3_intck*)sqlite3_malloc(sizeof(*pNew) + nDb + 1);
if( pNew==0 ){
rc = SQLITE_NOMEM;
}else{
memset(pNew, 0, sizeof(*pNew));
pNew->db = db;
pNew->zDb = (const char*)&pNew[1];
memcpy(&pNew[1], zDb, nDb+1);
rc = sqlite3_create_function(db, "parse_create_index",
2, SQLITE_UTF8, 0, intckParseCreateIndexFunc, 0, 0
);
if( rc!=SQLITE_OK ){
sqlite3_intck_close(pNew);
pNew = 0;
}
}
*ppOut = pNew;
return rc;
}
/*
** Free the integrity-check object.
*/
void sqlite3_intck_close(sqlite3_intck *p){
if( p ){
sqlite3_finalize(p->pCheck);
sqlite3_create_function(
p->db, "parse_create_index", 1, SQLITE_UTF8, 0, 0, 0, 0
);
sqlite3_free(p->zObj);
sqlite3_free(p->zKey);
sqlite3_free(p->zTestSql);
sqlite3_free(p->zErr);
sqlite3_free(p->zMessage);
sqlite3_free(p);
}
}
/*
** Step the integrity-check object.
*/
int sqlite3_intck_step(sqlite3_intck *p){
if( p->rc==SQLITE_OK ){
if( p->zMessage ){
sqlite3_free(p->zMessage);
p->zMessage = 0;
}
if( p->bCorruptSchema ){
p->rc = SQLITE_DONE;
}else
if( p->pCheck==0 ){
intckFindObject(p);
if( p->rc==SQLITE_OK ){
if( p->zObj ){
char *zSql = 0;
zSql = intckCheckObjectSql(p, p->zObj, p->zKey, &p->nKeyVal);
p->pCheck = intckPrepare(p, zSql);
sqlite3_free(zSql);
sqlite3_free(p->zKey);
p->zKey = 0;
}else{
p->rc = SQLITE_DONE;
}
}else if( p->rc==SQLITE_CORRUPT ){
p->rc = SQLITE_OK;
p->zMessage = intckMprintf(p, "%s",
"corruption found while reading database schema"
);
p->bCorruptSchema = 1;
}
}
if( p->pCheck ){
assert( p->rc==SQLITE_OK );
if( sqlite3_step(p->pCheck)==SQLITE_ROW ){
/* Normal case, do nothing. */
}else{
intckFinalize(p, p->pCheck);
p->pCheck = 0;
p->nKeyVal = 0;
if( p->rc==SQLITE_CORRUPT ){
p->rc = SQLITE_OK;
p->zMessage = intckMprintf(p,
"corruption found while scanning database object %s", p->zObj
);
}
}
}
}
return p->rc;
}
/*
** Return a message describing the corruption encountered by the most recent
** call to sqlite3_intck_step(), or NULL if no corruption was encountered.
*/
const char *sqlite3_intck_message(sqlite3_intck *p){
assert( p->pCheck==0 || p->zMessage==0 );
if( p->zMessage ){
return p->zMessage;
}
if( p->pCheck ){
return (const char*)sqlite3_column_text(p->pCheck, 0);
}
return 0;
}
/*
** Return the error code and message.
*/
int sqlite3_intck_error(sqlite3_intck *p, const char **pzErr){
if( pzErr ) *pzErr = p->zErr;
return (p->rc==SQLITE_DONE ? SQLITE_OK : p->rc);
}
/*
** Close any read transaction the integrity-check object is holding open
** on the database.
*/
int sqlite3_intck_unlock(sqlite3_intck *p){
if( p->rc==SQLITE_OK && p->pCheck ){
assert( p->zKey==0 && p->nKeyVal>0 );
intckSaveKey(p);
intckFinalize(p, p->pCheck);
p->pCheck = 0;
}
return p->rc;
}
/*
** Return the SQL statement used to check object zObj. Or, if zObj is
** NULL, the current SQL statement.
*/
const char *sqlite3_intck_test_sql(sqlite3_intck *p, const char *zObj){
sqlite3_free(p->zTestSql);
if( zObj ){
p->zTestSql = intckCheckObjectSql(p, zObj, 0, 0);
}else{
if( p->zObj ){
p->zTestSql = intckCheckObjectSql(p, p->zObj, p->zKey, 0);
}else{
sqlite3_free(p->zTestSql);
p->zTestSql = 0;
}
}
return p->zTestSql;
}

171
ext/intck/sqlite3intck.h Normal file
View File

@ -0,0 +1,171 @@
/*
** 2024-02-08
**
** 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.
**
*************************************************************************
*/
/*
** Incremental Integrity-Check Extension
** -------------------------------------
**
** This module contains code to check whether or not an SQLite database
** is well-formed or corrupt. This is the same task as performed by SQLite's
** built-in "PRAGMA integrity_check" command. This module differs from
** "PRAGMA integrity_check" in that:
**
** + It is less thorough - this module does not detect certain types
** of corruption that are detected by the PRAGMA command. However,
** it does detect all kinds of corruption that are likely to cause
** errors in SQLite applications.
**
** + It is slower. Sometimes up to three times slower.
**
** + It allows integrity-check operations to be split into multiple
** transactions, so that the database does not need to be read-locked
** for the duration of the integrity-check.
**
** One way to use the API to run integrity-check on the "main" database
** of handle db is:
**
** int rc = SQLITE_OK;
** sqlite3_intck *p = 0;
**
** sqlite3_intck_open(db, "main", &p);
** while( SQLITE_OK==sqlite3_intck_step(p) ){
** const char *zMsg = sqlite3_intck_message(p);
** if( zMsg ) printf("corruption: %s\n", zMsg);
** }
** rc = sqlite3_intck_error(p, &zErr);
** if( rc!=SQLITE_OK ){
** printf("error occured (rc=%d), (errmsg=%s)\n", rc, zErr);
** }
** sqlite3_intck_close(p);
**
** Usually, the sqlite3_intck object opens a read transaction within the
** first call to sqlite3_intck_step() and holds it open until the
** integrity-check is complete. However, if sqlite3_intck_unlock() is
** called, the read transaction is ended and a new read transaction opened
** by the subsequent call to sqlite3_intck_step().
*/
#ifndef _SQLITE_INTCK_H
#define _SQLITE_INTCK_H
#include "sqlite3.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
** An ongoing incremental integrity-check operation is represented by an
** opaque pointer of the following type.
*/
typedef struct sqlite3_intck sqlite3_intck;
/*
** Open a new incremental integrity-check object. If successful, populate
** output variable (*ppOut) with the new object handle and return SQLITE_OK.
** Or, if an error occurs, set (*ppOut) to NULL and return an SQLite error
** code (e.g. SQLITE_NOMEM).
**
** The integrity-check will be conducted on database zDb (which must be "main",
** "temp", or the name of an attached database) of database handle db. Once
** this function has been called successfully, the caller should not use
** database handle db until the integrity-check object has been destroyed
** using sqlite3_intck_close().
*/
int sqlite3_intck_open(
sqlite3 *db, /* Database handle */
const char *zDb, /* Database name ("main", "temp" etc.) */
sqlite3_intck **ppOut /* OUT: New sqlite3_intck handle */
);
/*
** Close and release all resources associated with a handle opened by an
** earlier call to sqlite3_intck_open(). The results of using an
** integrity-check handle after it has been passed to this function are
** undefined.
*/
void sqlite3_intck_close(sqlite3_intck *pCk);
/*
** Do the next step of the integrity-check operation specified by the handle
** passed as the only argument. This function returns SQLITE_DONE if the
** integrity-check operation is finished, or an SQLite error code if
** an error occurs, or SQLITE_OK if no error occurs but the integrity-check
** is not finished. It is not considered an error if database corruption
** is encountered.
**
** Following a successful call to sqlite3_intck_step() (one that returns
** SQLITE_OK), sqlite3_intck_message() returns a non-NULL value if
** corruption was detected in the db.
**
** If an error occurs and a value other than SQLITE_OK or SQLITE_DONE is
** returned, then the integrity-check handle is placed in an error state.
** In this state all subsequent calls to sqlite3_intck_step() or
** sqlite3_intck_unlock() will immediately return the same error. The
** sqlite3_intck_error() method may be used to obtain an English language
** error message in this case.
*/
int sqlite3_intck_step(sqlite3_intck *pCk);
/*
** If the previous call to sqlite3_intck_step() encountered corruption
** within the database, then this function returns a pointer to a buffer
** containing a nul-terminated string describing the corruption in
** English. If the previous call to sqlite3_intck_step() did not encounter
** corruption, or if there was no previous call, this function returns
** NULL.
*/
const char *sqlite3_intck_message(sqlite3_intck *pCk);
/*
** Close any read-transaction opened by an earlier call to
** sqlite3_intck_step(). Any subsequent call to sqlite3_intck_step() will
** open a new transaction. Return SQLITE_OK if successful, or an SQLite error
** code otherwise.
**
** If an error occurs, then the integrity-check handle is placed in an error
** state. In this state all subsequent calls to sqlite3_intck_step() or
** sqlite3_intck_unlock() will immediately return the same error. The
** sqlite3_intck_error() method may be used to obtain an English language
** error message in this case.
*/
int sqlite3_intck_unlock(sqlite3_intck *pCk);
/*
** If an error has occurred in an earlier call to sqlite3_intck_step()
** or sqlite3_intck_unlock(), then this method returns the associated
** SQLite error code. Additionally, if pzErr is not NULL, then (*pzErr)
** may be set to point to a nul-terminated string containing an English
** language error message. Or, if no error message is available, to
** NULL.
**
** If no error has occurred within sqlite3_intck_step() or
** sqlite_intck_unlock() calls on the handle passed as the first argument,
** then SQLITE_OK is returned and (*pzErr) set to NULL.
*/
int sqlite3_intck_error(sqlite3_intck *pCk, const char **pzErr);
/*
** This API is used for testing only. It returns the full-text of an SQL
** statement used to test object zObj, which may be a table or index.
** The returned buffer is valid until the next call to either this function
** or sqlite3_intck_close() on the same sqlite3_intck handle.
*/
const char *sqlite3_intck_test_sql(sqlite3_intck *pCk, const char *zObj);
#ifdef __cplusplus
} /* end of the 'extern "C"' block */
#endif
#endif /* ifndef _SQLITE_INTCK_H */

238
ext/intck/test_intck.c Normal file
View File

@ -0,0 +1,238 @@
/*
** 2010 August 28
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** Code for testing all sorts of SQLite interfaces. This code
** is not included in the SQLite library.
*/
#include "sqlite3.h"
#include "sqlite3intck.h"
#if defined(INCLUDE_SQLITE_TCL_H)
# include "sqlite_tcl.h"
#else
# include "tcl.h"
#endif
#include <string.h>
#include <assert.h>
/* In test1.c */
int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb);
const char *sqlite3ErrName(int);
typedef struct TestIntck TestIntck;
struct TestIntck {
sqlite3_intck *intck;
};
static int testIntckCmd(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
struct Subcmd {
const char *zName;
int nArg;
const char *zExpect;
} aCmd[] = {
{"close", 0, ""}, /* 0 */
{"step", 0, ""}, /* 1 */
{"message", 0, ""}, /* 2 */
{"error", 0, ""}, /* 3 */
{"unlock", 0, ""}, /* 4 */
{"test_sql", 1, ""}, /* 5 */
{0 , 0}
};
int rc = TCL_OK;
int iIdx = -1;
TestIntck *p = (TestIntck*)clientData;
if( objc<2 ){
Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ...");
return TCL_ERROR;
}
rc = Tcl_GetIndexFromObjStruct(
interp, objv[1], aCmd, sizeof(aCmd[0]), "SUB-COMMAND", 0, &iIdx
);
if( rc ) return rc;
if( objc!=2+aCmd[iIdx].nArg ){
Tcl_WrongNumArgs(interp, 2, objv, aCmd[iIdx].zExpect);
return TCL_ERROR;
}
switch( iIdx ){
case 0: assert( 0==strcmp("close", aCmd[iIdx].zName) ); {
Tcl_DeleteCommand(interp, Tcl_GetStringFromObj(objv[0], 0));
break;
}
case 1: assert( 0==strcmp("step", aCmd[iIdx].zName) ); {
rc = sqlite3_intck_step(p->intck);
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
break;
}
case 2: assert( 0==strcmp("message", aCmd[iIdx].zName) ); {
const char *z = sqlite3_intck_message(p->intck);
Tcl_SetObjResult(interp, Tcl_NewStringObj(z ? z : "", -1));
break;
}
case 3: assert( 0==strcmp("error", aCmd[iIdx].zName) ); {
const char *zErr = 0;
rc = sqlite3_intck_error(p->intck, 0);
Tcl_Obj *pRes = Tcl_NewObj();
Tcl_ListObjAppendElement(
interp, pRes, Tcl_NewStringObj(sqlite3ErrName(rc), -1)
);
sqlite3_intck_error(p->intck, &zErr);
Tcl_ListObjAppendElement(
interp, pRes, Tcl_NewStringObj(zErr ? zErr : 0, -1)
);
Tcl_SetObjResult(interp, pRes);
break;
}
case 4: assert( 0==strcmp("unlock", aCmd[iIdx].zName) ); {
rc = sqlite3_intck_unlock(p->intck);
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
break;
}
case 5: assert( 0==strcmp("test_sql", aCmd[iIdx].zName) ); {
const char *zObj = Tcl_GetString(objv[2]);
const char *zSql = sqlite3_intck_test_sql(p->intck, zObj[0] ? zObj : 0);
Tcl_SetObjResult(interp, Tcl_NewStringObj(zSql, -1));
break;
}
}
return TCL_OK;
}
/*
** Destructor for commands created by test_sqlite3_intck().
*/
static void testIntckFree(void *clientData){
TestIntck *p = (TestIntck*)clientData;
sqlite3_intck_close(p->intck);
ckfree(p);
}
/*
** tclcmd: sqlite3_intck DB DBNAME
*/
static int test_sqlite3_intck(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
char zName[64];
int iName = 0;
Tcl_CmdInfo info;
TestIntck *p = 0;
sqlite3 *db = 0;
const char *zDb = 0;
int rc = SQLITE_OK;
if( objc!=3 ){
Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
return TCL_ERROR;
}
p = (TestIntck*)ckalloc(sizeof(TestIntck));
memset(p, 0, sizeof(TestIntck));
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
return TCL_ERROR;
}
zDb = Tcl_GetString(objv[2]);
if( zDb[0]=='\0' ) zDb = 0;
rc = sqlite3_intck_open(db, zDb, &p->intck);
if( rc!=SQLITE_OK ){
ckfree(p);
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3_errstr(rc), -1));
return TCL_ERROR;
}
do {
sprintf(zName, "intck%d", iName++);
}while( Tcl_GetCommandInfo(interp, zName, &info)!=0 );
Tcl_CreateObjCommand(interp, zName, testIntckCmd, (void*)p, testIntckFree);
Tcl_SetObjResult(interp, Tcl_NewStringObj(zName, -1));
return TCL_OK;
}
/*
** tclcmd: test_do_intck DB DBNAME
*/
static int test_do_intck(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
sqlite3 *db = 0;
const char *zDb = 0;
int rc = SQLITE_OK;
sqlite3_intck *pCk = 0;
Tcl_Obj *pRet = 0;
const char *zErr = 0;
if( objc!=3 ){
Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
return TCL_ERROR;
}
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
return TCL_ERROR;
}
zDb = Tcl_GetString(objv[2]);
pRet = Tcl_NewObj();
Tcl_IncrRefCount(pRet);
rc = sqlite3_intck_open(db, zDb, &pCk);
if( rc==SQLITE_OK ){
while( sqlite3_intck_step(pCk)==SQLITE_OK ){
const char *zMsg = sqlite3_intck_message(pCk);
if( zMsg ){
Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj(zMsg, -1));
}
}
rc = sqlite3_intck_error(pCk, &zErr);
}
if( rc!=SQLITE_OK ){
if( zErr ){
Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1));
}else{
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
}
}else{
Tcl_SetObjResult(interp, pRet);
}
Tcl_DecrRefCount(pRet);
sqlite3_intck_close(pCk);
sqlite3_intck_close(0);
return rc ? TCL_ERROR : TCL_OK;
}
int Sqlitetestintck_Init(Tcl_Interp *interp){
Tcl_CreateObjCommand(interp, "sqlite3_intck", test_sqlite3_intck, 0, 0);
Tcl_CreateObjCommand(interp, "test_do_intck", test_do_intck, 0, 0);
return TCL_OK;
}

View File

@ -446,9 +446,9 @@ static int cksmRead(
** (2) checksum verification is enabled
** (3) we are not in the middle of checkpoint
*/
if( iAmt>=512 /* (1) */
&& p->verifyCksm /* (2) */
&& !p->inCkpt /* (3) */
if( iAmt>=512 && (iAmt & (iAmt-1))==0 /* (1) */
&& p->verifyCksm /* (2) */
&& !p->inCkpt /* (3) */
){
u8 cksum[8];
cksmCompute((u8*)zBuf, iAmt-8, cksum);

View File

@ -372,7 +372,9 @@ static int writeFile(
#if !defined(_WIN32) && !defined(WIN32)
if( S_ISLNK(mode) ){
const char *zTo = (const char*)sqlite3_value_text(pData);
if( zTo==0 || symlink(zTo, zFile)<0 ) return 1;
if( zTo==0 ) return 1;
unlink(zFile);
if( symlink(zTo, zFile)<0 ) return 1;
}else
#endif
{
@ -458,13 +460,19 @@ static int writeFile(
return 1;
}
#else
/* Legacy unix */
struct timeval times[2];
times[0].tv_usec = times[1].tv_usec = 0;
times[0].tv_sec = time(0);
times[1].tv_sec = mtime;
if( utimes(zFile, times) ){
return 1;
/* Legacy unix.
**
** Do not use utimes() on a symbolic link - it sees through the link and
** modifies the timestamps on the target. Or fails if the target does
** not exist. */
if( 0==S_ISLNK(mode) ){
struct timeval times[2];
times[0].tv_usec = times[1].tv_usec = 0;
times[0].tv_sec = time(0);
times[1].tv_sec = mtime;
if( utimes(zFile, times) ){
return 1;
}
}
#endif
}

View File

@ -103,16 +103,20 @@ SQLITE_EXTENSION_INIT1
** index is ix. The 0th member is given by smBase. The sequence members
** progress per ix increment by smStep.
*/
static sqlite3_int64 genSeqMember(sqlite3_int64 smBase,
sqlite3_int64 smStep,
sqlite3_uint64 ix){
if( ix>=(sqlite3_uint64)LLONG_MAX ){
static sqlite3_int64 genSeqMember(
sqlite3_int64 smBase,
sqlite3_int64 smStep,
sqlite3_uint64 ix
){
static const sqlite3_uint64 mxI64 =
((sqlite3_uint64)0x7fffffff)<<32 | 0xffffffff;
if( ix>=mxI64 ){
/* Get ix into signed i64 range. */
ix -= (sqlite3_uint64)LLONG_MAX;
ix -= mxI64;
/* With 2's complement ALU, this next can be 1 step, but is split into
* 2 for UBSAN's satisfaction (and hypothetical 1's complement ALUs.) */
smBase += (LLONG_MAX/2) * smStep;
smBase += (LLONG_MAX - LLONG_MAX/2) * smStep;
smBase += (mxI64/2) * smStep;
smBase += (mxI64 - mxI64/2) * smStep;
}
/* Under UBSAN (or on 1's complement machines), must do this last term
* in steps to avoid the dreaded (and harmless) signed multiply overlow. */
@ -372,13 +376,13 @@ static int seriesEof(sqlite3_vtab_cursor *cur){
** parameter. (idxStr is not used in this implementation.) idxNum
** is a bitmask showing which constraints are available:
**
** 1: start=VALUE
** 2: stop=VALUE
** 4: step=VALUE
**
** Also, if bit 8 is set, that means that the series should be output
** in descending order rather than in ascending order. If bit 16 is
** set, then output must appear in ascending order.
** 0x01: start=VALUE
** 0x02: stop=VALUE
** 0x04: step=VALUE
** 0x08: descending order
** 0x10: ascending order
** 0x20: LIMIT VALUE
** 0x40: OFFSET VALUE
**
** This routine should initialize the cursor and position it so that it
** is pointing at the first row, or pointing off the end of the table
@ -392,26 +396,44 @@ static int seriesFilter(
series_cursor *pCur = (series_cursor *)pVtabCursor;
int i = 0;
(void)idxStrUnused;
if( idxNum & 1 ){
if( idxNum & 0x01 ){
pCur->ss.iBase = sqlite3_value_int64(argv[i++]);
}else{
pCur->ss.iBase = 0;
}
if( idxNum & 2 ){
if( idxNum & 0x02 ){
pCur->ss.iTerm = sqlite3_value_int64(argv[i++]);
}else{
pCur->ss.iTerm = 0xffffffff;
}
if( idxNum & 4 ){
if( idxNum & 0x04 ){
pCur->ss.iStep = sqlite3_value_int64(argv[i++]);
if( pCur->ss.iStep==0 ){
pCur->ss.iStep = 1;
}else if( pCur->ss.iStep<0 ){
if( (idxNum & 16)==0 ) idxNum |= 8;
if( (idxNum & 0x10)==0 ) idxNum |= 0x08;
}
}else{
pCur->ss.iStep = 1;
}
if( idxNum & 0x20 ){
sqlite3_int64 iLimit = sqlite3_value_int64(argv[i++]);
sqlite3_int64 iTerm;
if( idxNum & 0x40 ){
sqlite3_int64 iOffset = sqlite3_value_int64(argv[i++]);
if( iOffset>0 ){
pCur->ss.iBase += pCur->ss.iStep*iOffset;
}
}
if( iLimit>=0 ){
iTerm = pCur->ss.iBase + (iLimit - 1)*pCur->ss.iStep;
if( pCur->ss.iStep<0 ){
if( iTerm>pCur->ss.iTerm ) pCur->ss.iTerm = iTerm;
}else{
if( iTerm<pCur->ss.iTerm ) pCur->ss.iTerm = iTerm;
}
}
}
for(i=0; i<argc; i++){
if( sqlite3_value_type(argv[i])==SQLITE_NULL ){
/* If any of the constraints have a NULL value, then return no rows.
@ -422,7 +444,7 @@ static int seriesFilter(
break;
}
}
if( idxNum & 8 ){
if( idxNum & 0x08 ){
pCur->ss.isReversing = pCur->ss.iStep > 0;
}else{
pCur->ss.isReversing = pCur->ss.iStep < 0;
@ -442,10 +464,13 @@ static int seriesFilter(
**
** The query plan is represented by bits in idxNum:
**
** (1) start = $value -- constraint exists
** (2) stop = $value -- constraint exists
** (4) step = $value -- constraint exists
** (8) output in descending order
** 0x01 start = $value -- constraint exists
** 0x02 stop = $value -- constraint exists
** 0x04 step = $value -- constraint exists
** 0x08 output is in descending order
** 0x10 output is in ascending order
** 0x20 LIMIT $value -- constraint exists
** 0x40 OFFSET $value -- constraint exists
*/
static int seriesBestIndex(
sqlite3_vtab *pVTab,
@ -453,10 +478,12 @@ static int seriesBestIndex(
){
int i, j; /* Loop over constraints */
int idxNum = 0; /* The query plan bitmask */
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
int bStartSeen = 0; /* EQ constraint seen on the START column */
#endif
int unusableMask = 0; /* Mask of unusable constraints */
int nArg = 0; /* Number of arguments that seriesFilter() expects */
int aIdx[3]; /* Constraints on start, stop, and step */
int aIdx[5]; /* Constraints on start, stop, step, LIMIT, OFFSET */
const struct sqlite3_index_constraint *pConstraint;
/* This implementation assumes that the start, stop, and step columns
@ -464,28 +491,54 @@ static int seriesBestIndex(
assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 );
assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 );
aIdx[0] = aIdx[1] = aIdx[2] = -1;
aIdx[0] = aIdx[1] = aIdx[2] = aIdx[3] = aIdx[4] = -1;
pConstraint = pIdxInfo->aConstraint;
for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
int iCol; /* 0 for start, 1 for stop, 2 for step */
int iMask; /* bitmask for those column */
int op = pConstraint->op;
if( op>=SQLITE_INDEX_CONSTRAINT_LIMIT
&& op<=SQLITE_INDEX_CONSTRAINT_OFFSET
){
if( pConstraint->usable==0 ){
/* do nothing */
}else if( op==SQLITE_INDEX_CONSTRAINT_LIMIT ){
aIdx[3] = i;
idxNum |= 0x20;
}else{
assert( op==SQLITE_INDEX_CONSTRAINT_OFFSET );
aIdx[4] = i;
idxNum |= 0x40;
}
continue;
}
if( pConstraint->iColumn<SERIES_COLUMN_START ) continue;
iCol = pConstraint->iColumn - SERIES_COLUMN_START;
assert( iCol>=0 && iCol<=2 );
iMask = 1 << iCol;
if( iCol==0 ) bStartSeen = 1;
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
if( iCol==0 && op==SQLITE_INDEX_CONSTRAINT_EQ ){
bStartSeen = 1;
}
#endif
if( pConstraint->usable==0 ){
unusableMask |= iMask;
continue;
}else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){
}else if( op==SQLITE_INDEX_CONSTRAINT_EQ ){
idxNum |= iMask;
aIdx[iCol] = i;
}
}
for(i=0; i<3; i++){
if( aIdx[3]==0 ){
/* Ignore OFFSET if LIMIT is omitted */
idxNum &= ~0x60;
aIdx[4] = 0;
}
for(i=0; i<5; i++){
if( (j = aIdx[i])>=0 ){
pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg;
pIdxInfo->aConstraintUsage[j].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY;
pIdxInfo->aConstraintUsage[j].omit =
!SQLITE_SERIES_CONSTRAINT_VERIFY || i>=3;
}
}
/* The current generate_column() implementation requires at least one
@ -506,19 +559,22 @@ static int seriesBestIndex(
** this plan is unusable */
return SQLITE_CONSTRAINT;
}
if( (idxNum & 3)==3 ){
if( (idxNum & 0x03)==0x03 ){
/* Both start= and stop= boundaries are available. This is the
** the preferred case */
pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0));
pIdxInfo->estimatedRows = 1000;
if( pIdxInfo->nOrderBy>=1 && pIdxInfo->aOrderBy[0].iColumn==0 ){
if( pIdxInfo->aOrderBy[0].desc ){
idxNum |= 8;
idxNum |= 0x08;
}else{
idxNum |= 16;
idxNum |= 0x10;
}
pIdxInfo->orderByConsumed = 1;
}
}else if( (idxNum & 0x21)==0x21 ){
/* We have start= and LIMIT */
pIdxInfo->estimatedRows = 2500;
}else{
/* If either boundary is missing, we have to generate a huge span
** of numbers. Make this case very expensive so that the query

View File

@ -81,7 +81,7 @@ static void sqlarUncompressFunc(
sqlite3_value **argv
){
uLong nData;
uLongf sz;
sqlite3_int64 sz;
assert( argc==2 );
sz = sqlite3_value_int(argv[1]);
@ -89,14 +89,15 @@ static void sqlarUncompressFunc(
if( sz<=0 || sz==(nData = sqlite3_value_bytes(argv[0])) ){
sqlite3_result_value(context, argv[0]);
}else{
uLongf szf = sz;
const Bytef *pData= sqlite3_value_blob(argv[0]);
Bytef *pOut = sqlite3_malloc(sz);
if( pOut==0 ){
sqlite3_result_error_nomem(context);
}else if( Z_OK!=uncompress(pOut, &sz, pData, nData) ){
}else if( Z_OK!=uncompress(pOut, &szf, pData, nData) ){
sqlite3_result_error(context, "error in uncompress()", -1);
}else{
sqlite3_result_blob(context, pOut, sz, SQLITE_TRANSIENT);
sqlite3_result_blob(context, pOut, szf, SQLITE_TRANSIENT);
}
sqlite3_free(pOut);
}

View File

@ -38,8 +38,9 @@ SQLITE_EXTENSION_INIT1
typedef struct vtablog_vtab vtablog_vtab;
struct vtablog_vtab {
sqlite3_vtab base; /* Base class - must be first */
char *zDb; /* Schema name. argv[1] of xConnect/xCreate */
char *zName; /* Table name. argv[2] of xConnect/xCreate */
int nRow; /* Number of rows in the table */
int iInst; /* Instance number for this vtablog table */
int nCursor; /* Number of cursors created */
};
@ -167,15 +168,14 @@ static int vtablogConnectCreate(
char **pzErr,
int isCreate
){
static int nInst = 0;
vtablog_vtab *pNew;
int i;
int rc;
int iInst = ++nInst;
char *zSchema = 0;
char *zNRow = 0;
printf("vtablog%s(tab=%d):\n", isCreate ? "Create" : "Connect", iInst);
printf("%s.%s.%s():\n", argv[1], argv[2],
isCreate ? "xCreate" : "xConnect");
printf(" argc=%d\n", argc);
for(i=0; i<argc; i++){
printf(" argv[%d] = ", i);
@ -189,17 +189,18 @@ static int vtablogConnectCreate(
for(i=3; i<argc; i++){
const char *z = argv[i];
if( vtablog_string_parameter(pzErr, "schema", z, &zSchema) ){
return SQLITE_ERROR;
rc = SQLITE_ERROR;
goto vtablog_end_connect;
}
if( vtablog_string_parameter(pzErr, "rows", z, &zNRow) ){
return SQLITE_ERROR;
rc = SQLITE_ERROR;
goto vtablog_end_connect;
}
}
if( zSchema==0 ){
*pzErr = sqlite3_mprintf("no schema defined");
return SQLITE_ERROR;
zSchema = sqlite3_mprintf("%s","CREATE TABLE x(a,b);");
}
printf(" schema = '%s'\n", zSchema);
rc = sqlite3_declare_vtab(db, zSchema);
if( rc==SQLITE_OK ){
pNew = sqlite3_malloc( sizeof(*pNew) );
@ -208,8 +209,14 @@ static int vtablogConnectCreate(
memset(pNew, 0, sizeof(*pNew));
pNew->nRow = 10;
if( zNRow ) pNew->nRow = atoi(zNRow);
pNew->iInst = iInst;
printf(" nrow = %d\n", pNew->nRow);
pNew->zDb = sqlite3_mprintf("%s", argv[1]);
pNew->zName = sqlite3_mprintf("%s", argv[2]);
}
vtablog_end_connect:
sqlite3_free(zSchema);
sqlite3_free(zNRow);
return rc;
}
static int vtablogCreate(
@ -237,7 +244,9 @@ static int vtablogConnect(
*/
static int vtablogDisconnect(sqlite3_vtab *pVtab){
vtablog_vtab *pTab = (vtablog_vtab*)pVtab;
printf("vtablogDisconnect(%d)\n", pTab->iInst);
printf("%s.%s.xDisconnect()\n", pTab->zDb, pTab->zName);
sqlite3_free(pTab->zDb);
sqlite3_free(pTab->zName);
sqlite3_free(pVtab);
return SQLITE_OK;
}
@ -247,7 +256,9 @@ static int vtablogDisconnect(sqlite3_vtab *pVtab){
*/
static int vtablogDestroy(sqlite3_vtab *pVtab){
vtablog_vtab *pTab = (vtablog_vtab*)pVtab;
printf("vtablogDestroy(%d)\n", pTab->iInst);
printf("%s.%s.xDestroy()\n", pTab->zDb, pTab->zName);
sqlite3_free(pTab->zDb);
sqlite3_free(pTab->zName);
sqlite3_free(pVtab);
return SQLITE_OK;
}
@ -258,7 +269,8 @@ static int vtablogDestroy(sqlite3_vtab *pVtab){
static int vtablogOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
vtablog_vtab *pTab = (vtablog_vtab*)p;
vtablog_cursor *pCur;
printf("vtablogOpen(tab=%d, cursor=%d)\n", pTab->iInst, ++pTab->nCursor);
printf("%s.%s.xOpen(cursor=%d)\n", pTab->zDb, pTab->zName,
++pTab->nCursor);
pCur = sqlite3_malloc( sizeof(*pCur) );
if( pCur==0 ) return SQLITE_NOMEM;
memset(pCur, 0, sizeof(*pCur));
@ -273,7 +285,7 @@ static int vtablogOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
static int vtablogClose(sqlite3_vtab_cursor *cur){
vtablog_cursor *pCur = (vtablog_cursor*)cur;
vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
printf("vtablogClose(tab=%d, cursor=%d)\n", pTab->iInst, pCur->iCursor);
printf("%s.%s.xClose(cursor=%d)\n", pTab->zDb, pTab->zName, pCur->iCursor);
sqlite3_free(cur);
return SQLITE_OK;
}
@ -285,8 +297,9 @@ static int vtablogClose(sqlite3_vtab_cursor *cur){
static int vtablogNext(sqlite3_vtab_cursor *cur){
vtablog_cursor *pCur = (vtablog_cursor*)cur;
vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
printf("vtablogNext(tab=%d, cursor=%d) rowid %d -> %d\n",
pTab->iInst, pCur->iCursor, (int)pCur->iRowid, (int)pCur->iRowid+1);
printf("%s.%s.xNext(cursor=%d) rowid %d -> %d\n",
pTab->zDb, pTab->zName, pCur->iCursor,
(int)pCur->iRowid, (int)pCur->iRowid+1);
pCur->iRowid++;
return SQLITE_OK;
}
@ -310,8 +323,8 @@ static int vtablogColumn(
}else{
sqlite3_snprintf(sizeof(zVal),zVal,"{%d}%d", i, pCur->iRowid);
}
printf("vtablogColumn(tab=%d, cursor=%d, i=%d): [%s]\n",
pTab->iInst, pCur->iCursor, i, zVal);
printf("%s.%s.xColumn(cursor=%d, i=%d): [%s]\n",
pTab->zDb, pTab->zName, pCur->iCursor, i, zVal);
sqlite3_result_text(ctx, zVal, -1, SQLITE_TRANSIENT);
return SQLITE_OK;
}
@ -323,8 +336,8 @@ static int vtablogColumn(
static int vtablogRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
vtablog_cursor *pCur = (vtablog_cursor*)cur;
vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
printf("vtablogRowid(tab=%d, cursor=%d): %d\n",
pTab->iInst, pCur->iCursor, (int)pCur->iRowid);
printf("%s.%s.xRowid(cursor=%d): %d\n",
pTab->zDb, pTab->zName, pCur->iCursor, (int)pCur->iRowid);
*pRowid = pCur->iRowid;
return SQLITE_OK;
}
@ -337,8 +350,8 @@ static int vtablogEof(sqlite3_vtab_cursor *cur){
vtablog_cursor *pCur = (vtablog_cursor*)cur;
vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
int rc = pCur->iRowid >= pTab->nRow;
printf("vtablogEof(tab=%d, cursor=%d): %d\n",
pTab->iInst, pCur->iCursor, rc);
printf("%s.%s.xEof(cursor=%d): %d\n",
pTab->zDb, pTab->zName, pCur->iCursor, rc);
return rc;
}
@ -417,7 +430,7 @@ static int vtablogFilter(
){
vtablog_cursor *pCur = (vtablog_cursor *)cur;
vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
printf("vtablogFilter(tab=%d, cursor=%d):\n", pTab->iInst, pCur->iCursor);
printf("%s.%s.xFilter(cursor=%d):\n", pTab->zDb, pTab->zName, pCur->iCursor);
pCur->iRowid = 0;
return SQLITE_OK;
}
@ -430,12 +443,37 @@ static int vtablogFilter(
*/
static int vtablogBestIndex(
sqlite3_vtab *tab,
sqlite3_index_info *pIdxInfo
sqlite3_index_info *p
){
vtablog_vtab *pTab = (vtablog_vtab*)tab;
printf("vtablogBestIndex(tab=%d):\n", pTab->iInst);
pIdxInfo->estimatedCost = (double)500;
pIdxInfo->estimatedRows = 500;
int i;
printf("%s.%s.xBestIndex():\n", pTab->zDb, pTab->zName);
printf(" colUsed: 0x%016llx\n", p->colUsed);
printf(" nConstraint: %d\n", p->nConstraint);
for(i=0; i<p->nConstraint; i++){
printf(
" constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n",
i,
p->aConstraint[i].iColumn,
p->aConstraint[i].iTermOffset,
p->aConstraint[i].op,
p->aConstraint[i].usable,
sqlite3_vtab_collation(p,i));
}
printf(" nOrderBy: %d\n", p->nOrderBy);
for(i=0; i<p->nOrderBy; i++){
printf(" orderby[%d]: col=%d desc=%d\n",
i,
p->aOrderBy[i].iColumn,
p->aOrderBy[i].desc);
}
p->estimatedCost = (double)500;
p->estimatedRows = 500;
printf(" idxNum=%d\n", p->idxNum);
printf(" idxStr=NULL\n");
printf(" orderByConsumed=%d\n", p->orderByConsumed);
printf(" estimatedCost=%g\n", p->estimatedCost);
printf(" estimatedRows=%lld\n", p->estimatedRows);
return SQLITE_OK;
}
@ -454,7 +492,7 @@ static int vtablogUpdate(
){
vtablog_vtab *pTab = (vtablog_vtab*)tab;
int i;
printf("vtablogUpdate(tab=%d):\n", pTab->iInst);
printf("%s.%s.xUpdate():\n", pTab->zDb, pTab->zName);
printf(" argc=%d\n", argc);
for(i=0; i<argc; i++){
printf(" argv[%d]=", i);
@ -464,12 +502,88 @@ static int vtablogUpdate(
return SQLITE_OK;
}
static int vtablogBegin(sqlite3_vtab *tab){
vtablog_vtab *pTab = (vtablog_vtab*)tab;
printf("%s.%s.xBegin()\n", pTab->zDb, pTab->zName);
return SQLITE_OK;
}
static int vtablogSync(sqlite3_vtab *tab){
vtablog_vtab *pTab = (vtablog_vtab*)tab;
printf("%s.%s.xSync()\n", pTab->zDb, pTab->zName);
return SQLITE_OK;
}
static int vtablogCommit(sqlite3_vtab *tab){
vtablog_vtab *pTab = (vtablog_vtab*)tab;
printf("%s.%s.xCommit()\n", pTab->zDb, pTab->zName);
return SQLITE_OK;
}
static int vtablogRollback(sqlite3_vtab *tab){
vtablog_vtab *pTab = (vtablog_vtab*)tab;
printf("%s.%s.xRollback()\n", pTab->zDb, pTab->zName);
return SQLITE_OK;
}
static int vtablogSavepoint(sqlite3_vtab *tab, int N){
vtablog_vtab *pTab = (vtablog_vtab*)tab;
printf("%s.%s.xSavepoint(%d)\n", pTab->zDb, pTab->zName, N);
return SQLITE_OK;
}
static int vtablogRelease(sqlite3_vtab *tab, int N){
vtablog_vtab *pTab = (vtablog_vtab*)tab;
printf("%s.%s.xRelease(%d)\n", pTab->zDb, pTab->zName, N);
return SQLITE_OK;
}
static int vtablogRollbackTo(sqlite3_vtab *tab, int N){
vtablog_vtab *pTab = (vtablog_vtab*)tab;
printf("%s.%s.xRollbackTo(%d)\n", pTab->zDb, pTab->zName, N);
return SQLITE_OK;
}
static int vtablogFindMethod(
sqlite3_vtab *tab,
int nArg,
const char *zName,
void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
void **ppArg
){
vtablog_vtab *pTab = (vtablog_vtab*)tab;
printf("%s.%s.xFindMethod(nArg=%d, zName=%s)\n",
pTab->zDb, pTab->zName, nArg, zName);
return SQLITE_OK;
}
static int vtablogRename(sqlite3_vtab *tab, const char *zNew){
vtablog_vtab *pTab = (vtablog_vtab*)tab;
printf("%s.%s.xRename('%s')\n", pTab->zDb, pTab->zName, zNew);
sqlite3_free(pTab->zName);
pTab->zName = sqlite3_mprintf("%s", zNew);
return SQLITE_OK;
}
/* Any table name that contains the text "shadow" is seen as a
** shadow table. Nothing else is.
*/
static int vtablogShadowName(const char *zName){
printf("vtablog.xShadowName('%s')\n", zName);
return sqlite3_strglob("*shadow*", zName)==0;
}
static int vtablogIntegrity(
sqlite3_vtab *tab,
const char *zSchema,
const char *zTabName,
int mFlags,
char **pzErr
){
vtablog_vtab *pTab = (vtablog_vtab*)tab;
printf("%s.%s.xIntegrity(mFlags=0x%x)\n", pTab->zDb, pTab->zName, mFlags);
return 0;
}
/*
** This following structure defines all the methods for the
** vtablog virtual table.
*/
static sqlite3_module vtablogModule = {
0, /* iVersion */
4, /* iVersion */
vtablogCreate, /* xCreate */
vtablogConnect, /* xConnect */
vtablogBestIndex, /* xBestIndex */
@ -483,17 +597,17 @@ static sqlite3_module vtablogModule = {
vtablogColumn, /* xColumn - read data */
vtablogRowid, /* xRowid - read data */
vtablogUpdate, /* xUpdate */
0, /* xBegin */
0, /* xSync */
0, /* xCommit */
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
0, /* xShadowName */
0 /* xIntegrity */
vtablogBegin, /* xBegin */
vtablogSync, /* xSync */
vtablogCommit, /* xCommit */
vtablogRollback, /* xRollback */
vtablogFindMethod, /* xFindMethod */
vtablogRename, /* xRename */
vtablogSavepoint, /* xSavepoint */
vtablogRelease, /* xRelease */
vtablogRollbackTo, /* xRollbackTo */
vtablogShadowName, /* xShadowName */
vtablogIntegrity /* xIntegrity */
};
#ifdef _WIN32

View File

@ -199,6 +199,7 @@ typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;
typedef sqlite3_int64 i64;
typedef sqlite3_uint64 u64;
#endif
/*
@ -885,6 +886,7 @@ static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){
if( rc!=SQLITE_ROW ){
rc = resetAndCollectError(pIter->pTblIter, &p->zErrmsg);
pIter->zTbl = 0;
pIter->zDataTbl = 0;
}else{
pIter->zTbl = (const char*)sqlite3_column_text(pIter->pTblIter, 0);
pIter->zDataTbl = (const char*)sqlite3_column_text(pIter->pTblIter,1);
@ -2979,7 +2981,7 @@ static i64 rbuShmChecksum(sqlite3rbu *p){
u32 volatile *ptr;
p->rc = pDb->pMethods->xShmMap(pDb, 0, 32*1024, 0, (void volatile**)&ptr);
if( p->rc==SQLITE_OK ){
iRet = ((i64)ptr[10] << 32) + ptr[11];
iRet = (i64)(((u64)ptr[10] << 32) + ptr[11]);
}
}
return iRet;

View File

@ -88,6 +88,15 @@ typedef unsigned int u32;
typedef struct DbdataTable DbdataTable;
typedef struct DbdataCursor DbdataCursor;
typedef struct DbdataBuffer DbdataBuffer;
/*
** Buffer type.
*/
struct DbdataBuffer {
u8 *aBuf;
sqlite3_int64 nBuf;
};
/* Cursor object */
struct DbdataCursor {
@ -104,7 +113,7 @@ struct DbdataCursor {
sqlite3_int64 iRowid;
/* Only for the sqlite_dbdata table */
u8 *pRec; /* Buffer containing current record */
DbdataBuffer rec;
sqlite3_int64 nRec; /* Size of pRec[] in bytes */
sqlite3_int64 nHdr; /* Size of header in bytes */
int iField; /* Current field number */
@ -149,6 +158,31 @@ struct DbdataTable {
" schema TEXT HIDDEN" \
")"
/*
** Ensure the buffer passed as the first argument is at least nMin bytes
** in size. If an error occurs while attempting to resize the buffer,
** SQLITE_NOMEM is returned. Otherwise, SQLITE_OK.
*/
static int dbdataBufferSize(DbdataBuffer *pBuf, sqlite3_int64 nMin){
if( nMin>pBuf->nBuf ){
sqlite3_int64 nNew = nMin+16384;
u8 *aNew = (u8*)sqlite3_realloc64(pBuf->aBuf, nNew);
if( aNew==0 ) return SQLITE_NOMEM;
pBuf->aBuf = aNew;
pBuf->nBuf = nNew;
}
return SQLITE_OK;
}
/*
** Release the allocation managed by buffer pBuf.
*/
static void dbdataBufferFree(DbdataBuffer *pBuf){
sqlite3_free(pBuf->aBuf);
memset(pBuf, 0, sizeof(*pBuf));
}
/*
** Connect to an sqlite_dbdata (pAux==0) or sqlite_dbptr (pAux!=0) virtual
** table.
@ -289,9 +323,9 @@ static void dbdataResetCursor(DbdataCursor *pCsr){
pCsr->iField = 0;
pCsr->bOnePage = 0;
sqlite3_free(pCsr->aPage);
sqlite3_free(pCsr->pRec);
pCsr->pRec = 0;
dbdataBufferFree(&pCsr->rec);
pCsr->aPage = 0;
pCsr->nRec = 0;
}
/*
@ -433,62 +467,74 @@ static void dbdataValue(
u8 *pData,
sqlite3_int64 nData
){
if( eType>=0 && dbdataValueBytes(eType)<=nData ){
switch( eType ){
case 0:
case 10:
case 11:
sqlite3_result_null(pCtx);
break;
case 8:
sqlite3_result_int(pCtx, 0);
break;
case 9:
sqlite3_result_int(pCtx, 1);
break;
case 1: case 2: case 3: case 4: case 5: case 6: case 7: {
sqlite3_uint64 v = (signed char)pData[0];
pData++;
switch( eType ){
case 7:
case 6: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2;
case 5: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2;
case 4: v = (v<<8) + pData[0]; pData++;
case 3: v = (v<<8) + pData[0]; pData++;
case 2: v = (v<<8) + pData[0]; pData++;
}
if( eType==7 ){
double r;
memcpy(&r, &v, sizeof(r));
sqlite3_result_double(pCtx, r);
}else{
sqlite3_result_int64(pCtx, (sqlite3_int64)v);
}
break;
}
default: {
int n = ((eType-12) / 2);
if( eType % 2 ){
switch( enc ){
#ifndef SQLITE_OMIT_UTF16
case SQLITE_UTF16BE:
sqlite3_result_text16be(pCtx, (void*)pData, n, SQLITE_TRANSIENT);
break;
case SQLITE_UTF16LE:
sqlite3_result_text16le(pCtx, (void*)pData, n, SQLITE_TRANSIENT);
break;
#endif
default:
sqlite3_result_text(pCtx, (char*)pData, n, SQLITE_TRANSIENT);
break;
if( eType>=0 ){
if( dbdataValueBytes(eType)<=nData ){
switch( eType ){
case 0:
case 10:
case 11:
sqlite3_result_null(pCtx);
break;
case 8:
sqlite3_result_int(pCtx, 0);
break;
case 9:
sqlite3_result_int(pCtx, 1);
break;
case 1: case 2: case 3: case 4: case 5: case 6: case 7: {
sqlite3_uint64 v = (signed char)pData[0];
pData++;
switch( eType ){
case 7:
case 6: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2;
case 5: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2;
case 4: v = (v<<8) + pData[0]; pData++;
case 3: v = (v<<8) + pData[0]; pData++;
case 2: v = (v<<8) + pData[0]; pData++;
}
}else{
sqlite3_result_blob(pCtx, pData, n, SQLITE_TRANSIENT);
if( eType==7 ){
double r;
memcpy(&r, &v, sizeof(r));
sqlite3_result_double(pCtx, r);
}else{
sqlite3_result_int64(pCtx, (sqlite3_int64)v);
}
break;
}
default: {
int n = ((eType-12) / 2);
if( eType % 2 ){
switch( enc ){
#ifndef SQLITE_OMIT_UTF16
case SQLITE_UTF16BE:
sqlite3_result_text16be(pCtx, (void*)pData, n, SQLITE_TRANSIENT);
break;
case SQLITE_UTF16LE:
sqlite3_result_text16le(pCtx, (void*)pData, n, SQLITE_TRANSIENT);
break;
#endif
default:
sqlite3_result_text(pCtx, (char*)pData, n, SQLITE_TRANSIENT);
break;
}
}else{
sqlite3_result_blob(pCtx, pData, n, SQLITE_TRANSIENT);
}
}
}
}else{
if( eType==7 ){
sqlite3_result_double(pCtx, 0.0);
}else if( eType<7 ){
sqlite3_result_int(pCtx, 0);
}else if( eType%2 ){
sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC);
}else{
sqlite3_result_blob(pCtx, "", 0, SQLITE_STATIC);
}
}
}
@ -551,7 +597,8 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
}
}else{
/* If there is no record loaded, load it now. */
if( pCsr->pRec==0 ){
assert( pCsr->rec.aBuf!=0 || pCsr->nRec==0 );
if( pCsr->nRec==0 ){
int bHasRowid = 0;
int nPointer = 0;
sqlite3_int64 nPayload = 0;
@ -595,6 +642,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
}else{
iOff += dbdataGetVarintU32(&pCsr->aPage[iOff], &nPayload);
if( nPayload>0x7fffff00 ) nPayload &= 0x3fff;
if( nPayload==0 ) nPayload = 1;
}
/* If this is a leaf intkey cell, load the rowid */
@ -629,13 +677,12 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
/* Allocate space for payload. And a bit more to catch small buffer
** overruns caused by attempting to read a varint or similar from
** near the end of a corrupt record. */
pCsr->pRec = (u8*)sqlite3_malloc64(nPayload+DBDATA_PADDING_BYTES);
if( pCsr->pRec==0 ) return SQLITE_NOMEM;
memset(pCsr->pRec, 0, nPayload+DBDATA_PADDING_BYTES);
pCsr->nRec = nPayload;
rc = dbdataBufferSize(&pCsr->rec, nPayload+DBDATA_PADDING_BYTES);
if( rc!=SQLITE_OK ) return rc;
assert( nPayload!=0 );
/* Load the nLocal bytes of payload */
memcpy(pCsr->pRec, &pCsr->aPage[iOff], nLocal);
memcpy(pCsr->rec.aBuf, &pCsr->aPage[iOff], nLocal);
iOff += nLocal;
/* Load content from overflow pages */
@ -653,19 +700,22 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
nCopy = U-4;
if( nCopy>nRem ) nCopy = nRem;
memcpy(&pCsr->pRec[nPayload-nRem], &aOvfl[4], nCopy);
memcpy(&pCsr->rec.aBuf[nPayload-nRem], &aOvfl[4], nCopy);
nRem -= nCopy;
pgnoOvfl = get_uint32(aOvfl);
sqlite3_free(aOvfl);
}
nPayload -= nRem;
}
memset(&pCsr->rec.aBuf[nPayload], 0, DBDATA_PADDING_BYTES);
pCsr->nRec = nPayload;
iHdr = dbdataGetVarintU32(pCsr->pRec, &nHdr);
iHdr = dbdataGetVarintU32(pCsr->rec.aBuf, &nHdr);
if( nHdr>nPayload ) nHdr = 0;
pCsr->nHdr = nHdr;
pCsr->pHdrPtr = &pCsr->pRec[iHdr];
pCsr->pPtr = &pCsr->pRec[pCsr->nHdr];
pCsr->pHdrPtr = &pCsr->rec.aBuf[iHdr];
pCsr->pPtr = &pCsr->rec.aBuf[pCsr->nHdr];
pCsr->iField = (bHasRowid ? -1 : 0);
}
}
@ -673,7 +723,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
pCsr->iField++;
if( pCsr->iField>0 ){
sqlite3_int64 iType;
if( pCsr->pHdrPtr>=&pCsr->pRec[pCsr->nRec]
if( pCsr->pHdrPtr>=&pCsr->rec.aBuf[pCsr->nRec]
|| pCsr->iField>=DBDATA_MX_FIELD
){
bNextPage = 1;
@ -681,8 +731,8 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
int szField = 0;
pCsr->pHdrPtr += dbdataGetVarintU32(pCsr->pHdrPtr, &iType);
szField = dbdataValueBytes(iType);
if( (pCsr->nRec - (pCsr->pPtr - pCsr->pRec))<szField ){
pCsr->pPtr = &pCsr->pRec[pCsr->nRec];
if( (pCsr->nRec - (pCsr->pPtr - pCsr->rec.aBuf))<szField ){
pCsr->pPtr = &pCsr->rec.aBuf[pCsr->nRec];
}else{
pCsr->pPtr += szField;
}
@ -692,20 +742,18 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
if( bNextPage ){
sqlite3_free(pCsr->aPage);
sqlite3_free(pCsr->pRec);
pCsr->aPage = 0;
pCsr->pRec = 0;
pCsr->nRec = 0;
if( pCsr->bOnePage ) return SQLITE_OK;
pCsr->iPgno++;
}else{
if( pCsr->iField<0 || pCsr->pHdrPtr<&pCsr->pRec[pCsr->nHdr] ){
if( pCsr->iField<0 || pCsr->pHdrPtr<&pCsr->rec.aBuf[pCsr->nHdr] ){
return SQLITE_OK;
}
/* Advance to the next cell. The next iteration of the loop will load
** the record and so on. */
sqlite3_free(pCsr->pRec);
pCsr->pRec = 0;
pCsr->nRec = 0;
pCsr->iCell++;
}
}
@ -895,12 +943,12 @@ static int dbdataColumn(
case DBDATA_COLUMN_VALUE: {
if( pCsr->iField<0 ){
sqlite3_result_int64(ctx, pCsr->iIntkey);
}else if( &pCsr->pRec[pCsr->nRec] >= pCsr->pPtr ){
}else if( &pCsr->rec.aBuf[pCsr->nRec] >= pCsr->pPtr ){
sqlite3_int64 iType;
dbdataGetVarintU32(pCsr->pHdrPtr, &iType);
dbdataValue(
ctx, pCsr->enc, iType, pCsr->pPtr,
&pCsr->pRec[pCsr->nRec] - pCsr->pPtr
&pCsr->rec.aBuf[pCsr->nRec] - pCsr->pPtr
);
}
break;

View File

@ -342,7 +342,7 @@ foreach enc {utf8 utf16 utf16le utf16be} {
DELETE FROM sqlite_schema WHERE name='t1';
}
proc my_sql_hook {sql} {
proc my_sql_hook2 {sql} {
if {[string match "INSERT INTO lostandfound*" $sql]} {
lappend ::script $sql
}
@ -350,7 +350,7 @@ foreach enc {utf8 utf16 utf16le utf16be} {
}
do_test 18.$enc.2 {
set ::script [list]
set R [sqlite3_recover_init_sql db main my_sql_hook]
set R [sqlite3_recover_init_sql db main my_sql_hook2]
$R config lostandfound lostandfound
$R run
$R finish
@ -358,7 +358,18 @@ foreach enc {utf8 utf16 utf16le utf16be} {
} {{INSERT INTO lostandfound VALUES(2, 2, 2, 1, 'abc', 'def')}}
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 19.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
ALTER TABLE t1 ADD COLUMN c NOT NULL DEFAULT 13;
INSERT INTO t1 VALUES(3, 'three', 'hello world');
}
do_recover_test 19.1

View File

@ -0,0 +1,549 @@
# 2024 May 1
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
source [file join [file dirname [info script]] recover_common.tcl]
set testprefix recovercorrupt3
#| 0: d5 d5 9b d5 d5 d5 d5 d5 d5 d5 d5 d5 d5 d5 d5 d5 ................
#| 16: 04 00 00 00 1d 00 00 00 00 00 00 00 5f 5f 5f 5f ............____
#| 32: 5f 5f 5f 5f 5f 5f 5f 5f 71 5f 5f 5f 02 02 02 02 ________q___....
#| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
#| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
#| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
#| 96: 02 02 02 02
#-------------------------------------------------------------------------
reset_db
do_test 1.0 {
sqlite3 db {}
db deserialize [decode_hexdb {
| size 3821 pagesize 1024 filename clusterfuzz-testcase-sql_recovery_fuzzer-5803962339885056
| 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 01 00 00 00 02 .....@ ........
| 32: 00 00 00 00 00 00 00 00 00 00 00 01 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 01 ................
| 96: 00 2e 7a 70 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 352: 02 02 02 02 a0 02 02 02 02 02 02 02 02 02 02 02 ................
| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 29 29 ..............))
| 464: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 480: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 496: 29 29 29 dd dd dd dd dd dd dd dd dd dd dd dd dd ))).............
| 512: dd dd dd dd dd dd dd dd dd 6e 69 d2 e9 e9 e9 d2 .........ni.....
| 528: d2 d2 d2 d2 dd dd dd dd dd dd dd dd dd dd dd dd ................
| 544: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 560: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 576: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 592: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 608: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 624: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 640: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 656: dd dd dd dd dd dd dd da dd dd dd dd dd dd dd dd ................
| 672: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 688: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 704: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 720: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 736: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 752: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 768: dd dd dd dd dd dd dd dd dd dd dd dd dd 29 29 29 .............)))
| 784: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 800: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 816: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 832: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 848: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 864: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 880: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 896: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 912: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 928: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 944: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 960: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 976: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 992: 29 29 29 29 29 29 29 29 29 29 dd dd dd dd dd dd ))))))))))......
| 1008: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| page 2 offset 1024
| 0: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 16: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 32: dd dd 6e 69 d2 e9 e9 e9 d2 d2 d2 d2 d2 dd dd dd ..ni............
| 48: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 64: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 80: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 96: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 112: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 128: dd dd dd dd dd dd 29 29 29 29 29 29 29 29 29 29 ......))))))))))
| 144: 29 29 29 29 29 29 29 29 29 ad a5 29 29 29 29 00 )))))))))..)))).
| 160: 75 9c 11 00 5b e5 64 28 7c ca 09 69 28 2d 69 00 u...[.d(|..i(-i.
| 176: 85 88 6c 81 48 83 a0 93 c0 c0 82 8b 81 84 85 f9 ..l.H...........
| 192: 88 7a 00 7f 00 96 40 7b 12 4b 84 75 a0 00 99 a0 .z....@..K.u....
| 208: df a0 7e 81 c6 90 8f 7f 84 85 cc 84 82 90 88 60 ..~............`
| 224: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 240: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 256: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 272: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 288: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 02 02 ................
| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 736: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 752: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 768: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 784: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 800: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 816: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 832: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 848: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 864: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 880: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 896: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 912: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 928: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 944: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 960: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 976: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 992: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 1008: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| page 3 offset 2048
| 0: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 16: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 32: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 96: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 736: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 752: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 768: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 784: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 800: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 816: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 832: 02 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 848: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 864: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 880: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 896: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 912: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 928: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 944: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 960: 80 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 976: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 992: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 1008: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| page 4 offset 3072
| 0: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 16: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 32: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 96: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 736: 5f 5f 5f 5f 5f 5f 5f 00 d5 fe fe fe 08 00 00 00 _______.........
| end clusterfuzz-testcase-sql_recovery_fuzzer-5803962339885056
}]} {}
sqlite3_dbdata_init db
do_execsql_test 1.1 {
PRAGMA writable_schema = 1;
}
do_test 1.2 {
set R [sqlite3_recover_init db main test.db2]
$R run
$R finish
} {}
#-------------------------------------------------------------------------
reset_db
do_test 2.0 {
sqlite3 db {}
db deserialize [decode_hexdb {
| size 3821 pagesize 1024 filename clusterfuzz-testcase-sql_recovery_fuzzer-5803962339885056
| 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: 04 00 01 01 00 40 20 20 00 00 00 01 00 00 00 02 .....@ ........
| 32: 00 00 00 00 00 00 00 00 00 00 00 01 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 01 ................
| 96: 00 2e 7a 70 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 352: 02 02 02 02 a0 02 02 02 02 02 02 02 02 02 02 02 ................
| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 29 29 ..............))
| 464: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 480: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 496: 29 29 29 dd dd dd dd dd dd dd dd dd dd dd dd dd ))).............
| 512: dd dd dd dd dd dd dd dd dd 6e 69 d2 e9 e9 e9 d2 .........ni.....
| 528: d2 d2 d2 d2 dd dd dd dd dd dd dd dd dd dd dd dd ................
| 544: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 560: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 576: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 592: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 608: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 624: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 640: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 656: dd dd dd dd dd dd dd da dd dd dd dd dd dd dd dd ................
| 672: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 688: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 704: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 720: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 736: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 752: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 768: dd dd dd dd dd dd dd dd dd dd dd dd dd 29 29 29 .............)))
| 784: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 800: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 816: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 832: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 848: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 864: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 880: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 896: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 912: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 928: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 944: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 960: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 976: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 ))))))))))))))))
| 992: 29 29 29 29 29 29 29 29 29 29 dd dd dd dd dd dd ))))))))))......
| 1008: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| page 2 offset 1024
| 0: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 16: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 32: dd dd 6e 69 d2 e9 e9 e9 d2 d2 d2 d2 d2 dd dd dd ..ni............
| 48: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 64: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 80: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 96: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 112: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................
| 128: dd dd dd dd dd dd 29 29 29 29 29 29 29 29 29 29 ......))))))))))
| 144: 29 29 29 29 29 29 29 29 29 ad a5 29 29 29 29 00 )))))))))..)))).
| 160: 75 9c 11 00 5b e5 64 28 7c ca 09 69 28 2d 69 00 u...[.d(|..i(-i.
| 176: 85 88 6c 81 48 83 a0 93 c0 c0 82 8b 81 84 85 f9 ..l.H...........
| 192: 88 7a 00 7f 00 96 40 7b 12 4b 84 75 a0 00 99 a0 .z....@..K.u....
| 208: df a0 7e 81 c6 90 8f 7f 84 85 cc 84 82 90 88 60 ..~............`
| 224: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 240: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 256: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 272: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 288: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 02 02 ................
| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 736: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 752: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 768: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 784: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 800: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 816: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 832: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 848: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 864: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 880: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 896: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 912: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 928: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 944: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 960: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 976: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 992: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 1008: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| page 3 offset 2048
| 0: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 16: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 32: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 96: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 736: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 752: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 768: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 784: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 800: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 816: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 832: 02 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 848: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 864: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 880: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 896: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 912: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 928: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 944: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................
| 960: 80 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 976: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 992: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 1008: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| page 4 offset 3072
| 0: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 16: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 32: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 96: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................
| 736: 5f 5f 5f 5f 5f 5f 5f 00 d5 fe fe fe 08 00 00 00 _______.........
| end clusterfuzz-testcase-sql_recovery_fuzzer-5803962339885056
}]} {}
sqlite3_dbdata_init db
do_execsql_test 2.1 {
PRAGMA writable_schema = 1;
}
do_test 2.2 {
set R [sqlite3_recover_init db main test.db2]
$R run
$R finish
} {}
finish_test

View File

@ -0,0 +1,64 @@
# 2024 May 15
#
# 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]] recover_common.tcl]
set testprefix recovercorrupt4
database_may_be_corrupt
if {[permutation]!="inmemory_journal"} {
# This test cannot be run with the inmemory_journal permutation, as it
# must open a truncated, corrupt, database file. With the inmemory_journal
# permutation, this fails (SQLITE_CORRUPT error) when the [sqlite3] wrapper
# executes "PRAGMA journal_mode = memory".
do_execsql_test 1.0 {
CREATE TABLE rows(indexed INTEGER NOT NULL, unindexed INTEGER NOT NULL, filler BLOB NOT NULL DEFAULT 13);
-- CREATE UNIQUE INDEX rows_index ON rows(indexed);
INSERT INTO rows(indexed, unindexed, filler) VALUES(1, 1, x'31');
INSERT INTO rows(indexed, unindexed, filler) VALUES(2, 2, x'32');
INSERT INTO rows(indexed, unindexed, filler) VALUES(4, 4, x'34');
INSERT INTO rows(indexed, unindexed, filler) VALUES(8, 8, randomblob(2048));
}
db close
do_test 1.1 {
set sz [expr [file size test.db] - 1024]
set fd [open test.db]
fconfigure $fd -encoding binary -translation binary
set data [read $fd $sz]
set fd2 [open test.db2 w]
fconfigure $fd2 -encoding binary -translation binary
puts -nonewline $fd2 $data
close $fd2
set {} {}
} {}
do_test 1.2 {
forcedelete test.db3
sqlite3 db test.db2
set R [sqlite3_recover_init db main test.db3]
$R run
$R finish
} {}
do_test 1.3 {
sqlite3 db test.db3
execsql {
SELECT indexed, unindexed FROM rows
}
} {1 1 2 2 4 4 8 8}
}
finish_test

View File

@ -363,8 +363,8 @@ static int recoverError(
va_start(ap, zFmt);
if( zFmt ){
z = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
}
va_end(ap);
sqlite3_free(p->zErrMsg);
p->zErrMsg = z;
p->errCode = errCode;

View File

@ -1841,6 +1841,8 @@ static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){
return SQLITE_OK;
}
int sqlite3IntFloatCompare(i64,double);
/*
** Rtree virtual table module xFilter method.
*/
@ -1870,7 +1872,8 @@ static int rtreeFilter(
i64 iNode = 0;
int eType = sqlite3_value_numeric_type(argv[0]);
if( eType==SQLITE_INTEGER
|| (eType==SQLITE_FLOAT && sqlite3_value_double(argv[0])==iRowid)
|| (eType==SQLITE_FLOAT
&& 0==sqlite3IntFloatCompare(iRowid,sqlite3_value_double(argv[0])))
){
rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode);
}else{
@ -3225,6 +3228,7 @@ constraint:
*/
static int rtreeBeginTransaction(sqlite3_vtab *pVtab){
Rtree *pRtree = (Rtree *)pVtab;
assert( pRtree->inWrTrans==0 );
pRtree->inWrTrans = 1;
return SQLITE_OK;
}

View File

@ -797,4 +797,22 @@ do_test 23.0 {
db eval {PRAGMA integrity_check;}
} {ok}
reset_db
do_execsql_test 24.0 {
CREATE VIRTUAL TABLE rt1 USING rtree_i32(rid, c1, c2);
INSERT INTO rt1(rid, c1, c2) VALUES (9223372036854775807, 10, 18);
}
do_execsql_test 24.1 {
SELECT (rid = (CAST (9223372036854775807 AS REAL)))
FROM rt1 WHERE
(rid = (CAST (9223372036854775807 AS REAL)));
}
do_execsql_test 24.2 {
DELETE FROM rt1;
INSERT INTO rt1(rid, c1, c2) VALUES(1,2,3);
SELECT * FROM rt1 WHERE rid=1.005;
} {}
finish_test

View File

@ -0,0 +1,101 @@
# 2011 March 07
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
# 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 sessionchange
do_execsql_test 1.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
}
do_test 1.1 {
set C [changeset_from_sql {
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t1 VALUES(10, 20, 30);
}]
set res [list]
set iter [sqlite3changeset_start $C]
while {[$iter next]=="SQLITE_ROW"} {
lappend res [$iter data]
}
$iter finalize
set res
} [list \
{INSERT t1 0 X.. {} {i 10 i 20 i 30}} \
{INSERT t1 0 X.. {} {i 1 i 2 i 3}} \
]
do_test 1.2 {
sqlite3changegroup grp
set iter [sqlite3changeset_start $C]
while {[$iter next]=="SQLITE_ROW"} {
grp add_change $iter
}
$iter finalize
set res [list]
grp output
sqlite3session_foreach c [grp output] { lappend res $c }
grp delete
set res
} [list \
{INSERT t1 0 X.. {} {i 10 i 20 i 30}} \
{INSERT t1 0 X.. {} {i 1 i 2 i 3}} \
]
do_test 1.3.1 {
set iter [sqlite3changeset_start $C]
sqlite3changegroup grp
list [catch { grp add_change $iter } msg] $msg
} {1 SQLITE_ERROR}
do_test 1.3.2 {
while {[$iter next]=="SQLITE_ROW"} { }
list [catch { grp add_change $iter } msg] $msg
} {1 SQLITE_ERROR}
grp delete
$iter finalize
do_test 1.4 {
set res [list]
set iter [sqlite3changeset_start -invert $C]
while {[$iter next]=="SQLITE_ROW"} {
lappend res [$iter data]
}
$iter finalize
set res
} [list \
{DELETE t1 0 X.. {i 10 i 20 i 30} {}} \
{DELETE t1 0 X.. {i 1 i 2 i 3} {}} \
]
do_test 1.5 {
sqlite3changegroup grp
set iter [sqlite3changeset_start -invert $C]
$iter next
list [catch { grp add_change $iter } msg] $msg
} {1 SQLITE_ERROR}
$iter finalize
grp delete
finish_test

View File

@ -0,0 +1,76 @@
# 2011 March 07
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
# 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 sessionconflict
db close
sqlite3_shutdown
test_sqlite3_log log
proc log {code msg} { puts "LOG $code $msg" }
sqlite3 db test.db
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_execsql_test -db db2 1.1 {
INSERT INTO t1 VALUES(6, 6, 6);
}
proc xConflict {args} {
return "ABORT"
}
do_test 1.2 {
set chng [changeset_from_sql {
UPDATE t1 SET b=10, c=10 WHERE a=1;
UPDATE t1 SET b=444 WHERE a=2;
INSERT INTO t1 VALUES(4, 4, 4);
INSERT INTO t1 VALUES(5, 5, 5);
INSERT INTO t1 VALUES(6, 6, 6);
}]
execsql BEGIN db2
set res [list [catch { sqlite3changeset_apply db2 $chng xConflict } msg] $msg]
execsql ROLLBACK db2
set res
} {1 SQLITE_ABORT}
do_execsql_test -db db2 1.3 {
SELECT * FROM t1;
} {
1 1 1
2 2 2
3 3 3
6 6 6
}
finish_test

View File

@ -92,7 +92,7 @@ do_test 2.1 {
} {}
do_execsql_test -db db2 2.2 {
SELECT * FROM sqlite_stat1
SELECT * FROM sqlite_stat1 ORDER BY tbl, idx
} {
t1 sqlite_autoindex_t1_1 {32 1}
t1 t1b {32 4}
@ -104,7 +104,7 @@ do_test 2.3 {
} {}
do_execsql_test -db db2 2.4 {
SELECT * FROM sqlite_stat1
SELECT * FROM sqlite_stat1 ORDER BY tbl, idx;
} {
t1 sqlite_autoindex_t1_1 {32 1}
t1 t1b {32 4}

View File

@ -3685,14 +3685,14 @@ static int sessionChangesetNextOne(
p->rc = sessionInputBuffer(&p->in, 2);
if( p->rc!=SQLITE_OK ) return p->rc;
sessionDiscardData(&p->in);
p->in.iCurrent = p->in.iNext;
/* If the iterator is already at the end of the changeset, return DONE. */
if( p->in.iNext>=p->in.nData ){
return SQLITE_DONE;
}
sessionDiscardData(&p->in);
p->in.iCurrent = p->in.iNext;
op = p->in.aData[p->in.iNext++];
while( op=='T' || op=='P' ){
if( pbNew ) *pbNew = 1;
@ -5427,6 +5427,7 @@ struct sqlite3_changegroup {
int rc; /* Error code */
int bPatch; /* True to accumulate patchsets */
SessionTable *pList; /* List of tables in current patch */
SessionBuffer rec;
sqlite3 *db; /* Configured by changegroup_schema() */
char *zDb; /* Configured by changegroup_schema() */
@ -5725,108 +5726,128 @@ static int sessionChangesetExtendRecord(
}
/*
** Add all changes in the changeset traversed by the iterator passed as
** the first argument to the changegroup hash tables.
** Locate or create a SessionTable object that may be used to add the
** change currently pointed to by iterator pIter to changegroup pGrp.
** If successful, set output variable (*ppTab) to point to the table
** object and return SQLITE_OK. Otherwise, if some error occurs, return
** an SQLite error code and leave (*ppTab) set to NULL.
*/
static int sessionChangesetToHash(
sqlite3_changeset_iter *pIter, /* Iterator to read from */
sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */
int bRebase /* True if hash table is for rebasing */
static int sessionChangesetFindTable(
sqlite3_changegroup *pGrp,
const char *zTab,
sqlite3_changeset_iter *pIter,
SessionTable **ppTab
){
u8 *aRec;
int nRec;
int rc = SQLITE_OK;
SessionTable *pTab = 0;
SessionBuffer rec = {0, 0, 0};
int nTab = (int)strlen(zTab);
u8 *abPK = 0;
int nCol = 0;
while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){
const char *zNew;
int nCol;
int op;
int iHash;
int bIndirect;
SessionChange *pChange;
SessionChange *pExist = 0;
SessionChange **pp;
*ppTab = 0;
sqlite3changeset_pk(pIter, &abPK, &nCol);
/* Ensure that only changesets, or only patchsets, but not a mixture
** of both, are being combined. It is an error to try to combine a
** changeset and a patchset. */
if( pGrp->pList==0 ){
pGrp->bPatch = pIter->bPatchset;
}else if( pIter->bPatchset!=pGrp->bPatch ){
rc = SQLITE_ERROR;
break;
/* Search the list for an existing table */
for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){
if( 0==sqlite3_strnicmp(pTab->zName, zTab, nTab+1) ) break;
}
/* If one was not found above, create a new table now */
if( !pTab ){
SessionTable **ppNew;
pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nTab+1);
if( !pTab ){
return SQLITE_NOMEM;
}
memset(pTab, 0, sizeof(SessionTable));
pTab->nCol = nCol;
pTab->abPK = (u8*)&pTab[1];
memcpy(pTab->abPK, abPK, nCol);
pTab->zName = (char*)&pTab->abPK[nCol];
memcpy(pTab->zName, zTab, nTab+1);
sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect);
if( !pTab || sqlite3_stricmp(zNew, pTab->zName) ){
/* Search the list for a matching table */
int nNew = (int)strlen(zNew);
u8 *abPK;
sqlite3changeset_pk(pIter, &abPK, 0);
for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){
if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break;
}
if( !pTab ){
SessionTable **ppTab;
pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nNew+1);
if( !pTab ){
rc = SQLITE_NOMEM;
break;
}
memset(pTab, 0, sizeof(SessionTable));
pTab->nCol = nCol;
pTab->abPK = (u8*)&pTab[1];
memcpy(pTab->abPK, abPK, nCol);
pTab->zName = (char*)&pTab->abPK[nCol];
memcpy(pTab->zName, zNew, nNew+1);
if( pGrp->db ){
pTab->nCol = 0;
rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb);
if( rc ){
assert( pTab->azCol==0 );
sqlite3_free(pTab);
break;
}
}
/* The new object must be linked on to the end of the list, not
** simply added to the start of it. This is to ensure that the
** tables within the output of sqlite3changegroup_output() are in
** the right order. */
for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext);
*ppTab = pTab;
}
if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){
rc = SQLITE_SCHEMA;
break;
if( pGrp->db ){
pTab->nCol = 0;
rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb);
if( rc ){
assert( pTab->azCol==0 );
sqlite3_free(pTab);
return rc;
}
}
if( nCol<pTab->nCol ){
assert( pGrp->db );
rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, &rec);
if( rc ) break;
aRec = rec.aBuf;
nRec = rec.nBuf;
}
/* The new object must be linked on to the end of the list, not
** simply added to the start of it. This is to ensure that the
** tables within the output of sqlite3changegroup_output() are in
** the right order. */
for(ppNew=&pGrp->pList; *ppNew; ppNew=&(*ppNew)->pNext);
*ppNew = pTab;
}
if( sessionGrowHash(0, pIter->bPatchset, pTab) ){
rc = SQLITE_NOMEM;
break;
}
/* Check that the table is compatible. */
if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){
rc = SQLITE_SCHEMA;
}
*ppTab = pTab;
return rc;
}
/*
** Add the change currently indicated by iterator pIter to the hash table
** belonging to changegroup pGrp.
*/
static int sessionOneChangeToHash(
sqlite3_changegroup *pGrp,
sqlite3_changeset_iter *pIter,
int bRebase
){
int rc = SQLITE_OK;
int nCol = 0;
int op = 0;
int iHash = 0;
int bIndirect = 0;
SessionChange *pChange = 0;
SessionChange *pExist = 0;
SessionChange **pp = 0;
SessionTable *pTab = 0;
u8 *aRec = &pIter->in.aData[pIter->in.iCurrent + 2];
int nRec = (pIter->in.iNext - pIter->in.iCurrent) - 2;
/* Ensure that only changesets, or only patchsets, but not a mixture
** of both, are being combined. It is an error to try to combine a
** changeset and a patchset. */
if( pGrp->pList==0 ){
pGrp->bPatch = pIter->bPatchset;
}else if( pIter->bPatchset!=pGrp->bPatch ){
rc = SQLITE_ERROR;
}
if( rc==SQLITE_OK ){
const char *zTab = 0;
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
rc = sessionChangesetFindTable(pGrp, zTab, pIter, &pTab);
}
if( rc==SQLITE_OK && nCol<pTab->nCol ){
SessionBuffer *pBuf = &pGrp->rec;
rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, pBuf);
aRec = pBuf->aBuf;
nRec = pBuf->nBuf;
assert( pGrp->db );
}
if( rc==SQLITE_OK && sessionGrowHash(0, pIter->bPatchset, pTab) ){
rc = SQLITE_NOMEM;
}
if( rc==SQLITE_OK ){
/* Search for existing entry. If found, remove it from the hash table.
** Code below may link it back in. */
iHash = sessionChangeHash(
pTab, (pIter->bPatchset && op==SQLITE_DELETE), aRec, pTab->nChange
);
/* Search for existing entry. If found, remove it from the hash table.
** Code below may link it back in.
*/
for(pp=&pTab->apChange[iHash]; *pp; pp=&(*pp)->pNext){
int bPkOnly1 = 0;
int bPkOnly2 = 0;
@ -5841,19 +5862,41 @@ static int sessionChangesetToHash(
break;
}
}
}
if( rc==SQLITE_OK ){
rc = sessionChangeMerge(pTab, bRebase,
pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange
);
if( rc ) break;
if( pChange ){
pChange->pNext = pTab->apChange[iHash];
pTab->apChange[iHash] = pChange;
pTab->nEntry++;
}
}
if( rc==SQLITE_OK && pChange ){
pChange->pNext = pTab->apChange[iHash];
pTab->apChange[iHash] = pChange;
pTab->nEntry++;
}
if( rc==SQLITE_OK ) rc = pIter->rc;
return rc;
}
/*
** Add all changes in the changeset traversed by the iterator passed as
** the first argument to the changegroup hash tables.
*/
static int sessionChangesetToHash(
sqlite3_changeset_iter *pIter, /* Iterator to read from */
sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */
int bRebase /* True if hash table is for rebasing */
){
u8 *aRec;
int nRec;
int rc = SQLITE_OK;
while( SQLITE_ROW==(sessionChangesetNext(pIter, &aRec, &nRec, 0)) ){
rc = sessionOneChangeToHash(pGrp, pIter, bRebase);
if( rc!=SQLITE_OK ) break;
}
sqlite3_free(rec.aBuf);
if( rc==SQLITE_OK ) rc = pIter->rc;
return rc;
}
@ -5981,6 +6024,23 @@ int sqlite3changegroup_add(sqlite3_changegroup *pGrp, int nData, void *pData){
return rc;
}
/*
** Add a single change to a changeset-group.
*/
int sqlite3changegroup_add_change(
sqlite3_changegroup *pGrp,
sqlite3_changeset_iter *pIter
){
if( pIter->in.iCurrent==pIter->in.iNext
|| pIter->rc!=SQLITE_OK
|| pIter->bInvert
){
/* Iterator does not point to any valid entry or is an INVERT iterator. */
return SQLITE_ERROR;
}
return sessionOneChangeToHash(pGrp, pIter, 0);
}
/*
** Obtain a buffer containing a changeset representing the concatenation
** of all changesets added to the group so far.
@ -6030,6 +6090,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){
if( pGrp ){
sqlite3_free(pGrp->zDb);
sessionDeleteTable(0, pGrp->pList);
sqlite3_free(pGrp->rec.aBuf);
sqlite3_free(pGrp);
}
}
@ -6431,6 +6492,7 @@ int sqlite3rebaser_rebase_strm(
void sqlite3rebaser_delete(sqlite3_rebaser *p){
if( p ){
sessionDeleteTable(0, p->grp.pList);
sqlite3_free(p->grp.rec.aBuf);
sqlite3_free(p);
}
}

View File

@ -1057,6 +1057,30 @@ int sqlite3changegroup_schema(sqlite3_changegroup*, sqlite3*, const char *zDb);
*/
int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
/*
** CAPI3REF: Add A Single Change To A Changegroup
** METHOD: sqlite3_changegroup
**
** This function adds the single change currently indicated by the iterator
** passed as the second argument to the changegroup object. The rules for
** adding the change are just as described for [sqlite3changegroup_add()].
**
** If the change is successfully added to the changegroup, SQLITE_OK is
** returned. Otherwise, an SQLite error code is returned.
**
** The iterator must point to a valid entry when this function is called.
** If it does not, SQLITE_ERROR is returned and no change is added to the
** changegroup. Additionally, the iterator must not have been opened with
** the SQLITE_CHANGESETAPPLY_INVERT flag. In this case SQLITE_ERROR is also
** returned.
*/
int sqlite3changegroup_add_change(
sqlite3_changegroup*,
sqlite3_changeset_iter*
);
/*
** CAPI3REF: Obtain A Composite Changeset From A Changegroup
** METHOD: sqlite3_changegroup

View File

@ -1038,6 +1038,64 @@ static int SQLITE_TCLAPI test_sqlite3changeset_concat(
return rc;
}
static Tcl_Obj *testIterData(sqlite3_changeset_iter *pIter){
Tcl_Obj *pVar = 0;
int nCol; /* Number of columns in table */
int nCol2; /* Number of columns in table */
int op; /* SQLITE_INSERT, UPDATE or DELETE */
const char *zTab; /* Name of table change applies to */
Tcl_Obj *pOld; /* Vector of old.* values */
Tcl_Obj *pNew; /* Vector of new.* values */
int bIndirect;
char *zPK;
unsigned char *abPK;
int i;
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
pVar = Tcl_NewObj();
Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
op==SQLITE_INSERT ? "INSERT" :
op==SQLITE_UPDATE ? "UPDATE" :
"DELETE", -1
));
Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
zPK = ckalloc(nCol+1);
memset(zPK, 0, nCol+1);
sqlite3changeset_pk(pIter, &abPK, &nCol2);
assert( nCol==nCol2 );
for(i=0; i<nCol; i++){
zPK[i] = (abPK[i] ? 'X' : '.');
}
Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1));
ckfree(zPK);
pOld = Tcl_NewObj();
if( op!=SQLITE_INSERT ){
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
sqlite3changeset_old(pIter, i, &pVal);
test_append_value(pOld, pVal);
}
}
pNew = Tcl_NewObj();
if( op!=SQLITE_DELETE ){
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
sqlite3changeset_new(pIter, i, &pVal);
test_append_value(pNew, pVal);
}
}
Tcl_ListObjAppendElement(0, pVar, pOld);
Tcl_ListObjAppendElement(0, pVar, pNew);
return pVar;
}
/*
** sqlite3session_foreach VARNAME CHANGESET SCRIPT
*/
@ -1111,67 +1169,8 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach(
}
while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
int nCol; /* Number of columns in table */
int nCol2; /* Number of columns in table */
int op; /* SQLITE_INSERT, UPDATE or DELETE */
const char *zTab; /* Name of table change applies to */
Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */
Tcl_Obj *pOld; /* Vector of old.* values */
Tcl_Obj *pNew; /* Vector of new.* values */
int bIndirect;
char *zPK;
unsigned char *abPK;
int i;
/* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this
** iterator. */
int nDummy;
if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){
sqlite3changeset_finalize(pIter);
return TCL_ERROR;
}
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
pVar = Tcl_NewObj();
Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
op==SQLITE_INSERT ? "INSERT" :
op==SQLITE_UPDATE ? "UPDATE" :
"DELETE", -1
));
Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
zPK = ckalloc(nCol+1);
memset(zPK, 0, nCol+1);
sqlite3changeset_pk(pIter, &abPK, &nCol2);
assert( nCol==nCol2 );
for(i=0; i<nCol; i++){
zPK[i] = (abPK[i] ? 'X' : '.');
}
Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1));
ckfree(zPK);
pOld = Tcl_NewObj();
if( op!=SQLITE_INSERT ){
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
sqlite3changeset_old(pIter, i, &pVal);
test_append_value(pOld, pVal);
}
}
pNew = Tcl_NewObj();
if( op!=SQLITE_DELETE ){
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
sqlite3changeset_new(pIter, i, &pVal);
test_append_value(pNew, pVal);
}
}
Tcl_ListObjAppendElement(0, pVar, pOld);
Tcl_ListObjAppendElement(0, pVar, pNew);
Tcl_Obj *pVar = 0; /* Tcl value to set $VARNAME to */
pVar = testIterData(pIter);
Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
rc = Tcl_EvalObjEx(interp, pScript, 0);
if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
@ -1459,6 +1458,12 @@ struct TestChangegroup {
sqlite3_changegroup *pGrp;
};
typedef struct TestChangeIter TestChangeIter;
struct TestChangeIter {
sqlite3_changeset_iter *pIter;
};
/*
** Destructor for Tcl changegroup command object.
*/
@ -1491,6 +1496,7 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
{ "add", 1, "CHANGESET", }, /* 1 */
{ "output", 0, "", }, /* 2 */
{ "delete", 0, "", }, /* 3 */
{ "add_change", 1, "ITERATOR", }, /* 4 */
{ 0 }
};
int rc = TCL_OK;
@ -1542,6 +1548,24 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
break;
};
case 4: { /* add_change */
Tcl_CmdInfo cmdInfo; /* Database Tcl command (objv[2]) info */
TestChangeIter *pIter = 0;
const char *zIter = Tcl_GetString(objv[2]);
if( 0==Tcl_GetCommandInfo(interp, zIter, &cmdInfo) ){
Tcl_AppendResult(interp, "no such iter: ", Tcl_GetString(objv[2]), 0);
return TCL_ERROR;
}
pIter = (struct TestChangeIter*)cmdInfo.objClientData;
rc = sqlite3changegroup_add_change(p->pGrp, pIter->pIter);
if( rc!=SQLITE_OK ){
rc = test_session_error(interp, rc, 0);
}
break;
};
default: { /* delete */
assert( iSub==3 );
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
@ -1585,6 +1609,115 @@ static int SQLITE_TCLAPI test_sqlite3changegroup(
return TCL_OK;
}
extern const char *sqlite3ErrName(int);
/*
** Destructor for Tcl iterator command object.
*/
static void test_iter_del(void *clientData){
TestChangeIter *p = (TestChangeIter*)clientData;
sqlite3changeset_finalize(p->pIter);
ckfree(p);
}
static int SQLITE_TCLAPI test_iter_cmd(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
static const char *aSub[] = {
"next", /* 0 */
"data", /* 1 */
"finalize", /* 2 */
0
};
int iSub = 0;
TestChangeIter *p = (TestChangeIter*)clientData;
int rc = SQLITE_OK;
if( objc<2 ){
Tcl_WrongNumArgs(interp, 1, objv, "CMD");
return TCL_ERROR;
}
if( Tcl_GetIndexFromObj(interp, objv[1], aSub, "sub-command", 0, &iSub) ){
return TCL_ERROR;
}
switch( iSub ){
case 0:
rc = sqlite3changeset_next(p->pIter);
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
break;
case 1:
Tcl_SetObjResult(interp, testIterData(p->pIter));
break;
case 2:
rc = sqlite3changeset_finalize(p->pIter);
p->pIter = 0;
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
break;
default:
assert( 0 );
break;
}
return TCL_OK;
}
/*
** Tclcmd: sqlite3changeset_start ?-invert? CHANGESET
*/
static int SQLITE_TCLAPI test_sqlite3changeset_start(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
int isInvert = 0;
void *pChangeset = 0; /* Buffer containing changeset */
int nChangeset = 0; /* Size of buffer aChangeset in bytes */
TestChangeIter *pNew = 0;
sqlite3_changeset_iter *pIter = 0;
int flags = 0;
int rc = SQLITE_OK;
static int iCmd = 1;
char zCmd[64];
if( objc==3 ){
int n = 0;
const char *z = Tcl_GetStringFromObj(objv[1], &n);
isInvert = (n>=2 && sqlite3_strnicmp(z, "-invert", n)==0);
}
if( objc!=2 && (objc!=3 || !isInvert) ){
Tcl_WrongNumArgs(interp, 1, objv, "?-invert? CHANGESET");
return TCL_ERROR;
}
flags = isInvert ? SQLITE_CHANGESETSTART_INVERT : 0;
pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[objc-1], &nChangeset);
rc = sqlite3changeset_start_v2(&pIter, nChangeset, pChangeset, flags);
if( rc!=SQLITE_OK ){
char *zErr = sqlite3_mprintf(
"error in sqlite3changeset_start_v2() - %d", rc
);
Tcl_AppendResult(interp, zErr, (char*)0);
return TCL_ERROR;
}
pNew = (TestChangeIter*)ckalloc(sizeof(TestChangeIter));
pNew->pIter = pIter;
sprintf(zCmd, "csiter%d", iCmd++);
Tcl_CreateObjCommand(interp, zCmd, test_iter_cmd, (void*)pNew, test_iter_del);
Tcl_SetObjResult(interp, Tcl_NewStringObj(zCmd, -1));
return TCL_OK;
}
int TestSession_Init(Tcl_Interp *interp){
struct Cmd {
const char *zCmd;
@ -1592,6 +1725,7 @@ int TestSession_Init(Tcl_Interp *interp){
} aCmd[] = {
{ "sqlite3session", test_sqlite3session },
{ "sqlite3changegroup", test_sqlite3changegroup },
{ "sqlite3changeset_start", test_sqlite3changeset_start },
{ "sqlite3session_foreach", test_sqlite3session_foreach },
{ "sqlite3changeset_invert", test_sqlite3changeset_invert },
{ "sqlite3changeset_concat", test_sqlite3changeset_concat },

View File

@ -1,3 +1,15 @@
*********************************** NOTICE ************************************
* This extension is deprecated. The SQLite developers do not maintain this *
* extension. At some point in the future, it might disappear from the source *
* tree. *
* *
* If you are using this extension and think it should be supported moving *
* forward, visit the SQLite Forum (https://sqlite.org/forum) and argue your *
* case there. *
* *
* This deprecation notice was added on 2024-01-22. *
*******************************************************************************
Activate the user authentication logic by including the
ext/userauth/userauth.c source code file in the build and
adding the -DSQLITE_USER_AUTHENTICATION compile-time option.

View File

@ -43,8 +43,9 @@
# which generates the makefile code, rather than using $(call) and
# $(eval), or at least centralize the setup of the numerous vars
# related to each build variant $(JS_BUILD_MODES). (Update: an
# external script was attempted but it's even less legible than the
# $(eval) indirection going on in this file.
# external script was attempted but generating properly-escaped
# makefile code from within a shell script is even less legible
# than the $(eval) indirection going on in this file.)
#
default: all
#default: quick
@ -288,6 +289,12 @@ endif
# embedding in the JS files and in building the distribution zip file.
# It must NOT be in $(dir.tmp) because we need it to survive the
# cleanup process for the dist build to work properly.
#
# Slight caveat: this uses the version info from the in-tree
# sqlite3.c/h, which may diff from a user-provided $(sqlite3.c). The
# end result is that the generated JS files may have static version
# info from $(bin.version-info) which differ from their runtime-emited
# version info (e.g. from sqlite3_libversion()).
bin.version-info := $(dir.top)/version-info
.NOTPARALLEL: $(bin.version-info)
$(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile
@ -306,8 +313,9 @@ DISTCLEAN_FILES += $(bin.stripccomments)
########################################################################
# C-PP.FILTER: a $(call)able to transform $(1) to $(2) via ./c-pp -f
# $(1) ...
# C-PP.FILTER: a $(call)able to transform $(1) to $(2) via:
#
# ./c-pp -f $(1) -o $(2) $(3)
#
# Historical notes:
#
@ -333,19 +341,26 @@ DISTCLEAN_FILES += $(bin.stripccomments)
#
# Note that the SQLITE_... build flags used here have NO EFFECT on the
# JS/WASM build. They are solely for use with $(bin.c-pp) itself.
#
# -D... flags which should be included in all invocations should be
# appended to $(C-PP.FILTER.global).
bin.c-pp := ./c-pp
$(bin.c-pp): c-pp.c $(sqlite3.c) $(MAKEFILE)
$(CC) -O0 -o $@ c-pp.c $(sqlite3.c) '-DCMPP_DEFAULT_DELIM="//#"' -I$(dir.top) \
-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_UTF16 \
-DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_WAL -DSQLITE_THREADSAFE=0 \
-DSQLITE_TEMP_STORE=3
C-PP.FILTER.global ?=
ifeq (1,$(SQLITE_C_IS_SEE))
C-PP.FILTER.global += -Denable-see
endif
define C-PP.FILTER
# Create $2 from $1 using $(bin.c-pp)
# $1 = Input file: c-pp -f $(1).js
# $2 = Output file: c-pp -o $(2).js
# $3 = optional c-pp -D... flags
$(2): $(1) $$(MAKEFILE) $$(bin.c-pp)
$$(bin.c-pp) -f $(1) -o $$@ $(3)
$$(bin.c-pp) -f $(1) -o $$@ $(3) $(C-PP.FILTER.global)
CLEAN_FILES += $(2)
endef
# /end C-PP.FILTER
@ -432,8 +447,10 @@ sqlite3-api.jses += $(sqlite3-api-build-version.js)
sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js
# sqlite3-api-worker.js = the Worker1 API:
sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js
# sqlite3-v-helper = helper APIs for VFSes and VTABLEs:
sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js
# sqlite3-vfs-helper = helper APIs for VFSes:
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-helper.c-pp.js
# sqlite3-vtab-helper = helper APIs for VTABLEs:
sqlite3-api.jses += $(dir.api)/sqlite3-vtab-helper.c-pp.js
# sqlite3-vfs-opfs.c-pp.js = the first OPFS VFS:
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js
# sqlite3-vfs-opfs-sahpool.c-pp.js = the second OPFS VFS:
@ -449,13 +466,14 @@ sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js
# the first OPFS VFS and necessarily an external file.
SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js
SOAP.js.bld := $(dir.dout)/$(notdir $(SOAP.js))
sqlite3-api.ext.jses += $(SOAP.js.bld)
#
# $(sqlite3-api.ext.jses) = API-related files which are standalone files,
# not part of the amalgamation.
#
sqlite3-api.ext.jses := $(SOAP.js.bld)
$(SOAP.js.bld): $(SOAP.js)
cp $< $@
all quick: $(sqlite3-api.ext.jses)
q: quick
########################################################################
# $(sqlite3-api*.*js) contain the core library code but not the
# Emscripten-related glue which deals with loading sqlite3.wasm. In
@ -528,6 +546,10 @@ emcc.jsflags += -sSTRICT_JS=0
# 3.1.31. The fix for that in newer emcc's is to throw a built-time
# error if STRICT_JS is used together with those options.
# emcc.jsflags += -sSTRICT=1
# -sSTRICT=1 Causes failures about unknown symbols which the build
# tools should be installing, e.g. __syscall_geteuid32
# -sENVIRONMENT values for the various build modes:
emcc.environment.vanilla := web,worker
emcc.environment.bundler-friendly := $(emcc.environment.vanilla)
@ -548,6 +570,11 @@ emcc.environment.node := node
# time with 16mb+ memory and 3X time when starting with 8MB. However,
# such test results are inconsistent due to browser internals which
# are opaque to us.
#
# 2024-03-04: emsdk 3.1.55 replaces INITIAL_MEMORY with INITIAL_HEAP,
# but also says (in its changelog): "Note that it is currently not
# supported in all configurations (#21071)."
# https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md
emcc.jsflags += -sALLOW_MEMORY_GROWTH
emcc.INITIAL_MEMORY.128 := 134217728
emcc.INITIAL_MEMORY.96 := 100663296
@ -813,13 +840,13 @@ pre-post-jses.deps.common := $(extern-pre-js.js) $(sqlite3-license-version.js)
# $4 = resulting sqlite-api JS/MJS file
# $5 = resulting JS/MJS file
# $6 = -D... flags for $(bin.c-pp)
# $7 = emcc -sXYZ flags (CURRENTLY UNUSED - was factored out)
# $7 = optional extra flags for emcc
#
# Maintenance reminder: be careful not to introduce spaces around args
# ($1, $2), otherwise string concatenation will malfunction.
#
# emcc.environment.$(2) must be set to a value for emcc's
# -sENVIRONMENT flag.
# Before calling this, emcc.environment.$(2) must be set to a value
# for emcc's -sENVIRONMENT flag.
#
# $(cflags.$(1)) and $(cflags.$(1).$(2)) may be defined to append
# CFLAGS to a given build mode.
@ -926,18 +953,39 @@ sqlite3-worker1.js.in := $(dir.api)/sqlite3-worker1.c-pp.js
sqlite3-worker1-promiser.js.in := $(dir.api)/sqlite3-worker1-promiser.c-pp.js
sqlite3-worker1.js := $(dir.dout)/sqlite3-worker1.js
sqlite3-worker1-promiser.js := $(dir.dout)/sqlite3-worker1-promiser.js
sqlite3-worker1-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-bundler-friendly.mjs
sqlite3-worker1-promiser.mjs := $(dir.dout)/sqlite3-worker1-promiser.mjs
sqlite3-worker1-bundler-friendly.mjs := $(dir.dout)/sqlite3-worker1-bundler-friendly.mjs
sqlite3-worker1-promiser-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-promiser-bundler-friendly.js
$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1.js)))
$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.js),\
$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.mjs),\
$(c-pp.D.sqlite3-bundler-friendly)))
$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.js)))
$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),\
$(sqlite3-worker1-promiser-bundler-friendly.js),\
$(c-pp.D.sqlite3-bundler-friendly)))
$(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.js) \
$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.mjs),\
-Dtarget=es6-module -Dtarget=es6-bundler-friendly))
$(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.mjs) \
$(sqlite3-worker1-promiser-bundler-friendly.js)
$(sqlite3.js) $(sqlite3.mjs): $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js)
$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.js,demo-worker1-promiser.js))
$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.js,demo-worker1-promiser.mjs,\
-Dtarget=es6-module))
$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.html,demo-worker1-promiser.html))
$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.html,demo-worker1-promiser-esm.html,\
-Dtarget=es6-module))
all: $(sqlite3-worker1.js) \
$(sqlite3-worker1-promiser.js) $(sqlite3-worker1-promiser.mjs)
demo-worker1-promiser.html: $(sqlite3-worker1-promiser.js) demo-worker1-promiser.js
demo-worker1-promiser-esm.html: $(sqlite3-worker1-promiser.mjs) demo-worker1-promiser.mjs
all: demo-worker1-promiser.html demo-worker1-promiser-esm.html
sqlite3-api.ext.jses += \
$(sqlite3-worker1-promiser.mjs) \
$(sqlite3-worker1-bundler-friendly.mjs) \
$(sqlite3-worker1.js)
all quick: $(sqlite3-api.ext.jses)
q: quick
########################################################################
# batch-runner.js is part of one of the test apps which reads in SQL
@ -978,6 +1026,9 @@ emcc.speedtest1.common += -sABORTING_MALLOC
emcc.speedtest1.common += -sSTRICT_JS=0
emcc.speedtest1.common += -sMODULARIZE
emcc.speedtest1.common += -Wno-limited-postlink-optimizations
emcc.speedtest1.common += -Wno-unused-main
# ^^^^ -Wno-unused-main is for emcc 3.1.52+. speedtest1 has a wasm_main() which is
# exported and called by the JS code.
EXPORTED_FUNCTIONS.speedtest1 := $(abspath $(dir.tmp)/EXPORTED_FUNCTIONS.speedtest1)
emcc.speedtest1.common += -sSTACK_SIZE=512KB
emcc.speedtest1.common += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.speedtest1)

View File

@ -174,11 +174,17 @@ const Rx = newObj({
squiggly: /[{}]/
});
const Util = newObj({
toss,
unlink: function(fn){
return 0==sqlite3.wasm.sqlite3_wasm_vfs_unlink(0,fn);
unlink: function f(fn){
if(!f.unlink){
f.unlink = sqlite3.wasm.xWrap('sqlite3__wasm_vfs_unlink','int',
['*','string']);
}
return 0==f.unlink(0,fn);
},
argvToString: (list)=>{
@ -197,7 +203,7 @@ const Util = newObj({
utf8Encode: (str)=>__utf8Encoder.encode(str),
strglob: sqlite3.wasm.xWrap('sqlite3_wasm_SQLTester_strglob','int',
strglob: sqlite3.wasm.xWrap('sqlite3__wasm_SQLTester_strglob','int',
['string','string'])
})/*Util*/;

View File

@ -78,10 +78,12 @@ browser client:
a Promise-based interface into the Worker #1 API. This is
a far user-friendlier way to interface with databases running
in a Worker thread.
- **`sqlite3-v-helper.js`**\
Installs `sqlite3.vfs` and `sqlite3.vtab`, namespaces which contain
helpers for use by downstream code which creates `sqlite3_vfs`
and `sqlite3_module` implementations.
- **`sqlite3-vfs-helper.js`**\
Installs the `sqlite3.vfs` namespace, which contain helpers for use
by downstream code which creates `sqlite3_vfs` implementations.
- **`sqlite3-vtab-helper.js`**\
Installs the `sqlite3.vtab` namespace, which contain helpers for use
by downstream code which creates `sqlite3_module` implementations.
- **`sqlite3-vfs-opfs.c-pp.js`**\
is an sqlite3 VFS implementation which supports the Origin-Private
FileSystem (OPFS) as a storage layer to provide persistent storage

View File

@ -19,8 +19,10 @@ Module.postRun.push(function(Module/*the Emscripten-style module object*/){
- sqlite3-api-glue.js => glues previous parts together
- sqlite3-api-oo.js => SQLite3 OO API #1
- sqlite3-api-worker1.js => Worker-based API
- sqlite3-vfs-helper.js => Internal-use utilities for...
- sqlite3-vfs-opfs.js => OPFS VFS
- sqlite3-vfs-helper.c-pp.js => Utilities for VFS impls
- sqlite3-vtab-helper.c-pp.js => Utilities for virtual table impls
- sqlite3-vfs-opfs.c-pp.js => OPFS VFS
- sqlite3-vfs-opfs-sahpool.c-pp.js => OPFS SAHPool VFS
- sqlite3-api-cleanup.js => final API cleanup
- post-js-footer.js => closes this postRun() function
*/

View File

@ -14,7 +14,8 @@
previous steps of the sqlite3-api.js bootstrapping process:
sqlite3-api-prologue.js, whwasmutil.js, and jaccwabyt.js. It
initializes the main API pieces so that the downstream components
(e.g. sqlite3-api-oo1.js) have all that they need.
(e.g. sqlite3-api-oo1.js) have all of the infrastructure that they
need.
*/
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
'use strict';
@ -328,7 +329,16 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]);
}
if(wasm.exports.sqlite3_activate_see instanceof Function){
//#if enable-see
if(wasm.exports.sqlite3_key_v2 instanceof Function){
/**
This code is capable of using an SEE build but note that an SEE
WASM build is generally incompatible with SEE's license
conditions. It is permitted for use internally in organizations
which have licensed SEE, but not for public sites because
exposing an SEE build of sqlite3.wasm effectively provides all
clients with a working copy of the commercial SEE code.
*/
wasm.bindingSignatures.push(
["sqlite3_key", "int", "sqlite3*", "string", "int"],
["sqlite3_key_v2","int","sqlite3*","string","*","int"],
@ -337,10 +347,14 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
["sqlite3_activate_see", undefined, "string"]
);
}
//#endif enable-see
/**
Functions which require BigInt (int64) support are separated from
the others because we need to conditionally bind them or apply
dummy impls, depending on the capabilities of the environment.
(That said: we never actually build without BigInt support,
and such builds are untested.)
Note that not all of these functions directly require int64
but are only for use with APIs which require int64. For example,
@ -359,7 +373,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/* Careful! Short version: de/serialize() are problematic because they
might use a different allocator than the user for managing the
deserialized block. de/serialize() are ONLY safe to use with
sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */,
sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. Because
of this, the canonical builds of sqlite3.wasm/js guarantee that
sqlite3.wasm.alloc() and friends use those allocators. Custom builds
may not guarantee that, however. */,
["sqlite3_drop_modules", "int", ["sqlite3*", "**"]],
["sqlite3_last_insert_rowid", "i64", ["sqlite3*"]],
["sqlite3_malloc64", "*","i64"],
@ -422,8 +439,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
// Add session/changeset APIs...
if(wasm.bigIntEnabled && !!wasm.exports.sqlite3changegroup_add){
/* ACHTUNG: 2022-12-23: the session/changeset API bindings are
COMPLETELY UNTESTED. */
/**
FuncPtrAdapter options for session-related callbacks with the
native signature "i(ps)". This proxy converts the 2nd argument
@ -601,16 +616,25 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/**
Functions which are intended solely for API-internal use by the
WASM components, not client code. These get installed into
sqlite3.wasm. Some of them get exposed to clients via variants
named sqlite3_js_...().
sqlite3.util. Some of them get exposed to clients via variants
in sqlite3_js_...().
2024-01-11: these were renamed, with two underscores in the
prefix, to ensure that clients do not accidentally depend on
them. They have always been documented as internal-use-only, so
no clients "should" be depending on the old names.
*/
wasm.bindingSignatures.wasm = [
["sqlite3_wasm_db_reset", "int", "sqlite3*"],
["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"],
["sqlite3_wasm_vfs_create_file", "int",
"sqlite3_vfs*","string","*", "int"],
["sqlite3_wasm_posix_create_file", "int", "string","*", "int"],
["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"]
wasm.bindingSignatures.wasmInternal = [
["sqlite3__wasm_db_reset", "int", "sqlite3*"],
["sqlite3__wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"],
[/* DO NOT USE. This is deprecated since 2023-08-11 because it can
trigger assert() in debug builds when used with file sizes
which are not sizes to a multiple of a valid db page size. */
"sqlite3__wasm_vfs_create_file", "int", "sqlite3_vfs*","string","*", "int"
],
["sqlite3__wasm_posix_create_file", "int", "string","*", "int"],
["sqlite3__wasm_vfs_unlink", "int", "sqlite3_vfs*","string"],
["sqlite3__wasm_qfmt_token","string:dealloc", "string","int"]
];
/**
@ -652,7 +676,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Use case: sqlite3_bind_pointer() and sqlite3_result_pointer()
call for "a static string and preferably a string
literal". This converter is used to ensure that the string
literal." This converter is used to ensure that the string
value seen by those functions is long-lived and behaves as they
need it to.
*/
@ -674,14 +698,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
`sqlite3_vfs*` via capi.sqlite3_vfs.pointer.
*/
const __xArgPtr = wasm.xWrap.argAdapter('*');
const nilType = function(){}/*a class no value can ever be an instance of*/;
const nilType = function(){
/*a class which no value can ever be an instance of*/
};
wasm.xWrap.argAdapter('sqlite3_filename', __xArgPtr)
('sqlite3_context*', __xArgPtr)
('sqlite3_value*', __xArgPtr)
('void*', __xArgPtr)
('sqlite3_changegroup*', __xArgPtr)
('sqlite3_changeset_iter*', __xArgPtr)
//('sqlite3_rebaser*', __xArgPtr)
('sqlite3_session*', __xArgPtr)
('sqlite3_stmt*', (v)=>
__xArgPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType))
@ -742,8 +767,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
for(const e of wasm.bindingSignatures){
capi[e[0]] = wasm.xWrap.apply(null, e);
}
for(const e of wasm.bindingSignatures.wasm){
wasm[e[0]] = wasm.xWrap.apply(null, e);
for(const e of wasm.bindingSignatures.wasmInternal){
util[e[0]] = wasm.xWrap.apply(null, e);
}
/* For C API functions which cannot work properly unless
@ -765,9 +790,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
implicitly making it part of the public interface. */
delete wasm.bindingSignatures;
if(wasm.exports.sqlite3_wasm_db_error){
if(wasm.exports.sqlite3__wasm_db_error){
const __db_err = wasm.xWrap(
'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string'
'sqlite3__wasm_db_error', 'int', 'sqlite3*', 'int', 'string'
);
/**
Sets the given db's error state. Accepts:
@ -785,7 +810,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Returns the resulting code. Pass (pDb,0,0) to clear the error
state.
*/
util.sqlite3_wasm_db_error = function(pDb, resultCode, message){
util.sqlite3__wasm_db_error = function(pDb, resultCode, message){
if(resultCode instanceof sqlite3.WasmAllocError){
resultCode = capi.SQLITE_NOMEM;
message = 0 /*avoid allocating message string*/;
@ -796,17 +821,17 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return pDb ? __db_err(pDb, resultCode, message) : resultCode;
};
}else{
util.sqlite3_wasm_db_error = function(pDb,errCode,msg){
console.warn("sqlite3_wasm_db_error() is not exported.",arguments);
util.sqlite3__wasm_db_error = function(pDb,errCode,msg){
console.warn("sqlite3__wasm_db_error() is not exported.",arguments);
return errCode;
};
}
}/*xWrap() bindings*/
{/* Import C-level constants and structs... */
const cJson = wasm.xCall('sqlite3_wasm_enum_json');
const cJson = wasm.xCall('sqlite3__wasm_enum_json');
if(!cJson){
toss("Maintenance required: increase sqlite3_wasm_enum_json()'s",
toss("Maintenance required: increase sqlite3__wasm_enum_json()'s",
"static buffer size!");
}
//console.debug('wasm.ctype length =',wasm.cstrlen(cJson));
@ -877,7 +902,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
delete capi[k];
}
capi.sqlite3_vtab_config = wasm.xWrap(
'sqlite3_wasm_vtab_config','int',[
'sqlite3__wasm_vtab_config','int',[
'sqlite3*', 'int', 'int']
);
}/* end vtab-related setup */
@ -889,7 +914,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
consistency with non-special-case wrappings.
*/
const __dbArgcMismatch = (pDb,f,n)=>{
return util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE,
return util.sqlite3__wasm_db_error(pDb, capi.SQLITE_MISUSE,
f+"() requires "+n+" argument"+
(1===n?"":'s')+".");
};
@ -898,7 +923,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
argument and require SQLITE_UTF8. Sets the db error code to
SQLITE_FORMAT and returns that code. */
const __errEncoding = (pDb)=>{
return util.sqlite3_wasm_db_error(
return util.sqlite3__wasm_db_error(
pDb, capi.SQLITE_FORMAT, "SQLITE_UTF8 is the only supported encoding."
);
};
@ -1128,7 +1153,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
return rc;
}catch(e){
return util.sqlite3_wasm_db_error(pDb, e);
return util.sqlite3__wasm_db_error(pDb, e);
}
};
@ -1254,7 +1279,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return rc;
}catch(e){
console.error("sqlite3_create_function_v2() setup threw:",e);
return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
return util.sqlite3__wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
}
};
@ -1299,7 +1324,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return rc;
}catch(e){
console.error("sqlite3_create_window_function() setup threw:",e);
return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
return util.sqlite3__wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
}
};
/**
@ -1394,7 +1419,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null);
case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail);
default:
return util.sqlite3_wasm_db_error(
return util.sqlite3__wasm_db_error(
pDb, capi.SQLITE_MISUSE,
"Invalid SQL argument type for sqlite3_prepare_v2/v3()."
);
@ -1438,7 +1463,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}else if('string'===typeof text){
[p, n] = wasm.allocCString(text);
}else{
return util.sqlite3_wasm_db_error(
return util.sqlite3__wasm_db_error(
capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE,
"Invalid 3rd argument type for sqlite3_bind_text()."
);
@ -1446,7 +1471,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return __bindText(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC);
}catch(e){
wasm.dealloc(p);
return util.sqlite3_wasm_db_error(
return util.sqlite3__wasm_db_error(
capi.sqlite3_db_handle(pStmt), e
);
}
@ -1472,7 +1497,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}else if('string'===typeof pMem){
[p, n] = wasm.allocCString(pMem);
}else{
return util.sqlite3_wasm_db_error(
return util.sqlite3__wasm_db_error(
capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE,
"Invalid 3rd argument type for sqlite3_bind_blob()."
);
@ -1480,7 +1505,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return __bindBlob(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC);
}catch(e){
wasm.dealloc(p);
return util.sqlite3_wasm_db_error(
return util.sqlite3__wasm_db_error(
capi.sqlite3_db_handle(pStmt), e
);
}
@ -1504,11 +1529,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
case capi.SQLITE_CONFIG_SORTERREF_SIZE: // 28 /* int nByte */
case capi.SQLITE_CONFIG_STMTJRNL_SPILL: // 26 /* int nByte */
case capi.SQLITE_CONFIG_URI:// 17 /* int */
return wasm.exports.sqlite3_wasm_config_i(op, args[0]);
return wasm.exports.sqlite3__wasm_config_i(op, args[0]);
case capi.SQLITE_CONFIG_LOOKASIDE: // 13 /* int int */
return wasm.exports.sqlite3_wasm_config_ii(op, args[0], args[1]);
return wasm.exports.sqlite3__wasm_config_ii(op, args[0], args[1]);
case capi.SQLITE_CONFIG_MEMDB_MAXSIZE: // 29 /* sqlite3_int64 */
return wasm.exports.sqlite3_wasm_config_j(op, args[0]);
return wasm.exports.sqlite3__wasm_config_j(op, args[0]);
case capi.SQLITE_CONFIG_GETMALLOC: // 5 /* sqlite3_mem_methods* */
case capi.SQLITE_CONFIG_GETMUTEX: // 11 /* sqlite3_mutex_methods* */
case capi.SQLITE_CONFIG_GETPCACHE2: // 19 /* sqlite3_pcache_methods2* */
@ -1529,6 +1554,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
case capi.SQLITE_CONFIG_SQLLOG: // 21 /* xSqllog, void* */
case capi.SQLITE_CONFIG_WIN32_HEAPSIZE: // 23 /* int nByte */
default:
/* maintenance note: we specifically do not include
SQLITE_CONFIG_ROWID_IN_VIEW here, on the grounds that
it's only for legacy support and no apps written with
this API require that. */
return capi.SQLITE_NOTFOUND;
}
};
@ -1574,11 +1603,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if( pKvvfs ){/* kvvfs-specific glue */
if(util.isUIThread()){
const kvvfsMethods = new capi.sqlite3_kvvfs_methods(
wasm.exports.sqlite3_wasm_kvvfs_methods()
wasm.exports.sqlite3__wasm_kvvfs_methods()
);
delete capi.sqlite3_kvvfs_methods;
const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack,
const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKeyOnPstack,
pstack = wasm.pstack;
const kvvfsStorage = (zClass)=>
@ -1587,7 +1616,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/**
Implementations for members of the object referred to by
sqlite3_wasm_kvvfs_methods(). We swap out the native
sqlite3__wasm_kvvfs_methods(). We swap out the native
implementations with these, which use localStorage or
sessionStorage for their backing store.
*/
@ -1667,5 +1696,181 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
}/*pKvvfs*/
/* Warn if client-level code makes use of FuncPtrAdapter. */
wasm.xWrap.FuncPtrAdapter.warnOnUse = true;
const StructBinder = sqlite3.StructBinder
/* we require a local alias b/c StructBinder is removed from the sqlite3
object during the final steps of the API cleanup. */;
/**
Installs a StructBinder-bound function pointer member of the
given name and function in the given StructBinder.StructType
target object.
It creates a WASM proxy for the given function and arranges for
that proxy to be cleaned up when tgt.dispose() is called. Throws
on the slightest hint of error, e.g. tgt is-not-a StructType,
name does not map to a struct-bound member, etc.
As a special case, if the given function is a pointer, then
`wasm.functionEntry()` is used to validate that it is a known
function. If so, it is used as-is with no extra level of proxying
or cleanup, else an exception is thrown. It is legal to pass a
value of 0, indicating a NULL pointer, with the caveat that 0
_is_ a legal function pointer in WASM but it will not be accepted
as such _here_. (Justification: the function at address zero must
be one which initially came from the WASM module, not a method we
want to bind to a virtual table or VFS.)
This function returns a proxy for itself which is bound to tgt
and takes 2 args (name,func). That function returns the same
thing as this one, permitting calls to be chained.
If called with only 1 arg, it has no side effects but returns a
func with the same signature as described above.
ACHTUNG: because we cannot generically know how to transform JS
exceptions into result codes, the installed functions do no
automatic catching of exceptions. It is critical, to avoid
undefined behavior in the C layer, that methods mapped via
this function do not throw. The exception, as it were, to that
rule is...
If applyArgcCheck is true then each JS function (as opposed to
function pointers) gets wrapped in a proxy which asserts that it
is passed the expected number of arguments, throwing if the
argument count does not match expectations. That is only intended
for dev-time usage for sanity checking, and may leave the C
environment in an undefined state.
*/
const installMethod = function callee(
tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck
){
if(!(tgt instanceof StructBinder.StructType)){
toss("Usage error: target object is-not-a StructType.");
}else if(!(func instanceof Function) && !wasm.isPtr(func)){
toss("Usage errror: expecting a Function or WASM pointer to one.");
}
if(1===arguments.length){
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
}
if(!callee.argcProxy){
callee.argcProxy = function(tgt, funcName, func,sig){
return function(...args){
if(func.length!==arguments.length){
toss("Argument mismatch for",
tgt.structInfo.name+"::"+funcName
+": Native signature is:",sig);
}
return func.apply(this, args);
}
};
/* An ondispose() callback for use with
StructBinder-created types. */
callee.removeFuncList = function(){
if(this.ondispose.__removeFuncList){
this.ondispose.__removeFuncList.forEach(
(v,ndx)=>{
if('number'===typeof v){
try{wasm.uninstallFunction(v)}
catch(e){/*ignore*/}
}
/* else it's a descriptive label for the next number in
the list. */
}
);
delete this.ondispose.__removeFuncList;
}
};
}/*static init*/
const sigN = tgt.memberSignature(name);
if(sigN.length<2){
toss("Member",name,"does not have a function pointer signature:",sigN);
}
const memKey = tgt.memberKey(name);
const fProxy = (applyArgcCheck && !wasm.isPtr(func))
/** This middle-man proxy is only for use during development, to
confirm that we always pass the proper number of
arguments. We know that the C-level code will always use the
correct argument count. */
? callee.argcProxy(tgt, memKey, func, sigN)
: func;
if(wasm.isPtr(fProxy)){
if(fProxy && !wasm.functionEntry(fProxy)){
toss("Pointer",fProxy,"is not a WASM function table entry.");
}
tgt[memKey] = fProxy;
}else{
const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
tgt[memKey] = pFunc;
if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){
tgt.addOnDispose('ondispose.__removeFuncList handler',
callee.removeFuncList);
tgt.ondispose.__removeFuncList = [];
}
tgt.ondispose.__removeFuncList.push(memKey, pFunc);
}
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
}/*installMethod*/;
installMethod.installMethodArgcCheck = false;
/**
Installs methods into the given StructBinder.StructType-type
instance. Each entry in the given methods object must map to a
known member of the given StructType, else an exception will be
triggered. See installMethod() for more details, including the
semantics of the 3rd argument.
As an exception to the above, if any two or more methods in the
2nd argument are the exact same function, installMethod() is
_not_ called for the 2nd and subsequent instances, and instead
those instances get assigned the same method pointer which is
created for the first instance. This optimization is primarily to
accommodate special handling of sqlite3_module::xConnect and
xCreate methods.
On success, returns its first argument. Throws on error.
*/
const installMethods = function(
structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck
){
const seen = new Map /* map of <Function, memberName> */;
for(const k of Object.keys(methods)){
const m = methods[k];
const prior = seen.get(m);
if(prior){
const mkey = structInstance.memberKey(k);
structInstance[mkey] = structInstance[structInstance.memberKey(prior)];
}else{
installMethod(structInstance, k, m, applyArgcCheck);
seen.set(m, k);
}
}
return structInstance;
};
/**
Equivalent to calling installMethod(this,...arguments) with a
first argument of this object. If called with 1 or 2 arguments
and the first is an object, it's instead equivalent to calling
installMethods(this,...arguments).
*/
StructBinder.StructType.prototype.installMethod = function callee(
name, func, applyArgcCheck = installMethod.installMethodArgcCheck
){
return (arguments.length < 3 && name && 'object'===typeof name)
? installMethods(this, ...arguments)
: installMethod(this, ...arguments);
};
/**
Equivalent to calling installMethods() with a first argument
of this object.
*/
StructBinder.StructType.prototype.installMethods = function(
methods, applyArgcCheck = installMethod.installMethodArgcCheck
){
return installMethods(this, methods, applyArgcCheck);
};
});

View File

@ -87,6 +87,104 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
*/
const __vfsPostOpenSql = Object.create(null);
//#if enable-see
/**
Converts ArrayBuffer or Uint8Array ba into a string of hex
digits.
*/
const byteArrayToHex = function(ba){
if( ba instanceof ArrayBuffer ){
ba = new Uint8Array(ba);
}
const li = [];
const digits = "0123456789abcdef";
for( const d of ba ){
li.push( digits[(d & 0xf0) >> 4], digits[d & 0x0f] );
}
return li.join('');
};
/**
Internal helper to apply an SEE key to a just-opened
database. Requires that db be-a DB object which has just been
opened, opt be the options object processed by its ctor, and opt
must have either the key, hexkey, or textkey properties, either
as a string, an ArrayBuffer, or a Uint8Array.
This is a no-op in non-SEE builds. It throws on error and returns
without side effects if none of the key/textkey/hexkey options
are set. It throws if more than one is set or if any are set to
values of an invalid type.
Returns true if it applies the key, else an unspecified falsy value.
*/
const dbCtorApplySEEKey = function(db,opt){
if( !capi.sqlite3_key_v2 ) return;
let keytype;
let key;
const check = (opt.key ? 1 : 0) + (opt.hexkey ? 1 : 0) + (opt.textkey ? 1 : 0);
if( !check ) return;
else if( check>1 ){
toss3(capi.SQLITE_MISUSE,
"Only ONE of (key, hexkey, textkey) may be provided.");
}
if( opt.key ){
/* It is not legal to bind an argument to PRAGMA key=?, so we
convert it to a hexkey... */
keytype = 'key';
key = opt.key;
if('string'===typeof key){
key = new TextEncoder('utf-8').encode(key);
}
if((key instanceof ArrayBuffer) || (key instanceof Uint8Array)){
key = byteArrayToHex(key);
keytype = 'hexkey';
}else{
toss3(capi.SQLITE_MISUSE,
"Invalid value for the 'key' option. Expecting a string,",
"ArrayBuffer, or Uint8Array.");
return;
}
}else if( opt.textkey ){
/* For textkey we need it to be in string form, so convert it to
a string if it's a byte array... */
keytype = 'textkey';
key = opt.textkey;
if(key instanceof ArrayBuffer){
key = new Uint8Array(key);
}
if(key instanceof Uint8Array){
key = new TextDecoder('utf-8').decode(key);
}else if('string'!==typeof key){
toss3(capi.SQLITE_MISUSE,
"Invalid value for the 'textkey' option. Expecting a string,",
"ArrayBuffer, or Uint8Array.");
}
}else if( opt.hexkey ){
keytype = 'hexkey';
key = opt.hexkey;
if((key instanceof ArrayBuffer) || (key instanceof Uint8Array)){
key = byteArrayToHex(key);
}else if('string'!==typeof key){
toss3(capi.SQLITE_MISUSE,
"Invalid value for the 'hexkey' option. Expecting a string,",
"ArrayBuffer, or Uint8Array.");
}
/* else assume it's valid hex codes */
}else{
return;
}
let stmt;
try{
stmt = db.prepare("PRAGMA "+keytype+"="+util.sqlite3__wasm_qfmt_token(key, 1));
stmt.step();
}finally{
if(stmt) stmt.finalize();
}
return true;
};
//#endif enable-see
/**
A proxy for DB class constructors. It must be called with the
being-construct DB object as its "this". See the DB constructor
@ -175,16 +273,28 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
__ptrMap.set(this, pDb);
__stmtMap.set(this, Object.create(null));
try{
//#if enable-see
dbCtorApplySEEKey(this,opt);
//#endif
// Check for per-VFS post-open SQL/callback...
const pVfs = capi.sqlite3_js_db_vfs(pDb);
if(!pVfs) toss3("Internal error: cannot get VFS for new db handle.");
const pVfs = capi.sqlite3_js_db_vfs(pDb)
|| toss3("Internal error: cannot get VFS for new db handle.");
const postInitSql = __vfsPostOpenSql[pVfs];
if(postInitSql instanceof Function){
postInitSql(this, sqlite3);
}else if(postInitSql){
checkSqlite3Rc(
pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0)
);
if(postInitSql){
/**
Reminder: if this db is encrypted and the client did _not_ pass
in the key, any init code will fail, causing the ctor to throw.
We don't actually know whether the db is encrypted, so we cannot
sensibly apply any heuristics which skip the init code only for
encrypted databases for which no key has yet been supplied.
*/
if(postInitSql instanceof Function){
postInitSql(this, sqlite3);
}else{
checkSqlite3Rc(
pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0)
);
}
}
}catch(e){
this.close();
@ -280,6 +390,36 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
- `flags`: open-mode flags
- `vfs`: the VFS fname
//#if enable-see
SEE-capable builds optionally support ONE of the following
additional options:
- `key`, `hexkey`, or `textkey`: encryption key as a string,
ArrayBuffer, or Uint8Array. These flags function as documented
for the SEE pragmas of the same names. Using a byte array for
`hexkey` is equivalent to the same series of hex codes in
string form, so `'666f6f'` is equivalent to
`Uint8Array([0x66,0x6f,0x6f])`. A `textkey` byte array is
assumed to be UTF-8. A `key` string is transformed into a UTF-8
byte array, and a `key` byte array is transformed into a
`hexkey` with the same bytes.
In non-SEE builds, these options are ignored. In SEE builds,
`PRAGMA key/textkey/hexkey=X` is executed immediately after
opening the db. If more than one of the options is provided,
or any option has an invalid argument type, an exception is
thrown.
Note that some DB subclasses may run post-initialization SQL
code, e.g. to set a busy-handler timeout or tweak the page cache
size. Such code is run _after_ the SEE key is applied. If no key
is supplied and the database is encrypted, execution of the
post-initialization SQL will fail, causing the constructor to
throw.
//#endif enable-see
The `filename` and `vfs` arguments may be either JS strings or
C-strings allocated via WASM. `flags` is required to be a JS
string (because it's specific to this API, which is specific
@ -288,7 +428,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
For purposes of passing a DB instance to C-style sqlite3
functions, the DB object's read-only `pointer` property holds its
`sqlite3*` pointer value. That property can also be used to check
whether this DB instance is still open.
whether this DB instance is still open: it will evaluate to
`undefined` after the DB object's close() method is called.
In the main window thread, the filenames `":localStorage:"` and
`":sessionStorage:"` are special: they cause the db to use either
@ -433,40 +574,56 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
out.returnVal = ()=>opt.resultRows;
}
if(opt.callback || opt.resultRows){
switch((undefined===opt.rowMode)
? 'array' : opt.rowMode) {
case 'object': out.cbArg = (stmt)=>stmt.get(Object.create(null)); break;
case 'array': out.cbArg = (stmt)=>stmt.get([]); break;
case 'stmt':
if(Array.isArray(opt.resultRows)){
toss3("exec(): invalid rowMode for a resultRows array: must",
"be one of 'array', 'object',",
"a result column number, or column name reference.");
}
out.cbArg = (stmt)=>stmt;
switch((undefined===opt.rowMode) ? 'array' : opt.rowMode) {
case 'object':
out.cbArg = (stmt,cache)=>{
if( !cache.columnNames ) cache.columnNames = stmt.getColumnNames([]);
/* https://sqlite.org/forum/forumpost/3632183d2470617d:
conversion of rows to objects (key/val pairs) is
somewhat expensive for large data sets because of the
native-to-JS conversion of the column names. If we
instead cache the names and build objects from that
list of strings, it can run twice as fast. The
difference is not noticeable for small data sets but
becomes human-perceivable when enough rows are
involved. */
const row = stmt.get([]);
const rv = Object.create(null);
for( const i in cache.columnNames ) rv[cache.columnNames[i]] = row[i];
return rv;
};
break;
case 'array': out.cbArg = (stmt)=>stmt.get([]); break;
case 'stmt':
if(Array.isArray(opt.resultRows)){
toss3("exec(): invalid rowMode for a resultRows array: must",
"be one of 'array', 'object',",
"a result column number, or column name reference.");
}
out.cbArg = (stmt)=>stmt;
break;
default:
if(util.isInt32(opt.rowMode)){
out.cbArg = (stmt)=>stmt.get(opt.rowMode);
break;
default:
if(util.isInt32(opt.rowMode)){
out.cbArg = (stmt)=>stmt.get(opt.rowMode);
break;
}else if('string'===typeof opt.rowMode
&& opt.rowMode.length>1
&& '$'===opt.rowMode[0]){
/* "$X": fetch column named "X" (case-sensitive!). Prior
to 2022-12-14 ":X" and "@X" were also permitted, but
having so many options is unnecessary and likely to
cause confusion. */
const $colName = opt.rowMode.substr(1);
out.cbArg = (stmt)=>{
const rc = stmt.get(Object.create(null))[$colName];
return (undefined===rc)
? toss3(capi.SQLITE_NOTFOUND,
"exec(): unknown result column:",$colName)
: rc;
};
break;
}
toss3("Invalid rowMode:",opt.rowMode);
}else if('string'===typeof opt.rowMode
&& opt.rowMode.length>1
&& '$'===opt.rowMode[0]){
/* "$X": fetch column named "X" (case-sensitive!). Prior
to 2022-12-14 ":X" and "@X" were also permitted, but
having so many options is unnecessary and likely to
cause confusion. */
const $colName = opt.rowMode.substr(1);
out.cbArg = (stmt)=>{
const rc = stmt.get(Object.create(null))[$colName];
return (undefined===rc)
? toss3(capi.SQLITE_NOTFOUND,
"exec(): unknown result column:",$colName)
: rc;
};
break;
}
toss3("Invalid rowMode:",opt.rowMode);
}
}
return out;
@ -884,10 +1041,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
and names. */) ? 0 : 1;
evalFirstResult = false;
if(arg.cbArg || resultRows){
const cbArgCache = Object.create(null)
/* 2nd arg for arg.cbArg, used by (at least) row-to-object
converter */;
for(; stmt.step(); stmt._lockedByExec = false){
if(0===gotColNames++) stmt.getColumnNames(opt.columnNames);
if(0===gotColNames++){
stmt.getColumnNames(cbArgCache.columnNames = (opt.columnNames || []));
}
stmt._lockedByExec = true;
const row = arg.cbArg(stmt);
const row = arg.cbArg(stmt,cbArgCache);
if(resultRows) resultRows.push(row);
if(callback && false === callback.call(opt, row, stmt)){
break;
@ -1522,7 +1684,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
they are larger than 32 bits, else double or int32, depending
on whether they have a fractional part. Booleans are bound as
integer 0 or 1. It is not expected the distinction of binding
doubles which have no fractional parts is integers is
doubles which have no fractional parts and integers is
significant for the majority of clients due to sqlite3's data
typing model. If [BigInt] support is enabled then this
routine will bind BigInt values as 64-bit integers if they'll
@ -1706,7 +1868,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
an exception is thrown.
By default it will determine the data type of the result
automatically. If passed a second arugment, it must be one
automatically. If passed a second argument, it must be one
of the enumeration values for sqlite3 types, which are
defined as members of the sqlite3 module: SQLITE_INTEGER,
SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB. Any other value,
@ -1906,16 +2068,26 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Functionally equivalent to DB(storageName,'c','kvvfs') except
that it throws if the given storage name is not one of 'local'
or 'session'.
As of version 3.46, the argument may optionally be an options
object in the form:
{
filename: 'session'|'local',
... etc. (all options supported by the DB ctor)
}
noting that the 'vfs' option supported by main DB
constructor is ignored here: the vfs is always 'kvvfs'.
*/
sqlite3.oo1.JsStorageDb = function(storageName='session'){
const opt = dbCtorHelper.normalizeArgs(...arguments);
storageName = opt.filename;
if('session'!==storageName && 'local'!==storageName){
toss3("JsStorageDb db name must be one of 'session' or 'local'.");
}
dbCtorHelper.call(this, {
filename: storageName,
flags: 'c',
vfs: "kvvfs"
});
opt.vfs = 'kvvfs';
dbCtorHelper.call(this, opt);
};
const jdb = sqlite3.oo1.JsStorageDb;
jdb.prototype = Object.create(DB.prototype);

View File

@ -37,7 +37,7 @@
This function expects a configuration object, intended to abstract
away details specific to any given WASM environment, primarily so
that it can be used without any _direct_ dependency on
that it can be used without any direct dependency on
Emscripten. (Note the default values for the config object!) The
config object is only honored the first time this is
called. Subsequent calls ignore the argument and return the same
@ -98,14 +98,37 @@
The returned object is the top-level sqlite3 namespace object.
Client code may optionally assign sqlite3ApiBootstrap.defaultConfig
an object-type value before calling sqlite3ApiBootstrap() (without
arguments) in order to tell that call to use this object as its
default config value. The intention of this is to provide
downstream clients with a reasonably flexible approach for plugging
in an environment-suitable configuration without having to define a
new global-scope symbol.
However, because clients who access this library via an
Emscripten-hosted module will not have an opportunity to call
sqlite3ApiBootstrap() themselves, nor to access it before it is
called, an alternative option for setting the configuration is to
define globalThis.sqlite3ApiConfig to an object. If it is set, it
is used instead of sqlite3ApiBootstrap.defaultConfig if
sqlite3ApiBootstrap() is called without arguments.
Both sqlite3ApiBootstrap.defaultConfig and
globalThis.sqlite3ApiConfig get deleted by sqlite3ApiBootstrap()
because any changes to them made after that point would have no
useful effect.
*/
'use strict';
globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
apiConfig = (globalThis.sqlite3ApiConfig || sqlite3ApiBootstrap.defaultConfig)
){
if(sqlite3ApiBootstrap.sqlite3){ /* already initalized */
console.warn("sqlite3ApiBootstrap() called multiple times.",
"Config and external initializers are ignored on calls after the first.");
(sqlite3ApiBootstrap.sqlite3.config || console).warn(
"sqlite3ApiBootstrap() called multiple times.",
"Config and external initializers are ignored on calls after the first."
);
return sqlite3ApiBootstrap.sqlite3;
}
const config = Object.assign(Object.create(null),{
@ -114,8 +137,16 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
bigIntEnabled: (()=>{
if('undefined'!==typeof Module){
/* Emscripten module will contain HEAPU64 when built with
-sWASM_BIGINT=1, else it will not. */
return !!Module.HEAPU64;
-sWASM_BIGINT=1, else it will not.
As of emsdk 3.1.55, when building in strict mode, HEAPxyz
are only available if _explicitly_ included in the exports,
else they are not. We do not (as of 2024-03-04) use -sSTRICT
for the canonical builds.
*/
if( !!Module.HEAPU64 ) return true;
/* Else fall through and hope for the best. Nobody _really_
builds this without BigInt support, do they? */
}
return !!globalThis.BigInt64Array;
})(),
@ -149,6 +180,15 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
config[k] = config[k]();
}
});
/**
Eliminate any confusion about whether these config objects may
be used after library initialization by eliminating the outward-facing
objects...
*/
delete globalThis.sqlite3ApiConfig;
delete sqlite3ApiBootstrap.defaultConfig;
/**
The main sqlite3 binding API gets installed into this object,
mimicking the C API as closely as we can. The numerous members
@ -205,7 +245,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
The exception's message is created by concatenating its
arguments with a space between each, except for the
two-args-with-an-objec form and that the first argument will
two-args-with-an-object form and that the first argument will
get coerced to a string, as described above, if it's an
integer.
@ -1061,7 +1101,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
are undefined if the passed-in value did not come from
this.pointer.
*/
restore: wasm.exports.sqlite3_wasm_pstack_restore,
restore: wasm.exports.sqlite3__wasm_pstack_restore,
/**
Attempts to allocate the given number of bytes from the
pstack. On success, it zeroes out a block of memory of the
@ -1083,7 +1123,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
if('string'===typeof n && !(n = wasm.sizeofIR(n))){
WasmAllocError.toss("Invalid value for pstack.alloc(",arguments[0],")");
}
return wasm.exports.sqlite3_wasm_pstack_alloc(n)
return wasm.exports.sqlite3__wasm_pstack_alloc(n)
|| WasmAllocError.toss("Could not allocate",n,
"bytes from the pstack.");
},
@ -1163,10 +1203,10 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
*/
pointer: {
configurable: false, iterable: true, writeable: false,
get: wasm.exports.sqlite3_wasm_pstack_ptr
get: wasm.exports.sqlite3__wasm_pstack_ptr
//Whether or not a setter as an alternative to restore() is
//clearer or would just lead to confusion is unclear.
//set: wasm.exports.sqlite3_wasm_pstack_restore
//set: wasm.exports.sqlite3__wasm_pstack_restore
},
/**
sqlite3.wasm.pstack.quota to the total number of bytes
@ -1175,7 +1215,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
*/
quota: {
configurable: false, iterable: true, writeable: false,
get: wasm.exports.sqlite3_wasm_pstack_quota
get: wasm.exports.sqlite3__wasm_pstack_quota
},
/**
sqlite3.wasm.pstack.remaining resolves to the amount of space
@ -1183,7 +1223,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
*/
remaining: {
configurable: false, iterable: true, writeable: false,
get: wasm.exports.sqlite3_wasm_pstack_remaining
get: wasm.exports.sqlite3__wasm_pstack_remaining
}
})/*wasm.pstack properties*/;
@ -1256,14 +1296,14 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
}
try{
if(pdir && 0===wasm.xCallWrapped(
'sqlite3_wasm_init_wasmfs', 'i32', ['string'], pdir
'sqlite3__wasm_init_wasmfs', 'i32', ['string'], pdir
)){
return __wasmfsOpfsDir = pdir;
}else{
return __wasmfsOpfsDir = "";
}
}catch(e){
// sqlite3_wasm_init_wasmfs() is not available
// sqlite3__wasm_init_wasmfs() is not available
return __wasmfsOpfsDir = "";
}
};
@ -1365,7 +1405,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
const zSchema = schema
? (wasm.isPtr(schema) ? schema : wasm.scopedAllocCString(''+schema))
: 0;
let rc = wasm.exports.sqlite3_wasm_db_serialize(
let rc = wasm.exports.sqlite3__wasm_db_serialize(
pDb, zSchema, ppOut, pSize, 0
);
if(rc){
@ -1391,7 +1431,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
or not provided, then "main" is assumed.
*/
capi.sqlite3_js_db_vfs =
(dbPointer, dbName=0)=>wasm.sqlite3_wasm_db_vfs(dbPointer, dbName);
(dbPointer, dbName=0)=>util.sqlite3__wasm_db_vfs(dbPointer, dbName);
/**
A thin wrapper around capi.sqlite3_aggregate_context() which
@ -1449,7 +1489,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
if(!util.isInt32(dataLen) || dataLen<0){
SQLite3Error.toss("Invalid 3rd argument for sqlite3_js_posix_create_file().");
}
const rc = wasm.sqlite3_wasm_posix_create_file(filename, pData, dataLen);
const rc = util.sqlite3__wasm_posix_create_file(filename, pData, dataLen);
if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code",
capi.sqlite3_js_rc_str(rc));
}finally{
@ -1551,7 +1591,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
SQLite3Error.toss("Invalid 4th argument for sqlite3_js_vfs_create_file().");
}
try{
const rc = wasm.sqlite3_wasm_vfs_create_file(vfs, filename, pData, dataLen);
const rc = util.sqlite3__wasm_vfs_create_file(vfs, filename, pData, dataLen);
if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code",
capi.sqlite3_js_rc_str(rc));
}finally{
@ -1672,12 +1712,12 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
*/
capi.sqlite3_db_config = function(pDb, op, ...args){
if(!this.s){
this.s = wasm.xWrap('sqlite3_wasm_db_config_s','int',
this.s = wasm.xWrap('sqlite3__wasm_db_config_s','int',
['sqlite3*', 'int', 'string:static']
/* MAINDBNAME requires a static string */);
this.pii = wasm.xWrap('sqlite3_wasm_db_config_pii', 'int',
this.pii = wasm.xWrap('sqlite3__wasm_db_config_pii', 'int',
['sqlite3*', 'int', '*','int', 'int']);
this.ip = wasm.xWrap('sqlite3_wasm_db_config_ip','int',
this.ip = wasm.xWrap('sqlite3__wasm_db_config_ip','int',
['sqlite3*', 'int', 'int','*']);
}
switch(op){
@ -1798,7 +1838,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
/**
Calls either sqlite3_result_error_nomem(), if e is-a
WasmAllocError, or sqlite3_result_error(). In the latter case,
the second arugment is coerced to a string to create the error
the second argument is coerced to a string to create the error
message.
The first argument is a (sqlite3_context*). Returns void.

View File

@ -359,6 +359,7 @@
*/
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const util = sqlite3.util;
sqlite3.initWorker1API = function(){
'use strict';
const toss = (...args)=>{throw new Error(args.join(' '))};
@ -409,12 +410,12 @@ sqlite3.initWorker1API = function(){
if(db){
delete this.dbs[getDbId(db)];
const filename = db.filename;
const pVfs = sqlite3.wasm.sqlite3_wasm_db_vfs(db.pointer, 0);
const pVfs = util.sqlite3__wasm_db_vfs(db.pointer, 0);
db.close();
const ddNdx = this.dbList.indexOf(db);
if(ddNdx>=0) this.dbList.splice(ddNdx, 1);
if(alsoUnlink && filename && pVfs){
sqlite3.wasm.sqlite3_wasm_vfs_unlink(pVfs, filename);
util.sqlite3__wasm_vfs_unlink(pVfs, filename);
}
}
},
@ -458,11 +459,6 @@ sqlite3.initWorker1API = function(){
return wState.dbList[0] && getDbId(wState.dbList[0]);
};
const guessVfs = function(filename){
const m = /^file:.+(vfs=(\w+))/.exec(filename);
return sqlite3.capi.sqlite3_vfs_find(m ? m[2] : 0);
};
const isSpecialDbFilename = (n)=>{
return ""===n || ':'===n[0];
};
@ -484,36 +480,8 @@ sqlite3.initWorker1API = function(){
toss("Throwing because of simulateError flag.");
}
const rc = Object.create(null);
let byteArray, pVfs;
oargs.vfs = args.vfs;
if(isSpecialDbFilename(args.filename)){
oargs.filename = args.filename || "";
}else{
oargs.filename = args.filename;
byteArray = args.byteArray;
if(byteArray) pVfs = guessVfs(args.filename);
}
if(pVfs){
/* 2022-11-02: this feature is as-yet untested except that
sqlite3_wasm_vfs_create_file() has been tested from the
browser dev console. */
let pMem;
try{
pMem = sqlite3.wasm.allocFromTypedArray(byteArray);
const rc = sqlite3.wasm.sqlite3_wasm_vfs_create_file(
pVfs, oargs.filename, pMem, byteArray.byteLength
);
if(rc) sqlite3.SQLite3Error.toss(rc);
}catch(e){
throw new sqlite3.SQLite3Error(
e.name+' creating '+args.filename+": "+e.message, {
cause: e
}
);
}finally{
if(pMem) sqlite3.wasm.dealloc(pMem);
}
}
oargs.filename = args.filename || "";
const db = wState.open(oargs);
rc.filename = db.filename;
rc.persistent = !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs");

View File

@ -51,7 +51,7 @@
*/
"use strict";
const wPost = (type,...args)=>postMessage({type, payload:args});
const installAsyncProxy = function(self){
const installAsyncProxy = function(){
const toss = function(...args){throw new Error(args.join(' '))};
if(globalThis.window === globalThis){
toss("This code cannot run from the main thread.",
@ -87,35 +87,6 @@ const installAsyncProxy = function(self){
const log = (...args)=>logImpl(2, ...args);
const warn = (...args)=>logImpl(1, ...args);
const error = (...args)=>logImpl(0, ...args);
const metrics = Object.create(null);
metrics.reset = ()=>{
let k;
const r = (m)=>(m.count = m.time = m.wait = 0);
for(k in state.opIds){
r(metrics[k] = Object.create(null));
}
let s = metrics.s11n = Object.create(null);
s = s.serialize = Object.create(null);
s.count = s.time = 0;
s = metrics.s11n.deserialize = Object.create(null);
s.count = s.time = 0;
};
metrics.dump = ()=>{
let k, n = 0, t = 0, w = 0;
for(k in state.opIds){
const m = metrics[k];
n += m.count;
t += m.time;
w += m.wait;
m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
}
console.log(globalThis?.location?.href,
"metrics for",globalThis?.location?.href,":\n",
metrics,
"\nTotal of",n,"op(s) for",t,"ms",
"approx",w,"ms spent waiting on OPFS APIs.");
console.log("Serialization metrics:",metrics.s11n);
};
/**
__openFiles is a map of sqlite3_file pointers (integers) to
@ -265,23 +236,34 @@ const installAsyncProxy = function(self){
this.name = 'GetSyncHandleError';
}
};
/**
Attempts to find a suitable SQLITE_xyz result code for Error
object e. Returns either such a translation or rc if if it does
not know how to translate the exception.
*/
GetSyncHandleError.convertRc = (e,rc)=>{
if(1){
return (
e instanceof GetSyncHandleError
&& ((e.cause.name==='NoModificationAllowedError')
/* Inconsistent exception.name from Chrome/ium with the
same exception.message text: */
|| (e.cause.name==='DOMException'
&& 0===e.cause.message.indexOf('Access Handles cannot')))
) ? (
/*console.warn("SQLITE_BUSY",e),*/
state.sq3Codes.SQLITE_BUSY
) : rc;
}else{
return rc;
if( e instanceof GetSyncHandleError ){
if( e.cause.name==='NoModificationAllowedError'
/* Inconsistent exception.name from Chrome/ium with the
same exception.message text: */
|| (e.cause.name==='DOMException'
&& 0===e.cause.message.indexOf('Access Handles cannot')) ){
return state.sq3Codes.SQLITE_BUSY;
}else if( 'NotFoundError'===e.cause.name ){
/**
Maintenance reminder: SQLITE_NOTFOUND, though it looks like
a good match, has different semantics than NotFoundError
and is not suitable here.
*/
return state.sq3Codes.SQLITE_CANTOPEN;
}
}else if( 'NotFoundError'===e?.name ){
return state.sq3Codes.SQLITE_CANTOPEN;
}
}
return rc;
};
/**
Returns the sync access handle associated with the given file
handle object (which must be a valid handle object, as created by
@ -347,37 +329,6 @@ const installAsyncProxy = function(self){
if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs);
};
/**
We track 2 different timers: the "metrics" timer records how much
time we spend performing work. The "wait" timer records how much
time we spend waiting on the underlying OPFS timer. See the calls
to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd()
throughout this file to see how they're used.
*/
const __mTimer = Object.create(null);
__mTimer.op = undefined;
__mTimer.start = undefined;
const mTimeStart = (op)=>{
__mTimer.start = performance.now();
__mTimer.op = op;
//metrics[op] || toss("Maintenance required: missing metrics for",op);
++metrics[op].count;
};
const mTimeEnd = ()=>(
metrics[__mTimer.op].time += performance.now() - __mTimer.start
);
const __wTimer = Object.create(null);
__wTimer.op = undefined;
__wTimer.start = undefined;
const wTimeStart = (op)=>{
__wTimer.start = performance.now();
__wTimer.op = op;
//metrics[op] || toss("Maintenance required: missing metrics for",op);
};
const wTimeEnd = ()=>(
metrics[__wTimer.op].wait += performance.now() - __wTimer.start
);
/**
Gets set to true by the 'opfs-async-shutdown' command to quit the
wait loop. This is only intended for debugging purposes: we cannot
@ -388,37 +339,24 @@ const installAsyncProxy = function(self){
/**
Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods
methods, as well as helpers like mkdir(). Maintenance reminder:
members are in alphabetical order to simplify finding them.
methods, as well as helpers like mkdir().
*/
const vfsAsyncImpls = {
'opfs-async-metrics': async ()=>{
mTimeStart('opfs-async-metrics');
metrics.dump();
storeAndNotify('opfs-async-metrics', 0);
mTimeEnd();
},
'opfs-async-shutdown': async ()=>{
flagAsyncShutdown = true;
storeAndNotify('opfs-async-shutdown', 0);
},
mkdir: async (dirname)=>{
mTimeStart('mkdir');
let rc = 0;
wTimeStart('mkdir');
try {
await getDirForFilename(dirname+"/filepart", true);
}catch(e){
state.s11n.storeException(2,e);
rc = state.sq3Codes.SQLITE_IOERR;
}finally{
wTimeEnd();
}
storeAndNotify('mkdir', rc);
mTimeEnd();
},
xAccess: async (filename)=>{
mTimeStart('xAccess');
/* OPFS cannot support the full range of xAccess() queries
sqlite3 calls for. We can essentially just tell if the file
is accessible, but if it is then it's automatically writable
@ -431,26 +369,20 @@ const installAsyncProxy = function(self){
accessible, non-0 means not accessible.
*/
let rc = 0;
wTimeStart('xAccess');
try{
const [dh, fn] = await getDirForFilename(filename);
await dh.getFileHandle(fn);
}catch(e){
state.s11n.storeException(2,e);
rc = state.sq3Codes.SQLITE_IOERR;
}finally{
wTimeEnd();
}
storeAndNotify('xAccess', rc);
mTimeEnd();
},
xClose: async function(fid/*sqlite3_file pointer*/){
const opName = 'xClose';
mTimeStart(opName);
__implicitLocks.delete(fid);
const fh = __openFiles[fid];
let rc = 0;
wTimeStart(opName);
if(fh){
delete __openFiles[fid];
await closeSyncHandle(fh);
@ -462,15 +394,11 @@ const installAsyncProxy = function(self){
state.s11n.serialize();
rc = state.sq3Codes.SQLITE_NOTFOUND;
}
wTimeEnd();
storeAndNotify(opName, rc);
mTimeEnd();
},
xDelete: async function(...args){
mTimeStart('xDelete');
const rc = await vfsAsyncImpls.xDeleteNoWait(...args);
storeAndNotify('xDelete', rc);
mTimeEnd();
},
xDeleteNoWait: async function(filename, syncDir = 0, recursive = false){
/* The syncDir flag is, for purposes of the VFS API's semantics,
@ -486,7 +414,6 @@ const installAsyncProxy = function(self){
is false.
*/
let rc = 0;
wTimeStart('xDelete');
try {
while(filename){
const [hDir, filenamePart] = await getDirForFilename(filename, false);
@ -502,14 +429,11 @@ const installAsyncProxy = function(self){
state.s11n.storeException(2,e);
rc = state.sq3Codes.SQLITE_IOERR_DELETE;
}
wTimeEnd();
return rc;
},
xFileSize: async function(fid/*sqlite3_file pointer*/){
mTimeStart('xFileSize');
const fh = __openFiles[fid];
let rc = 0;
wTimeStart('xFileSize');
try{
const sz = await (await getSyncHandle(fh,'xFileSize')).getSize();
state.s11n.serialize(Number(sz));
@ -518,19 +442,15 @@ const installAsyncProxy = function(self){
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR);
}
await releaseImplicitLock(fh);
wTimeEnd();
storeAndNotify('xFileSize', rc);
mTimeEnd();
},
xLock: async function(fid/*sqlite3_file pointer*/,
lockType/*SQLITE_LOCK_...*/){
mTimeStart('xLock');
const fh = __openFiles[fid];
let rc = 0;
const oldLockType = fh.xLock;
fh.xLock = lockType;
if( !fh.syncHandle ){
wTimeStart('xLock');
try {
await getSyncHandle(fh,'xLock');
__implicitLocks.delete(fid);
@ -539,18 +459,14 @@ const installAsyncProxy = function(self){
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_LOCK);
fh.xLock = oldLockType;
}
wTimeEnd();
}
storeAndNotify('xLock',rc);
mTimeEnd();
},
xOpen: async function(fid/*sqlite3_file pointer*/, filename,
flags/*SQLITE_OPEN_...*/,
opfsFlags/*OPFS_...*/){
const opName = 'xOpen';
mTimeStart(opName);
const create = (state.sq3Codes.SQLITE_OPEN_CREATE & flags);
wTimeStart('xOpen');
try{
let hDir, filenamePart;
try {
@ -558,12 +474,17 @@ const installAsyncProxy = function(self){
}catch(e){
state.s11n.storeException(1,e);
storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND);
mTimeEnd();
wTimeEnd();
return;
}
if( state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN & opfsFlags ){
try{
await hDir.removeEntry(filenamePart);
}catch(e){
/* ignoring */
//warn("Ignoring failed Unlink of",filename,":",e);
}
}
const hFile = await hDir.getFileHandle(filenamePart, {create});
wTimeEnd();
const fh = Object.assign(Object.create(null),{
fid: fid,
filenameAbs: filename,
@ -578,76 +499,50 @@ const installAsyncProxy = function(self){
fh.releaseImplicitLocks =
(opfsFlags & state.opfsFlags.OPFS_UNLOCK_ASAP)
|| state.opfsFlags.defaultUnlockAsap;
if(0 /* this block is modelled after something wa-sqlite
does but it leads to immediate contention on journal files.
Update: this approach reportedly only works for DELETE journal
mode. */
&& (0===(flags & state.sq3Codes.SQLITE_OPEN_MAIN_DB))){
/* sqlite does not lock these files, so go ahead and grab an OPFS
lock. */
fh.xLock = "xOpen"/* Truthy value to keep entry from getting
flagged as auto-locked. String value so
that we can easily distinguish is later
if needed. */;
await getSyncHandle(fh,'xOpen');
}
__openFiles[fid] = fh;
storeAndNotify(opName, 0);
}catch(e){
wTimeEnd();
error(opName,e);
state.s11n.storeException(1,e);
storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR);
}
mTimeEnd();
},
xRead: async function(fid/*sqlite3_file pointer*/,n,offset64){
mTimeStart('xRead');
let rc = 0, nRead;
const fh = __openFiles[fid];
try{
wTimeStart('xRead');
nRead = (await getSyncHandle(fh,'xRead')).read(
fh.sabView.subarray(0, n),
{at: Number(offset64)}
);
wTimeEnd();
if(nRead < n){/* Zero-fill remaining bytes */
fh.sabView.fill(0, nRead, n);
rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ;
}
}catch(e){
if(undefined===nRead) wTimeEnd();
error("xRead() failed",e,fh);
state.s11n.storeException(1,e);
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_READ);
}
await releaseImplicitLock(fh);
storeAndNotify('xRead',rc);
mTimeEnd();
},
xSync: async function(fid/*sqlite3_file pointer*/,flags/*ignored*/){
mTimeStart('xSync');
const fh = __openFiles[fid];
let rc = 0;
if(!fh.readOnly && fh.syncHandle){
try {
wTimeStart('xSync');
await fh.syncHandle.flush();
}catch(e){
state.s11n.storeException(2,e);
rc = state.sq3Codes.SQLITE_IOERR_FSYNC;
}
wTimeEnd();
}
storeAndNotify('xSync',rc);
mTimeEnd();
},
xTruncate: async function(fid/*sqlite3_file pointer*/,size){
mTimeStart('xTruncate');
let rc = 0;
const fh = __openFiles[fid];
wTimeStart('xTruncate');
try{
affirmNotRO('xTruncate', fh);
await (await getSyncHandle(fh,'xTruncate')).truncate(size);
@ -657,33 +552,25 @@ const installAsyncProxy = function(self){
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_TRUNCATE);
}
await releaseImplicitLock(fh);
wTimeEnd();
storeAndNotify('xTruncate',rc);
mTimeEnd();
},
xUnlock: async function(fid/*sqlite3_file pointer*/,
lockType/*SQLITE_LOCK_...*/){
mTimeStart('xUnlock');
let rc = 0;
const fh = __openFiles[fid];
if( state.sq3Codes.SQLITE_LOCK_NONE===lockType
&& fh.syncHandle ){
wTimeStart('xUnlock');
try { await closeSyncHandle(fh) }
catch(e){
state.s11n.storeException(1,e);
rc = state.sq3Codes.SQLITE_IOERR_UNLOCK;
}
wTimeEnd();
}
storeAndNotify('xUnlock',rc);
mTimeEnd();
},
xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){
mTimeStart('xWrite');
let rc;
const fh = __openFiles[fid];
wTimeStart('xWrite');
try{
affirmNotRO('xWrite', fh);
rc = (
@ -697,9 +584,7 @@ const installAsyncProxy = function(self){
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_WRITE);
}
await releaseImplicitLock(fh);
wTimeEnd();
storeAndNotify('xWrite',rc);
mTimeEnd();
}
}/*vfsAsyncImpls*/;
@ -733,8 +618,6 @@ const installAsyncProxy = function(self){
}
};
state.s11n.deserialize = function(clear=false){
++metrics.s11n.deserialize.count;
const t = performance.now();
const argc = viewU8[0];
const rc = argc ? [] : null;
if(argc){
@ -759,12 +642,9 @@ const installAsyncProxy = function(self){
}
if(clear) viewU8[0] = 0;
//log("deserialize:",argc, rc);
metrics.s11n.deserialize.time += performance.now() - t;
return rc;
};
state.s11n.serialize = function(...args){
const t = performance.now();
++metrics.s11n.serialize.count;
if(args.length){
//log("serialize():",args);
const typeIds = [];
@ -795,7 +675,6 @@ const installAsyncProxy = function(self){
}else{
viewU8[0] = 0;
}
metrics.s11n.serialize.time += performance.now() - t;
};
state.s11n.storeException = state.asyncS11nExceptions
@ -877,7 +756,6 @@ const installAsyncProxy = function(self){
}
});
initS11n();
metrics.reset();
log("init state",state);
wPost('opfs-async-inited');
waitLoop();
@ -890,9 +768,6 @@ const installAsyncProxy = function(self){
waitLoop();
}
break;
case 'opfs-async-metrics':
metrics.dump();
break;
}
};
wPost('opfs-async-loaded');
@ -911,5 +786,5 @@ if(!globalThis.SharedArrayBuffer){
!navigator?.storage?.getDirectory){
wPost('opfs-unavailable',"Missing required OPFS APIs.");
}else{
installAsyncProxy(self);
installAsyncProxy();
}

View File

@ -0,0 +1,103 @@
/*
** 2022-11-30
**
** The author disclaims copyright to this source code. In place of a
** legal notice, here is a blessing:
**
** * May you do good and not evil.
** * May you find forgiveness for yourself and forgive others.
** * May you share freely, never taking more than you give.
*/
/**
This file installs sqlite3.vfs, a namespace of helpers for use in
the creation of JavaScript implementations of sqlite3_vfs.
*/
'use strict';
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
const vfs = Object.create(null);
sqlite3.vfs = vfs;
/**
Uses sqlite3_vfs_register() to register this
sqlite3.capi.sqlite3_vfs instance. This object must have already
been filled out properly. If the first argument is truthy, the
VFS is registered as the default VFS, else it is not.
On success, returns this object. Throws on error.
*/
capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){
if(!(this instanceof sqlite3.capi.sqlite3_vfs)){
toss("Expecting a sqlite3_vfs-type argument.");
}
const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0);
if(rc){
toss("sqlite3_vfs_register(",this,") failed with rc",rc);
}
if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){
toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS",
this);
}
return this;
};
/**
A wrapper for
sqlite3.StructBinder.StructType.prototype.installMethods() or
registerVfs() to reduce installation of a VFS and/or its I/O
methods to a single call.
Accepts an object which contains the properties "io" and/or
"vfs", each of which is itself an object with following properties:
- `struct`: an sqlite3.StructBinder.StructType-type struct. This
must be a populated (except for the methods) object of type
sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the
"vfs" entry).
- `methods`: an object mapping sqlite3_io_methods method names
(e.g. 'xClose') to JS implementations of those methods. The JS
implementations must be call-compatible with their native
counterparts.
For each of those object, this function passes its (`struct`,
`methods`, (optional) `applyArgcCheck`) properties to
installMethods().
If the `vfs` entry is set then:
- Its `struct` property's registerVfs() is called. The
`vfs` entry may optionally have an `asDefault` property, which
gets passed as the argument to registerVfs().
- If `struct.$zName` is falsy and the entry has a string-type
`name` property, `struct.$zName` is set to the C-string form of
that `name` value before registerVfs() is called. That string
gets added to the on-dispose state of the struct.
On success returns this object. Throws on error.
*/
vfs.installVfs = function(opt){
let count = 0;
const propList = ['io','vfs'];
for(const key of propList){
const o = opt[key];
if(o){
++count;
o.struct.installMethods(o.methods, !!o.applyArgcCheck);
if('vfs'===key){
if(!o.struct.$zName && 'string'===typeof o.name){
o.struct.addOnDispose(
o.struct.$zName = wasm.allocCString(o.name)
);
}
o.struct.registerVfs(!!o.asDefault);
}
}
}
if(!count) toss("Misuse: installVfs() options object requires at least",
"one of:", propList);
return this;
};
}/*sqlite3ApiBootstrap.initializers.push()*/);

View File

@ -1137,8 +1137,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
existing content. Throws if the pool has no available file slots,
on I/O error, or if the input does not appear to be a
database. In the latter case, only a cursory examination is made.
Note that this routine is _only_ for importing database files,
not arbitrary files, the reason being that this VFS will
Results are undefined if the given db name refers to an opened
db. Note that this routine is _only_ for importing database
files, not arbitrary files, the reason being that this VFS will
automatically clean up any non-database files so importing them
is pointless.

View File

@ -245,7 +245,8 @@ const installOpfsVfs = function callee(options){
opfsIoMethods.$iVersion = 1;
opfsVfs.$iVersion = 2/*yes, two*/;
opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
opfsVfs.$mxPathname = 1024/*sure, why not?*/;
opfsVfs.$mxPathname = 1024/* sure, why not? The OPFS name length limit
is undocumented/unspecified. */;
opfsVfs.$zName = wasm.allocCString("opfs");
// All C-side memory of opfsVfs is zeroed out, but just to be explicit:
opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null;
@ -391,6 +392,7 @@ const installOpfsVfs = function callee(options){
'SQLITE_ACCESS_EXISTS',
'SQLITE_ACCESS_READWRITE',
'SQLITE_BUSY',
'SQLITE_CANTOPEN',
'SQLITE_ERROR',
'SQLITE_IOERR',
'SQLITE_IOERR_ACCESS',
@ -422,13 +424,28 @@ const installOpfsVfs = function callee(options){
});
state.opfsFlags = Object.assign(Object.create(null),{
/**
Flag for use with xOpen(). "opfs-unlock-asap=1" enables
this. See defaultUnlockAsap, below.
Flag for use with xOpen(). URI flag "opfs-unlock-asap=1"
enables this. See defaultUnlockAsap, below.
*/
OPFS_UNLOCK_ASAP: 0x01,
/**
Flag for use with xOpen(). URI flag "delete-before-open=1"
tells the VFS to delete the db file before attempting to open
it. This can be used, e.g., to replace a db which has been
corrupted (without forcing us to expose a delete/unlink()
function in the public API).
Failure to unlink the file is ignored but may lead to
downstream errors. An unlink can fail if, e.g., another tab
has the handle open.
It goes without saying that deleting a file out from under another
instance results in Undefined Behavior.
*/
OPFS_UNLINK_BEFORE_OPEN: 0x02,
/**
If true, any async routine which implicitly acquires a sync
access handle (i.e. an OPFS lock) will release that locks at
access handle (i.e. an OPFS lock) will release that lock at
the end of the call which acquires it. If false, such
"autolocks" are not released until the VFS is idle for some
brief amount of time.
@ -455,9 +472,22 @@ const installOpfsVfs = function callee(options){
Atomics.notify(state.sabOPView, state.opIds.whichOp)
/* async thread will take over here */;
const t = performance.now();
Atomics.wait(state.sabOPView, state.opIds.rc, -1)
/* When this wait() call returns, the async half will have
completed the operation and reported its results. */;
while('not-equal'!==Atomics.wait(state.sabOPView, state.opIds.rc, -1)){
/*
The reason for this loop is buried in the details of a long
discussion at:
https://github.com/sqlite/sqlite-wasm/issues/12
Summary: in at least one browser flavor, under high loads,
the wait()/notify() pairings can get out of sync. Calling
wait() here until it returns 'not-equal' gets them back in
sync.
*/
}
/* When the above wait() call returns 'not-equal', the async
half will have completed the operation and reported its results
in the state.opIds.rc slot of the SAB. */
const rc = Atomics.load(state.sabOPView, state.opIds.rc);
metrics[op].wait += performance.now() - t;
if(rc && state.asyncS11nExceptions){
@ -704,9 +734,18 @@ const installOpfsVfs = function callee(options){
involve an inherent race condition. For the time being,
pending a better solution, we simply report whether the
given pFile is open.
Update 2024-06-12: based on forum discussions, this
function now always sets pOut to 0 (false):
https://sqlite.org/forum/forumpost/a2f573b00cda1372
*/
const f = __openFiles[pFile];
wasm.poke(pOut, f.lockType ? 1 : 0, 'i32');
if(1){
wasm.poke(pOut, 0, 'i32');
}else{
const f = __openFiles[pFile];
wasm.poke(pOut, f.lockType ? 1 : 0, 'i32');
}
return 0;
},
xClose: function(pFile){
@ -722,7 +761,6 @@ const installOpfsVfs = function callee(options){
return rc;
},
xDeviceCharacteristics: function(pFile){
//debug("xDeviceCharacteristics(",pFile,")");
return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
},
xFileControl: function(pFile, opId, pArg){
@ -874,13 +912,17 @@ const installOpfsVfs = function callee(options){
let opfsFlags = 0;
if(0===zName){
zName = randomFilename();
}else if('number'===typeof zName){
}else if(wasm.isPtr(zName)){
if(capi.sqlite3_uri_boolean(zName, "opfs-unlock-asap", 0)){
/* -----------------------^^^^^ MUST pass the untranslated
C-string here. */
opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP;
}
if(capi.sqlite3_uri_boolean(zName, "delete-before-open", 0)){
opfsFlags |= state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN;
}
zName = wasm.cstrToJs(zName);
//warn("xOpen zName =",zName, "opfsFlags =",opfsFlags);
}
const fh = Object.create(null);
fh.fid = pFile;
@ -993,27 +1035,6 @@ const installOpfsVfs = function callee(options){
*/
opfsUtil.randomFilename = randomFilename;
/**
Re-registers the OPFS VFS. This is intended only for odd use
cases which have to call sqlite3_shutdown() as part of their
initialization process, which will unregister the VFS
registered by installOpfsVfs(). If passed a truthy value, the
OPFS VFS is registered as the default VFS, else it is not made
the default. Returns the result of the the
sqlite3_vfs_register() call.
Design note: the problem of having to re-register things after
a shutdown/initialize pair is more general. How to best plug
that in to the library is unclear. In particular, we cannot
hook in to any C-side calls to sqlite3_initialize(), so we
cannot add an after-initialize callback mechanism.
*/
opfsUtil.registerVfs = (asDefault=false)=>{
return wasm.exports.sqlite3_vfs_register(
opfsVfs.pointer, asDefault ? 1 : 0
);
};
/**
Returns a promise which resolves to an object which represents
all files and directories in the OPFS tree. The top-most object
@ -1213,16 +1234,18 @@ const installOpfsVfs = function callee(options){
Asynchronously imports the given bytes (a byte array or
ArrayBuffer) into the given database file.
Results are undefined if the given db name refers to an opened
db.
If passed a function for its second argument, its behaviour
changes to async and it imports its data in chunks fed to it by
the given callback function. It calls the callback (which may
be async) repeatedly, expecting either a Uint8Array or
ArrayBuffer (to denote new input) or undefined (to denote
EOF). For so long as the callback continues to return
non-undefined, it will append incoming data to the given
VFS-hosted database file. When called this way, the resolved
value of the returned Promise is the number of bytes written to
the target file.
changes: imports its data in chunks fed to it by the given
callback function. It calls the callback (which may be async)
repeatedly, expecting either a Uint8Array or ArrayBuffer (to
denote new input) or undefined (to denote EOF). For so long as
the callback continues to return non-undefined, it will append
incoming data to the given VFS-hosted database file. When
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

View File

@ -10,19 +10,13 @@
*/
/**
This file installs sqlite3.vfs, and object which exists to assist
in the creation of JavaScript implementations of sqlite3_vfs, along
with its virtual table counterpart, sqlite3.vtab.
This file installs sqlite3.vtab, a namespace of helpers for use in
the creation of JavaScript implementations virtual tables.
*/
'use strict';
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
const vfs = Object.create(null), vtab = Object.create(null);
const StructBinder = sqlite3.StructBinder
/* we require a local alias b/c StructBinder is removed from the sqlite3
object during the final steps of the API cleanup. */;
sqlite3.vfs = vfs;
const vtab = Object.create(null);
sqlite3.vtab = vtab;
const sii = capi.sqlite3_index_info;
@ -72,257 +66,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return asPtr ? ptr : new sii.sqlite3_index_orderby(ptr);
};
/**
Installs a StructBinder-bound function pointer member of the
given name and function in the given StructType target object.
It creates a WASM proxy for the given function and arranges for
that proxy to be cleaned up when tgt.dispose() is called. Throws
on the slightest hint of error, e.g. tgt is-not-a StructType,
name does not map to a struct-bound member, etc.
As a special case, if the given function is a pointer, then
`wasm.functionEntry()` is used to validate that it is a known
function. If so, it is used as-is with no extra level of proxying
or cleanup, else an exception is thrown. It is legal to pass a
value of 0, indicating a NULL pointer, with the caveat that 0
_is_ a legal function pointer in WASM but it will not be accepted
as such _here_. (Justification: the function at address zero must
be one which initially came from the WASM module, not a method we
want to bind to a virtual table or VFS.)
This function returns a proxy for itself which is bound to tgt
and takes 2 args (name,func). That function returns the same
thing as this one, permitting calls to be chained.
If called with only 1 arg, it has no side effects but returns a
func with the same signature as described above.
ACHTUNG: because we cannot generically know how to transform JS
exceptions into result codes, the installed functions do no
automatic catching of exceptions. It is critical, to avoid
undefined behavior in the C layer, that methods mapped via
this function do not throw. The exception, as it were, to that
rule is...
If applyArgcCheck is true then each JS function (as opposed to
function pointers) gets wrapped in a proxy which asserts that it
is passed the expected number of arguments, throwing if the
argument count does not match expectations. That is only intended
for dev-time usage for sanity checking, and will leave the C
environment in an undefined state.
*/
const installMethod = function callee(
tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck
){
if(!(tgt instanceof StructBinder.StructType)){
toss("Usage error: target object is-not-a StructType.");
}else if(!(func instanceof Function) && !wasm.isPtr(func)){
toss("Usage errror: expecting a Function or WASM pointer to one.");
}
if(1===arguments.length){
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
}
if(!callee.argcProxy){
callee.argcProxy = function(tgt, funcName, func,sig){
return function(...args){
if(func.length!==arguments.length){
toss("Argument mismatch for",
tgt.structInfo.name+"::"+funcName
+": Native signature is:",sig);
}
return func.apply(this, args);
}
};
/* An ondispose() callback for use with
StructBinder-created types. */
callee.removeFuncList = function(){
if(this.ondispose.__removeFuncList){
this.ondispose.__removeFuncList.forEach(
(v,ndx)=>{
if('number'===typeof v){
try{wasm.uninstallFunction(v)}
catch(e){/*ignore*/}
}
/* else it's a descriptive label for the next number in
the list. */
}
);
delete this.ondispose.__removeFuncList;
}
};
}/*static init*/
const sigN = tgt.memberSignature(name);
if(sigN.length<2){
toss("Member",name,"does not have a function pointer signature:",sigN);
}
const memKey = tgt.memberKey(name);
const fProxy = (applyArgcCheck && !wasm.isPtr(func))
/** This middle-man proxy is only for use during development, to
confirm that we always pass the proper number of
arguments. We know that the C-level code will always use the
correct argument count. */
? callee.argcProxy(tgt, memKey, func, sigN)
: func;
if(wasm.isPtr(fProxy)){
if(fProxy && !wasm.functionEntry(fProxy)){
toss("Pointer",fProxy,"is not a WASM function table entry.");
}
tgt[memKey] = fProxy;
}else{
const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
tgt[memKey] = pFunc;
if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){
tgt.addOnDispose('ondispose.__removeFuncList handler',
callee.removeFuncList);
tgt.ondispose.__removeFuncList = [];
}
tgt.ondispose.__removeFuncList.push(memKey, pFunc);
}
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
}/*installMethod*/;
installMethod.installMethodArgcCheck = false;
/**
Installs methods into the given StructType-type instance. Each
entry in the given methods object must map to a known member of
the given StructType, else an exception will be triggered. See
installMethod() for more details, including the semantics of the
3rd argument.
As an exception to the above, if any two or more methods in the
2nd argument are the exact same function, installMethod() is
_not_ called for the 2nd and subsequent instances, and instead
those instances get assigned the same method pointer which is
created for the first instance. This optimization is primarily to
accommodate special handling of sqlite3_module::xConnect and
xCreate methods.
On success, returns its first argument. Throws on error.
*/
const installMethods = function(
structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck
){
const seen = new Map /* map of <Function, memberName> */;
for(const k of Object.keys(methods)){
const m = methods[k];
const prior = seen.get(m);
if(prior){
const mkey = structInstance.memberKey(k);
structInstance[mkey] = structInstance[structInstance.memberKey(prior)];
}else{
installMethod(structInstance, k, m, applyArgcCheck);
seen.set(m, k);
}
}
return structInstance;
};
/**
Equivalent to calling installMethod(this,...arguments) with a
first argument of this object. If called with 1 or 2 arguments
and the first is an object, it's instead equivalent to calling
installMethods(this,...arguments).
*/
StructBinder.StructType.prototype.installMethod = function callee(
name, func, applyArgcCheck = installMethod.installMethodArgcCheck
){
return (arguments.length < 3 && name && 'object'===typeof name)
? installMethods(this, ...arguments)
: installMethod(this, ...arguments);
};
/**
Equivalent to calling installMethods() with a first argument
of this object.
*/
StructBinder.StructType.prototype.installMethods = function(
methods, applyArgcCheck = installMethod.installMethodArgcCheck
){
return installMethods(this, methods, applyArgcCheck);
};
/**
Uses sqlite3_vfs_register() to register this
sqlite3.capi.sqlite3_vfs. This object must have already been
filled out properly. If the first argument is truthy, the VFS is
registered as the default VFS, else it is not.
On success, returns this object. Throws on error.
*/
capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){
if(!(this instanceof sqlite3.capi.sqlite3_vfs)){
toss("Expecting a sqlite3_vfs-type argument.");
}
const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0);
if(rc){
toss("sqlite3_vfs_register(",this,") failed with rc",rc);
}
if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){
toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS",
this);
}
return this;
};
/**
A wrapper for installMethods() or registerVfs() to reduce
installation of a VFS and/or its I/O methods to a single
call.
Accepts an object which contains the properties "io" and/or
"vfs", each of which is itself an object with following properties:
- `struct`: an sqlite3.StructType-type struct. This must be a
populated (except for the methods) object of type
sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the
"vfs" entry).
- `methods`: an object mapping sqlite3_io_methods method names
(e.g. 'xClose') to JS implementations of those methods. The JS
implementations must be call-compatible with their native
counterparts.
For each of those object, this function passes its (`struct`,
`methods`, (optional) `applyArgcCheck`) properties to
installMethods().
If the `vfs` entry is set then:
- Its `struct` property's registerVfs() is called. The
`vfs` entry may optionally have an `asDefault` property, which
gets passed as the argument to registerVfs().
- If `struct.$zName` is falsy and the entry has a string-type
`name` property, `struct.$zName` is set to the C-string form of
that `name` value before registerVfs() is called. That string
gets added to the on-dispose state of the struct.
On success returns this object. Throws on error.
*/
vfs.installVfs = function(opt){
let count = 0;
const propList = ['io','vfs'];
for(const key of propList){
const o = opt[key];
if(o){
++count;
installMethods(o.struct, o.methods, !!o.applyArgcCheck);
if('vfs'===key){
if(!o.struct.$zName && 'string'===typeof o.name){
o.struct.addOnDispose(
o.struct.$zName = wasm.allocCString(o.name)
);
}
o.struct.registerVfs(!!o.asDefault);
}
}
}
if(!count) toss("Misuse: installVfs() options object requires at least",
"one of:", propList);
return this;
};
/**
Internal factory function for xVtab and xCursor impls.
*/
@ -456,30 +199,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
*/
vtab.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo);
/**
Given an error object, this function returns
sqlite3.capi.SQLITE_NOMEM if (e instanceof
sqlite3.WasmAllocError), else it returns its
second argument. Its intended usage is in the methods
of a sqlite3_vfs or sqlite3_module:
```
try{
let rc = ...
return rc;
}catch(e){
return sqlite3.vtab.exceptionToRc(e, sqlite3.capi.SQLITE_XYZ);
// where SQLITE_XYZ is some call-appropriate result code.
}
```
*/
/**vfs.exceptionToRc = vtab.exceptionToRc =
(e, defaultRc=capi.SQLITE_ERROR)=>(
(e instanceof sqlite3.WasmAllocError)
? capi.SQLITE_NOMEM
: defaultRc
);*/
/**
Given an sqlite3_module method name and error object, this
function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof
@ -525,20 +244,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
};
vtab.xError.errorReporter = 1 ? console.error.bind(console) : false;
/**
"The problem" with this is that it introduces an outer function with
a different arity than the passed-in method callback. That means we
cannot do argc validation on these. Additionally, some methods (namely
xConnect) may have call-specific error handling. It would be a shame to
hard-coded that per-method support in this function.
*/
/** vtab.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){
return function(...args){
try { method(...args); }
}catch(e){ return vtab.xError(methodName, e, defaultRc) }
};
*/
/**
A helper for sqlite3_vtab::xRowid() and xUpdate()
implementations. It must be passed the final argument to one of
@ -685,12 +390,12 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
remethods[k] = fwrap(k, m);
}
}
installMethods(mod, remethods, false);
mod.installMethods(remethods, false);
}else{
// No automatic exception handling. Trust the client
// to not throw.
installMethods(
mod, methods, !!opt.applyArgcCheck/*undocumented option*/
mod.installMethods(
methods, !!opt.applyArgcCheck/*undocumented option*/
);
}
if(0===mod.$iVersion){

View File

@ -238,28 +238,28 @@
** Another option is to malloc() a chunk of our own and call that our
** "stack".
*/
SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_end(void){
SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_end(void){
extern void __heap_base
/* see https://stackoverflow.com/questions/10038964 */;
return &__heap_base;
}
SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_begin(void){
SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_begin(void){
extern void __data_end;
return &__data_end;
}
static void * pWasmStackPtr = 0;
SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_ptr(void){
if(!pWasmStackPtr) pWasmStackPtr = sqlite3_wasm_stack_end();
SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_ptr(void){
if(!pWasmStackPtr) pWasmStackPtr = sqlite3__wasm_stack_end();
return pWasmStackPtr;
}
SQLITE_WASM_EXPORT void sqlite3_wasm_stack_restore(void * p){
SQLITE_WASM_EXPORT void sqlite3__wasm_stack_restore(void * p){
pWasmStackPtr = p;
}
SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_alloc(int n){
SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_alloc(int n){
if(n<=0) return 0;
n = (n + 7) & ~7 /* align to 8-byte boundary */;
unsigned char * const p = (unsigned char *)sqlite3_wasm_stack_ptr();
unsigned const char * const b = (unsigned const char *)sqlite3_wasm_stack_begin();
unsigned char * const p = (unsigned char *)sqlite3__wasm_stack_ptr();
unsigned const char * const b = (unsigned const char *)sqlite3__wasm_stack_begin();
if(b + n >= p || b + n < b/*overflow*/) return 0;
return pWasmStackPtr = p - n;
}
@ -267,7 +267,7 @@ SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_alloc(int n){
/*
** State for the "pseudo-stack" allocator implemented in
** sqlite3_wasm_pstack_xyz(). In order to avoid colliding with
** sqlite3__wasm_pstack_xyz(). In order to avoid colliding with
** Emscripten-controled stack space, it carves out a bit of stack
** memory to use for that purpose. This memory ends up in the
** WASM-managed memory, such that routines which manipulate the wasm
@ -291,14 +291,14 @@ static struct {
/*
** Returns the current pstack position.
*/
SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_ptr(void){
SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_ptr(void){
return PStack.pPos;
}
/*
** Sets the pstack position poitner to p. Results are undefined if the
** given value did not come from sqlite3_wasm_pstack_ptr().
** given value did not come from sqlite3__wasm_pstack_ptr().
*/
SQLITE_WASM_EXPORT void sqlite3_wasm_pstack_restore(unsigned char * p){
SQLITE_WASM_EXPORT void sqlite3__wasm_pstack_restore(unsigned char * p){
assert(p>=PStack.pBegin && p<=PStack.pEnd && p>=PStack.pPos);
assert(0==((unsigned long long)p & 0x7));
if(p>=PStack.pBegin && p<=PStack.pEnd /*&& p>=PStack.pPos*/){
@ -313,7 +313,7 @@ SQLITE_WASM_EXPORT void sqlite3_wasm_pstack_restore(unsigned char * p){
** JS code from having to do so, and most uses of the pstack will
** call for doing so).
*/
SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_alloc(int n){
SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_alloc(int n){
if( n<=0 ) return 0;
//if( n & 0x7 ) n += 8 - (n & 0x7) /* align to 8-byte boundary */;
n = (n + 7) & ~7 /* align to 8-byte boundary */;
@ -324,9 +324,9 @@ SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_alloc(int n){
}
/*
** Return the number of bytes left which can be
** sqlite3_wasm_pstack_alloc()'d.
** sqlite3__wasm_pstack_alloc()'d.
*/
SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_remaining(void){
SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_remaining(void){
assert(PStack.pPos >= PStack.pBegin);
assert(PStack.pPos <= PStack.pEnd);
return (int)(PStack.pPos - PStack.pBegin);
@ -337,7 +337,7 @@ SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_remaining(void){
** any space which is currently allocated. This value is a
** compile-time constant.
*/
SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_quota(void){
SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_quota(void){
return (int)(PStack.pEnd - PStack.pBegin);
}
@ -356,7 +356,7 @@ SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_quota(void){
** Returns err_code.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){
int sqlite3__wasm_db_error(sqlite3*db, int err_code, const char *zMsg){
if( db!=0 ){
if( 0!=zMsg ){
const int nMsg = sqlite3Strlen30(zMsg);
@ -380,7 +380,7 @@ struct WasmTestStruct {
};
typedef struct WasmTestStruct WasmTestStruct;
SQLITE_WASM_EXPORT
void sqlite3_wasm_test_struct(WasmTestStruct * s){
void sqlite3__wasm_test_struct(WasmTestStruct * s){
if(s){
s->v4 *= 2;
s->v8 = s->v4 * 2;
@ -408,7 +408,7 @@ void sqlite3_wasm_test_struct(WasmTestStruct * s){
** increased. In debug builds that will trigger an assert().
*/
SQLITE_WASM_EXPORT
const char * sqlite3_wasm_enum_json(void){
const char * sqlite3__wasm_enum_json(void){
static char aBuffer[1024 * 20] = {0} /* where the JSON goes */;
int n = 0, nChildren = 0, nStruct = 0
/* output counters for figuring out where commas go */;
@ -425,7 +425,7 @@ const char * sqlite3_wasm_enum_json(void){
/* Core output macros... */
#define lenCheck assert(zPos < zEnd - 128 \
&& "sqlite3_wasm_enum_json() buffer is too small."); \
&& "sqlite3__wasm_enum_json() buffer is too small."); \
if( zPos >= zEnd - 128 ) return 0
#define outf(format,...) \
zPos += snprintf(zPos, ((size_t)(zEnd - zPos)), format, __VA_ARGS__); \
@ -545,6 +545,10 @@ const char * sqlite3_wasm_enum_json(void){
DefInt(SQLITE_CONFIG_SMALL_MALLOC);
DefInt(SQLITE_CONFIG_SORTERREF_SIZE);
DefInt(SQLITE_CONFIG_MEMDB_MAXSIZE);
/* maintenance note: we specifically do not include
SQLITE_CONFIG_ROWID_IN_VIEW here, on the grounds that
it's only for legacy support and no apps written with
this API require that. */
} _DefGroup;
DefGroup(dataTypes) {
@ -1220,7 +1224,7 @@ const char * sqlite3_wasm_enum_json(void){
** call is returned.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){
int sqlite3__wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){
int rc = SQLITE_MISUSE /* ??? */;
if( 0==pVfs && 0!=zName ) pVfs = sqlite3_vfs_find(0);
if( zName && pVfs && pVfs->xDelete ){
@ -1238,7 +1242,7 @@ int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){
** given name is open.
*/
SQLITE_WASM_EXPORT
sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){
sqlite3_vfs * sqlite3__wasm_db_vfs(sqlite3 *pDb, const char *zDbName){
sqlite3_vfs * pVfs = 0;
sqlite3_file_control(pDb, zDbName ? zDbName : "main",
SQLITE_FCNTL_VFS_POINTER, &pVfs);
@ -1261,7 +1265,7 @@ sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){
** SQLITE_MISUSE if pDb is NULL.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_db_reset(sqlite3 *pDb){
int sqlite3__wasm_db_reset(sqlite3 *pDb){
int rc = SQLITE_MISUSE;
if( pDb ){
sqlite3_table_column_metadata(pDb, "main", 0, 0, 0, 0, 0, 0, 0);
@ -1288,11 +1292,11 @@ int sqlite3_wasm_db_reset(sqlite3 *pDb){
** takes no measures to ensure that is the case.
**
** This implementation appears to work fine, but
** sqlite3_wasm_db_serialize() is arguably the better way to achieve
** sqlite3__wasm_db_serialize() is arguably the better way to achieve
** this.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_db_export_chunked( sqlite3* pDb,
int sqlite3__wasm_db_export_chunked( sqlite3* pDb,
int (*xCallback)(unsigned const char *zOut, int n) ){
sqlite3_int64 nSize = 0;
sqlite3_int64 nPos = 0;
@ -1343,7 +1347,7 @@ int sqlite3_wasm_db_export_chunked( sqlite3* pDb,
** sqlite3_free() to free it.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
int sqlite3__wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
unsigned char **pOut,
sqlite3_int64 *nOut, unsigned int mFlags ){
unsigned char * z;
@ -1366,7 +1370,7 @@ int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
** this function's out-of-scope use of the sqlite3_vfs/file/io_methods
** APIs leads to triggering of assertions in the core library. Its use
** is now deprecated and VFS-specific APIs for importing files need to
** be found to replace it. sqlite3_wasm_posix_create_file() is
** be found to replace it. sqlite3__wasm_posix_create_file() is
** suitable for the "unix" family of VFSes.
**
** Creates a new file using the I/O API of the given VFS, containing
@ -1407,7 +1411,7 @@ int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
** support is disabled or unavailable.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs,
int sqlite3__wasm_vfs_create_file( sqlite3_vfs *pVfs,
const char *zFilename,
const unsigned char * pData,
int nData ){
@ -1497,7 +1501,7 @@ int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs,
** SQLITE_IOERR on error.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_posix_create_file( const char *zFilename,
int sqlite3__wasm_posix_create_file( const char *zFilename,
const unsigned char * pData,
int nData ){
int rc;
@ -1520,17 +1524,17 @@ int sqlite3_wasm_posix_create_file( const char *zFilename,
** for use by the sqlite project's own JS/WASM bindings.
**
** Allocates sqlite3KvvfsMethods.nKeySize bytes from
** sqlite3_wasm_pstack_alloc() and returns 0 if that allocation fails,
** sqlite3__wasm_pstack_alloc() and returns 0 if that allocation fails,
** else it passes that string to kvstorageMakeKey() and returns a
** NUL-terminated pointer to that string. It is up to the caller to
** use sqlite3_wasm_pstack_restore() to free the returned pointer.
** use sqlite3__wasm_pstack_restore() to free the returned pointer.
*/
SQLITE_WASM_EXPORT
char * sqlite3_wasm_kvvfsMakeKeyOnPstack(const char *zClass,
char * sqlite3__wasm_kvvfsMakeKeyOnPstack(const char *zClass,
const char *zKeyIn){
assert(sqlite3KvvfsMethods.nKeySize>24);
char *zKeyOut =
(char *)sqlite3_wasm_pstack_alloc(sqlite3KvvfsMethods.nKeySize);
(char *)sqlite3__wasm_pstack_alloc(sqlite3KvvfsMethods.nKeySize);
if(zKeyOut){
kvstorageMakeKey(zClass, zKeyIn, zKeyOut);
}
@ -1545,7 +1549,7 @@ char * sqlite3_wasm_kvvfsMakeKeyOnPstack(const char *zClass,
** I/O methods and associated state.
*/
SQLITE_WASM_EXPORT
sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){
sqlite3_kvvfs_methods * sqlite3__wasm_kvvfs_methods(void){
return &sqlite3KvvfsMethods;
}
@ -1560,7 +1564,7 @@ sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){
** valid value.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){
int sqlite3__wasm_vtab_config(sqlite3 *pDb, int op, int arg){
switch(op){
case SQLITE_VTAB_DIRECTONLY:
case SQLITE_VTAB_INNOCUOUS:
@ -1580,7 +1584,7 @@ int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){
** (int,int*) variadic args.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){
int sqlite3__wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){
switch(op){
case SQLITE_DBCONFIG_ENABLE_FKEY:
case SQLITE_DBCONFIG_ENABLE_TRIGGER:
@ -1613,7 +1617,7 @@ int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){
** (void*,int,int) variadic args.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){
int sqlite3__wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){
switch(op){
case SQLITE_DBCONFIG_LOOKASIDE:
return sqlite3_db_config(pDb, op, pArg1, arg2, arg3);
@ -1629,7 +1633,7 @@ int sqlite3_wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int
** (const char *) variadic args.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){
int sqlite3__wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){
switch(op){
case SQLITE_DBCONFIG_MAINDBNAME:
return sqlite3_db_config(pDb, op, zArg);
@ -1646,7 +1650,7 @@ int sqlite3_wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){
** a single integer argument.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_config_i(int op, int arg){
int sqlite3__wasm_config_i(int op, int arg){
return sqlite3_config(op, arg);
}
@ -1658,7 +1662,7 @@ int sqlite3_wasm_config_i(int op, int arg){
** two int arguments.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_config_ii(int op, int arg1, int arg2){
int sqlite3__wasm_config_ii(int op, int arg1, int arg2){
return sqlite3_config(op, arg1, arg2);
}
@ -1670,39 +1674,28 @@ int sqlite3_wasm_config_ii(int op, int arg1, int arg2){
** a single i64 argument.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_config_j(int op, sqlite3_int64 arg){
int sqlite3__wasm_config_j(int op, sqlite3_int64 arg){
return sqlite3_config(op, arg);
}
#if 0
// Pending removal after verification of a workaround discussed in the
// forum post linked to below.
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.
**
** Returns a pointer to sqlite3_free(). In compliant browsers the
** return value, when passed to sqlite3.wasm.exports.functionEntry(),
** must resolve to the same function as
** sqlite3.wasm.exports.sqlite3_free. i.e. from a dev console where
** sqlite3 is exported globally, the following must be true:
**
** ```
** sqlite3.wasm.functionEntry(
** sqlite3.wasm.exports.sqlite3_wasm_ptr_to_sqlite3_free()
** ) === sqlite3.wasm.exports.sqlite3_free
** ```
**
** Using a function to return this pointer, as opposed to exporting it
** via sqlite3_wasm_enum_json(), is an attempt to work around a
** Safari-specific quirk covered at
** https://sqlite.org/forum/info/e5b20e1feb37a19a.
**/
** If z is not NULL, returns the result of passing z to
** sqlite3_mprintf()'s %Q modifier (if addQuotes is true) or %q (if
** addQuotes is 0). Returns NULL if z is NULL or on OOM.
*/
SQLITE_WASM_EXPORT
void * sqlite3_wasm_ptr_to_sqlite3_free(void){
return (void*)sqlite3_free;
char * sqlite3__wasm_qfmt_token(char *z, int addQuotes){
char * rc = 0;
if( z ){
rc = addQuotes
? sqlite3_mprintf("%Q", z)
: sqlite3_mprintf("%q", z);
}
return rc;
}
#endif
#if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS)
#include <emscripten/wasmfs.h>
@ -1729,7 +1722,7 @@ void * sqlite3_wasm_ptr_to_sqlite3_free(void){
** defined, SQLITE_NOTFOUND is returned without side effects.
*/
SQLITE_WASM_EXPORT
int sqlite3_wasm_init_wasmfs(const char *zMountPoint){
int sqlite3__wasm_init_wasmfs(const char *zMountPoint){
static backend_t pOpfs = 0;
if( !zMountPoint || !*zMountPoint ) zMountPoint = "/opfs";
if( !pOpfs ){
@ -1749,7 +1742,7 @@ int sqlite3_wasm_init_wasmfs(const char *zMountPoint){
}
#else
SQLITE_WASM_EXPORT
int sqlite3_wasm_init_wasmfs(const char *zUnused){
int sqlite3__wasm_init_wasmfs(const char *zUnused){
//emscripten_console_warn("WASMFS OPFS is not compiled in.");
if(zUnused){/*unused*/}
return SQLITE_NOTFOUND;
@ -1759,51 +1752,51 @@ int sqlite3_wasm_init_wasmfs(const char *zUnused){
#if SQLITE_WASM_TESTS
SQLITE_WASM_EXPORT
int sqlite3_wasm_test_intptr(int * p){
int sqlite3__wasm_test_intptr(int * p){
return *p = *p * 2;
}
SQLITE_WASM_EXPORT
void * sqlite3_wasm_test_voidptr(void * p){
void * sqlite3__wasm_test_voidptr(void * p){
return p;
}
SQLITE_WASM_EXPORT
int64_t sqlite3_wasm_test_int64_max(void){
int64_t sqlite3__wasm_test_int64_max(void){
return (int64_t)0x7fffffffffffffff;
}
SQLITE_WASM_EXPORT
int64_t sqlite3_wasm_test_int64_min(void){
return ~sqlite3_wasm_test_int64_max();
int64_t sqlite3__wasm_test_int64_min(void){
return ~sqlite3__wasm_test_int64_max();
}
SQLITE_WASM_EXPORT
int64_t sqlite3_wasm_test_int64_times2(int64_t x){
int64_t sqlite3__wasm_test_int64_times2(int64_t x){
return x * 2;
}
SQLITE_WASM_EXPORT
void sqlite3_wasm_test_int64_minmax(int64_t * min, int64_t *max){
*max = sqlite3_wasm_test_int64_max();
*min = sqlite3_wasm_test_int64_min();
void sqlite3__wasm_test_int64_minmax(int64_t * min, int64_t *max){
*max = sqlite3__wasm_test_int64_max();
*min = sqlite3__wasm_test_int64_min();
/*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/
}
SQLITE_WASM_EXPORT
int64_t sqlite3_wasm_test_int64ptr(int64_t * p){
/*printf("sqlite3_wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/
int64_t sqlite3__wasm_test_int64ptr(int64_t * p){
/*printf("sqlite3__wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/
return *p = *p * 2;
}
SQLITE_WASM_EXPORT
void sqlite3_wasm_test_stack_overflow(int recurse){
if(recurse) sqlite3_wasm_test_stack_overflow(recurse);
void sqlite3__wasm_test_stack_overflow(int recurse){
if(recurse) sqlite3__wasm_test_stack_overflow(recurse);
}
/* For testing the 'string:dealloc' whwasmutil.xWrap() conversion. */
SQLITE_WASM_EXPORT
char * sqlite3_wasm_test_str_hello(int fail){
char * sqlite3__wasm_test_str_hello(int fail){
char * s = fail ? 0 : (char *)sqlite3_malloc(6);
if(s){
memcpy(s, "hello", 5);
@ -1838,12 +1831,12 @@ char * sqlite3_wasm_test_str_hello(int fail){
** optional + or - sign in front, or a hexadecimal
** literal of the form 0x...
*/
static int sqlite3_wasm_SQLTester_strnotglob(const char *zGlob, const char *z){
static int sqlite3__wasm_SQLTester_strnotglob(const char *zGlob, const char *z){
int c, c2;
int invert;
int seen;
typedef int (*recurse_f)(const char *,const char *);
static const recurse_f recurse = sqlite3_wasm_SQLTester_strnotglob;
static const recurse_f recurse = sqlite3__wasm_SQLTester_strnotglob;
while( (c = (*(zGlob++)))!=0 ){
if( c=='*' ){
@ -1918,11 +1911,10 @@ static int sqlite3_wasm_SQLTester_strnotglob(const char *zGlob, const char *z){
}
SQLITE_WASM_EXPORT
int sqlite3_wasm_SQLTester_strglob(const char *zGlob, const char *z){
return !sqlite3_wasm_SQLTester_strnotglob(zGlob, z);
int sqlite3__wasm_SQLTester_strglob(const char *zGlob, const char *z){
return !sqlite3__wasm_SQLTester_strnotglob(zGlob, z);
}
#endif /* SQLITE_WASM_TESTS */
#undef SQLITE_WASM_EXPORT

View File

@ -42,9 +42,13 @@
- `onready` (optional, but...): this callback is called with no
arguments when the worker fires its initial
'sqlite3-api'/'worker1-ready' message, which it does when
sqlite3.initWorker1API() completes its initialization. This is
the simplest way to tell the worker to kick off work at the
earliest opportunity.
sqlite3.initWorker1API() completes its initialization. This is the
simplest way to tell the worker to kick off work at the earliest
opportunity, and the only way to know when the worker module has
completed loading. The irony of using a callback for this, instead
of returning a promise from sqlite3Worker1Promiser() is not lost on
the developers: see sqlite3Worker1Promiser.v2() which uses a
Promise instead.
- `onunhandled` (optional): a callback which gets passed the
message event object for any worker.onmessage() events which
@ -156,6 +160,7 @@ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfi
if(!config.worker) config.worker = callee.defaultConfig.worker;
if('function'===typeof config.worker) config.worker = config.worker();
let dbId;
let promiserFunc;
config.worker.onmessage = function(ev){
ev = ev.data;
debug('worker1.onmessage',ev);
@ -163,7 +168,7 @@ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfi
if(!msgHandler){
if(ev && 'sqlite3-api'===ev.type && 'worker1-ready'===ev.result) {
/*fired one time when the Worker1 API initializes*/
if(config.onready) config.onready();
if(config.onready) config.onready(promiserFunc);
return;
}
msgHandler = handlerMap[ev.type] /* check for exec per-row callback */;
@ -192,7 +197,7 @@ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfi
try {msgHandler.resolve(ev)}
catch(e){msgHandler.reject(e)}
}/*worker.onmessage()*/;
return function(/*(msgType, msgArgs) || (msgEnvelope)*/){
return promiserFunc = function(/*(msgType, msgArgs) || (msgEnvelope)*/){
let msg;
if(1===arguments.length){
msg = arguments[0];
@ -202,7 +207,7 @@ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfi
msg.args = arguments[1];
msg.dbId = msg.args.dbId;
}else{
toss("Invalid arugments for sqlite3Worker1Promiser()-created factory.");
toss("Invalid arguments for sqlite3Worker1Promiser()-created factory.");
}
if(!msg.dbId && msg.type!=='open') msg.dbId = dbId;
msg.messageId = genMsgId(msg);
@ -246,9 +251,10 @@ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfi
return p;
};
}/*sqlite3Worker1Promiser()*/;
globalThis.sqlite3Worker1Promiser.defaultConfig = {
worker: function(){
//#if target=es6-bundler-friendly
//#if target=es6-module
return new Worker(new URL("sqlite3-worker1-bundler-friendly.mjs", import.meta.url),{
type: 'module'
});
@ -269,14 +275,72 @@ globalThis.sqlite3Worker1Promiser.defaultConfig = {
return new Worker(theJs + globalThis.location.search);
//#endif
}
//#ifnot target=es6-bundler-friendly
//#ifnot target=es6-module
.bind({
currentScript: globalThis?.document?.currentScript
})
//#endif
,
onerror: (...args)=>console.error('worker1 promiser error',...args)
};
}/*defaultConfig*/;
/**
sqlite3Worker1Promiser.v2(), added in 3.46, works identically to
sqlite3Worker1Promiser() except that it returns a Promise instead
of relying an an onready callback in the config object. The Promise
resolves to the same factory function which
sqlite3Worker1Promiser() returns.
If config is-a function or is an object which contains an onready
function, that function is replaced by a proxy which will resolve
after calling the original function and will reject if that
function throws.
*/
sqlite3Worker1Promiser.v2 = function(config){
let oldFunc;
if( 'function' == typeof config ){
oldFunc = config;
config = {};
}else if('function'===typeof config?.onready){
oldFunc = config.onready;
delete config.onready;
}
const promiseProxy = Object.create(null);
config = Object.assign((config || Object.create(null)),{
onready: async function(func){
try {
if( oldFunc ) await oldFunc(func);
promiseProxy.resolve(func);
}
catch(e){promiseProxy.reject(e)}
}
});
const p = new Promise(function(resolve,reject){
promiseProxy.resolve = resolve;
promiseProxy.reject = reject;
});
try{
this.original(config);
}catch(e){
promiseProxy.reject(e);
}
return p;
}.bind({
/* We do this because clients are
recommended to delete globalThis.sqlite3Worker1Promiser. */
original: sqlite3Worker1Promiser
});
//#if target=es6-module
/**
When built as a module, we export sqlite3Worker1Promiser.v2()
instead of sqlite3Worker1Promise() because (A) its interface is more
conventional for ESM usage and (B) the ESM option export option for
this API did not exist until v2 was created, so there's no backwards
incompatibility.
*/
export default sqlite3Worker1Promiser.v2;
//#endif /* target=es6-module */
//#else
/* Built with the omit-oo1 flag. */
//#endif ifnot omit-oo1

View File

@ -1382,15 +1382,19 @@ globalThis.WhWasmUtilInstaller = function(target){
conversion of argument or return types, but see xWrap() and
xCallWrapped() for variants which do.
If the first argument is a function is is assumed to be
a WASM-bound function and is used as-is instead of looking up
the function via xGet().
As a special case, if passed only 1 argument after the name and
that argument in an Array, that array's entries become the
function arguments. (This is not an ambiguous case because it's
not legal to pass an Array object to a WASM function.)
*/
target.xCall = function(fname, ...args){
const f = target.xGet(fname);
const f = (fname instanceof Function) ? fname : target.xGet(fname);
if(!(f instanceof Function)) toss("Exported symbol",fname,"is not a function.");
if(f.length!==args.length) __argcMismatch(fname,f.length)
if(f.length!==args.length) __argcMismatch(((f===fname) ? f.name : fname),f.length)
/* This is arguably over-pedantic but we want to help clients keep
from shooting themselves in the foot when calling C APIs. */;
return (2===arguments.length && Array.isArray(arguments[1]))
@ -1537,7 +1541,7 @@ globalThis.WhWasmUtilInstaller = function(target){
jsFuncToWasm().
- bindScope (string): one of ('transient', 'context',
'singleton'). Bind scopes are:
'singleton', 'permanent'). Bind scopes are:
- 'transient': it will convert JS functions to WASM only for
the duration of the xWrap()'d function call, using
@ -1787,11 +1791,29 @@ globalThis.WhWasmUtilInstaller = function(target){
const __xResultAdapterCheck =
(t)=>xResult.get(t) || toss("Result adapter not found:",t);
/**
Fetches the xWrap() argument adapter mapped to t, calls it,
passing in all remaining arguments, and returns the result.
Throws if t is not mapped to an argument converter.
*/
cache.xWrap.convertArg = (t,...args)=>__xArgAdapterCheck(t)(...args);
/**
Identical to convertArg() except that it does not perform
an is-defined check on the mapping to t before invoking it.
*/
cache.xWrap.convertArgNoCheck = (t,...args)=>xArg.get(t)(...args);
/**
Fetches the xWrap() result adapter mapped to t, calls it, passing
it v, and returns the result. Throws if t is not mapped to an
argument converter.
*/
cache.xWrap.convertResult =
(t,v)=>(null===t ? v : (t ? __xResultAdapterCheck(t)(v) : undefined));
/**
Identical to convertResult() except that it does not perform an
is-defined check on the mapping to t before invoking it.
*/
cache.xWrap.convertResultNoCheck =
(t,v)=>(null===t ? v : (t ? xResult.get(t)(v) : undefined));
@ -1903,15 +1925,15 @@ globalThis.WhWasmUtilInstaller = function(target){
const C-string, encoded as UTF-8, copies it to a JS string,
and returns that JS string.
- `string:dealloc` or `utf8:dealloc) (results): treats the result value
as a non-const UTF-8 C-string, ownership of which has just been
transfered to the caller. It copies the C-string to a JS
string, frees the C-string, and returns the JS string. If such
a result value is NULL, the JS result is `null`. Achtung: when
using an API which returns results from a specific allocator,
e.g. `my_malloc()`, this conversion _is not legal_. Instead, an
equivalent conversion which uses the appropriate deallocator is
required. For example:
- `string:dealloc` or `utf8:dealloc` (results): treats the result
value as a non-const UTF-8 C-string, ownership of which has
just been transfered to the caller. It copies the C-string to a
JS string, frees the C-string, and returns the JS string. If
such a result value is NULL, the JS result is `null`. Achtung:
when using an API which returns results from a specific
allocator, e.g. `my_malloc()`, this conversion _is not
legal_. Instead, an equivalent conversion which uses the
appropriate deallocator is required. For example:
```js
target.xWrap.resultAdapter('string:my_free',(i)=>{
@ -2012,8 +2034,12 @@ globalThis.WhWasmUtilInstaller = function(target){
arguments may be passed in after that one, and what those
arguments are, is _not_ part of the public interface and is
_not_ stable.
Maintenance reminder: the Ember framework modifies the core
Array type, breaking for-in loops.
*/
for(const i in args) args[i] = cxw.convertArgNoCheck(
let i = 0;
for(; i < args.length; ++i) args[i] = cxw.convertArgNoCheck(
argTypes[i], args[i], args, i
);
return cxw.convertResultNoCheck(resultType, xf.apply(null,args));

View File

@ -6,7 +6,11 @@
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
<link rel="stylesheet" href="common/emscripten.css"/>
<link rel="stylesheet" href="common/testing.css"/>
//#if target=es6-module
<title>worker-promise (via ESM) tests</title>
//#else
<title>worker-promise tests</title>
//#endif
</head>
<body>
<header id='titlebar'><span>worker-promise tests</span></header>
@ -22,13 +26,17 @@
</figure>
<div class="emscripten" id="module-status">Downloading...</div>
<div class="emscripten">
<progress value="0" max="100" id="module-progress" hidden='1'></progress>
<progress value="0" max="100" id="module-progress" hidden='1'></progress>
</div><!-- /emscripten bits -->
<div>Most stuff on this page happens in the dev console.</div>
<hr>
<div id='test-output'></div>
<script src="common/SqliteTestUtil.js"></script>
//#if target=es6-module
<script src="demo-worker1-promiser.mjs" type="module"></script>
//#else
<script src="jswasm/sqlite3-worker1-promiser.js"></script>
<script src="demo-worker1-promiser.js"></script>
//#endif
</body>
</html>

View File

@ -13,9 +13,15 @@
Demonstration of the sqlite3 Worker API #1 Promiser: a Promise-based
proxy for for the sqlite3 Worker #1 API.
*/
'use strict';
(function(){
const T = self.SqliteTestUtil;
//#if target=es6-module
import {default as promiserFactory} from "./jswasm/sqlite3-worker1-promiser.mjs";
//#else
"use strict";
const promiserFactory = globalThis.sqlite3Worker1Promiser.v2;
delete globalThis.sqlite3Worker1Promiser;
//#endif
(async function(){
const T = globalThis.SqliteTestUtil;
const eOutput = document.querySelector('#test-output');
const warn = console.warn.bind(console);
const error = console.error.bind(console);
@ -33,31 +39,35 @@
logHtml("","Total test count:",T.counter+". Total time =",(performance.now() - startTime),"ms");
};
//why is this triggered even when we catch() a Promise?
//window.addEventListener('unhandledrejection', function(event) {
// warn('unhandledrejection',event);
//});
const promiserConfig = {
worker: ()=>{
const w = new Worker("jswasm/sqlite3-worker1.js");
w.onerror = (event)=>error("worker.onerror",event);
return w;
//#ifnot target=es6-module
/**
The v1 interfaces uses an onready function. The v2 interface optionally
accepts one but does not require it. If provided, it is called _before_
the promise is resolved, and the promise is rejected if onready() throws.
*/
onready: function(f){
/* f === the function returned by promiserFactory().
Ostensibly (f === workerPromise) but this function is
called before the promiserFactory() Promise resolves, so
before workerPromise is set. */
console.warn("This is the v2 interface - you don't need an onready() function.");
},
//#endif
debug: 1 ? undefined : (...args)=>console.debug('worker debug',...args),
onunhandled: function(ev){
error("Unhandled worker message:",ev.data);
},
onready: function(){
self.sqlite3TestModule.setStatus(null)/*hide the HTML-side is-loading spinner*/;
runTests();
},
onerror: function(ev){
error("worker1 error:",ev);
}
};
const workerPromise = self.sqlite3Worker1Promiser(promiserConfig);
delete self.sqlite3Worker1Promiser;
const workerPromise = await promiserFactory(promiserConfig)
.then((func)=>{
console.log("Init complete. Starting tests momentarily.");
globalThis.sqlite3TestModule.setStatus(null)/*hide the HTML-side is-loading spinner*/;
return func;
});
const wtest = async function(msgType, msgArgs, callback){
if(2===arguments.length && 'function'===typeof msgArgs){
@ -271,5 +281,5 @@
}).finally(()=>logHtml('',"That's all, folks!"));
}/*runTests2()*/;
log("Init complete, but async init bits may still be running.");
runTests();
})();

View File

@ -19,10 +19,15 @@ MAKEFILE.dist := $(lastword $(MAKEFILE_LIST))
# built, and won't be built until we expand the dependencies. Thus we
# have to use a temporary name for the archive until we can get
# that binary built.
ifeq (,$(filter snapshot,$(MAKECMDGOALS)))
dist-name-prefix := sqlite-wasm
ifeq (1,$(SQLITE_C_IS_SEE))
dist-name-extra := -see
else
dist-name-prefix := sqlite-wasm-snapshot-$(shell /usr/bin/date +%Y%m%d)
dist-name-extra :=
endif
ifeq (,$(filter snapshot,$(MAKECMDGOALS)))
dist-name-prefix := sqlite-wasm$(dist-name-extra)
else
dist-name-prefix := sqlite-wasm$(dist-name-extra)-snapshot-$(shell /usr/bin/date +%Y%m%d)
endif
dist-name := $(dist-name-prefix)-TEMP
@ -49,12 +54,18 @@ dist.top.extras := \
tester1.js tester1.mjs \
demo-jsstorage.html demo-jsstorage.js \
demo-worker1.html demo-worker1.js \
demo-worker1-promiser.html demo-worker1-promiser.js
dist.jswasm.extras := $(sqlite3-api.ext.jses) $(sqlite3.wasm)
demo-worker1-promiser.html demo-worker1-promiser.js \
demo-worker1-promiser-esm.html demo-worker1-promiser.mjs
dist.jswasm.extras := $(sqlite3.wasm) \
$(sqlite3-api.ext.jses)
dist.common.extras := \
$(wildcard $(dir.common)/*.css) \
$(dir.common)/SqliteTestUtil.js
#$(info sqlite3-worker1-promiser.mjs = $(sqlite3-worker1-promiser.mjs))
#$(info sqlite3-worker1.js = $(sqlite3-worker1.js))
#$(info sqlite3-api.ext.jses = $(sqlite3-api.ext.jses))
#$(info dist.jswasm.extras = $(dist.jswasm.extras))
.PHONY: dist snapshot
# DIST_STRIP_COMMENTS $(call)able to be used in stripping C-style
# from the dist copies of certain files.
@ -67,7 +78,8 @@ endef
# STRIP_K1.js = list of JS files which need to be passed through
# $(bin.stripcomments) with a single -k flag.
STRIP_K1.js := $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js) \
$(sqlite3-worker1-bundler-friendly.js) $(sqlite3-worker1-promiser-bundler-friendly.js)
$(sqlite3-worker1-bundler-friendly.js) \
$(sqlite3-api.ext.jses)
# STRIP_K2.js = list of JS files which need to be passed through
# $(bin.stripcomments) with two -k flags.
STRIP_K2.js := $(sqlite3.js) $(sqlite3.mjs) \
@ -88,6 +100,7 @@ STRIP_K2.js := $(sqlite3.js) $(sqlite3.mjs) \
dist: \
$(bin.stripccomments) $(bin.version-info) \
$(dist.build) $(STRIP_K1.js) $(STRIP_K2.js) \
$(dist.jswasm.extras) $(dist.common.extras) \
$(MAKEFILE) $(MAKEFILE.dist)
@echo "Making end-user deliverables..."
@rm -fr $(dist-dir.top)

View File

@ -9,16 +9,18 @@ MAKEFILE.fiddle := $(lastword $(MAKEFILE_LIST))
# shell.c and its build flags...
make-np-0 := make -C $(dir.top) -n -p
make-np-1 := sed -e 's/(TOP)/(dir.top)/g'
# Extract SHELL_OPT and SHELL_DEP from the top-most makefile and import
# them as vars here...
$(eval $(shell $(make-np-0) | grep -e '^SHELL_OPT ' | $(make-np-1)))
$(eval $(shell $(make-np-0) | grep -e '^SHELL_SRC ' | $(make-np-1)))
$(eval $(shell $(make-np-0) | grep -e '^SHELL_DEP ' | $(make-np-1)))
# ^^^ can't do that in 1 invocation b/c newlines get stripped
ifeq (,$(SHELL_OPT))
$(error Could not parse SHELL_OPT from $(dir.top)/Makefile.)
endif
ifeq (,$(SHELL_SRC))
$(error Could not parse SHELL_SRC from $(dir.top)/Makefile.)
ifeq (,$(SHELL_DEP))
$(error Could not parse SHELL_DEP from $(dir.top)/Makefile.)
endif
$(dir.top)/shell.c: $(SHELL_SRC) $(dir.top)/tool/mkshellc.tcl $(sqlite3.c)
$(dir.top)/shell.c: $(SHELL_DEP) $(dir.top)/tool/mkshellc.tcl $(sqlite3.c)
$(MAKE) -C $(dir.top) shell.c
# /shell.c
########################################################################

View File

@ -166,11 +166,10 @@
stdout("SQLite version", capi.sqlite3_libversion(),
capi.sqlite3_sourceid().substr(0,19));
stdout('Welcome to the "fiddle" shell.');
if(sqlite3.opfs){
if(capi.sqlite3_vfs_find("opfs")){
stdout("\nOPFS is available. To open a persistent db, use:\n\n",
" .open file:name?vfs=opfs\n\nbut note that some",
"features (e.g. upload) do not yet work with OPFS.");
sqlite3.opfs.registerVfs();
}
stdout('\nEnter ".help" for usage hints.');
this.exec([ // initialization commands...
@ -317,7 +316,7 @@
};
console.warn("Unknown fiddle-worker message type:",ev);
};
/**
emscripten module for use with build mode -sMODULARIZE.
*/
@ -374,9 +373,7 @@
"for use in the dev console.", sqlite3);
globalThis.sqlite3 = sqlite3;
const dbVfs = sqlite3.wasm.xWrap('fiddle_db_vfs', "*", ['string']);
fiddleModule.fsUnlink = (fn)=>{
return sqlite3.wasm.sqlite3_wasm_vfs_unlink(dbVfs(0), fn);
};
fiddleModule.fsUnlink = (fn)=>fiddleModule.FS.unlink(fn);
wMsg('fiddle-ready');
}).catch(e=>{
console.error("Fiddle worker init failed:",e);

View File

@ -403,8 +403,10 @@
E('#btn-reset').addEventListener('click',()=>SF.resetDb());
const taInput = E('#input');
const btnClearIn = E('#btn-clear');
const selectExamples = E('#select-examples');
btnClearIn.addEventListener('click',function(){
taInput.value = '';
selectExamples.selectedIndex = 0;
},false);
// Ctrl-enter and shift-enter both run the current SQL.
taInput.addEventListener('keydown',function(ev){
@ -733,16 +735,15 @@
]},
//{name: "Timer on", sql: ".timer on"},
// ^^^ re-enable if emscripten re-enables getrusage()
{name: "Box Mode", sql: ".mode box"},
{name: "Setup table T", sql:[
".nullvalue NULL\n",
"CREATE TABLE t(a,b);\n",
"INSERT INTO t(a,b) VALUES('abc',123),('def',456),(NULL,789),('ghi',012);\n",
"SELECT * FROM t;\n"
]},
{name: "Table list", sql: ".tables"},
{name: "Box Mode", sql: ".mode box"},
{name: "JSON Mode", sql: ".mode json"},
{name: "Mandlebrot", sql:[
{name: "sqlite_schema", sql: "select * from sqlite_schema"},
{name: "Mandelbrot", sql:[
"WITH RECURSIVE",
" xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2),\n",
" yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0),\n",
@ -760,7 +761,13 @@
" FROM m2 GROUP BY cy\n",
" )\n",
"SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;\n",
]}
]},
{name: "JSON pretty-print",
sql: "select json_pretty(json_object('ex',json('[52,3.14159]')))"
},
{name: "JSON pretty-print (with tabs)",
sql: "select json_pretty(json_object('ex',json('[52,3.14159]')),char(0x09))"
}
];
const newOpt = function(lbl,val){
const o = document.createElement('option');

View File

@ -97,6 +97,8 @@
wrapper is significantly easier to use, however.</li>
<li><a href='demo-worker1-promiser.html'>demo-worker1-promiser</a>:
a demo of the Promise-based wrapper of the Worker1 API.</li>
<li><a href='demo-worker1-promiser-esm.html'>demo-worker1-promiser-esm</a>:
same as the previous demo except loads the promiser from an ESM module.</li>
</ul>
</li>
</ul>

View File

@ -84,6 +84,8 @@
wrapper is significantly easier to use, however.</li>
<li><a href='demo-worker1-promiser.html'>demo-worker1-promiser</a>:
a demo of the Promise-based wrapper of the Worker1 API.</li>
<li><a href='demo-worker1-promiser-esm.html'>demo-worker1-promiser-esm</a>:
same as the previous demo except loads the promiser from an ESM module.</li>
</ul>
</li>
<li>speedtest1 ports (sqlite3's primary benchmarking tool)...

View File

@ -111,10 +111,6 @@
self.sqlite3InitModule(EmscriptenModule).then(async (sqlite3)=>{
const S = globalThis.S = App.sqlite3 = sqlite3;
log("Loaded speedtest1 module. Setting up...");
App.vfsUnlink = function(pDb, fname){
const pVfs = S.wasm.sqlite3_wasm_db_vfs(pDb, 0);
if(pVfs) S.wasm.sqlite3_wasm_vfs_unlink(pVfs, fname||0);
};
App.pDir = wasmfsDir(S.wasm);
App.wasm = S.wasm;
//if(App.pDir) log("Persistent storage:",pDir);

View File

@ -63,7 +63,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
/* Predicate for tests/groups. */
const testIsTodo = ()=>false;
const haveWasmCTests = ()=>{
return !!wasm.exports.sqlite3_wasm_test_intptr;
return !!wasm.exports.sqlite3__wasm_test_intptr;
};
const hasOpfs = ()=>{
return globalThis.FileSystemHandle
@ -722,7 +722,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
//log("xCall()...");
{
const pJson = w.xCall('sqlite3_wasm_enum_json');
const pJson = w.xCall('sqlite3__wasm_enum_json');
T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300);
}
@ -736,9 +736,9 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
T.mustThrowMatching(()=>fw(1), /requires 0 arg/);
let rc = fw();
T.assert('string'===typeof rc).assert(rc.length>5);
rc = w.xCallWrapped('sqlite3_wasm_enum_json','*');
rc = w.xCallWrapped('sqlite3__wasm_enum_json','*');
T.assert(rc>0 && Number.isFinite(rc));
rc = w.xCallWrapped('sqlite3_wasm_enum_json','utf8');
rc = w.xCallWrapped('sqlite3__wasm_enum_json','utf8');
T.assert('string'===typeof rc).assert(rc.length>300);
@ -821,7 +821,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
if(haveWasmCTests()){
if(!sqlite3.config.useStdAlloc){
fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:dealloc',['i32']);
fw = w.xWrap('sqlite3__wasm_test_str_hello', 'utf8:dealloc',['i32']);
rc = fw(0);
T.assert('hello'===rc);
rc = fw(1);
@ -831,14 +831,14 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
if(w.bigIntEnabled){
w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v));
w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v));
fw = w.xWrap('sqlite3_wasm_test_int64_times2','thrice','twice');
fw = w.xWrap('sqlite3__wasm_test_int64_times2','thrice','twice');
rc = fw(1);
T.assert(12n===rc);
w.scopedAllocCall(function(){
const pI1 = w.scopedAlloc(8), pI2 = pI1+4;
w.pokePtr([pI1, pI2], 0);
const f = w.xWrap('sqlite3_wasm_test_int64_minmax',undefined,['i64*','i64*']);
const f = w.xWrap('sqlite3__wasm_test_int64_minmax',undefined,['i64*','i64*']);
const [r1, r2] = w.peek64([pI1, pI2]);
T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2));
});
@ -942,7 +942,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8).
assert(0===wts.$ppV).assert(0===wts.$xFunc);
const testFunc =
W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/);
W.xGet('sqlite3__wasm_test_struct'/*name gets mangled in -O3 builds!*/);
let counter = 0;
//log("wts.pointer =",wts.pointer);
const wtsFunc = function(arg){
@ -1128,7 +1128,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
T.g('sqlite3.oo1')
.t('Create db', function(sqlite3){
const dbFile = '/tester1.db';
wasm.sqlite3_wasm_vfs_unlink(0, dbFile);
sqlite3.util.sqlite3__wasm_vfs_unlink(0, dbFile);
const db = this.db = new sqlite3.oo1.DB(dbFile, 0 ? 'ct' : 'c');
db.onclose = {
disposeAfter: [],
@ -1459,7 +1459,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
rv = db.exec("SELECT 1 WHERE 0",{rowMode: 0});
T.assert(Array.isArray(rv)).assert(0===rv.length);
if(wasm.bigIntEnabled && haveWasmCTests()){
const mI = wasm.xCall('sqlite3_wasm_test_int64_max');
const mI = wasm.xCall('sqlite3__wasm_test_int64_max');
const b = BigInt(Number.MAX_SAFE_INTEGER * 2);
T.assert(b === db.selectValue("SELECT "+b)).
assert(b === db.selectValue("SELECT ?", b)).
@ -1482,7 +1482,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
/*step() skipped intentionally*/.reset(true);
} finally {
T.assert(0===st.finalize())
.assert(undefined===st.finalize());
.assert(undefined===st.finalize());
}
try {
@ -1685,7 +1685,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
T.assert(n>0 && db2.selectValue(sql) === n);
}finally{
db2.close();
wasm.sqlite3_wasm_vfs_unlink(0, filename);
sqlite3.util.sqlite3__wasm_vfs_unlink(0, filename);
}
}
}/*sqlite3_js_posix_create_file()*/)
@ -2075,7 +2075,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
try{
ptrInt = w.scopedAlloc(4);
w.poke32(ptrInt,origValue);
const cf = w.xGet('sqlite3_wasm_test_intptr');
const cf = w.xGet('sqlite3__wasm_test_intptr');
const oldPtrInt = ptrInt;
T.assert(origValue === w.peek32(ptrInt));
const rc = cf(ptrInt);
@ -2090,13 +2090,13 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
const v64 = ()=>w.peek64(pi64)
T.assert(v64() == o64);
//T.assert(o64 === w.peek64(pi64));
const cf64w = w.xGet('sqlite3_wasm_test_int64ptr');
const cf64w = w.xGet('sqlite3__wasm_test_int64ptr');
cf64w(pi64);
T.assert(v64() == BigInt(2 * o64));
cf64w(pi64);
T.assert(v64() == BigInt(4 * o64));
const biTimes2 = w.xGet('sqlite3_wasm_test_int64_times2');
const biTimes2 = w.xGet('sqlite3__wasm_test_int64_times2');
T.assert(BigInt(2 * o64) ===
biTimes2(BigInt(o64)/*explicit conv. required to avoid TypeError
in the call :/ */));
@ -2106,13 +2106,13 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
const g64 = (p)=>w.peek64(p);
w.poke64([pMin, pMax], 0);
const minMaxI64 = [
w.xCall('sqlite3_wasm_test_int64_min'),
w.xCall('sqlite3_wasm_test_int64_max')
w.xCall('sqlite3__wasm_test_int64_min'),
w.xCall('sqlite3__wasm_test_int64_max')
];
T.assert(minMaxI64[0] < BigInt(Number.MIN_SAFE_INTEGER)).
assert(minMaxI64[1] > BigInt(Number.MAX_SAFE_INTEGER));
//log("int64_min/max() =",minMaxI64, typeof minMaxI64[0]);
w.xCall('sqlite3_wasm_test_int64_minmax', pMin, pMax);
w.xCall('sqlite3__wasm_test_int64_minmax', pMin, pMax);
T.assert(g64(pMin) === minMaxI64[0], "int64 mismatch").
assert(g64(pMax) === minMaxI64[1], "int64 mismatch");
//log("pMin",g64(pMin), "pMax",g64(pMax));
@ -2560,7 +2560,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
////////////////////////////////////////////////////////////////////////
.t('Close db', function(){
T.assert(this.db).assert(wasm.isPtr(this.db.pointer));
//wasm.sqlite3_wasm_db_reset(this.db); // will leak virtual tables!
//wasm.sqlite3__wasm_db_reset(this.db); // will leak virtual tables!
this.db.close();
T.assert(!this.db.pointer);
})
@ -2587,7 +2587,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
const pVfs = capi.sqlite3_vfs_find('kvvfs');
T.assert(pVfs);
const JDb = this.JDb = sqlite3.oo1.JsStorageDb;
const unlink = this.kvvfsUnlink = ()=>{JDb.clearStorage(filename)};
const unlink = this.kvvfsUnlink = ()=>JDb.clearStorage(this.kvvfsDbFile);
unlink();
let db = new JDb(filename);
try {
@ -2605,6 +2605,60 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
}
}
}/*kvvfs sanity checks*/)
//#if enable-see
.t({
name: 'kvvfs with SEE encryption',
predicate: ()=>(isUIThread()
|| "Only available in main thread."),
test: function(sqlite3){
this.kvvfsUnlink();
let db;
const encOpt1 = 1
? {textkey: 'foo'}
: {key: 'foo'};
const encOpt2 = encOpt1.textkey
? encOpt1
: {hexkey: new Uint8Array([0x66,0x6f,0x6f]/*==>"foo"*/)}
try{
db = new this.JDb({
filename: this.kvvfsDbFile,
...encOpt1
});
db.exec([
"create table t(a,b);",
"insert into t(a,b) values(1,2),(3,4)"
]);
db.close();
let err;
try{
db = new this.JDb({
filename: this.kvvfsDbFile,
flags: 'ct'
});
T.assert(db) /* opening is fine, but... */;
db.exec("select 1 from sqlite_schema");
console.warn("sessionStorage =",sessionStorage);
}catch(e){
err = e;
}finally{
db.close();
}
T.assert(err,"Expecting an exception")
.assert(sqlite3.capi.SQLITE_NOTADB==err.resultCode,
"Expecting NOTADB");
db = new sqlite3.oo1.DB({
filename: this.kvvfsDbFile,
vfs: 'kvvfs',
...encOpt2
});
T.assert( 4===db.selectValue('select sum(a) from t') );
}finally{
if( db ) db.close();
this.kvvfsUnlink();
}
}
})/*kvvfs with SEE*/
//#endif enable-see
;/* end kvvfs tests */
////////////////////////////////////////////////////////////////////////
@ -2888,18 +2942,17 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
.t({
name: 'OPFS db sanity checks',
test: async function(sqlite3){
T.assert(capi.sqlite3_vfs_find('opfs'));
const opfs = sqlite3.opfs;
const filename = this.opfsDbFile = '/dir/sqlite3-tester1.db';
const pVfs = this.opfsVfs = capi.sqlite3_vfs_find('opfs');
T.assert(pVfs);
const unlink = this.opfsUnlink =
(fn=filename)=>{wasm.sqlite3_wasm_vfs_unlink(pVfs,fn)};
unlink();
let db = new sqlite3.oo1.OpfsDb(filename);
const fileUri = 'file://'+filename+'?delete-before-open=1';
const initSql = [
'create table p(a);',
'insert into p(a) values(1),(2),(3)'
];
let db = new sqlite3.oo1.OpfsDb(fileUri);
try {
db.exec([
'create table p(a);',
'insert into p(a) values(1),(2),(3)'
]);
db.exec(initSql);
T.assert(3 === db.selectValue('select count(*) from p'));
db.close();
db = new sqlite3.oo1.OpfsDb(filename);
@ -2911,7 +2964,14 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
&& 0===this.opfsDbExport.byteLength % 512);
}finally{
db.close();
unlink();
}
T.assert(await opfs.entryExists(filename));
try {
db = new sqlite3.oo1.OpfsDb(fileUri);
db.exec(initSql) /* will throw if delete-before-open did not work */;
T.assert(3 === db.selectValue('select count(*) from p'));
}finally{
if(db) db.close();
}
}
}/*OPFS db sanity checks*/)
@ -2919,15 +2979,17 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
name: 'OPFS import',
test: async function(sqlite3){
let db;
const filename = this.opfsDbFile;
try {
const exp = this.opfsDbExport;
const filename = this.opfsDbFile;
delete this.opfsDbExport;
this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, exp);
db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
T.assert(6 === db.selectValue('select count(*) from p')).
assert( this.opfsImportSize == exp.byteLength );
db.close();
const unlink = this.opfsUnlink =
(fn=filename)=>sqlite3.util.sqlite3__wasm_vfs_unlink("opfs",fn);
this.opfsUnlink(filename);
T.assert(!(await sqlite3.opfs.entryExists(filename)));
// Try again with a function as an input source:
@ -2954,11 +3016,9 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
name: '(Internal-use) OPFS utility APIs',
test: async function(sqlite3){
const filename = this.opfsDbFile;
const pVfs = this.opfsVfs;
const unlink = this.opfsUnlink;
T.assert(filename && pVfs && !!unlink);
T.assert(filename && !!unlink);
delete this.opfsDbFile;
delete this.opfsVfs;
delete this.opfsUnlink;
/**************************************************************
ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended
@ -3209,6 +3269,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
print: log,
printErr: error
}).then(async function(sqlite3){
TestUtil.assert(!!sqlite3.util);
log("Done initializing WASM/JS bits. Running tests...");
sqlite3.config.warn("Installing sqlite3 bits as global S for local dev/test purposes.");
globalThis.S = sqlite3;
@ -3227,9 +3288,9 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
logClass('warning',"BigInt/int64 support is disabled.");
}
if(haveWasmCTests()){
log("sqlite3_wasm_test_...() APIs are available.");
log("sqlite3__wasm_test_...() APIs are available.");
}else{
logClass('warning',"sqlite3_wasm_test_...() APIs unavailable.");
logClass('warning',"sqlite3__wasm_test_...() APIs unavailable.");
}
log("registered vfs list =",capi.sqlite3_js_vfs_list().join(', '));
TestUtil.runTests(sqlite3);

67
main.mk
View File

@ -230,7 +230,6 @@ SRC += \
SRC += \
$(TOP)/ext/misc/stmt.c
# FTS5 things
#
FTS5_HDR = \
@ -375,7 +374,9 @@ TESTSRC += \
$(TOP)/ext/rtree/test_rtreedoc.c \
$(TOP)/ext/recover/sqlite3recover.c \
$(TOP)/ext/recover/dbdata.c \
$(TOP)/ext/recover/test_recover.c
$(TOP)/ext/recover/test_recover.c \
$(TOP)/ext/intck/test_intck.c \
$(TOP)/ext/intck/sqlite3intck.c
#TESTSRC += $(TOP)/ext/fts3/fts3_tokenizer.c
@ -659,7 +660,7 @@ target_source: $(SRC) $(TOP)/tool/vdbe-compress.tcl fts5.c
touch target_source
sqlite3.c: target_source $(TOP)/tool/mksqlite3c.tcl src-verify
tclsh $(TOP)/tool/mksqlite3c.tcl
tclsh $(TOP)/tool/mksqlite3c.tcl $(EXTRA_SRC)
cp tsrc/sqlite3ext.h .
cp $(TOP)/ext/session/sqlite3session.h .
echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c
@ -671,7 +672,7 @@ sqlite3ext.h: target_source
cp tsrc/sqlite3ext.h .
sqlite3.c-debug: target_source $(TOP)/tool/mksqlite3c.tcl src-verify
tclsh $(TOP)/tool/mksqlite3c.tcl --linemacros=1
tclsh $(TOP)/tool/mksqlite3c.tcl --linemacros=1 $(EXTRA_SRC)
echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c
cat sqlite3.c >>tclsqlite3.c
echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c
@ -717,8 +718,7 @@ opcodes.c: opcodes.h $(TOP)/tool/mkopcodec.tcl
tclsh $(TOP)/tool/mkopcodec.tcl opcodes.h >opcodes.c
opcodes.h: parse.h $(TOP)/src/vdbe.c $(TOP)/tool/mkopcodeh.tcl
cat parse.h $(TOP)/src/vdbe.c | \
tclsh $(TOP)/tool/mkopcodeh.tcl >opcodes.h
cat parse.h $(TOP)/src/vdbe.c | tclsh $(TOP)/tool/mkopcodeh.tcl >opcodes.h
# Rules to build parse.c and parse.h - the outputs of lemon.
#
@ -741,32 +741,37 @@ keywordhash.h: $(TOP)/tool/mkkeywordhash.c
$(BCC) -o mkkeywordhash $(OPTS) $(TOP)/tool/mkkeywordhash.c
./mkkeywordhash >keywordhash.h
# Source files that go into making shell.c
SHELL_SRC = \
$(TOP)/src/shell.c.in \
$(TOP)/ext/misc/appendvfs.c \
$(TOP)/ext/misc/completion.c \
$(TOP)/ext/misc/base64.c \
$(TOP)/ext/misc/base85.c \
$(TOP)/ext/misc/decimal.c \
$(TOP)/ext/misc/fileio.c \
$(TOP)/ext/misc/ieee754.c \
$(TOP)/ext/misc/regexp.c \
$(TOP)/ext/misc/series.c \
$(TOP)/ext/misc/shathree.c \
$(TOP)/ext/misc/sqlar.c \
$(TOP)/ext/misc/uint.c \
$(TOP)/ext/expert/sqlite3expert.c \
$(TOP)/ext/expert/sqlite3expert.h \
$(TOP)/ext/misc/zipfile.c \
$(TOP)/ext/misc/memtrace.c \
$(TOP)/ext/misc/pcachetrace.c \
$(TOP)/ext/recover/dbdata.c \
$(TOP)/ext/recover/sqlite3recover.c \
$(TOP)/ext/recover/sqlite3recover.h \
$(TOP)/src/test_windirent.c
# Source and header files that shell.c depends on
SHELL_DEP = \
$(TOP)/src/shell.c.in \
$(TOP)/ext/consio/console_io.c \
$(TOP)/ext/consio/console_io.h \
$(TOP)/ext/expert/sqlite3expert.c \
$(TOP)/ext/expert/sqlite3expert.h \
$(TOP)/ext/intck/sqlite3intck.c \
$(TOP)/ext/intck/sqlite3intck.h \
$(TOP)/ext/misc/appendvfs.c \
$(TOP)/ext/misc/base64.c \
$(TOP)/ext/misc/base85.c \
$(TOP)/ext/misc/completion.c \
$(TOP)/ext/misc/decimal.c \
$(TOP)/ext/misc/fileio.c \
$(TOP)/ext/misc/ieee754.c \
$(TOP)/ext/misc/memtrace.c \
$(TOP)/ext/misc/pcachetrace.c \
$(TOP)/ext/misc/regexp.c \
$(TOP)/ext/misc/series.c \
$(TOP)/ext/misc/shathree.c \
$(TOP)/ext/misc/sqlar.c \
$(TOP)/ext/misc/uint.c \
$(TOP)/ext/misc/zipfile.c \
$(TOP)/ext/recover/dbdata.c \
$(TOP)/ext/recover/sqlite3recover.c \
$(TOP)/ext/recover/sqlite3recover.h \
$(TOP)/src/test_windirent.c \
$(TOP)/src/test_windirent.h
shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl
shell.c: $(SHELL_DEP) $(TOP)/tool/mkshellc.tcl
tclsh $(TOP)/tool/mkshellc.tcl >shell.c

401
manifest

File diff suppressed because it is too large Load Diff

View File

@ -1 +1 @@
8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355
c9c2ab54ba1f5f46360f1b4f35d849cd3f080e6fc2b6c60e91b16c63f69a1e33

View File

@ -1320,7 +1320,7 @@ static int renameResolveTrigger(Parse *pParse){
/* ALWAYS() because if the table of the trigger does not exist, the
** error would have been hit before this point */
if( ALWAYS(pParse->pTriggerTab) ){
rc = sqlite3ViewGetColumnNames(pParse, pParse->pTriggerTab);
rc = sqlite3ViewGetColumnNames(pParse, pParse->pTriggerTab)!=0;
}
/* Resolve symbols in WHEN clause */
@ -2262,7 +2262,12 @@ void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){
if( i==pTab->iPKey ){
sqlite3VdbeAddOp2(v, OP_Null, 0, regOut);
}else{
char aff = pTab->aCol[i].affinity;
if( aff==SQLITE_AFF_REAL ){
pTab->aCol[i].affinity = SQLITE_AFF_NUMERIC;
}
sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, i, regOut);
pTab->aCol[i].affinity = aff;
}
nField++;
}

View File

@ -872,7 +872,7 @@ static void statGet(
if( iVal==2 && p->nRow*10 <= nDistinct*11 ) iVal = 1;
sqlite3_str_appendf(&sStat, " %llu", iVal);
#ifdef SQLITE_ENABLE_STAT4
assert( p->current.anEq[i] );
assert( p->current.anEq[i] || p->nRow==0 );
#endif
}
sqlite3ResultStrAccum(context, &sStat);
@ -1057,7 +1057,7 @@ static void analyzeOneTable(
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
int nCol; /* Number of columns in pIdx. "N" */
int addrRewind; /* Address of "OP_Rewind iIdxCur" */
int addrGotoEnd; /* Address of "OP_Rewind iIdxCur" */
int addrNextRow; /* Address of "next_row:" */
const char *zIdxName; /* Name of the index */
int nColTest; /* Number of columns to test for changes */
@ -1081,9 +1081,14 @@ static void analyzeOneTable(
/*
** Pseudo-code for loop that calls stat_push():
**
** Rewind csr
** if eof(csr) goto end_of_scan;
** regChng = 0
** Rewind csr
** if eof(csr){
** stat_init() with count = 0;
** goto end_of_scan;
** }
** count()
** stat_init()
** goto chng_addr_0;
**
** next_row:
@ -1122,41 +1127,36 @@ static void analyzeOneTable(
sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
VdbeComment((v, "%s", pIdx->zName));
/* Invoke the stat_init() function. The arguments are:
**
/* Implementation of the following:
**
** regChng = 0
** Rewind csr
** if eof(csr){
** stat_init() with count = 0;
** goto end_of_scan;
** }
** count()
** stat_init()
** goto chng_addr_0;
*/
assert( regTemp2==regStat+4 );
sqlite3VdbeAddOp2(v, OP_Integer, db->nAnalysisLimit, regTemp2);
/* Arguments to stat_init():
** (1) the number of columns in the index including the rowid
** (or for a WITHOUT ROWID table, the number of PK columns),
** (2) the number of columns in the key without the rowid/pk
** (3) estimated number of rows in the index,
*/
** (3) estimated number of rows in the index. */
sqlite3VdbeAddOp2(v, OP_Integer, nCol, regStat+1);
assert( regRowid==regStat+2 );
sqlite3VdbeAddOp2(v, OP_Integer, pIdx->nKeyCol, regRowid);
#ifdef SQLITE_ENABLE_STAT4
if( OptimizationEnabled(db, SQLITE_Stat4) ){
sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regTemp);
addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur);
VdbeCoverage(v);
}else
#endif
{
addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur);
VdbeCoverage(v);
sqlite3VdbeAddOp3(v, OP_Count, iIdxCur, regTemp, 1);
}
assert( regTemp2==regStat+4 );
sqlite3VdbeAddOp2(v, OP_Integer, db->nAnalysisLimit, regTemp2);
sqlite3VdbeAddOp3(v, OP_Count, iIdxCur, regTemp,
OptimizationDisabled(db, SQLITE_Stat4));
sqlite3VdbeAddFunctionCall(pParse, 0, regStat+1, regStat, 4,
&statInitFuncdef, 0);
addrGotoEnd = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur);
VdbeCoverage(v);
/* Implementation of the following:
**
** Rewind csr
** if eof(csr) goto end_of_scan;
** regChng = 0
** goto next_push_0;
**
*/
sqlite3VdbeAddOp2(v, OP_Integer, 0, regChng);
addrNextRow = sqlite3VdbeCurrentAddr(v);
@ -1263,6 +1263,12 @@ static void analyzeOneTable(
}
/* Add the entry to the stat1 table. */
if( pIdx->pPartIdxWhere ){
/* Partial indexes might get a zero-entry in sqlite_stat1. But
** an empty table is omitted from sqlite_stat1. */
sqlite3VdbeJumpHere(v, addrGotoEnd);
addrGotoEnd = 0;
}
callStatGet(pParse, regStat, STAT_GET_STAT1, regStat1);
assert( "BBB"[0]==SQLITE_AFF_TEXT );
sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0);
@ -1286,6 +1292,13 @@ static void analyzeOneTable(
int addrIsNull;
u8 seekOp = HasRowid(pTab) ? OP_NotExists : OP_NotFound;
/* No STAT4 data is generated if the number of rows is zero */
if( addrGotoEnd==0 ){
sqlite3VdbeAddOp2(v, OP_Cast, regStat1, SQLITE_AFF_INTEGER);
addrGotoEnd = sqlite3VdbeAddOp1(v, OP_IfNot, regStat1);
VdbeCoverage(v);
}
if( doOnce ){
int mxCol = nCol;
Index *pX;
@ -1338,7 +1351,7 @@ static void analyzeOneTable(
#endif /* SQLITE_ENABLE_STAT4 */
/* End of analysis */
sqlite3VdbeJumpHere(v, addrRewind);
if( addrGotoEnd ) sqlite3VdbeJumpHere(v, addrGotoEnd);
}

View File

@ -151,8 +151,47 @@ int corruptPageError(int lineno, MemPage *p){
# define SQLITE_CORRUPT_PAGE(pMemPage) SQLITE_CORRUPT_PGNO(pMemPage->pgno)
#endif
/* Default value for SHARED_LOCK_TRACE macro if shared-cache is disabled
** or if the lock tracking is disabled. This is always the value for
** release builds.
*/
#define SHARED_LOCK_TRACE(X,MSG,TAB,TYPE) /*no-op*/
#ifndef SQLITE_OMIT_SHARED_CACHE
#if 0
/* ^---- Change to 1 and recompile to enable shared-lock tracing
** for debugging purposes.
**
** Print all shared-cache locks on a BtShared. Debugging use only.
*/
static void sharedLockTrace(
BtShared *pBt,
const char *zMsg,
int iRoot,
int eLockType
){
BtLock *pLock;
if( iRoot>0 ){
printf("%s-%p %u%s:", zMsg, pBt, iRoot, eLockType==READ_LOCK?"R":"W");
}else{
printf("%s-%p:", zMsg, pBt);
}
for(pLock=pBt->pLock; pLock; pLock=pLock->pNext){
printf(" %p/%u%s", pLock->pBtree, pLock->iTable,
pLock->eLock==READ_LOCK ? "R" : "W");
while( pLock->pNext && pLock->pBtree==pLock->pNext->pBtree ){
pLock = pLock->pNext;
printf(",%u%s", pLock->iTable, pLock->eLock==READ_LOCK ? "R" : "W");
}
}
printf("\n");
fflush(stdout);
}
#undef SHARED_LOCK_TRACE
#define SHARED_LOCK_TRACE(X,MSG,TAB,TYPE) sharedLockTrace(X,MSG,TAB,TYPE)
#endif /* Shared-lock tracing */
#ifdef SQLITE_DEBUG
/*
**** This function is only used as part of an assert() statement. ***
@ -229,6 +268,8 @@ static int hasSharedCacheTableLock(
iTab = iRoot;
}
SHARED_LOCK_TRACE(pBtree->pBt,"hasLock",iRoot,eLockType);
/* Search for the required lock. Either a write-lock on root-page iTab, a
** write-lock on the schema table, or (if the client is reading) a
** read-lock on iTab will suffice. Return 1 if any of these are found. */
@ -362,6 +403,8 @@ static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){
BtLock *pLock = 0;
BtLock *pIter;
SHARED_LOCK_TRACE(pBt,"setLock", iTable, eLock);
assert( sqlite3BtreeHoldsMutex(p) );
assert( eLock==READ_LOCK || eLock==WRITE_LOCK );
assert( p->db!=0 );
@ -429,6 +472,8 @@ static void clearAllSharedCacheTableLocks(Btree *p){
assert( p->sharable || 0==*ppIter );
assert( p->inTrans>0 );
SHARED_LOCK_TRACE(pBt, "clearAllLocks", 0, 0);
while( *ppIter ){
BtLock *pLock = *ppIter;
assert( (pBt->btsFlags & BTS_EXCLUSIVE)==0 || pBt->pWriter==pLock->pBtree );
@ -467,6 +512,9 @@ static void clearAllSharedCacheTableLocks(Btree *p){
*/
static void downgradeAllSharedCacheTableLocks(Btree *p){
BtShared *pBt = p->pBt;
SHARED_LOCK_TRACE(pBt, "downgradeLocks", 0, 0);
if( pBt->pWriter==p ){
BtLock *pLock;
pBt->pWriter = 0;
@ -5080,9 +5128,12 @@ static int accessPayload(
if( pCur->aOverflow==0
|| nOvfl*(int)sizeof(Pgno) > sqlite3MallocSize(pCur->aOverflow)
){
Pgno *aNew = (Pgno*)sqlite3Realloc(
pCur->aOverflow, nOvfl*2*sizeof(Pgno)
);
Pgno *aNew;
if( sqlite3FaultSim(413) ){
aNew = 0;
}else{
aNew = (Pgno*)sqlite3Realloc(pCur->aOverflow, nOvfl*2*sizeof(Pgno));
}
if( aNew==0 ){
return SQLITE_NOMEM_BKPT;
}else{
@ -5092,6 +5143,12 @@ static int accessPayload(
memset(pCur->aOverflow, 0, nOvfl*sizeof(Pgno));
pCur->curFlags |= BTCF_ValidOvfl;
}else{
/* Sanity check the validity of the overflow page cache */
assert( pCur->aOverflow[0]==nextPage
|| pCur->aOverflow[0]==0
|| CORRUPT_DB );
assert( pCur->aOverflow[0]!=0 || pCur->aOverflow[offset/ovflSize]==0 );
/* If the overflow page-list cache has been allocated and the
** entry for the first required overflow page is valid, skip
** directly to it.
@ -5573,6 +5630,23 @@ int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){
return rc;
}
#ifdef SQLITE_DEBUG
/* The cursors is CURSOR_VALID and has BTCF_AtLast set. Verify that
** this flags are true for a consistent database.
**
** This routine is is called from within assert() statements only.
** It is an internal verification routine and does not appear in production
** builds.
*/
static int cursorIsAtLastEntry(BtCursor *pCur){
int ii;
for(ii=0; ii<pCur->iPage; ii++){
if( pCur->aiIdx[ii]!=pCur->apPage[ii]->nCell ) return 0;
}
return pCur->ix==pCur->pPage->nCell-1 && pCur->pPage->leaf!=0;
}
#endif
/* Move the cursor to the last entry in the table. Return SQLITE_OK
** on success. Set *pRes to 0 if the cursor actually points to something
** or set *pRes to 1 if the table is empty.
@ -5601,18 +5675,7 @@ int sqlite3BtreeLast(BtCursor *pCur, int *pRes){
/* If the cursor already points to the last entry, this is a no-op. */
if( CURSOR_VALID==pCur->eState && (pCur->curFlags & BTCF_AtLast)!=0 ){
#ifdef SQLITE_DEBUG
/* This block serves to assert() that the cursor really does point
** to the last entry in the b-tree. */
int ii;
for(ii=0; ii<pCur->iPage; ii++){
assert( pCur->aiIdx[ii]==pCur->apPage[ii]->nCell );
}
assert( pCur->ix==pCur->pPage->nCell-1 || CORRUPT_DB );
testcase( pCur->ix!=pCur->pPage->nCell-1 );
/* ^-- dbsqlfuzz b92b72e4de80b5140c30ab71372ca719b8feb618 */
assert( pCur->pPage->leaf );
#endif
assert( cursorIsAtLastEntry(pCur) || CORRUPT_DB );
*pRes = 0;
return SQLITE_OK;
}
@ -5665,6 +5728,7 @@ int sqlite3BtreeTableMoveto(
}
if( pCur->info.nKey<intKey ){
if( (pCur->curFlags & BTCF_AtLast)!=0 ){
assert( cursorIsAtLastEntry(pCur) || CORRUPT_DB );
*pRes = -1;
return SQLITE_OK;
}
@ -6131,10 +6195,10 @@ i64 sqlite3BtreeRowCountEst(BtCursor *pCur){
assert( cursorOwnsBtShared(pCur) );
assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
/* Currently this interface is only called by the OP_IfSmaller
** opcode, and it that case the cursor will always be valid and
** will always point to a leaf node. */
if( NEVER(pCur->eState!=CURSOR_VALID) ) return -1;
/* Currently this interface is only called by the OP_IfSizeBetween
** opcode and the OP_Count opcode with P3=1. In either case,
** the cursor will always be valid unless the btree is empty. */
if( pCur->eState!=CURSOR_VALID ) return 0;
if( NEVER(pCur->pPage->leaf==0) ) return -1;
n = pCur->pPage->nCell;
@ -8265,7 +8329,7 @@ static int balance_nonroot(
** table-interior, index-leaf, or index-interior).
*/
if( pOld->aData[0]!=apOld[0]->aData[0] ){
rc = SQLITE_CORRUPT_BKPT;
rc = SQLITE_CORRUPT_PAGE(pOld);
goto balance_cleanup;
}
@ -8289,7 +8353,7 @@ static int balance_nonroot(
memset(&b.szCell[b.nCell], 0, sizeof(b.szCell[0])*(limit+pOld->nOverflow));
if( pOld->nOverflow>0 ){
if( NEVER(limit<pOld->aiOvfl[0]) ){
rc = SQLITE_CORRUPT_BKPT;
rc = SQLITE_CORRUPT_PAGE(pOld);
goto balance_cleanup;
}
limit = pOld->aiOvfl[0];
@ -8932,7 +8996,7 @@ static int anotherValidCursor(BtCursor *pCur){
&& pOther->eState==CURSOR_VALID
&& pOther->pPage==pCur->pPage
){
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PAGE(pCur->pPage);
}
}
return SQLITE_OK;
@ -8992,7 +9056,7 @@ static int balance(BtCursor *pCur){
/* The page being written is not a root page, and there is currently
** more than one reference to it. This only happens if the page is one
** of its own ancestor pages. Corruption. */
rc = SQLITE_CORRUPT_BKPT;
rc = SQLITE_CORRUPT_PAGE(pPage);
}else{
MemPage * const pParent = pCur->apPage[iPage-1];
int const iIdx = pCur->aiIdx[iPage-1];
@ -9156,7 +9220,7 @@ static SQLITE_NOINLINE int btreeOverwriteOverflowCell(
rc = btreeGetPage(pBt, ovflPgno, &pPage, 0);
if( rc ) return rc;
if( sqlite3PagerPageRefcount(pPage->pDbPage)!=1 || pPage->isInit ){
rc = SQLITE_CORRUPT_BKPT;
rc = SQLITE_CORRUPT_PAGE(pPage);
}else{
if( iOffset+ovflPageSize<(u32)nTotal ){
ovflPgno = get4byte(pPage->aData);
@ -9184,7 +9248,7 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){
if( pCur->info.pPayload + pCur->info.nLocal > pPage->aDataEnd
|| pCur->info.pPayload < pPage->aData + pPage->cellOffset
){
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PAGE(pPage);
}
if( pCur->info.nLocal==nTotal ){
/* The entire cell is local */
@ -9265,7 +9329,7 @@ int sqlite3BtreeInsert(
** Which can only happen if the SQLITE_NoSchemaError flag was set when
** the schema was loaded. This cannot be asserted though, as a user might
** set the flag, load the schema, and then unset the flag. */
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PGNO(pCur->pgnoRoot);
}
}
@ -9388,7 +9452,7 @@ int sqlite3BtreeInsert(
if( pPage->nFree<0 ){
if( NEVER(pCur->eState>CURSOR_INVALID) ){
/* ^^^^^--- due to the moveToRoot() call above */
rc = SQLITE_CORRUPT_BKPT;
rc = SQLITE_CORRUPT_PAGE(pPage);
}else{
rc = btreeComputeFreeSpace(pPage);
}
@ -9430,7 +9494,7 @@ int sqlite3BtreeInsert(
CellInfo info;
assert( idx>=0 );
if( idx>=pPage->nCell ){
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PAGE(pPage);
}
rc = sqlite3PagerWrite(pPage->pDbPage);
if( rc ){
@ -9457,10 +9521,10 @@ int sqlite3BtreeInsert(
** necessary to add the PTRMAP_OVERFLOW1 pointer-map entry. */
assert( rc==SQLITE_OK ); /* clearCell never fails when nLocal==nPayload */
if( oldCell < pPage->aData+pPage->hdrOffset+10 ){
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PAGE(pPage);
}
if( oldCell+szNew > pPage->aDataEnd ){
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PAGE(pPage);
}
memcpy(oldCell, newCell, szNew);
return SQLITE_OK;
@ -9562,7 +9626,7 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){
nIn = pSrc->info.nLocal;
aIn = pSrc->info.pPayload;
if( aIn+nIn>pSrc->pPage->aDataEnd ){
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PAGE(pSrc->pPage);
}
nRem = pSrc->info.nPayload;
if( nIn==nRem && nIn<pDest->pPage->maxLocal ){
@ -9587,7 +9651,7 @@ int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){
if( nRem>nIn ){
if( aIn+nIn+4>pSrc->pPage->aDataEnd ){
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PAGE(pSrc->pPage);
}
ovflIn = get4byte(&pSrc->info.pPayload[nIn]);
}
@ -9683,7 +9747,7 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
assert( rc!=SQLITE_OK || CORRUPT_DB || pCur->eState==CURSOR_VALID );
if( rc || pCur->eState!=CURSOR_VALID ) return rc;
}else{
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PGNO(pCur->pgnoRoot);
}
}
assert( pCur->eState==CURSOR_VALID );
@ -9692,14 +9756,14 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
iCellIdx = pCur->ix;
pPage = pCur->pPage;
if( pPage->nCell<=iCellIdx ){
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PAGE(pPage);
}
pCell = findCell(pPage, iCellIdx);
if( pPage->nFree<0 && btreeComputeFreeSpace(pPage) ){
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PAGE(pPage);
}
if( pCell<&pPage->aCellIdx[pPage->nCell] ){
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PAGE(pPage);
}
/* If the BTREE_SAVEPOSITION bit is on, then the cursor position must
@ -9790,7 +9854,7 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
n = pCur->pPage->pgno;
}
pCell = findCell(pLeaf, pLeaf->nCell-1);
if( pCell<&pLeaf->aData[4] ) return SQLITE_CORRUPT_BKPT;
if( pCell<&pLeaf->aData[4] ) return SQLITE_CORRUPT_PAGE(pLeaf);
nCell = pLeaf->xCellSize(pLeaf, pCell);
assert( MX_CELL_SIZE(pBt) >= nCell );
pTmp = pBt->pTmpSpace;
@ -9906,7 +9970,7 @@ static int btreeCreateTable(Btree *p, Pgno *piTable, int createTabFlags){
*/
sqlite3BtreeGetMeta(p, BTREE_LARGEST_ROOT_PAGE, &pgnoRoot);
if( pgnoRoot>btreePagecount(pBt) ){
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PGNO(pgnoRoot);
}
pgnoRoot++;
@ -9954,7 +10018,7 @@ static int btreeCreateTable(Btree *p, Pgno *piTable, int createTabFlags){
}
rc = ptrmapGet(pBt, pgnoRoot, &eType, &iPtrPage);
if( eType==PTRMAP_ROOTPAGE || eType==PTRMAP_FREEPAGE ){
rc = SQLITE_CORRUPT_BKPT;
rc = SQLITE_CORRUPT_PGNO(pgnoRoot);
}
if( rc!=SQLITE_OK ){
releasePage(pRoot);
@ -10044,14 +10108,14 @@ static int clearDatabasePage(
assert( sqlite3_mutex_held(pBt->mutex) );
if( pgno>btreePagecount(pBt) ){
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PGNO(pgno);
}
rc = getAndInitPage(pBt, pgno, &pPage, 0);
if( rc ) return rc;
if( (pBt->openFlags & BTREE_SINGLE)==0
&& sqlite3PagerPageRefcount(pPage->pDbPage) != (1 + (pgno==1))
){
rc = SQLITE_CORRUPT_BKPT;
rc = SQLITE_CORRUPT_PAGE(pPage);
goto cleardatabasepage_out;
}
hdr = pPage->hdrOffset;
@ -10155,7 +10219,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){
assert( p->inTrans==TRANS_WRITE );
assert( iTable>=2 );
if( iTable>btreePagecount(pBt) ){
return SQLITE_CORRUPT_BKPT;
return SQLITE_CORRUPT_PGNO(iTable);
}
rc = sqlite3BtreeClearTable(p, iTable, 0);
@ -10749,6 +10813,9 @@ static int checkTreePage(
** number of cells on the page. */
nCell = get2byte(&data[hdr+3]);
assert( pPage->nCell==nCell );
if( pPage->leaf || pPage->intKey==0 ){
pCheck->nRow += nCell;
}
/* EVIDENCE-OF: R-23882-45353 The cell pointer array of a b-tree page
** immediately follows the b-tree page header. */
@ -10860,6 +10927,7 @@ static int checkTreePage(
btreeHeapInsert(heap, (pc<<16)|(pc+size-1));
}
}
assert( heap!=0 );
/* Add the freeblocks to the min-heap
**
** EVIDENCE-OF: R-20690-50594 The second field of the b-tree page header
@ -10959,6 +11027,7 @@ int sqlite3BtreeIntegrityCheck(
sqlite3 *db, /* Database connection that is running the check */
Btree *p, /* The btree to be checked */
Pgno *aRoot, /* An array of root pages numbers for individual trees */
Mem *aCnt, /* Memory cells to write counts for each tree to */
int nRoot, /* Number of entries in aRoot[] */
int mxErr, /* Stop reporting errors after this many */
int *pnErr, /* OUT: Write number of errors seen to this variable */
@ -10972,7 +11041,9 @@ int sqlite3BtreeIntegrityCheck(
int bPartial = 0; /* True if not checking all btrees */
int bCkFreelist = 1; /* True to scan the freelist */
VVA_ONLY( int nRef );
assert( nRoot>0 );
assert( aCnt!=0 );
/* aRoot[0]==0 means this is a partial check */
if( aRoot[0]==0 ){
@ -11045,15 +11116,18 @@ int sqlite3BtreeIntegrityCheck(
testcase( pBt->db->flags & SQLITE_CellSizeCk );
pBt->db->flags &= ~(u64)SQLITE_CellSizeCk;
for(i=0; (int)i<nRoot && sCheck.mxErr; i++){
i64 notUsed;
if( aRoot[i]==0 ) continue;
sCheck.nRow = 0;
if( aRoot[i] ){
i64 notUsed;
#ifndef SQLITE_OMIT_AUTOVACUUM
if( pBt->autoVacuum && aRoot[i]>1 && !bPartial ){
checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0);
}
if( pBt->autoVacuum && aRoot[i]>1 && !bPartial ){
checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0);
}
#endif
sCheck.v0 = aRoot[i];
checkTreePage(&sCheck, aRoot[i], &notUsed, LARGEST_INT64);
sCheck.v0 = aRoot[i];
checkTreePage(&sCheck, aRoot[i], &notUsed, LARGEST_INT64);
}
sqlite3MemSetArrayInt64(aCnt, i, sCheck.nRow);
}
pBt->db->flags = savedDbFlags;

View File

@ -331,6 +331,7 @@ int sqlite3BtreeIntegrityCheck(
sqlite3 *db, /* Database connection that is running the check */
Btree *p, /* The btree to be checked */
Pgno *aRoot, /* An array of root pages numbers for individual trees */
sqlite3_value *aCnt, /* OUT: entry counts for each btree in aRoot[] */
int nRoot, /* Number of entries in aRoot[] */
int mxErr, /* Stop reporting errors after this many */
int *pnErr, /* OUT: Write number of errors seen to this variable */

View File

@ -707,6 +707,7 @@ struct IntegrityCk {
StrAccum errMsg; /* Accumulate the error message text here */
u32 *heap; /* Min-heap used for analyzing cell coverage */
sqlite3 *db; /* Database connection running the check */
i64 nRow; /* Number of rows visited in current tree */
};
/*

View File

@ -189,7 +189,7 @@ void sqlite3FinishCoding(Parse *pParse){
}
sqlite3VdbeAddOp0(v, OP_Halt);
#if SQLITE_USER_AUTHENTICATION
#if SQLITE_USER_AUTHENTICATION && !defined(SQLITE_OMIT_SHARED_CACHE)
if( pParse->nTableLock>0 && db->init.busy==0 ){
sqlite3UserAuthInit(db);
if( db->auth.authLevel<UAUTH_User ){
@ -2828,20 +2828,20 @@ void sqlite3EndTable(
int regRowid; /* Rowid of the next row to insert */
int addrInsLoop; /* Top of the loop for inserting rows */
Table *pSelTab; /* A table that describes the SELECT results */
int iCsr; /* Write cursor on the new table */
if( IN_SPECIAL_PARSE ){
pParse->rc = SQLITE_ERROR;
pParse->nErr++;
return;
}
iCsr = pParse->nTab++;
regYield = ++pParse->nMem;
regRec = ++pParse->nMem;
regRowid = ++pParse->nMem;
assert(pParse->nTab==1);
sqlite3MayAbort(pParse);
sqlite3VdbeAddOp3(v, OP_OpenWrite, 1, pParse->regRoot, iDb);
sqlite3VdbeAddOp3(v, OP_OpenWrite, iCsr, pParse->regRoot, iDb);
sqlite3VdbeChangeP5(v, OPFLAG_P2ISREG);
pParse->nTab = 2;
addrTop = sqlite3VdbeCurrentAddr(v) + 1;
sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop);
if( pParse->nErr ) return;
@ -2862,11 +2862,11 @@ void sqlite3EndTable(
VdbeCoverage(v);
sqlite3VdbeAddOp3(v, OP_MakeRecord, dest.iSdst, dest.nSdst, regRec);
sqlite3TableAffinity(v, p, 0);
sqlite3VdbeAddOp2(v, OP_NewRowid, 1, regRowid);
sqlite3VdbeAddOp3(v, OP_Insert, 1, regRec, regRowid);
sqlite3VdbeAddOp2(v, OP_NewRowid, iCsr, regRowid);
sqlite3VdbeAddOp3(v, OP_Insert, iCsr, regRec, regRowid);
sqlite3VdbeGoto(v, addrInsLoop);
sqlite3VdbeJumpHere(v, addrInsLoop);
sqlite3VdbeAddOp1(v, OP_Close, 1);
sqlite3VdbeAddOp1(v, OP_Close, iCsr);
}
/* Compute the complete text of the CREATE statement */
@ -2923,13 +2923,10 @@ void sqlite3EndTable(
/* Test for cycles in generated columns and illegal expressions
** in CHECK constraints and in DEFAULT clauses. */
if( p->tabFlags & TF_HasGenerated ){
sqlite3VdbeAddOp4(v, OP_SqlExec, 1, 0, 0,
sqlite3VdbeAddOp4(v, OP_SqlExec, 0x0001, 0, 0,
sqlite3MPrintf(db, "SELECT*FROM\"%w\".\"%w\"",
db->aDb[iDb].zDbSName, p->zName), P4_DYNAMIC);
}
sqlite3VdbeAddOp4(v, OP_SqlExec, 1, 0, 0,
sqlite3MPrintf(db, "PRAGMA \"%w\".integrity_check(%Q)",
db->aDb[iDb].zDbSName, p->zName), P4_DYNAMIC);
}
/* Add the table to the in-memory representation of the database.
@ -3067,8 +3064,9 @@ create_view_fail:
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE)
/*
** The Table structure pTable is really a VIEW. Fill in the names of
** the columns of the view in the pTable structure. Return the number
** of errors. If an error is seen leave an error message in pParse->zErrMsg.
** the columns of the view in the pTable structure. Return non-zero if
** there are errors. If an error is seen an error message is left
** in pParse->zErrMsg.
*/
static SQLITE_NOINLINE int viewGetColumnNames(Parse *pParse, Table *pTable){
Table *pSelTab; /* A fake table from which we get the result set */
@ -3191,7 +3189,7 @@ static SQLITE_NOINLINE int viewGetColumnNames(Parse *pParse, Table *pTable){
sqlite3DeleteColumnNames(db, pTable);
}
#endif /* SQLITE_OMIT_VIEW */
return nErr;
return nErr + pParse->nErr;
}
int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
assert( pTable!=0 );

File diff suppressed because it is too large Load Diff

View File

@ -1,370 +0,0 @@
/*
** SQLCipher
** crypto.h developed by Stephen Lombardo (Zetetic LLC)
** sjlombardo at zetetic dot net
** http://zetetic.net
**
** Copyright (c) 2008, ZETETIC LLC
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** * Neither the name of the ZETETIC LLC nor the
** names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY
** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
*/
/* BEGIN SQLCIPHER */
#ifdef SQLITE_HAS_CODEC
#ifndef CRYPTO_H
#define CRYPTO_H
#include "sqliteInt.h"
#include "btreeInt.h"
#include "pager.h"
#include "vdbeInt.h"
#if !defined(SQLCIPHER_OMIT_LOG_DEVICE)
#if defined(__ANDROID__)
#include <android/log.h>
#elif defined(__APPLE__)
#include <TargetConditionals.h>
#include <os/log.h>
#endif
#endif
#include <time.h>
#if defined(_WIN32) || defined(SQLITE_OS_WINRT)
#include <windows.h> /* amalgamator: dontcache */
#else
#include <sys/time.h> /* amalgamator: dontcache */
#endif
#ifndef OMIT_MEMLOCK
#if defined(__unix__) || defined(__APPLE__) || defined(_AIX)
#include <errno.h> /* amalgamator: dontcache */
#include <unistd.h> /* amalgamator: dontcache */
#include <sys/resource.h> /* amalgamator: dontcache */
#include <sys/mman.h> /* amalgamator: dontcache */
#endif
#endif
#include "sqlcipher.h"
/* extensions defined in pager.c */
void *sqlcipherPagerGetCodec(Pager*);
void sqlcipherPagerSetCodec(Pager*, void *(*)(void*,void*,Pgno,int), void (*)(void*,int,int), void (*)(void*), void *);
int sqlite3pager_is_sj_pgno(Pager*, Pgno);
void sqlite3pager_error(Pager*, int);
void sqlite3pager_reset(Pager *pPager);
/* end extensions defined in pager.c */
#if !defined (SQLCIPHER_CRYPTO_CC) \
&& !defined (SQLCIPHER_CRYPTO_LIBTOMCRYPT) \
&& !defined (SQLCIPHER_CRYPTO_NSS) \
&& !defined (SQLCIPHER_CRYPTO_OPENSSL)
#define SQLCIPHER_CRYPTO_OPENSSL
#endif
#define FILE_HEADER_SZ 16
#define CIPHER_XSTR(s) CIPHER_STR(s)
#define CIPHER_STR(s) #s
#ifndef CIPHER_VERSION_NUMBER
#define CIPHER_VERSION_NUMBER 4.6.0
#endif
#ifndef CIPHER_VERSION_BUILD
#define CIPHER_VERSION_BUILD community
#endif
#define CIPHER_DECRYPT 0
#define CIPHER_ENCRYPT 1
#define CIPHER_READ_CTX 0
#define CIPHER_WRITE_CTX 1
#define CIPHER_READWRITE_CTX 2
#ifndef PBKDF2_ITER
#define PBKDF2_ITER 256000
#endif
#define SQLCIPHER_FLAG_GET(FLAG,BIT) ((FLAG & BIT) != 0)
#define SQLCIPHER_FLAG_SET(FLAG,BIT) FLAG |= BIT
#define SQLCIPHER_FLAG_UNSET(FLAG,BIT) FLAG &= ~BIT
/* possible flags for codec_ctx->flags */
#define CIPHER_FLAG_HMAC (1 << 0)
#define CIPHER_FLAG_LE_PGNO (1 << 1)
#define CIPHER_FLAG_BE_PGNO (1 << 2)
#define CIPHER_FLAG_KEY_USED (1 << 3)
#define CIPHER_FLAG_HAS_KDF_SALT (1 << 4)
#ifndef DEFAULT_CIPHER_FLAGS
#define DEFAULT_CIPHER_FLAGS CIPHER_FLAG_HMAC | CIPHER_FLAG_LE_PGNO
#endif
/* by default, sqlcipher will use a reduced number of iterations to generate
the HMAC key / or transform a raw cipher key
*/
#ifndef FAST_PBKDF2_ITER
#define FAST_PBKDF2_ITER 2
#endif
/* this if a fixed random array that will be xor'd with the database salt to ensure that the
salt passed to the HMAC key derivation function is not the same as that used to derive
the encryption key. This can be overridden at compile time but it will make the resulting
binary incompatible with the default builds when using HMAC. A future version of SQLcipher
will likely allow this to be defined at runtime via pragma */
#ifndef HMAC_SALT_MASK
#define HMAC_SALT_MASK 0x3a
#endif
#ifndef CIPHER_MAX_IV_SZ
#define CIPHER_MAX_IV_SZ 16
#endif
#ifndef CIPHER_MAX_KEY_SZ
#define CIPHER_MAX_KEY_SZ 64
#endif
/*
** Simple shared routines for converting hex char strings to binary data
*/
static int cipher_hex2int(char c) {
return (c>='0' && c<='9') ? (c)-'0' :
(c>='A' && c<='F') ? (c)-'A'+10 :
(c>='a' && c<='f') ? (c)-'a'+10 : 0;
}
static void cipher_hex2bin(const unsigned char *hex, int sz, unsigned char *out){
int i;
for(i = 0; i < sz; i += 2){
out[i/2] = (cipher_hex2int(hex[i])<<4) | cipher_hex2int(hex[i+1]);
}
}
static void cipher_bin2hex(const unsigned char* in, int sz, char *out) {
int i;
for(i=0; i < sz; i++) {
sqlite3_snprintf(3, out + (i*2), "%02x ", in[i]);
}
}
static int cipher_isHex(const unsigned char *hex, int sz){
int i;
for(i = 0; i < sz; i++) {
unsigned char c = hex[i];
if ((c < '0' || c > '9') &&
(c < 'A' || c > 'F') &&
(c < 'a' || c > 'f')) {
return 0;
}
}
return 1;
}
/* possible flags for simulating specific test conditions */
#ifdef SQLCIPHER_TEST
#define TEST_FAIL_ENCRYPT 0x01
#define TEST_FAIL_DECRYPT 0x02
#define TEST_FAIL_MIGRATE 0x04
unsigned int sqlcipher_get_test_flags(void);
void sqlcipher_set_test_flags(unsigned int);
int sqlcipher_get_test_rand(void);
void sqlcipher_set_test_rand(int);
int sqlcipher_get_test_fail(void);
#endif
/* extensions defined in crypto_impl.c */
/* the default implementation of SQLCipher uses a cipher_ctx
to keep track of read / write state separately. The following
struct and associated functions are defined here */
typedef struct {
int derive_key;
int pass_sz;
unsigned char *key;
unsigned char *hmac_key;
unsigned char *pass;
char *keyspec;
} cipher_ctx;
typedef struct {
int store_pass;
int kdf_iter;
int fast_kdf_iter;
int kdf_salt_sz;
int key_sz;
int iv_sz;
int block_sz;
int page_sz;
int keyspec_sz;
int reserve_sz;
int hmac_sz;
int plaintext_header_sz;
int hmac_algorithm;
int kdf_algorithm;
unsigned int flags;
unsigned char *kdf_salt;
unsigned char *hmac_kdf_salt;
unsigned char *buffer;
Btree *pBt;
cipher_ctx *read_ctx;
cipher_ctx *write_ctx;
sqlcipher_provider *provider;
void *provider_ctx;
} codec_ctx ;
/* crypto.c functions */
int sqlcipher_codec_pragma(sqlite3*, int, Parse*, const char *, const char*);
int sqlcipherCodecAttach(sqlite3*, int, const void *, int);
void sqlcipherCodecGetKey(sqlite3*, int, void**, int*);
void sqlcipher_exportFunc(sqlite3_context *, int, sqlite3_value **);
/* crypto_impl.c functions */
void sqlcipher_init_memmethods(void);
/* activation and initialization */
void sqlcipher_activate(void);
void sqlcipher_deactivate(void);
int sqlcipher_codec_ctx_init(codec_ctx **, Db *, Pager *, const void *, int);
void sqlcipher_codec_ctx_free(codec_ctx **);
int sqlcipher_codec_key_derive(codec_ctx *);
int sqlcipher_codec_key_copy(codec_ctx *, int);
/* page cipher implementation */
int sqlcipher_page_cipher(codec_ctx *, int, Pgno, int, int, unsigned char *, unsigned char *);
/* context setters & getters */
void sqlcipher_codec_ctx_set_error(codec_ctx *, int);
void sqlcipher_codec_get_pass(codec_ctx *, void **, int *);
int sqlcipher_codec_ctx_set_pass(codec_ctx *, const void *, int, int);
void sqlcipher_codec_get_keyspec(codec_ctx *, void **zKey, int *nKey);
int sqlcipher_codec_ctx_set_pagesize(codec_ctx *, int);
int sqlcipher_codec_ctx_get_pagesize(codec_ctx *);
int sqlcipher_codec_ctx_get_reservesize(codec_ctx *);
void sqlcipher_set_default_pagesize(int page_size);
int sqlcipher_get_default_pagesize(void);
void sqlcipher_set_default_kdf_iter(int iter);
int sqlcipher_get_default_kdf_iter(void);
int sqlcipher_codec_ctx_set_kdf_iter(codec_ctx *, int);
int sqlcipher_codec_ctx_get_kdf_iter(codec_ctx *ctx);
int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int sz);
int sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx, void **salt);
int sqlcipher_codec_ctx_set_fast_kdf_iter(codec_ctx *, int);
int sqlcipher_codec_ctx_get_fast_kdf_iter(codec_ctx *);
const char* sqlcipher_codec_ctx_get_cipher(codec_ctx *ctx);
void* sqlcipher_codec_ctx_get_data(codec_ctx *);
void sqlcipher_set_default_use_hmac(int use);
int sqlcipher_get_default_use_hmac(void);
void sqlcipher_set_hmac_salt_mask(unsigned char mask);
unsigned char sqlcipher_get_hmac_salt_mask(void);
int sqlcipher_codec_ctx_set_use_hmac(codec_ctx *ctx, int use);
int sqlcipher_codec_ctx_get_use_hmac(codec_ctx *ctx);
const char* sqlcipher_codec_get_cipher_provider(codec_ctx *ctx);
int sqlcipher_codec_ctx_migrate(codec_ctx *ctx);
int sqlcipher_codec_add_random(codec_ctx *ctx, const char *data, int random_sz);
int sqlcipher_cipher_profile(sqlite3 *db, const char *destination);
int sqlcipher_codec_get_store_pass(codec_ctx *ctx);
void sqlcipher_codec_get_pass(codec_ctx *ctx, void **zKey, int *nKey);
void sqlcipher_codec_set_store_pass(codec_ctx *ctx, int value);
int sqlcipher_codec_fips_status(codec_ctx *ctx);
const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx);
int sqlcipher_set_default_plaintext_header_size(int size);
int sqlcipher_get_default_plaintext_header_size(void);
int sqlcipher_codec_ctx_set_plaintext_header_size(codec_ctx *ctx, int size);
int sqlcipher_codec_ctx_get_plaintext_header_size(codec_ctx *ctx);
int sqlcipher_set_default_hmac_algorithm(int algorithm);
int sqlcipher_get_default_hmac_algorithm(void);
int sqlcipher_codec_ctx_set_hmac_algorithm(codec_ctx *ctx, int algorithm);
int sqlcipher_codec_ctx_get_hmac_algorithm(codec_ctx *ctx);
int sqlcipher_set_default_kdf_algorithm(int algorithm);
int sqlcipher_get_default_kdf_algorithm(void);
int sqlcipher_codec_ctx_set_kdf_algorithm(codec_ctx *ctx, int algorithm);
int sqlcipher_codec_ctx_get_kdf_algorithm(codec_ctx *ctx);
void sqlcipher_set_mem_security(int);
int sqlcipher_get_mem_security(void);
int sqlcipher_find_db_index(sqlite3 *db, const char *zDb);
int sqlcipher_codec_ctx_integrity_check(codec_ctx *, Parse *, char *);
int sqlcipher_set_log(const char *destination);
void sqlcipher_set_log_level(unsigned int level);
#define SQLCIPHER_LOG_NONE 0x00
#define SQLCIPHER_LOG_ERROR 0x01
#define SQLCIPHER_LOG_WARN 0x02
#define SQLCIPHER_LOG_INFO 0x04
#define SQLCIPHER_LOG_DEBUG 0x08
#define SQLCIPHER_LOG_TRACE 0x10
#define SQLCIPHER_LOG_ALL 0xffffffff
#ifdef SQLCIPHER_OMIT_LOG
#define sqlcipher_log(tag, message, ...)
#else
void sqlcipher_log(unsigned int tag, const char *message, ...);
#endif
void sqlcipher_vdbe_return_string(Parse*, const char*, const char*, int);
#ifdef CODEC_DEBUG_PAGEDATA
#define CODEC_HEXDUMP(DESC,BUFFER,LEN) \
{ \
int __pctr; \
printf(DESC); \
for(__pctr=0; __pctr < LEN; __pctr++) { \
if(__pctr % 16 == 0) printf("\n%05x: ",__pctr); \
printf("%02x ",((unsigned char*) BUFFER)[__pctr]); \
} \
printf("\n"); \
fflush(stdout); \
}
#else
#define CODEC_HEXDUMP(DESC,BUFFER,LEN)
#endif
#endif
#endif
/* END SQLCIPHER */

View File

@ -31,7 +31,6 @@
/* BEGIN SQLCIPHER */
#ifdef SQLITE_HAS_CODEC
#ifdef SQLCIPHER_CRYPTO_CC
#include "crypto.h"
#include "sqlcipher.h"
#include <CommonCrypto/CommonCrypto.h>
#include <Security/SecRandom.h>

File diff suppressed because it is too large Load Diff

View File

@ -48,9 +48,9 @@ static int sqlcipher_ltc_add_random(void *ctx, void *buffer, int length) {
int block_sz = data_to_read < FORTUNA_MAX_SZ ? data_to_read : FORTUNA_MAX_SZ;
const unsigned char * data = (const unsigned char *)buffer;
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND");
sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND));
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND");
while(data_to_read > 0){
rc = fortuna_add_entropy(data, block_sz, &prng);
@ -64,9 +64,9 @@ static int sqlcipher_ltc_add_random(void *ctx, void *buffer, int length) {
}
fortuna_ready(&prng);
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND");
sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND));
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND");
return rc;
}
@ -74,9 +74,9 @@ static int sqlcipher_ltc_add_random(void *ctx, void *buffer, int length) {
static int sqlcipher_ltc_activate(void *ctx) {
unsigned char random_buffer[FORTUNA_MAX_SZ];
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_memset(random_buffer, 0, FORTUNA_MAX_SZ);
if(ltc_init == 0) {
@ -102,17 +102,17 @@ static int sqlcipher_ltc_activate(void *ctx) {
}
sqlcipher_memset(random_buffer, 0, FORTUNA_MAX_SZ);
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
return SQLITE_OK;
}
static int sqlcipher_ltc_deactivate(void *ctx) {
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
ltc_ref_count--;
if(ltc_ref_count == 0){
@ -120,9 +120,9 @@ static int sqlcipher_ltc_deactivate(void *ctx) {
sqlcipher_memset((void *)&prng, 0, sizeof(prng));
}
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
return SQLITE_OK;
}
@ -136,15 +136,15 @@ static const char* sqlcipher_ltc_get_provider_version(void *ctx) {
}
static int sqlcipher_ltc_random(void *ctx, void *buffer, int length) {
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND");
sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND));
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND");
fortuna_read(buffer, length, &prng);
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND");
sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND));
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_ltc_random: left SQLCIPHER_MUTEX_PROVIDER_RAND");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_ltc_random: left SQLCIPHER_MUTEX_PROVIDER_RAND");
return SQLITE_OK;
}

View File

@ -31,7 +31,6 @@
/* BEGIN SQLCIPHER */
#ifdef SQLITE_HAS_CODEC
#ifdef SQLCIPHER_CRYPTO_NSS
#include "crypto.h"
#include "sqlcipher.h"
#include <nss/blapit.h>
#include <nss/nss.h>
@ -44,25 +43,25 @@ int sqlcipher_nss_setup(sqlcipher_provider *p);
static int sqlcipher_nss_activate(void *ctx) {
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
if (nss_init_context == NULL) {
nss_init_context = NSS_InitContext("", "", "", "", NULL,
NSS_INIT_READONLY | NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB |
NSS_INIT_FORCEOPEN | NSS_INIT_OPTIMIZESPACE | NSS_INIT_NOROOTINIT);
}
nss_init_count++;
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
return SQLITE_OK;
}
static int sqlcipher_nss_deactivate(void *ctx) {
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
nss_init_count--;
if (nss_init_count == 0 && nss_init_context != NULL) {
@ -70,9 +69,9 @@ static int sqlcipher_nss_deactivate(void *ctx) {
nss_init_context = NULL;
}
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
sqlcipher_log(SQLCIPHER_LOG_TRACE, "sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
sqlcipher_log(SQLCIPHER_LOG_TRACE, SQLCIPHER_LOG_MUTEX, "sqlcipher_nss_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE");
return SQLITE_OK;
}

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