Compare commits
236 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ceada8d03 | |||
| eecb3e6b16 | |||
| 7bc0d3862a | |||
| 2300c5a646 | |||
| dde17957b7 | |||
| cf6b0e27d2 | |||
| 36d39bcc48 | |||
| 024806ffb6 | |||
| c84cb56f81 | |||
| 2f2b676b7b | |||
| 2718c7dec7 | |||
| 4a6393e96e | |||
| 48b9d17680 | |||
| 9dba2cc038 | |||
| adb21d4482 | |||
| 57acbb1380 | |||
| f74840dadb | |||
| 2e4f1fd0f8 | |||
| b484ae62b8 | |||
| be531827a6 | |||
| bbefc9e821 | |||
| 7771ab2192 | |||
| fe553b671c | |||
| 3d25b94a13 | |||
| b8b877b3dd | |||
| 5b6692dddd | |||
| 7956fbb7b6 | |||
| 764d1809ce | |||
| 5aa46f9bd6 | |||
| bfc3e65a04 | |||
| 5d0ce656d4 | |||
| 3c3a5d4ada | |||
| fc9f56c453 | |||
| 9826456ab0 | |||
| 945e5d112e | |||
| adcbc76bb6 | |||
| f0629994ab | |||
| ab8ad8dde3 | |||
| 7a5d37f540 | |||
| 5b2b13c8c2 | |||
| b6c8623045 | |||
| cb2ec5c3fd | |||
| 05c3446396 | |||
| 6bfb2e062e | |||
| fb9d9cc7c4 | |||
| 612beb5e38 | |||
| a240942122 | |||
| 5e9d4c221d | |||
| 23dbdf125c | |||
| bd14b600fa | |||
| 3dc850321e | |||
| 5a344ef67c | |||
| e3e03777c7 | |||
| 27fab1c045 | |||
| cd35fbc9ca | |||
| f17089eb01 | |||
| ab18639258 | |||
| d4a1c953a1 | |||
| 93b95c6c84 | |||
| c697613beb | |||
| f5078e74dd | |||
| 47f677d8d7 | |||
| 3b5a4942f1 | |||
| 42f5c182f6 | |||
| 2bbacc1cf3 | |||
| 7773fffb18 | |||
| bfaa3dbd61 | |||
| 841efe0ca1 | |||
| 7e1f717b09 | |||
| db6fe08914 | |||
| b031157128 | |||
| ef180caded | |||
| 9c1eb40d69 | |||
| 9ee5641ae0 | |||
| 254fa054ec | |||
| a693b000df | |||
| f8d7432aa3 | |||
| 50232dd821 | |||
| 834c48c279 | |||
| 51883dfc8b | |||
| 15e8543ecd | |||
| a63585eb8e | |||
| 9b5c67f784 | |||
| 8e90c85bfb | |||
| dcb3992c66 | |||
| 0131a914d3 | |||
| 2dd93c8838 | |||
| c1b7fdd43e | |||
| fccd5d43a9 | |||
| ec6e332f01 | |||
| eb30704cc9 | |||
| de26c16ea1 | |||
| 0c1fa5c8c6 | |||
| 6ccceeb3bd | |||
| 4921c159bd | |||
| 1d735ad3bb | |||
| b7ee5667c2 | |||
| a233f09330 | |||
| bfbe2b8c80 | |||
| 886fac36ba | |||
| 4af5f425f2 | |||
| 8ba5d0d4f7 | |||
| 51abf049c7 | |||
| ed432167dc | |||
| cdbf6d01a4 | |||
| 3a5fc00808 | |||
| eb816a7547 | |||
| 908803f5e0 | |||
| 699bd8121e | |||
| c7a82772dc | |||
| 5d9c916150 | |||
| 55fba4f05c | |||
| 02bc6f8fb9 | |||
| cb0267185c | |||
| 165ba4a2a5 | |||
| 8706485d1a | |||
| c0f69fcec7 | |||
| 4d13729861 | |||
| 56391f2769 | |||
| 7393c7424e | |||
| 3eba5d00b6 | |||
| 03d405f046 | |||
| b25057c291 | |||
| 9c54156a9c | |||
| d828d0debc | |||
| 6aab028db0 | |||
| 9f16a96d4b | |||
| 741802e825 | |||
| fd80ce4b10 | |||
| 9ee9b0f4c3 | |||
| c5856def1e | |||
| 0a2afca904 | |||
| 1af0a4e1df | |||
| 7365bcd72c | |||
| 1c6276eac6 | |||
| 827c9b785e | |||
| 4956bd5f9a | |||
| 63624a0ab8 | |||
| dc1bf28ad4 | |||
| d6396e9b16 | |||
| d4d01a689d | |||
| a096fcc73c | |||
| 50179f91f3 | |||
| 35dcd0e892 | |||
| 7f002db7e3 | |||
| 995b2457e2 | |||
| 7fff2e1cb9 | |||
| e3b047b3d7 | |||
| 7037787d30 | |||
| 606f718480 | |||
| e3c3be8511 | |||
| aa59505ae8 | |||
| 55b36d5c07 | |||
| 6dbb452b38 | |||
| 2ecf1ec8c9 | |||
| 9a89254e7a | |||
| fe329a7370 | |||
| 4d55d35df6 | |||
| 246e1d8faf | |||
| 0195d8ef5d | |||
| e32c7c2b64 | |||
| 0ba9725489 | |||
| ff8e42e2ca | |||
| a8dee8df64 | |||
| 7d931b985a | |||
| 8bbf544747 | |||
| a7d16a5cd1 | |||
| c18d304066 | |||
| 3d4ad18cbe | |||
| 21ac1de75b | |||
| 96b9dc9b36 | |||
| adeb47e57b | |||
| 12891cc492 | |||
| 820fc4933b | |||
| b00083429a | |||
| b0b18f6dca | |||
| 76cb74da4d | |||
| db454d72b6 | |||
| 99b14894eb | |||
| 03206c433c | |||
| 93f88ebbb1 | |||
| faf010d77a | |||
| 7c9ecc62c5 | |||
| 04b9cf1e7e | |||
| f687ba597f | |||
| 76ff0699bb | |||
| 607d4c2184 | |||
| 8c9ea487d0 | |||
| 1d9497a0d0 | |||
| 31441fb50b | |||
| f12cc2a68c | |||
| cd9491ca2f | |||
| e8a1ea6d8b | |||
| 0faad42d59 | |||
| 411547ec6c | |||
| 9a5a469c7a | |||
| 573ebef187 | |||
| 144cd3df05 | |||
| 3561ec2ede | |||
| 01be463eeb | |||
| a62b6bd93c | |||
| f56af5ee2d | |||
| 3f531da564 | |||
| 7b8996c878 | |||
| c8a9d15887 | |||
| 6b3e51bd33 | |||
| 987f821f79 | |||
| f6cf5ea790 | |||
| de36c76a23 | |||
| fef3410f7f | |||
| 57888f7300 | |||
| 4073b26a20 | |||
| ac0a42233a | |||
| 3c40ed4281 | |||
| 0cd4f69c0c | |||
| bf3cf57e15 | |||
| f5e89dba9d | |||
| 0408529b48 | |||
| 5cf03728a6 | |||
| 8b994f29a5 | |||
| 654a965367 | |||
| 0c52b373a0 | |||
| f5cebf71fe | |||
| c299a11c02 | |||
| 8c5847a257 | |||
| b87b25f219 | |||
| 572a21c8e9 | |||
| 70af25d03d | |||
| cec0beebb3 | |||
| 64b310ed1a | |||
| 699bdf056b | |||
| 7b3d71e9cc | |||
| 1a9cde3ba9 | |||
| 773d2d6c09 | |||
| 37d36205f3 | |||
| 3d39434c36 |
+3
-1
@@ -433,6 +433,7 @@ TESTSRC = \
|
||||
$(TOP)/ext/fts3/fts3_term.c \
|
||||
$(TOP)/ext/fts3/fts3_test.c \
|
||||
$(TOP)/ext/session/test_session.c \
|
||||
$(TOP)/ext/session/sqlite3changebatch.c \
|
||||
$(TOP)/ext/rbu/test_rbu.c
|
||||
|
||||
# Statically linked extensions
|
||||
@@ -522,8 +523,9 @@ TESTSRC2 = \
|
||||
$(TOP)/ext/fts3/fts3_tokenizer.c \
|
||||
$(TOP)/ext/fts3/fts3_write.c \
|
||||
$(TOP)/ext/async/sqlite3async.c \
|
||||
$(TOP)/ext/session/sqlite3session.c \
|
||||
$(TOP)/ext/misc/stmt.c \
|
||||
$(TOP)/ext/session/sqlite3session.c \
|
||||
$(TOP)/ext/session/test_session.c \
|
||||
fts5.c
|
||||
|
||||
# Header files used by all library source files.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.69 for sqlite 3.37.0.
|
||||
# Generated by GNU Autoconf 2.69 for sqlite 3.37.2.
|
||||
#
|
||||
#
|
||||
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
|
||||
@@ -726,8 +726,8 @@ MAKEFLAGS=
|
||||
# Identity of this package.
|
||||
PACKAGE_NAME='sqlite'
|
||||
PACKAGE_TARNAME='sqlite'
|
||||
PACKAGE_VERSION='3.37.0'
|
||||
PACKAGE_STRING='sqlite 3.37.0'
|
||||
PACKAGE_VERSION='3.37.2'
|
||||
PACKAGE_STRING='sqlite 3.37.2'
|
||||
PACKAGE_BUGREPORT=''
|
||||
PACKAGE_URL=''
|
||||
|
||||
@@ -770,11 +770,11 @@ ac_includes_default="\
|
||||
ac_subst_vars='LTLIBOBJS
|
||||
LIBOBJS
|
||||
BUILD_CFLAGS
|
||||
AMALGAMATION_LINE_MACROS
|
||||
USE_GCOV
|
||||
OPT_FEATURE_FLAGS
|
||||
HAVE_ZLIB
|
||||
USE_AMALGAMATION
|
||||
AMALGAMATION_LINE_MACROS
|
||||
TARGET_DEBUG
|
||||
TARGET_HAVE_EDITLINE
|
||||
TARGET_HAVE_READLINE
|
||||
@@ -903,7 +903,6 @@ with_readline_lib
|
||||
with_readline_inc
|
||||
enable_debug
|
||||
enable_amalgamation
|
||||
amalgamation_line_macros
|
||||
enable_load_extension
|
||||
enable_math
|
||||
enable_all
|
||||
@@ -1469,7 +1468,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 sqlite 3.37.0 to adapt to many kinds of systems.
|
||||
\`configure' configures sqlite 3.37.2 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@@ -1534,7 +1533,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of sqlite 3.37.0:";;
|
||||
short | recursive ) echo "Configuration of sqlite 3.37.2:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@@ -1662,7 +1661,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
sqlite configure 3.37.0
|
||||
sqlite configure 3.37.2
|
||||
generated by GNU Autoconf 2.69
|
||||
|
||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||
@@ -2081,7 +2080,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 sqlite $as_me 3.37.0, which was
|
||||
It was created by sqlite $as_me 3.37.2, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
@@ -3939,13 +3938,13 @@ if ${lt_cv_nm_interface+:} false; then :
|
||||
else
|
||||
lt_cv_nm_interface="BSD nm"
|
||||
echo "int some_variable = 0;" > conftest.$ac_ext
|
||||
(eval echo "\"\$as_me:3940: $ac_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:3941: $ac_compile\"" >&5)
|
||||
(eval "$ac_compile" 2>conftest.err)
|
||||
cat conftest.err >&5
|
||||
(eval echo "\"\$as_me:3943: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
|
||||
(eval echo "\"\$as_me:3944: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
|
||||
(eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
|
||||
cat conftest.err >&5
|
||||
(eval echo "\"\$as_me:3946: output\"" >&5)
|
||||
(eval echo "\"\$as_me:3947: output\"" >&5)
|
||||
cat conftest.out >&5
|
||||
if $GREP 'External.*some_variable' conftest.out > /dev/null; then
|
||||
lt_cv_nm_interface="MS dumpbin"
|
||||
@@ -5151,7 +5150,7 @@ ia64-*-hpux*)
|
||||
;;
|
||||
*-*-irix6*)
|
||||
# Find out which ABI we are using.
|
||||
echo '#line 5152 "configure"' > conftest.$ac_ext
|
||||
echo '#line 5153 "configure"' > conftest.$ac_ext
|
||||
if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
|
||||
(eval $ac_compile) 2>&5
|
||||
ac_status=$?
|
||||
@@ -6676,11 +6675,11 @@ else
|
||||
-e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
|
||||
-e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
|
||||
-e 's:$: $lt_compiler_flag:'`
|
||||
(eval echo "\"\$as_me:6677: $lt_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:6678: $lt_compile\"" >&5)
|
||||
(eval "$lt_compile" 2>conftest.err)
|
||||
ac_status=$?
|
||||
cat conftest.err >&5
|
||||
echo "$as_me:6681: \$? = $ac_status" >&5
|
||||
echo "$as_me:6682: \$? = $ac_status" >&5
|
||||
if (exit $ac_status) && test -s "$ac_outfile"; then
|
||||
# The compiler can only warn and ignore the option if not recognized
|
||||
# So say no if there are warnings other than the usual output.
|
||||
@@ -7015,11 +7014,11 @@ else
|
||||
-e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
|
||||
-e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
|
||||
-e 's:$: $lt_compiler_flag:'`
|
||||
(eval echo "\"\$as_me:7016: $lt_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:7017: $lt_compile\"" >&5)
|
||||
(eval "$lt_compile" 2>conftest.err)
|
||||
ac_status=$?
|
||||
cat conftest.err >&5
|
||||
echo "$as_me:7020: \$? = $ac_status" >&5
|
||||
echo "$as_me:7021: \$? = $ac_status" >&5
|
||||
if (exit $ac_status) && test -s "$ac_outfile"; then
|
||||
# The compiler can only warn and ignore the option if not recognized
|
||||
# So say no if there are warnings other than the usual output.
|
||||
@@ -7120,11 +7119,11 @@ else
|
||||
-e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
|
||||
-e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
|
||||
-e 's:$: $lt_compiler_flag:'`
|
||||
(eval echo "\"\$as_me:7121: $lt_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:7122: $lt_compile\"" >&5)
|
||||
(eval "$lt_compile" 2>out/conftest.err)
|
||||
ac_status=$?
|
||||
cat out/conftest.err >&5
|
||||
echo "$as_me:7125: \$? = $ac_status" >&5
|
||||
echo "$as_me:7126: \$? = $ac_status" >&5
|
||||
if (exit $ac_status) && test -s out/conftest2.$ac_objext
|
||||
then
|
||||
# The compiler can only warn and ignore the option if not recognized
|
||||
@@ -7175,11 +7174,11 @@ else
|
||||
-e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
|
||||
-e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
|
||||
-e 's:$: $lt_compiler_flag:'`
|
||||
(eval echo "\"\$as_me:7176: $lt_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:7177: $lt_compile\"" >&5)
|
||||
(eval "$lt_compile" 2>out/conftest.err)
|
||||
ac_status=$?
|
||||
cat out/conftest.err >&5
|
||||
echo "$as_me:7180: \$? = $ac_status" >&5
|
||||
echo "$as_me:7181: \$? = $ac_status" >&5
|
||||
if (exit $ac_status) && test -s out/conftest2.$ac_objext
|
||||
then
|
||||
# The compiler can only warn and ignore the option if not recognized
|
||||
@@ -9555,7 +9554,7 @@ else
|
||||
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
|
||||
lt_status=$lt_dlunknown
|
||||
cat > conftest.$ac_ext <<_LT_EOF
|
||||
#line 9556 "configure"
|
||||
#line 9557 "configure"
|
||||
#include "confdefs.h"
|
||||
|
||||
#if HAVE_DLFCN_H
|
||||
@@ -9651,7 +9650,7 @@ else
|
||||
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
|
||||
lt_status=$lt_dlunknown
|
||||
cat > conftest.$ac_ext <<_LT_EOF
|
||||
#line 9652 "configure"
|
||||
#line 9653 "configure"
|
||||
#include "confdefs.h"
|
||||
|
||||
#if HAVE_DLFCN_H
|
||||
@@ -10307,13 +10306,6 @@ done
|
||||
#
|
||||
USE_AMALGAMATION=1
|
||||
|
||||
#########
|
||||
# By default, amalgamation sqlite3.c will have #line directives.
|
||||
# This is a build option not shown by ./configure --help
|
||||
# To control it, use configure option: amalgamation_line_macros=?
|
||||
# where ? is no to suppress #line directives or yes to create them.
|
||||
AMALGAMATION_LINE_MACROS=--linemacros=0
|
||||
|
||||
#########
|
||||
# See whether we can run specific tclsh versions known to work well;
|
||||
# if not, then we fall back to plain tclsh.
|
||||
@@ -11284,19 +11276,6 @@ if test "${enable_amalgamation}" = "no" ; then
|
||||
USE_AMALGAMATION=0
|
||||
fi
|
||||
|
||||
########
|
||||
# See whether --disable
|
||||
if test "${amalgamation_line_macros+set}" = set; then :
|
||||
enableval=$amalgamation_line_macros;
|
||||
fi
|
||||
|
||||
if test "${amalgamation_line_macros}" = "yes" ; then
|
||||
AMALGAMATION_LINE_MACROS=--linemacros=1
|
||||
fi
|
||||
if test "${amalgamation_line_macros}" = "no" ; then
|
||||
AMALGAMATION_LINE_MACROS=--linemacros=0
|
||||
fi
|
||||
|
||||
|
||||
#########
|
||||
# Look for zlib. Only needed by extensions and by the sqlite3.exe shell
|
||||
@@ -11882,6 +11861,17 @@ else
|
||||
fi
|
||||
|
||||
|
||||
#########
|
||||
# Enable/disabled amalagamation line macros
|
||||
########
|
||||
AMALGAMATION_LINE_MACROS=--linemacros=0
|
||||
if test "${amalgamation_line_macros}" = "yes" ; then
|
||||
AMALGAMATION_LINE_MACROS=--linemacros=1
|
||||
fi
|
||||
if test "${amalgamation_line_macros}" = "no" ; then
|
||||
AMALGAMATION_LINE_MACROS=--linemacros=0
|
||||
fi
|
||||
|
||||
|
||||
#########
|
||||
# Output the config header
|
||||
@@ -12400,7 +12390,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 sqlite $as_me 3.37.0, which was
|
||||
This file was extended by sqlite $as_me 3.37.2, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -12466,7 +12456,7 @@ _ACEOF
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
sqlite config.status 3.37.0
|
||||
sqlite config.status 3.37.2
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
||||
@@ -791,6 +791,17 @@ else
|
||||
fi
|
||||
AC_SUBST(USE_GCOV)
|
||||
|
||||
#########
|
||||
# Enable/disabled amalagamation line macros
|
||||
########
|
||||
AMALGAMATION_LINE_MACROS=--linemacros=0
|
||||
if test "${amalgamation_line_macros}" = "yes" ; then
|
||||
AMALGAMATION_LINE_MACROS=--linemacros=1
|
||||
fi
|
||||
if test "${amalgamation_line_macros}" = "no" ; then
|
||||
AMALGAMATION_LINE_MACROS=--linemacros=0
|
||||
fi
|
||||
AC_SUBST(AMALGAMATION_LINE_MACROS)
|
||||
|
||||
#########
|
||||
# Output the config header
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
|
||||
Begin Concurrent
|
||||
================
|
||||
|
||||
## Overview
|
||||
|
||||
Usually, SQLite allows at most one writer to proceed concurrently. The
|
||||
BEGIN CONCURRENT enhancement allows multiple writers to process write
|
||||
transactions simultanously if the database is in "wal" or "wal2" mode,
|
||||
although the system still serializes COMMIT commands.
|
||||
|
||||
When a write-transaction is opened with "BEGIN CONCURRENT", actually
|
||||
locking the database is deferred until a COMMIT is executed. This means
|
||||
that any number of transactions started with BEGIN CONCURRENT may proceed
|
||||
concurrently. The system uses optimistic page-level-locking to prevent
|
||||
conflicting concurrent transactions from being committed.
|
||||
|
||||
When a BEGIN CONCURRENT transaction is committed, the system checks whether
|
||||
or not any of the database pages that the transaction has read have been
|
||||
modified since the BEGIN CONCURRENT was opened. In other words - it asks
|
||||
if the transaction being committed operates on a different set of data than
|
||||
all other concurrently executing transactions. If the answer is "yes, this
|
||||
transaction did not read or modify any data modified by any concurrent
|
||||
transaction", then the transaction is committed as normal. Otherwise, if the
|
||||
transaction does conflict, it cannot be committed and an SQLITE_BUSY_SNAPSHOT
|
||||
error is returned. At this point, all the client can do is ROLLBACK the
|
||||
transaction.
|
||||
|
||||
If SQLITE_BUSY_SNAPSHOT is returned, messages are output via the sqlite3_log
|
||||
mechanism indicating the page and table or index on which the conflict
|
||||
occurred. This can be useful when optimizing concurrency.
|
||||
|
||||
## Application Programming Notes
|
||||
|
||||
In order to serialize COMMIT processing, SQLite takes a lock on the database
|
||||
as part of each COMMIT command and releases it before returning. At most one
|
||||
writer may hold this lock at any one time. If a writer cannot obtain the lock,
|
||||
it uses SQLite's busy-handler to pause and retry for a while:
|
||||
|
||||
<a href=https://www.sqlite.org/c3ref/busy_handler.html>
|
||||
https://www.sqlite.org/c3ref/busy_handler.html
|
||||
</a>
|
||||
|
||||
If there is significant contention for the writer lock, this mechanism can be
|
||||
inefficient. In this case it is better for the application to use a mutex or
|
||||
some other mechanism that supports blocking to ensure that at most one writer
|
||||
is attempting to COMMIT a BEGIN CONCURRENT transaction at a time. This is
|
||||
usually easier if all writers are part of the same operating system process.
|
||||
|
||||
If all database clients (readers and writers) are located in the same OS
|
||||
process, and if that OS is a Unix variant, then it can be more efficient to
|
||||
the built-in VFS "unix-excl" instead of the default "unix". This is because it
|
||||
uses more efficient locking primitives.
|
||||
|
||||
The key to maximizing concurrency using BEGIN CONCURRENT is to ensure that
|
||||
there are a large number of non-conflicting transactions. In SQLite, each
|
||||
table and each index is stored as a separate b-tree, each of which is
|
||||
distributed over a discrete set of database pages. This means that:
|
||||
|
||||
* Two transactions that write to different sets of tables never
|
||||
conflict, and that
|
||||
|
||||
* Two transactions that write to the same tables or indexes only
|
||||
conflict if the values of the keys (either primary keys or indexed
|
||||
rows) are fairly close together. For example, given a large
|
||||
table with the schema:
|
||||
|
||||
<pre> CREATE TABLE t1(a INTEGER PRIMARY KEY, b BLOB);</pre>
|
||||
|
||||
writing two rows with adjacent values for "a" probably will cause a
|
||||
conflict (as the two keys are stored on the same page), but writing two
|
||||
rows with vastly different values for "a" will not (as the keys will likly
|
||||
be stored on different pages).
|
||||
|
||||
Note that, in SQLite, if values are not explicitly supplied for an INTEGER
|
||||
PRIMARY KEY, as for example in:
|
||||
|
||||
>
|
||||
INSERT INTO t1(b) VALUES(<blob-value>);
|
||||
|
||||
then monotonically increasing values are assigned automatically. This is
|
||||
terrible for concurrency, as it all but ensures that all new rows are
|
||||
added to the same database page. In such situations, it is better to
|
||||
explicitly assign random values to INTEGER PRIMARY KEY fields.
|
||||
|
||||
This problem also comes up for non-WITHOUT ROWID tables that do not have an
|
||||
explicit INTEGER PRIMARY KEY column. In these cases each table has an implicit
|
||||
INTEGER PRIMARY KEY column that is assigned increasing values, leading to the
|
||||
same problem as omitting to assign a value to an explicit INTEGER PRIMARY KEY
|
||||
column.
|
||||
|
||||
For both explicit and implicit INTEGER PRIMARY KEYs, it is possible to have
|
||||
SQLite assign values at random (instead of the monotonically increasing
|
||||
values) by writing a row with a rowid equal to the largest possible signed
|
||||
64-bit integer to the table. For example:
|
||||
|
||||
INSERT INTO t1(a) VALUES(9223372036854775807);
|
||||
|
||||
Applications should take care not to malfunction due to the presence of such
|
||||
rows.
|
||||
|
||||
The nature of some types of indexes, for example indexes on timestamp fields,
|
||||
can also cause problems (as concurrent transactions may assign similar
|
||||
timestamps that will be stored on the same db page to new records). In these
|
||||
cases the database schema may need to be rethought to increase the concurrency
|
||||
provided by page-level-locking.
|
||||
|
||||
+1
-1
@@ -4473,7 +4473,7 @@ void sqlite3Fts3DoclistPrev(
|
||||
|
||||
assert( nDoclist>0 );
|
||||
assert( *pbEof==0 );
|
||||
assert( p || *piDocid==0 );
|
||||
assert_fts3_nc( p || *piDocid==0 );
|
||||
assert( !p || (p>aDoclist && p<&aDoclist[nDoclist]) );
|
||||
|
||||
if( p==0 ){
|
||||
|
||||
@@ -1879,8 +1879,12 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){
|
||||
int iRowidOff;
|
||||
iRowidOff = fts5LeafFirstRowidOff(pNew);
|
||||
if( iRowidOff ){
|
||||
pIter->pLeaf = pNew;
|
||||
pIter->iLeafOffset = iRowidOff;
|
||||
if( iRowidOff>=pNew->szLeaf ){
|
||||
p->rc = FTS5_CORRUPT;
|
||||
}else{
|
||||
pIter->pLeaf = pNew;
|
||||
pIter->iLeafOffset = iRowidOff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# 2015 Apr 24
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# This file tests that FTS5 handles corrupt databases (i.e. internal
|
||||
# inconsistencies in the backing tables) correctly. In this case
|
||||
# "correctly" means without crashing.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5corrupt6
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
sqlite3_fts5_may_be_corrupt 1
|
||||
database_may_be_corrupt
|
||||
|
||||
proc editblock {block} {
|
||||
binary format Sa* 20000 [string range $block 2 end]
|
||||
}
|
||||
db func editblock editblock
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(abc, def);
|
||||
WITH a(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM a WHERE i<1000
|
||||
)
|
||||
INSERT INTO ft SELECT
|
||||
'abc abc abc abc abc abc abc abc abc abc',
|
||||
'def def def def def def def def def def'
|
||||
FROM a;
|
||||
UPDATE ft_data SET block = editblock(block) WHERE id=(
|
||||
SELECT id FROM ft_data ORDER BY id LIMIT 1 OFFSET 5
|
||||
);
|
||||
}
|
||||
|
||||
do_catchsql_test 1.1 {
|
||||
SELECT rowid FROM ft('def') ORDER BY rowid DESC LIMIT 1 OFFSET 9999;
|
||||
} {1 {database disk image is malformed}}
|
||||
|
||||
|
||||
sqlite3_fts5_may_be_corrupt 0
|
||||
finish_test
|
||||
|
||||
@@ -304,7 +304,7 @@ static int box_query(sqlite3_rtree_query_info *pInfo){
|
||||
static void box_query_destroy(void *p){
|
||||
BoxQueryCtx *pCtx = (BoxQueryCtx*)p;
|
||||
Tcl_DecrRefCount(pCtx->pScript);
|
||||
ckfree(pCtx);
|
||||
ckfree((char*)pCtx);
|
||||
}
|
||||
|
||||
static int SQLITE_TCLAPI register_box_query(
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
# 2016 August 23
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix changebatch1
|
||||
|
||||
|
||||
proc sql_to_changeset {method sql} {
|
||||
sqlite3session S db main
|
||||
S attach *
|
||||
execsql $sql
|
||||
set ret [S $method]
|
||||
S delete
|
||||
return $ret
|
||||
}
|
||||
|
||||
proc do_changebatch_test {tn method args} {
|
||||
set C [list]
|
||||
foreach a $args {
|
||||
lappend C [sql_to_changeset $method $a]
|
||||
}
|
||||
|
||||
sqlite3changebatch cb db
|
||||
set i 1
|
||||
foreach ::cs [lrange $C 0 end-1] {
|
||||
set rc [cb add $::cs]
|
||||
if {$rc!="SQLITE_OK"} { error "expected SQLITE_OK, got $rc (i=$i)" }
|
||||
incr i
|
||||
}
|
||||
|
||||
set ::cs [lindex $C end]
|
||||
do_test $tn { cb add [set ::cs] } SQLITE_CONSTRAINT
|
||||
cb delete
|
||||
}
|
||||
|
||||
proc do_changebatch_test1 {tn args} {
|
||||
uplevel do_changebatch_test $tn changeset $args
|
||||
}
|
||||
proc do_changebatch_test2 {tn args} {
|
||||
uplevel do_changebatch_test $tn fullchangeset $args
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# The body of the following loop contains tests for database schemas
|
||||
# that do not feature multi-column UNIQUE constraints. In this case
|
||||
# it doesn't matter if the changesets are generated using
|
||||
# sqlite3session_changeset() or sqlite3session_fullchangeset().
|
||||
#
|
||||
foreach {tn testfunction} {
|
||||
1 do_changebatch_test1
|
||||
2 do_changebatch_test2
|
||||
} {
|
||||
reset_db
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_execsql_test $tn.1.0 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
}
|
||||
|
||||
$testfunction $tn.1.1 {
|
||||
INSERT INTO t1 VALUES(1, 1);
|
||||
} {
|
||||
DELETE FROM t1 WHERE a=1;
|
||||
}
|
||||
|
||||
do_execsql_test $tn.1.2.0 {
|
||||
INSERT INTO t1 VALUES(1, 1);
|
||||
INSERT INTO t1 VALUES(2, 2);
|
||||
INSERT INTO t1 VALUES(3, 3);
|
||||
}
|
||||
$testfunction $tn.1.2.1 {
|
||||
DELETE FROM t1 WHERE a=2;
|
||||
} {
|
||||
INSERT INTO t1 VALUES(2, 2);
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_execsql_test $tn.2.0 {
|
||||
CREATE TABLE x1(a, b PRIMARY KEY, c UNIQUE);
|
||||
CREATE TABLE x2(a PRIMARY KEY, b UNIQUE, c UNIQUE);
|
||||
CREATE INDEX x1a ON x1(a);
|
||||
|
||||
INSERT INTO x1 VALUES(1, 1, 'a');
|
||||
INSERT INTO x1 VALUES(1, 2, 'b');
|
||||
INSERT INTO x1 VALUES(1, 3, 'c');
|
||||
}
|
||||
|
||||
$testfunction $tn.2.1 {
|
||||
DELETE FROM x1 WHERE b=2;
|
||||
} {
|
||||
UPDATE x1 SET c='b' WHERE b=3;
|
||||
}
|
||||
|
||||
$testfunction $tn.2.2 {
|
||||
DELETE FROM x1 WHERE b=1;
|
||||
} {
|
||||
INSERT INTO x1 VALUES(1, 5, 'a');
|
||||
}
|
||||
|
||||
set L [list]
|
||||
for {set i 1000} {$i < 10000} {incr i} {
|
||||
lappend L "INSERT INTO x2 VALUES($i, $i, 'x' || $i)"
|
||||
}
|
||||
lappend L "DELETE FROM x2 WHERE b=1005"
|
||||
$testfunction $tn.2.3 {*}$L
|
||||
|
||||
execsql { INSERT INTO x1 VALUES('f', 'f', 'f') }
|
||||
$testfunction $tn.2.4 {
|
||||
INSERT INTO x2 VALUES('f', 'f', 'f');
|
||||
} {
|
||||
INSERT INTO x1 VALUES('g', 'g', 'g');
|
||||
} {
|
||||
DELETE FROM x1 WHERE b='f';
|
||||
} {
|
||||
INSERT INTO x2 VALUES('g', 'g', 'g');
|
||||
} {
|
||||
INSERT INTO x1 VALUES('f', 'f', 'f');
|
||||
}
|
||||
|
||||
execsql {
|
||||
DELETE FROM x1;
|
||||
INSERT INTO x1 VALUES(1.5, 1.5, 1.5);
|
||||
}
|
||||
$testfunction $tn.2.5 {
|
||||
DELETE FROM x1 WHERE b BETWEEN 1 AND 2;
|
||||
} {
|
||||
INSERT INTO x1 VALUES(2.5, 2.5, 2.5);
|
||||
} {
|
||||
INSERT INTO x1 VALUES(1.5, 1.5, 1.5);
|
||||
}
|
||||
|
||||
execsql {
|
||||
DELETE FROM x2;
|
||||
INSERT INTO x2 VALUES(X'abcd', X'1234', X'7890');
|
||||
INSERT INTO x2 VALUES(X'0000', X'0000', X'0000');
|
||||
}
|
||||
breakpoint
|
||||
$testfunction $tn.2.6 {
|
||||
UPDATE x2 SET c = X'1234' WHERE a=X'abcd';
|
||||
INSERT INTO x2 VALUES(X'1234', X'abcd', X'7890');
|
||||
} {
|
||||
DELETE FROM x2 WHERE b=X'0000';
|
||||
} {
|
||||
INSERT INTO x2 VALUES(1, X'0000', NULL);
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test some multi-column UNIQUE constraints. First Using _changeset() to
|
||||
# demonstrate the problem, then using _fullchangeset() to show that it has
|
||||
# been fixed.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE TABLE y1(a PRIMARY KEY, b, c, UNIQUE(b, c));
|
||||
INSERT INTO y1 VALUES(1, 1, 1);
|
||||
INSERT INTO y1 VALUES(2, 2, 2);
|
||||
INSERT INTO y1 VALUES(3, 3, 3);
|
||||
INSERT INTO y1 VALUES(4, 3, 4);
|
||||
BEGIN;
|
||||
}
|
||||
|
||||
do_test 3.1.1 {
|
||||
set c1 [sql_to_changeset changeset { DELETE FROM y1 WHERE a=4 }]
|
||||
set c2 [sql_to_changeset changeset { UPDATE y1 SET c=4 WHERE a=3 }]
|
||||
sqlite3changebatch cb db
|
||||
cb add $c1
|
||||
cb add $c2
|
||||
} {SQLITE_OK}
|
||||
do_test 3.1.2 {
|
||||
cb delete
|
||||
execsql ROLLBACK
|
||||
} {}
|
||||
|
||||
do_test 3.1.1 {
|
||||
set c1 [sql_to_changeset fullchangeset { DELETE FROM y1 WHERE a=4 }]
|
||||
set c2 [sql_to_changeset fullchangeset { UPDATE y1 SET c=4 WHERE a=3 }]
|
||||
sqlite3changebatch cb db
|
||||
cb add $c1
|
||||
cb add $c2
|
||||
} {SQLITE_CONSTRAINT}
|
||||
do_test 3.1.2 {
|
||||
cb delete
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 4.0 {
|
||||
CREATE TABLE t1(x, y, z, PRIMARY KEY(x, y), UNIQUE(z));
|
||||
}
|
||||
|
||||
do_test 4.1 {
|
||||
set c1 [sql_to_changeset fullchangeset { INSERT INTO t1 VALUES(1, 2, 3) }]
|
||||
execsql {
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1(w, x, y, z, PRIMARY KEY(x, y), UNIQUE(z));
|
||||
}
|
||||
sqlite3changebatch cb db
|
||||
list [catch { cb add $c1 } msg] $msg
|
||||
} {1 SQLITE_RANGE}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,42 @@
|
||||
# 2011 Mar 21
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# The focus of this file is testing the session module.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
set testprefix changebatchfault
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a, b, c PRIMARY KEY, UNIQUE(a, b));
|
||||
INSERT INTO t1 VALUES('a', 'a', 'a');
|
||||
INSERT INTO t1 VALUES('b', 'b', 'b');
|
||||
}
|
||||
|
||||
set ::c1 [changeset_from_sql { delete from t1 where c='a' }]
|
||||
set ::c2 [changeset_from_sql { insert into t1 values('c', 'c', 'c') }]
|
||||
|
||||
do_faultsim_test 1 -faults oom-* -body {
|
||||
sqlite3changebatch cb db
|
||||
cb add $::c1
|
||||
cb add $::c2
|
||||
} -test {
|
||||
faultsim_test_result {0 SQLITE_OK} {1 SQLITE_NOMEM}
|
||||
catch { cb delete }
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -29,7 +29,7 @@ do_test 1.0 {
|
||||
WITH s(i) AS (
|
||||
VALUES(1) UNION ALL SELECT i+1 FROM s WHERe i<10000
|
||||
)
|
||||
INSERT INTO t1 SELECT 'abcde', randomblob(16), i FROM s;
|
||||
INSERT INTO t1 SELECT 'abcde', randomblob(18), i FROM s;
|
||||
}
|
||||
compare_db db db2
|
||||
} {}
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
|
||||
#if !defined(SQLITE_TEST) || (defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK))
|
||||
|
||||
#include "sqlite3session.h"
|
||||
#include "sqlite3changebatch.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct BatchTable BatchTable;
|
||||
typedef struct BatchIndex BatchIndex;
|
||||
typedef struct BatchIndexEntry BatchIndexEntry;
|
||||
typedef struct BatchHash BatchHash;
|
||||
|
||||
struct sqlite3_changebatch {
|
||||
sqlite3 *db; /* Database handle used to read schema */
|
||||
BatchTable *pTab; /* First in linked list of tables */
|
||||
int iChangesetId; /* Current changeset id */
|
||||
int iNextIdxId; /* Next available index id */
|
||||
int nEntry; /* Number of entries in hash table */
|
||||
int nHash; /* Number of hash buckets */
|
||||
BatchIndexEntry **apHash; /* Array of hash buckets */
|
||||
};
|
||||
|
||||
struct BatchTable {
|
||||
BatchIndex *pIdx; /* First in linked list of UNIQUE indexes */
|
||||
BatchTable *pNext; /* Next table */
|
||||
char zTab[1]; /* Table name */
|
||||
};
|
||||
|
||||
struct BatchIndex {
|
||||
BatchIndex *pNext; /* Next index on same table */
|
||||
int iId; /* Index id (assigned internally) */
|
||||
int bPk; /* True for PK index */
|
||||
int nCol; /* Size of aiCol[] array */
|
||||
int *aiCol; /* Array of columns that make up index */
|
||||
};
|
||||
|
||||
struct BatchIndexEntry {
|
||||
BatchIndexEntry *pNext; /* Next colliding hash table entry */
|
||||
int iChangesetId; /* Id of associated changeset */
|
||||
int iIdxId; /* Id of index this key is from */
|
||||
int szRecord;
|
||||
char aRecord[1];
|
||||
};
|
||||
|
||||
/*
|
||||
** Allocate and zero a block of nByte bytes. Must be freed using cbFree().
|
||||
*/
|
||||
static void *cbMalloc(int *pRc, int nByte){
|
||||
void *pRet;
|
||||
|
||||
if( *pRc ){
|
||||
pRet = 0;
|
||||
}else{
|
||||
pRet = sqlite3_malloc(nByte);
|
||||
if( pRet ){
|
||||
memset(pRet, 0, nByte);
|
||||
}else{
|
||||
*pRc = SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Free an allocation made by cbMalloc().
|
||||
*/
|
||||
static void cbFree(void *p){
|
||||
sqlite3_free(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the hash bucket that pEntry belongs in.
|
||||
*/
|
||||
static int cbHash(sqlite3_changebatch *p, BatchIndexEntry *pEntry){
|
||||
unsigned int iHash = (unsigned int)pEntry->iIdxId;
|
||||
unsigned char *pEnd = (unsigned char*)&pEntry->aRecord[pEntry->szRecord];
|
||||
unsigned char *pIter;
|
||||
|
||||
for(pIter=(unsigned char*)pEntry->aRecord; pIter<pEnd; pIter++){
|
||||
iHash += (iHash << 7) + *pIter;
|
||||
}
|
||||
|
||||
return (int)(iHash % p->nHash);
|
||||
}
|
||||
|
||||
/*
|
||||
** Resize the hash table.
|
||||
*/
|
||||
static int cbHashResize(sqlite3_changebatch *p){
|
||||
int rc = SQLITE_OK;
|
||||
BatchIndexEntry **apNew;
|
||||
int nNew = (p->nHash ? p->nHash*2 : 512);
|
||||
int i;
|
||||
|
||||
apNew = cbMalloc(&rc, sizeof(BatchIndexEntry*) * nNew);
|
||||
if( rc==SQLITE_OK ){
|
||||
int nHash = p->nHash;
|
||||
p->nHash = nNew;
|
||||
for(i=0; i<nHash; i++){
|
||||
BatchIndexEntry *pEntry;
|
||||
while( (pEntry=p->apHash[i])!=0 ){
|
||||
int iHash = cbHash(p, pEntry);
|
||||
p->apHash[i] = pEntry->pNext;
|
||||
pEntry->pNext = apNew[iHash];
|
||||
apNew[iHash] = pEntry;
|
||||
}
|
||||
}
|
||||
|
||||
cbFree(p->apHash);
|
||||
p->apHash = apNew;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Allocate a new sqlite3_changebatch object.
|
||||
*/
|
||||
int sqlite3changebatch_new(sqlite3 *db, sqlite3_changebatch **pp){
|
||||
sqlite3_changebatch *pRet;
|
||||
int rc = SQLITE_OK;
|
||||
*pp = pRet = (sqlite3_changebatch*)cbMalloc(&rc, sizeof(sqlite3_changebatch));
|
||||
if( pRet ){
|
||||
pRet->db = db;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Add a BatchIndex entry for index zIdx to table pTab.
|
||||
*/
|
||||
static int cbAddIndex(
|
||||
sqlite3_changebatch *p,
|
||||
BatchTable *pTab,
|
||||
const char *zIdx,
|
||||
int bPk
|
||||
){
|
||||
int nCol = 0;
|
||||
sqlite3_stmt *pIndexInfo = 0;
|
||||
BatchIndex *pNew = 0;
|
||||
int rc;
|
||||
char *zIndexInfo;
|
||||
|
||||
zIndexInfo = (char*)sqlite3_mprintf("PRAGMA main.index_info = %Q", zIdx);
|
||||
if( zIndexInfo ){
|
||||
rc = sqlite3_prepare_v2(p->db, zIndexInfo, -1, &pIndexInfo, 0);
|
||||
sqlite3_free(zIndexInfo);
|
||||
}else{
|
||||
rc = SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
while( SQLITE_ROW==sqlite3_step(pIndexInfo) ){ nCol++; }
|
||||
rc = sqlite3_reset(pIndexInfo);
|
||||
}
|
||||
|
||||
pNew = (BatchIndex*)cbMalloc(&rc, sizeof(BatchIndex) + sizeof(int) * nCol);
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew->nCol = nCol;
|
||||
pNew->bPk = bPk;
|
||||
pNew->aiCol = (int*)&pNew[1];
|
||||
pNew->iId = p->iNextIdxId++;
|
||||
while( SQLITE_ROW==sqlite3_step(pIndexInfo) ){
|
||||
int i = sqlite3_column_int(pIndexInfo, 0);
|
||||
int j = sqlite3_column_int(pIndexInfo, 1);
|
||||
pNew->aiCol[i] = j;
|
||||
}
|
||||
rc = sqlite3_reset(pIndexInfo);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew->pNext = pTab->pIdx;
|
||||
pTab->pIdx = pNew;
|
||||
}else{
|
||||
cbFree(pNew);
|
||||
}
|
||||
sqlite3_finalize(pIndexInfo);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Free the object passed as the first argument.
|
||||
*/
|
||||
static void cbFreeTable(BatchTable *pTab){
|
||||
BatchIndex *pIdx;
|
||||
BatchIndex *pIdxNext;
|
||||
for(pIdx=pTab->pIdx; pIdx; pIdx=pIdxNext){
|
||||
pIdxNext = pIdx->pNext;
|
||||
cbFree(pIdx);
|
||||
}
|
||||
cbFree(pTab);
|
||||
}
|
||||
|
||||
/*
|
||||
** Find or create the BatchTable object named zTab.
|
||||
*/
|
||||
static int cbFindTable(
|
||||
sqlite3_changebatch *p,
|
||||
const char *zTab,
|
||||
BatchTable **ppTab
|
||||
){
|
||||
BatchTable *pRet = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
for(pRet=p->pTab; pRet; pRet=pRet->pNext){
|
||||
if( 0==sqlite3_stricmp(zTab, pRet->zTab) ) break;
|
||||
}
|
||||
|
||||
if( pRet==0 ){
|
||||
int nTab = strlen(zTab);
|
||||
pRet = (BatchTable*)cbMalloc(&rc, nTab + sizeof(BatchTable));
|
||||
if( pRet ){
|
||||
sqlite3_stmt *pIndexList = 0;
|
||||
char *zIndexList = 0;
|
||||
int rc2;
|
||||
memcpy(pRet->zTab, zTab, nTab);
|
||||
|
||||
zIndexList = sqlite3_mprintf("PRAGMA main.index_list = %Q", zTab);
|
||||
if( zIndexList==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
rc = sqlite3_prepare_v2(p->db, zIndexList, -1, &pIndexList, 0);
|
||||
sqlite3_free(zIndexList);
|
||||
}
|
||||
|
||||
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pIndexList) ){
|
||||
if( sqlite3_column_int(pIndexList, 2) ){
|
||||
const char *zIdx = (const char*)sqlite3_column_text(pIndexList, 1);
|
||||
const char *zTyp = (const char*)sqlite3_column_text(pIndexList, 3);
|
||||
rc = cbAddIndex(p, pRet, zIdx, (zTyp[0]=='p'));
|
||||
}
|
||||
}
|
||||
rc2 = sqlite3_finalize(pIndexList);
|
||||
if( rc==SQLITE_OK ) rc = rc2;
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
pRet->pNext = p->pTab;
|
||||
p->pTab = pRet;
|
||||
}else{
|
||||
cbFreeTable(pRet);
|
||||
pRet = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*ppTab = pRet;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Extract value iVal from the changeset iterator passed as the first
|
||||
** argument. Set *ppVal to point to the value before returning.
|
||||
**
|
||||
** This function attempts to extract the value using function xVal
|
||||
** (which is always either sqlite3changeset_new or sqlite3changeset_old).
|
||||
** If the call returns SQLITE_OK but does not supply an sqlite3_value*
|
||||
** pointer, an attempt to extract the value is made using the xFallback
|
||||
** function.
|
||||
*/
|
||||
static int cbGetChangesetValue(
|
||||
sqlite3_changeset_iter *pIter,
|
||||
int (*xVal)(sqlite3_changeset_iter*,int,sqlite3_value**),
|
||||
int (*xFallback)(sqlite3_changeset_iter*,int,sqlite3_value**),
|
||||
int iVal,
|
||||
sqlite3_value **ppVal
|
||||
){
|
||||
int rc = xVal(pIter, iVal, ppVal);
|
||||
if( rc==SQLITE_OK && *ppVal==0 && xFallback ){
|
||||
rc = xFallback(pIter, iVal, ppVal);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int cbAddToHash(
|
||||
sqlite3_changebatch *p,
|
||||
sqlite3_changeset_iter *pIter,
|
||||
BatchIndex *pIdx,
|
||||
int (*xVal)(sqlite3_changeset_iter*,int,sqlite3_value**),
|
||||
int (*xFallback)(sqlite3_changeset_iter*,int,sqlite3_value**),
|
||||
int *pbConf
|
||||
){
|
||||
BatchIndexEntry *pNew;
|
||||
int sz = pIdx->nCol;
|
||||
int i;
|
||||
int iOut = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
for(i=0; rc==SQLITE_OK && i<pIdx->nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
rc = cbGetChangesetValue(pIter, xVal, xFallback, pIdx->aiCol[i], &pVal);
|
||||
if( rc==SQLITE_OK ){
|
||||
int eType = 0;
|
||||
if( pVal ) eType = sqlite3_value_type(pVal);
|
||||
switch( eType ){
|
||||
case 0:
|
||||
case SQLITE_NULL:
|
||||
return SQLITE_OK;
|
||||
|
||||
case SQLITE_INTEGER:
|
||||
sz += 8;
|
||||
break;
|
||||
case SQLITE_FLOAT:
|
||||
sz += 8;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
|
||||
sz += sqlite3_value_bytes(pVal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pNew = cbMalloc(&rc, sizeof(BatchIndexEntry) + sz);
|
||||
if( pNew ){
|
||||
pNew->iChangesetId = p->iChangesetId;
|
||||
pNew->iIdxId = pIdx->iId;
|
||||
pNew->szRecord = sz;
|
||||
|
||||
for(i=0; i<pIdx->nCol; i++){
|
||||
int eType;
|
||||
sqlite3_value *pVal;
|
||||
rc = cbGetChangesetValue(pIter, xVal, xFallback, pIdx->aiCol[i], &pVal);
|
||||
if( rc!=SQLITE_OK ) break; /* coverage: condition is never true */
|
||||
eType = sqlite3_value_type(pVal);
|
||||
pNew->aRecord[iOut++] = eType;
|
||||
switch( eType ){
|
||||
case SQLITE_INTEGER: {
|
||||
sqlite3_int64 i64 = sqlite3_value_int64(pVal);
|
||||
memcpy(&pNew->aRecord[iOut], &i64, 8);
|
||||
iOut += 8;
|
||||
break;
|
||||
}
|
||||
case SQLITE_FLOAT: {
|
||||
double d64 = sqlite3_value_double(pVal);
|
||||
memcpy(&pNew->aRecord[iOut], &d64, sizeof(double));
|
||||
iOut += sizeof(double);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
int nByte = sqlite3_value_bytes(pVal);
|
||||
const char *z = (const char*)sqlite3_value_blob(pVal);
|
||||
memcpy(&pNew->aRecord[iOut], z, nByte);
|
||||
iOut += nByte;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && p->nEntry>=(p->nHash/2) ){
|
||||
rc = cbHashResize(p);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
BatchIndexEntry *pIter;
|
||||
int iHash = cbHash(p, pNew);
|
||||
|
||||
assert( iHash>=0 && iHash<p->nHash );
|
||||
for(pIter=p->apHash[iHash]; pIter; pIter=pIter->pNext){
|
||||
if( pNew->szRecord==pIter->szRecord
|
||||
&& 0==memcmp(pNew->aRecord, pIter->aRecord, pNew->szRecord)
|
||||
){
|
||||
if( pNew->iChangesetId!=pIter->iChangesetId ){
|
||||
*pbConf = 1;
|
||||
}
|
||||
cbFree(pNew);
|
||||
pNew = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( pNew ){
|
||||
pNew->pNext = p->apHash[iHash];
|
||||
p->apHash[iHash] = pNew;
|
||||
p->nEntry++;
|
||||
}
|
||||
}else{
|
||||
cbFree(pNew);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Add a changeset to the current batch.
|
||||
*/
|
||||
int sqlite3changebatch_add(sqlite3_changebatch *p, void *pBuf, int nBuf){
|
||||
sqlite3_changeset_iter *pIter; /* Iterator opened on pBuf/nBuf */
|
||||
int rc; /* Return code */
|
||||
int bConf = 0; /* Conflict was detected */
|
||||
|
||||
rc = sqlite3changeset_start(&pIter, nBuf, pBuf);
|
||||
if( rc==SQLITE_OK ){
|
||||
int rc2;
|
||||
for(rc2 = sqlite3changeset_next(pIter);
|
||||
rc2==SQLITE_ROW;
|
||||
rc2 = sqlite3changeset_next(pIter)
|
||||
){
|
||||
BatchTable *pTab;
|
||||
BatchIndex *pIdx;
|
||||
const char *zTab; /* Table this change applies to */
|
||||
int nCol; /* Number of columns in table */
|
||||
int op; /* UPDATE, INSERT or DELETE */
|
||||
|
||||
sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
|
||||
assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
|
||||
|
||||
rc = cbFindTable(p, zTab, &pTab);
|
||||
assert( pTab || rc!=SQLITE_OK );
|
||||
if( pTab ){
|
||||
for(pIdx=pTab->pIdx; pIdx && rc==SQLITE_OK; pIdx=pIdx->pNext){
|
||||
if( op==SQLITE_UPDATE && pIdx->bPk ) continue;
|
||||
if( op==SQLITE_UPDATE || op==SQLITE_DELETE ){
|
||||
rc = cbAddToHash(p, pIter, pIdx, sqlite3changeset_old, 0, &bConf);
|
||||
}
|
||||
if( op==SQLITE_UPDATE || op==SQLITE_INSERT ){
|
||||
rc = cbAddToHash(p, pIter, pIdx,
|
||||
sqlite3changeset_new, sqlite3changeset_old, &bConf
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
}
|
||||
|
||||
rc2 = sqlite3changeset_finalize(pIter);
|
||||
if( rc==SQLITE_OK ) rc = rc2;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && bConf ){
|
||||
rc = SQLITE_CONSTRAINT;
|
||||
}
|
||||
p->iChangesetId++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Zero an existing changebatch object.
|
||||
*/
|
||||
void sqlite3changebatch_zero(sqlite3_changebatch *p){
|
||||
int i;
|
||||
for(i=0; i<p->nHash; i++){
|
||||
BatchIndexEntry *pEntry;
|
||||
BatchIndexEntry *pNext;
|
||||
for(pEntry=p->apHash[i]; pEntry; pEntry=pNext){
|
||||
pNext = pEntry->pNext;
|
||||
cbFree(pEntry);
|
||||
}
|
||||
}
|
||||
cbFree(p->apHash);
|
||||
p->nHash = 0;
|
||||
p->apHash = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Delete a changebatch object.
|
||||
*/
|
||||
void sqlite3changebatch_delete(sqlite3_changebatch *p){
|
||||
BatchTable *pTab;
|
||||
BatchTable *pTabNext;
|
||||
|
||||
sqlite3changebatch_zero(p);
|
||||
for(pTab=p->pTab; pTab; pTab=pTabNext){
|
||||
pTabNext = pTab->pNext;
|
||||
cbFreeTable(pTab);
|
||||
}
|
||||
cbFree(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the db handle.
|
||||
*/
|
||||
sqlite3 *sqlite3changebatch_db(sqlite3_changebatch *p){
|
||||
return p->db;
|
||||
}
|
||||
|
||||
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
@@ -0,0 +1,82 @@
|
||||
|
||||
#if !defined(SQLITECHANGEBATCH_H_)
|
||||
#define SQLITECHANGEBATCH_H_ 1
|
||||
|
||||
typedef struct sqlite3_changebatch sqlite3_changebatch;
|
||||
|
||||
/*
|
||||
** Create a new changebatch object for detecting conflicts between
|
||||
** changesets associated with a schema equivalent to that of the "main"
|
||||
** database of the open database handle db passed as the first
|
||||
** parameter. It is the responsibility of the caller to ensure that
|
||||
** the database handle is not closed until after the changebatch
|
||||
** object has been deleted.
|
||||
**
|
||||
** A changebatch object is used to detect batches of non-conflicting
|
||||
** changesets. Changesets that do not conflict may be applied to the
|
||||
** target database in any order without affecting the final state of
|
||||
** the database.
|
||||
**
|
||||
** The changebatch object only works reliably if PRIMARY KEY and UNIQUE
|
||||
** constraints on tables affected by the changesets use collation
|
||||
** sequences that are equivalent to built-in collation sequence
|
||||
** BINARY for the == operation.
|
||||
**
|
||||
** If successful, SQLITE_OK is returned and (*pp) set to point to
|
||||
** the new changebatch object. If an error occurs, an SQLite error
|
||||
** code is returned and the final value of (*pp) is undefined.
|
||||
*/
|
||||
int sqlite3changebatch_new(sqlite3 *db, sqlite3_changebatch **pp);
|
||||
|
||||
/*
|
||||
** Argument p points to a buffer containing a changeset n bytes in
|
||||
** size. Assuming no error occurs, this function returns SQLITE_OK
|
||||
** if the changeset does not conflict with any changeset passed
|
||||
** to an sqlite3changebatch_add() call made on the same
|
||||
** sqlite3_changebatch* handle since the most recent call to
|
||||
** sqlite3changebatch_zero(). If the changeset does conflict with
|
||||
** an earlier such changeset, SQLITE_CONSTRAINT is returned. Or,
|
||||
** if an error occurs, some other SQLite error code may be returned.
|
||||
**
|
||||
** One changeset is said to conflict with another if
|
||||
** either:
|
||||
**
|
||||
** * the two changesets contain operations (INSERT, UPDATE or
|
||||
** DELETE) on the same row, identified by primary key, or
|
||||
**
|
||||
** * the two changesets contain operations (INSERT, UPDATE or
|
||||
** DELETE) on rows with identical values in any combination
|
||||
** of fields constrained by a UNIQUE constraint.
|
||||
**
|
||||
** Even if this function returns SQLITE_CONFLICT, the current
|
||||
** changeset is added to the internal data structures - so future
|
||||
** calls to this function may conflict with it. If this function
|
||||
** returns any result code other than SQLITE_OK or SQLITE_CONFLICT,
|
||||
** the result of any future call to sqlite3changebatch_add() is
|
||||
** undefined.
|
||||
**
|
||||
** Only changesets may be passed to this function. Passing a
|
||||
** patchset to this function results in an SQLITE_MISUSE error.
|
||||
*/
|
||||
int sqlite3changebatch_add(sqlite3_changebatch*, void *p, int n);
|
||||
|
||||
/*
|
||||
** Zero a changebatch object. This causes the records of all earlier
|
||||
** calls to sqlite3changebatch_add() to be discarded.
|
||||
*/
|
||||
void sqlite3changebatch_zero(sqlite3_changebatch*);
|
||||
|
||||
/*
|
||||
** Return a copy of the first argument passed to the sqlite3changebatch_new()
|
||||
** call used to create the changebatch object passed as the only argument
|
||||
** to this function.
|
||||
*/
|
||||
sqlite3 *sqlite3changebatch_db(sqlite3_changebatch*);
|
||||
|
||||
/*
|
||||
** Delete a changebatch object.
|
||||
*/
|
||||
void sqlite3changebatch_delete(sqlite3_changebatch*);
|
||||
|
||||
#endif /* !defined(SQLITECHANGEBATCH_H_) */
|
||||
|
||||
@@ -25,6 +25,13 @@ typedef struct SessionInput SessionInput;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
** The three different types of changesets generated.
|
||||
*/
|
||||
#define SESSIONS_PATCHSET 0
|
||||
#define SESSIONS_CHANGESET 1
|
||||
#define SESSIONS_FULLCHANGESET 2
|
||||
|
||||
static int sessions_strm_chunk_size = SESSIONS_STRM_CHUNK_SIZE;
|
||||
|
||||
typedef struct SessionHook SessionHook;
|
||||
@@ -2233,7 +2240,7 @@ static void sessionAppendCol(
|
||||
*/
|
||||
static int sessionAppendUpdate(
|
||||
SessionBuffer *pBuf, /* Buffer to append to */
|
||||
int bPatchset, /* True for "patchset", 0 for "changeset" */
|
||||
int ePatchset, /* True for "patchset", 0 for "changeset" */
|
||||
sqlite3_stmt *pStmt, /* Statement handle pointing at new row */
|
||||
SessionChange *p, /* Object containing old values */
|
||||
u8 *abPK /* Boolean array - true for PK columns */
|
||||
@@ -2297,8 +2304,8 @@ static int sessionAppendUpdate(
|
||||
|
||||
/* Add a field to the old.* record. This is omitted if this modules is
|
||||
** currently generating a patchset. */
|
||||
if( bPatchset==0 ){
|
||||
if( bChanged || abPK[i] ){
|
||||
if( ePatchset!=SESSIONS_PATCHSET ){
|
||||
if( ePatchset==SESSIONS_FULLCHANGESET || bChanged || abPK[i] ){
|
||||
sessionAppendBlob(pBuf, pCsr, nAdvance, &rc);
|
||||
}else{
|
||||
sessionAppendByte(pBuf, 0, &rc);
|
||||
@@ -2307,7 +2314,7 @@ static int sessionAppendUpdate(
|
||||
|
||||
/* Add a field to the new.* record. Or the only record if currently
|
||||
** generating a patchset. */
|
||||
if( bChanged || (bPatchset && abPK[i]) ){
|
||||
if( bChanged || (ePatchset==SESSIONS_PATCHSET && abPK[i]) ){
|
||||
sessionAppendCol(&buf2, pStmt, i, &rc);
|
||||
}else{
|
||||
sessionAppendByte(&buf2, 0, &rc);
|
||||
@@ -2333,7 +2340,7 @@ static int sessionAppendUpdate(
|
||||
*/
|
||||
static int sessionAppendDelete(
|
||||
SessionBuffer *pBuf, /* Buffer to append to */
|
||||
int bPatchset, /* True for "patchset", 0 for "changeset" */
|
||||
int eChangeset, /* One of SESSIONS_CHANGESET etc. */
|
||||
SessionChange *p, /* Object containing old values */
|
||||
int nCol, /* Number of columns in table */
|
||||
u8 *abPK /* Boolean array - true for PK columns */
|
||||
@@ -2343,7 +2350,7 @@ static int sessionAppendDelete(
|
||||
sessionAppendByte(pBuf, SQLITE_DELETE, &rc);
|
||||
sessionAppendByte(pBuf, p->bIndirect, &rc);
|
||||
|
||||
if( bPatchset==0 ){
|
||||
if( eChangeset!=SESSIONS_PATCHSET ){
|
||||
sessionAppendBlob(pBuf, p->aRecord, p->nRecord, &rc);
|
||||
}else{
|
||||
int i;
|
||||
@@ -2516,12 +2523,12 @@ static int sessionSelectBind(
|
||||
*/
|
||||
static void sessionAppendTableHdr(
|
||||
SessionBuffer *pBuf, /* Append header to this buffer */
|
||||
int bPatchset, /* Use the patchset format if true */
|
||||
int ePatchset, /* Use the patchset format if true */
|
||||
SessionTable *pTab, /* Table object to append header for */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
/* Write a table header */
|
||||
sessionAppendByte(pBuf, (bPatchset ? 'P' : 'T'), pRc);
|
||||
sessionAppendByte(pBuf, (ePatchset==SESSIONS_PATCHSET) ? 'P' : 'T', pRc);
|
||||
sessionAppendVarint(pBuf, pTab->nCol, pRc);
|
||||
sessionAppendBlob(pBuf, pTab->abPK, pTab->nCol, pRc);
|
||||
sessionAppendBlob(pBuf, (u8 *)pTab->zName, (int)strlen(pTab->zName)+1, pRc);
|
||||
@@ -2539,7 +2546,7 @@ static void sessionAppendTableHdr(
|
||||
*/
|
||||
static int sessionGenerateChangeset(
|
||||
sqlite3_session *pSession, /* Session object */
|
||||
int bPatchset, /* True for patchset, false for changeset */
|
||||
int ePatchset, /* One of SESSIONS_CHANGESET etc. */
|
||||
int (*xOutput)(void *pOut, const void *pData, int nData),
|
||||
void *pOut, /* First argument for xOutput */
|
||||
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
|
||||
@@ -2586,7 +2593,7 @@ static int sessionGenerateChangeset(
|
||||
}
|
||||
|
||||
/* Write a table header */
|
||||
sessionAppendTableHdr(&buf, bPatchset, pTab, &rc);
|
||||
sessionAppendTableHdr(&buf, ePatchset, pTab, &rc);
|
||||
|
||||
/* Build and compile a statement to execute: */
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -2611,10 +2618,10 @@ static int sessionGenerateChangeset(
|
||||
}
|
||||
}else{
|
||||
assert( abPK!=0 ); /* Because sessionSelectStmt() returned ok */
|
||||
rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK);
|
||||
rc = sessionAppendUpdate(&buf, ePatchset, pSel, p, abPK);
|
||||
}
|
||||
}else if( p->op!=SQLITE_INSERT ){
|
||||
rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK);
|
||||
rc = sessionAppendDelete(&buf, ePatchset, p, nCol, abPK);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_reset(pSel);
|
||||
@@ -2674,7 +2681,8 @@ int sqlite3session_changeset(
|
||||
int rc;
|
||||
|
||||
if( pnChangeset==0 || ppChangeset==0 ) return SQLITE_MISUSE;
|
||||
rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset,ppChangeset);
|
||||
rc = sessionGenerateChangeset(
|
||||
pSession, SESSIONS_CHANGESET, 0, 0, pnChangeset, ppChangeset);
|
||||
assert( rc || pnChangeset==0
|
||||
|| pSession->bEnableSize==0 || *pnChangeset<=pSession->nMaxChangesetSize
|
||||
);
|
||||
@@ -2690,7 +2698,8 @@ int sqlite3session_changeset_strm(
|
||||
void *pOut
|
||||
){
|
||||
if( xOutput==0 ) return SQLITE_MISUSE;
|
||||
return sessionGenerateChangeset(pSession, 0, xOutput, pOut, 0, 0);
|
||||
return sessionGenerateChangeset(
|
||||
pSession, SESSIONS_CHANGESET, xOutput, pOut, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2702,7 +2711,8 @@ int sqlite3session_patchset_strm(
|
||||
void *pOut
|
||||
){
|
||||
if( xOutput==0 ) return SQLITE_MISUSE;
|
||||
return sessionGenerateChangeset(pSession, 1, xOutput, pOut, 0, 0);
|
||||
return sessionGenerateChangeset(
|
||||
pSession, SESSIONS_PATCHSET, xOutput, pOut, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2718,9 +2728,20 @@ int sqlite3session_patchset(
|
||||
void **ppPatchset /* OUT: Buffer containing changeset */
|
||||
){
|
||||
if( pnPatchset==0 || ppPatchset==0 ) return SQLITE_MISUSE;
|
||||
return sessionGenerateChangeset(pSession, 1, 0, 0, pnPatchset, ppPatchset);
|
||||
return sessionGenerateChangeset(
|
||||
pSession, SESSIONS_PATCHSET, 0, 0, pnPatchset, ppPatchset);
|
||||
}
|
||||
|
||||
int sqlite3session_fullchangeset(
|
||||
sqlite3_session *pSession, /* Session object */
|
||||
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
|
||||
void **ppChangeset /* OUT: Buffer containing changeset */
|
||||
){
|
||||
return sessionGenerateChangeset(
|
||||
pSession, SESSIONS_FULLCHANGESET, 0, 0, pnChangeset, ppChangeset);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Enable or disable the session object passed as the first argument.
|
||||
*/
|
||||
@@ -5263,10 +5284,11 @@ static int sessionChangegroupOutput(
|
||||
** hash tables attached to the SessionTable objects in list p->pList.
|
||||
*/
|
||||
for(pTab=pGrp->pList; rc==SQLITE_OK && pTab; pTab=pTab->pNext){
|
||||
int eChangeset = pGrp->bPatch ? SESSIONS_PATCHSET : SESSIONS_CHANGESET;
|
||||
int i;
|
||||
if( pTab->nEntry==0 ) continue;
|
||||
|
||||
sessionAppendTableHdr(&buf, pGrp->bPatch, pTab, &rc);
|
||||
sessionAppendTableHdr(&buf, eChangeset, pTab, &rc);
|
||||
for(i=0; i<pTab->nChange; i++){
|
||||
SessionChange *p;
|
||||
for(p=pTab->apChange[i]; p; p=p->pNext){
|
||||
|
||||
@@ -355,6 +355,19 @@ int sqlite3session_changeset(
|
||||
void **ppChangeset /* OUT: Buffer containing changeset */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Generate A Full Changeset From A Session Object
|
||||
**
|
||||
** This function is similar to sqlite3session_changeset(), except that for
|
||||
** each row affected by an UPDATE statement, all old.* values are recorded
|
||||
** as part of the changeset, not just those modified.
|
||||
*/
|
||||
int sqlite3session_fullchangeset(
|
||||
sqlite3_session *pSession, /* Session object */
|
||||
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
|
||||
void **ppChangeset /* OUT: Buffer containing changeset */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Return An Upper-limit For The Size Of The Changeset
|
||||
** METHOD: sqlite3_session
|
||||
|
||||
+147
-17
@@ -221,6 +221,7 @@ static int testStreamOutput(
|
||||
** $session indirect INTEGER
|
||||
** $session patchset
|
||||
** $session table_filter SCRIPT
|
||||
** $session fullchangeset
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_session_cmd(
|
||||
void *clientData,
|
||||
@@ -234,20 +235,20 @@ static int SQLITE_TCLAPI test_session_cmd(
|
||||
const char *zSub;
|
||||
int nArg;
|
||||
const char *zMsg;
|
||||
int iSub;
|
||||
} aSub[] = {
|
||||
{ "attach", 1, "TABLE", }, /* 0 */
|
||||
{ "changeset", 0, "", }, /* 1 */
|
||||
{ "delete", 0, "", }, /* 2 */
|
||||
{ "enable", 1, "BOOL", }, /* 3 */
|
||||
{ "indirect", 1, "BOOL", }, /* 4 */
|
||||
{ "isempty", 0, "", }, /* 5 */
|
||||
{ "table_filter", 1, "SCRIPT", }, /* 6 */
|
||||
{ "attach", 1, "TABLE" }, /* 0 */
|
||||
{ "changeset", 0, "" }, /* 1 */
|
||||
{ "delete", 0, "" }, /* 2 */
|
||||
{ "enable", 1, "BOOL" }, /* 3 */
|
||||
{ "indirect", 1, "BOOL" }, /* 4 */
|
||||
{ "isempty", 0, "" }, /* 5 */
|
||||
{ "table_filter", 1, "SCRIPT" }, /* 6 */
|
||||
{ "patchset", 0, "", }, /* 7 */
|
||||
{ "diff", 2, "FROMDB TBL", }, /* 8 */
|
||||
{ "memory_used", 0, "", }, /* 9 */
|
||||
{ "changeset_size", 0, "", }, /* 10 */
|
||||
{ "object_config_size", 1, "INTEGER", }, /* 11 */
|
||||
{ "diff", 2, "FROMDB TBL" }, /* 8 */
|
||||
{ "fullchangeset",0, "" }, /* 9 */
|
||||
{ "memory_used", 0, "", }, /* 10 */
|
||||
{ "changeset_size", 0, "", }, /* 11 */
|
||||
{ "object_config_size", 1, "INTEGER", }, /* 12 */
|
||||
{ 0 }
|
||||
};
|
||||
int iSub;
|
||||
@@ -277,10 +278,11 @@ static int SQLITE_TCLAPI test_session_cmd(
|
||||
break;
|
||||
}
|
||||
|
||||
case 9: /* fullchangeset */
|
||||
case 7: /* patchset */
|
||||
case 1: { /* changeset */
|
||||
TestSessionsBlob o = {0, 0};
|
||||
if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){
|
||||
if( iSub!=9 && test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){
|
||||
void *pCtx = (void*)&o;
|
||||
if( iSub==7 ){
|
||||
rc = sqlite3session_patchset_strm(pSession, testStreamOutput, pCtx);
|
||||
@@ -290,6 +292,8 @@ static int SQLITE_TCLAPI test_session_cmd(
|
||||
}else{
|
||||
if( iSub==7 ){
|
||||
rc = sqlite3session_patchset(pSession, &o.n, &o.p);
|
||||
}else if( iSub==9 ){
|
||||
rc = sqlite3session_fullchangeset(pSession, &o.n, &o.p);
|
||||
}else{
|
||||
rc = sqlite3session_changeset(pSession, &o.n, &o.p);
|
||||
}
|
||||
@@ -304,6 +308,7 @@ static int SQLITE_TCLAPI test_session_cmd(
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 2: /* delete */
|
||||
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
|
||||
break;
|
||||
@@ -354,18 +359,18 @@ static int SQLITE_TCLAPI test_session_cmd(
|
||||
break;
|
||||
}
|
||||
|
||||
case 9: { /* memory_used */
|
||||
case 10: { /* memory_used */
|
||||
sqlite3_int64 nMalloc = sqlite3session_memory_used(pSession);
|
||||
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nMalloc));
|
||||
break;
|
||||
}
|
||||
|
||||
case 10: {
|
||||
case 11: {
|
||||
sqlite3_int64 nSize = sqlite3session_changeset_size(pSession);
|
||||
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nSize));
|
||||
break;
|
||||
}
|
||||
case 11: {
|
||||
case 12: {
|
||||
int rc;
|
||||
int iArg;
|
||||
if( Tcl_GetIntFromObj(interp, objv[2], &iArg) ){
|
||||
@@ -521,7 +526,7 @@ static int test_filter_handler(
|
||||
|
||||
Tcl_DecrRefCount(pEval);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
static int test_conflict_handler(
|
||||
void *pCtx, /* Pointer to TestConflictHandler structure */
|
||||
@@ -1163,6 +1168,127 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
#include "sqlite3changebatch.h"
|
||||
|
||||
typedef struct TestChangebatch TestChangebatch;
|
||||
struct TestChangebatch {
|
||||
sqlite3_changebatch *pChangebatch;
|
||||
};
|
||||
|
||||
/*
|
||||
** Tclcmd: $changebatch add BLOB
|
||||
** $changebatch zero
|
||||
** $changebatch delete
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_changebatch_cmd(
|
||||
void *clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
TestChangebatch *p = (TestChangebatch*)clientData;
|
||||
sqlite3_changebatch *pChangebatch = p->pChangebatch;
|
||||
struct SessionSubcmd {
|
||||
const char *zSub;
|
||||
int nArg;
|
||||
const char *zMsg;
|
||||
int iSub;
|
||||
} aSub[] = {
|
||||
{ "add", 1, "CHANGESET", }, /* 0 */
|
||||
{ "zero", 0, "", }, /* 1 */
|
||||
{ "delete", 0, "", }, /* 2 */
|
||||
{ 0 }
|
||||
};
|
||||
int iSub;
|
||||
int rc;
|
||||
|
||||
if( objc<2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
rc = Tcl_GetIndexFromObjStruct(interp,
|
||||
objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
|
||||
);
|
||||
if( rc!=TCL_OK ) return rc;
|
||||
if( objc!=2+aSub[iSub].nArg ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
switch( iSub ){
|
||||
case 0: { /* add */
|
||||
int nArg;
|
||||
unsigned char *pArg = Tcl_GetByteArrayFromObj(objv[2], &nArg);
|
||||
rc = sqlite3changebatch_add(pChangebatch, pArg, nArg);
|
||||
if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT ){
|
||||
return test_session_error(interp, rc, 0);
|
||||
}else{
|
||||
extern const char *sqlite3ErrName(int);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: { /* zero */
|
||||
sqlite3changebatch_zero(pChangebatch);
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: /* delete */
|
||||
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
|
||||
break;
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static void SQLITE_TCLAPI test_changebatch_del(void *clientData){
|
||||
TestChangebatch *p = (TestChangebatch*)clientData;
|
||||
sqlite3changebatch_delete(p->pChangebatch);
|
||||
ckfree((char*)p);
|
||||
}
|
||||
|
||||
/*
|
||||
** Tclcmd: sqlite3changebatch CMD DB-HANDLE
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_sqlite3changebatch(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3 *db;
|
||||
Tcl_CmdInfo info;
|
||||
int rc; /* sqlite3session_create() return code */
|
||||
TestChangebatch *p; /* New wrapper object */
|
||||
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){
|
||||
Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
db = *(sqlite3 **)info.objClientData;
|
||||
|
||||
p = (TestChangebatch*)ckalloc(sizeof(TestChangebatch));
|
||||
memset(p, 0, sizeof(TestChangebatch));
|
||||
rc = sqlite3changebatch_new(db, &p->pChangebatch);
|
||||
if( rc!=SQLITE_OK ){
|
||||
ckfree((char*)p);
|
||||
return test_session_error(interp, rc, 0);
|
||||
}
|
||||
|
||||
Tcl_CreateObjCommand(
|
||||
interp, Tcl_GetString(objv[1]), test_changebatch_cmd, (ClientData)p,
|
||||
test_changebatch_del
|
||||
);
|
||||
Tcl_SetObjResult(interp, objv[1]);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: CMD configure REBASE-BLOB
|
||||
** tclcmd: CMD rebase CHANGESET
|
||||
@@ -1345,6 +1471,10 @@ int TestSession_Init(Tcl_Interp *interp){
|
||||
Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
Tcl_CreateObjCommand(
|
||||
interp, "sqlite3changebatch", test_sqlite3changebatch, 0, 0
|
||||
);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -441,6 +441,7 @@ TESTSRC2 = \
|
||||
$(TOP)/ext/async/sqlite3async.c \
|
||||
$(TOP)/ext/misc/stmt.c \
|
||||
$(TOP)/ext/session/sqlite3session.c \
|
||||
$(TOP)/ext/session/sqlite3changebatch.c \
|
||||
$(TOP)/ext/session/test_session.c \
|
||||
fts5.c
|
||||
|
||||
@@ -1007,6 +1008,9 @@ THREADTEST3_SRC = $(TOP)/test/threadtest3.c \
|
||||
threadtest3$(EXE): sqlite3.o $(THREADTEST3_SRC) $(TOP)/src/test_multiplex.c
|
||||
$(TCCX) $(TOP)/test/threadtest3.c $(TOP)/src/test_multiplex.c sqlite3.o -o $@ $(THREADLIB)
|
||||
|
||||
bc_test1$(EXE): sqlite3.o $(TOP)/test/bc_test1.c $(TOP)/test/tt3_core.c
|
||||
$(TCCX) $(TOP)/test/bc_test1.c sqlite3.o -o $@ $(THREADLIB)
|
||||
|
||||
threadtest: threadtest3$(EXE)
|
||||
./threadtest3$(EXE)
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
C Version\s3.37.0
|
||||
D 2021-11-27T14:13:22.430
|
||||
C Merge\s3.37.2\schanges\sinto\sthe\sbegin-concurrent-pnu\sbranch.
|
||||
D 2022-01-07T14:09:33.971
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
F Makefile.in 0e91c42a1dd13a569b1fa4f4dfb7d3632f3164a1c05c71341533d67db5b641dd
|
||||
F Makefile.in f4d094d775d43e371d0f45d1d2bce6f3455a0f8d06aaf83f023787b31d3a54b8
|
||||
F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
|
||||
F Makefile.msc 88f05063ee36a5fb77d69b877ef824d0743b5325c95bfbf74d6ff17bd1c9fb1f
|
||||
F README.md 27fb76aa7eb57ed63a53bbba7292b6bf71f51125554f79f16b5d040edd1e6110
|
||||
F VERSION c6595fef606851f2bc3ebed6a7386c73751835fc909feab7c093739fa4b3c1d1
|
||||
F VERSION b8218d35896c0f381cf64a499abbab64eeb86b40116a7d35c5a39c6677e8bb89
|
||||
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
|
||||
F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2
|
||||
F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90
|
||||
@@ -34,10 +34,11 @@ F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63
|
||||
F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6
|
||||
F config.h.in 6376abec766e9a0785178b1823b5a587e9f1ccbc
|
||||
F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559
|
||||
F configure e8003577b8c433cf61371d27d4beeca3a3ed37611a504ff5f4d4286923b87d03 x
|
||||
F configure.ac 4e4b58b32f88c8da9914a2f2c3158f80e69907eccc019fcc7e3ba14ffd91c640
|
||||
F configure f8a03394ccec572e76a570aa858ccd24e0d54fd76c995b7220a4dfda8b899d27 x
|
||||
F configure.ac c8ba54bac7e73e000acdfef5e394fe21a3876aa09d0f5c07131bf5ac5a525299
|
||||
F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad
|
||||
F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd
|
||||
F doc/begin_concurrent.md 4bee2c3990d1eb800f1ce3726a911292a8e4b889300b2ffd4b08d357370db299
|
||||
F doc/lemon.html efc0cd2345d66905505d98f862e1c571512def0ceb5b016cb658fd4918eb76a3
|
||||
F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710
|
||||
F doc/trusted-schema.md 33625008620e879c7bcfbbfa079587612c434fa094d338b08242288d358c3e8a
|
||||
@@ -84,7 +85,7 @@ F ext/fts3/README.content b9078d0843a094d86af0d48dffbff13c906702b4c3558012e67b9c
|
||||
F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
|
||||
F ext/fts3/README.tokenizers b92bdeb8b46503f0dd301d364efc5ef59ef9fa8e2758b8e742f39fa93a2e422d
|
||||
F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
|
||||
F ext/fts3/fts3.c da41de21622774b1cb8c3415e8d2a6961ad7c978ab534f2a54434f76a5c4dfbc
|
||||
F ext/fts3/fts3.c 6bfdc941372ee431e3d362986674a8708eb0e700b28c3d35d46d953a9351155f
|
||||
F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
|
||||
F ext/fts3/fts3Int.h cff59b8b13dafe9d59924a5d710f771ed8b121a55cccbc99b6e2a723fcde14dc
|
||||
F ext/fts3/fts3_aux.c f0dc9bd98582615b7750218899bd0c729879b6bbf94d1be57ca1833ff49afc6f
|
||||
@@ -119,7 +120,7 @@ F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b7292
|
||||
F ext/fts5/fts5_config.c 501e7d3566bc92766b0e11c0109a7c5a6146bc41144195459af5422f6c2078aa
|
||||
F ext/fts5/fts5_expr.c fcd0770d53028c2b53a15d0f53bf6d0e01b1bf3dd97630b9fedf0801f03aa3ec
|
||||
F ext/fts5/fts5_hash.c d4fb70940359f2120ccd1de7ffe64cc3efe65de9e8995b822cd536ff64c96982
|
||||
F ext/fts5/fts5_index.c a3ada4897c3b14b8a15a8695d2cb3a46b5761137aae0964fc44efe96a877ddd0
|
||||
F ext/fts5/fts5_index.c b1b2e5d4a9e3b54c740d8354cc47e3fa879f54c2176de55e0b882dab45ab7b07
|
||||
F ext/fts5/fts5_main.c 7c6092a53e6802962fa07b0fad3e61cb077b6c98b74b727d8d44ac2cf63bd914
|
||||
F ext/fts5/fts5_storage.c 76c6085239eb44424004c022e9da17a5ecd5aaec859fba90ad47d3b08f4c8082
|
||||
F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae
|
||||
@@ -163,6 +164,7 @@ F ext/fts5/test/fts5corrupt2.test 7453752ba12ce91690c469a6449d412561cc604b1dec99
|
||||
F ext/fts5/test/fts5corrupt3.test 0e473620582a53ac61f468f364db8a151c1e18d2a879b16439d172c12c4c9828
|
||||
F ext/fts5/test/fts5corrupt4.test f4c08e2182a48d8b70975fd869ee5391855c06d8a0ff87b6a2529e7c5a88a1d3
|
||||
F ext/fts5/test/fts5corrupt5.test 550d0884c14424f9acad051a741f1dd99ec9342277d938e91ff3daf9123d1209
|
||||
F ext/fts5/test/fts5corrupt6.test bf8eeae07825b088b9665d9d8e4accbd8dc9bf3cb85b6c64cf6c9e18ccc420a4
|
||||
F ext/fts5/test/fts5delete.test 619295b20dbc1d840b403ee07c878f52378849c3c02e44f2ee143b3e978a0aa7
|
||||
F ext/fts5/test/fts5detail.test 54015e9c43ec4ba542cfb93268abdf280e0300f350efd08ee411284b03595cc4
|
||||
F ext/fts5/test/fts5determin.test 1b77879b2ae818b5b71c859e534ee334dac088b7cf3ff3bf76a2c82b1c788d11
|
||||
@@ -425,11 +427,13 @@ F ext/rtree/rtreedoc2.test 194ebb7d561452dcdc10bf03f44e30c082c2f0c14efeb07f5e02c
|
||||
F ext/rtree/rtreedoc3.test 555a878c4d79c4e37fa439a1c3b02ee65d3ebaf75d9e8d96a9c55d66db3efbf8
|
||||
F ext/rtree/rtreefuzz001.test 0fc793f67897c250c5fde96cefee455a5e2fb92f4feeabde5b85ea02040790ee
|
||||
F ext/rtree/sqlite3rtree.h 03c8db3261e435fbddcfc961471795cbf12b24e03001d0015b2636b0f3881373
|
||||
F ext/rtree/test_rtreedoc.c 0167f9243a56d08e79230f604f3979d6b7cde4816355acf7a7d436d9d788bf38
|
||||
F ext/rtree/test_rtreedoc.c 5ad4029d6804eb9efafcac1598a9e0582f6119e48f818854f5b4db1788ca8bd4
|
||||
F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
|
||||
F ext/rtree/util/randomshape.tcl 54ee03d0d4a1c621806f7f44d5b78d2db8fac26e0e8687c36c4bd0203b27dbff
|
||||
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
|
||||
F ext/rtree/visual01.txt e9c2564083bcd30ec51b07f881bffbf0e12b50a3f6fced0c222c5c1d2f94ac66
|
||||
F ext/session/changebatch1.test 9ceaa8f7b2a505933e250fbe6cbc550e4ce1e59d
|
||||
F ext/session/changebatchfault.test be49c793219bf387ad692a60856b921f0854ad6d
|
||||
F ext/session/changeset.c 7a1e6a14c7e92d36ca177e92e88b5281acd709f3b726298dc34ec0fb58869cb5
|
||||
F ext/session/changesetfuzz.c 227076ab0ae4447d742c01ee88a564da6478bbf26b65108bf8fac9cd8b0b24aa
|
||||
F ext/session/changesetfuzz1.test 2e1b90d888fbf0eea5e1bd2f1e527a48cc85f8e0ff75df1ec4e320b21f580b3a
|
||||
@@ -448,7 +452,7 @@ F ext/session/sessionD.test 4f91d0ca8afc4c3969c72c9f0b5ea9527e21de29039937d0d973
|
||||
F ext/session/sessionE.test b2010949c9d7415306f64e3c2072ddabc4b8250c98478d3c0c4d064bce83111d
|
||||
F ext/session/sessionF.test d37ed800881e742c208df443537bf29aa49fd56eac520d0f0c6df3e6320f3401
|
||||
F ext/session/sessionG.test 3828b944cd1285f4379340fd36f8b64c464fc84df6ff3ccbc95578fd87140b9c
|
||||
F ext/session/sessionH.test b17afdbd3b8f17e9bab91e235acf167cf35485db2ab2df0ea8893fbb914741a4
|
||||
F ext/session/sessionH.test ecabc041e04e48671a94cd7c02993bc73f75dced943280a026e89cdf64374eef
|
||||
F ext/session/session_common.tcl f613174665456b2d916ae8df3e5735092a1c1712f36f46840172e9a01e8cc53e
|
||||
F ext/session/session_speed_test.c dcf0ef58d76b70c8fbd9eab3be77cf9deb8bc1638fed8be518b62d6cbdef88b3
|
||||
F ext/session/sessionat.test 52993535f1230a42f70886643574ba7ae60ef854f8add9c8e3fcc3eb5c564bd2
|
||||
@@ -463,16 +467,18 @@ F ext/session/sessionrebase.test ccfa716b23bd1d3b03217ee58cfd90c78d4b99f53e6a9a2
|
||||
F ext/session/sessionsize.test 6f644aff31c7f1e4871e9ff3542766e18da68fc7e587b83a347ea9820a002dd8
|
||||
F ext/session/sessionstat1.test 218d351cf9fcd6648f125a26b607b140310160184723c2666091b54450a68fb5
|
||||
F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc
|
||||
F ext/session/sqlite3session.c 1d019c5caf51936ef24c761db63552b06e0e0d951c8740bba9639b17fa0cb107
|
||||
F ext/session/sqlite3session.h 0907de79bc13a2e3af30a6dc29acc60792a3eaf7d33d44cf52500d0f3c2b2171
|
||||
F ext/session/test_session.c f433f68a8a8c64b0f5bc74dc725078f12483301ad4ae8375205eef790274a787
|
||||
F ext/session/sqlite3changebatch.c d5553b79e012ee2cb06c0a96bdf9dfe19e66354390ea0036cc46c4953142d517
|
||||
F ext/session/sqlite3changebatch.h e72016998c9a22d439ddfd547b69e1ebac810c24
|
||||
F ext/session/sqlite3session.c 421854336ece362552508426d89091c1832148066f0641e8d72e22f58d9241f3
|
||||
F ext/session/sqlite3session.h c26e54521584ad26dcb459171d94bbe6412ca26ed4613551c7f0c80aad129abd
|
||||
F ext/session/test_session.c 226461035031632255c8098f3bcfa4ea27f0b051efd88857a2a36bcea5c294b9
|
||||
F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
|
||||
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
|
||||
F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
|
||||
F main.mk 002e77acdfeb08d1d8f4d360b01e130aa243fb5701728e81fac9085794f27155
|
||||
F main.mk 81e3b80b9efe1227bcdb64e3169b703fbbc0b36b83e4239df777eba87bc2622a
|
||||
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
|
||||
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
|
||||
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
|
||||
@@ -489,12 +495,12 @@ F src/analyze.c 7518b99e07c5494111fe3bd867f28f804b6c5c1ad0703ec3d116de9bab3fa516
|
||||
F src/attach.c e3f9d9a2a4a844750f3f348f37afb244535f21382cbfcd840152cb21cb41cfaf
|
||||
F src/auth.c f4fa91b6a90bbc8e0d0f738aa284551739c9543a367071f55574681e0f24f8cf
|
||||
F src/backup.c 3014889fa06e20e6adfa0d07b60097eec1f6e5b06671625f476a714d2356513d
|
||||
F src/bitvec.c 7c849aac407230278445cb069bebc5f89bf2ddd87c5ed9459b070a9175707b3d
|
||||
F src/bitvec.c 3907fcbe8a0c8c2db58d97087d15cdabbf2842adb9125df9ab9ff87d3db16775
|
||||
F src/btmutex.c 8acc2f464ee76324bf13310df5692a262b801808984c1b79defb2503bbafadb6
|
||||
F src/btree.c 13b965a0f3cd57221e3b4e61e24452ec264a5b163de347b03b5039ddcd95cd54
|
||||
F src/btree.h 74d64b8f28cfa4a894d14d4ed64fa432cd697b98b61708d4351482ae15913e22
|
||||
F src/btreeInt.h ee9348c4cb9077243b049edc93a82c1f32ca48baeabf2140d41362b9f9139ff7
|
||||
F src/build.c c46bd4f5a69f398410c4472f7c1c4291fb8078d2c9758a2dad5916edd1d30ecc
|
||||
F src/btree.c 163c266508279ccec096e3492edef9702682d0babd67e8e74dea8d00e58fa417
|
||||
F src/btree.h 900067641b64d619e6e2a93bd115c952a52f41d3bee32e551e2a4ceee05fc431
|
||||
F src/btreeInt.h 3f19f0be5af0b68cff55e58df4b11e7a0692d9e8a820ceaeba4084659a86cf28
|
||||
F src/build.c fba6792e5d9852620e6a6df4229dc3ffbe9c6a2340e858c70605385d1d083232
|
||||
F src/callback.c 106b585da1edd57d75fa579d823a5218e0bf37f191dbf7417eeb4a8a9a267dbc
|
||||
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
|
||||
F src/ctime.c 8159d5f706551861c18ec6c8f6bdf105e15ea00367f05d9ab65d31a1077facc1
|
||||
@@ -502,19 +508,19 @@ F src/date.c fa928630fecf1d436cdc7a7a5c950c781709023ca782c21b7a43cc7361a9451e
|
||||
F src/dbpage.c 8a01e865bf8bc6d7b1844b4314443a6436c07c3efe1d488ed89e81719047833a
|
||||
F src/dbstat.c 861e08690fcb0f2ee1165eff0060ea8d4f3e2ea10f80dab7d32ad70443a6ff2d
|
||||
F src/delete.c 0c151975fa99560767d7747f9b60543d0093d9f8b89f13d2d6058e9c83ad19e7
|
||||
F src/expr.c 4b6dfb224b6234ff4f529023993b503048e1b045ff49cbb911e7d28a28cca795
|
||||
F src/expr.c 827179c78d2ca7cc318392811de8151c60eacf7ce804b13e61bb7ef38f954846
|
||||
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
|
||||
F src/fkey.c 187b67af20c5795953a592832c5d985e4313fe503ebd8f95e3e9e9ad5a730bb5
|
||||
F src/func.c 1cfb09d7ffca81238eccefdb0293e1f5b7cfebbd1816dfad5ec6024742a7496b
|
||||
F src/fkey.c 5b73f7a7c00f06017531a5bd258cbc2c7a294e55a7f84a729fe27aa525242560
|
||||
F src/func.c a5dce8a8ff85310e2c88262a25bcded0a7e92be9a4bda24bbf6eab78e99c2f13
|
||||
F src/global.c 1f56aead86e8a18c4415638f5e6c4d0a0550427f4b3f5d065ba5164cc09c22e8
|
||||
F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19
|
||||
F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51
|
||||
F src/hwtime.h cb1d7e3e1ed94b7aa6fde95ae2c2daccc3df826be26fc9ed7fd90d1750ae6144
|
||||
F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
|
||||
F src/insert.c 7fcbbe9114ac402ea3c0c6a3810f13fc89cae8131ea1659ec472be7caac10192
|
||||
F src/insert.c 04c9b133be6152b4f229aab8e38230b3bf6d2e7e76455a893bff5f70e566237b
|
||||
F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
|
||||
F src/loadext.c e1dcff1c916bf6834e150b492eddda5d9792453182d2ad64294d2266b6e93c4c
|
||||
F src/main.c 7bd4fdc41ef53535271a1816ff043ba153cda03842b444b6e2f57b27b2cb9090
|
||||
F src/main.c cd5a17d7b575917fc6ec90205aa4b516839573b73e574df6ca1e6e944f8b89f5
|
||||
F src/malloc.c ef796bcc0e81d845d59a469f1cf235056caf9024172fd524e32136e65593647b
|
||||
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
||||
F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de
|
||||
@@ -522,7 +528,7 @@ F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75
|
||||
F src/mem3.c 30301196cace2a085cbedee1326a49f4b26deff0af68774ca82c1f7c06fda4f6
|
||||
F src/mem5.c 9bf955937b07f8c32541c8a9991f33ce3173d944
|
||||
F src/memdb.c c2dc88f97c410eb68a24468344b65526685e18354ddfd15906750c1eaf9dc2dd
|
||||
F src/memjournal.c a85f0dc5c02a42453d0bc3819ecfb5666cb6433e5deefcd93ccbe05c9f088b83
|
||||
F src/memjournal.c ff4336a98b05ede2adee7595f22d6f7d1cdc6bf0f0a5c3d77b0acdf017b2e8b2
|
||||
F src/msvc.h 3a15918220367a8876be3fa4f2abe423a861491e84b864fb2b7426bf022a28f8
|
||||
F src/mutex.c 5e3409715552348732e97b9194abe92fdfcd934cfb681df4ba0ab87ac6c18d25
|
||||
F src/mutex.h a7b2293c48db5f27007c3bdb21d438873637d12658f5a0bf8ad025bb96803c4a
|
||||
@@ -534,33 +540,33 @@ F src/os.c b1c4f2d485961e9a5b6b648c36687d25047c252222e9660b7cc25a6e1ea436ab
|
||||
F src/os.h 26890f540b475598cd9881dcc68931377b8d429d3ea3e2eeb64470cde64199f8
|
||||
F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85
|
||||
F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586
|
||||
F src/os_unix.c f5ad51cfd024116db8531feab9efd831c2621436dca1464e4ff1e8af9bf3252e
|
||||
F src/os_unix.c d6815223d2a91a63b34d240a98f51341e74f8aabd3f8391fb3131c0ab1bd4b1e
|
||||
F src/os_win.c 77d39873836f1831a9b0b91894fec45ab0e9ca8e067dc8c549e1d1eca1566fe9
|
||||
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
|
||||
F src/pager.c bc10c191d18bffd3d76eda5f162799e43a9f875ecfe7c4869f752e2ddef87ea2
|
||||
F src/pager.h 4bf9b3213a4b2bebbced5eaa8b219cf25d4a82f385d093cd64b7e93e5285f66f
|
||||
F src/parse.y 0bd7971a7489bbf6c3726f1b50da6e508bdff8fa493e9cc3f5a96b12cbb2361e
|
||||
F src/pager.c b862b8f488e994a7205838172d8ae3a4516c56bcc0216336275056e874aa7804
|
||||
F src/pager.h fdc30693c403aa9b4293d0b126346db7e450f8f0e5d2c1bb8355acb1cb8da6fd
|
||||
F src/parse.y 59631359574901cc5cd4780939a6740f6bc597bd473334e744c1a1c32d9adef3
|
||||
F src/pcache.c 084e638432c610f95aea72b8509f0845d2791293f39d1b82f0c0a7e089c3bb6b
|
||||
F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586
|
||||
F src/pcache1.c 54881292a9a5db202b2c0ac541c5e3ef9a5e8c4f1c1383adb2601d5499a60e65
|
||||
F src/pragma.c c536665ce8431c8b1efbf7e0a5c01852f49f7bf28f1954f8118b2d28e4a3797f
|
||||
F src/pragma.h 87330ed2fbfa2a1274de93ca0ab850fba336189228cb256089202c3b52766fad
|
||||
F src/prepare.c 7520a371f1de8a53e3023eba75bc0d3473196833c6363d285cad8d002eabef0b
|
||||
F src/pragma.h f98354c48571c490927029510566839bf9e7242569bfbb48032dafeb008481d2
|
||||
F src/prepare.c dab0c2995a33ee2c458354cb7dd13b2b33362425c52301e41d833add7660e7ca
|
||||
F src/printf.c 5901672228f305f7d493cbc4e7d76a61a5caecdbc1cd06b1f9ec42ea4265cf8d
|
||||
F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
|
||||
F src/random.c c6e61d041f230d46c658e6dfe7165fc1ecb0093d5fe28cfe74f389d261dc3af8
|
||||
F src/resolve.c 4a1db4aadd802683db40ca2dbbb268187bd195f10cbdb7206dbd8ac988795571
|
||||
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
|
||||
F src/select.c a7a3d9f54eb24821ec5f67f2e5589b68a5d42d46fc5849d7376886777d93a85a
|
||||
F src/shell.c.in 975f268ef261773fcbed1e519dfa10c4f33e8b1cffc12120563e61857fff07c6
|
||||
F src/sqlite.h.in 5cd209ac7dc4180f0e19292846f40440b8488015849ca0110c70b906b57d68f0
|
||||
F src/select.c 7e8082835f8b8891a0785dc163724a13b5c200abd0931fd3204b2578339c49bc
|
||||
F src/shell.c.in 1458b700144c8326fda2514aaddeda49d6f01f1d1ccf7b9b696c53a3535a119c
|
||||
F src/sqlite.h.in 32f7d112cb85d3f2fd56500e11a6bd6d19a969306650200dcde97c2638cfecfc
|
||||
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
|
||||
F src/sqlite3ext.h 8ff2fd2c166150b2e48639f5e506fb44e29f1a3f65031710b9e89d1c126ac839
|
||||
F src/sqliteInt.h 193e716a67c877a6054a8c261c932bdc64f8c8be9b66388c74f21d94a259b7ce
|
||||
F src/sqliteInt.h 4d301955804f800c3e2809fad8103458a42a1df92a1abf3bc6fc445d2717720d
|
||||
F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
|
||||
F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1
|
||||
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
|
||||
F src/tclsqlite.c 48f291e1a7e672a7204884d4c164a8ed3a522ff087c361ada2991f5d54e987f6
|
||||
F src/test1.c d6c945a8bb211ed72ea515f0b9743caeaf38c66da6418a7b1dcf3764e1368bbb
|
||||
F src/test1.c 2785b6fd05e4810f7215bf8de01c15670f39a19e49e3d927045f5e72ab08b4cd
|
||||
F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
|
||||
F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644
|
||||
F src/test4.c 7c4420e01c577b5c4add2cb03119743b1a357543d347773b9e717195ea967159
|
||||
@@ -575,7 +581,7 @@ F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0
|
||||
F src/test_bestindex.c 78809f11026f18a93fcfd798d9479cba37e1201c830260bf1edc674b2fa9b857
|
||||
F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce
|
||||
F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274
|
||||
F src/test_config.c 284c29912736f68b0a583a920bf63fd8f9125dffb8a75cb0676e58502b2f7908
|
||||
F src/test_config.c 8544c71df9131c25c4d0255188ec37a6e9af452f5520ee9df4bf30f35c4c7d29
|
||||
F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f
|
||||
F src/test_demovfs.c 86142ba864d4297d54c5b2e972e74f3141ae4b30f05b3a95824184ed2d3d7f91
|
||||
F src/test_devsym.c aff2255ea290d7718da08af30cdf18e470ff7325a5eff63e0057b1496ed66593
|
||||
@@ -616,17 +622,17 @@ F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
|
||||
F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
|
||||
F src/tokenize.c 865911afa00fed589cd03b25c140ca88544842aaef7b81f7d41ed769a7a54120
|
||||
F src/treeview.c 9dfdb7ff7f6645d0a6458dbdf4ffac041c071c4533a6db8bb6e502b979ac67bc
|
||||
F src/trigger.c 2ef56f0b7b75349a5557d0604b475126329c2e1a02432e7d49c4c710613e8254
|
||||
F src/update.c 69c4c10bc6873a80c0a77cb578f9fc60ee90003d03f9530bc3370fa24615772d
|
||||
F src/trigger.c 043d66ecb25a223c614681c8ab758f1aaf6e507c901d3a4668113afab1cc2dc7
|
||||
F src/update.c 440b9aae32b930fc8c40beff27cdc6060a2fc4c99d911e415f92bb8a4e6eab68
|
||||
F src/upsert.c 8789047a8f0a601ea42fa0256d1ba3190c13746b6ba940fe2d25643a7e991937
|
||||
F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0
|
||||
F src/util.c 30df8356e231dad33be10bb27897655002668343280004ba28c734489414a167
|
||||
F src/vacuum.c 6c38ddc52f0619865c91dae9c441d4d48bf3040d7dc1bc5b22da1e45547ed0b3
|
||||
F src/vdbe.c e98f1baf54a00db2c4669dbd04f8bbc89b5909a5b43e76fbbbf1a97007adba2b
|
||||
F src/vacuum.c 72867c740476d13f1c397015e4d3168b4e96a237a80b9afa67e1bb8500bfeeab
|
||||
F src/vdbe.c 5bf3a2db17f2cfc946e7e014cc66d01ad9ef5fe86087ab67a6dc4b9515800c69
|
||||
F src/vdbe.h 25dabb25c7e157b84e59260cfb5b466c3ac103ede9f36f4db371332c47601abe
|
||||
F src/vdbeInt.h 31fbabdc1ed61d9695337dfe5269ea94e1cf615c17f5cafeaa1bb01066820bab
|
||||
F src/vdbeapi.c 22c79072ae7d8a01e9bcae8ba16e918d60d202eaa9553b5fda38f99f7464d99a
|
||||
F src/vdbeaux.c 21db442d159fd745a7693d157b5f998260b6af4ca60de559fa3b7b68c7405af2
|
||||
F src/vdbeaux.c 0986116cd4d7eeafd3662ab7d6c17190c85e6d26e43d535f104a7fb42dd2764c
|
||||
F src/vdbeblob.c 29c4118f7ee615cdee829e8401f6ead1b96b95d545b4de0042f6de39c962c652
|
||||
F src/vdbemem.c a3d91dc9bb9ef725db77e4e9de7e1acef43192c9f8406c307665d503e3c2837c
|
||||
F src/vdbesort.c 513b481c8bab4a6578c92194a60cf3bc3b48736e4a53f8d2d7918121c5b594e7
|
||||
@@ -634,8 +640,8 @@ F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf8
|
||||
F src/vdbevtab.c f99b275366c5fc5e2d99f734729880994ab9500bdafde7fae3b02d562b9d323c
|
||||
F src/vtab.c 9d5c3f49d3a6959b6eef287bb8fa773563102a80a835c3314c57144412709e78
|
||||
F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
|
||||
F src/wal.c ed0398a7adf02c31e34aada42cc86c58f413a7afe5f741a5d373ad087abde028
|
||||
F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a
|
||||
F src/wal.c 80c323b66657ff8edffb10ab231990bc999fff098c6cff9982a1b5fa1739a93b
|
||||
F src/wal.h 7ffe787437f20a098af347011967a6d3bb8e5c3dc645e6be59eff44d2b2c5297
|
||||
F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b
|
||||
F src/where.c de0d4ff409c7b62a8803f9f267cc2c7fedddbc00de9ab7b5382c507383c18665
|
||||
F src/whereInt.h 83877a75a1bce056ea44aff02f1dfa958ad1d6038c213ddadb8652003b45151d
|
||||
@@ -688,7 +694,7 @@ F test/async2.test c0a9bd20816d7d6a2ceca7b8c03d3d69c28ffb8b
|
||||
F test/async3.test d73a062002376d7edc1fe3edff493edbec1fc2f7
|
||||
F test/async4.test 1787e3952128aa10238bf39945126de7ca23685a
|
||||
F test/async5.test 383ab533fdb9f7ad228cc99ee66e1acb34cc0dc0
|
||||
F test/atof1.test 10049623e77006691c4c2978c1dc8a3f75276377a53417811aa85bda7493f963
|
||||
F test/atof1.test 77e8517df10cb39f2e4e65dbefe1e81c8d8e65f29f68b09265ca6e534c68227e
|
||||
F test/atomic.test 065a453dde33c77ff586d91ccaa6ed419829d492dbb1a5694b8a09f3f9d7d061
|
||||
F test/atomic2.test b6863b4aa552543874f80b42fb3063f1c8c2e3d8e56b6562f00a3cc347b5c1da
|
||||
F test/atrc.c c388fac43dbba05c804432a7135ae688b32e8f25818e9994ffba4b64cf60c27c
|
||||
@@ -722,6 +728,7 @@ F test/backup_malloc.test 0c9abdf74c51e7bedb66d504cd684f28d4bd4027
|
||||
F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f
|
||||
F test/badutf2.test f310fd3b24a491b6b77bccdf14923b85d6ebcce751068c180d93a6b8ff854399
|
||||
F test/bc_common.tcl b5e42d80305be95697e6370e015af571e5333a1c
|
||||
F test/bc_test1.c e0a092579552e066ed4ce7bcdaecfa69c4aacc8d
|
||||
F test/bestindex1.test 7cc626f1f4a7483bb6b38487d467db4477083be5cd93958aeda5d5127640dc81
|
||||
F test/bestindex2.test 60266e2854055788459cbfd86cef575601eabe74a2c61faba72601739fea4398
|
||||
F test/bestindex3.test e061a6ed0f519beee037ba7e7a4c37f80c8a7e4a303e2559ed1f760e4b0235eb
|
||||
@@ -785,6 +792,15 @@ F test/collateB.test 1e68906951b846570f29f20102ed91d29e634854ee47454d725f2151eca
|
||||
F test/colmeta.test 2c765ea61ee37bc43bbe6d6047f89004e6508eb1
|
||||
F test/colname.test 87ad5458bb8709312dac0d6755fd30e8e4ca83298d0a9ef6e5c24277a3c3390e
|
||||
F test/columncount.test 6fe99c2f35738b0129357a1cf3fa483f76140f4cd8a89014c88c33c876d2638f
|
||||
F test/concfault.test af01b1f6cca45b8f17bb13b4406ec43ab1689181a08850b990596c7a9f4e656f
|
||||
F test/concurrent.test a0248ec6e3e79a5948453649cf86b5b359175cba55ea636b15426d6f0fa6c3da
|
||||
F test/concurrent2.test de748c7dd749c77e2af2c4b914b9b09a28ac09608042ca498c0251dc6f46aa1a
|
||||
F test/concurrent3.test 530671ac706f6a1d0f4992dbdd33a86408330d03cd90fb9e82ecb1b27f5fd081
|
||||
F test/concurrent4.test e0b12cd467137e50259df3b4f837507e82aaa07c35941c88664dc8ed1d089c44
|
||||
F test/concurrent5.test 0c16cbf7446af162a14e6def30445e94016064eb994e5aa4ebb2bebc59554176
|
||||
F test/concurrent6.test a7860e9ca13bb5fb76bcf41c5524fbfa9c37e6e258ecf84ffb5748a272488c67
|
||||
F test/concurrent7.test b96fa5c4cfdf8d5c0bc66b6934214500bad0260884a736f054ccc76e81aae85d
|
||||
F test/concurrent8.test b93937e74a8efb8b84f2fea7595b53418c5f29777bbe9cbdb5dc219b3dd72a7d
|
||||
F test/conflict.test ac0667090f66130ac77d5fb764655558ca6600dd6d88f670ca9123b61c448337
|
||||
F test/conflict2.test 5557909ce683b1073982f5d1b61dfb1d41e369533bfdaf003180c5bc87282dd1
|
||||
F test/conflict3.test 81865d9599609aca394fb3b9cd5f561d4729ea5b176bece3644f6ecb540f88ac
|
||||
@@ -811,7 +827,7 @@ F test/corruptJ.test 4d5ccc4bf959464229a836d60142831ef76a5aa4
|
||||
F test/corruptK.test 5b4212fe346699831c5ad559a62c54e11c0611bdde1ea8423a091f9c01aa32af
|
||||
F test/corruptL.test 7d3440831ca24ba64305583c4d4506d417d3f89f5775c0b7cc8102db078f8ff5
|
||||
F test/corruptM.test 7d574320e08c1b36caa3e47262061f186367d593a7e305d35f15289cc2c3e067
|
||||
F test/corruptN.test 60b5a62944b4f0029ba07edaa5fd8e670539d6b0a8d99db26c068d435675cbfe
|
||||
F test/corruptN.test f0b26fda7556581af9667904ce2fa1e5749cd018b56c8fb77bda143d3ab6ccbf
|
||||
F test/cost.test b11cdbf9f11ffe8ef99c9881bf390e61fe92baf2182bad1dbe6de59a7295c576
|
||||
F test/count.test 013d64569c15563c59472d17ec4ddc681034aaee65e8b01880bc88315fd4c673
|
||||
F test/countofview.test e17d6e6688cf74f22783c9ec6e788c0790ee4fbbaee713affd00b1ac0bb39b86
|
||||
@@ -907,7 +923,7 @@ F test/filter1.test 6c483ecf7886c8843a8612c021aa23f33c581f584151f251842b3a3592c9
|
||||
F test/filter2.tcl 44e525497ce07382915f01bd29ffd0fa49dab3adb87253b5e5103ba8f93393e8
|
||||
F test/filter2.test 485cf95d1f6d6ceee5632201ca52a71868599836f430cdee42e5f7f14666e30a
|
||||
F test/filterfault.test c08fb491d698e8df6c122c98f7db1c65ffcfcad2c1ab0e07fa8a5be1b34eaa8b
|
||||
F test/fkey1.test 03503639d266d565db90ee3b8fe211ba446624030ac4eb24895cec265e9631d0
|
||||
F test/fkey1.test 55663090ab6735319a52647057b9f19f8ec8c6c7d7da25170b71a75e3e5bdeb7
|
||||
F test/fkey2.test 1063d65e5923c054cfb8f0555a92a3ae0fa8c067275a33ee1715bd856cdb304c
|
||||
F test/fkey3.test 76d475c80b84ee7a5d062e56ccb6ea68882e2b49
|
||||
F test/fkey4.test 86446017011273aad8f9a99c1a65019e7bd9ca9d
|
||||
@@ -1202,6 +1218,7 @@ F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f
|
||||
F test/memdb.test c1f2a343ad14398d5d6debda6ea33e80d0dafcc7
|
||||
F test/memdb1.test 2c4e9cc10d21c6bf4e217d72b7f6b8ba9b2605971bb2c5e6df76018e189f98f5
|
||||
F test/memjournal.test 70f3a00c7f84ee2978ad14e831231caa1e7f23915a2c54b4f775a021d5740c6c
|
||||
F test/memjournal2.test 89a4e0d1084170a281efa4d54c2677599f986f44227f98f7dfae282802737b65
|
||||
F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2
|
||||
F test/memsubsys1.test 9e7555a22173b8f1c96c281ce289b338fcba2abe8b157f8798ca195bbf1d347e
|
||||
F test/memsubsys2.test 3e4a8d0c05fd3e5fa92017c64666730a520c7e08
|
||||
@@ -1234,6 +1251,7 @@ F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660
|
||||
F test/nan.test 437d40e6d0778b050d7750726c0cbd2c9936b81962926e8f8c48ca698f00f4d1
|
||||
F test/nockpt.test 8c43b25af63b0bd620cf1b003529e37b6f1dc53bd22690e96a1bd73f78dde53a
|
||||
F test/nolock.test f196cf8b8fbea4e2ca345140a2b3f3b0da45c76e
|
||||
F test/noop_update.test 74d71bbe5d37f7861cc2402ed620eb165b8e6b00159cdcada7df716420336651
|
||||
F test/normalize.test f23b6c5926c59548635fcf39678ac613e726121e073dd902a3062fbb83903b72
|
||||
F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf
|
||||
F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161
|
||||
@@ -1273,7 +1291,6 @@ F test/pagerfault2.test caf4c7facb914fd3b03a17b31ae2b180c8d6ca1f
|
||||
F test/pagerfault3.test 1003fcda009bf48a8e22a516e193b6ef0dd1bbd8
|
||||
F test/pageropt.test 84e4cc5cbca285357f7906e99b21be4f2bf5abc0
|
||||
F test/pagesize.test 5769fc62d8c890a83a503f67d47508dfdc543305
|
||||
F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035ce4b3
|
||||
F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
|
||||
F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
|
||||
F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
|
||||
@@ -1309,6 +1326,7 @@ F test/releasetest_data.tcl 7cea6c852ae6bb3a9ff1a2b910e4dd13c16a05f74443984dfd52
|
||||
F test/resetdb.test 8062cf10a09d8c048f8de7711e94571c38b38168db0e5877ba7561789e5eeb2b
|
||||
F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb
|
||||
F test/returning1.test f96c7245f6ac16038e802760cd90b93479369939a8a7a44e2329ee5aed28239c
|
||||
F test/returningfault.test ae4c4b5e8745813287a359d9ccdb9d5c883c2e68afb18fb0767937d5de5692a4
|
||||
F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa
|
||||
F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f
|
||||
F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a
|
||||
@@ -1650,6 +1668,7 @@ F test/triggerG.test 2b816093c91ba73c733cfa8aedcc210ad819d72a98b1da30768a3c56505
|
||||
F test/triggerupfrom.test d1f9e56090408115c522bee626cc33a2f3370f627a5e341d832589d72e3aa271
|
||||
F test/trustschema1.test 4e970aef0bfe0cee139703cc7209d0e0f07725d999b180ba50770f49edef1494
|
||||
F test/tt3_checkpoint.c ac7ca661d739280c89d9c253897df64a59a49369bd1247207ac0f655b622579d
|
||||
F test/tt3_core.c 8cd89ead95410f70e7fb02c79f1e040f9c5ad5cf
|
||||
F test/tt3_index.c 39eec10a35f57672225be4d182862152896dee4a
|
||||
F test/tt3_lookaside1.c 0377e202c3c2a50d688cb65ba203afeda6fafeb9
|
||||
F test/tt3_shared.c b37d22defc944a2ac4c91c927fd06c1d48cd51e2ce9d004fe868625bd2399f93
|
||||
@@ -1674,7 +1693,7 @@ F test/upfrom1.test 8cb06689e99cd707d884faa16da0e8eb26ff658bb01c47ddf72fadade666
|
||||
F test/upfrom2.test 88d39cb755db5789541e645d4e2764abc697a56958f28a3f8451a0e9342bbd6b
|
||||
F test/upfrom3.test 6130f24ebf97f5ea865e5d2a14a2d543fe5428a62e87cc60f62d875e45c1f5f0
|
||||
F test/upfromfault.test 3a10075a0043f0c4fad6614b2c371f88a8ba5a4acab68b907438413865d6a8d6
|
||||
F test/upsert1.test 88f9e258c6a0eeeb85937b08831e8daad440ba41f125af48439e9d33f266fb18
|
||||
F test/upsert1.test b0ae2f58680c5205b4bc1cdeed3c3d444057c506f6c44494fa3eac60731d68a2
|
||||
F test/upsert2.test 9c3cdbb1a890227f6504ce4b0e3de68f4cdfa16bb21d8641208a9239896c5a09
|
||||
F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3179c
|
||||
F test/upsert4.test 25d2a1da92f149331ae0c51ca6e3eee78189577585eab92de149900d62994fa5
|
||||
@@ -1720,7 +1739,7 @@ F test/vtab_err.test dcc8b7b9cb67522b3fe7a272c73856829dae4ab7fdb30399aea1b6981bd
|
||||
F test/vtab_shared.test 5253bff2355a9a3f014c15337da7e177ab0ef8ad
|
||||
F test/vtabdrop.test 65d4cf6722972e5499bdaf0c0d70ee3b8133944a4e4bc31862563f32a7edca12
|
||||
F test/wal.test b7cc6984709f54afbf8441747ced1f646af120bf0c1b1d847bfa39306fbea089
|
||||
F test/wal2.test 31f6e2c404b9f2cdf9ca19b105a1742fdc19653c2c936da39e3658c617524046
|
||||
F test/wal2.test 88d34cc62bf5460ad6b1f837cd56ab55731bb0637ff99e113b790873d9b60b14
|
||||
F test/wal3.test 2a93004bc0fb2b5c29888964024695bade278ab2
|
||||
F test/wal4.test 4744e155cd6299c6bd99d3eab1c82f77db9cdb3c
|
||||
F test/wal5.test 9c11da7aeccd83a46d79a556ad11a18d3cb15aa9
|
||||
@@ -1729,7 +1748,7 @@ F test/wal64k.test 2a525c0f45d709bae3765c71045ccec5df7d100ccbd3a7860fdba46c9addb
|
||||
F test/wal7.test 2ae8f427d240099cc4b2dfef63cff44e2a68a1bd
|
||||
F test/wal8.test d9df3fba4caad5854ed69ed673c68482514203c8
|
||||
F test/wal9.test 378e76a9ad09cd9bee06c172ad3547b0129a6750
|
||||
F test/wal_common.tcl a98f17fba96206122eff624db0ab13ec377be4fe
|
||||
F test/wal_common.tcl 37902864b63794e9bf59393b8887e21a9cbd4db8
|
||||
F test/walbak.test 018d4e5a3d45c6298d11b99f09a8ef6876527946
|
||||
F test/walbig.test f437473a16cfb314867c6b5d1dbcd519e73e3434
|
||||
F test/walblock.test be48f3a75eff0b4456209f26b3ce186c2015497d
|
||||
@@ -1803,7 +1822,7 @@ F test/window8.test 4ab16817414af0c904abe2ebdf88eb6c2b00058b84f9748c6174ff11fc45
|
||||
F test/window9.test 349c71eab4288a1ffc19e2f65872ec2c37e6cf8a1dda2ad300364b7450ae4836
|
||||
F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af23be
|
||||
F test/windowB.test b67bda5645f3226790e1a360c4225241840b84adb5aa2e69bfb0b27eef3b84d9
|
||||
F test/windowC.test 8799158a2a3ea365980371400f08fd4dff70eadffa5a1e45d42430246da70706
|
||||
F test/windowC.test 6fd75f5bb2f1343d34e470e36e68f0ff638d8a42f6aa7d99471261b31a0d42f2
|
||||
F test/windowerr.tcl f5acd6fbc210d7b5546c0e879d157888455cd4a17a1d3f28f07c1c8a387019e0
|
||||
F test/windowerr.test a8b752402109c15aa1c5efe1b93ccb0ce1ef84fa964ae1cd6684dd0b3cc1819b
|
||||
F test/windowfault.test 15094c1529424e62f798bc679e3fe9dfab6e8ba2f7dfe8c923b6248c31660a7c
|
||||
@@ -1815,7 +1834,7 @@ F test/with4.test 257be66c0c67fee1defbbac0f685c3465e2cad037f21ce65f23f86084f1982
|
||||
F test/with5.test 6248213c41fab36290b5b73aa3f937309dfba337004d9d8434c3fabc8c7d4be8
|
||||
F test/with6.test 661d7e416bef6c0a2556b2c9f0c8178a5b15932bed65246abed99723a8d4e7c0
|
||||
F test/withM.test 693b61765f2b387b5e3e24a4536e2e82de15ff64
|
||||
F test/without_rowid1.test df3de14f1cc422d2b0f9b79969b5ef8e51c86ed87834ab35fb5139403e7f5a03
|
||||
F test/without_rowid1.test 78fd9b437f4cdb46f76e6a510d545334e4f58e3e4ce37aaf19384eda5b27de8c
|
||||
F test/without_rowid2.test af260339f79d13cb220288b67cd287fbcf81ad99
|
||||
F test/without_rowid3.test 39ab0dd773eaa62e59b17093f875327630f54c4145458f6d2b053d68d4b2f67b
|
||||
F test/without_rowid4.test 4e08bcbaee0399f35d58b5581881e7a6243d458a
|
||||
@@ -1855,7 +1874,7 @@ F tool/lemon.c 258881835bd5bccd0c74fb110fe54244ff18e8e7ef3d949cbdab7187f02132bb
|
||||
F tool/lempar.c 57478ea48420da05faa873c6d1616321caa5464644588c97fbe8e0ea04450748
|
||||
F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9
|
||||
F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862
|
||||
F tool/logest.c 11346aa019e2e77a00902aa7d0cabd27bd2e8cca
|
||||
F tool/logest.c 06446d42942a7128ab4963eb438eb3f528e07095d5d1080f9105a6f337238d33
|
||||
F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439
|
||||
F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176
|
||||
F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8
|
||||
@@ -1866,7 +1885,7 @@ F tool/mkmsvcmin.tcl 6ecab9fe22c2c8de4d82d4c46797bda3d2deac8e763885f5a38d0c44a89
|
||||
F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef
|
||||
F tool/mkopcodeh.tcl 130b88697da6ec5b89b41844d955d08fb62c2552e889dec8c7bcecb28d8f50bd
|
||||
F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
|
||||
F tool/mkpragmatab.tcl de206c64b6e9ac8cd5e3cbd0ffe456f07d5710605ef8385d677e60ce3335ea12
|
||||
F tool/mkpragmatab.tcl 6c5c676b10f9ccac33b38b2b27d3b0578561f5c39137410e1e616ac3b79a4316
|
||||
F tool/mkshellc.tcl df5d249617f9cc94d5c48eb0401673eb3f31f383ecbc54e8a13ca3dd97e89450
|
||||
F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9
|
||||
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
|
||||
@@ -1883,7 +1902,7 @@ F tool/replace.tcl 937c931ad560688e85bdd6258bdc754371bb1e2732e1fb28ef441e44c9228
|
||||
F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a
|
||||
F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5
|
||||
F tool/run-speed-test.sh f95d19fd669b68c4c38b6b475242841d47c66076
|
||||
F tool/showdb.c 7cc12c6deeddfe40ba5d948b408730696d8365988da05fcb6b6a90ea4965e2b4
|
||||
F tool/showdb.c 72239e95e1d05a2941c6a1d86b4d857812be4dadd71f24e381db572f350fc172
|
||||
F tool/showjournal.c 5bad7ae8784a43d2b270d953060423b8bd480818
|
||||
F tool/showlocks.c 9cc5e66d4ebbf2d194f39db2527ece92077e86ae627ddd233ee48e16e8142564
|
||||
F tool/showshm.c a0ab6ec32dd1f11218ca2a4018f8fb875b59414801ab8ceed8b2e69b7b45a809
|
||||
@@ -1933,10 +1952,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P 7c567338ae4dd31097e9ae5c693b84369c35c5a52534797e8186b4be7297ba78
|
||||
R 1bdaa083fa0abfc53c61429627c688f2
|
||||
T +sym-major-release *
|
||||
T +sym-release *
|
||||
T +sym-version-3.37.0 *
|
||||
P 910001f28368896c3e6f11bb6ced3ec3171dcafefd9e6702106b32f3c451e229 872ba256cbf61d9290b571c0e6d82a20c224ca3ad82971edc46b29818d5d17a0
|
||||
R dbfa2c5ab48ce7f85490f2e1eb4b1b9a
|
||||
T *branch * begin-concurrent-3.37
|
||||
T *sym-begin-concurrent-3.37 *
|
||||
T -sym-begin-concurrent-pnu *
|
||||
U drh
|
||||
Z 6f6dbee10adfe1c5275055f73bc6984f
|
||||
Z bd2e7b7884e9a174ca68f763f142b3b9
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
bd41822c7424d393a30e92ff6cb254d25c26769889c1499a18a0b9339f5d6c8a
|
||||
e525892d3dcdd9914000b169ddab2cf651d7f9bcd5ff4d8a74f591e017149985
|
||||
@@ -171,6 +171,12 @@ int sqlite3BitvecSet(Bitvec *p, u32 i){
|
||||
if( p==0 ) return SQLITE_OK;
|
||||
assert( i>0 );
|
||||
assert( i<=p->iSize );
|
||||
if( i>p->iSize || i==0 ){
|
||||
sqlite3_log(SQLITE_ERROR,
|
||||
"Bitvec: setting bit %d of bitvec size %d\n", (int)i, (int)p->iSize
|
||||
);
|
||||
abort();
|
||||
}
|
||||
i--;
|
||||
while((p->iSize > BITVEC_NBIT) && p->iDivisor) {
|
||||
u32 bin = i/p->iDivisor;
|
||||
|
||||
+629
-52
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -350,6 +350,8 @@ sqlite3_uint64 sqlite3BtreeSeekCount(Btree*);
|
||||
# define sqlite3BtreeSeekCount(X) 0
|
||||
#endif
|
||||
|
||||
int sqlite3BtreeExclusiveLock(Btree *pBt);
|
||||
|
||||
#ifndef NDEBUG
|
||||
int sqlite3BtreeCursorIsValid(BtCursor*);
|
||||
#endif
|
||||
@@ -408,5 +410,4 @@ int sqlite3BtreeTransferRow(BtCursor*, BtCursor*, i64);
|
||||
# define sqlite3SchemaMutexHeld(X,Y,Z) 1
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* SQLITE_BTREE_H */
|
||||
|
||||
+17
-3
@@ -232,6 +232,7 @@
|
||||
typedef struct MemPage MemPage;
|
||||
typedef struct BtLock BtLock;
|
||||
typedef struct CellInfo CellInfo;
|
||||
typedef struct BtreePtrmap BtreePtrmap;
|
||||
|
||||
/*
|
||||
** This is a magic string that appears at the beginning of every
|
||||
@@ -275,6 +276,9 @@ struct MemPage {
|
||||
u8 intKey; /* True if table b-trees. False for index b-trees */
|
||||
u8 intKeyLeaf; /* True if the leaf of an intKey table */
|
||||
Pgno pgno; /* Page number for this page */
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
Pgno pgnoRoot; /* Root page of b-tree that this page belongs to */
|
||||
#endif
|
||||
/* Only the first 8 bytes (above) are zeroed by pager.c when a new page
|
||||
** is allocated. All fields that follow must be initialized before use */
|
||||
u8 leaf; /* True if a leaf page */
|
||||
@@ -454,6 +458,9 @@ struct BtShared {
|
||||
Btree *pWriter; /* Btree with currently open write transaction */
|
||||
#endif
|
||||
u8 *pTmpSpace; /* Temp space sufficient to hold a single cell */
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
BtreePtrmap *pMap;
|
||||
#endif
|
||||
int nPreformatSize; /* Size of last cell written by TransferRow() */
|
||||
};
|
||||
|
||||
@@ -671,12 +678,19 @@ struct BtCursor {
|
||||
** (sqliteMallocRaw), it is not possible to use conditional compilation.
|
||||
** So, this macro is defined instead.
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
#define ISAUTOVACUUM (pBt->autoVacuum)
|
||||
#else
|
||||
#ifdef SQLITE_OMIT_AUTOVACUUM
|
||||
#define ISAUTOVACUUM 0
|
||||
#else
|
||||
#define ISAUTOVACUUM (pBt->autoVacuum)
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_OMIT_CONCURRENT
|
||||
# define ISCONCURRENT 0
|
||||
#else
|
||||
# define ISCONCURRENT (pBt->pMap!=0)
|
||||
#endif
|
||||
|
||||
#define REQUIRE_PTRMAP (ISAUTOVACUUM || ISCONCURRENT)
|
||||
|
||||
/*
|
||||
** This structure is passed around through all the sanity checking routines
|
||||
|
||||
+4
-2
@@ -89,8 +89,10 @@ void sqlite3TableLock(
|
||||
u8 isWriteLock, /* True for a write lock */
|
||||
const char *zName /* Name of the table to be locked */
|
||||
){
|
||||
#ifdef SQLITE_OMIT_CONCURRENT
|
||||
if( iDb==1 ) return;
|
||||
if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return;
|
||||
#endif
|
||||
lockTable(pParse, iDb, iTab, isWriteLock, zName);
|
||||
}
|
||||
|
||||
@@ -5070,7 +5072,7 @@ void sqlite3BeginTransaction(Parse *pParse, int type){
|
||||
}
|
||||
v = sqlite3GetVdbe(pParse);
|
||||
if( !v ) return;
|
||||
if( type!=TK_DEFERRED ){
|
||||
if( type==TK_IMMEDIATE || type==TK_EXCLUSIVE ){
|
||||
for(i=0; i<db->nDb; i++){
|
||||
int eTxnType;
|
||||
Btree *pBt = db->aDb[i].pBt;
|
||||
@@ -5085,7 +5087,7 @@ void sqlite3BeginTransaction(Parse *pParse, int type){
|
||||
sqlite3VdbeUsesBtree(v, i);
|
||||
}
|
||||
}
|
||||
sqlite3VdbeAddOp0(v, OP_AutoCommit);
|
||||
sqlite3VdbeAddOp3(v, OP_AutoCommit, 0, 0, (type==TK_CONCURRENT));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+1
-1
@@ -2457,7 +2457,7 @@ int sqlite3ExprCanBeNull(const Expr *p){
|
||||
return ExprHasProperty(p, EP_CanBeNull) ||
|
||||
p->y.pTab==0 || /* Reference to column of index on expression */
|
||||
(p->iColumn>=0
|
||||
&& ALWAYS(p->y.pTab->aCol!=0) /* Defense against OOM problems */
|
||||
&& p->y.pTab->aCol!=0 /* Possible due to prior error */
|
||||
&& p->y.pTab->aCol[p->iColumn].notNull==0);
|
||||
default:
|
||||
return 1;
|
||||
|
||||
+19
@@ -702,6 +702,25 @@ static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Clear the apTrigger[] cache of CASCADE triggers for all foreign keys
|
||||
** in a particular database. This needs to happen when the schema
|
||||
** changes.
|
||||
*/
|
||||
void sqlite3FkClearTriggerCache(sqlite3 *db, int iDb){
|
||||
HashElem *k;
|
||||
Hash *pHash = &db->aDb[iDb].pSchema->tblHash;
|
||||
for(k=sqliteHashFirst(pHash); k; k=sqliteHashNext(k)){
|
||||
Table *pTab = sqliteHashData(k);
|
||||
FKey *pFKey;
|
||||
if( !IsOrdinaryTable(pTab) ) continue;
|
||||
for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){
|
||||
fkTriggerDelete(db, pFKey->apTrigger[0]); pFKey->apTrigger[0] = 0;
|
||||
fkTriggerDelete(db, pFKey->apTrigger[1]); pFKey->apTrigger[1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is called to generate code that runs when table pTab is
|
||||
** being dropped from the database. The SrcList passed as the second argument
|
||||
|
||||
+4
-2
@@ -511,8 +511,9 @@ static void randomFunc(
|
||||
sqlite3_value **NotUsed2
|
||||
){
|
||||
sqlite_int64 r;
|
||||
sqlite3 *db = sqlite3_context_db_handle(context);
|
||||
UNUSED_PARAMETER2(NotUsed, NotUsed2);
|
||||
sqlite3_randomness(sizeof(r), &r);
|
||||
sqlite3FastRandomness(&db->sPrng, sizeof(r), &r);
|
||||
if( r<0 ){
|
||||
/* We need to prevent a random number of 0x8000000000000000
|
||||
** (or -9223372036854775808) since when you do abs() of that
|
||||
@@ -538,6 +539,7 @@ static void randomBlob(
|
||||
){
|
||||
sqlite3_int64 n;
|
||||
unsigned char *p;
|
||||
sqlite3 *db = sqlite3_context_db_handle(context);
|
||||
assert( argc==1 );
|
||||
UNUSED_PARAMETER(argc);
|
||||
n = sqlite3_value_int64(argv[0]);
|
||||
@@ -546,7 +548,7 @@ static void randomBlob(
|
||||
}
|
||||
p = contextMalloc(context, n);
|
||||
if( p ){
|
||||
sqlite3_randomness(n, p);
|
||||
sqlite3FastRandomness(&db->sPrng, n, p);
|
||||
sqlite3_result_blob(context, (char*)p, n, sqlite3_free);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -43,7 +43,7 @@ void sqlite3OpenTable(
|
||||
}else{
|
||||
Index *pPk = sqlite3PrimaryKeyIndex(pTab);
|
||||
assert( pPk!=0 );
|
||||
assert( pPk->tnum==pTab->tnum );
|
||||
assert( pPk->tnum==pTab->tnum || CORRUPT_DB );
|
||||
sqlite3VdbeAddOp3(v, opcode, iCur, pPk->tnum, iDb);
|
||||
sqlite3VdbeSetP4KeyInfo(pParse, pPk);
|
||||
VdbeComment((v, "%s", pTab->zName));
|
||||
@@ -2009,6 +2009,7 @@ void sqlite3GenerateConstraintChecks(
|
||||
if( onError==OE_Replace /* IPK rule is REPLACE */
|
||||
&& onError!=overrideError /* Rules for other constraints are different */
|
||||
&& pTab->pIndex /* There exist other constraints */
|
||||
&& !upsertIpkDelay /* IPK check already deferred by UPSERT */
|
||||
){
|
||||
ipkTop = sqlite3VdbeAddOp0(v, OP_Goto)+1;
|
||||
VdbeComment((v, "defer IPK REPLACE until last"));
|
||||
@@ -2417,6 +2418,7 @@ void sqlite3GenerateConstraintChecks(
|
||||
if( ipkTop ){
|
||||
sqlite3VdbeGoto(v, ipkTop);
|
||||
VdbeComment((v, "Do IPK REPLACE"));
|
||||
assert( ipkBottom>0 );
|
||||
sqlite3VdbeJumpHere(v, ipkBottom);
|
||||
}
|
||||
|
||||
@@ -2547,7 +2549,6 @@ void sqlite3CompleteInsertion(
|
||||
}
|
||||
pik_flags = (useSeekResult ? OPFLAG_USESEEKRESULT : 0);
|
||||
if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){
|
||||
assert( pParse->nested==0 );
|
||||
pik_flags |= OPFLAG_NCHANGE;
|
||||
pik_flags |= (update_flags & OPFLAG_SAVEPOSITION);
|
||||
if( update_flags==0 ){
|
||||
|
||||
+41
-6
@@ -2857,6 +2857,8 @@ int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){
|
||||
if( newLimit>=0 ){ /* IMP: R-52476-28732 */
|
||||
if( newLimit>aHardLimit[limitId] ){
|
||||
newLimit = aHardLimit[limitId]; /* IMP: R-51463-25634 */
|
||||
}else if( newLimit<1 && limitId==SQLITE_LIMIT_LENGTH ){
|
||||
newLimit = 1;
|
||||
}
|
||||
db->aLimit[limitId] = newLimit;
|
||||
}
|
||||
@@ -3230,7 +3232,7 @@ static int openDatabase(
|
||||
db->aDb = db->aDbStatic;
|
||||
db->lookaside.bDisable = 1;
|
||||
db->lookaside.sz = 0;
|
||||
|
||||
sqlite3FastPrngInit(&db->sPrng);
|
||||
assert( sizeof(db->aLimit)==sizeof(aHardLimit) );
|
||||
memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit));
|
||||
db->aLimit[SQLITE_LIMIT_WORKER_THREADS] = SQLITE_DEFAULT_WORKER_THREADS;
|
||||
@@ -4260,12 +4262,16 @@ int sqlite3_test_control(int op, ...){
|
||||
*/
|
||||
case SQLITE_TESTCTRL_IMPOSTER: {
|
||||
sqlite3 *db = va_arg(ap, sqlite3*);
|
||||
int iDb;
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
db->init.iDb = sqlite3FindDbName(db, va_arg(ap,const char*));
|
||||
db->init.busy = db->init.imposterTable = va_arg(ap,int);
|
||||
db->init.newTnum = va_arg(ap,int);
|
||||
if( db->init.busy==0 && db->init.newTnum>0 ){
|
||||
sqlite3ResetAllSchemasOfConnection(db);
|
||||
iDb = sqlite3FindDbName(db, va_arg(ap,const char*));
|
||||
if( iDb>=0 ){
|
||||
db->init.iDb = iDb;
|
||||
db->init.busy = db->init.imposterTable = va_arg(ap,int);
|
||||
db->init.newTnum = va_arg(ap,int);
|
||||
if( db->init.busy==0 && db->init.newTnum>0 ){
|
||||
sqlite3ResetAllSchemasOfConnection(db);
|
||||
}
|
||||
}
|
||||
sqlite3_mutex_leave(db->mutex);
|
||||
break;
|
||||
@@ -4716,6 +4722,35 @@ void sqlite3_snapshot_free(sqlite3_snapshot *pSnapshot){
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_SNAPSHOT */
|
||||
|
||||
SQLITE_EXPERIMENTAL int sqlite3_wal_info(
|
||||
sqlite3 *db, const char *zDb,
|
||||
unsigned int *pnPrior, unsigned int *pnFrame
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
Btree *pBt;
|
||||
int iDb;
|
||||
|
||||
#ifdef SQLITE_ENABLE_API_ARMOR
|
||||
if( !sqlite3SafetyCheckOk(db) ){
|
||||
return SQLITE_MISUSE_BKPT;
|
||||
}
|
||||
#endif
|
||||
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
iDb = sqlite3FindDbName(db, zDb);
|
||||
if( iDb<0 ){
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
pBt = db->aDb[iDb].pBt;
|
||||
rc = sqlite3PagerWalInfo(sqlite3BtreePager(pBt), pnPrior, pnFrame);
|
||||
sqlite3_mutex_leave(db->mutex);
|
||||
#endif /* SQLITE_OMIT_WAL */
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
|
||||
/*
|
||||
** Given the name of a compile-time option, return true if that option
|
||||
|
||||
+1
-1
@@ -265,7 +265,7 @@ static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){
|
||||
p->pFirst = 0;
|
||||
}else{
|
||||
i64 iOff = p->nChunkSize;
|
||||
for(pIter=p->pFirst; ALWAYS(pIter) && iOff<=size; pIter=pIter->pNext){
|
||||
for(pIter=p->pFirst; ALWAYS(pIter) && iOff<size; pIter=pIter->pNext){
|
||||
iOff += p->nChunkSize;
|
||||
}
|
||||
if( ALWAYS(pIter) ){
|
||||
|
||||
@@ -1177,6 +1177,10 @@ struct unixInodeInfo {
|
||||
sem_t *pSem; /* Named POSIX semaphore */
|
||||
char aSemName[MAX_PATHNAME+2]; /* Name of that semaphore */
|
||||
#endif
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
sqlite3_int64 nSharedMapping; /* Size of mapped region in bytes */
|
||||
void *pSharedMapping; /* Memory mapped region */
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -1329,6 +1333,13 @@ static void releaseInodeInfo(unixFile *pFile){
|
||||
pInode->nRef--;
|
||||
if( pInode->nRef==0 ){
|
||||
assert( pInode->pShmNode==0 );
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
if( pInode->pSharedMapping ){
|
||||
osMunmap(pInode->pSharedMapping, pInode->nSharedMapping);
|
||||
pInode->pSharedMapping = 0;
|
||||
pInode->nSharedMapping = 0;
|
||||
}
|
||||
#endif
|
||||
sqlite3_mutex_enter(pInode->pLockMutex);
|
||||
closePendingFds(pFile);
|
||||
sqlite3_mutex_leave(pInode->pLockMutex);
|
||||
@@ -2192,6 +2203,14 @@ static int nolockUnlock(sqlite3_file *NotUsed, int NotUsed2){
|
||||
** Close the file.
|
||||
*/
|
||||
static int nolockClose(sqlite3_file *id) {
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
unixFile *pFd = (unixFile*)id;
|
||||
if( pFd->pInode ){
|
||||
unixEnterMutex();
|
||||
releaseInodeInfo(pFd);
|
||||
unixLeaveMutex();
|
||||
}
|
||||
#endif
|
||||
return closeUnixFile(id);
|
||||
}
|
||||
|
||||
@@ -4045,6 +4064,9 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){
|
||||
*(i64*)pArg = pFile->mmapSizeMax;
|
||||
if( newLimit>=0 && newLimit!=pFile->mmapSizeMax && pFile->nFetchOut==0 ){
|
||||
pFile->mmapSizeMax = newLimit;
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
if( pFile->pInode==0 )
|
||||
#endif
|
||||
if( pFile->mmapSize>0 ){
|
||||
unixUnmapfile(pFile);
|
||||
rc = unixMapfile(pFile, -1);
|
||||
@@ -5113,6 +5135,9 @@ static int unixShmUnmap(
|
||||
*/
|
||||
static void unixUnmapfile(unixFile *pFd){
|
||||
assert( pFd->nFetchOut==0 );
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
if( pFd->pInode ) return;
|
||||
#endif
|
||||
if( pFd->pMapRegion ){
|
||||
osMunmap(pFd->pMapRegion, pFd->mmapSizeActual);
|
||||
pFd->pMapRegion = 0;
|
||||
@@ -5244,6 +5269,28 @@ static int unixMapfile(unixFile *pFd, i64 nMap){
|
||||
nMap = pFd->mmapSizeMax;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
if( pFd->pInode ){
|
||||
unixInodeInfo *pInode = pFd->pInode;
|
||||
if( pFd->pMapRegion ) return SQLITE_OK;
|
||||
unixEnterMutex();
|
||||
if( pInode->pSharedMapping==0 ){
|
||||
u8 *pNew = osMmap(0, nMap, PROT_READ, MAP_SHARED, pFd->h, 0);
|
||||
if( pNew==MAP_FAILED ){
|
||||
unixLogError(SQLITE_OK, "mmap", pFd->zPath);
|
||||
pFd->mmapSizeMax = 0;
|
||||
}else{
|
||||
pInode->pSharedMapping = pNew;
|
||||
pInode->nSharedMapping = nMap;
|
||||
}
|
||||
}
|
||||
pFd->pMapRegion = pInode->pSharedMapping;
|
||||
pFd->mmapSizeActual = pFd->mmapSize = pInode->nSharedMapping;
|
||||
unixLeaveMutex();
|
||||
return SQLITE_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
assert( nMap>0 || (pFd->mmapSize==0 && pFd->pMapRegion==0) );
|
||||
if( nMap!=pFd->mmapSize ){
|
||||
unixRemapfile(pFd, nMap);
|
||||
@@ -5681,6 +5728,9 @@ static int fillInUnixFile(
|
||||
if( pLockingStyle == &posixIoMethods
|
||||
#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE
|
||||
|| pLockingStyle == &nfsIoMethods
|
||||
#endif
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
|| pLockingStyle == &nolockIoMethods
|
||||
#endif
|
||||
){
|
||||
unixEnterMutex();
|
||||
|
||||
+192
-26
@@ -658,6 +658,9 @@ struct Pager {
|
||||
u32 cksumInit; /* Quasi-random value added to every checksum */
|
||||
u32 nSubRec; /* Number of records written to sub-journal */
|
||||
Bitvec *pInJournal; /* One bit for each page in the database file */
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
Bitvec *pAllRead; /* Pages read within current CONCURRENT trans. */
|
||||
#endif
|
||||
sqlite3_file *fd; /* File descriptor for database */
|
||||
sqlite3_file *jfd; /* File descriptor for main journal */
|
||||
sqlite3_file *sjfd; /* File descriptor for sub-journal */
|
||||
@@ -914,7 +917,9 @@ static int assert_pager_state(Pager *p){
|
||||
if( !pagerUseWal(pPager) ){
|
||||
assert( p->eLock>=RESERVED_LOCK );
|
||||
}
|
||||
assert( pPager->dbSize==pPager->dbOrigSize );
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
assert( pPager->dbSize==pPager->dbOrigSize || pPager->pAllRead );
|
||||
#endif
|
||||
assert( pPager->dbOrigSize==pPager->dbFileSize );
|
||||
assert( pPager->dbOrigSize==pPager->dbHintSize );
|
||||
assert( pPager->setSuper==0 );
|
||||
@@ -1799,6 +1804,53 @@ static int addToSavepointBitvecs(Pager *pPager, Pgno pgno){
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/*
|
||||
** If they are not already, begin recording all pages read from the pager layer
|
||||
** by the b-tree layer This is used by concurrent transactions. Return
|
||||
** SQLITE_OK if successful, or an SQLite error code (SQLITE_NOMEM) if an error
|
||||
** occurs.
|
||||
*/
|
||||
int sqlite3PagerBeginConcurrent(Pager *pPager){
|
||||
int rc = SQLITE_OK;
|
||||
if( pPager->pAllRead==0 ){
|
||||
pPager->pAllRead = sqlite3BitvecCreate(pPager->dbSize);
|
||||
pPager->dbOrigSize = pPager->dbSize;
|
||||
if( pPager->pAllRead==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** Stop recording all pages read from the pager layer by the b-tree layer
|
||||
** and discard any current records.
|
||||
*/
|
||||
void sqlite3PagerEndConcurrent(Pager *pPager){
|
||||
sqlite3BitvecDestroy(pPager->pAllRead);
|
||||
pPager->pAllRead = 0;
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** Return true if the database is in wal mode. False otherwise.
|
||||
*/
|
||||
int sqlite3PagerIsWal(Pager *pPager){
|
||||
return pPager->pWal!=0;
|
||||
}
|
||||
#endif /* SQLITE_OMIT_CONCURRENT */
|
||||
|
||||
/*
|
||||
** Free the Pager.pInJournal and Pager.pAllRead bitvec objects.
|
||||
*/
|
||||
static void pagerFreeBitvecs(Pager *pPager){
|
||||
sqlite3BitvecDestroy(pPager->pInJournal);
|
||||
pPager->pInJournal = 0;
|
||||
sqlite3PagerEndConcurrent(pPager);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if the pager is in exclusive mode and not
|
||||
** in the ERROR state. Otherwise, it switches the pager to PAGER_OPEN
|
||||
@@ -1823,8 +1875,7 @@ static void pager_unlock(Pager *pPager){
|
||||
|| pPager->eState==PAGER_ERROR
|
||||
);
|
||||
|
||||
sqlite3BitvecDestroy(pPager->pInJournal);
|
||||
pPager->pInJournal = 0;
|
||||
pagerFreeBitvecs(pPager);
|
||||
releaseAllSavepoints(pPager);
|
||||
|
||||
if( pagerUseWal(pPager) ){
|
||||
@@ -2091,8 +2142,7 @@ static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){
|
||||
}
|
||||
#endif
|
||||
|
||||
sqlite3BitvecDestroy(pPager->pInJournal);
|
||||
pPager->pInJournal = 0;
|
||||
pagerFreeBitvecs(pPager);
|
||||
pPager->nRec = 0;
|
||||
if( rc==SQLITE_OK ){
|
||||
if( MEMDB || pagerFlushOnCommit(pPager, bCommit) ){
|
||||
@@ -3098,8 +3148,24 @@ static int pagerRollbackWal(Pager *pPager){
|
||||
** + Reload page content from the database (if refcount>0).
|
||||
*/
|
||||
pPager->dbSize = pPager->dbOrigSize;
|
||||
rc = sqlite3WalUndo(pPager->pWal, pagerUndoCallback, (void *)pPager);
|
||||
rc = sqlite3WalUndo(pPager->pWal, pagerUndoCallback, (void *)pPager,
|
||||
#ifdef SQLITE_OMIT_CONCURRENT
|
||||
0
|
||||
#else
|
||||
pPager->pAllRead!=0
|
||||
#endif
|
||||
);
|
||||
pList = sqlite3PcacheDirtyList(pPager->pPCache);
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/* If this is an CONCURRENT transaction, then page 1 must be reread from
|
||||
** the db file, even if it is not dirty. This is because the b-tree layer
|
||||
** may have already zeroed the nFree and iTrunk header fields. */
|
||||
if( rc==SQLITE_OK && (pList==0 || pList->pgno!=1) && pPager->pAllRead ){
|
||||
rc = pagerUndoCallback((void*)pPager, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
while( pList && rc==SQLITE_OK ){
|
||||
PgHdr *pNext = pList->pDirty;
|
||||
rc = pagerUndoCallback((void *)pPager, pList->pgno);
|
||||
@@ -3149,6 +3215,8 @@ static int pagerWalFrames(
|
||||
if( p->pgno<=nTruncate ){
|
||||
ppNext = &p->pDirty;
|
||||
nList++;
|
||||
PAGERTRACE(("TO-WAL %d page %d hash(%08x)\n",
|
||||
PAGERID(pPager), p->pgno, pager_pagehash(p)));
|
||||
}
|
||||
}
|
||||
assert( pList );
|
||||
@@ -3917,7 +3985,7 @@ static int pager_wait_on_lock(Pager *pPager, int locktype){
|
||||
#if defined(SQLITE_DEBUG)
|
||||
static void assertTruncateConstraintCb(PgHdr *pPg){
|
||||
assert( pPg->flags&PGHDR_DIRTY );
|
||||
assert( !subjRequiresPage(pPg) || pPg->pgno<=pPg->pPager->dbSize );
|
||||
assert( pPg->pgno<=pPg->pPager->dbSize || !subjRequiresPage(pPg) );
|
||||
}
|
||||
static void assertTruncateConstraint(Pager *pPager){
|
||||
sqlite3PcacheIterateDirty(pPager->pPCache, assertTruncateConstraintCb);
|
||||
@@ -4213,7 +4281,7 @@ static int syncJournal(Pager *pPager, int newHdr){
|
||||
assert( assert_pager_state(pPager) );
|
||||
assert( !pagerUseWal(pPager) );
|
||||
|
||||
rc = sqlite3PagerExclusiveLock(pPager);
|
||||
rc = sqlite3PagerExclusiveLock(pPager, 0, 0);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
if( !pPager->noSync ){
|
||||
@@ -4564,6 +4632,12 @@ static int pagerStress(void *p, PgHdr *pPg){
|
||||
pPager->aStat[PAGER_STAT_SPILL]++;
|
||||
pPg->pDirty = 0;
|
||||
if( pagerUseWal(pPager) ){
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/* If the transaction is a "BEGIN CONCURRENT" transaction, the page
|
||||
** cannot be flushed to disk. Return early in this case. */
|
||||
if( pPager->pAllRead ) return SQLITE_OK;
|
||||
#endif
|
||||
|
||||
/* Write a single frame for this page to the log. */
|
||||
rc = subjournalPageIfRequired(pPg);
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -5258,7 +5332,7 @@ int sqlite3PagerSharedLock(Pager *pPager){
|
||||
** may mean that the pager was in the error-state when this
|
||||
** function was called and the journal file does not exist.
|
||||
*/
|
||||
if( !isOpen(pPager->jfd) ){
|
||||
if( !isOpen(pPager->jfd) && pPager->journalMode!=PAGER_JOURNALMODE_OFF ){
|
||||
sqlite3_vfs * const pVfs = pPager->pVfs;
|
||||
int bExists; /* True if journal file exists */
|
||||
rc = sqlite3OsAccess(
|
||||
@@ -5483,6 +5557,18 @@ static int getPageNormal(
|
||||
assert( assert_pager_state(pPager) );
|
||||
assert( pPager->hasHeldSharedLock==1 );
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/* If this is an CONCURRENT transaction and the page being read was
|
||||
** present in the database file when the transaction was opened,
|
||||
** mark it as read in the pAllRead vector. */
|
||||
pPg = 0;
|
||||
if( pPager->pAllRead && pgno<=pPager->dbOrigSize ){
|
||||
PAGERTRACE(("USING page %d\n", pgno));
|
||||
rc = sqlite3BitvecSet(pPager->pAllRead, pgno);
|
||||
if( rc!=SQLITE_OK ) goto pager_acquire_err;
|
||||
}
|
||||
#endif
|
||||
|
||||
if( pgno==0 ) return SQLITE_CORRUPT_BKPT;
|
||||
pBase = sqlite3PcacheFetch(pPager->pPCache, pgno, 3);
|
||||
if( pBase==0 ){
|
||||
@@ -5820,11 +5906,14 @@ static int pager_open_journal(Pager *pPager){
|
||||
** Begin a write-transaction on the specified pager object. If a
|
||||
** write-transaction has already been opened, this function is a no-op.
|
||||
**
|
||||
** If the exFlag argument is false, then acquire at least a RESERVED
|
||||
** lock on the database file. If exFlag is true, then acquire at least
|
||||
** If the exFlag argument is 0, then acquire at least a RESERVED
|
||||
** lock on the database file. If exFlag is >0, then acquire at least
|
||||
** an EXCLUSIVE lock. If such a lock is already held, no locking
|
||||
** functions need be called.
|
||||
**
|
||||
** If (exFlag<0) and the database is in WAL mode, do not take any locks.
|
||||
** The transaction will run in CONCURRENT mode instead.
|
||||
**
|
||||
** If the subjInMemory argument is non-zero, then any sub-journal opened
|
||||
** within this transaction will be opened as an in-memory file. This
|
||||
** has no effect if the sub-journal is already opened (as it may be when
|
||||
@@ -5842,7 +5931,6 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
|
||||
|
||||
if( pPager->eState==PAGER_READER ){
|
||||
assert( pPager->pInJournal==0 );
|
||||
|
||||
if( pagerUseWal(pPager) ){
|
||||
/* If the pager is configured to use locking_mode=exclusive, and an
|
||||
** exclusive lock on the database is not already held, obtain it now.
|
||||
@@ -5858,9 +5946,10 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
|
||||
/* Grab the write lock on the log file. If successful, upgrade to
|
||||
** PAGER_RESERVED state. Otherwise, return an error code to the caller.
|
||||
** The busy-handler is not invoked if another connection already
|
||||
** holds the write-lock. If possible, the upper layer will call it.
|
||||
*/
|
||||
rc = sqlite3WalBeginWriteTransaction(pPager->pWal);
|
||||
** holds the write-lock. If possible, the upper layer will call it. */
|
||||
if( exFlag>=0 ){
|
||||
rc = sqlite3WalBeginWriteTransaction(pPager->pWal);
|
||||
}
|
||||
}else{
|
||||
/* Obtain a RESERVED lock on the database file. If the exFlag parameter
|
||||
** is true, then immediately upgrade this to an EXCLUSIVE lock. The
|
||||
@@ -5868,7 +5957,7 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
|
||||
** lock, but not when obtaining the RESERVED lock.
|
||||
*/
|
||||
rc = pagerLockDb(pPager, RESERVED_LOCK);
|
||||
if( rc==SQLITE_OK && exFlag ){
|
||||
if( rc==SQLITE_OK && exFlag>0 ){
|
||||
rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
|
||||
}
|
||||
}
|
||||
@@ -6168,7 +6257,7 @@ int sqlite3PagerWrite(PgHdr *pPg){
|
||||
** to sqlite3PagerWrite(). In other words, return TRUE if it is ok
|
||||
** to change the content of the page.
|
||||
*/
|
||||
#ifndef NDEBUG
|
||||
#if !defined(SQLITE_OMIT_CONCURRENT) || !defined(NDEBUG)
|
||||
int sqlite3PagerIswriteable(DbPage *pPg){
|
||||
return pPg->flags & PGHDR_WRITEABLE;
|
||||
}
|
||||
@@ -6324,17 +6413,26 @@ int sqlite3PagerSync(Pager *pPager, const char *zSuper){
|
||||
}
|
||||
|
||||
/*
|
||||
** This function may only be called while a write-transaction is active in
|
||||
** rollback. If the connection is in WAL mode, this call is a no-op.
|
||||
** Otherwise, if the connection does not already have an EXCLUSIVE lock on
|
||||
** the database file, an attempt is made to obtain one.
|
||||
** This function is called to ensure that all locks required to commit the
|
||||
** current write-transaction to the database file are held. If the db is
|
||||
** in rollback mode, this means the EXCLUSIVE lock on the database file.
|
||||
**
|
||||
** If the EXCLUSIVE lock is already held or the attempt to obtain it is
|
||||
** successful, or the connection is in WAL mode, SQLITE_OK is returned.
|
||||
** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is
|
||||
** returned.
|
||||
** Or, if this is a non-CONCURRENT transaction on a wal-mode database, this
|
||||
** function is a no-op.
|
||||
**
|
||||
** If this is an CONCURRENT transaction on a wal-mode database, this function
|
||||
** attempts to obtain the WRITER lock on the wal file and also checks to
|
||||
** see that the transaction can be safely committed (does not commit with
|
||||
** any other transaction committed since it was opened).
|
||||
**
|
||||
** If the required locks are already held or successfully obtained and
|
||||
** the transaction can be committed, SQLITE_OK is returned. If a required lock
|
||||
** cannot be obtained, SQLITE_BUSY is returned. Or, if the current transaction
|
||||
** is CONCURRENT and cannot be committed due to a conflict, SQLITE_BUSY_SNAPSHOT
|
||||
** is returned. Otherwise, if some other error occurs (IO error, OOM etc.),
|
||||
** and SQLite error code is returned.
|
||||
*/
|
||||
int sqlite3PagerExclusiveLock(Pager *pPager){
|
||||
int sqlite3PagerExclusiveLock(Pager *pPager, PgHdr *pPage1, Pgno *piConflict){
|
||||
int rc = pPager->errCode;
|
||||
assert( assert_pager_state(pPager) );
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -6346,10 +6444,73 @@ int sqlite3PagerExclusiveLock(Pager *pPager){
|
||||
if( 0==pagerUseWal(pPager) ){
|
||||
rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
|
||||
}
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
else{
|
||||
if( pPager->pAllRead ){
|
||||
/* This is an CONCURRENT transaction. Attempt to lock the wal database
|
||||
** here. If SQLITE_BUSY (but not SQLITE_BUSY_SNAPSHOT) is returned,
|
||||
** invoke the busy-handler and try again for as long as it returns
|
||||
** non-zero. */
|
||||
do {
|
||||
rc = sqlite3WalLockForCommit(
|
||||
pPager->pWal, pPage1, pPager->pAllRead, piConflict
|
||||
);
|
||||
}while( rc==SQLITE_BUSY
|
||||
&& pPager->xBusyHandler(pPager->pBusyHandlerArg)
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif /* SQLITE_OMIT_CONCURRENT */
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/*
|
||||
** This function is called as part of committing an CONCURRENT transaction.
|
||||
** At this point the wal WRITER lock is held, and all pages in the cache
|
||||
** except for page 1 are compatible with the snapshot at the head of the
|
||||
** wal file.
|
||||
**
|
||||
** This function updates the in-memory data structures and reloads the
|
||||
** contents of page 1 so that the client is operating on the snapshot
|
||||
** at the head of the wal file.
|
||||
**
|
||||
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
|
||||
*/
|
||||
int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage *pPage1){
|
||||
int rc;
|
||||
|
||||
assert( pPager->pWal && pPager->pAllRead );
|
||||
rc = sqlite3WalUpgradeSnapshot(pPager->pWal);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = readDbPage(pPage1);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** Set the in-memory cache of the database file size to nSz pages.
|
||||
*/
|
||||
void sqlite3PagerSetDbsize(Pager *pPager, Pgno nSz){
|
||||
pPager->dbSize = nSz;
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** If this is a WAL mode connection and the WRITER lock is currently held,
|
||||
** relinquish it.
|
||||
*/
|
||||
void sqlite3PagerDropExclusiveLock(Pager *pPager){
|
||||
if( pagerUseWal(pPager) ){
|
||||
sqlite3WalEndWriteTransaction(pPager->pWal);
|
||||
}
|
||||
}
|
||||
#endif /* SQLITE_OMIT_CONCURRENT */
|
||||
|
||||
|
||||
/*
|
||||
** Sync the database file for the pager pPager. zSuper points to the name
|
||||
** of a super-journal file that should be written into the individual
|
||||
@@ -7693,6 +7854,11 @@ void sqlite3PagerSnapshotUnlock(Pager *pPager){
|
||||
}
|
||||
|
||||
#endif /* SQLITE_ENABLE_SNAPSHOT */
|
||||
|
||||
int sqlite3PagerWalInfo(Pager *pPager, u32 *pnPrior, u32 *pnFrame){
|
||||
return sqlite3WalInfo(pPager->pWal, pnPrior, pnFrame);
|
||||
}
|
||||
|
||||
#endif /* !SQLITE_OMIT_WAL */
|
||||
|
||||
#ifdef SQLITE_ENABLE_ZIPVFS
|
||||
|
||||
+19
-2
@@ -162,7 +162,7 @@ void *sqlite3PagerGetExtra(DbPage *);
|
||||
void sqlite3PagerPagecount(Pager*, int*);
|
||||
int sqlite3PagerBegin(Pager*, int exFlag, int);
|
||||
int sqlite3PagerCommitPhaseOne(Pager*,const char *zSuper, int);
|
||||
int sqlite3PagerExclusiveLock(Pager*);
|
||||
int sqlite3PagerExclusiveLock(Pager*, DbPage *pPage1, Pgno*);
|
||||
int sqlite3PagerSync(Pager *pPager, const char *zSuper);
|
||||
int sqlite3PagerCommitPhaseTwo(Pager*);
|
||||
int sqlite3PagerRollback(Pager*);
|
||||
@@ -170,6 +170,7 @@ int sqlite3PagerOpenSavepoint(Pager *pPager, int n);
|
||||
int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
|
||||
int sqlite3PagerSharedLock(Pager *pPager);
|
||||
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
int sqlite3PagerCheckpoint(Pager *pPager, sqlite3*, int, int*, int*);
|
||||
int sqlite3PagerWalSupported(Pager *pPager);
|
||||
@@ -224,10 +225,26 @@ void sqlite3PagerTruncateImage(Pager*,Pgno);
|
||||
|
||||
void sqlite3PagerRekey(DbPage*, Pgno, u16);
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
void sqlite3PagerEndConcurrent(Pager*);
|
||||
int sqlite3PagerBeginConcurrent(Pager*);
|
||||
void sqlite3PagerDropExclusiveLock(Pager*);
|
||||
int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage*);
|
||||
void sqlite3PagerSetDbsize(Pager *pPager, Pgno);
|
||||
int sqlite3PagerIsWal(Pager*);
|
||||
#else
|
||||
# define sqlite3PagerEndConcurrent(x)
|
||||
#endif
|
||||
|
||||
#if defined(SQLITE_DEBUG) || !defined(SQLITE_OMIT_CONCURRENT)
|
||||
int sqlite3PagerIswriteable(DbPage*);
|
||||
#endif
|
||||
|
||||
int sqlite3PagerWalInfo(Pager*, u32 *pnPrior, u32 *pnFrame);
|
||||
|
||||
/* Functions to support testing and debugging. */
|
||||
#if !defined(NDEBUG) || defined(SQLITE_TEST)
|
||||
Pgno sqlite3PagerPagenumber(DbPage*);
|
||||
int sqlite3PagerIswriteable(DbPage*);
|
||||
#endif
|
||||
#ifdef SQLITE_TEST
|
||||
int *sqlite3PagerStats(Pager*);
|
||||
|
||||
+19
-3
@@ -105,6 +105,13 @@
|
||||
*/
|
||||
struct TrigEvent { int a; IdList * b; };
|
||||
|
||||
/*
|
||||
** Generate a syntax error
|
||||
*/
|
||||
static void parserSyntaxError(Parse *pParse, Token *p){
|
||||
sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", p);
|
||||
}
|
||||
|
||||
struct FrameBound { int eType; Expr *pExpr; };
|
||||
|
||||
/*
|
||||
@@ -164,7 +171,16 @@ trans_opt ::= TRANSACTION nm.
|
||||
transtype(A) ::= . {A = TK_DEFERRED;}
|
||||
transtype(A) ::= DEFERRED(X). {A = @X; /*A-overwrites-X*/}
|
||||
transtype(A) ::= IMMEDIATE(X). {A = @X; /*A-overwrites-X*/}
|
||||
transtype(A) ::= EXCLUSIVE(X). {A = @X; /*A-overwrites-X*/}
|
||||
transtype(A) ::= ID(X). {
|
||||
Token *p = &X;
|
||||
if( p->n==9 && sqlite3_strnicmp(p->z,"exclusive",9)==0 ){
|
||||
A = TK_EXCLUSIVE;
|
||||
}else if( p->n==10 && sqlite3_strnicmp(p->z,"concurrent",10)==0 ){
|
||||
A = TK_CONCURRENT; /*A-overwrites-X*/
|
||||
}else{
|
||||
parserSyntaxError(pParse, p);
|
||||
}
|
||||
}
|
||||
cmd ::= COMMIT|END(X) trans_opt. {sqlite3EndTransaction(pParse,@X);}
|
||||
cmd ::= ROLLBACK(X) trans_opt. {sqlite3EndTransaction(pParse,@X);}
|
||||
|
||||
@@ -296,7 +312,6 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,A,Y);}
|
||||
//
|
||||
%token_class id ID|INDEXED.
|
||||
|
||||
|
||||
// And "ids" is an identifer-or-string.
|
||||
//
|
||||
%token_class ids ID|STRING.
|
||||
@@ -1104,7 +1119,7 @@ expr(A) ::= VARIABLE(X). {
|
||||
Token t = X; /*A-overwrites-X*/
|
||||
assert( t.n>=2 );
|
||||
if( pParse->nested==0 ){
|
||||
sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t);
|
||||
parserSyntaxError(pParse, &t);
|
||||
A = 0;
|
||||
}else{
|
||||
A = sqlite3PExpr(pParse, TK_REGISTER, 0, 0);
|
||||
@@ -1871,6 +1886,7 @@ filter_clause(A) ::= FILTER LP WHERE expr(X) RP. { A = X; }
|
||||
UPLUS /* Unary plus */
|
||||
TRUTH /* IS TRUE or IS FALSE or IS NOT TRUE or IS NOT FALSE */
|
||||
REGISTER /* Reference to a VDBE register */
|
||||
CONCURRENT /* BEGIN CONCURRENT */
|
||||
VECTOR /* Vector */
|
||||
SELECT_COLUMN /* Choose a single column from a multi-column SELECT */
|
||||
IF_NULL_ROW /* the if-null-row operator */
|
||||
|
||||
@@ -441,6 +441,15 @@ static const PragmaName aPragmaName[] = {
|
||||
/* iArg: */ 0 },
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
|
||||
#if defined(SQLITE_ENABLE_NOOP_UPDATE)
|
||||
{/* zName: */ "noop_update",
|
||||
/* ePragTyp: */ PragTyp_FLAG,
|
||||
/* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
|
||||
/* ColNames: */ 0, 0,
|
||||
/* iArg: */ SQLITE_NoopUpdate },
|
||||
#endif
|
||||
#endif
|
||||
{/* zName: */ "optimize",
|
||||
/* ePragTyp: */ PragTyp_OPTIMIZE,
|
||||
|
||||
+2
-1
@@ -747,7 +747,7 @@ static int sqlite3Prepare(
|
||||
sParse.checkSchema = 0;
|
||||
}
|
||||
if( sParse.rc!=SQLITE_OK && sParse.rc!=SQLITE_DONE ){
|
||||
if( sParse.checkSchema ){
|
||||
if( sParse.checkSchema && db->init.busy==0 ){
|
||||
schemaIsValid(&sParse);
|
||||
}
|
||||
if( sParse.pVdbe ){
|
||||
@@ -808,6 +808,7 @@ static int sqlite3LockAndPrepare(
|
||||
** reset is considered a permanent error. */
|
||||
rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail);
|
||||
assert( rc==SQLITE_OK || *ppStmt==0 );
|
||||
if( rc==SQLITE_OK || db->mallocFailed ) break;
|
||||
}while( rc==SQLITE_ERROR_RETRY
|
||||
|| (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt++)==0) );
|
||||
sqlite3BtreeLeaveAll(db);
|
||||
|
||||
@@ -111,6 +111,28 @@ void sqlite3_randomness(int N, void *pBuf){
|
||||
sqlite3_mutex_leave(mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
** Initialize a fast PRNG. A Fast PRNG is called "fast" because it does
|
||||
** not need a mutex to operate, though it does use a mutex to initialize.
|
||||
** The quality of the randomness is not as good as the global PRNG.
|
||||
*/
|
||||
void sqlite3FastPrngInit(FastPrng *pPrng){
|
||||
sqlite3_randomness(sizeof(*pPrng), pPrng);
|
||||
pPrng->x |= 1;
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate N bytes of pseudo-randomness using a FastPrng
|
||||
*/
|
||||
void sqlite3FastRandomness(FastPrng *pPrng, int N, void *P){
|
||||
unsigned char *pOut = (unsigned char*)P;
|
||||
while( N-->0 ){
|
||||
pPrng->x = ((pPrng->x)>>1) ^ ((1+~((pPrng->x)&1)) & 0xd0000001);
|
||||
pPrng->y = (pPrng->y)*1103515245 + 12345;
|
||||
*(pOut++) = (pPrng->x ^ pPrng->y) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef SQLITE_UNTESTABLE
|
||||
/*
|
||||
** For testing purposes, we sometimes want to preserve the state of
|
||||
|
||||
+1
-1
@@ -2140,7 +2140,7 @@ int sqlite3ColumnsFromExprList(
|
||||
if( zName[j]==':' ) nName = j;
|
||||
}
|
||||
zName = sqlite3MPrintf(db, "%.*z:%u", nName, zName, ++cnt);
|
||||
if( cnt>3 ) sqlite3_randomness(sizeof(cnt), &cnt);
|
||||
if( cnt>3 ) sqlite3FastRandomness(&db->sPrng, sizeof(cnt), &cnt);
|
||||
}
|
||||
pCol->zCnName = zName;
|
||||
pCol->hName = sqlite3StrIHash(zName);
|
||||
|
||||
+45
-35
@@ -8941,16 +8941,8 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
char *zNewFilename = 0; /* Name of the database file to open */
|
||||
int iName = 1; /* Index in azArg[] of the filename */
|
||||
int newFlag = 0; /* True to delete file before opening */
|
||||
/* Close the existing database */
|
||||
session_close_all(p, -1);
|
||||
close_db(p->db);
|
||||
p->db = 0;
|
||||
p->pAuxDb->zDbFilename = 0;
|
||||
sqlite3_free(p->pAuxDb->zFreeOnClose);
|
||||
p->pAuxDb->zFreeOnClose = 0;
|
||||
p->openMode = SHELL_OPEN_UNSPEC;
|
||||
p->openFlags = 0;
|
||||
p->szMax = 0;
|
||||
int openMode = SHELL_OPEN_UNSPEC;
|
||||
|
||||
/* Check for command-line arguments */
|
||||
for(iName=1; iName<nArg; iName++){
|
||||
const char *z = azArg[iName];
|
||||
@@ -8958,19 +8950,19 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
newFlag = 1;
|
||||
#ifdef SQLITE_HAVE_ZLIB
|
||||
}else if( optionMatch(z, "zip") ){
|
||||
p->openMode = SHELL_OPEN_ZIPFILE;
|
||||
openMode = SHELL_OPEN_ZIPFILE;
|
||||
#endif
|
||||
}else if( optionMatch(z, "append") ){
|
||||
p->openMode = SHELL_OPEN_APPENDVFS;
|
||||
openMode = SHELL_OPEN_APPENDVFS;
|
||||
}else if( optionMatch(z, "readonly") ){
|
||||
p->openMode = SHELL_OPEN_READONLY;
|
||||
openMode = SHELL_OPEN_READONLY;
|
||||
}else if( optionMatch(z, "nofollow") ){
|
||||
p->openFlags |= SQLITE_OPEN_NOFOLLOW;
|
||||
#ifndef SQLITE_OMIT_DESERIALIZE
|
||||
}else if( optionMatch(z, "deserialize") ){
|
||||
p->openMode = SHELL_OPEN_DESERIALIZE;
|
||||
openMode = SHELL_OPEN_DESERIALIZE;
|
||||
}else if( optionMatch(z, "hexdb") ){
|
||||
p->openMode = SHELL_OPEN_HEXDB;
|
||||
openMode = SHELL_OPEN_HEXDB;
|
||||
}else if( optionMatch(z, "maxsize") && iName+1<nArg ){
|
||||
p->szMax = integerValue(azArg[++iName]);
|
||||
#endif /* SQLITE_OMIT_DESERIALIZE */
|
||||
@@ -8986,6 +8978,18 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
zNewFilename = sqlite3_mprintf("%s", z);
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the existing database */
|
||||
session_close_all(p, -1);
|
||||
close_db(p->db);
|
||||
p->db = 0;
|
||||
p->pAuxDb->zDbFilename = 0;
|
||||
sqlite3_free(p->pAuxDb->zFreeOnClose);
|
||||
p->pAuxDb->zFreeOnClose = 0;
|
||||
p->openMode = openMode;
|
||||
p->openFlags = 0;
|
||||
p->szMax = 0;
|
||||
|
||||
/* If a filename is specified, try to open it first */
|
||||
if( zNewFilename || p->openMode==SHELL_OPEN_HEXDB ){
|
||||
if( newFlag && !p->bSafeMode ) shellDeleteFile(zNewFilename);
|
||||
@@ -10204,30 +10208,31 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
static const struct {
|
||||
const char *zCtrlName; /* Name of a test-control option */
|
||||
int ctrlCode; /* Integer code for that option */
|
||||
int unSafe; /* Not valid for --safe mode */
|
||||
const char *zUsage; /* Usage notes */
|
||||
} aCtrl[] = {
|
||||
{ "always", SQLITE_TESTCTRL_ALWAYS, "BOOLEAN" },
|
||||
{ "assert", SQLITE_TESTCTRL_ASSERT, "BOOLEAN" },
|
||||
/*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS, "" },*/
|
||||
/*{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, "" },*/
|
||||
{ "byteorder", SQLITE_TESTCTRL_BYTEORDER, "" },
|
||||
{ "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,"BOOLEAN" },
|
||||
/*{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, "" },*/
|
||||
{ "imposter", SQLITE_TESTCTRL_IMPOSTER, "SCHEMA ON/OFF ROOTPAGE"},
|
||||
{ "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS, "" },
|
||||
{ "localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,"BOOLEAN" },
|
||||
{ "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT, "BOOLEAN" },
|
||||
{ "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS, "DISABLE-MASK" },
|
||||
{ "always", SQLITE_TESTCTRL_ALWAYS, 1, "BOOLEAN" },
|
||||
{ "assert", SQLITE_TESTCTRL_ASSERT, 1, "BOOLEAN" },
|
||||
/*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, "" },*/
|
||||
/*{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, 1, "" },*/
|
||||
{ "byteorder", SQLITE_TESTCTRL_BYTEORDER, 0, "" },
|
||||
{ "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN" },
|
||||
/*{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, 1,"" },*/
|
||||
{ "imposter", SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"},
|
||||
{ "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,"" },
|
||||
{ "localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN" },
|
||||
{ "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN" },
|
||||
{ "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK" },
|
||||
#ifdef YYCOVERAGE
|
||||
{ "parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE, "" },
|
||||
{ "parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE,0,"" },
|
||||
#endif
|
||||
{ "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE, "OFFSET " },
|
||||
{ "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE, "" },
|
||||
{ "prng_save", SQLITE_TESTCTRL_PRNG_SAVE, "" },
|
||||
{ "prng_seed", SQLITE_TESTCTRL_PRNG_SEED, "SEED ?db?" },
|
||||
{ "seek_count", SQLITE_TESTCTRL_SEEK_COUNT, "" },
|
||||
{ "sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, "NMAX" },
|
||||
{ "tune", SQLITE_TESTCTRL_TUNE, "ID VALUE" },
|
||||
{ "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET " },
|
||||
{ "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE,0, "" },
|
||||
{ "prng_save", SQLITE_TESTCTRL_PRNG_SAVE, 0, "" },
|
||||
{ "prng_seed", SQLITE_TESTCTRL_PRNG_SEED, 0, "SEED ?db?" },
|
||||
{ "seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" },
|
||||
{ "sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" },
|
||||
{ "tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" },
|
||||
};
|
||||
int testctrl = -1;
|
||||
int iCtrl = -1;
|
||||
@@ -10275,6 +10280,11 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
if( testctrl<0 ){
|
||||
utf8_printf(stderr,"Error: unknown test-control: %s\n"
|
||||
"Use \".testctrl --help\" for help\n", zCmd);
|
||||
}else if( aCtrl[iCtrl].unSafe && p->bSafeMode ){
|
||||
utf8_printf(stderr,
|
||||
"line %d: \".testctrl %s\" may not be used in safe mode\n",
|
||||
p->lineno, aCtrl[iCtrl].zCtrlName);
|
||||
exit(1);
|
||||
}else{
|
||||
switch(testctrl){
|
||||
|
||||
|
||||
@@ -9932,6 +9932,31 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
|
||||
*/
|
||||
SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Wal related information regarding the most recent COMMIT
|
||||
** EXPERIMENTAL
|
||||
**
|
||||
** This function reports on the state of the wal file (if any) for database
|
||||
** zDb, which should be "main", "temp", or the name of the attached database.
|
||||
** Its results - the values written to the output parameters - are only
|
||||
** defined if the most recent SQL command on the connection was a successful
|
||||
** COMMIT that wrote data to wal-mode database zDb.
|
||||
**
|
||||
** Assuming the above conditions are met, output parameter (*pnFrame) is set
|
||||
** to the total number of frames in the wal file. Parameter (*pnPrior) is
|
||||
** set to the number of frames that were present in the wal file before the
|
||||
** most recent transaction was committed. So that the number of frames written
|
||||
** by the most recent transaction is (*pnFrame)-(*pnPrior).
|
||||
**
|
||||
** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code. It
|
||||
** is not an error if this function is called at a time when the results
|
||||
** are undefined.
|
||||
*/
|
||||
SQLITE_EXPERIMENTAL int sqlite3_wal_info(
|
||||
sqlite3 *db, const char *zDb,
|
||||
unsigned int *pnPrior, unsigned int *pnFrame
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Serialize a database
|
||||
**
|
||||
|
||||
+27
-2
@@ -1166,6 +1166,7 @@ typedef struct DbFixer DbFixer;
|
||||
typedef struct Schema Schema;
|
||||
typedef struct Expr Expr;
|
||||
typedef struct ExprList ExprList;
|
||||
typedef struct FastPrng FastPrng;
|
||||
typedef struct FKey FKey;
|
||||
typedef struct FuncDestructor FuncDestructor;
|
||||
typedef struct FuncDef FuncDef;
|
||||
@@ -1287,6 +1288,14 @@ typedef int VList;
|
||||
# define SQLITE_DEFAULT_WAL_SYNCHRONOUS SQLITE_DEFAULT_SYNCHRONOUS
|
||||
#endif
|
||||
|
||||
/*
|
||||
** State of a simple PRNG used for the per-connection and per-pager
|
||||
** pseudo-random number generators.
|
||||
*/
|
||||
struct FastPrng {
|
||||
unsigned int x, y;
|
||||
};
|
||||
|
||||
/*
|
||||
** Each database file to be accessed by the system is an instance
|
||||
** of the following structure. There are normally two of these structures
|
||||
@@ -1532,6 +1541,7 @@ struct sqlite3 {
|
||||
u32 dbOptFlags; /* Flags to enable/disable optimizations */
|
||||
u8 enc; /* Text encoding */
|
||||
u8 autoCommit; /* The auto-commit flag. */
|
||||
u8 eConcurrent; /* CONCURRENT_* value */
|
||||
u8 temp_store; /* 1: file 2: memory 0: default */
|
||||
u8 mallocFailed; /* True if we have seen a malloc failure */
|
||||
u8 bBenignMalloc; /* Do not require OOMs if true */
|
||||
@@ -1545,6 +1555,7 @@ struct sqlite3 {
|
||||
u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */
|
||||
u8 eOpenState; /* Current condition of the connection */
|
||||
int nextPagesize; /* Pagesize after VACUUM if >0 */
|
||||
FastPrng sPrng; /* State of the per-connection PRNG */
|
||||
i64 nChange; /* Value returned by sqlite3_changes() */
|
||||
i64 nTotalChange; /* Value returned by sqlite3_total_changes() */
|
||||
int aLimit[SQLITE_N_LIMIT]; /* Limits */
|
||||
@@ -1654,6 +1665,13 @@ struct sqlite3 {
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
** Candidate values for sqlite3.eConcurrent
|
||||
*/
|
||||
#define CONCURRENT_NONE 0
|
||||
#define CONCURRENT_OPEN 1
|
||||
#define CONCURRENT_SCHEMA 2
|
||||
|
||||
/*
|
||||
** A macro to discover the encoding of a database.
|
||||
*/
|
||||
@@ -1714,6 +1732,9 @@ struct sqlite3 {
|
||||
/* the count using a callback. */
|
||||
#define SQLITE_CorruptRdOnly HI(0x00002) /* Prohibit writes due to error */
|
||||
|
||||
/* Flags used by the Pragma noop_update enhancement */
|
||||
#define SQLITE_NoopUpdate HI(0x0001000) /* UPDATE operations are no-ops */
|
||||
|
||||
/* Flags used only if debugging */
|
||||
#ifdef SQLITE_DEBUG
|
||||
#define SQLITE_SqlTrace HI(0x0100000) /* Debug print SQL as it executes */
|
||||
@@ -3555,6 +3576,8 @@ struct Parse {
|
||||
AutoincInfo *pAinc; /* Information about AUTOINCREMENT counters */
|
||||
Parse *pToplevel; /* Parse structure for main program (or NULL) */
|
||||
Table *pTriggerTab; /* Table triggers are being coded for */
|
||||
TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */
|
||||
ParseCleanup *pCleanup; /* List of cleanup operations to run after parse */
|
||||
union {
|
||||
int addrCrTab; /* Address of OP_CreateBtree on CREATE TABLE */
|
||||
Returning *pReturning; /* The RETURNING clause */
|
||||
@@ -3609,9 +3632,7 @@ struct Parse {
|
||||
Token sArg; /* Complete text of a module argument */
|
||||
Table **apVtabLock; /* Pointer to virtual tables needing locking */
|
||||
#endif
|
||||
TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */
|
||||
With *pWith; /* Current WITH clause, or NULL */
|
||||
ParseCleanup *pCleanup; /* List of cleanup operations to run after parse */
|
||||
#ifndef SQLITE_OMIT_ALTERTABLE
|
||||
RenameToken *pRename; /* Tokens subject to renaming by ALTER TABLE */
|
||||
#endif
|
||||
@@ -4629,6 +4650,8 @@ Vdbe *sqlite3GetVdbe(Parse*);
|
||||
void sqlite3PrngSaveState(void);
|
||||
void sqlite3PrngRestoreState(void);
|
||||
#endif
|
||||
void sqlite3FastPrngInit(FastPrng*);
|
||||
void sqlite3FastRandomness(FastPrng*, int N, void *P);
|
||||
void sqlite3RollbackAll(sqlite3*,int);
|
||||
void sqlite3CodeVerifySchema(Parse*, int);
|
||||
void sqlite3CodeVerifyNamedSchema(Parse*, const char *zDb);
|
||||
@@ -5145,6 +5168,7 @@ const char *sqlite3JournalModename(int);
|
||||
int sqlite3FkRequired(Parse*, Table*, int*, int);
|
||||
u32 sqlite3FkOldmask(Parse*, Table*);
|
||||
FKey *sqlite3FkReferences(Table *);
|
||||
void sqlite3FkClearTriggerCache(sqlite3*,int);
|
||||
#else
|
||||
#define sqlite3FkActions(a,b,c,d,e,f)
|
||||
#define sqlite3FkCheck(a,b,c,d,e,f)
|
||||
@@ -5152,6 +5176,7 @@ const char *sqlite3JournalModename(int);
|
||||
#define sqlite3FkOldmask(a,b) 0
|
||||
#define sqlite3FkRequired(a,b,c,d) 0
|
||||
#define sqlite3FkReferences(a) 0
|
||||
#define sqlite3FkClearTriggerCache(a,b)
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_FOREIGN_KEY
|
||||
void sqlite3FkDelete(sqlite3 *, Table*);
|
||||
|
||||
+38
-2
@@ -8051,6 +8051,41 @@ static int SQLITE_TCLAPI test_dbconfig_maindbname_icecube(
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Usage: sqlite3_wal_info DB DBNAME
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_wal_info(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int rc;
|
||||
sqlite3 *db;
|
||||
char *zName;
|
||||
unsigned int nPrior;
|
||||
unsigned int nFrame;
|
||||
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
|
||||
zName = Tcl_GetString(objv[2]);
|
||||
|
||||
rc = sqlite3_wal_info(db, zName, &nPrior, &nFrame);
|
||||
if( rc!=SQLITE_OK ){
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
return TCL_ERROR;
|
||||
}else{
|
||||
Tcl_Obj *pNew = Tcl_NewObj();
|
||||
Tcl_ListObjAppendElement(interp, pNew, Tcl_NewWideIntObj((i64)nPrior));
|
||||
Tcl_ListObjAppendElement(interp, pNew, Tcl_NewWideIntObj((i64)nFrame));
|
||||
Tcl_SetObjResult(interp, pNew);
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Usage: sqlite3_mmap_warm DB DBNAME
|
||||
*/
|
||||
@@ -8611,8 +8646,9 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
|
||||
{ "sqlite3_snapshot_open_blob", test_snapshot_open_blob, 0 },
|
||||
{ "sqlite3_snapshot_cmp_blob", test_snapshot_cmp_blob, 0 },
|
||||
#endif
|
||||
{ "sqlite3_delete_database", test_delete_database, 0 },
|
||||
{ "atomic_batch_write", test_atomic_batch_write, 0 },
|
||||
{ "sqlite3_delete_database", test_delete_database, 0 },
|
||||
{ "sqlite3_wal_info", test_wal_info, 0 },
|
||||
{ "atomic_batch_write", test_atomic_batch_write, 0 },
|
||||
{ "sqlite3_mmap_warm", test_mmap_warm, 0 },
|
||||
{ "sqlite3_config_sorterref", test_config_sorterref, 0 },
|
||||
{ "sqlite3_autovacuum_pages", test_autovacuum_pages, 0 },
|
||||
|
||||
@@ -681,6 +681,12 @@ Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY);
|
||||
Tcl_SetVar2(interp, "sqlite_options", "truncate_opt", "1", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
Tcl_SetVar2(interp, "sqlite_options", "concurrent", "1", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
Tcl_SetVar2(interp, "sqlite_options", "concurrent", "0", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_OMIT_UTF16
|
||||
Tcl_SetVar2(interp, "sqlite_options", "utf16", "0", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
|
||||
+8
-5
@@ -933,7 +933,7 @@ static void codeReturningTrigger(
|
||||
}
|
||||
sqlite3ExprListDelete(db, sSelect.pEList);
|
||||
pNew = sqlite3ExpandReturning(pParse, pReturning->pReturnEL, pTab);
|
||||
if( pNew ){
|
||||
if( !db->mallocFailed ){
|
||||
NameContext sNC;
|
||||
memset(&sNC, 0, sizeof(sNC));
|
||||
if( pReturning->nRetCol==0 ){
|
||||
@@ -945,7 +945,9 @@ static void codeReturningTrigger(
|
||||
sNC.ncFlags = NC_UBaseReg;
|
||||
pParse->eTriggerOp = pTrigger->op;
|
||||
pParse->pTriggerTab = pTab;
|
||||
if( sqlite3ResolveExprListNames(&sNC, pNew)==SQLITE_OK ){
|
||||
if( sqlite3ResolveExprListNames(&sNC, pNew)==SQLITE_OK
|
||||
&& !db->mallocFailed
|
||||
){
|
||||
int i;
|
||||
int nCol = pNew->nExpr;
|
||||
int reg = pParse->nMem+1;
|
||||
@@ -953,16 +955,17 @@ static void codeReturningTrigger(
|
||||
pReturning->iRetReg = reg;
|
||||
for(i=0; i<nCol; i++){
|
||||
Expr *pCol = pNew->a[i].pExpr;
|
||||
assert( pCol!=0 ); /* Due to !db->mallocFailed ~9 lines above */
|
||||
sqlite3ExprCodeFactorable(pParse, pCol, reg+i);
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, i, reg+i);
|
||||
sqlite3VdbeAddOp2(v, OP_NewRowid, pReturning->iRetCur, reg+i+1);
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, pReturning->iRetCur, reg+i, reg+i+1);
|
||||
}
|
||||
sqlite3ExprListDelete(db, pNew);
|
||||
pParse->eTriggerOp = 0;
|
||||
pParse->pTriggerTab = 0;
|
||||
}
|
||||
sqlite3ExprListDelete(db, pNew);
|
||||
pParse->eTriggerOp = 0;
|
||||
pParse->pTriggerTab = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -451,6 +451,17 @@ void sqlite3Update(
|
||||
*/
|
||||
chngRowid = chngPk = 0;
|
||||
for(i=0; i<pChanges->nExpr; i++){
|
||||
#if defined(SQLITE_ENABLE_NOOP_UPDATE) && !defined(SQLITE_OMIT_FLAG_PRAGMAS)
|
||||
if( db->flags & SQLITE_NoopUpdate ){
|
||||
Token x;
|
||||
sqlite3ExprDelete(db, pChanges->a[i].pExpr);
|
||||
x.z = pChanges->a[i].zEName;
|
||||
x.n = sqlite3Strlen30(x.z);
|
||||
pChanges->a[i].pExpr =
|
||||
sqlite3PExpr(pParse, TK_UPLUS, sqlite3ExprAlloc(db, TK_ID, &x, 0), 0);
|
||||
if( db->mallocFailed ) goto update_cleanup;
|
||||
}
|
||||
#endif
|
||||
u8 hCol = sqlite3StrIHash(pChanges->a[i].zEName);
|
||||
/* If this is an UPDATE with a FROM clause, do not resolve expressions
|
||||
** here. The call to sqlite3Select() below will do that. */
|
||||
|
||||
@@ -389,6 +389,7 @@ end_of_vacuum:
|
||||
** is closed by the DETACH.
|
||||
*/
|
||||
db->autoCommit = 1;
|
||||
assert( db->eConcurrent==0 );
|
||||
|
||||
if( pDb ){
|
||||
sqlite3BtreeClose(pDb->pBt);
|
||||
|
||||
+49
-7
@@ -3474,6 +3474,7 @@ case OP_Savepoint: {
|
||||
** is committed.
|
||||
*/
|
||||
int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint;
|
||||
assert( db->eConcurrent==0 || db->isTransactionSavepoint==0 );
|
||||
if( isTransaction && p1==SAVEPOINT_RELEASE ){
|
||||
if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){
|
||||
goto vdbe_return;
|
||||
@@ -3557,23 +3558,31 @@ case OP_Savepoint: {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: AutoCommit P1 P2 * * *
|
||||
/* Opcode: AutoCommit P1 P2 P3 * *
|
||||
**
|
||||
** Set the database auto-commit flag to P1 (1 or 0). If P2 is true, roll
|
||||
** back any currently active btree transactions. If there are any active
|
||||
** VMs (apart from this one), then a ROLLBACK fails. A COMMIT fails if
|
||||
** there are active writing VMs or active VMs that use shared cache.
|
||||
**
|
||||
** If P3 is non-zero, then this instruction is being executed as part of
|
||||
** a "BEGIN CONCURRENT" command.
|
||||
**
|
||||
** This instruction causes the VM to halt.
|
||||
*/
|
||||
case OP_AutoCommit: {
|
||||
int desiredAutoCommit;
|
||||
int iRollback;
|
||||
int bConcurrent;
|
||||
int hrc;
|
||||
|
||||
desiredAutoCommit = pOp->p1;
|
||||
iRollback = pOp->p2;
|
||||
bConcurrent = pOp->p3;
|
||||
assert( desiredAutoCommit==1 || desiredAutoCommit==0 );
|
||||
assert( desiredAutoCommit==1 || iRollback==0 );
|
||||
assert( desiredAutoCommit==0 || bConcurrent==0 );
|
||||
assert( db->autoCommit==0 || db->eConcurrent==CONCURRENT_NONE );
|
||||
assert( db->nVdbeActive>0 ); /* At least this one VM is active */
|
||||
assert( p->bIsReader );
|
||||
|
||||
@@ -3582,10 +3591,17 @@ case OP_AutoCommit: {
|
||||
assert( desiredAutoCommit==1 );
|
||||
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
|
||||
db->autoCommit = 1;
|
||||
}else if( desiredAutoCommit && db->nVdbeWrite>0 ){
|
||||
/* If this instruction implements a COMMIT and other VMs are writing
|
||||
** return an error indicating that the other VMs must complete first.
|
||||
*/
|
||||
db->eConcurrent = CONCURRENT_NONE;
|
||||
}else if( desiredAutoCommit
|
||||
&& (db->nVdbeWrite>0 || (db->eConcurrent && db->nVdbeActive>1)) ){
|
||||
/* A transaction may only be committed if there are no other active
|
||||
** writer VMs. If the transaction is CONCURRENT, then it may only be
|
||||
** committed if there are no active VMs at all (readers or writers).
|
||||
**
|
||||
** If this instruction is a COMMIT and the transaction may not be
|
||||
** committed due to one of the conditions above, return an error
|
||||
** indicating that other VMs must complete before the COMMIT can
|
||||
** be processed. */
|
||||
sqlite3VdbeError(p, "cannot commit transaction - "
|
||||
"SQL statements in progress");
|
||||
rc = SQLITE_BUSY;
|
||||
@@ -3595,12 +3611,16 @@ case OP_AutoCommit: {
|
||||
}else{
|
||||
db->autoCommit = (u8)desiredAutoCommit;
|
||||
}
|
||||
if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){
|
||||
hrc = sqlite3VdbeHalt(p);
|
||||
if( (hrc & 0xFF)==SQLITE_BUSY ){
|
||||
p->pc = (int)(pOp - aOp);
|
||||
db->autoCommit = (u8)(1-desiredAutoCommit);
|
||||
p->rc = rc = SQLITE_BUSY;
|
||||
p->rc = hrc;
|
||||
rc = SQLITE_BUSY;
|
||||
goto vdbe_return;
|
||||
}
|
||||
assert( bConcurrent==CONCURRENT_NONE || bConcurrent==CONCURRENT_OPEN );
|
||||
db->eConcurrent = (u8)bConcurrent;
|
||||
sqlite3CloseSavepoints(db);
|
||||
if( p->rc==SQLITE_OK ){
|
||||
rc = SQLITE_DONE;
|
||||
@@ -3807,12 +3827,24 @@ case OP_SetCookie: {
|
||||
pDb = &db->aDb[pOp->p1];
|
||||
assert( pDb->pBt!=0 );
|
||||
assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) );
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
if( db->eConcurrent
|
||||
&& (pOp->p2==BTREE_USER_VERSION || pOp->p2==BTREE_APPLICATION_ID)
|
||||
){
|
||||
rc = SQLITE_ERROR;
|
||||
sqlite3VdbeError(p, "cannot modify %s within CONCURRENT transaction",
|
||||
pOp->p2==BTREE_USER_VERSION ? "user_version" : "application_id"
|
||||
);
|
||||
goto abort_due_to_error;
|
||||
}
|
||||
#endif
|
||||
/* See note about index shifting on OP_ReadCookie */
|
||||
rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, pOp->p3);
|
||||
if( pOp->p2==BTREE_SCHEMA_VERSION ){
|
||||
/* When the schema cookie changes, record the new cookie internally */
|
||||
pDb->pSchema->schema_cookie = pOp->p3 - pOp->p5;
|
||||
db->mDbFlags |= DBFLAG_SchemaChange;
|
||||
sqlite3FkClearTriggerCache(db, pOp->p1);
|
||||
}else if( pOp->p2==BTREE_FILE_FORMAT ){
|
||||
/* Record changes in the file format */
|
||||
pDb->pSchema->file_format = pOp->p3;
|
||||
@@ -3955,6 +3987,11 @@ case OP_OpenWrite:
|
||||
pX = pDb->pBt;
|
||||
assert( pX!=0 );
|
||||
if( pOp->opcode==OP_OpenWrite ){
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
if( db->eConcurrent==CONCURRENT_OPEN && p2==1 && iDb!=1 ){
|
||||
db->eConcurrent = CONCURRENT_SCHEMA;
|
||||
}
|
||||
#endif
|
||||
assert( OPFLAG_FORDELETE==BTREE_FORDELETE );
|
||||
wrFlag = BTREE_WRCSR | (pOp->p5 & OPFLAG_FORDELETE);
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
@@ -7578,6 +7615,11 @@ case OP_CursorUnlock: {
|
||||
*/
|
||||
case OP_TableLock: {
|
||||
u8 isWriteLock = (u8)pOp->p3;
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
if( isWriteLock && db->eConcurrent && pOp->p2==1 && pOp->p1!=1 ){
|
||||
db->eConcurrent = CONCURRENT_SCHEMA;
|
||||
}
|
||||
#endif
|
||||
if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommit) ){
|
||||
int p1 = pOp->p1;
|
||||
assert( p1>=0 && p1<db->nDb );
|
||||
|
||||
+24
-4
@@ -2682,10 +2682,27 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
|
||||
assert( i!=1 );
|
||||
nTrans++;
|
||||
}
|
||||
rc = sqlite3PagerExclusiveLock(pPager);
|
||||
rc = sqlite3BtreeExclusiveLock(pBt);
|
||||
sqlite3BtreeLeave(pBt);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
if( db->eConcurrent && (rc & 0xFF)==SQLITE_BUSY ){
|
||||
/* An SQLITE_BUSY or SQLITE_BUSY_SNAPSHOT was encountered while
|
||||
** attempting to take the WRITER lock on a wal file. Release the
|
||||
** WRITER locks on all wal files and return early. */
|
||||
for(i=0; i<db->nDb; i++){
|
||||
Btree *pBt = db->aDb[i].pBt;
|
||||
if( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ){
|
||||
sqlite3BtreeEnter(pBt);
|
||||
sqlite3PagerDropExclusiveLock(sqlite3BtreePager(pBt));
|
||||
sqlite3BtreeLeave(pBt);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
@@ -3088,6 +3105,7 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
|
||||
sqlite3CloseSavepoints(db);
|
||||
db->autoCommit = 1;
|
||||
db->eConcurrent = CONCURRENT_NONE;
|
||||
p->nChange = 0;
|
||||
}
|
||||
}
|
||||
@@ -3126,9 +3144,9 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
** is required. */
|
||||
rc = vdbeCommit(db, p);
|
||||
}
|
||||
if( rc==SQLITE_BUSY && p->readOnly ){
|
||||
if( (rc & 0xFF)==SQLITE_BUSY && p->readOnly ){
|
||||
sqlite3VdbeLeave(p);
|
||||
return SQLITE_BUSY;
|
||||
return rc;
|
||||
}else if( rc!=SQLITE_OK ){
|
||||
p->rc = rc;
|
||||
sqlite3RollbackAll(db, SQLITE_OK);
|
||||
@@ -3153,6 +3171,7 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
|
||||
sqlite3CloseSavepoints(db);
|
||||
db->autoCommit = 1;
|
||||
db->eConcurrent = CONCURRENT_NONE;
|
||||
p->nChange = 0;
|
||||
}
|
||||
}
|
||||
@@ -3174,6 +3193,7 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
|
||||
sqlite3CloseSavepoints(db);
|
||||
db->autoCommit = 1;
|
||||
db->eConcurrent = CONCURRENT_NONE;
|
||||
p->nChange = 0;
|
||||
}
|
||||
}
|
||||
@@ -4934,7 +4954,7 @@ int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){
|
||||
/* The index entry must begin with a header size */
|
||||
getVarint32NR((u8*)m.z, szHdr);
|
||||
testcase( szHdr==3 );
|
||||
testcase( szHdr==m.n );
|
||||
testcase( szHdr==(u32)m.n );
|
||||
testcase( szHdr>0x7fffffff );
|
||||
assert( m.n>=0 );
|
||||
if( unlikely(szHdr<3 || szHdr>(unsigned)m.n) ){
|
||||
|
||||
@@ -526,8 +526,10 @@ struct Wal {
|
||||
WalIndexHdr hdr; /* Wal-index header for current transaction */
|
||||
u32 minFrame; /* Ignore wal frames before this one */
|
||||
u32 iReCksum; /* On commit, recalculate checksums from here */
|
||||
u32 nPriorFrame; /* For sqlite3WalInfo() */
|
||||
const char *zWalName; /* Name of WAL file */
|
||||
u32 nCkpt; /* Checkpoint sequence counter in the wal-header */
|
||||
FastPrng sPrng; /* Random number generator */
|
||||
#ifdef SQLITE_DEBUG
|
||||
u8 lockError; /* True if a locking error has occurred */
|
||||
#endif
|
||||
@@ -925,7 +927,7 @@ static const char *walLockName(int lockIdx){
|
||||
/*
|
||||
** Set or release locks on the WAL. Locks are either shared or exclusive.
|
||||
** A lock cannot be moved directly between shared and exclusive - it must go
|
||||
** through the unlocked state first.
|
||||
** through the concurrent state first.
|
||||
**
|
||||
** In locking_mode=EXCLUSIVE, all of these routines become no-ops.
|
||||
*/
|
||||
@@ -1228,7 +1230,7 @@ static int walIndexRecover(Wal *pWal){
|
||||
/* Obtain an exclusive lock on all byte in the locking range not already
|
||||
** locked by the caller. The caller is guaranteed to have locked the
|
||||
** WAL_WRITE_LOCK byte, and may have also locked the WAL_CKPT_LOCK byte.
|
||||
** If successful, the same bytes that are locked here are unlocked before
|
||||
** If successful, the same bytes that are locked here are concurrent before
|
||||
** this function returns.
|
||||
*/
|
||||
assert( pWal->ckptLock==1 || pWal->ckptLock==0 );
|
||||
@@ -1546,6 +1548,7 @@ int sqlite3WalOpen(
|
||||
pRet->syncHeader = 1;
|
||||
pRet->padToSectorBoundary = 1;
|
||||
pRet->exclusiveMode = (bNoShm ? WAL_HEAPMEMORY_MODE: WAL_NORMAL_MODE);
|
||||
sqlite3FastPrngInit(&pRet->sPrng);
|
||||
|
||||
/* Open file handle on the write-ahead log file. */
|
||||
flags = (SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_WAL);
|
||||
@@ -2183,7 +2186,7 @@ static int walCheckpoint(
|
||||
rc = SQLITE_BUSY;
|
||||
}else if( eMode>=SQLITE_CHECKPOINT_RESTART ){
|
||||
u32 salt1;
|
||||
sqlite3_randomness(4, &salt1);
|
||||
sqlite3FastRandomness(&pWal->sPrng, 4, &salt1);
|
||||
assert( pInfo->nBackfill==pWal->hdr.mxFrame );
|
||||
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(1), WAL_NREADER-1);
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -2299,6 +2302,49 @@ int sqlite3WalClose(
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Try to copy the wal-index header from shared-memory into (*pHdr). Return
|
||||
** zero if successful or non-zero otherwise. If the header is corrupted
|
||||
** (either because the two copies are inconsistent or because the checksum
|
||||
** values are incorrect), the read fails and non-zero is returned.
|
||||
*/
|
||||
static int walIndexLoadHdr(Wal *pWal, WalIndexHdr *pHdr){
|
||||
u32 aCksum[2]; /* Checksum on the header content */
|
||||
WalIndexHdr h2; /* Second copy of the header content */
|
||||
WalIndexHdr volatile *aHdr; /* Header in shared memory */
|
||||
|
||||
/* The first page of the wal-index must be mapped at this point. */
|
||||
assert( pWal->nWiData>0 && pWal->apWiData[0] );
|
||||
|
||||
/* Read the header. This might happen concurrently with a write to the
|
||||
** same area of shared memory on a different CPU in a SMP,
|
||||
** meaning it is possible that an inconsistent snapshot is read
|
||||
** from the file. If this happens, return non-zero.
|
||||
**
|
||||
** There are two copies of the header at the beginning of the wal-index.
|
||||
** When reading, read [0] first then [1]. Writes are in the reverse order.
|
||||
** Memory barriers are used to prevent the compiler or the hardware from
|
||||
** reordering the reads and writes.
|
||||
*/
|
||||
aHdr = walIndexHdr(pWal);
|
||||
memcpy(pHdr, (void *)&aHdr[0], sizeof(h2));
|
||||
walShmBarrier(pWal);
|
||||
memcpy(&h2, (void *)&aHdr[1], sizeof(h2));
|
||||
|
||||
if( memcmp(&h2, pHdr, sizeof(h2))!=0 ){
|
||||
return 1; /* Dirty read */
|
||||
}
|
||||
if( h2.isInit==0 ){
|
||||
return 1; /* Malformed header - probably all zeros */
|
||||
}
|
||||
walChecksumBytes(1, (u8*)&h2, sizeof(h2)-sizeof(h2.aCksum), 0, aCksum);
|
||||
if( aCksum[0]!=h2.aCksum[0] || aCksum[1]!=h2.aCksum[1] ){
|
||||
return 1; /* Checksum does not match */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Try to read the wal-index header. Return 0 on success and 1 if
|
||||
** there is a problem.
|
||||
@@ -2317,43 +2363,10 @@ int sqlite3WalClose(
|
||||
** is read successfully and the checksum verified, return zero.
|
||||
*/
|
||||
static SQLITE_NO_TSAN int walIndexTryHdr(Wal *pWal, int *pChanged){
|
||||
u32 aCksum[2]; /* Checksum on the header content */
|
||||
WalIndexHdr h1, h2; /* Two copies of the header content */
|
||||
WalIndexHdr volatile *aHdr; /* Header in shared memory */
|
||||
WalIndexHdr h1; /* Copy of the header content */
|
||||
|
||||
/* The first page of the wal-index must be mapped at this point. */
|
||||
assert( pWal->nWiData>0 && pWal->apWiData[0] );
|
||||
|
||||
/* Read the header. This might happen concurrently with a write to the
|
||||
** same area of shared memory on a different CPU in a SMP,
|
||||
** meaning it is possible that an inconsistent snapshot is read
|
||||
** from the file. If this happens, return non-zero.
|
||||
**
|
||||
** tag-20200519-1:
|
||||
** There are two copies of the header at the beginning of the wal-index.
|
||||
** When reading, read [0] first then [1]. Writes are in the reverse order.
|
||||
** Memory barriers are used to prevent the compiler or the hardware from
|
||||
** reordering the reads and writes. TSAN and similar tools can sometimes
|
||||
** give false-positive warnings about these accesses because the tools do not
|
||||
** account for the double-read and the memory barrier. The use of mutexes
|
||||
** here would be problematic as the memory being accessed is potentially
|
||||
** shared among multiple processes and not all mutex implementions work
|
||||
** reliably in that environment.
|
||||
*/
|
||||
aHdr = walIndexHdr(pWal);
|
||||
memcpy(&h1, (void *)&aHdr[0], sizeof(h1)); /* Possible TSAN false-positive */
|
||||
walShmBarrier(pWal);
|
||||
memcpy(&h2, (void *)&aHdr[1], sizeof(h2));
|
||||
|
||||
if( memcmp(&h1, &h2, sizeof(h1))!=0 ){
|
||||
return 1; /* Dirty read */
|
||||
}
|
||||
if( h1.isInit==0 ){
|
||||
return 1; /* Malformed header - probably all zeros */
|
||||
}
|
||||
walChecksumBytes(1, (u8*)&h1, sizeof(h1)-sizeof(h1.aCksum), 0, aCksum);
|
||||
if( aCksum[0]!=h1.aCksum[0] || aCksum[1]!=h1.aCksum[1] ){
|
||||
return 1; /* Checksum does not match */
|
||||
if( walIndexLoadHdr(pWal, &h1) ){
|
||||
return 1;
|
||||
}
|
||||
|
||||
if( memcmp(&pWal->hdr, &h1, sizeof(WalIndexHdr)) ){
|
||||
@@ -3049,6 +3062,7 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
|
||||
testcase( rc==SQLITE_PROTOCOL );
|
||||
testcase( rc==SQLITE_OK );
|
||||
|
||||
pWal->nPriorFrame = pWal->hdr.mxFrame;
|
||||
#ifdef SQLITE_ENABLE_SNAPSHOT
|
||||
if( rc==SQLITE_OK ){
|
||||
if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){
|
||||
@@ -3154,8 +3168,7 @@ int sqlite3WalFindFrame(
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/* Search the hash table or tables for an entry matching page number
|
||||
** pgno. Each iteration of the following for() loop searches one
|
||||
/* Each iteration of the following for() loop searches one
|
||||
** hash table (each hash table indexes up to HASHTABLE_NPAGE frames).
|
||||
**
|
||||
** This code might run concurrently to the code in walIndexAppend()
|
||||
@@ -3261,6 +3274,35 @@ Pgno sqlite3WalDbsize(Wal *pWal){
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Take the WRITER lock on the WAL file. Return SQLITE_OK if successful,
|
||||
** or an SQLite error code otherwise. This routine does not invoke any
|
||||
** busy-handler callbacks, that is done at a higher level.
|
||||
*/
|
||||
static int walWriteLock(Wal *pWal){
|
||||
int rc;
|
||||
|
||||
/* Cannot start a write transaction without first holding a read lock */
|
||||
assert( pWal->readLock>=0 );
|
||||
assert( pWal->writeLock==0 );
|
||||
assert( pWal->iReCksum==0 );
|
||||
|
||||
/* If this is a read-only connection, obtaining a write-lock is not
|
||||
** possible. In this case return SQLITE_READONLY. Otherwise, attempt
|
||||
** to grab the WRITER lock. Set Wal.writeLock to true and return
|
||||
** SQLITE_OK if successful, or leave Wal.writeLock clear and return
|
||||
** an SQLite error code (possibly SQLITE_BUSY) otherwise. */
|
||||
if( pWal->readOnly ){
|
||||
rc = SQLITE_READONLY;
|
||||
}else{
|
||||
rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1);
|
||||
if( rc==SQLITE_OK ){
|
||||
pWal->writeLock = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function starts a write transaction on the WAL.
|
||||
@@ -3287,38 +3329,209 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Cannot start a write transaction without first holding a read
|
||||
** transaction. */
|
||||
assert( pWal->readLock>=0 );
|
||||
assert( pWal->writeLock==0 && pWal->iReCksum==0 );
|
||||
|
||||
if( pWal->readOnly ){
|
||||
return SQLITE_READONLY;
|
||||
|
||||
rc = walWriteLock(pWal);
|
||||
if( rc==SQLITE_OK ){
|
||||
/* If another connection has written to the database file since the
|
||||
** time the read transaction on this connection was started, then
|
||||
** the write is disallowed. Release the WRITER lock and return
|
||||
** SQLITE_BUSY_SNAPSHOT in this case. */
|
||||
if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){
|
||||
walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
|
||||
pWal->writeLock = 0;
|
||||
rc = SQLITE_BUSY_SNAPSHOT;
|
||||
}
|
||||
}
|
||||
|
||||
/* Only one writer allowed at a time. Get the write lock. Return
|
||||
** SQLITE_BUSY if unable.
|
||||
*/
|
||||
rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1);
|
||||
if( rc ){
|
||||
return rc;
|
||||
}
|
||||
pWal->writeLock = 1;
|
||||
|
||||
/* If another connection has written to the database file since the
|
||||
** time the read transaction on this connection was started, then
|
||||
** the write is disallowed.
|
||||
*/
|
||||
if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){
|
||||
walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
|
||||
pWal->writeLock = 0;
|
||||
rc = SQLITE_BUSY_SNAPSHOT;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is called by a writer that has a read-lock on aReadmark[0]
|
||||
** (pWal->readLock==0). This function relinquishes that lock and takes a
|
||||
** lock on a different aReadmark[] slot.
|
||||
**
|
||||
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
|
||||
*/
|
||||
static int walUpgradeReadlock(Wal *pWal){
|
||||
int cnt;
|
||||
int rc;
|
||||
assert( pWal->writeLock && pWal->readLock==0 );
|
||||
walUnlockShared(pWal, WAL_READ_LOCK(0));
|
||||
pWal->readLock = -1;
|
||||
cnt = 0;
|
||||
do{
|
||||
int notUsed;
|
||||
rc = walTryBeginRead(pWal, ¬Used, 1, ++cnt);
|
||||
}while( rc==WAL_RETRY );
|
||||
assert( (rc&0xff)!=SQLITE_BUSY ); /* BUSY not possible when useWal==1 */
|
||||
testcase( (rc&0xff)==SQLITE_IOERR );
|
||||
testcase( rc==SQLITE_PROTOCOL );
|
||||
testcase( rc==SQLITE_OK );
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/*
|
||||
** This function is only ever called when committing a "BEGIN CONCURRENT"
|
||||
** transaction. It may be assumed that no frames have been written to
|
||||
** the wal file. The second parameter is a pointer to the in-memory
|
||||
** representation of page 1 of the database (which may or may not be
|
||||
** dirty). The third is a bitvec with a bit set for each page in the
|
||||
** database file that was read by the current concurrent transaction.
|
||||
**
|
||||
** This function performs three tasks:
|
||||
**
|
||||
** 1) It obtains the WRITER lock on the wal file,
|
||||
**
|
||||
** 2) It checks that there are no conflicts between the current
|
||||
** transaction and any transactions committed to the wal file since
|
||||
** it was opened, and
|
||||
**
|
||||
** 3) It ejects any non-dirty pages from the page-cache that have been
|
||||
** written by another client since the CONCURRENT transaction was started
|
||||
** (so as to avoid ending up with an inconsistent cache after the
|
||||
** current transaction is committed).
|
||||
**
|
||||
** If no error occurs and the caller may proceed with committing the
|
||||
** transaction, SQLITE_OK is returned. SQLITE_BUSY is returned if the WRITER
|
||||
** lock cannot be obtained. Or, if the WRITER lock can be obtained but there
|
||||
** are conflicts with a committed transaction, SQLITE_BUSY_SNAPSHOT. Finally,
|
||||
** if an error (i.e. an OOM condition or IO error), an SQLite error code
|
||||
** is returned.
|
||||
*/
|
||||
int sqlite3WalLockForCommit(
|
||||
Wal *pWal,
|
||||
PgHdr *pPg1,
|
||||
Bitvec *pAllRead,
|
||||
Pgno *piConflict
|
||||
){
|
||||
int rc = walWriteLock(pWal);
|
||||
|
||||
/* If the database has been modified since this transaction was started,
|
||||
** check if it is still possible to commit. The transaction can be
|
||||
** committed if:
|
||||
**
|
||||
** a) None of the pages in pList have been modified since the
|
||||
** transaction opened, and
|
||||
**
|
||||
** b) The database schema cookie has not been modified since the
|
||||
** transaction was started.
|
||||
*/
|
||||
if( rc==SQLITE_OK ){
|
||||
WalIndexHdr head;
|
||||
|
||||
if( walIndexLoadHdr(pWal, &head) ){
|
||||
/* This branch is taken if the wal-index header is corrupted. This
|
||||
** occurs if some other writer has crashed while committing a
|
||||
** transaction to this database since the current concurrent transaction
|
||||
** was opened. */
|
||||
rc = SQLITE_BUSY_SNAPSHOT;
|
||||
}else if( memcmp(&pWal->hdr, (void*)&head, sizeof(WalIndexHdr))!=0 ){
|
||||
int iHash;
|
||||
int iLast = walFramePage(head.mxFrame);
|
||||
u32 iFirst = pWal->hdr.mxFrame+1; /* First wal frame to check */
|
||||
if( memcmp(pWal->hdr.aSalt, (u32*)head.aSalt, sizeof(u32)*2) ){
|
||||
assert( pWal->readLock==0 );
|
||||
iFirst = 1;
|
||||
}
|
||||
if( pPg1==0 ){
|
||||
/* If pPg1==0, then the current transaction modified the database
|
||||
** schema. This means it conflicts with all other transactions. */
|
||||
*piConflict = 1;
|
||||
rc = SQLITE_BUSY_SNAPSHOT;
|
||||
}
|
||||
for(iHash=walFramePage(iFirst); rc==SQLITE_OK && iHash<=iLast; iHash++){
|
||||
WalHashLoc sLoc;
|
||||
|
||||
rc = walHashGet(pWal, iHash, &sLoc);
|
||||
if( rc==SQLITE_OK ){
|
||||
u32 i, iMin, iMax;
|
||||
assert( head.mxFrame>=sLoc.iZero );
|
||||
iMin = (sLoc.iZero >= iFirst) ? 1 : (iFirst - sLoc.iZero);
|
||||
iMax = (iHash==0) ? HASHTABLE_NPAGE_ONE : HASHTABLE_NPAGE;
|
||||
if( iMax>(head.mxFrame-sLoc.iZero) ) iMax = (head.mxFrame-sLoc.iZero);
|
||||
for(i=iMin; rc==SQLITE_OK && i<=iMax; i++){
|
||||
PgHdr *pPg;
|
||||
if( sLoc.aPgno[i-1]==1 ){
|
||||
/* Check that the schema cookie has not been modified. If
|
||||
** it has not, the commit can proceed. */
|
||||
u8 aNew[4];
|
||||
u8 *aOld = &((u8*)pPg1->pData)[40];
|
||||
int sz;
|
||||
i64 iOffset;
|
||||
sz = pWal->hdr.szPage;
|
||||
sz = (sz&0xfe00) + ((sz&0x0001)<<16);
|
||||
iOffset = walFrameOffset(i+sLoc.iZero, sz) + WAL_FRAME_HDRSIZE+40;
|
||||
rc = sqlite3OsRead(pWal->pWalFd, aNew, sizeof(aNew), iOffset);
|
||||
if( rc==SQLITE_OK && memcmp(aOld, aNew, sizeof(aNew)) ){
|
||||
rc = SQLITE_BUSY_SNAPSHOT;
|
||||
}
|
||||
}else if( sqlite3BitvecTestNotNull(pAllRead, sLoc.aPgno[i-1]) ){
|
||||
*piConflict = sLoc.aPgno[i-1];
|
||||
rc = SQLITE_BUSY_SNAPSHOT;
|
||||
}else
|
||||
if( (pPg = sqlite3PagerLookup(pPg1->pPager, sLoc.aPgno[i-1])) ){
|
||||
/* Page aPgno[i-1], which is present in the pager cache, has been
|
||||
** modified since the current CONCURRENT transaction was started.
|
||||
** However it was not read by the current transaction, so is not
|
||||
** a conflict. There are two possibilities: (a) the page was
|
||||
** allocated at the of the file by the current transaction or
|
||||
** (b) was present in the cache at the start of the transaction.
|
||||
**
|
||||
** For case (a), do nothing. This page will be moved within the
|
||||
** database file by the commit code to avoid the conflict. The
|
||||
** call to PagerUnref() is to release the reference grabbed by
|
||||
** the sqlite3PagerLookup() above.
|
||||
**
|
||||
** In case (b), drop the page from the cache - otherwise
|
||||
** following the snapshot upgrade the cache would be inconsistent
|
||||
** with the database as stored on disk. */
|
||||
if( sqlite3PagerIswriteable(pPg) ){
|
||||
sqlite3PagerUnref(pPg);
|
||||
}else{
|
||||
sqlite3PcacheDrop(pPg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pWal->nPriorFrame = pWal->hdr.mxFrame;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** This function is called as part of committing an CONCURRENT transaction.
|
||||
** It is assumed that sqlite3WalLockForCommit() has already been successfully
|
||||
** called and so (a) the WRITER lock is held and (b) it is known that the
|
||||
** wal-index-header stored in shared memory is not corrupt.
|
||||
**
|
||||
** Before returning, this function upgrades the client so that it is
|
||||
** operating on the database snapshot currently at the head of the wal file
|
||||
** (even if the CONCURRENT transaction ran against an older snapshot).
|
||||
**
|
||||
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
|
||||
*/
|
||||
int sqlite3WalUpgradeSnapshot(Wal *pWal){
|
||||
int rc = SQLITE_OK;
|
||||
assert( pWal->writeLock );
|
||||
memcpy(&pWal->hdr, (void*)walIndexHdr(pWal), sizeof(WalIndexHdr));
|
||||
|
||||
/* If this client has its read-lock on slot aReadmark[0] and the entire
|
||||
** wal has not been checkpointed, switch it to a different slot. Otherwise
|
||||
** any reads performed between now and committing the transaction will
|
||||
** read from the old snapshot - not the one just upgraded to. */
|
||||
if( pWal->readLock==0 && pWal->hdr.mxFrame!=walCkptInfo(pWal)->nBackfill ){
|
||||
rc = walUpgradeReadlock(pWal);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#endif /* SQLITE_OMIT_CONCURRENT */
|
||||
|
||||
/*
|
||||
** End a write transaction. The commit has already been done. This
|
||||
** routine merely releases the lock.
|
||||
@@ -3345,9 +3558,14 @@ int sqlite3WalEndWriteTransaction(Wal *pWal){
|
||||
** Otherwise, if the callback function does not return an error, this
|
||||
** function returns SQLITE_OK.
|
||||
*/
|
||||
int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){
|
||||
int sqlite3WalUndo(
|
||||
Wal *pWal,
|
||||
int (*xUndo)(void *, Pgno),
|
||||
void *pUndoCtx,
|
||||
int bConcurrent /* True if this is a CONCURRENT transaction */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
if( ALWAYS(pWal->writeLock) ){
|
||||
if( pWal->writeLock ){
|
||||
Pgno iMax = pWal->hdr.mxFrame;
|
||||
Pgno iFrame;
|
||||
|
||||
@@ -3355,6 +3573,13 @@ int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){
|
||||
** was in before the client began writing to the database.
|
||||
*/
|
||||
memcpy(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr));
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
if( bConcurrent ){
|
||||
pWal->hdr.aCksum[0]++;
|
||||
}
|
||||
#else
|
||||
UNUSED_PARAMETER(bConcurrent);
|
||||
#endif
|
||||
|
||||
for(iFrame=pWal->hdr.mxFrame+1;
|
||||
ALWAYS(rc==SQLITE_OK) && iFrame<=iMax;
|
||||
@@ -3386,7 +3611,6 @@ int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){
|
||||
** point in the event of a savepoint rollback (via WalSavepointUndo()).
|
||||
*/
|
||||
void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){
|
||||
assert( pWal->writeLock );
|
||||
aWalData[0] = pWal->hdr.mxFrame;
|
||||
aWalData[1] = pWal->hdr.aFrameCksum[0];
|
||||
aWalData[2] = pWal->hdr.aFrameCksum[1];
|
||||
@@ -3402,7 +3626,7 @@ void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){
|
||||
int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
assert( pWal->writeLock );
|
||||
assert( pWal->writeLock || aWalData[0]==pWal->hdr.mxFrame );
|
||||
assert( aWalData[3]!=pWal->nCkpt || aWalData[0]<=pWal->hdr.mxFrame );
|
||||
|
||||
if( aWalData[3]!=pWal->nCkpt ){
|
||||
@@ -3438,14 +3662,13 @@ int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){
|
||||
*/
|
||||
static int walRestartLog(Wal *pWal){
|
||||
int rc = SQLITE_OK;
|
||||
int cnt;
|
||||
|
||||
if( pWal->readLock==0 ){
|
||||
volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
|
||||
assert( pInfo->nBackfill==pWal->hdr.mxFrame );
|
||||
if( pInfo->nBackfill>0 ){
|
||||
u32 salt1;
|
||||
sqlite3_randomness(4, &salt1);
|
||||
sqlite3FastRandomness(&pWal->sPrng, 4, &salt1);
|
||||
rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
|
||||
if( rc==SQLITE_OK ){
|
||||
/* If all readers are using WAL_READ_LOCK(0) (in other words if no
|
||||
@@ -3459,21 +3682,21 @@ static int walRestartLog(Wal *pWal){
|
||||
** to handle if this transaction is rolled back. */
|
||||
walRestartHdr(pWal, salt1);
|
||||
walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
|
||||
pWal->nPriorFrame = 0;
|
||||
}else if( rc!=SQLITE_BUSY ){
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
walUnlockShared(pWal, WAL_READ_LOCK(0));
|
||||
pWal->readLock = -1;
|
||||
cnt = 0;
|
||||
do{
|
||||
int notUsed;
|
||||
rc = walTryBeginRead(pWal, ¬Used, 1, ++cnt);
|
||||
}while( rc==WAL_RETRY );
|
||||
assert( (rc&0xff)!=SQLITE_BUSY ); /* BUSY not possible when useWal==1 */
|
||||
testcase( (rc&0xff)==SQLITE_IOERR );
|
||||
testcase( rc==SQLITE_PROTOCOL );
|
||||
testcase( rc==SQLITE_OK );
|
||||
|
||||
/* Regardless of whether or not the wal file was restarted, change the
|
||||
** read-lock held by this client to a slot other than aReadmark[0].
|
||||
** Clients with a lock on aReadmark[0] read from the database file
|
||||
** only - never from the wal file. This means that if a writer holding
|
||||
** a lock on aReadmark[0] were to commit a transaction but not close the
|
||||
** read-transaction, subsequent read operations would read directly from
|
||||
** the database file - ignoring the new pages just appended
|
||||
** to the wal file. */
|
||||
rc = walUpgradeReadlock(pWal);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -3657,7 +3880,7 @@ int sqlite3WalFrames(
|
||||
sqlite3Put4byte(&aWalHdr[4], WAL_MAX_VERSION);
|
||||
sqlite3Put4byte(&aWalHdr[8], szPage);
|
||||
sqlite3Put4byte(&aWalHdr[12], pWal->nCkpt);
|
||||
if( pWal->nCkpt==0 ) sqlite3_randomness(8, pWal->hdr.aSalt);
|
||||
if( pWal->nCkpt==0 ) sqlite3FastRandomness(&pWal->sPrng, 8, pWal->hdr.aSalt);
|
||||
memcpy(&aWalHdr[16], pWal->hdr.aSalt, 8);
|
||||
walChecksumBytes(1, aWalHdr, WAL_HDRSIZE-2*4, 0, aCksum);
|
||||
sqlite3Put4byte(&aWalHdr[24], aCksum[0]);
|
||||
@@ -3734,6 +3957,7 @@ int sqlite3WalFrames(
|
||||
p->flags |= PGHDR_WAL_APPEND;
|
||||
}
|
||||
|
||||
|
||||
/* Recalculate checksums within the wal file if required. */
|
||||
if( isCommit && pWal->iReCksum ){
|
||||
rc = walRewriteChecksums(pWal, iFrame);
|
||||
@@ -3917,7 +4141,6 @@ int sqlite3WalCheckpoint(
|
||||
|
||||
/* Copy data from the log to the database file. */
|
||||
if( rc==SQLITE_OK ){
|
||||
|
||||
if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
}else{
|
||||
@@ -4150,4 +4373,16 @@ sqlite3_file *sqlite3WalFile(Wal *pWal){
|
||||
return pWal->pWalFd;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the values required by sqlite3_wal_info().
|
||||
*/
|
||||
int sqlite3WalInfo(Wal *pWal, u32 *pnPrior, u32 *pnFrame){
|
||||
int rc = SQLITE_OK;
|
||||
if( pWal ){
|
||||
*pnFrame = pWal->hdr.mxFrame;
|
||||
*pnPrior = pWal->nPriorFrame;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* #ifndef SQLITE_OMIT_WAL */
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
# define sqlite3WalDbsize(y) 0
|
||||
# define sqlite3WalBeginWriteTransaction(y) 0
|
||||
# define sqlite3WalEndWriteTransaction(x) 0
|
||||
# define sqlite3WalUndo(x,y,z) 0
|
||||
# define sqlite3WalUndo(w,x,y,z) 0
|
||||
# define sqlite3WalSavepoint(y,z)
|
||||
# define sqlite3WalSavepointUndo(y,z) 0
|
||||
# define sqlite3WalFrames(u,v,w,x,y,z) 0
|
||||
@@ -83,7 +83,7 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal);
|
||||
int sqlite3WalEndWriteTransaction(Wal *pWal);
|
||||
|
||||
/* Undo any frames written (but not committed) to the log */
|
||||
int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx);
|
||||
int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx, int);
|
||||
|
||||
/* Return an integer that records the current (uncommitted) write
|
||||
** position in the WAL */
|
||||
@@ -136,6 +136,15 @@ int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot);
|
||||
void sqlite3WalSnapshotUnlock(Wal *pWal);
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/* Tell the wal layer that we want to commit a concurrent transaction */
|
||||
int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pPg, Bitvec *pRead, Pgno*);
|
||||
|
||||
/* Upgrade the state of the client to take into account changes written
|
||||
** by other connections */
|
||||
int sqlite3WalUpgradeSnapshot(Wal *pWal);
|
||||
#endif /* SQLITE_OMIT_CONCURRENT */
|
||||
|
||||
#ifdef SQLITE_ENABLE_ZIPVFS
|
||||
/* If the WAL file is not empty, return the number of bytes of content
|
||||
** stored in each frame (i.e. the db page-size when the WAL was created).
|
||||
@@ -151,5 +160,8 @@ int sqlite3WalWriteLock(Wal *pWal, int bLock);
|
||||
void sqlite3WalDb(Wal *pWal, sqlite3 *db);
|
||||
#endif
|
||||
|
||||
/* sqlite3_wal_info() data */
|
||||
int sqlite3WalInfo(Wal *pWal, u32 *pnPrior, u32 *pnFrame);
|
||||
|
||||
#endif /* ifndef SQLITE_OMIT_WAL */
|
||||
#endif /* SQLITE_WAL_H */
|
||||
|
||||
@@ -19,6 +19,11 @@ if {$::longdouble_size<=8} {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
if {![info exists tcl_platform(machine)]
|
||||
|| $::tcl_platform(machine)!="x86_64"} {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
expr srand(1)
|
||||
for {set i 1} {$i<20000} {incr i} {
|
||||
|
||||
+556
@@ -0,0 +1,556 @@
|
||||
/*
|
||||
** 2016-05-07
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
*/
|
||||
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include "tt3_core.c"
|
||||
|
||||
#ifdef USE_OSINST
|
||||
# include "../src/test_osinst.c"
|
||||
#else
|
||||
# define vfslog_time() 0
|
||||
#endif
|
||||
|
||||
typedef struct Config Config;
|
||||
typedef struct ThreadCtx ThreadCtx;
|
||||
|
||||
#define THREAD_TIME_INSERT 0
|
||||
#define THREAD_TIME_COMMIT 1
|
||||
#define THREAD_TIME_ROLLBACK 2
|
||||
#define THREAD_TIME_WRITER 3
|
||||
#define THREAD_TIME_CKPT 4
|
||||
|
||||
struct ThreadCtx {
|
||||
Config *pConfig;
|
||||
Sqlite *pDb;
|
||||
Error *pErr;
|
||||
sqlite3_int64 aTime[5];
|
||||
};
|
||||
|
||||
struct Config {
|
||||
int nIPT; /* --inserts-per-transaction */
|
||||
int nThread; /* --threads */
|
||||
int nSecond; /* --seconds */
|
||||
int bMutex; /* --mutex */
|
||||
int nAutoCkpt; /* --autockpt */
|
||||
int bRm; /* --rm */
|
||||
int bClearCache; /* --clear-cache */
|
||||
int nMmap; /* mmap limit in MB */
|
||||
char *zFile;
|
||||
int bOsinst; /* True to use osinst */
|
||||
|
||||
ThreadCtx *aCtx; /* Array of size nThread */
|
||||
|
||||
pthread_cond_t cond;
|
||||
pthread_mutex_t mutex;
|
||||
int nCondWait; /* Number of threads waiting on hCond */
|
||||
sqlite3_vfs *pVfs;
|
||||
};
|
||||
|
||||
|
||||
typedef struct VfsWrapperFd VfsWrapperFd;
|
||||
struct VfsWrapperFd {
|
||||
sqlite3_file base; /* Base class */
|
||||
int bWriter; /* True if holding shm WRITER lock */
|
||||
int iTid;
|
||||
Config *pConfig;
|
||||
sqlite3_file *pFd; /* Underlying file descriptor */
|
||||
};
|
||||
|
||||
/* Methods of the wrapper VFS */
|
||||
static int vfsWrapOpen(sqlite3_vfs*, const char*, sqlite3_file*, int, int*);
|
||||
static int vfsWrapDelete(sqlite3_vfs*, const char*, int);
|
||||
static int vfsWrapAccess(sqlite3_vfs*, const char*, int, int*);
|
||||
static int vfsWrapFullPathname(sqlite3_vfs*, const char *, int, char*);
|
||||
static void *vfsWrapDlOpen(sqlite3_vfs*, const char*);
|
||||
static void vfsWrapDlError(sqlite3_vfs*, int, char*);
|
||||
static void (*vfsWrapDlSym(sqlite3_vfs*,void*, const char*))(void);
|
||||
static void vfsWrapDlClose(sqlite3_vfs*, void*);
|
||||
static int vfsWrapRandomness(sqlite3_vfs*, int, char*);
|
||||
static int vfsWrapSleep(sqlite3_vfs*, int);
|
||||
static int vfsWrapCurrentTime(sqlite3_vfs*, double*);
|
||||
static int vfsWrapGetLastError(sqlite3_vfs*, int, char*);
|
||||
static int vfsWrapCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
|
||||
static int vfsWrapSetSystemCall(sqlite3_vfs*, const char*, sqlite3_syscall_ptr);
|
||||
static sqlite3_syscall_ptr vfsWrapGetSystemCall(sqlite3_vfs*, const char*);
|
||||
static const char *vfsWrapNextSystemCall(sqlite3_vfs*, const char*);
|
||||
|
||||
/* Methods of wrapper sqlite3_io_methods object (see vfsWrapOpen()) */
|
||||
static int vfsWrapClose(sqlite3_file*);
|
||||
static int vfsWrapRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
|
||||
static int vfsWrapWrite(sqlite3_file*, const void*, int iAmt, sqlite3_int64);
|
||||
static int vfsWrapTruncate(sqlite3_file*, sqlite3_int64 size);
|
||||
static int vfsWrapSync(sqlite3_file*, int flags);
|
||||
static int vfsWrapFileSize(sqlite3_file*, sqlite3_int64 *pSize);
|
||||
static int vfsWrapLock(sqlite3_file*, int);
|
||||
static int vfsWrapUnlock(sqlite3_file*, int);
|
||||
static int vfsWrapCheckReservedLock(sqlite3_file*, int *pResOut);
|
||||
static int vfsWrapFileControl(sqlite3_file*, int op, void *pArg);
|
||||
static int vfsWrapSectorSize(sqlite3_file*);
|
||||
static int vfsWrapDeviceCharacteristics(sqlite3_file*);
|
||||
static int vfsWrapShmMap(sqlite3_file*, int iPg, int, int, void volatile**);
|
||||
static int vfsWrapShmLock(sqlite3_file*, int offset, int n, int flags);
|
||||
static void vfsWrapShmBarrier(sqlite3_file*);
|
||||
static int vfsWrapShmUnmap(sqlite3_file*, int deleteFlag);
|
||||
static int vfsWrapFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **);
|
||||
static int vfsWrapUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p);
|
||||
|
||||
static int vfsWrapOpen(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zName,
|
||||
sqlite3_file *pFd,
|
||||
int flags,
|
||||
int *fout
|
||||
){
|
||||
static sqlite3_io_methods methods = {
|
||||
3,
|
||||
vfsWrapClose, vfsWrapRead, vfsWrapWrite,
|
||||
vfsWrapTruncate, vfsWrapSync, vfsWrapFileSize,
|
||||
vfsWrapLock, vfsWrapUnlock, vfsWrapCheckReservedLock,
|
||||
vfsWrapFileControl, vfsWrapSectorSize, vfsWrapDeviceCharacteristics,
|
||||
vfsWrapShmMap, vfsWrapShmLock, vfsWrapShmBarrier,
|
||||
vfsWrapShmUnmap, vfsWrapFetch, vfsWrapUnfetch
|
||||
};
|
||||
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
int rc;
|
||||
|
||||
memset(pWrapper, 0, sizeof(VfsWrapperFd));
|
||||
if( flags & SQLITE_OPEN_MAIN_DB ){
|
||||
pWrapper->iTid = (int)sqlite3_uri_int64(zName, "tid", 0);
|
||||
}
|
||||
|
||||
pWrapper->pFd = (sqlite3_file*)&pWrapper[1];
|
||||
pWrapper->pConfig = pConfig;
|
||||
rc = pConfig->pVfs->xOpen(pConfig->pVfs, zName, pWrapper->pFd, flags, fout);
|
||||
if( rc==SQLITE_OK ){
|
||||
pWrapper->base.pMethods = &methods;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int vfsWrapDelete(sqlite3_vfs *pVfs, const char *a, int b){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xDelete(pConfig->pVfs, a, b);
|
||||
}
|
||||
static int vfsWrapAccess(sqlite3_vfs *pVfs, const char *a, int b, int *c){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xAccess(pConfig->pVfs, a, b, c);
|
||||
}
|
||||
static int vfsWrapFullPathname(sqlite3_vfs *pVfs, const char *a, int b, char*c){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xFullPathname(pConfig->pVfs, a, b, c);
|
||||
}
|
||||
static void *vfsWrapDlOpen(sqlite3_vfs *pVfs, const char *a){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xDlOpen(pConfig->pVfs, a);
|
||||
}
|
||||
static void vfsWrapDlError(sqlite3_vfs *pVfs, int a, char *b){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xDlError(pConfig->pVfs, a, b);
|
||||
}
|
||||
static void (*vfsWrapDlSym(sqlite3_vfs *pVfs, void *a, const char *b))(void){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xDlSym(pConfig->pVfs, a, b);
|
||||
}
|
||||
static void vfsWrapDlClose(sqlite3_vfs *pVfs, void *a){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xDlClose(pConfig->pVfs, a);
|
||||
}
|
||||
static int vfsWrapRandomness(sqlite3_vfs *pVfs, int a, char *b){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xRandomness(pConfig->pVfs, a, b);
|
||||
}
|
||||
static int vfsWrapSleep(sqlite3_vfs *pVfs, int a){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xSleep(pConfig->pVfs, a);
|
||||
}
|
||||
static int vfsWrapCurrentTime(sqlite3_vfs *pVfs, double *a){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xCurrentTime(pConfig->pVfs, a);
|
||||
}
|
||||
static int vfsWrapGetLastError(sqlite3_vfs *pVfs, int a, char *b){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xGetLastError(pConfig->pVfs, a, b);
|
||||
}
|
||||
static int vfsWrapCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *a){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xCurrentTimeInt64(pConfig->pVfs, a);
|
||||
}
|
||||
static int vfsWrapSetSystemCall(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *a,
|
||||
sqlite3_syscall_ptr b
|
||||
){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xSetSystemCall(pConfig->pVfs, a, b);
|
||||
}
|
||||
static sqlite3_syscall_ptr vfsWrapGetSystemCall(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *a
|
||||
){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xGetSystemCall(pConfig->pVfs, a);
|
||||
}
|
||||
static const char *vfsWrapNextSystemCall(sqlite3_vfs *pVfs, const char *a){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xNextSystemCall(pConfig->pVfs, a);
|
||||
}
|
||||
|
||||
static int vfsWrapClose(sqlite3_file *pFd){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
pWrapper->pFd->pMethods->xClose(pWrapper->pFd);
|
||||
pWrapper->pFd = 0;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
static int vfsWrapRead(sqlite3_file *pFd, void *a, int b, sqlite3_int64 c){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xRead(pWrapper->pFd, a, b, c);
|
||||
}
|
||||
static int vfsWrapWrite(
|
||||
sqlite3_file *pFd,
|
||||
const void *a, int b,
|
||||
sqlite3_int64 c
|
||||
){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xWrite(pWrapper->pFd, a, b, c);
|
||||
}
|
||||
static int vfsWrapTruncate(sqlite3_file *pFd, sqlite3_int64 a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xTruncate(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapSync(sqlite3_file *pFd, int a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xSync(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapFileSize(sqlite3_file *pFd, sqlite3_int64 *a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xFileSize(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapLock(sqlite3_file *pFd, int a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xLock(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapUnlock(sqlite3_file *pFd, int a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xUnlock(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapCheckReservedLock(sqlite3_file *pFd, int *a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xCheckReservedLock(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapFileControl(sqlite3_file *pFd, int a, void *b){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xFileControl(pWrapper->pFd, a, b);
|
||||
}
|
||||
static int vfsWrapSectorSize(sqlite3_file *pFd){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xSectorSize(pWrapper->pFd);
|
||||
}
|
||||
static int vfsWrapDeviceCharacteristics(sqlite3_file *pFd){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xDeviceCharacteristics(pWrapper->pFd);
|
||||
}
|
||||
static int vfsWrapShmMap(
|
||||
sqlite3_file *pFd,
|
||||
int a, int b, int c,
|
||||
void volatile **d
|
||||
){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xShmMap(pWrapper->pFd, a, b, c, d);
|
||||
}
|
||||
static int vfsWrapShmLock(sqlite3_file *pFd, int offset, int n, int flags){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
Config *pConfig = pWrapper->pConfig;
|
||||
int bMutex = 0;
|
||||
int rc;
|
||||
|
||||
if( (offset==0 && n==1)
|
||||
&& (flags & SQLITE_SHM_LOCK) && (flags & SQLITE_SHM_EXCLUSIVE)
|
||||
){
|
||||
pthread_mutex_lock(&pConfig->mutex);
|
||||
pWrapper->bWriter = 1;
|
||||
bMutex = 1;
|
||||
if( pWrapper->iTid ){
|
||||
sqlite3_int64 t = vfslog_time();
|
||||
pConfig->aCtx[pWrapper->iTid-1].aTime[THREAD_TIME_WRITER] -= t;
|
||||
}
|
||||
}
|
||||
|
||||
rc = pWrapper->pFd->pMethods->xShmLock(pWrapper->pFd, offset, n, flags);
|
||||
|
||||
if( (rc!=SQLITE_OK && bMutex)
|
||||
|| (offset==0 && (flags & SQLITE_SHM_UNLOCK) && pWrapper->bWriter)
|
||||
){
|
||||
assert( pWrapper->bWriter );
|
||||
pthread_mutex_unlock(&pConfig->mutex);
|
||||
pWrapper->bWriter = 0;
|
||||
if( pWrapper->iTid ){
|
||||
sqlite3_int64 t = vfslog_time();
|
||||
pConfig->aCtx[pWrapper->iTid-1].aTime[THREAD_TIME_WRITER] += t;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
static void vfsWrapShmBarrier(sqlite3_file *pFd){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xShmBarrier(pWrapper->pFd);
|
||||
}
|
||||
static int vfsWrapShmUnmap(sqlite3_file *pFd, int a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xShmUnmap(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapFetch(sqlite3_file *pFd, sqlite3_int64 a, int b, void **c){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xFetch(pWrapper->pFd, a, b, c);
|
||||
}
|
||||
static int vfsWrapUnfetch(sqlite3_file *pFd, sqlite3_int64 a, void *b){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xUnfetch(pWrapper->pFd, a, b);
|
||||
}
|
||||
|
||||
static void create_vfs(Config *pConfig){
|
||||
static sqlite3_vfs vfs = {
|
||||
3, 0, 0, 0, "wrapper", 0,
|
||||
vfsWrapOpen, vfsWrapDelete, vfsWrapAccess,
|
||||
vfsWrapFullPathname, vfsWrapDlOpen, vfsWrapDlError,
|
||||
vfsWrapDlSym, vfsWrapDlClose, vfsWrapRandomness,
|
||||
vfsWrapSleep, vfsWrapCurrentTime, vfsWrapGetLastError,
|
||||
vfsWrapCurrentTimeInt64, vfsWrapSetSystemCall, vfsWrapGetSystemCall,
|
||||
vfsWrapNextSystemCall
|
||||
};
|
||||
sqlite3_vfs *pVfs;
|
||||
|
||||
pVfs = sqlite3_vfs_find(0);
|
||||
vfs.mxPathname = pVfs->mxPathname;
|
||||
vfs.szOsFile = pVfs->szOsFile + sizeof(VfsWrapperFd);
|
||||
vfs.pAppData = (void*)pConfig;
|
||||
pConfig->pVfs = pVfs;
|
||||
|
||||
sqlite3_vfs_register(&vfs, 1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Wal hook used by connections in thread_main().
|
||||
*/
|
||||
static int thread_wal_hook(
|
||||
void *pArg, /* Pointer to ThreadCtx object */
|
||||
sqlite3 *db,
|
||||
const char *zDb,
|
||||
int nFrame
|
||||
){
|
||||
ThreadCtx *pCtx = (ThreadCtx*)pArg;
|
||||
Config *pConfig = pCtx->pConfig;
|
||||
|
||||
if( pConfig->nAutoCkpt && nFrame>=pConfig->nAutoCkpt ){
|
||||
pCtx->aTime[THREAD_TIME_CKPT] -= vfslog_time();
|
||||
pthread_mutex_lock(&pConfig->mutex);
|
||||
if( pConfig->nCondWait>=0 ){
|
||||
pConfig->nCondWait++;
|
||||
if( pConfig->nCondWait==pConfig->nThread ){
|
||||
execsql(pCtx->pErr, pCtx->pDb, "PRAGMA wal_checkpoint");
|
||||
pthread_cond_broadcast(&pConfig->cond);
|
||||
}else{
|
||||
pthread_cond_wait(&pConfig->cond, &pConfig->mutex);
|
||||
}
|
||||
pConfig->nCondWait--;
|
||||
}
|
||||
pthread_mutex_unlock(&pConfig->mutex);
|
||||
pCtx->aTime[THREAD_TIME_CKPT] += vfslog_time();
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *thread_main(int iTid, void *pArg){
|
||||
Config *pConfig = (Config*)pArg;
|
||||
Error err = {0}; /* Error code and message */
|
||||
Sqlite db = {0}; /* SQLite database connection */
|
||||
int nAttempt = 0; /* Attempted transactions */
|
||||
int nCommit = 0; /* Successful transactions */
|
||||
int j;
|
||||
ThreadCtx *pCtx = &pConfig->aCtx[iTid-1];
|
||||
char *zUri = 0;
|
||||
|
||||
#ifdef USE_OSINST
|
||||
char *zOsinstName = 0;
|
||||
char *zLogName = 0;
|
||||
if( pConfig->bOsinst ){
|
||||
zOsinstName = sqlite3_mprintf("osinst%d", iTid);
|
||||
zLogName = sqlite3_mprintf("bc_test1.log.%d.%d", (int)getpid(), iTid);
|
||||
zUri = sqlite3_mprintf(
|
||||
"file:%s?vfs=%s&tid=%d", pConfig->zFile, zOsinstName, iTid
|
||||
);
|
||||
sqlite3_vfslog_new(zOsinstName, 0, zLogName);
|
||||
opendb(&err, &db, zUri, 0);
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
zUri = sqlite3_mprintf("file:%s?tid=%d", pConfig->zFile, iTid);
|
||||
opendb(&err, &db, zUri, 0);
|
||||
}
|
||||
|
||||
sqlite3_busy_handler(db.db, 0, 0);
|
||||
sql_script_printf(&err, &db,
|
||||
"PRAGMA wal_autocheckpoint = 0;"
|
||||
"PRAGMA synchronous = 0;"
|
||||
"PRAGMA mmap_size = %lld;",
|
||||
(i64)(pConfig->nMmap) * 1024 * 1024
|
||||
);
|
||||
|
||||
pCtx->pConfig = pConfig;
|
||||
pCtx->pErr = &err;
|
||||
pCtx->pDb = &db;
|
||||
sqlite3_wal_hook(db.db, thread_wal_hook, (void*)pCtx);
|
||||
|
||||
while( !timetostop(&err) ){
|
||||
execsql(&err, &db, "BEGIN CONCURRENT");
|
||||
|
||||
pCtx->aTime[THREAD_TIME_INSERT] -= vfslog_time();
|
||||
for(j=0; j<pConfig->nIPT; j++){
|
||||
execsql(&err, &db,
|
||||
"INSERT INTO t1 VALUES"
|
||||
"(randomblob(10), randomblob(20), randomblob(30), randomblob(200))"
|
||||
);
|
||||
}
|
||||
pCtx->aTime[THREAD_TIME_INSERT] += vfslog_time();
|
||||
|
||||
pCtx->aTime[THREAD_TIME_COMMIT] -= vfslog_time();
|
||||
execsql(&err, &db, "COMMIT");
|
||||
pCtx->aTime[THREAD_TIME_COMMIT] += vfslog_time();
|
||||
|
||||
pCtx->aTime[THREAD_TIME_ROLLBACK] -= vfslog_time();
|
||||
nAttempt++;
|
||||
if( err.rc==SQLITE_OK ){
|
||||
nCommit++;
|
||||
}else{
|
||||
clear_error(&err, SQLITE_BUSY);
|
||||
execsql(&err, &db, "ROLLBACK");
|
||||
}
|
||||
pCtx->aTime[THREAD_TIME_ROLLBACK] += vfslog_time();
|
||||
|
||||
if( pConfig->bClearCache ){
|
||||
sqlite3_db_release_memory(db.db);
|
||||
}
|
||||
}
|
||||
|
||||
closedb(&err, &db);
|
||||
|
||||
#ifdef USE_OSINST
|
||||
if( pConfig->bOsinst ){
|
||||
sqlite3_vfslog_finalize(zOsinstName);
|
||||
sqlite3_free(zOsinstName);
|
||||
sqlite3_free(zLogName);
|
||||
}
|
||||
#endif
|
||||
sqlite3_free(zUri);
|
||||
|
||||
pthread_mutex_lock(&pConfig->mutex);
|
||||
pConfig->nCondWait = -1;
|
||||
pthread_cond_broadcast(&pConfig->cond);
|
||||
pthread_mutex_unlock(&pConfig->mutex);
|
||||
|
||||
return sqlite3_mprintf("commits: %d/%d insert: %dms"
|
||||
" commit: %dms"
|
||||
" rollback: %dms"
|
||||
" writer: %dms"
|
||||
" checkpoint: %dms",
|
||||
nCommit, nAttempt,
|
||||
(int)(pCtx->aTime[THREAD_TIME_INSERT]/1000),
|
||||
(int)(pCtx->aTime[THREAD_TIME_COMMIT]/1000),
|
||||
(int)(pCtx->aTime[THREAD_TIME_ROLLBACK]/1000),
|
||||
(int)(pCtx->aTime[THREAD_TIME_WRITER]/1000),
|
||||
(int)(pCtx->aTime[THREAD_TIME_CKPT]/1000)
|
||||
);
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv){
|
||||
Error err = {0}; /* Error code and message */
|
||||
Sqlite db = {0}; /* SQLite database connection */
|
||||
Threadset threads = {0}; /* Test threads */
|
||||
Config conf = {5, 3, 5};
|
||||
int i;
|
||||
|
||||
CmdlineArg apArg[] = {
|
||||
{ "-seconds", CMDLINE_INT, offsetof(Config, nSecond) },
|
||||
{ "-inserts", CMDLINE_INT, offsetof(Config, nIPT) },
|
||||
{ "-threads", CMDLINE_INT, offsetof(Config, nThread) },
|
||||
{ "-mutex", CMDLINE_BOOL, offsetof(Config, bMutex) },
|
||||
{ "-rm", CMDLINE_BOOL, offsetof(Config, bRm) },
|
||||
{ "-autockpt",CMDLINE_INT, offsetof(Config, nAutoCkpt) },
|
||||
{ "-mmap", CMDLINE_INT, offsetof(Config, nMmap) },
|
||||
{ "-clear-cache", CMDLINE_BOOL, offsetof(Config, bClearCache) },
|
||||
{ "-file", CMDLINE_STRING, offsetof(Config, zFile) },
|
||||
{ "-osinst", CMDLINE_BOOL, offsetof(Config, bOsinst) },
|
||||
{ 0, 0, 0 }
|
||||
};
|
||||
|
||||
conf.nAutoCkpt = 1000;
|
||||
cmdline_process(apArg, argc, argv, (void*)&conf);
|
||||
if( err.rc==SQLITE_OK ){
|
||||
char *z = cmdline_construct(apArg, (void*)&conf);
|
||||
printf("With: %s\n", z);
|
||||
sqlite3_free(z);
|
||||
}
|
||||
if( conf.zFile==0 ){
|
||||
conf.zFile = "xyz.db";
|
||||
}
|
||||
|
||||
/* Create the special VFS - "wrapper". And the mutex and condition
|
||||
** variable. */
|
||||
create_vfs(&conf);
|
||||
pthread_mutex_init(&conf.mutex, 0);
|
||||
pthread_cond_init(&conf.cond, 0);
|
||||
|
||||
conf.aCtx = sqlite3_malloc(sizeof(ThreadCtx) * conf.nThread);
|
||||
memset(conf.aCtx, 0, sizeof(ThreadCtx) * conf.nThread);
|
||||
|
||||
/* Ensure the schema has been created */
|
||||
opendb(&err, &db, conf.zFile, conf.bRm);
|
||||
sql_script(&err, &db,
|
||||
"PRAGMA journal_mode = wal;"
|
||||
"CREATE TABLE IF NOT EXISTS t1(a PRIMARY KEY, b, c, d) WITHOUT ROWID;"
|
||||
"CREATE INDEX IF NOT EXISTS t1b ON t1(b);"
|
||||
"CREATE INDEX IF NOT EXISTS t1c ON t1(c);"
|
||||
);
|
||||
|
||||
setstoptime(&err, conf.nSecond*1000);
|
||||
if( conf.nThread==1 ){
|
||||
char *z = thread_main(1, (void*)&conf);
|
||||
printf("Thread 0 says: %s\n", (z==0 ? "..." : z));
|
||||
fflush(stdout);
|
||||
}else{
|
||||
for(i=0; i<conf.nThread; i++){
|
||||
launch_thread(&err, &threads, thread_main, (void*)&conf);
|
||||
}
|
||||
join_all_threads(&err, &threads);
|
||||
}
|
||||
|
||||
if( err.rc==SQLITE_OK ){
|
||||
printf("Database is %dK\n", (int)(filesize(&err, conf.zFile) / 1024));
|
||||
}
|
||||
if( err.rc==SQLITE_OK ){
|
||||
char *zWal = sqlite3_mprintf("%s-wal", conf.zFile);
|
||||
printf("Wal file is %dK\n", (int)(filesize(&err, zWal) / 1024));
|
||||
}
|
||||
|
||||
closedb(&err, &db);
|
||||
print_and_free_err(&err);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
# 2015 Aug 25
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# This file contains fault injection tests designed to test the concurrent
|
||||
# transactions feature.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
set testprefix concfault
|
||||
|
||||
# This test will not work with an in-memory journal, as the database will
|
||||
# become corrupt if an error is injected into a transaction after it starts
|
||||
# writing data out to the db file.
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_test 1-pre1 {
|
||||
execsql {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(randomblob(1000), randomblob(100));
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
DELETE FROM t1 WHERE rowid%2;
|
||||
}
|
||||
faultsim_save_and_close
|
||||
} {}
|
||||
|
||||
do_faultsim_test 1.1 -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
execsql {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1000), randomblob(100));
|
||||
COMMIT;
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
catchsql { ROLLBACK }
|
||||
faultsim_integrity_check
|
||||
}
|
||||
|
||||
do_faultsim_test 1.2 -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
execsql {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1000), randomblob(100));
|
||||
ROLLBACK;
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
catchsql { ROLLBACK }
|
||||
faultsim_integrity_check
|
||||
}
|
||||
|
||||
do_faultsim_test 1.3 -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
execsql {
|
||||
BEGIN CONCURRENT;
|
||||
DELETE FROM t1;
|
||||
COMMIT;
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
catchsql { ROLLBACK }
|
||||
faultsim_integrity_check
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
PRAGMA auto_vacuum = 0;
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(randomblob(1000), randomblob(100));
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
DELETE FROM t1 WHERE rowid%2;
|
||||
} {wal}
|
||||
|
||||
faultsim_save_and_close
|
||||
do_faultsim_test 1 -prep {
|
||||
faultsim_restore_and_reopen
|
||||
execsql {
|
||||
SELECT * FROM t1;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES(1, 2);
|
||||
}
|
||||
sqlite3 db2 test.db
|
||||
execsql {
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
INSERT INTO t1 VALUES(randomblob(1000), randomblob(1000));
|
||||
} db2
|
||||
db2 close
|
||||
} -body {
|
||||
execsql { COMMIT }
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
catchsql { ROLLBACK }
|
||||
set res [catchsql { SELECT count(*) FROM t1 }]
|
||||
if {$res!="0 9"} { error "expected {0 9} got {$res}" }
|
||||
faultsim_integrity_check
|
||||
}
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,687 @@
|
||||
# 2015 July 26
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
set ::testprefix concurrent
|
||||
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
} {wal}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
CREATE TABLE t1(k INTEGER PRIMARY KEY, v);
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(1, 'abcd');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
SELECT * FROM t1;
|
||||
} {1 abcd}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(2, 'efgh');
|
||||
ROLLBACK;
|
||||
}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
SELECT * FROM t1;
|
||||
} {1 abcd}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# CONCURRENT transactions cannot do cache spills.
|
||||
#
|
||||
foreach {tn trans spill} {
|
||||
1 {BEGIN CONCURRENT} 0
|
||||
2 {BEGIN} 1
|
||||
} {
|
||||
do_test 1.5.$tn {
|
||||
sqlite3 db2 test.db
|
||||
set walsz [file size test.db-wal]
|
||||
|
||||
execsql { PRAGMA cache_size = 10 } db2
|
||||
execsql $trans db2
|
||||
execsql {
|
||||
WITH cnt(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM cnt WHERE i<50)
|
||||
INSERT INTO t1(v) SELECT randomblob(900) FROM cnt;
|
||||
} db2
|
||||
|
||||
expr {[file size test.db-wal]==$walsz}
|
||||
} [expr !$spill]
|
||||
|
||||
execsql ROLLBACK db2
|
||||
db2 close
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# CONCURRENT transactions man not be committed while there are active
|
||||
# readers.
|
||||
do_execsql_test 1.6.setup {
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1(a, b);
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES(3, 4);
|
||||
INSERT INTO t1 VALUES(5, 6);
|
||||
}
|
||||
foreach {tn trans commit_ok} {
|
||||
1 {BEGIN CONCURRENT} 0
|
||||
2 {BEGIN} 1
|
||||
} {
|
||||
do_test 1.6.$tn.1 {
|
||||
set stmt [sqlite3_prepare db "SELECT * FROM t1" -1 dummy]
|
||||
sqlite3_step $stmt
|
||||
} SQLITE_ROW
|
||||
do_test 1.6.$tn.2 {
|
||||
execsql $trans
|
||||
execsql { INSERT INTO t1 VALUES(7, 8) }
|
||||
} {}
|
||||
|
||||
if { $commit_ok } {
|
||||
do_test 1.6.$tn.3 { catchsql COMMIT } {0 {}}
|
||||
} else {
|
||||
do_test 1.6.$tn.4 { catchsql COMMIT } {/1 {cannot commit transaction .*}/}
|
||||
}
|
||||
|
||||
sqlite3_finalize $stmt
|
||||
catchsql ROLLBACK
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# CONCURRENT transactions may not modify the db schema.
|
||||
#
|
||||
sqlite3 db2 test.db
|
||||
foreach {tn sql} {
|
||||
1 { CREATE TABLE xx(a, b) }
|
||||
2 { DROP TABLE t1 }
|
||||
3 { CREATE INDEX i1 ON t1(a) }
|
||||
4 { CREATE VIEW v1 AS SELECT * FROM t1 }
|
||||
} {
|
||||
do_catchsql_test 1.7.0.$tn.1 "
|
||||
BEGIN CONCURRENT;
|
||||
$sql
|
||||
" {0 {}}
|
||||
|
||||
db2 eval {INSERT INTO t1 DEFAULT VALUES}
|
||||
|
||||
do_catchsql_test 1.7.0.$tn.2 {
|
||||
COMMIT
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_execsql_test 1.7.0.$tn.2 ROLLBACK
|
||||
|
||||
do_execsql_test 1.7.0.$tn.3 {
|
||||
SELECT sql FROM sqlite_master;
|
||||
SELECT sql FROM sqlite_temp_master;
|
||||
} {{CREATE TABLE t1(a, b)}}
|
||||
|
||||
#do_execsql_test 1.7.0.$tn.3 COMMIT
|
||||
}
|
||||
|
||||
# Except the temp db schema.
|
||||
foreach {tn sql} {
|
||||
1 { CREATE TEMP TABLE xx(a, b) }
|
||||
2 { DROP TABLE xx }
|
||||
3 { CREATE TEMP TABLE yy(a, b) }
|
||||
4 { CREATE VIEW temp.v1 AS SELECT * FROM t1 }
|
||||
5 { CREATE INDEX yyi1 ON yy(a); }
|
||||
6 { CREATE TABLE temp.zz(a, b) }
|
||||
} {
|
||||
do_catchsql_test 1.7.1.$tn.1 "
|
||||
BEGIN CONCURRENT;
|
||||
$sql
|
||||
" {0 {}}
|
||||
|
||||
do_execsql_test 1.7.1.$tn.2 COMMIT
|
||||
}
|
||||
|
||||
|
||||
do_execsql_test 1.7.1.x {
|
||||
SELECT sql FROM sqlite_master;
|
||||
SELECT sql FROM sqlite_temp_master;
|
||||
} {
|
||||
{CREATE TABLE t1(a, b)}
|
||||
{CREATE TABLE yy(a, b)}
|
||||
{CREATE VIEW v1 AS SELECT * FROM t1}
|
||||
{CREATE INDEX yyi1 ON yy(a)}
|
||||
{CREATE TABLE zz(a, b)}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# If an auto-vacuum database is written within an CONCURRENT transaction, it
|
||||
# is handled in the same way as for a non-CONCURRENT transaction.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 1.8.1 {
|
||||
PRAGMA auto_vacuum = 1;
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x, y);
|
||||
INSERT INTO t1 VALUES('x', 'y');
|
||||
} {wal}
|
||||
|
||||
do_execsql_test 1.8.2 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t1;
|
||||
COMMIT;
|
||||
} {x y}
|
||||
|
||||
do_catchsql_test 1.8.3 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES('a', 'b');
|
||||
} {0 {}}
|
||||
|
||||
do_test 1.8.4 {
|
||||
sqlite3 db2 test.db
|
||||
catchsql {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES('c', 'd');
|
||||
} db2
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test 1.8.5 {
|
||||
db eval COMMIT
|
||||
db2 eval COMMIT
|
||||
} {}
|
||||
db close
|
||||
db2 close
|
||||
|
||||
do_multiclient_test tn {
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# 1. Start an CONCURRENT transaction using [db1].
|
||||
#
|
||||
# 2. Start and then rollback a regular transaction using [db2]. This
|
||||
# can be done as the ongoing [db1] transaction is CONCURRENT.
|
||||
#
|
||||
# 3. The [db1] transaction can now be committed, as [db2] has relinquished
|
||||
# the write lock.
|
||||
#
|
||||
do_test 2.$tn.1.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(k INTEGER PRIMARY KEY, v);
|
||||
INSERT INTO t1 VALUES(1, 'one');
|
||||
}
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(2, 'two');
|
||||
}
|
||||
code1 { sqlite3_get_autocommit db }
|
||||
} 0
|
||||
|
||||
do_test 2.$tn.1.2 {
|
||||
sql2 {
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(3, 'three');
|
||||
ROLLBACK;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.1.3 {
|
||||
sql1 COMMIT
|
||||
sql2 { SELECT * FROM t1 }
|
||||
} {1 one 2 two}
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# 1. Start an CONCURRENT transaction using [db1].
|
||||
#
|
||||
# 2. Commit a transaction using [db2].
|
||||
#
|
||||
# 3. Try to commit with [db1]. Check that SQLITE_BUSY_SNAPSHOT is returned,
|
||||
# and the transaction is not rolled back.
|
||||
#
|
||||
do_test 2.$tn.2.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(-1, 'hello world');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.2.2 {
|
||||
sql2 {
|
||||
INSERT INTO t1 VALUES(3, 'three');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.2.3.1 {
|
||||
set rc [catch { sql1 COMMIT } msg]
|
||||
list $rc $msg
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test 2.$tn.2.3.2 {
|
||||
code1 { list [sqlite3_extended_errcode db] [sqlite3_get_autocommit db] }
|
||||
} {SQLITE_BUSY_SNAPSHOT 0}
|
||||
|
||||
do_test 2.$tn.2.3.3 {
|
||||
sql1 {
|
||||
SELECT * FROM t1;
|
||||
ROLLBACK;
|
||||
}
|
||||
} {-1 {hello world} 1 one 2 two}
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# 1. Start an CONCURRENT transaction using [db1].
|
||||
#
|
||||
# 2. Open a transaction using [db2].
|
||||
#
|
||||
# 3. Try to commit with [db1]. Check that SQLITE_BUSY is returned,
|
||||
# and the transaction is not rolled back.
|
||||
#
|
||||
# 4. Have [db2] roll its transaction back. Then check that [db1] can
|
||||
# commit.
|
||||
#
|
||||
do_test 2.$tn.3.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(4, 'four');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.3.2 {
|
||||
sql2 {
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(-1, 'xyz');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.3.3.1 {
|
||||
set rc [catch { sql1 COMMIT } msg]
|
||||
list $rc $msg
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test 2.$tn.3.3.2 {
|
||||
code1 { list [sqlite3_extended_errcode db] [sqlite3_get_autocommit db] }
|
||||
} {SQLITE_BUSY 0}
|
||||
|
||||
do_test 2.$tn.3.3.3 {
|
||||
sql1 { SELECT * FROM t1; }
|
||||
} {1 one 2 two 3 three 4 four}
|
||||
|
||||
do_test 2.$tn.3.4 {
|
||||
sql2 ROLLBACK
|
||||
sql1 COMMIT
|
||||
sql1 { SELECT * FROM t1; }
|
||||
} {1 one 2 two 3 three 4 four}
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# 1. Create a second table - t2.
|
||||
#
|
||||
# 2. Write to t1 with [db] and t2 with [db2].
|
||||
#
|
||||
# 3. See if it worked.
|
||||
#
|
||||
do_test 2.$tn.4.1 {
|
||||
sql1 { CREATE TABLE t2(a, b) }
|
||||
} {}
|
||||
do_test 2.$tn.4.2 {
|
||||
sql2 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES('i', 'n');
|
||||
}
|
||||
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(5, 'five');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
sql2 COMMIT
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.4.3.1 {
|
||||
sql2 {SELECT * FROM t1}
|
||||
} {1 one 2 two 3 three 4 four 5 five}
|
||||
do_test 2.$tn.4.3.2 {
|
||||
sql1 {SELECT * FROM t1}
|
||||
} {1 one 2 two 3 three 4 four 5 five}
|
||||
|
||||
do_test 2.$tn.4.3.3 { sql2 {SELECT * FROM t2} } {i n}
|
||||
do_test 2.$tn.4.3.4 { sql1 {SELECT * FROM t2} } {i n}
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# The "schema cookie" issue.
|
||||
#
|
||||
# 1. Begin and CONCURRENT write to "t1" using [db]
|
||||
#
|
||||
# 2. Create an index on t1 using [db2].
|
||||
#
|
||||
# 3. Attempt to commit the CONCURRENT write. This is an SQLITE_BUSY_SNAPSHOT,
|
||||
# even though there is no page collision.
|
||||
#
|
||||
do_test 2.$tn.5.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(6, 'six');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.5.2 {
|
||||
sql2 { CREATE INDEX i1 ON t1(v); }
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.5.3 {
|
||||
list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
|
||||
} {1 {database is locked} SQLITE_BUSY_SNAPSHOT}
|
||||
|
||||
do_test 2.$tn.5.4 {
|
||||
sql2 { PRAGMA integrity_check }
|
||||
} {ok}
|
||||
catch { sql1 ROLLBACK }
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
#
|
||||
# 1. Begin an CONCURRENT write to "t1" using [db]
|
||||
#
|
||||
# 2. Lots of inserts into t2. Enough to grow the db file and modify page 1.
|
||||
#
|
||||
# 3. Check that the CONCURRENT transaction can not be committed.
|
||||
#
|
||||
do_test 2.$tn.6.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(6, 'six');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.6.2 {
|
||||
sql2 {
|
||||
WITH src(a,b) AS (
|
||||
VALUES(1,1) UNION ALL SELECT a+1,b+1 FROM src WHERE a<10000
|
||||
) INSERT INTO t2 SELECT * FROM src;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.6.3 {
|
||||
sql1 { SELECT count(*) FROM t2 }
|
||||
list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
|
||||
} {1 {database is locked} SQLITE_BUSY_SNAPSHOT}
|
||||
sql1 ROLLBACK
|
||||
|
||||
do_test 2.$tn.6.4 {
|
||||
sql1 {
|
||||
SELECT count(*) FROM t1;
|
||||
SELECT count(*) FROM t2;
|
||||
}
|
||||
} {5 10001}
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
#
|
||||
# 1. Begin an big CONCURRENT write to "t1" using [db] - large enough to
|
||||
# grow the db file.
|
||||
#
|
||||
# 2. Lots of inserts into t2. Also enough to grow the db file.
|
||||
#
|
||||
# 3. Check that the CONCURRENT transaction cannot be committed (due to a clash
|
||||
# on page 1 - the db size field).
|
||||
#
|
||||
do_test 2.$tn.7.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
WITH src(a,b) AS (
|
||||
VALUES(10000,10000) UNION ALL SELECT a+1,b+1 FROM src WHERE a<20000
|
||||
) INSERT INTO t1 SELECT * FROM src;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.7.2 {
|
||||
sql2 {
|
||||
WITH src(a,b) AS (
|
||||
VALUES(1,1) UNION ALL SELECT a+1,b+1 FROM src WHERE a<10000
|
||||
) INSERT INTO t2 SELECT * FROM src;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.7.3 {
|
||||
list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
|
||||
} {0 {} SQLITE_OK}
|
||||
|
||||
do_test 2.$tn.7.4 { sql3 { PRAGMA integrity_check } } ok
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Concurrent transactions may not modify the user_version or application_id.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x, y);
|
||||
INSERT INTO t1 VALUES('a', 'b');
|
||||
PRAGMA user_version = 10;
|
||||
} {wal}
|
||||
do_execsql_test 3.1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES('c', 'd');
|
||||
SELECT * FROM t1;
|
||||
} {a b c d}
|
||||
do_catchsql_test 3.2 {
|
||||
PRAGMA user_version = 11;
|
||||
} {1 {cannot modify user_version within CONCURRENT transaction}}
|
||||
do_execsql_test 3.3 {
|
||||
PRAGMA user_version;
|
||||
SELECT * FROM t1;
|
||||
} {10 a b c d}
|
||||
do_catchsql_test 3.4 {
|
||||
PRAGMA application_id = 11;
|
||||
} {1 {cannot modify application_id within CONCURRENT transaction}}
|
||||
do_execsql_test 3.5 {
|
||||
COMMIT;
|
||||
PRAGMA user_version;
|
||||
PRAGMA application_id;
|
||||
SELECT * FROM t1;
|
||||
} {10 0 a b c d}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# However, another transaction modifying the user_version or application_id
|
||||
# should not cause a conflict. And committing a concurrent transaction does not
|
||||
# clobber the modification - even if the concurrent transaction allocates or
|
||||
# frees database pages.
|
||||
#
|
||||
do_multiclient_test tn {
|
||||
do_test 4.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE ttt(y UNIQUE, z UNIQUE);
|
||||
PRAGMA user_version = 14;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO ttt VALUES('y', 'z');
|
||||
}
|
||||
} {wal}
|
||||
do_test 4.$tn.2 {
|
||||
sql2 { PRAGMA user_version = 16 }
|
||||
sql1 COMMIT
|
||||
sql1 { PRAGMA user_version }
|
||||
} {16}
|
||||
|
||||
do_test 4.$tn.3 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO ttt VALUES(randomblob(10000), randomblob(4));
|
||||
PRAGMA user_version;
|
||||
}
|
||||
} {16}
|
||||
do_test 4.$tn.4 {
|
||||
sql2 { PRAGMA user_version = 1234 }
|
||||
sql1 {
|
||||
PRAGMA user_version;
|
||||
COMMIT;
|
||||
PRAGMA user_version;
|
||||
PRAGMA integrity_check;
|
||||
}
|
||||
} {16 1234 ok}
|
||||
|
||||
do_test 4.$tn.5 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
DELETE FROM ttt;
|
||||
PRAGMA user_version;
|
||||
}
|
||||
} {1234}
|
||||
do_test 4.$tn.4 {
|
||||
sql2 { PRAGMA user_version = 5678 }
|
||||
sql1 {
|
||||
PRAGMA user_version;
|
||||
COMMIT;
|
||||
PRAGMA user_version;
|
||||
PRAGMA integrity_check;
|
||||
}
|
||||
} {1234 5678 ok}
|
||||
}
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test 5.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE tt(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
|
||||
INSERT INTO tt VALUES(1, randomblob(400));
|
||||
BEGIN CONCURRENT;
|
||||
}
|
||||
} {wal}
|
||||
|
||||
do_test 5.$tn.2 {
|
||||
sql1 { UPDATE t2 SET b=5 WHERE a=3 }
|
||||
sql2 { INSERT INTO tt VALUES(2, randomblob(6000)) }
|
||||
} {}
|
||||
|
||||
do_test 5.$tn.3 {
|
||||
sql1 { COMMIT }
|
||||
} {}
|
||||
}
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test 6.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(1, 'one');
|
||||
INSERT INTO t2 VALUES(2, 'two');
|
||||
}
|
||||
} {wal}
|
||||
|
||||
do_test 6.$tn.2 {
|
||||
sql2 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t2;
|
||||
INSERT INTO t1 VALUES(3, 'three');
|
||||
}
|
||||
} {2 two}
|
||||
|
||||
do_test 6.$tn.3 {
|
||||
sql1 {
|
||||
INSERT INTO t2 VALUES(3, 'three');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 6.$tn.2 {
|
||||
list [catch { sql2 { COMMIT } } msg] $msg
|
||||
} {1 {database is locked}}
|
||||
}
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test 7.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t1 SELECT NULL, randomblob(400) FROM s;
|
||||
|
||||
CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<50000)
|
||||
INSERT INTO t2 SELECT NULL, randomblob(400) FROM s;
|
||||
|
||||
CREATE TABLE t3(a INTEGER PRIMARY KEY, b);
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t3 SELECT NULL, randomblob(400) FROM s;
|
||||
|
||||
CREATE TABLE t4(a INTEGER PRIMARY KEY, b);
|
||||
|
||||
}
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
do_test 7.$tn.2 {
|
||||
sql2 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t1;
|
||||
INSERT INTO t4 VALUES(1, 2);
|
||||
}
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
do_test 7.$tn.3 {
|
||||
sql3 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t3;
|
||||
INSERT INTO t4 VALUES(1, 2);
|
||||
}
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
do_test 7.$tn.4 {
|
||||
sql1 {
|
||||
UPDATE t1 SET b=randomblob(400);
|
||||
UPDATE t2 SET b=randomblob(400);
|
||||
UPDATE t3 SET b=randomblob(400);
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 7.$tn.5 {
|
||||
csql2 { COMMIT }
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test 7.$tn.6 {
|
||||
csql3 { COMMIT }
|
||||
} {1 {database is locked}}
|
||||
|
||||
|
||||
csql2 ROLLBACK
|
||||
csql3 ROLLBACK
|
||||
|
||||
# The following test works with $tn==1 (sql2 and sql3 use separate
|
||||
# processes), but is quite slow. So only run it with $tn==2 (all
|
||||
# connections in the same process).
|
||||
#
|
||||
if {$tn==2} {
|
||||
do_test 7.$tn.7 {
|
||||
for {set i 1} {$i < 10000} {incr i} {
|
||||
sql3 {
|
||||
PRAGMA wal_checkpoint;
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t3;
|
||||
INSERT INTO t4 VALUES(1, 2);
|
||||
}
|
||||
|
||||
sql1 {
|
||||
UPDATE t2 SET b = randomblob(400) WHERE rowid <= $i;
|
||||
UPDATE t3 SET b = randomblob(400) WHERE rowid = 1;
|
||||
}
|
||||
|
||||
if {[csql3 COMMIT]!={1 {database is locked}}} {
|
||||
error "Failed at i=$i"
|
||||
}
|
||||
csql3 ROLLBACK
|
||||
}
|
||||
} {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,626 @@
|
||||
# 2015 July 26
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Miscellaneous tests for transactions started with BEGIN CONCURRENT.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
set ::testprefix concurrent2
|
||||
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_multiclient_test tn {
|
||||
|
||||
do_test 1.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE t2(y);
|
||||
}
|
||||
} {wal}
|
||||
do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
|
||||
# Test that an CONCURRENT transaction that allocates/frees no pages does
|
||||
# not conflict with a transaction that does allocate pages.
|
||||
do_test 1.$tn.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(4);
|
||||
}
|
||||
sql2 {
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
}
|
||||
sql1 {
|
||||
COMMIT;
|
||||
}
|
||||
} {}
|
||||
do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
|
||||
# But that an CONCURRENT transaction does conflict with a transaction
|
||||
# that modifies the db schema.
|
||||
do_test 1.$tn.3 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(5);
|
||||
}
|
||||
sql2 {
|
||||
CREATE TABLE t3(z);
|
||||
}
|
||||
list [catch { sql1 COMMIT } msg] $msg
|
||||
} {1 {database is locked}}
|
||||
do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
|
||||
# Test that an CONCURRENT transaction that allocates at least one page
|
||||
# does not conflict with a transaction that allocates no pages.
|
||||
do_test 1.$tn.4 {
|
||||
sql1 {
|
||||
ROLLBACK;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
}
|
||||
sql2 {
|
||||
INSERT INTO t2 VALUES(8);
|
||||
}
|
||||
sql1 {
|
||||
COMMIT;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
}
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test 2.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x UNIQUE);
|
||||
CREATE TABLE t2(y UNIQUE);
|
||||
}
|
||||
} {wal}
|
||||
|
||||
do_test 2.$tn.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
}
|
||||
sql2 {
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
}
|
||||
sql1 COMMIT
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.3 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
|
||||
do_test 2.$tn.4 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
DELETE FROM t1;
|
||||
}
|
||||
sql2 {
|
||||
DELETE FROM t2;
|
||||
}
|
||||
sql1 COMMIT
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
|
||||
do_test 2.$tn.6 {
|
||||
sql1 {
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
DELETE FROM t1 WHERE rowid=1;
|
||||
}
|
||||
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
DELETE FROM t1 WHERE rowid=2;
|
||||
}
|
||||
|
||||
sql2 {
|
||||
DELETE FROM t2;
|
||||
}
|
||||
|
||||
sql1 COMMIT
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.7 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# When an CONCURRENT transaction is opened on a database, the nFree and
|
||||
# iTrunk header fields of the cached version of page 1 are both set
|
||||
# to 0. This allows an CONCURRENT transaction to use its own private
|
||||
# free-page-list, which is merged with the main database free-list when
|
||||
# the transaction is committed.
|
||||
#
|
||||
# The following tests check that nFree/iTrunk are correctly restored if
|
||||
# an CONCURRENT transaction is rolled back, and that savepoint rollbacks
|
||||
# that occur within CONCURRENT transactions do not incorrectly restore
|
||||
# these fields to their on-disk values.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x, y);
|
||||
INSERT INTO t1 VALUES(randomblob(1500), randomblob(1500));
|
||||
DELETE FROM t1;
|
||||
} {wal}
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
ROLLBACK;
|
||||
}
|
||||
|
||||
do_execsql_test 3.2 { PRAGMA integrity_check } {ok}
|
||||
do_execsql_test 3.3 { PRAGMA freelist_count } {2}
|
||||
|
||||
do_execsql_test 3.4.1 {
|
||||
BEGIN CONCURRENT;
|
||||
PRAGMA freelist_count;
|
||||
} {2}
|
||||
do_execsql_test 3.4.2 {
|
||||
SAVEPOINT xyz;
|
||||
INSERT INTO t1 VALUES(randomblob(1500), NULL);
|
||||
PRAGMA freelist_count;
|
||||
} {0}
|
||||
do_execsql_test 3.4.3 {
|
||||
ROLLBACK TO xyz;
|
||||
} {}
|
||||
do_execsql_test 3.4.4 { PRAGMA freelist_count } {0}
|
||||
do_execsql_test 3.4.5 { COMMIT; PRAGMA freelist_count } {2}
|
||||
do_execsql_test 3.4.6 { PRAGMA integrity_check } {ok}
|
||||
|
||||
do_execsql_test 3.5.1 {
|
||||
BEGIN CONCURRENT;
|
||||
UPDATE t1 SET x=randomblob(10) WHERE y=555;
|
||||
PRAGMA freelist_count;
|
||||
} {0}
|
||||
do_execsql_test 3.5.2 {
|
||||
ROLLBACK;
|
||||
PRAGMA freelist_count;
|
||||
} {2}
|
||||
do_execsql_test 3.5.3 { PRAGMA integrity_check } {ok}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that nothing goes wrong if an CONCURRENT transaction allocates a
|
||||
# page at the end of the file, frees it within the same transaction, and
|
||||
# then has to move the same page to avoid a conflict on COMMIT.
|
||||
#
|
||||
do_multiclient_test tn {
|
||||
do_test 4.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE t2(x);
|
||||
}
|
||||
} {wal}
|
||||
|
||||
do_test 4.$tn.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
DELETE FROM t1 WHERE rowid = 1;
|
||||
}
|
||||
|
||||
sql2 {
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
DELETE FROM t2 WHERE rowid IN (1, 2);
|
||||
}
|
||||
|
||||
sql1 COMMIT
|
||||
} {}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_multiclient_test tn {
|
||||
do_test 5.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE t2(x);
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
PRAGMA page_count;
|
||||
}
|
||||
} {wal 4}
|
||||
|
||||
do_test 5.$tn.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
PRAGMA page_count;
|
||||
}
|
||||
} {5}
|
||||
|
||||
do_test 5.$tn.3 {
|
||||
sql2 {
|
||||
DELETE FROM t1;
|
||||
PRAGMA freelist_count;
|
||||
PRAGMA page_count;
|
||||
}
|
||||
} {1 4}
|
||||
|
||||
do_test 5.$tn.4 { sql1 COMMIT } {}
|
||||
do_test 5.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_multiclient_test tn {
|
||||
do_test 6.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x);
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
PRAGMA wal_checkpoint;
|
||||
}
|
||||
} {wal 0 5 5}
|
||||
|
||||
do_test 6.$tn.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 6.$tn.3 {
|
||||
sql2 {
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
COMMIT;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 6.$tn.4 {
|
||||
list [catch { sql1 COMMIT } msg] $msg
|
||||
} {1 {database is locked}}
|
||||
do_test 6.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
do_test 6.$tn.5 { sql3 { SELECT count(*) from t1 } } {3}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that if a corrupt wal-index-header is encountered when attempting
|
||||
# to commit a CONCURRENT transaction, the transaction is not committed
|
||||
# (or rolled back) and that SQLITE_BUSY_SNAPSHOT is returned to the user.
|
||||
#
|
||||
catch { db close }
|
||||
forcedelete test.db
|
||||
testvfs tvfs
|
||||
sqlite3 db test.db -vfs tvfs
|
||||
do_execsql_test 7.1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
BEGIN;
|
||||
CREATE TABLE t1(a, b, PRIMARY KEY(a));
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES(3, 4);
|
||||
COMMIT;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(5, 6);
|
||||
INSERT INTO t1 VALUES(7, 8);
|
||||
SELECT * FROM t1;
|
||||
} {wal 1 2 3 4 5 6 7 8}
|
||||
|
||||
# Corrupt the wal-index header
|
||||
incr_tvfs_hdr test.db 11 1
|
||||
|
||||
do_catchsql_test 7.2.1 { COMMIT } {1 {database is locked}}
|
||||
do_test 7.2.2 { sqlite3_extended_errcode db } SQLITE_BUSY_SNAPSHOT
|
||||
|
||||
do_execsql_test 7.3.1 {
|
||||
SELECT * FROM t1;
|
||||
ROLLBACK;
|
||||
} {1 2 3 4 5 6 7 8}
|
||||
do_execsql_test 7.3.2 {
|
||||
SELECT * FROM t1;
|
||||
} {1 2 3 4}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that "PRAGMA integrity_check" works within a concurrent
|
||||
# transaction. Within a concurrent transaction, "PRAGMA integrity_check"
|
||||
# is unable to detect unused database pages, but can detect other types
|
||||
# of corruption.
|
||||
#
|
||||
reset_db
|
||||
do_test 8.1 {
|
||||
execsql {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE kv(k INTEGER PRIMARY KEY, v UNIQUE);
|
||||
INSERT INTO kv VALUES(NULL, randomblob(750));
|
||||
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
|
||||
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
|
||||
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
|
||||
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
|
||||
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
|
||||
DELETE FROM kv WHERE rowid%2;
|
||||
}
|
||||
set v [db one {PRAGMA freelist_count}]
|
||||
expr $v==33 || $v==34
|
||||
} {1}
|
||||
do_execsql_test 8.2 { PRAGMA integrity_check } ok
|
||||
do_execsql_test 8.3 {
|
||||
BEGIN CONCURRENT;
|
||||
PRAGMA integrity_check;
|
||||
} {ok}
|
||||
do_execsql_test 8.4 {
|
||||
INSERT INTO kv VALUES(1100, 1100);
|
||||
PRAGMA integrity_check;
|
||||
} {ok}
|
||||
do_execsql_test 8.5 {
|
||||
COMMIT;
|
||||
PRAGMA integrity_check;
|
||||
} {ok}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that concurrent transactions do not allow foreign-key constraints
|
||||
# to be bypassed.
|
||||
#
|
||||
do_multiclient_test tn {
|
||||
do_test 9.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE pp(i INTEGER PRIMARY KEY, j);
|
||||
CREATE TABLE cc(a, b REFERENCES pp);
|
||||
|
||||
WITH seq(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM seq WHERE i<100)
|
||||
INSERT INTO pp SELECT i, randomblob(1000) FROM seq;
|
||||
|
||||
PRAGMA foreign_keys = 1;
|
||||
}
|
||||
} {wal}
|
||||
|
||||
|
||||
do_test 9.$tn.2.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO cc VALUES(42, 42);
|
||||
}
|
||||
} {}
|
||||
do_test 9.$tn.2.2 {
|
||||
sql2 { DELETE FROM pp WHERE i=42 }
|
||||
list [catch { sql1 COMMIT } msg] $msg
|
||||
} {1 {database is locked}}
|
||||
do_test 9.$tn.2.3 {
|
||||
sql1 ROLLBACK
|
||||
} {}
|
||||
|
||||
do_test 9.$tn.3.1 {
|
||||
sql1 {
|
||||
PRAGMA foreign_keys = 0;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO cc VALUES(43, 43);
|
||||
}
|
||||
} {}
|
||||
do_test 9.$tn.3.2 {
|
||||
sql2 { DELETE FROM pp WHERE i=43 }
|
||||
list [catch { sql1 COMMIT } msg] $msg
|
||||
} {0 {}}
|
||||
|
||||
do_test 9.$tn.4.1 {
|
||||
sql1 {
|
||||
PRAGMA foreign_keys = on;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO cc VALUES(44, 44);
|
||||
}
|
||||
} {}
|
||||
do_test 9.$tn.4.2 {
|
||||
sql2 { DELETE FROM pp WHERE i=1 }
|
||||
list [catch { sql1 COMMIT } msg] $msg
|
||||
} {0 {}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that even if a SELECT statement appears before all writes within
|
||||
# a CONCURRENT transaction, the pages it reads are still considered when
|
||||
# considering whether or not the transaction may be committed.
|
||||
#
|
||||
do_multiclient_test tn {
|
||||
do_test 10.$tn.1.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(a);
|
||||
CREATE TABLE t2(b);
|
||||
CREATE TABLE t3(c);
|
||||
INSERT INTO t1 VALUES(1), (2), (3);
|
||||
INSERT INTO t2 VALUES(1), (2), (3);
|
||||
INSERT INTO t3 VALUES(1), (2), (3);
|
||||
}
|
||||
} {wal}
|
||||
|
||||
do_test 10.$tn.1.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t1;
|
||||
INSERT INTO t2 VALUES(4);
|
||||
}
|
||||
} {1 2 3}
|
||||
|
||||
do_test 10.$tn.1.3 {
|
||||
sql2 { INSERT INTO t1 VALUES(4) }
|
||||
list [catch {sql1 COMMIT} msg] $msg
|
||||
} {1 {database is locked}}
|
||||
sql1 ROLLBACK
|
||||
|
||||
# In this case, because the "SELECT * FROM t1" is first stepped before
|
||||
# the "BEGIN CONCURRENT", the pages it reads are not recorded by the
|
||||
# pager object. And so the transaction can be committed. Technically
|
||||
# this behaviour (the effect of an ongoing SELECT on a BEGIN CONCURRENT
|
||||
# transacation) is undefined.
|
||||
#
|
||||
do_test 10.$tn.2.1 {
|
||||
code1 {
|
||||
set ::stmt [sqlite3_prepare db "SELECT * FROM t1" -1 dummy]
|
||||
sqlite3_step $::stmt
|
||||
}
|
||||
} {SQLITE_ROW}
|
||||
do_test 10.$tn.2.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES(4);
|
||||
}
|
||||
code1 {
|
||||
set res [list]
|
||||
lappend res [sqlite3_column_int $::stmt 0]
|
||||
while {[sqlite3_step $::stmt]=="SQLITE_ROW"} {
|
||||
lappend res [sqlite3_column_int $::stmt 0]
|
||||
}
|
||||
sqlite3_finalize $::stmt
|
||||
set res
|
||||
}
|
||||
} {1 2 3 4}
|
||||
do_test 10.$tn.2.3 {
|
||||
sql2 { INSERT INTO t1 VALUES(5) }
|
||||
sql1 COMMIT
|
||||
} {}
|
||||
|
||||
# More tests surrounding long-lived prepared statements and concurrent
|
||||
# transactions.
|
||||
do_test 10.$tn.3.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t1;
|
||||
COMMIT;
|
||||
}
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES(5);
|
||||
}
|
||||
sql2 {
|
||||
INSERT INTO t1 VALUES(5);
|
||||
}
|
||||
sql1 COMMIT
|
||||
sql3 {
|
||||
SELECT * FROM t2;
|
||||
}
|
||||
} {1 2 3 4 5}
|
||||
do_test 10.$tn.3.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t1;
|
||||
ROLLBACK;
|
||||
}
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES(6);
|
||||
}
|
||||
sql2 {
|
||||
INSERT INTO t1 VALUES(6);
|
||||
}
|
||||
sql1 COMMIT
|
||||
sql3 { SELECT * FROM t2 }
|
||||
} {1 2 3 4 5 6}
|
||||
do_test 10.$tn.3.3 {
|
||||
sql1 { BEGIN CONCURRENT }
|
||||
code1 {
|
||||
set ::stmt [sqlite3_prepare db "SELECT * FROM t1" -1 dummy]
|
||||
sqlite3_step $::stmt
|
||||
}
|
||||
sql1 {
|
||||
INSERT INTO t2 VALUES(7);
|
||||
SELECT * FROM t3;
|
||||
ROLLBACK;
|
||||
BEGIN CONCURRENT;
|
||||
}
|
||||
sql2 { INSERT INTO t3 VALUES(5) }
|
||||
code1 { sqlite3_finalize $::stmt }
|
||||
sql1 {
|
||||
INSERT INTO t2 VALUES(8);
|
||||
COMMIT;
|
||||
}
|
||||
} {}
|
||||
}
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test 11.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(a);
|
||||
}
|
||||
} {wal}
|
||||
|
||||
do_test 11.$tn.2 {
|
||||
code1 { sqlite3_wal_info db main }
|
||||
} {0 2}
|
||||
|
||||
do_test 11.$tn.3 {
|
||||
sql1 { INSERT INTO t1 VALUES(1) }
|
||||
code1 { sqlite3_wal_info db main }
|
||||
} {2 3}
|
||||
|
||||
do_test 11.$tn.4 {
|
||||
sql2 { INSERT INTO t1 VALUES(2) }
|
||||
code2 { sqlite3_wal_info db2 main }
|
||||
} {3 4}
|
||||
|
||||
do_test 11.$tn.5 {
|
||||
sql1 { PRAGMA wal_checkpoint }
|
||||
sql2 { INSERT INTO t1 VALUES(3) }
|
||||
code2 { sqlite3_wal_info db2 main }
|
||||
} {0 1}
|
||||
}
|
||||
|
||||
reset_db
|
||||
do_execsql_test 12.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE tx(a INTEGER PRIMARY KEY, b);
|
||||
} {wal}
|
||||
do_test 12.1 {
|
||||
for {set i 0} {$i < 50} {incr i} {
|
||||
execsql {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO tx(b) VALUES( randomblob( 1200 ) );
|
||||
COMMIT;
|
||||
}
|
||||
}
|
||||
execsql { PRAGMA page_size }
|
||||
} {1024}
|
||||
do_execsql_test 12.2 {
|
||||
DELETE FROM tx;
|
||||
}
|
||||
do_test 12.3 {
|
||||
for {set i 0} {$i < 50} {incr i} {
|
||||
execsql {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO tx(b) VALUES( randomblob( 1200 ) );
|
||||
COMMIT;
|
||||
}
|
||||
}
|
||||
execsql { PRAGMA page_size }
|
||||
} {1024}
|
||||
do_execsql_test 12.4 {
|
||||
DELETE FROM tx;
|
||||
}
|
||||
do_test 12.5 {
|
||||
execsql { BEGIN CONCURRENT }
|
||||
for {set i 0} {$i < 5000} {incr i} {
|
||||
execsql {
|
||||
INSERT INTO tx(b) VALUES( randomblob( 1200 ) );
|
||||
}
|
||||
}
|
||||
execsql { COMMIT }
|
||||
execsql { PRAGMA page_size }
|
||||
} {1024}
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,231 @@
|
||||
# 2015 July 26
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Tests for transactions started with BEGIN CONCURRENT. The tests in this
|
||||
# file focus on testing that deferred page allocation works properly.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
set ::testprefix concurrent3
|
||||
|
||||
if {$AUTOVACUUM} { finish_test ; return }
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
db close
|
||||
sqlite3_shutdown
|
||||
test_sqlite3_log xLog
|
||||
proc xLog {error_code msg} {
|
||||
# puts "$error_code: $msg"
|
||||
# Enable the previous for debugging
|
||||
}
|
||||
reset_db
|
||||
|
||||
proc create_schema {} {
|
||||
db eval {
|
||||
PRAGMA journal_mode = wal;
|
||||
|
||||
CREATE TABLE t1(x, y);
|
||||
CREATE TABLE t2(x, y);
|
||||
CREATE TABLE t3(x, y);
|
||||
CREATE TABLE t4(x, y);
|
||||
|
||||
CREATE INDEX i1 ON t1(y, x);
|
||||
CREATE INDEX i2 ON t2(y, x);
|
||||
CREATE INDEX i3 ON t3(y, x);
|
||||
CREATE INDEX i4 ON t4(y, x);
|
||||
}
|
||||
}
|
||||
|
||||
proc do_sql_op {iTbl iOp} {
|
||||
set db "db$iTbl"
|
||||
|
||||
switch $iOp {
|
||||
"i" {
|
||||
set sql "
|
||||
WITH cnt(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM cnt WHERE i<10)
|
||||
INSERT INTO t$iTbl SELECT randomblob(800), randomblob(800) FROM cnt;
|
||||
"
|
||||
}
|
||||
|
||||
"d" {
|
||||
set sql "
|
||||
DELETE FROM t$iTbl WHERE rowid IN (
|
||||
SELECT rowid FROM t$iTbl ORDER BY 1 ASC LIMIT 10
|
||||
)
|
||||
"
|
||||
}
|
||||
|
||||
"D" {
|
||||
set sql "
|
||||
DELETE FROM t$iTbl WHERE rowid IN (
|
||||
SELECT rowid FROM t$iTbl o WHERE (
|
||||
SELECT count(*) FROM t$iTbl i WHERE i.rowid<o.rowid
|
||||
) % 2
|
||||
)
|
||||
"
|
||||
}
|
||||
|
||||
"I" {
|
||||
set sql "
|
||||
INSERT INTO t$iTbl SELECT randomblob(800), randomblob(800) FROM t$iTbl;
|
||||
"
|
||||
}
|
||||
|
||||
default {
|
||||
error "bad iOp parameter: $iOp"
|
||||
}
|
||||
}
|
||||
|
||||
$db eval $sql
|
||||
}
|
||||
|
||||
|
||||
set DBLIST {db1 db2 db3 db4}
|
||||
|
||||
create_schema
|
||||
foreach {tn oplist} {
|
||||
1 {1i 2i 3i 4i}
|
||||
2 {1iii 2iii 3iii 4iii}
|
||||
3 {1d 2d 3d 4d}
|
||||
. -----------------------
|
||||
4 {1i}
|
||||
5 {1d 2i}
|
||||
. -----------------------
|
||||
6 {1iii 2iii 3iii 4iii}
|
||||
7 {1di 2id 3iii 4ddd}
|
||||
8 {1iii 2iii 3iii 4iii}
|
||||
9 {1D 2II}
|
||||
10 {1I 2D 3I 4D}
|
||||
11 {1III 3dddddd 4III}
|
||||
} {
|
||||
if {[string range $oplist 0 0]=="-"} {
|
||||
reset_db
|
||||
create_schema
|
||||
continue
|
||||
}
|
||||
foreach db $DBLIST { sqlite3 $db test.db }
|
||||
|
||||
do_test 1.$tn {
|
||||
foreach db $DBLIST { $db eval "BEGIN CONCURRENT" }
|
||||
|
||||
foreach op $oplist {
|
||||
set iTbl [string range $op 0 0]
|
||||
foreach char [split [string range $op 1 end] {}] {
|
||||
do_sql_op $iTbl $char
|
||||
}
|
||||
}
|
||||
|
||||
foreach db $DBLIST { $db eval "COMMIT" }
|
||||
db eval {PRAGMA integrity_check}
|
||||
} {ok}
|
||||
|
||||
foreach db $DBLIST {
|
||||
$db close
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
proc create_schema2 {} {
|
||||
db eval {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
|
||||
CREATE INDEX i1 ON t1(y);
|
||||
}
|
||||
}
|
||||
|
||||
proc randint {nMax} {
|
||||
db eval {SELECT abs(random() % $nMax)}
|
||||
}
|
||||
|
||||
proc do_sql_op2 {db iOp} {
|
||||
switch -- $iOp {
|
||||
i {
|
||||
# Insert 1 rows.
|
||||
set r [randint 1000000000]
|
||||
set ::rows($r) 1
|
||||
#puts "insert row $r"
|
||||
$db eval { INSERT OR IGNORE INTO t1 VALUES($r, randomblob(50)); }
|
||||
}
|
||||
|
||||
d {
|
||||
# Insert 1 row
|
||||
set keys [array names ::rows]
|
||||
set r [randint [llength $keys]]
|
||||
set rowid [lindex $keys $r]
|
||||
$db eval { DELETE FROM t1 WHERE x=$rowid }
|
||||
unset ::rows($rowid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach {tn nRepeat oplist} {
|
||||
- - ----------------------------
|
||||
1 100 { 1iiiiiiiiii }
|
||||
2 100 { 1i 2d }
|
||||
3 100 { 1d 2i }
|
||||
4 50 { 1d 2i 3d }
|
||||
5 500 { 1i 2i 3i 4i }
|
||||
6 500 { 1i 2d 3d 4d }
|
||||
} {
|
||||
if {[string range $oplist 0 0]=="-"} {
|
||||
array unset rows
|
||||
reset_db
|
||||
create_schema2
|
||||
continue
|
||||
}
|
||||
|
||||
foreach db $DBLIST {
|
||||
sqlite3 $db test.db
|
||||
set stats($db,0) 0
|
||||
set stats($db,1) 0
|
||||
}
|
||||
array unset used
|
||||
|
||||
do_test 2.$tn {
|
||||
|
||||
for {set i 0} {$i < $nRepeat} {incr i} {
|
||||
foreach db $DBLIST { $db eval "BEGIN CONCURRENT" }
|
||||
|
||||
foreach op $oplist {
|
||||
set iDb [string range $op 0 0]
|
||||
set used(db$iDb) 1
|
||||
foreach char [split [string range $op 1 end] {}] {
|
||||
do_sql_op2 "db$iDb" $char
|
||||
}
|
||||
}
|
||||
|
||||
foreach db $DBLIST {
|
||||
set rc [catch { $db eval COMMIT } msg]
|
||||
if {$rc} { $db eval ROLLBACK }
|
||||
incr stats($db,$rc)
|
||||
}
|
||||
set res [db eval {PRAGMA integrity_check}]
|
||||
if {$res != "ok"} { puts "after $db $rc: $res" ; after 1000000 }
|
||||
}
|
||||
} {}
|
||||
|
||||
foreach db $DBLIST {
|
||||
$db close
|
||||
}
|
||||
# foreach k [lsort [array names used]] {
|
||||
# puts "$k: $stats($k,0) committed, $stats($k,1) rolled back"
|
||||
# }
|
||||
}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,194 @@
|
||||
# 2017 May 26
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Miscellaneous tests for transactions started with BEGIN CONCURRENT.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
set ::testprefix concurrent4
|
||||
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x PRIMARY KEY, y UNIQUE);
|
||||
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM s;
|
||||
DELETE FROM t1 WHERE rowid<2;
|
||||
} {wal}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1(rowid, x, y) VALUES(1000, randomblob(3000), randomblob(3000));
|
||||
SAVEPOINT abc;
|
||||
DELETE FROM t1 WHERE rowid = 1000;
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 { ROLLBACK TO abc }
|
||||
do_execsql_test 1.3 { COMMIT }
|
||||
do_execsql_test 1.4 { PRAGMA integrity_check } {ok}
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test 2.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(a, b);
|
||||
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM s;
|
||||
|
||||
CREATE TABLE t2(a, b);
|
||||
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t2 SELECT randomblob(400), randomblob(400) FROM s;
|
||||
}
|
||||
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(3000), randomblob(3000));
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.2 {
|
||||
sql2 {
|
||||
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<10)
|
||||
INSERT INTO t2 SELECT randomblob(400), randomblob(400) FROM s;
|
||||
}
|
||||
sql2 {
|
||||
DELETE FROM t2 WHERE rowid<10;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.3 {
|
||||
sql1 {
|
||||
COMMIT;
|
||||
PRAGMA integrity_check;
|
||||
}
|
||||
} {ok}
|
||||
do_test 2.$tn.4 {
|
||||
sql2 {
|
||||
PRAGMA integrity_check;
|
||||
}
|
||||
} {ok}
|
||||
}
|
||||
|
||||
reset_db
|
||||
do_execsql_test 3.1 {
|
||||
PRAGMA page_size = 1024;
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t2(x);
|
||||
INSERT INTO t2 VALUES(randomblob(5000));
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(25, randomblob(104));
|
||||
DELETE FROM t2;
|
||||
} {wal}
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
BEGIN CONCURRENT;
|
||||
REPLACE INTO t1 VALUES(25, randomblob(1117));
|
||||
COMMIT;
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test the effect of BEGIN CONCURRENT transactions that consist entirely
|
||||
# of read-only statements.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 4.0 {
|
||||
PRAGMA page_size = 1024;
|
||||
PRAGMA journal_mode = wal;
|
||||
|
||||
CREATE TABLE t4(a, b);
|
||||
INSERT INTO t4 VALUES(1, 2);
|
||||
INSERT INTO t4 VALUES(3, 4);
|
||||
} {wal}
|
||||
|
||||
sqlite3 db2 test.db
|
||||
do_test 4.1.1 {
|
||||
db eval {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t4 VALUES(5, 6);
|
||||
}
|
||||
|
||||
db2 eval {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t4;
|
||||
ROLLBACK;
|
||||
}
|
||||
} {1 2 3 4}
|
||||
|
||||
do_test 4.1.2 {
|
||||
db eval { COMMIT }
|
||||
db2 eval { SELECT * FROM t4 }
|
||||
} {1 2 3 4 5 6}
|
||||
|
||||
do_test 4.2.1 {
|
||||
db eval {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t4 VALUES(7, 8);
|
||||
}
|
||||
|
||||
db2 eval {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t4;
|
||||
COMMIT;
|
||||
}
|
||||
} {1 2 3 4 5 6}
|
||||
|
||||
do_test 4.2.2 {
|
||||
db eval { COMMIT }
|
||||
db2 eval { SELECT * FROM t4 }
|
||||
} {1 2 3 4 5 6 7 8}
|
||||
|
||||
do_test 4.3 {
|
||||
db2 eval {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t4;
|
||||
}
|
||||
|
||||
db eval {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t4 VALUES(9, 10);
|
||||
COMMIT;
|
||||
}
|
||||
db2 eval {
|
||||
SELECT * FROM t4;
|
||||
COMMIT;
|
||||
}
|
||||
} {1 2 3 4 5 6 7 8}
|
||||
|
||||
set sz [file size test.db-wal]
|
||||
do_test 4.4.1 {
|
||||
db eval {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t4;
|
||||
SELECT * FROM sqlite_master;
|
||||
}
|
||||
|
||||
db eval COMMIT
|
||||
file size test.db-wal
|
||||
} $sz
|
||||
do_test 4.4.2 {
|
||||
db eval {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t4;
|
||||
SELECT * FROM sqlite_master;
|
||||
ROLLBACK;
|
||||
}
|
||||
file size test.db-wal
|
||||
} $sz
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# 2017 May 26
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
set ::testprefix concurrent5
|
||||
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
db close
|
||||
sqlite3_shutdown
|
||||
test_sqlite3_log [list lappend ::log]
|
||||
set ::log [list]
|
||||
|
||||
sqlite3 db test.db
|
||||
|
||||
proc do_test_conflict_msg {tn msg} {
|
||||
set msg "cannot commit CONCURRENT transaction - [string trim $msg]"
|
||||
uplevel [list do_test $tn {lindex $::log end} $msg]
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x, y);
|
||||
CREATE TABLE t2(c);
|
||||
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM s;
|
||||
} {wal}
|
||||
|
||||
sqlite3 db2 test.db
|
||||
|
||||
do_test 1.1.1 {
|
||||
set ::log [list]
|
||||
db2 eval {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT count(*) FROM t1;
|
||||
INSERT INTO t2 VALUES(10);
|
||||
}
|
||||
|
||||
db eval {
|
||||
INSERT INTO t1 VALUES(randomblob(200), randomblob(200));
|
||||
}
|
||||
|
||||
catchsql COMMIT db2
|
||||
} {1 {database is locked}}
|
||||
do_test_conflict_msg 1.1.2 {
|
||||
conflict at page 2 (read-only page; part of db table t1; content=0500000063021100...)
|
||||
}
|
||||
|
||||
do_test 1.2.1 {
|
||||
set ::log [list]
|
||||
db2 eval {
|
||||
ROLLBACK;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(11, 12);
|
||||
}
|
||||
|
||||
db eval {
|
||||
INSERT INTO t1 VALUES(12, 11);
|
||||
}
|
||||
|
||||
catchsql COMMIT db2
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test_conflict_msg 1.2.2 {
|
||||
conflict at page 105 (read/write page; part of db table t1; content=0D00000002026100...)
|
||||
}
|
||||
|
||||
do_test 1.3.1 {
|
||||
set ::log [list]
|
||||
db2 eval {
|
||||
ROLLBACK;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES('x');
|
||||
}
|
||||
|
||||
db eval {
|
||||
INSERT INTO t2 VALUES('y');
|
||||
}
|
||||
|
||||
catchsql COMMIT db2
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test_conflict_msg 1.3.2 {
|
||||
conflict at page 3 (read/write page; part of db table t2; content=0D0000000103FB00...)
|
||||
}
|
||||
|
||||
do_test 1.4.1 {
|
||||
set ::log [list]
|
||||
|
||||
execsql {
|
||||
ROLLBACK;
|
||||
CREATE TABLE t3(a INTEGER PRIMARY KEY, b INTEGER);
|
||||
CREATE INDEX i3 ON t3(b);
|
||||
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<5000
|
||||
) INSERT INTO t3 SELECT i, i FROM s;
|
||||
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t3 VALUES(0, 5001);
|
||||
} db2
|
||||
|
||||
execsql { INSERT INTO t3 VALUES(NULL, 5002) } db
|
||||
catchsql COMMIT db2
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test_conflict_msg 1.3.2 {
|
||||
conflict at page 211 (read/write page; part of db index t3.i3; content=0A0310006300D800...)
|
||||
}
|
||||
|
||||
db close
|
||||
db2 close
|
||||
sqlite3_shutdown
|
||||
test_sqlite3_log
|
||||
sqlite3_initialize
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# 2017 May 26
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
set ::testprefix concurrent6
|
||||
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
sqlite3 db2 test.db
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA page_size = 1024;
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE t2(x);
|
||||
CREATE TABLE t3(x);
|
||||
CREATE TABLE t4(x);
|
||||
|
||||
INSERT INTO t1 VALUES(zeroblob(1500));
|
||||
} {wal}
|
||||
|
||||
do_execsql_test -db db2 1.1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t3 VALUES(zeroblob(4000));
|
||||
DELETE FROM t1;
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t2 SELECT zeroblob(1000) FROM s;
|
||||
|
||||
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t4 SELECT zeroblob(1000) FROM s;
|
||||
|
||||
DELETE FROM t4;
|
||||
}
|
||||
|
||||
do_execsql_test -db db2 1.3 {
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# 2018 Jan 5
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set ::testprefix concurrent7
|
||||
|
||||
sqlite3 db2 test.db
|
||||
|
||||
do_execsql_test 1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE t2(x);
|
||||
} {wal}
|
||||
|
||||
do_execsql_test -db db2 2 {
|
||||
SELECT * FROM t1;
|
||||
}
|
||||
|
||||
do_execsql_test 3 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
DELETE FROM t1 WHERE rowid = 1;
|
||||
}
|
||||
|
||||
do_execsql_test -db db2 4 {
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
DELETE FROM t2 WHERE rowid IN (1, 2);
|
||||
}
|
||||
|
||||
do_execsql_test 5 {
|
||||
COMMIT;
|
||||
PRAGMA integrity_check;
|
||||
} {ok}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# 2020 July 17
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set ::testprefix concurrent8
|
||||
|
||||
source $testdir/lock_common.tcl
|
||||
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_multiclient_test tn {
|
||||
|
||||
do_test 1.$tn.0 {
|
||||
sql1 {
|
||||
CREATE TABLE t1(x, y);
|
||||
PRAGMA journal_mode = wal;
|
||||
}
|
||||
} {wal}
|
||||
|
||||
do_test 1.$tn.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(1, 1);
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 1.$tn.2 {
|
||||
sql2 {
|
||||
CREATE TABLE t2(a, b);
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 1.$tn.3 {
|
||||
list [catch { sql1 { COMMIT } } msg] $msg
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test 1.$tn.4 {
|
||||
code1 { db errorcode }
|
||||
} {517} ;# SQLITE_BUSY_SNAPSHOT
|
||||
|
||||
do_test 1.$tn.5 {
|
||||
sql1 {
|
||||
ROLLBACK;
|
||||
BEGIN CONCURRENT;
|
||||
CREATE TABLE t3(a, b);
|
||||
COMMIT;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 1.$tn.6 {
|
||||
set nPg [sql1 {PRAGMA page_count}]
|
||||
sql1 "BEGIN CONCURRENT"
|
||||
for {set i 0} {$i<250} {incr i} {
|
||||
sql1 "CREATE TABLE z$i (a, b, c)"
|
||||
}
|
||||
sql1 "COMMIT"
|
||||
set nPg2 [sql1 {PRAGMA page_count}]
|
||||
expr $nPg2>$nPg
|
||||
} {1}
|
||||
|
||||
do_test 1.$tn.7 {
|
||||
sql2 { PRAGMA integrity_check }
|
||||
} {ok}
|
||||
|
||||
do_test 1.$tn.8 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
CREATE TABLE t4(a, b);
|
||||
}
|
||||
sql2 {
|
||||
INSERT INTO t1 VALUES(2, 2);
|
||||
}
|
||||
list [catch { sql1 COMMIT } msg] $msg
|
||||
} {1 {database is locked}}
|
||||
sql1 ROLLBACK
|
||||
|
||||
do_test 1.$tn.9 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
CREATE TEMP TABLE t5(a, b);
|
||||
INSERT INTO t2 VALUES('x', 'x');
|
||||
}
|
||||
sql2 {
|
||||
INSERT INTO t1 VALUES(3, 3);
|
||||
CREATE TEMP TABLE t1(x, y);
|
||||
}
|
||||
sql1 COMMIT
|
||||
} {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
+1
-1
@@ -146,7 +146,7 @@ do_catchsql_test 2.1 {
|
||||
SELECT count(*) FROM sqlite_schema;
|
||||
WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<1000)
|
||||
INSERT INTO t1(a) SELECT randomblob(null) FROM c;
|
||||
} {1 {database disk image is malformed}}
|
||||
} {0 4}
|
||||
|
||||
reset_db
|
||||
if {![info exists ::G(perm:presql)]} {
|
||||
|
||||
@@ -241,5 +241,35 @@ do_execsql_test 7.2 {
|
||||
PRAGMA foreign_key_check;
|
||||
} {}
|
||||
|
||||
# 2021-12-31 forum https://sqlite.org/forum/forumpost/24bd1fef7e9323ef
|
||||
# Memory leak caused by sqlite3NestedParse() running on a corrupt system
|
||||
# table. Discovered by Jingzhou Fu.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 8.1 {
|
||||
PRAGMA writable_schema=ON;
|
||||
PRAGMA foreign_keys = ON;
|
||||
CREATE TABLE sqlite_stat1 (tbl INTEGER PRIMARY KEY DESC, idx UNIQUE DEFAULT NULL) WITHOUT ROWID;
|
||||
PRAGMA writable_schema=OFF;
|
||||
CREATE TABLE sqlsim4(stat PRIMARY KEY);;
|
||||
CREATE TABLE t1(sqlsim7 REFERENCES sqlite_stat1 ON DELETE CASCADE);
|
||||
DROP table "sqlsim4";
|
||||
} {}
|
||||
# 2022-01-01 dbsqlfuzz 1c57440219f6f0aedf5e8f72a8ddd75f15aea381
|
||||
# Follow-up case to the above. Assertion is not true if the schema
|
||||
# is corrupt.
|
||||
reset_db
|
||||
database_may_be_corrupt
|
||||
do_execsql_test 8.2 {
|
||||
CREATE TABLE t1(a REFERENCES sqlite_stat1 ON DELETE CASCADE);
|
||||
CREATE TABLE t2(a TEXT PRIMARY KEY);
|
||||
PRAGMA writable_schema=ON;
|
||||
CREATE TABLE sqlite_stat1(tbl INTEGER PRIMARY KEY DESC, idx UNIQUE DEFAULT NULL) WITHOUT ROWID;
|
||||
UPDATE sqlite_schema SET name='sqlite_autoindex_sqlite_stat1_1' WHERE name='sqlite_autoindex_sqlite_stat1_2';
|
||||
PRAGMA writable_schema=RESET;
|
||||
} {}
|
||||
do_catchsql_test 8.3 {
|
||||
REINDEX;
|
||||
} {1 {database disk image is malformed}}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# 2022 Jan 01
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# Tests focused on the in-memory journal.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
set testprefix memjournal2
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA journal_mode = memory;
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE);
|
||||
} {memory}
|
||||
|
||||
set nRow [expr 2000]
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
BEGIN;
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$nRow
|
||||
)
|
||||
INSERT INTO t1 SELECT NULL, randomblob(700) FROM s;
|
||||
}
|
||||
|
||||
for {set jj 200} {$jj <= 300} {incr jj} {
|
||||
do_execsql_test 1.2.$jj.1 {
|
||||
SAVEPOINT one;
|
||||
UPDATE t1 SET b=randomblob(700) WHERE a<=$jj;
|
||||
}
|
||||
do_execsql_test 1.2.$jj.2 {
|
||||
SAVEPOINT two;
|
||||
UPDATE t1 SET b=randomblob(700) WHERE a==1;
|
||||
ROLLBACK TO two;
|
||||
RELEASE two;
|
||||
}
|
||||
do_execsql_test 1.2.$jj.3 {
|
||||
SAVEPOINT two;
|
||||
UPDATE t1 SET b=randomblob(700) WHERE a==1;
|
||||
ROLLBACK TO two;
|
||||
RELEASE two;
|
||||
}
|
||||
|
||||
do_execsql_test 1.2.$jj.4 {
|
||||
PRAGMA integrity_check;
|
||||
ROLLBACK TO one;
|
||||
RELEASE one;
|
||||
} {ok}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# 2020 September 01
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix noop_update
|
||||
|
||||
if {[db eval {PRAGMA noop_update}]==""} {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(x, y);
|
||||
INSERT INTO t1 VALUES('a', 111);
|
||||
}
|
||||
do_execsql_test 1.1 {
|
||||
UPDATE t1 SET y=222 WHERE x='a';
|
||||
SELECT * FROM t1;
|
||||
} {a 222}
|
||||
do_execsql_test 1.2 {
|
||||
PRAGMA noop_update = 1;
|
||||
UPDATE t1 SET y=333 WHERE x='a';
|
||||
SELECT * FROM t1;
|
||||
} {a 222}
|
||||
|
||||
finish_test
|
||||
@@ -1,103 +0,0 @@
|
||||
# 2014-08-24
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library.
|
||||
# The focus of this script is testing details of the SQL language parser.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
do_catchsql_test parser1-1.1 {
|
||||
CREATE TABLE t1(
|
||||
a TEXT PRIMARY KEY,
|
||||
b TEXT,
|
||||
FOREIGN KEY(b COLLATE nocase DESC) REFERENCES t1(a COLLATE binary ASC)
|
||||
);
|
||||
} {1 {syntax error after column name "b"}}
|
||||
|
||||
|
||||
# Verify that a legacy schema in the sqlite_master file is allowed to have
|
||||
# COLLATE, ASC, and DESC keywords on the id list of a FK constraint, and that
|
||||
# those keywords are silently ignored.
|
||||
#
|
||||
sqlite3_db_config db DEFENSIVE 0
|
||||
do_execsql_test parser1-1.2 {
|
||||
CREATE TABLE t1(
|
||||
a TEXT PRIMARY KEY,
|
||||
b TEXT,
|
||||
FOREIGN KEY(b) REFERENCES t1(a)
|
||||
);
|
||||
INSERT INTO t1 VALUES('abc',NULL),('xyz','abc');
|
||||
PRAGMA writable_schema=on;
|
||||
UPDATE sqlite_master SET sql='CREATE TABLE t1(
|
||||
a TEXT PRIMARY KEY,
|
||||
b TEXT,
|
||||
FOREIGN KEY(b COLLATE nocase) REFERENCES t1(a)
|
||||
)' WHERE name='t1';
|
||||
SELECT name FROM sqlite_master WHERE sql LIKE '%collate%';
|
||||
} {t1}
|
||||
sqlite3 db2 test.db
|
||||
do_test parser1-1.3 {
|
||||
sqlite3 db2 test.db
|
||||
db2 eval {SELECT * FROM t1 ORDER BY 1}
|
||||
} {abc {} xyz abc}
|
||||
db2 close
|
||||
|
||||
do_execsql_test parser1-1.4 {
|
||||
UPDATE sqlite_master SET sql='CREATE TABLE t1(
|
||||
a TEXT PRIMARY KEY,
|
||||
b TEXT,
|
||||
FOREIGN KEY(b ASC) REFERENCES t1(a)
|
||||
)' WHERE name='t1';
|
||||
SELECT name FROM sqlite_master WHERE sql LIKE '%ASC%';
|
||||
} {t1}
|
||||
sqlite3 db2 test.db
|
||||
do_test parser1-1.5 {
|
||||
sqlite3 db2 test.db
|
||||
db2 eval {SELECT * FROM t1 ORDER BY 1}
|
||||
} {abc {} xyz abc}
|
||||
db2 close
|
||||
|
||||
do_catchsql_test parser1-2.1 {
|
||||
WITH RECURSIVE
|
||||
c(x COLLATE binary) AS (VALUES(1) UNION SELECT x+1 FROM c WHERE x<5)
|
||||
SELECT x FROM c;
|
||||
} {1 {syntax error after column name "x"}}
|
||||
do_catchsql_test parser1-2.2 {
|
||||
WITH RECURSIVE
|
||||
c(x ASC) AS (VALUES(1) UNION SELECT x+1 FROM c WHERE x<5)
|
||||
SELECT x FROM c;
|
||||
} {1 {syntax error after column name "x"}}
|
||||
|
||||
# Verify that the comma between multiple table constraints is
|
||||
# optional.
|
||||
#
|
||||
# The missing comma is technically a syntax error. But we have to support
|
||||
# it because there might be legacy databases that omit the commas in their
|
||||
# sqlite_master tables.
|
||||
#
|
||||
do_execsql_test parser1-3.1 {
|
||||
CREATE TABLE t300(id INTEGER PRIMARY KEY);
|
||||
CREATE TABLE t301(
|
||||
id INTEGER PRIMARY KEY,
|
||||
c1 INTEGER NOT NULL,
|
||||
c2 INTEGER NOT NULL,
|
||||
c3 BOOLEAN NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY(c1) REFERENCES t300(id) ON DELETE CASCADE ON UPDATE RESTRICT
|
||||
/* no comma */
|
||||
FOREIGN KEY(c2) REFERENCES t300(id) ON DELETE CASCADE ON UPDATE RESTRICT
|
||||
/* no comma */
|
||||
UNIQUE(c1, c2)
|
||||
);
|
||||
PRAGMA foreign_key_list(t301);
|
||||
} {0 0 t300 c2 id RESTRICT CASCADE NONE 1 0 t300 c1 id RESTRICT CASCADE NONE}
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,36 @@
|
||||
# 2022 January 5
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1 (b);
|
||||
} {}
|
||||
faultsim_save_and_close
|
||||
|
||||
do_faultsim_test pagerfault-1 -faults oom-t* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
execsql {
|
||||
INSERT INTO t1(b) VALUES(65) RETURNING (
|
||||
SELECT * FROM sqlite_temp_schema
|
||||
) AS aaa;
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {1 {sub-select returns 5 columns - expected 1}}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
+1035
File diff suppressed because it is too large
Load Diff
@@ -241,4 +241,18 @@ do_catchsql_test upsert1-1000 {
|
||||
ON CONFLICT(c2) DO UPDATE SET c1 = c0;
|
||||
} {1 {NOT NULL constraint failed: t0.c0}}
|
||||
|
||||
# 2021-12-29 forum post https://sqlite.org/forum/forumpost/06b16b8b29f8c8c3
|
||||
# By Jingzhou Fu. When there is both an INTEGER PRIMARY KEY ON CONFLICT REPLACE
|
||||
# and an upsert on a constraint other than the INTEGER PRIMARY KEY, the
|
||||
# constraint checking logic generates invalid bytecode which might result
|
||||
# in a NULL pointer dereference.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test upsert1-1100 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY ON CONFLICT REPLACE, b UNIQUE);
|
||||
INSERT INTO t1(b) VALUES(22);
|
||||
INSERT INTO t1 VALUES(2,22) ON CONFLICT (b) DO NOTHING;
|
||||
SELECT * FROM t1;
|
||||
} {1 22}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -35,43 +35,6 @@ proc cond_incr_sync_count {adj} {
|
||||
}
|
||||
}
|
||||
|
||||
proc set_tvfs_hdr {file args} {
|
||||
|
||||
# Set $nHdr to the number of bytes in the wal-index header:
|
||||
set nHdr 48
|
||||
set nInt [expr {$nHdr/4}]
|
||||
|
||||
if {[llength $args]>2} {
|
||||
error {wrong # args: should be "set_tvfs_hdr fileName ?val1? ?val2?"}
|
||||
}
|
||||
|
||||
set blob [tvfs shm $file]
|
||||
if {$::tcl_platform(byteOrder)=="bigEndian"} {set fmt I} {set fmt i}
|
||||
|
||||
if {[llength $args]} {
|
||||
set ia [lindex $args 0]
|
||||
set ib $ia
|
||||
if {[llength $args]==2} {
|
||||
set ib [lindex $args 1]
|
||||
}
|
||||
binary scan $blob a[expr $nHdr*2]a* dummy tail
|
||||
set blob [binary format ${fmt}${nInt}${fmt}${nInt}a* $ia $ib $tail]
|
||||
tvfs shm $file $blob
|
||||
}
|
||||
|
||||
binary scan $blob ${fmt}${nInt} ints
|
||||
return $ints
|
||||
}
|
||||
|
||||
proc incr_tvfs_hdr {file idx incrval} {
|
||||
set ints [set_tvfs_hdr $file]
|
||||
set v [lindex $ints $idx]
|
||||
incr v $incrval
|
||||
lset ints $idx $v
|
||||
set_tvfs_hdr $file $ints
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test case wal2-1.*:
|
||||
#
|
||||
|
||||
@@ -90,4 +90,44 @@ proc wal_fix_walindex_cksum {hdrvar} {
|
||||
lset hdr 11 $c2
|
||||
}
|
||||
|
||||
# This command assumes that $file is the name of a database file opened
|
||||
# in wal mode using a [testvfs] VFS. It returns a list of the 12 32-bit
|
||||
# integers that make up the wal-index-header for the named file.
|
||||
#
|
||||
proc set_tvfs_hdr {file args} {
|
||||
|
||||
# Set $nHdr to the number of bytes in the wal-index header:
|
||||
set nHdr 48
|
||||
set nInt [expr {$nHdr/4}]
|
||||
|
||||
if {[llength $args]>2} {
|
||||
error {wrong # args: should be "set_tvfs_hdr fileName ?val1? ?val2?"}
|
||||
}
|
||||
|
||||
set blob [tvfs shm $file]
|
||||
if {$::tcl_platform(byteOrder)=="bigEndian"} {set fmt I} {set fmt i}
|
||||
|
||||
if {[llength $args]} {
|
||||
set ia [lindex $args 0]
|
||||
set ib $ia
|
||||
if {[llength $args]==2} {
|
||||
set ib [lindex $args 1]
|
||||
}
|
||||
binary scan $blob a[expr $nHdr*2]a* dummy tail
|
||||
set blob [binary format ${fmt}${nInt}${fmt}${nInt}a* $ia $ib $tail]
|
||||
tvfs shm $file $blob
|
||||
}
|
||||
|
||||
binary scan $blob ${fmt}${nInt} ints
|
||||
return $ints
|
||||
}
|
||||
|
||||
proc incr_tvfs_hdr {file idx incrval} {
|
||||
set ints [set_tvfs_hdr $file]
|
||||
set v [lindex $ints $idx]
|
||||
incr v $incrval
|
||||
lset ints $idx $v
|
||||
set_tvfs_hdr $file $ints
|
||||
}
|
||||
|
||||
|
||||
|
||||
+10
-2
@@ -13,7 +13,7 @@
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix windowB
|
||||
set testprefix windowC
|
||||
|
||||
ifcapable !windowfunc {
|
||||
finish_test
|
||||
@@ -67,11 +67,19 @@ foreach {tn bBlob seps} {
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 2.0 {
|
||||
PRAGMA encoding=UTF16;
|
||||
PRAGMA encoding=UTF16le;
|
||||
WITH separator(x) AS (VALUES(',a,'),(',bc,')),
|
||||
value(y) AS (VALUES(1),(x'5585d09013455178cd11ce4a'))
|
||||
SELECT group_concat(y,x) OVER (ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING)
|
||||
FROM separator, value;
|
||||
} {{} 1 蕕郐䔓硑ᇍ䫎 1}
|
||||
reset_db
|
||||
do_execsql_test 2.1 {
|
||||
PRAGMA encoding=UTF16be;
|
||||
WITH separator(x) AS (VALUES(',a,'),(',bc,')),
|
||||
value(y) AS (VALUES(1),(x'5585d09013455178cd11ce4a'))
|
||||
SELECT group_concat(y,x) OVER (ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING)
|
||||
FROM separator, value;
|
||||
} {{} 1 喅킐ፅ典촑칊 1}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -470,5 +470,20 @@ ifcapable altertable {
|
||||
EXPLAIN QUERY PLAN SELECT * FROM dual, t1 WHERE a=10 AND b=10;
|
||||
} {~/b=/}
|
||||
}
|
||||
|
||||
|
||||
# 2022-01-01 https://sqlite.org/forum/forumpost/b03d86f951 PoC #1
|
||||
# Omit an assert() from 2013 that no longer serves any purpose and
|
||||
# is no longer always true.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 15.1 {
|
||||
PRAGMA writable_schema=ON;
|
||||
CREATE TABLE sqlite_sequence (name PRIMARY KEY) WITHOUT ROWID;
|
||||
PRAGMA writable_schema=OFF;
|
||||
CREATE TABLE c1(x);
|
||||
INSERT INTO sqlite_sequence(name) VALUES('c0'),('c1'),('c2');
|
||||
ALTER TABLE c1 RENAME TO a;
|
||||
SELECT name FROM sqlite_sequence ORDER BY +name;
|
||||
} {a c0 c2}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -75,6 +75,9 @@ static sqlite3_uint64 logEstToInt(LogEst x){
|
||||
x /= 10;
|
||||
if( n>=5 ) n -= 2;
|
||||
else if( n>=1 ) n -= 1;
|
||||
if( x>60 ){
|
||||
return (((sqlite3_uint64)0xffffffff)<<32)+(sqlite3_uint64)0xffffffff;
|
||||
}
|
||||
if( x>=3 ) return (n+8)<<(x-3);
|
||||
return (n+8)>>(3-x);
|
||||
}
|
||||
|
||||
@@ -107,6 +107,12 @@ set pragma_def {
|
||||
IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
|
||||
IF: defined(SQLITE_DEBUG)
|
||||
|
||||
NAME: noop_update
|
||||
TYPE: FLAG
|
||||
ARG: SQLITE_NoopUpdate
|
||||
IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
|
||||
IF: defined(SQLITE_ENABLE_NOOP_UPDATE)
|
||||
|
||||
NAME: ignore_check_constraints
|
||||
TYPE: FLAG
|
||||
ARG: SQLITE_IgnoreChecks
|
||||
|
||||
+3
-3
@@ -726,7 +726,7 @@ static void decode_btree_page(
|
||||
}
|
||||
if( showMap ){
|
||||
printf("Page map: (H=header P=cell-index 1=page-1-header .=free-space)\n");
|
||||
for(i=0; i<g.pagesize; i+=64){
|
||||
for(i=0; (u32)i<g.pagesize; i+=64){
|
||||
printf(" %03x: %.64s\n", i, &zMap[i]);
|
||||
}
|
||||
sqlite3_free(zMap);
|
||||
@@ -861,7 +861,7 @@ static int allZero(unsigned char *a, int n){
|
||||
*/
|
||||
static void page_usage_btree(
|
||||
u32 pgno, /* Page to describe */
|
||||
u32 parent, /* Parent of this page. 0 for root pages */
|
||||
int parent, /* Parent of this page. 0 for root pages */
|
||||
int idx, /* Which child of the parent */
|
||||
const char *zName /* Name of the table */
|
||||
){
|
||||
@@ -954,7 +954,7 @@ static void page_usage_freelist(u32 pgno){
|
||||
int iNext;
|
||||
int parent = 1;
|
||||
|
||||
while( pgno>0 && pgno<=g.mxPage && (cnt++)<g.mxPage ){
|
||||
while( pgno>0 && pgno<=g.mxPage && (u32)(cnt++)<g.mxPage ){
|
||||
page_usage_msg(pgno, "freelist trunk #%d child of %d", cnt, parent);
|
||||
a = fileRead((pgno-1)*g.pagesize, g.pagesize);
|
||||
iNext = decodeInt32(a);
|
||||
|
||||
Reference in New Issue
Block a user