Compare commits
196 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d1ce2755f0 | |||
| ebce46f02f | |||
| 83f62816f8 | |||
| 0dabcd139d | |||
| 5d1448d08a | |||
| 50b2a41330 | |||
| 1d74432fef | |||
| a917ee7056 | |||
| 9afd67cce7 | |||
| 4632f91403 | |||
| 36f60e6c1a | |||
| 61df55d0be | |||
| e81d229fb7 | |||
| 0e224d93ea | |||
| 1c67749166 | |||
| a6e7202677 | |||
| 6f737371c0 | |||
| efa8369214 | |||
| 47b427423d | |||
| 1b96f9167f | |||
| cf0906575d | |||
| cc11526e22 | |||
| 873849b81e | |||
| a2ead23868 | |||
| af7cce98c5 | |||
| 7a9bbfe1fa | |||
| c18c7523a4 | |||
| 9f811a35b8 | |||
| 2d3c5385bf | |||
| 6c248201b1 | |||
| 8a67a46340 | |||
| bd42642431 | |||
| 8aaf63c6ac | |||
| 876f09b560 | |||
| 38119058aa | |||
| f99a27fab9 | |||
| 2599705105 | |||
| a91fe45339 | |||
| 5bd96b2446 | |||
| 5ddf0cbe25 | |||
| 2e8edc1d45 | |||
| 63702bccaa | |||
| 124a6aa7d5 | |||
| ef2056e533 | |||
| 58c10d9469 | |||
| c09701db9b | |||
| 81b70d97eb | |||
| 20220d4682 | |||
| 658096e124 | |||
| 581b229362 | |||
| c9823e3960 | |||
| a0f878959f | |||
| 93717c2306 | |||
| 9ba2d13ed3 | |||
| e744f66008 | |||
| e68decd54c | |||
| 82cdf4987d | |||
| 60e53c57cf | |||
| dc03af67a4 | |||
| 647e3ed2ba | |||
| 595c3ecc94 | |||
| 23164c984e | |||
| 5b5d4492f2 | |||
| 7de8ae22f7 | |||
| 1eca330a08 | |||
| 181063d477 | |||
| f9967cb302 | |||
| cdae00b3e8 | |||
| 386e359044 | |||
| f4ae37ca44 | |||
| fef1c11f92 | |||
| 111815609c | |||
| 12439f9c16 | |||
| 0148013416 | |||
| 18e143d1d5 | |||
| 09d72e4a6e | |||
| 1110f8ffcd | |||
| 50925d23ab | |||
| 09e96ea731 | |||
| ceeb9eeaac | |||
| 17ca53da26 | |||
| 833ed7ee64 | |||
| 5c3a25b450 | |||
| a9d788f08f | |||
| 7fa8d65539 | |||
| ef2e43304e | |||
| 8f825a7a1b | |||
| 8ea10ea520 | |||
| 1b2d170a8f | |||
| 410acf6944 | |||
| fb77b3ed3f | |||
| 8525e2d931 | |||
| 09f1652f36 | |||
| 0a4c31d87b | |||
| 6e2b63893c | |||
| dd69129f2a | |||
| 93b461ff00 | |||
| d4bb0896d4 | |||
| 9e5d21092d | |||
| d5ab4dd9e4 | |||
| 961c2a9f36 | |||
| 7c1588b3ad | |||
| f31b9fcf86 | |||
| 5a05a68315 | |||
| dd24b1c8a1 | |||
| 25a6e6ec8b | |||
| 0c072b698b | |||
| 3aeb58c90c | |||
| 4eaf5883b3 | |||
| 411d9e7b2e | |||
| 14412e9526 | |||
| 805e8c96b9 | |||
| 7dfacc9f96 | |||
| 286f30f85c | |||
| 1943356268 | |||
| 83a8b6dd82 | |||
| e30436b605 | |||
| ce9902f8c2 | |||
| 54021c3bd0 | |||
| 24a689cf01 | |||
| 2966b85df3 | |||
| d3c5471a9a | |||
| de4d1c357f | |||
| 7d31de600b | |||
| 216433636d | |||
| 70b58fc119 | |||
| 63f4a9c4db | |||
| 21d4f0cc7b | |||
| 49a40ef6d2 | |||
| a5f425cc6e | |||
| 4ea63e5609 | |||
| d086065644 | |||
| dba0ceea60 | |||
| f551aa4e80 | |||
| 7862e19468 | |||
| 03ca59afb9 | |||
| 55171a75ef | |||
| a4b47b034c | |||
| 0d3f0a9c11 | |||
| 63e7ed3b44 | |||
| 8628c10e27 | |||
| 062bafb851 | |||
| 0c2ba994d2 | |||
| 3823208d5b | |||
| 7fc4223411 | |||
| a7ec1f9a1b | |||
| d25d938877 | |||
| a5af4a6614 | |||
| 7bb5a6db40 | |||
| 85a05d895a | |||
| adf54de51e | |||
| 10deb35995 | |||
| 8dd07389ac | |||
| 24c32c2e39 | |||
| e621556724 | |||
| ac5e1f82ce | |||
| 4f1387e9ab | |||
| 267c44771f | |||
| aa15047796 | |||
| a910f3f1c5 | |||
| 69a55ca17d | |||
| 9d6acd960f | |||
| e1e67abc5c | |||
| 0fc20a32c0 | |||
| 524ddc940d | |||
| b44b802378 | |||
| d10ed826eb | |||
| 4e379078e7 | |||
| 924c4545d3 | |||
| fa23b4fc61 | |||
| a08f737503 | |||
| 35c7f538e9 | |||
| ece2d7f2be | |||
| 8d64c12a10 | |||
| 57b1800773 | |||
| 25e7d8af75 | |||
| bdb149d063 | |||
| 4e9b48cfc4 | |||
| bd953dfcad | |||
| 0d066bc8a6 | |||
| f0e9e59d8d | |||
| 2597ec6385 | |||
| 23dfa67c1e | |||
| b041f8ffdd | |||
| 56d3b21317 | |||
| 5020ddc243 | |||
| 0639c58836 | |||
| 5575d6421b | |||
| 88bd53dfd0 | |||
| 95f5d85d4a | |||
| 32a79760b5 | |||
| 60aca33a8b | |||
| 0f4bf3435a | |||
| c7e7c88873 | |||
| 0deaea218d | |||
| 95484726f6 |
@@ -57,6 +57,7 @@ LIBTCL = @TCL_LIB_SPEC@
|
||||
#
|
||||
READLINE_FLAGS = -DHAVE_READLINE=@TARGET_HAVE_READLINE@ @TARGET_READLINE_INC@
|
||||
READLINE_FLAGS += -DHAVE_EDITLINE=@TARGET_HAVE_EDITLINE@
|
||||
READLINE_FLAGS += -DHAVE_LINENOISE=@TARGET_HAVE_LINENOISE@
|
||||
|
||||
# The library that programs using readline() must link against.
|
||||
#
|
||||
@@ -1468,6 +1469,11 @@ amalgamation-tarball: sqlite3.c sqlite3rc.h
|
||||
snapshot-tarball: sqlite3.c sqlite3rc.h
|
||||
TOP=$(TOP) sh $(TOP)/tool/mkautoconfamal.sh --snapshot
|
||||
|
||||
# Build a ZIP archive containing various command-line tools.
|
||||
#
|
||||
tool-zip: testfixture sqlite3 sqldiff sqlite3_analyzer tool/mktoolzip.tcl
|
||||
./testfixture $(TOP)/tool/mktoolzip.tcl
|
||||
|
||||
# The next two rules are used to support the "threadtest" target. Building
|
||||
# threadtest runs a few thread-safety tests that are implemented in C. This
|
||||
# target is invoked by the releasetest.tcl script.
|
||||
|
||||
@@ -2466,6 +2466,9 @@ extensiontest: testfixture.exe testloadext.dll
|
||||
@set PATH=$(LIBTCLPATH);$(PATH)
|
||||
.\testfixture.exe $(TOP)\test\loadext.test $(TESTOPTS)
|
||||
|
||||
tool-zip: testfixture.exe sqlite3.exe sqldiff.exe sqlite3_analyzer.exe tool\mktoolzip.tcl
|
||||
.\testfixture.exe $(TOP)\tool\mktoolzip.tcl
|
||||
|
||||
coretestprogs: $(TESTPROGS)
|
||||
|
||||
testprogs: coretestprogs srcck1.exe fuzzcheck.exe sessionfuzz.exe
|
||||
|
||||
@@ -19,7 +19,7 @@ dnl to configure the system for the local environment.
|
||||
# so that we create the export library with the dll.
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
AC_INIT([sqlite],[3.43.0])
|
||||
AC_INIT([sqlite],[3.44.0])
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Call TEA_INIT as the first TEA_ macro to set up initial vars.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.69 for sqlite 3.43.0.
|
||||
# Generated by GNU Autoconf 2.69 for sqlite 3.44.0.
|
||||
#
|
||||
#
|
||||
# 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.43.0'
|
||||
PACKAGE_STRING='sqlite 3.43.0'
|
||||
PACKAGE_VERSION='3.44.0'
|
||||
PACKAGE_STRING='sqlite 3.44.0'
|
||||
PACKAGE_BUGREPORT=''
|
||||
PACKAGE_URL=''
|
||||
|
||||
@@ -776,6 +776,7 @@ OPT_FEATURE_FLAGS
|
||||
HAVE_ZLIB
|
||||
USE_AMALGAMATION
|
||||
TARGET_DEBUG
|
||||
TARGET_HAVE_LINENOISE
|
||||
TARGET_HAVE_EDITLINE
|
||||
TARGET_HAVE_READLINE
|
||||
TARGET_READLINE_INC
|
||||
@@ -903,6 +904,7 @@ enable_editline
|
||||
enable_readline
|
||||
with_readline_lib
|
||||
with_readline_inc
|
||||
with_linenoise
|
||||
enable_debug
|
||||
enable_amalgamation
|
||||
enable_load_extension
|
||||
@@ -1470,7 +1472,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.43.0 to adapt to many kinds of systems.
|
||||
\`configure' configures sqlite 3.44.0 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@@ -1535,7 +1537,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of sqlite 3.43.0:";;
|
||||
short | recursive ) echo "Configuration of sqlite 3.44.0:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@@ -1587,6 +1589,7 @@ Optional Packages:
|
||||
(tclConfig.sh)
|
||||
--with-readline-lib specify readline library
|
||||
--with-readline-inc specify readline include paths
|
||||
--with-linenoise=DIR source directory for linenoise library
|
||||
|
||||
Some influential environment variables:
|
||||
CC C compiler command
|
||||
@@ -1665,7 +1668,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
sqlite configure 3.43.0
|
||||
sqlite configure 3.44.0
|
||||
generated by GNU Autoconf 2.69
|
||||
|
||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||
@@ -2084,7 +2087,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.43.0, which was
|
||||
It was created by sqlite $as_me 3.44.0, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
@@ -3942,13 +3945,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:3945: $ac_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:3948: $ac_compile\"" >&5)
|
||||
(eval "$ac_compile" 2>conftest.err)
|
||||
cat conftest.err >&5
|
||||
(eval echo "\"\$as_me:3948: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
|
||||
(eval echo "\"\$as_me:3951: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
|
||||
(eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
|
||||
cat conftest.err >&5
|
||||
(eval echo "\"\$as_me:3951: output\"" >&5)
|
||||
(eval echo "\"\$as_me:3954: output\"" >&5)
|
||||
cat conftest.out >&5
|
||||
if $GREP 'External.*some_variable' conftest.out > /dev/null; then
|
||||
lt_cv_nm_interface="MS dumpbin"
|
||||
@@ -5154,7 +5157,7 @@ ia64-*-hpux*)
|
||||
;;
|
||||
*-*-irix6*)
|
||||
# Find out which ABI we are using.
|
||||
echo '#line 5157 "configure"' > conftest.$ac_ext
|
||||
echo '#line 5160 "configure"' > conftest.$ac_ext
|
||||
if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
|
||||
(eval $ac_compile) 2>&5
|
||||
ac_status=$?
|
||||
@@ -6679,11 +6682,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:6682: $lt_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:6685: $lt_compile\"" >&5)
|
||||
(eval "$lt_compile" 2>conftest.err)
|
||||
ac_status=$?
|
||||
cat conftest.err >&5
|
||||
echo "$as_me:6686: \$? = $ac_status" >&5
|
||||
echo "$as_me:6689: \$? = $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.
|
||||
@@ -7018,11 +7021,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:7021: $lt_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:7024: $lt_compile\"" >&5)
|
||||
(eval "$lt_compile" 2>conftest.err)
|
||||
ac_status=$?
|
||||
cat conftest.err >&5
|
||||
echo "$as_me:7025: \$? = $ac_status" >&5
|
||||
echo "$as_me:7028: \$? = $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.
|
||||
@@ -7123,11 +7126,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:7126: $lt_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:7129: $lt_compile\"" >&5)
|
||||
(eval "$lt_compile" 2>out/conftest.err)
|
||||
ac_status=$?
|
||||
cat out/conftest.err >&5
|
||||
echo "$as_me:7130: \$? = $ac_status" >&5
|
||||
echo "$as_me:7133: \$? = $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
|
||||
@@ -7178,11 +7181,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:7181: $lt_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:7184: $lt_compile\"" >&5)
|
||||
(eval "$lt_compile" 2>out/conftest.err)
|
||||
ac_status=$?
|
||||
cat out/conftest.err >&5
|
||||
echo "$as_me:7185: \$? = $ac_status" >&5
|
||||
echo "$as_me:7188: \$? = $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
|
||||
@@ -9558,7 +9561,7 @@ else
|
||||
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
|
||||
lt_status=$lt_dlunknown
|
||||
cat > conftest.$ac_ext <<_LT_EOF
|
||||
#line 9561 "configure"
|
||||
#line 9564 "configure"
|
||||
#include "confdefs.h"
|
||||
|
||||
#if HAVE_DLFCN_H
|
||||
@@ -9654,7 +9657,7 @@ else
|
||||
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
|
||||
lt_status=$lt_dlunknown
|
||||
cat > conftest.$ac_ext <<_LT_EOF
|
||||
#line 9657 "configure"
|
||||
#line 9660 "configure"
|
||||
#include "confdefs.h"
|
||||
|
||||
#if HAVE_DLFCN_H
|
||||
@@ -11245,6 +11248,27 @@ fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check whether --with-linenoise was given.
|
||||
if test "${with_linenoise+set}" = set; then :
|
||||
withval=$with_linenoise; with_linenoise=$withval
|
||||
else
|
||||
with_linenoise="no"
|
||||
fi
|
||||
|
||||
if test "x$with_linenoise" != "xno"; then
|
||||
TARGET_HAVE_READLINE=0
|
||||
TARGET_HAVE_EDITLINE=0
|
||||
TARGET_HAVE_LINENOISE=1
|
||||
TARGET_READLINE_INC="-I${with_linenoise}"
|
||||
TARGET_READLINE_LIBS="${with_linenoise}/linenoise.c"
|
||||
echo "using linenoise source code at ${with_linenoise}"
|
||||
else
|
||||
TARGET_HAVE_LINENOISE=0
|
||||
echo "not using linenoise"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -11321,7 +11345,7 @@ fi
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build type" >&5
|
||||
$as_echo_n "checking build type... " >&6; }
|
||||
if test "${enable_debug}" = "yes" ; then
|
||||
TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0"
|
||||
TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0 -Wall"
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: debug" >&5
|
||||
$as_echo "debug" >&6; }
|
||||
else
|
||||
@@ -12457,7 +12481,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.43.0, which was
|
||||
This file was extended by sqlite $as_me 3.44.0, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -12523,7 +12547,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.43.0
|
||||
sqlite config.status 3.44.0
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
||||
+18
-1
@@ -598,11 +598,28 @@ if test x"$with_readline" != xno; then
|
||||
TARGET_HAVE_READLINE=1
|
||||
fi
|
||||
fi
|
||||
AC_ARG_WITH([linenoise],
|
||||
[AS_HELP_STRING([--with-linenoise=DIR],[source directory for linenoise library])],
|
||||
[with_linenoise=$withval],
|
||||
[with_linenoise="no"])
|
||||
if test "x$with_linenoise" != "xno"; then
|
||||
TARGET_HAVE_READLINE=0
|
||||
TARGET_HAVE_EDITLINE=0
|
||||
TARGET_HAVE_LINENOISE=1
|
||||
TARGET_READLINE_INC="-I${with_linenoise}"
|
||||
TARGET_READLINE_LIBS="${with_linenoise}/linenoise.c"
|
||||
echo "using linenoise source code at ${with_linenoise}"
|
||||
else
|
||||
TARGET_HAVE_LINENOISE=0
|
||||
echo "not using linenoise"
|
||||
fi
|
||||
|
||||
AC_SUBST(TARGET_READLINE_LIBS)
|
||||
AC_SUBST(TARGET_READLINE_INC)
|
||||
AC_SUBST(TARGET_HAVE_READLINE)
|
||||
AC_SUBST(TARGET_HAVE_EDITLINE)
|
||||
AC_SUBST(TARGET_HAVE_LINENOISE)
|
||||
|
||||
|
||||
##########
|
||||
# Figure out what C libraries are required to compile programs
|
||||
@@ -615,7 +632,7 @@ AC_SEARCH_LIBS(fdatasync, [rt])
|
||||
AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug],[enable debugging & verbose explain]))
|
||||
AC_MSG_CHECKING([build type])
|
||||
if test "${enable_debug}" = "yes" ; then
|
||||
TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0"
|
||||
TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0 -Wall"
|
||||
AC_MSG_RESULT([debug])
|
||||
else
|
||||
TARGET_DEBUG="-DNDEBUG"
|
||||
|
||||
+26
-1
@@ -3980,8 +3980,32 @@ static int fts3ShadowName(const char *zName){
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of the xIntegrity() method on the FTS3/FTS4 virtual
|
||||
** table.
|
||||
*/
|
||||
static int fts3Integrity(sqlite3_vtab *pVtab, char **pzErr){
|
||||
Fts3Table *p = (Fts3Table*)pVtab;
|
||||
char *zSql;
|
||||
int rc;
|
||||
|
||||
zSql = sqlite3_mprintf(
|
||||
"INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');",
|
||||
p->zDb, p->zName, p->zName);
|
||||
rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
|
||||
sqlite3_free(zSql);
|
||||
if( (rc&0xff)==SQLITE_CORRUPT ){
|
||||
*pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s",
|
||||
p->bFts4 ? 4 : 3, p->zDb, p->zName);
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const sqlite3_module fts3Module = {
|
||||
/* iVersion */ 3,
|
||||
/* iVersion */ 4,
|
||||
/* xCreate */ fts3CreateMethod,
|
||||
/* xConnect */ fts3ConnectMethod,
|
||||
/* xBestIndex */ fts3BestIndexMethod,
|
||||
@@ -4005,6 +4029,7 @@ static const sqlite3_module fts3Module = {
|
||||
/* xRelease */ fts3ReleaseMethod,
|
||||
/* xRollbackTo */ fts3RollbackToMethod,
|
||||
/* xShadowName */ fts3ShadowName,
|
||||
/* xIntegrity */ fts3Integrity,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -5218,7 +5218,7 @@ static u64 fts3ChecksumIndex(
|
||||
int rc;
|
||||
u64 cksum = 0;
|
||||
|
||||
assert( *pRc==SQLITE_OK );
|
||||
if( *pRc ) return 0;
|
||||
|
||||
memset(&filter, 0, sizeof(filter));
|
||||
memset(&csr, 0, sizeof(csr));
|
||||
|
||||
+106
-76
@@ -3272,7 +3272,7 @@ static Fts5Iter *fts5MultiIterAlloc(
|
||||
int nSeg
|
||||
){
|
||||
Fts5Iter *pNew;
|
||||
int nSlot; /* Power of two >= nSeg */
|
||||
i64 nSlot; /* Power of two >= nSeg */
|
||||
|
||||
for(nSlot=2; nSlot<nSeg; nSlot=nSlot*2);
|
||||
pNew = fts5IdxMalloc(p,
|
||||
@@ -5048,7 +5048,6 @@ static void fts5DoSecureDelete(
|
||||
int iPgIdx = pSeg->pLeaf->szLeaf;
|
||||
|
||||
u64 iDelta = 0;
|
||||
u64 iNextDelta = 0;
|
||||
int iNextOff = 0;
|
||||
int iOff = 0;
|
||||
int nIdx = 0;
|
||||
@@ -5056,8 +5055,6 @@ static void fts5DoSecureDelete(
|
||||
int bLastInDoclist = 0;
|
||||
int iIdx = 0;
|
||||
int iStart = 0;
|
||||
int iKeyOff = 0;
|
||||
int iPrevKeyOff = 0;
|
||||
int iDelKeyOff = 0; /* Offset of deleted key, if any */
|
||||
|
||||
nIdx = nPg-iPgIdx;
|
||||
@@ -5082,10 +5079,21 @@ static void fts5DoSecureDelete(
|
||||
** This block sets the following variables:
|
||||
**
|
||||
** iStart:
|
||||
** The offset of the first byte of the rowid or delta-rowid
|
||||
** value for the doclist entry being removed.
|
||||
**
|
||||
** iDelta:
|
||||
** The value of the rowid or delta-rowid value for the doclist
|
||||
** entry being removed.
|
||||
**
|
||||
** iNextOff:
|
||||
** The offset of the next entry following the position list
|
||||
** for the one being removed. If the position list for this
|
||||
** entry overflows onto the next leaf page, this value will be
|
||||
** greater than pLeaf->szLeaf.
|
||||
*/
|
||||
{
|
||||
int iSOP;
|
||||
int iSOP; /* Start-Of-Position-list */
|
||||
if( pSeg->iLeafPgno==pSeg->iTermLeafPgno ){
|
||||
iStart = pSeg->iTermLeafOffset;
|
||||
}else{
|
||||
@@ -5121,14 +5129,20 @@ static void fts5DoSecureDelete(
|
||||
}
|
||||
|
||||
iOff = iStart;
|
||||
|
||||
/* Set variable bLastInDoclist to true if this entry happens to be
|
||||
** the last rowid in the doclist for its term. */
|
||||
if( iNextOff>=iPgIdx ){
|
||||
int pgno = pSeg->iLeafPgno+1;
|
||||
fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist);
|
||||
iNextOff = iPgIdx;
|
||||
}else{
|
||||
/* Set bLastInDoclist to true if the entry being removed is the last
|
||||
/* Loop through the page-footer. If iNextOff (offset of the
|
||||
** entry following the one we are removing) is equal to the
|
||||
** offset of a key on this page, then the entry is the last
|
||||
** in its doclist. */
|
||||
for(iIdx=0, iKeyOff=0; iIdx<nIdx; /* no-op */){
|
||||
int iKeyOff = 0;
|
||||
for(iIdx=0; iIdx<nIdx; /* no-op */){
|
||||
u32 iVal = 0;
|
||||
iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
|
||||
iKeyOff += iVal;
|
||||
@@ -5138,30 +5152,47 @@ static void fts5DoSecureDelete(
|
||||
}
|
||||
}
|
||||
|
||||
if( fts5GetU16(&aPg[0])==iStart && (bLastInDoclist||iNextOff==iPgIdx) ){
|
||||
/* If this is (a) the first rowid on a page and (b) is not followed by
|
||||
** another position list on the same page, set the "first-rowid" field
|
||||
** of the header to 0. */
|
||||
if( fts5GetU16(&aPg[0])==iStart && (bLastInDoclist || iNextOff==iPgIdx) ){
|
||||
fts5PutU16(&aPg[0], 0);
|
||||
}
|
||||
|
||||
if( bLastInDoclist==0 ){
|
||||
if( iNextOff!=iPgIdx ){
|
||||
u64 iNextDelta = 0;
|
||||
iNextOff += fts5GetVarint(&aPg[iNextOff], &iNextDelta);
|
||||
iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta + iNextDelta);
|
||||
}
|
||||
}else if(
|
||||
iStart==pSeg->iTermLeafOffset && pSeg->iLeafPgno==pSeg->iTermLeafPgno
|
||||
pSeg->iLeafPgno==pSeg->iTermLeafPgno
|
||||
&& iStart==pSeg->iTermLeafOffset
|
||||
){
|
||||
/* The entry being removed was the only position list in its
|
||||
** doclist. Therefore the term needs to be removed as well. */
|
||||
int iKey = 0;
|
||||
for(iIdx=0, iKeyOff=0; iIdx<nIdx; iKey++){
|
||||
int iKeyOff = 0;
|
||||
|
||||
/* Set iKeyOff to the offset of the term that will be removed - the
|
||||
** last offset in the footer that is not greater than iStart. */
|
||||
for(iIdx=0; iIdx<nIdx; iKey++){
|
||||
u32 iVal = 0;
|
||||
iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
|
||||
if( (iKeyOff+iVal)>(u32)iStart ) break;
|
||||
iKeyOff += iVal;
|
||||
}
|
||||
assert_nc( iKey>=1 );
|
||||
|
||||
/* Set iDelKeyOff to the value of the footer entry to remove from
|
||||
** the page. */
|
||||
iDelKeyOff = iOff = iKeyOff;
|
||||
|
||||
if( iNextOff!=iPgIdx ){
|
||||
/* This is the only position-list associated with the term, and there
|
||||
** is another term following it on this page. So the subsequent term
|
||||
** needs to be moved to replace the term associated with the entry
|
||||
** being removed. */
|
||||
int nPrefix = 0;
|
||||
int nSuffix = 0;
|
||||
int nPrefix2 = 0;
|
||||
@@ -5198,80 +5229,79 @@ static void fts5DoSecureDelete(
|
||||
}
|
||||
}
|
||||
}else if( iStart==4 ){
|
||||
int iPgno;
|
||||
int iPgno;
|
||||
|
||||
assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno );
|
||||
/* The entry being removed may be the only position list in
|
||||
** its doclist. */
|
||||
for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){
|
||||
Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno));
|
||||
int bEmpty = (pPg && pPg->nn==4);
|
||||
fts5DataRelease(pPg);
|
||||
if( bEmpty==0 ) break;
|
||||
}
|
||||
assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno );
|
||||
/* The entry being removed may be the only position list in
|
||||
** its doclist. */
|
||||
for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){
|
||||
Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno));
|
||||
int bEmpty = (pPg && pPg->nn==4);
|
||||
fts5DataRelease(pPg);
|
||||
if( bEmpty==0 ) break;
|
||||
}
|
||||
|
||||
if( iPgno==pSeg->iTermLeafPgno ){
|
||||
i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno);
|
||||
Fts5Data *pTerm = fts5DataRead(p, iId);
|
||||
if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){
|
||||
u8 *aTermIdx = &pTerm->p[pTerm->szLeaf];
|
||||
int nTermIdx = pTerm->nn - pTerm->szLeaf;
|
||||
int iTermIdx = 0;
|
||||
int iTermOff = 0;
|
||||
if( iPgno==pSeg->iTermLeafPgno ){
|
||||
i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno);
|
||||
Fts5Data *pTerm = fts5DataRead(p, iId);
|
||||
if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){
|
||||
u8 *aTermIdx = &pTerm->p[pTerm->szLeaf];
|
||||
int nTermIdx = pTerm->nn - pTerm->szLeaf;
|
||||
int iTermIdx = 0;
|
||||
int iTermOff = 0;
|
||||
|
||||
while( 1 ){
|
||||
u32 iVal = 0;
|
||||
int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal);
|
||||
iTermOff += iVal;
|
||||
if( (iTermIdx+nByte)>=nTermIdx ) break;
|
||||
iTermIdx += nByte;
|
||||
}
|
||||
nTermIdx = iTermIdx;
|
||||
|
||||
memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx);
|
||||
fts5PutU16(&pTerm->p[2], iTermOff);
|
||||
|
||||
fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx);
|
||||
if( nTermIdx==0 ){
|
||||
fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno);
|
||||
}
|
||||
while( 1 ){
|
||||
u32 iVal = 0;
|
||||
int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal);
|
||||
iTermOff += iVal;
|
||||
if( (iTermIdx+nByte)>=nTermIdx ) break;
|
||||
iTermIdx += nByte;
|
||||
}
|
||||
fts5DataRelease(pTerm);
|
||||
nTermIdx = iTermIdx;
|
||||
|
||||
memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx);
|
||||
fts5PutU16(&pTerm->p[2], iTermOff);
|
||||
|
||||
fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx);
|
||||
if( nTermIdx==0 ){
|
||||
fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno);
|
||||
}
|
||||
}
|
||||
fts5DataRelease(pTerm);
|
||||
}
|
||||
}
|
||||
|
||||
if( p->rc==SQLITE_OK ){
|
||||
const int nMove = nPg - iNextOff; /* Number of bytes to move */
|
||||
int nShift = iNextOff - iOff; /* Distance to move them */
|
||||
|
||||
int iPrevKeyOut = 0;
|
||||
int iKeyIn = 0;
|
||||
|
||||
memmove(&aPg[iOff], &aPg[iNextOff], nMove);
|
||||
iPgIdx -= nShift;
|
||||
nPg = iPgIdx;
|
||||
fts5PutU16(&aPg[2], iPgIdx);
|
||||
|
||||
for(iIdx=0; iIdx<nIdx; /* no-op */){
|
||||
u32 iVal = 0;
|
||||
iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
|
||||
iKeyIn += iVal;
|
||||
if( iKeyIn!=iDelKeyOff ){
|
||||
int iKeyOut = (iKeyIn - (iKeyIn>iOff ? nShift : 0));
|
||||
nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOut - iPrevKeyOut);
|
||||
iPrevKeyOut = iKeyOut;
|
||||
}
|
||||
}
|
||||
|
||||
if( p->rc==SQLITE_OK ){
|
||||
const int nMove = nPg - iNextOff;
|
||||
int nShift = 0;
|
||||
|
||||
memmove(&aPg[iOff], &aPg[iNextOff], nMove);
|
||||
iPgIdx -= (iNextOff - iOff);
|
||||
nPg = iPgIdx;
|
||||
fts5PutU16(&aPg[2], iPgIdx);
|
||||
|
||||
nShift = iNextOff - iOff;
|
||||
for(iIdx=0, iKeyOff=0, iPrevKeyOff=0; iIdx<nIdx; /* no-op */){
|
||||
u32 iVal = 0;
|
||||
iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
|
||||
iKeyOff += iVal;
|
||||
if( iKeyOff!=iDelKeyOff ){
|
||||
if( iKeyOff>iOff ){
|
||||
iKeyOff -= nShift;
|
||||
nShift = 0;
|
||||
}
|
||||
nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOff - iPrevKeyOff);
|
||||
iPrevKeyOff = iKeyOff;
|
||||
}
|
||||
}
|
||||
|
||||
if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){
|
||||
fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno);
|
||||
}
|
||||
|
||||
assert_nc( nPg>4 || fts5GetU16(aPg)==0 );
|
||||
fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg,nPg);
|
||||
if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){
|
||||
fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno);
|
||||
}
|
||||
sqlite3_free(aIdx);
|
||||
|
||||
assert_nc( nPg>4 || fts5GetU16(aPg)==0 );
|
||||
fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg, nPg);
|
||||
}
|
||||
sqlite3_free(aIdx);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+38
-4
@@ -405,6 +405,10 @@ static int fts5InitVtab(
|
||||
pConfig->pzErrmsg = 0;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){
|
||||
rc = sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, (int)1);
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
fts5FreeVtab(pTab);
|
||||
pTab = 0;
|
||||
@@ -1329,6 +1333,9 @@ static int fts5FilterMethod(
|
||||
pCsr->iFirstRowid = fts5GetRowidLimit(pRowidGe, SMALLEST_INT64);
|
||||
}
|
||||
|
||||
rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
|
||||
if( rc!=SQLITE_OK ) goto filter_out;
|
||||
|
||||
if( pTab->pSortCsr ){
|
||||
/* If pSortCsr is non-NULL, then this call is being made as part of
|
||||
** processing for a "... MATCH <expr> ORDER BY rank" query (ePlan is
|
||||
@@ -1351,6 +1358,7 @@ static int fts5FilterMethod(
|
||||
pCsr->pExpr = pTab->pSortCsr->pExpr;
|
||||
rc = fts5CursorFirst(pTab, pCsr, bDesc);
|
||||
}else if( pCsr->pExpr ){
|
||||
assert( rc==SQLITE_OK );
|
||||
rc = fts5CursorParseRank(pConfig, pCsr, pRank);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( bOrderByRank ){
|
||||
@@ -1685,7 +1693,7 @@ static int fts5UpdateMethod(
|
||||
assert( nArg!=1 || eType0==SQLITE_INTEGER );
|
||||
|
||||
/* Filter out attempts to run UPDATE or DELETE on contentless tables.
|
||||
** This is not suported. Except - DELETE is supported if the CREATE
|
||||
** This is not suported. Except - they are both supported if the CREATE
|
||||
** VIRTUAL TABLE statement contained "contentless_delete=1". */
|
||||
if( eType0==SQLITE_INTEGER
|
||||
&& pConfig->eContent==FTS5_CONTENT_NONE
|
||||
@@ -1714,7 +1722,8 @@ static int fts5UpdateMethod(
|
||||
}
|
||||
|
||||
else if( eType0!=SQLITE_INTEGER ){
|
||||
/* If this is a REPLACE, first remove the current entry (if any) */
|
||||
/* An INSERT statement. If the conflict-mode is REPLACE, first remove
|
||||
** the current entry (if any). */
|
||||
if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){
|
||||
i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */
|
||||
rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0);
|
||||
@@ -2850,9 +2859,33 @@ static int fts5ShadowName(const char *zName){
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Run an integrity check on the FTS5 data structures. Return a string
|
||||
** if anything is found amiss. Return a NULL pointer if everything is
|
||||
** OK.
|
||||
*/
|
||||
static int fts5Integrity(sqlite3_vtab *pVtab, char **pzErr){
|
||||
Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
|
||||
Fts5Config *pConfig = pTab->p.pConfig;
|
||||
char *zSql;
|
||||
int rc;
|
||||
zSql = sqlite3_mprintf(
|
||||
"INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');",
|
||||
pConfig->zDb, pConfig->zName, pConfig->zName);
|
||||
rc = sqlite3_exec(pConfig->db, zSql, 0, 0, 0);
|
||||
sqlite3_free(zSql);
|
||||
if( (rc&0xff)==SQLITE_CORRUPT ){
|
||||
*pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s",
|
||||
pConfig->zDb, pConfig->zName);
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
return rc;
|
||||
|
||||
}
|
||||
|
||||
static int fts5Init(sqlite3 *db){
|
||||
static const sqlite3_module fts5Mod = {
|
||||
/* iVersion */ 3,
|
||||
/* iVersion */ 4,
|
||||
/* xCreate */ fts5CreateMethod,
|
||||
/* xConnect */ fts5ConnectMethod,
|
||||
/* xBestIndex */ fts5BestIndexMethod,
|
||||
@@ -2875,7 +2908,8 @@ static int fts5Init(sqlite3 *db){
|
||||
/* xSavepoint */ fts5SavepointMethod,
|
||||
/* xRelease */ fts5ReleaseMethod,
|
||||
/* xRollbackTo */ fts5RollbackToMethod,
|
||||
/* xShadowName */ fts5ShadowName
|
||||
/* xShadowName */ fts5ShadowName,
|
||||
/* xIntegrity */ fts5Integrity
|
||||
};
|
||||
|
||||
int rc;
|
||||
|
||||
@@ -65,7 +65,9 @@ foreach w {a b c d e f} {
|
||||
|
||||
do_execsql_test 2.4 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
}
|
||||
PRAGMA integrity_check;
|
||||
PRAGMA integrity_check(t1);
|
||||
} {ok ok}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@@ -88,6 +90,7 @@ foreach {i x y} {
|
||||
} {
|
||||
do_execsql_test 3.$i.1 { INSERT INTO t1 VALUES($x, $y) }
|
||||
do_execsql_test 3.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
|
||||
do_execsql_test 3.$i.3 { PRAGMA integrity_check(t1) } ok
|
||||
if {[set_test_counter errors]} break
|
||||
}
|
||||
|
||||
@@ -135,7 +138,7 @@ foreach {i x y} {
|
||||
10 {ddd abcde dddd dd c} {dddd c c d abcde}
|
||||
} {
|
||||
do_execsql_test 5.$i.1 { INSERT INTO t1 VALUES($x, $y) }
|
||||
do_execsql_test 5.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
|
||||
do_execsql_test 5.$i.2 { PRAGMA integrity_check(t1) } ok
|
||||
if {[set_test_counter errors]} break
|
||||
}
|
||||
|
||||
|
||||
@@ -307,5 +307,31 @@ do_catchsql_test 10.1.4 {
|
||||
SELECT group_concat(firstcol(t1), '.') FROM t1 GROUP BY rowid
|
||||
} {1 {unable to use function firstcol in the requested context}}
|
||||
|
||||
finish_test
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that xInstCount() works from within an xPhraseQuery() callback.
|
||||
#
|
||||
reset_db
|
||||
|
||||
proc xCallback {cmd} {
|
||||
incr ::hitcount [$cmd xInstCount]
|
||||
return SQLITE_OK
|
||||
}
|
||||
proc fts5_hitcount {cmd} {
|
||||
set ::hitcount 0
|
||||
$cmd xQueryPhrase 0 xCallback
|
||||
return $::hitcount
|
||||
}
|
||||
sqlite3_fts5_create_function db fts5_hitcount fts5_hitcount
|
||||
|
||||
do_execsql_test 11.1 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(z);
|
||||
INSERT INTO x1 VALUES('one two three');
|
||||
INSERT INTO x1 VALUES('one two one three one');
|
||||
INSERT INTO x1 VALUES('one two three');
|
||||
}
|
||||
|
||||
do_execsql_test 11.2 {
|
||||
SELECT fts5_hitcount(x1) FROM x1('one') LIMIT 1;
|
||||
} {5}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -65,4 +65,44 @@ do_execsql_test 2.1 {
|
||||
INSERT INTO fts_idx(fts_idx) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Tests for OR IGNORE conflict handling.
|
||||
#
|
||||
reset_db
|
||||
foreach_detail_mode $::testprefix {
|
||||
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(xyz, detail=%DETAIL%);
|
||||
|
||||
BEGIN;
|
||||
INSERT INTO t1(rowid, xyz) VALUES(13, 'thirteen documents');
|
||||
INSERT INTO t1(rowid, xyz) VALUES(14, 'fourteen documents');
|
||||
INSERT INTO t1(rowid, xyz) VALUES(15, 'fifteen documents');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
set db_cksum [cksum]
|
||||
foreach {tn sql} {
|
||||
1 {
|
||||
INSERT OR IGNORE INTO t1(rowid, xyz) VALUES(14, 'new text');
|
||||
}
|
||||
2 {
|
||||
UPDATE OR IGNORE t1 SET rowid=13 WHERE rowid=15;
|
||||
}
|
||||
3 {
|
||||
INSERT OR IGNORE INTO t1(rowid, xyz)
|
||||
SELECT 13, 'some text'
|
||||
UNION ALL
|
||||
SELECT 14, 'some text'
|
||||
UNION ALL
|
||||
SELECT 15, 'some text'
|
||||
}
|
||||
} {
|
||||
do_execsql_test 3.1.$tn.1 $sql
|
||||
do_test 3.1.$tn.2 { cksum } $db_cksum
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -294,4 +294,3 @@ do_catchsql_test 7.2.5 {
|
||||
} {1 {recursively defined fts5 content table}}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -268,4 +268,3 @@ do_execsql_test 8.2 {
|
||||
} {}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -205,4 +205,3 @@ foreach {tn step} {
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -193,4 +193,3 @@ do_execsql_test 3.7 {
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -245,4 +245,3 @@ do_execsql_test 4.3 {
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -56,4 +56,3 @@ foreach {tn up err} {
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -48,6 +48,10 @@ do_test 1.3 {
|
||||
}
|
||||
catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
|
||||
} {1 {database disk image is malformed}}
|
||||
do_execsql_test 1.3b {
|
||||
PRAGMA integrity_check(t1);
|
||||
} {{malformed inverted index for FTS5 table main.t1}}
|
||||
|
||||
|
||||
do_test 1.4 {
|
||||
db_restore_and_reopen
|
||||
|
||||
@@ -167,6 +167,9 @@ foreach {tn hdr} {
|
||||
do_test 3.$tn.$tn2.2 {
|
||||
catchsql { INSERT INTO x3(x3) VALUES('integrity-check') }
|
||||
} {1 {database disk image is malformed}}
|
||||
do_execsql_test 3.$tn.$tn2.3 {
|
||||
PRAGMA integrity_check(x3);
|
||||
} {{malformed inverted index for FTS5 table main.x3}}
|
||||
}
|
||||
|
||||
execsql ROLLBACK
|
||||
|
||||
@@ -44,12 +44,12 @@ do_catchsql_test 1.2.2 {
|
||||
|
||||
do_catchsql_test 1.3.1 {
|
||||
SELECT highlight(t1, 4, '<b>', '</b>') FROM t1('*reads');
|
||||
} {1 {no such cursor: 1}}
|
||||
} {1 {no such cursor: 2}}
|
||||
|
||||
do_catchsql_test 1.3.2 {
|
||||
SELECT a FROM t1
|
||||
WHERE rank = (SELECT highlight(t1, 4, '<b>', '</b>') FROM t1('*reads'));
|
||||
} {1 {no such cursor: 1}}
|
||||
} {1 {no such cursor: 2}}
|
||||
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
|
||||
@@ -180,4 +180,28 @@ do_execsql_test 6.1 {
|
||||
{table table table} {the table names.} {rank on an fts5 table}
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# forum post: https://sqlite.org/forum/forumpost/a2dd636330
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t USING fts5 (a, b);
|
||||
INSERT INTO t (a, b) VALUES ('data1', 'sentence1'), ('data2', 'sentence2');
|
||||
INSERT INTO t(t, rank) VALUES ('rank', 'bm25(10.0,1.0)');
|
||||
}
|
||||
|
||||
sqlite3 db2 test.db
|
||||
do_execsql_test -db db2 1.1 {
|
||||
SELECT *, rank<0.0 FROM t('data*') ORDER BY RANK;
|
||||
} {data1 sentence1 1 data2 sentence2 1}
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
INSERT INTO t(t, rank) VALUES ('rank', 'bm25(10.0,1.0)');
|
||||
}
|
||||
do_execsql_test -db db2 1.3 {
|
||||
SELECT *, rank<0.0 FROM t('data*') ORDER BY RANK;
|
||||
} {data1 sentence1 1 data2 sentence2 1}
|
||||
db2 close
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -273,6 +273,76 @@ do_execsql_test 5.3 {
|
||||
do_execsql_test 5.4 { SELECT rowid FROM t1('abc'); } 2
|
||||
do_execsql_test 5.5 { SELECT rowid FROM t1('aa'); } 2
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Tests for the bug fixed by https://sqlite.org/src/info/4b60a1c3
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 6.0 {
|
||||
CREATE VIRTUAL TABLE fts USING fts5(content);
|
||||
INSERT INTO fts(fts, rank) VALUES ('secure-delete', 1);
|
||||
INSERT INTO fts(rowid, content) VALUES
|
||||
(3407, 'profile profile profile profile profile profile profile profile pull pulling pulling really');
|
||||
DELETE FROM fts WHERE rowid IS 3407;
|
||||
INSERT INTO fts(fts) VALUES ('integrity-check');
|
||||
}
|
||||
|
||||
foreach {tn detail} {
|
||||
1 full
|
||||
2 column
|
||||
3 none
|
||||
} {
|
||||
do_execsql_test 6.1.$detail "
|
||||
DROP TABLE IF EXISTS t1;
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, detail=$detail);
|
||||
"
|
||||
|
||||
do_execsql_test 6.2.$detail {
|
||||
INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
|
||||
}
|
||||
|
||||
for {set ii 1} {$ii < 100} {incr ii} {
|
||||
do_execsql_test 6.3.$detail.$ii.1 {
|
||||
BEGIN;
|
||||
INSERT INTO t1(rowid, x) VALUES(10, 'word1');
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<CAST($ii AS integer)
|
||||
)
|
||||
INSERT INTO t1(x) SELECT 'word3' FROM s;
|
||||
COMMIT;
|
||||
INSERT INTO t1(t1) VALUES('optimize');
|
||||
}
|
||||
|
||||
do_execsql_test 6.3.$detail.$ii.2 {
|
||||
DELETE FROM t1 WHERE rowid=10;
|
||||
INSERT INTO t1(t1) VALUES ('integrity-check');
|
||||
}
|
||||
|
||||
do_execsql_test 6.3.$detail.$ii.3 {
|
||||
DELETE FROM t1;
|
||||
}
|
||||
|
||||
do_execsql_test 6.3.$detail.$ii.4 {
|
||||
BEGIN;
|
||||
INSERT INTO t1(rowid, x) VALUES(10, 'tokenA');
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<CAST($ii AS integer)
|
||||
)
|
||||
INSERT INTO t1(x) SELECT group_concat('tokenB ') FROM s;
|
||||
COMMIT;
|
||||
INSERT INTO t1(t1) VALUES('optimize');
|
||||
}
|
||||
|
||||
do_execsql_test 6.3.$detail.$ii.5 {
|
||||
DELETE FROM t1 WHERE rowid=10;
|
||||
INSERT INTO t1(t1) VALUES ('integrity-check');
|
||||
}
|
||||
|
||||
do_execsql_test 6.3.$detail.$ii.6 {
|
||||
DELETE FROM t1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -70,5 +70,26 @@ do_execsql_test 2.2 {
|
||||
SELECT rowid FROM t1('def')
|
||||
} {-100000 -99999 9223372036854775800}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x);
|
||||
INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd)
|
||||
}
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
BEGIN;
|
||||
INSERT INTO t1(rowid, x)
|
||||
VALUES(51869, 'when whenever where weress what turn'),
|
||||
(51871, 'to were');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
DELETE FROM t1 WHERE rowid=51871;
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# 2023 Feb 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.
|
||||
#
|
||||
#*************************************************************************
|
||||
#
|
||||
# TESTRUNNER: slow
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
ifcapable !fts5 { finish_test ; return }
|
||||
set ::testprefix fts5secure7
|
||||
|
||||
|
||||
set NVOCAB 500
|
||||
set NDOC [expr 1000]
|
||||
|
||||
set NREP 100
|
||||
set nDeletePerRep [expr 5]
|
||||
|
||||
set VOCAB [list]
|
||||
|
||||
proc select_one {list} {
|
||||
set n [llength $list]
|
||||
lindex $list [expr {abs(int(rand()*$n))}]
|
||||
}
|
||||
|
||||
proc init_vocab {} {
|
||||
set L [split "abcdefghijklmnopqrstuvwxyz" {}]
|
||||
set nL [llength $L]
|
||||
for {set i 0} {$i < $::NVOCAB} {incr i} {
|
||||
set n [expr {6 + int(rand()*8)}]
|
||||
set word ""
|
||||
for {set j 0} {$j < $n} {incr j} {
|
||||
append word [select_one $L]
|
||||
}
|
||||
lappend ::VOCAB $word
|
||||
}
|
||||
}
|
||||
|
||||
proc get_word {} {
|
||||
select_one $::VOCAB
|
||||
}
|
||||
|
||||
proc get_document {nWord} {
|
||||
set ret [list]
|
||||
for {set i 0} {$i < $nWord} {incr i} {
|
||||
lappend ret [get_word]
|
||||
}
|
||||
return $ret
|
||||
}
|
||||
|
||||
init_vocab
|
||||
|
||||
db func document [list get_document 12]
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(body);
|
||||
INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
|
||||
}
|
||||
do_execsql_test 1.1 {
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$NDOC
|
||||
)
|
||||
INSERT INTO t1 SELECT document() FROM s;
|
||||
}
|
||||
|
||||
for {set iRep 0} {$iRep < $NREP} {incr iRep} {
|
||||
set lRowid [db eval {SELECT rowid FROM t1}]
|
||||
for {set iDel 0} {$iDel < $nDeletePerRep} {incr iDel} {
|
||||
set idx [select_one $lRowid]
|
||||
db eval {
|
||||
DELETE FROM t1 WHERE rowid=$idx
|
||||
}
|
||||
}
|
||||
db eval {
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$nDeletePerRep
|
||||
)
|
||||
INSERT INTO t1 SELECT document() FROM s;
|
||||
}
|
||||
do_execsql_test 1.2.$iRep {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
}
|
||||
}
|
||||
|
||||
reset_db
|
||||
db func document [list get_document 12]
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(body);
|
||||
INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 128);
|
||||
}
|
||||
do_execsql_test 2.1 {
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$NDOC
|
||||
)
|
||||
INSERT INTO t1 SELECT document() FROM s;
|
||||
}
|
||||
for {set ii 0} {$ii < $NDOC} {incr ii} {
|
||||
set lRowid [db eval {SELECT rowid FROM t1}]
|
||||
set idx [select_one $lRowid]
|
||||
db eval { DELETE FROM t1 WHERE rowid=$idx }
|
||||
do_execsql_test 2.2.$ii {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
+144
-73
@@ -26,11 +26,19 @@ dir.src.c := $(dir.src)/c
|
||||
dir.bld := $(dir.jni)/bld
|
||||
dir.bld.c := $(dir.bld)
|
||||
dir.src.jni := $(dir.src)/org/sqlite/jni
|
||||
dir.src.jni.tester := $(dir.src.jni)/tester
|
||||
mkdir := mkdir -p
|
||||
dir.src.fts5 := $(dir.src.jni)/fts5
|
||||
dir.tests := $(dir.src)/tests
|
||||
mkdir ?= mkdir -p
|
||||
$(dir.bld.c):
|
||||
$(mkdir) $@
|
||||
|
||||
javac.flags ?= -Xlint:unchecked -Xlint:deprecation
|
||||
java.flags ?=
|
||||
jnicheck ?= 1
|
||||
ifeq (1,$(jnicheck))
|
||||
java.flags += -Xcheck:jni
|
||||
endif
|
||||
|
||||
classpath := $(dir.src)
|
||||
CLEAN_FILES := $(package.jar)
|
||||
DISTCLEAN_FILES := $(dir.jni)/*~ $(dir.src.c)/*~ $(dir.src.jni)/*~
|
||||
@@ -38,16 +46,27 @@ DISTCLEAN_FILES := $(dir.jni)/*~ $(dir.src.c)/*~ $(dir.src.jni)/*~
|
||||
sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
|
||||
.NOTPARALLEL: $(sqlite3-jni.h)
|
||||
SQLite3Jni.java := src/org/sqlite/jni/SQLite3Jni.java
|
||||
SQLTester.java := src/org/sqlite/jni/tester/SQLTester.java
|
||||
SQLTester.java := src/org/sqlite/jni/SQLTester.java
|
||||
SQLite3Jni.class := $(SQLite3Jni.java:.java=.class)
|
||||
SQLTester.class := $(SQLTester.java:.java=.class)
|
||||
|
||||
########################################################################
|
||||
# The future of FTS5 customization in this API is as yet unclear.
|
||||
# It would be a real doozy to bind to JNI.
|
||||
# The pieces are all in place, and are all thin proxies so not much
|
||||
# complexity, but some semantic changes were required in porting
|
||||
# which are largely untested.
|
||||
#
|
||||
# Reminder: this flag influences the contents of $(sqlite3-jni.h),
|
||||
# which is checked in. Please do not check in changes to that file in
|
||||
# which the fts5 APIs have been stripped unless that feature is
|
||||
# intended to be stripped for good.
|
||||
enable.fts5 ?= 1
|
||||
# If enable.tester is 0, the org/sqlite/jni/tester/* bits are elided.
|
||||
enable.tester ?= 1
|
||||
|
||||
ifeq (,$(wildcard $(dir.tests)/*))
|
||||
enable.tester := 0
|
||||
else
|
||||
enable.tester := 1
|
||||
endif
|
||||
|
||||
# bin.version-info = binary to output various sqlite3 version info
|
||||
# building the distribution zip file.
|
||||
@@ -59,6 +78,10 @@ $(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile
|
||||
# Be explicit about which Java files to compile so that we can work on
|
||||
# in-progress files without requiring them to be in a compilable statae.
|
||||
JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/%,\
|
||||
annotation/Canonical.java \
|
||||
annotation/NotNull.java \
|
||||
annotation/Nullable.java \
|
||||
AbstractCollationCallback.java \
|
||||
AggregateFunction.java \
|
||||
AuthorizerCallback.java \
|
||||
AutoExtensionCallback.java \
|
||||
@@ -66,71 +89,73 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/%,\
|
||||
CollationCallback.java \
|
||||
CollationNeededCallback.java \
|
||||
CommitHookCallback.java \
|
||||
ConfigLogCallback.java \
|
||||
ConfigSqllogCallback.java \
|
||||
NativePointerHolder.java \
|
||||
NotNull.java \
|
||||
Nullable.java \
|
||||
OutputPointer.java \
|
||||
PrepareMultiCallback.java \
|
||||
PreupdateHookCallback.java \
|
||||
ProgressHandlerCallback.java \
|
||||
ResultCode.java \
|
||||
RollbackHookCallback.java \
|
||||
ScalarFunction.java \
|
||||
SQLFunction.java \
|
||||
CallbackProxy.java \
|
||||
SQLite3Jni.java \
|
||||
Tester1.java \
|
||||
TableColumnMetadata.java \
|
||||
TraceV2Callback.java \
|
||||
UpdateHookCallback.java \
|
||||
ValueHolder.java \
|
||||
WindowFunction.java \
|
||||
XDestroyCallback.java \
|
||||
package-info.java \
|
||||
sqlite3.java \
|
||||
sqlite3_context.java \
|
||||
sqlite3_stmt.java \
|
||||
sqlite3_value.java \
|
||||
)
|
||||
JAVA_FILES.unittest := $(patsubst %,$(dir.src.jni)/%,\
|
||||
Tester1.java \
|
||||
)
|
||||
ifeq (1,$(enable.fts5))
|
||||
JAVA_FILES.main += $(patsubst %,$(dir.src.jni)/%,\
|
||||
JAVA_FILES.unittest += $(patsubst %,$(dir.src.jni)/%,\
|
||||
TesterFts5.java \
|
||||
)
|
||||
JAVA_FILES.main += $(patsubst %,$(dir.src.fts5)/%,\
|
||||
fts5_api.java \
|
||||
fts5_extension_function.java \
|
||||
fts5_tokenizer.java \
|
||||
Fts5.java \
|
||||
Fts5Context.java \
|
||||
Fts5ExtensionApi.java \
|
||||
Fts5Function.java \
|
||||
Fts5PhraseIter.java \
|
||||
Fts5Tokenizer.java \
|
||||
TesterFts5.java \
|
||||
XTokenizeCallback.java \
|
||||
)
|
||||
endif
|
||||
JAVA_FILES.tester := $(dir.src.jni.tester)/SQLTester.java
|
||||
JAVA_FILES.tester := $(SQLTester.java)
|
||||
JAVA_FILES.package.info := \
|
||||
$(dir.src.jni)/package-info.java \
|
||||
$(dir.src.jni)/annotation/package-info.java
|
||||
|
||||
CLASS_FILES.main := $(JAVA_FILES.main:.java=.class)
|
||||
CLASS_FILES.unittest := $(JAVA_FILES.unittest:.java=.class)
|
||||
CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class)
|
||||
|
||||
JAVA_FILES += $(JAVA_FILES.main)
|
||||
JAVA_FILES += $(JAVA_FILES.main) $(JAVA_FILES.unittest)
|
||||
ifeq (1,$(enable.tester))
|
||||
JAVA_FILES += $(JAVA_FILES.tester)
|
||||
endif
|
||||
|
||||
CLASS_FILES :=
|
||||
define DOTCLASS_DEPS
|
||||
$(1).class: $(1).java $(MAKEFILE)
|
||||
define CLASSFILE_DEPS
|
||||
all: $(1).class
|
||||
CLASS_FILES += $(1).class
|
||||
endef
|
||||
$(foreach B,$(basename $(JAVA_FILES)),$(eval $(call DOTCLASS_DEPS,$(B))))
|
||||
$(CLASS_FILES.tester): $(CLASS_FILES.main)
|
||||
javac.flags ?= -Xlint:unchecked -Xlint:deprecation
|
||||
java.flags ?=
|
||||
jnicheck ?= 1
|
||||
ifeq (1,$(jnicheck))
|
||||
java.flags += -Xcheck:jni
|
||||
endif
|
||||
$(SQLite3Jni.class): $(JAVA_FILES)
|
||||
$(foreach B,$(basename \
|
||||
$(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.tester)),\
|
||||
$(eval $(call CLASSFILE_DEPS,$(B))))
|
||||
$(CLASS_FILES): $(JAVA_FILES) $(MAKEFILE)
|
||||
$(bin.javac) $(javac.flags) -h $(dir.bld.c) -cp $(classpath) $(JAVA_FILES)
|
||||
all: $(SQLite3Jni.class)
|
||||
|
||||
#.PHONY: classfiles
|
||||
|
||||
########################################################################
|
||||
@@ -165,6 +190,9 @@ $(sqlite3.h):
|
||||
$(sqlite3.c): $(sqlite3.h)
|
||||
|
||||
opt.threadsafe ?= 1
|
||||
opt.fatal-oom ?= 1
|
||||
opt.debug ?= 1
|
||||
opt.metrics ?= 1
|
||||
SQLITE_OPT = \
|
||||
-DSQLITE_ENABLE_RTREE \
|
||||
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
|
||||
@@ -182,10 +210,14 @@ SQLITE_OPT = \
|
||||
-DSQLITE_TEMP_STORE=2 \
|
||||
-DSQLITE_USE_URI=1 \
|
||||
-DSQLITE_C=$(sqlite3.c) \
|
||||
-DSQLITE_JNI_FATAL_OOM=0 \
|
||||
-DSQLITE_DEBUG
|
||||
-DSQLITE_JNI_FATAL_OOM=$(opt.fatal-oom) \
|
||||
-DSQLITE_JNI_ENABLE_METRICS=$(opt.metrics)
|
||||
|
||||
SQLITE_OPT += -g -DDEBUG -UNDEBUG
|
||||
ifeq (1,$(opt.debug))
|
||||
SQLITE_OPT += -DSQLITE_DEBUG -g -DDEBUG -UNDEBUG
|
||||
else
|
||||
SQLITE_OPT += -O2
|
||||
endif
|
||||
|
||||
ifeq (1,$(enable.fts5))
|
||||
SQLITE_OPT += -DSQLITE_ENABLE_FTS5
|
||||
@@ -194,25 +226,29 @@ endif
|
||||
sqlite3-jni.c := $(dir.src.c)/sqlite3-jni.c
|
||||
sqlite3-jni.o := $(dir.bld.c)/sqlite3-jni.o
|
||||
sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
|
||||
sqlite3-jni.dll := $(dir.bld.c)/libsqlite3-jni.so
|
||||
package.dll := $(dir.bld.c)/libsqlite3-jni.so
|
||||
# All javac-generated .h files must be listed in $(sqlite3-jni.h.in):
|
||||
sqlite3-jni.h.in :=
|
||||
# $(java.with.jni) lists all Java files which contain JNI decls:
|
||||
java.with.jni :=
|
||||
define ADD_JNI_H
|
||||
sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni_$(1).h
|
||||
$$(dir.bld.c)/org_sqlite_jni_$(1).h: $$(dir.src.jni)/$(1).java
|
||||
sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni$(3)_$(2).h
|
||||
java.with.jni += $(1)/$(2).java
|
||||
$$(dir.bld.c)/org_sqlite_jni$(3)_$(2).h: $(1)/$(2).java
|
||||
endef
|
||||
$(eval $(call ADD_JNI_H,SQLite3Jni))
|
||||
# Invoke ADD_JNI_H once for each Java file which includes JNI
|
||||
# declarations:
|
||||
$(eval $(call ADD_JNI_H,$(dir.src.jni),SQLite3Jni,))
|
||||
$(eval $(call ADD_JNI_H,$(dir.src.jni),SQLTester,_tester))
|
||||
ifeq (1,$(enable.fts5))
|
||||
$(eval $(call ADD_JNI_H,Fts5ExtensionApi))
|
||||
$(eval $(call ADD_JNI_H,fts5_api))
|
||||
$(eval $(call ADD_JNI_H,fts5_tokenizer))
|
||||
$(eval $(call ADD_JNI_H,$(dir.src.fts5),Fts5ExtensionApi,_fts5))
|
||||
$(eval $(call ADD_JNI_H,$(dir.src.fts5),fts5_api,_fts5))
|
||||
$(eval $(call ADD_JNI_H,$(dir.src.fts5),fts5_tokenizer,_fts5))
|
||||
endif
|
||||
ifeq (1,$(enable.tester))
|
||||
sqlite3-jni.h.in += $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h
|
||||
$(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h: $(dir.src.jni.tester)/SQLTester.java
|
||||
endif
|
||||
#sqlite3-jni.dll.cfiles := $(dir.src.c)
|
||||
sqlite3-jni.dll.cflags = \
|
||||
$(sqlite3-jni.h.in): $(dir.bld.c)
|
||||
|
||||
#package.dll.cfiles :=
|
||||
package.dll.cflags = \
|
||||
-fPIC \
|
||||
-I. \
|
||||
-I$(dir $(sqlite3.h)) \
|
||||
@@ -220,31 +256,44 @@ sqlite3-jni.dll.cflags = \
|
||||
-I$(JDK_HOME)/include \
|
||||
$(patsubst %,-I%,$(patsubst %.h,,$(wildcard $(JDK_HOME)/include/*))) \
|
||||
-Wall
|
||||
# Using (-Wall -Wextra) triggers an untennable number of
|
||||
# gcc warnings from sqlite3.c for mundane things like
|
||||
# unused parameters.
|
||||
#
|
||||
# The gross $(patsubst...) above is to include the platform-specific
|
||||
# subdir which lives under $(JDK_HOME)/include and is a required
|
||||
# include path for client-level code.
|
||||
#
|
||||
# Using (-Wall -Wextra) triggers an untennable number of
|
||||
# gcc warnings from sqlite3.c for mundane things like
|
||||
# unused parameters.
|
||||
########################################################################
|
||||
ifeq (1,$(enable.tester))
|
||||
sqlite3-jni.dll.cflags += -DSQLITE_JNI_ENABLE_SQLTester
|
||||
package.dll.cflags += -DSQLITE_JNI_ENABLE_SQLTester
|
||||
endif
|
||||
|
||||
$(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE)
|
||||
cat $(sqlite3-jni.h.in) > $@
|
||||
$(sqlite3-jni.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h)
|
||||
$(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE)
|
||||
$(CC) $(sqlite3-jni.dll.cflags) $(SQLITE_OPT) \
|
||||
@cat $(sqlite3-jni.h.in) > $@.tmp
|
||||
@if cmp $@ $@.tmp >/dev/null; then \
|
||||
rm -f $@.tmp; \
|
||||
echo "$@ not modified"; \
|
||||
else \
|
||||
mv $@.tmp $@; \
|
||||
echo "Updated $@"; \
|
||||
fi
|
||||
@if [ x1 != x$(enable.fts5) ]; then \
|
||||
echo "*** REMINDER:"; \
|
||||
echo "*** enable.fts5=0, so please do not check in changes to $@."; \
|
||||
fi
|
||||
|
||||
$(package.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h)
|
||||
$(package.dll): $(sqlite3-jni.c) $(MAKEFILE)
|
||||
$(CC) $(package.dll.cflags) $(SQLITE_OPT) \
|
||||
$(sqlite3-jni.c) -shared -o $@
|
||||
all: $(sqlite3-jni.dll)
|
||||
all: $(package.dll)
|
||||
|
||||
.PHONY: test test-one
|
||||
test.flags ?=
|
||||
test.main.flags = -ea -Djava.library.path=$(dir.bld.c) \
|
||||
$(java.flags) -cp $(classpath) \
|
||||
org.sqlite.jni.Tester1
|
||||
test.deps := $(SQLite3Jni.class) $(sqlite3-jni.dll)
|
||||
test.deps := $(CLASS_FILES) $(package.dll)
|
||||
test-one: $(test.deps)
|
||||
$(bin.java) $(test.main.flags) $(test.flags)
|
||||
test-sqllog: $(test.deps)
|
||||
@@ -252,7 +301,7 @@ test-sqllog: $(test.deps)
|
||||
$(bin.java) $(test.main.flags) -sqllog
|
||||
test-mt: $(test.deps)
|
||||
@echo "Testing in multi-threaded mode:";
|
||||
$(bin.java) $(test.main.flags) -t 5 -r 20 -shuffle $(test.flags)
|
||||
$(bin.java) $(test.main.flags) -t 7 -r 50 -shuffle $(test.flags)
|
||||
|
||||
test: test-one test-mt
|
||||
tests: test test-sqllog
|
||||
@@ -261,24 +310,24 @@ tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test))
|
||||
tester.flags ?= # --verbose
|
||||
.PHONY: tester tester-local tester-ext
|
||||
ifeq (1,$(enable.tester))
|
||||
tester-local: $(CLASS_FILES.tester) $(sqlite3-jni.dll)
|
||||
tester-local: $(CLASS_FILES.tester) $(package.dll)
|
||||
$(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
|
||||
$(java.flags) -cp $(classpath) \
|
||||
org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.scripts)
|
||||
org.sqlite.jni.SQLTester $(tester.flags) $(tester.scripts)
|
||||
tester: tester-local
|
||||
else
|
||||
tester:
|
||||
@echo "SQLTester support is disabled. Build with enable.tester=1 to enable it."
|
||||
@echo "SQLTester support is disabled."
|
||||
endif
|
||||
|
||||
tester.extdir.default := src/tests/ext
|
||||
tester.extdir.default := $(dir.tests)/ext
|
||||
tester.extdir ?= $(tester.extdir.default)
|
||||
tester.extern-scripts := $(wildcard $(tester.extdir)/*.test)
|
||||
ifneq (,$(tester.extern-scripts))
|
||||
tester-ext:
|
||||
$(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
|
||||
$(java.flags) -cp $(classpath) \
|
||||
org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.extern-scripts)
|
||||
org.sqlite.jni.SQLTester $(tester.flags) $(tester.extern-scripts)
|
||||
else
|
||||
tester-ext:
|
||||
@echo "******************************************************"; \
|
||||
@@ -292,37 +341,59 @@ endif
|
||||
tester-ext: tester-local
|
||||
tester: tester-ext
|
||||
tests: tester
|
||||
|
||||
########################################################################
|
||||
# Build each SQLITE_THREADMODE variant and run all tests against them.
|
||||
multitest: clean
|
||||
$(MAKE) opt.threadsafe=0 tests clean
|
||||
$(MAKE) opt.threadsafe=1 tests clean
|
||||
$(MAKE) opt.threadsafe=2 tests clean
|
||||
define MULTIOPT
|
||||
multitest: multitest-$(1)
|
||||
multitest-$(1):
|
||||
$$(MAKE) opt.debug=$$(opt.debug) $(patsubst %,opt.%,$(2)) \
|
||||
tests clean enable.fts5=1
|
||||
endef
|
||||
|
||||
$(eval $(call MULTIOPT,01,threadsafe=0 oom=1))
|
||||
$(eval $(call MULTIOPT,00,threadsafe=0 oom=0))
|
||||
$(eval $(call MULTIOPT,11,threadsafe=1 oom=1))
|
||||
$(eval $(call MULTIOPT,10,threadsafe=1 oom=0))
|
||||
$(eval $(call MULTIOPT,21,threadsafe=2 oom=1))
|
||||
$(eval $(call MULTIOPT,20,threadsafe=2 oom=0))
|
||||
|
||||
|
||||
########################################################################
|
||||
# jar bundle...
|
||||
package.jar.in := $(abspath $(dir.src)/jar.in)
|
||||
CLEAN_FILES += $(package.jar.in)
|
||||
$(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main)
|
||||
cd $(dir.src); ls -1 org/sqlite/jni/*.java org/sqlite/jni/*.class > $@
|
||||
@echo "To use this jar you will need the -Djava.library.path=DIR/CONTAINING/libsqlite3-jni.so flag."
|
||||
@echo "e.g. java -jar $@ -Djava.library.path=bld"
|
||||
JAVA_FILES.jar := $(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.package.info)
|
||||
CLASS_FILES.jar := $(filter-out %/package-info.class,$(JAVA_FILES.jar:.java=.class))
|
||||
$(package.jar.in): $(package.dll) $(MAKEFILE)
|
||||
ls -1 \
|
||||
$(dir.src.jni)/*.java $(dir.src.jni)/*.class \
|
||||
$(dir.src.jni)/annotation/*.java $(dir.src.jni)/annotation/*.class \
|
||||
| sed -e 's,^$(dir.src)/,,' | sort > $@
|
||||
|
||||
$(package.jar): $(CLASS_FILES) $(MAKEFILE) $(package.jar.in)
|
||||
rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~
|
||||
$(package.jar): $(CLASS_FILES.jar) $(MAKEFILE) $(package.jar.in)
|
||||
@rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~
|
||||
cd $(dir.src); $(bin.jar) -cfe ../$@ org.sqlite.jni.Tester1 @$(package.jar.in)
|
||||
@ls -la $@
|
||||
@echo "To use this jar you will need the -Djava.library.path=DIR/CONTAINING/libsqlite3-jni.so flag."
|
||||
@echo "e.g. java -Djava.library.path=bld -jar $@"
|
||||
|
||||
jar: $(package.jar)
|
||||
run-jar: $(package.jar) $(package.dll)
|
||||
$(bin.java) -Djava.library.path=$(dir.bld) -jar $(package.jar) $(run-jar.flags)
|
||||
|
||||
########################################################################
|
||||
# javadoc...
|
||||
dir.doc := $(dir.jni)/javadoc
|
||||
doc.index := $(dir.doc)/index.html
|
||||
javadoc.exclude := -exclude org.sqlite.jni.fts5
|
||||
# ^^^^ 2023-09-13: elide the fts5 parts from the public docs for
|
||||
# the time being, as it's not clear where the Java bindings for
|
||||
# those bits are going.
|
||||
$(doc.index): $(JAVA_FILES.main) $(MAKEFILE)
|
||||
@if [ -d $(dir.doc) ]; then rm -fr $(dir.doc)/*; fi
|
||||
$(bin.javadoc) -cp $(classpath) -d $(dir.doc) -quiet org.sqlite.jni
|
||||
$(bin.javadoc) -cp $(classpath) -d $(dir.doc) -quiet \
|
||||
-subpackages org.sqlite.jni $(javadoc.exclude)
|
||||
@echo "javadoc output is in $@"
|
||||
|
||||
.PHONY: doc javadoc docserve
|
||||
@@ -340,7 +411,7 @@ docserve: $(doc.index)
|
||||
CLEAN_FILES += $(dir.bld.c)/* \
|
||||
$(dir.src.jni)/*.class \
|
||||
$(dir.src.jni.tester)/*.class \
|
||||
$(sqlite3-jni.dll) \
|
||||
$(package.dll) \
|
||||
hs_err_pid*.log
|
||||
|
||||
.PHONY: clean distclean
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ Hello World
|
||||
|
||||
```java
|
||||
import org.sqlite.jni.*;
|
||||
import static SQLite3Jni.*;
|
||||
import static org.sqlite.jni.SQLite3Jni.*;
|
||||
|
||||
...
|
||||
|
||||
|
||||
+2101
-1452
File diff suppressed because it is too large
Load Diff
+722
-432
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
** 2023-08-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 is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
import org.sqlite.jni.annotation.NotNull;
|
||||
|
||||
/**
|
||||
An implementation of {@link CollationCallback} which provides a
|
||||
no-op xDestroy() method.
|
||||
*/
|
||||
public abstract class AbstractCollationCallback
|
||||
implements CollationCallback, XDestroyCallback {
|
||||
/**
|
||||
Must compare the given byte arrays and return the result using
|
||||
{@code memcmp()} semantics.
|
||||
*/
|
||||
public abstract int call(@NotNull byte[] lhs, @NotNull byte[] rhs);
|
||||
|
||||
/**
|
||||
Optionally override to be notified when the UDF is finalized by
|
||||
SQLite. This implementation does nothing.
|
||||
*/
|
||||
public void xDestroy(){}
|
||||
}
|
||||
@@ -15,12 +15,12 @@ package org.sqlite.jni;
|
||||
|
||||
|
||||
/**
|
||||
SQLFunction subclass for creating aggregate functions. Its T is
|
||||
the data type of its "accumulator" state, an instance of which is
|
||||
A SQLFunction implementation for aggregate functions. Its T is the
|
||||
data type of its "accumulator" state, an instance of which is
|
||||
intended to be be managed using the getAggregateState() and
|
||||
takeAggregateState() methods.
|
||||
*/
|
||||
public abstract class AggregateFunction<T> extends SQLFunction {
|
||||
public abstract class AggregateFunction<T> implements SQLFunction {
|
||||
|
||||
/**
|
||||
As for the xStep() argument of the C API's
|
||||
@@ -48,7 +48,7 @@ public abstract class AggregateFunction<T> extends SQLFunction {
|
||||
|
||||
/**
|
||||
To be called from the implementation's xStep() method, as well
|
||||
as the xValue() and xInverse() methods of the Window<T>
|
||||
as the xValue() and xInverse() methods of the {@link WindowFunction}
|
||||
subclass, to fetch the current per-call UDF state. On the
|
||||
first call to this method for any given sqlite3_context
|
||||
argument, the context is set to the given initial value. On all other
|
||||
|
||||
@@ -12,16 +12,15 @@
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
import org.sqlite.jni.annotation.*;
|
||||
|
||||
/**
|
||||
Callback for use with sqlite3_set_authorizer().
|
||||
Callback for use with {@link SQLite3Jni#sqlite3_set_authorizer}.
|
||||
*/
|
||||
public interface AuthorizerCallback extends SQLite3CallbackProxy {
|
||||
public interface AuthorizerCallback extends CallbackProxy {
|
||||
/**
|
||||
Must function as described for the C-level
|
||||
sqlite3_set_authorizer() callback.
|
||||
|
||||
Must not throw.
|
||||
*/
|
||||
int call(int opId, @Nullable String s1, @Nullable String s2,
|
||||
@Nullable String s3, @Nullable String s4);
|
||||
|
||||
@@ -14,28 +14,27 @@
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
A callback for use with the sqlite3_auto_extension() family of
|
||||
APIs.
|
||||
Callback for use with the {@link SQLite3Jni#sqlite3_auto_extension}
|
||||
family of APIs.
|
||||
*/
|
||||
public interface AutoExtensionCallback extends SQLite3CallbackProxy {
|
||||
public interface AutoExtensionCallback extends CallbackProxy {
|
||||
/**
|
||||
Must function as described for a C-level
|
||||
sqlite3_auto_extension() callback, with the caveat that the
|
||||
signature is shorter.
|
||||
sqlite3_auto_extension() callback.
|
||||
|
||||
This callback may throw and the exception's error message will
|
||||
<p>This callback may throw and the exception's error message will
|
||||
be set as the db's error string.
|
||||
|
||||
Tips for implementations:
|
||||
<p>Tips for implementations:
|
||||
|
||||
- Opening a database from an auto-extension handler will lead to
|
||||
<p>- Opening a database from an auto-extension handler will lead to
|
||||
an endless recursion of the auto-handler triggering itself
|
||||
indirectly for each newly-opened database.
|
||||
|
||||
- If this routine is stateful, it may be useful to make the
|
||||
<p>- If this routine is stateful, it may be useful to make the
|
||||
overridden method synchronized.
|
||||
|
||||
- Results are undefined if db is closed by an auto-extension.
|
||||
<p>- Results are undefined if the given db is closed by an auto-extension.
|
||||
*/
|
||||
int call(sqlite3 db);
|
||||
}
|
||||
|
||||
@@ -13,18 +13,14 @@
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
|
||||
/**
|
||||
Callback for use with sqlite3_busy_handler()
|
||||
Callback for use with {@link SQLite3Jni#sqlite3_busy_handler}.
|
||||
*/
|
||||
public abstract class BusyHandlerCallback implements SQLite3CallbackProxy {
|
||||
public interface BusyHandlerCallback extends CallbackProxy {
|
||||
/**
|
||||
Must function as documented for the C-level
|
||||
sqlite3_busy_handler() callback argument, minus the (void*)
|
||||
argument the C-level function requires.
|
||||
|
||||
Any exceptions thrown by this callback are suppressed in order to
|
||||
retain the C-style API semantics of the JNI bindings.
|
||||
*/
|
||||
public abstract int call(int n);
|
||||
int call(int n);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
** 2023-08-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 is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
/**
|
||||
This marker interface exists soley for use as a documentation and
|
||||
class-grouping tool. It should be applied to interfaces or
|
||||
classes which have a call() method implementing some specific
|
||||
callback interface on behalf of the C library.
|
||||
|
||||
<p>Unless very explicitely documented otherwise, callbacks must
|
||||
never throw. Any which do throw but should not might trigger debug
|
||||
output regarding the error, but the exception will not be
|
||||
propagated. For callback interfaces which support returning error
|
||||
info to the core, the JNI binding will convert any exceptions to
|
||||
C-level error information. For callback interfaces which do not
|
||||
support, all exceptions will necessarily be suppressed in order to
|
||||
retain the C-style no-throw semantics.
|
||||
|
||||
<p>Callbacks of this style follow a common naming convention:
|
||||
|
||||
<p>1) They use the UpperCamelCase form of the C function they're
|
||||
proxying for, minus the {@code sqlite3_} prefix, plus a {@code
|
||||
Callback} suffix. e.g. {@code sqlite3_busy_handler()}'s callback is
|
||||
named {@code BusyHandlerCallback}. Exceptions are made where that
|
||||
would potentially be ambiguous, e.g. {@link ConfigSqllogCallback}
|
||||
instead of {@code ConfigCallback} because the {@code
|
||||
sqlite3_config()} interface may need to support more callback types
|
||||
in the future.
|
||||
|
||||
<p>2) They all have a {@code call()} method but its signature is
|
||||
callback-specific.
|
||||
*/
|
||||
public interface CallbackProxy {}
|
||||
@@ -12,20 +12,24 @@
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
import org.sqlite.jni.annotation.NotNull;
|
||||
|
||||
/**
|
||||
Callback for use with sqlite3_create_collation()
|
||||
Callback for use with {@link SQLite3Jni#sqlite3_create_collation}.
|
||||
|
||||
@see AbstractCollationCallback
|
||||
*/
|
||||
public abstract class CollationCallback
|
||||
implements SQLite3CallbackProxy, XDestroyCallback {
|
||||
public interface CollationCallback
|
||||
extends CallbackProxy, XDestroyCallback {
|
||||
/**
|
||||
Must compare the given byte arrays using memcmp() semantics.
|
||||
Must compare the given byte arrays and return the result using
|
||||
{@code memcmp()} semantics.
|
||||
*/
|
||||
public abstract int call(byte[] lhs, byte[] rhs);
|
||||
int call(@NotNull byte[] lhs, @NotNull byte[] rhs);
|
||||
|
||||
/**
|
||||
Called by SQLite when the collation is destroyed. If a collation
|
||||
requires custom cleanup, override this method.
|
||||
*/
|
||||
public void xDestroy(){}
|
||||
void xDestroy();
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback for use with sqlite3_collation_needed().
|
||||
Callback for use with {@link SQLite3Jni#sqlite3_collation_needed}.
|
||||
*/
|
||||
public interface CollationNeededCallback extends SQLite3CallbackProxy {
|
||||
public interface CollationNeededCallback extends CallbackProxy {
|
||||
/**
|
||||
Has the same semantics as the C-level sqlite3_create_collation()
|
||||
callback.
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback for use with sqlite3_commit_hook()
|
||||
Callback for use with {@link SQLite3Jni#sqlite3_commit_hook}.
|
||||
*/
|
||||
public interface CommitHookCallback extends SQLite3CallbackProxy {
|
||||
public interface CommitHookCallback extends CallbackProxy {
|
||||
/**
|
||||
Works as documented for the C-level sqlite3_commit_hook()
|
||||
callback. Must not throw.
|
||||
|
||||
+8
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
** 2023-07-21
|
||||
** 2023-08-23
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
@@ -14,12 +14,12 @@
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
A helper class which simply holds a single value. Its current use
|
||||
is for communicating values out of anonymous classes, as doing so
|
||||
requires a "final" reference.
|
||||
A callback for use with sqlite3_config().
|
||||
*/
|
||||
public class ValueHolder<T> {
|
||||
public T value;
|
||||
public ValueHolder(){}
|
||||
public ValueHolder(T v){value = v;}
|
||||
public interface ConfigLogCallback {
|
||||
/**
|
||||
Must function as described for a C-level callback for
|
||||
{@link SQLite3Jni#sqlite3_config(ConfigLogCallback)}, with the slight signature change.
|
||||
*/
|
||||
void call(int errCode, String msg);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
** 2023-08-04
|
||||
**
|
||||
** 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 is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Fts5Function is used in conjunction with the
|
||||
sqlite3_create_fts_function() JNI-bound API to give that native code
|
||||
access to the callback functions needed in order to implement
|
||||
FTS5 auxiliary functions in Java.
|
||||
*/
|
||||
public abstract class Fts5Function {
|
||||
|
||||
public abstract void xFunction(Fts5ExtensionApi pApi, Fts5Context pFts,
|
||||
sqlite3_context pCtx, sqlite3_value argv[]);
|
||||
public void xDestroy() {}
|
||||
}
|
||||
@@ -29,5 +29,18 @@ package org.sqlite.jni;
|
||||
public class NativePointerHolder<ContextType> {
|
||||
//! Only set from JNI, where access permissions don't matter.
|
||||
private volatile long nativePointer = 0;
|
||||
/**
|
||||
For use ONLY by package-level APIs which act as proxies for
|
||||
close/finalize operations. Such ops must call this to zero out
|
||||
the pointer so that this object is not carrying a stale
|
||||
pointer. This function returns the prior value of the pointer and
|
||||
sets it to 0.
|
||||
*/
|
||||
final long clearNativePointer() {
|
||||
final long rv = nativePointer;
|
||||
nativePointer= 0;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public final long getNativePointer(){ return nativePointer; }
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
This annotation is for flagging parameters which may not legally be
|
||||
null. Note that the C-style API does not throw any exceptions on
|
||||
its own because it has a no-throw policy in order to retain its
|
||||
C-style semantics, but it may trigger NullPointerExceptions (or
|
||||
similar) if passed a null for a parameter flagged with this
|
||||
annotation.
|
||||
|
||||
<p>This annotation is informational only. No policy is in place to
|
||||
programmatically ensure that NotNull is conformed to in client
|
||||
code.
|
||||
|
||||
<p>This annotation is solely for the use by the classes in this
|
||||
package but is made public so that javadoc will link to it from the
|
||||
annotated functions. It is not part of the public API and
|
||||
client-level code must not rely on it.
|
||||
*/
|
||||
@java.lang.annotation.Documented
|
||||
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
|
||||
@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
|
||||
public @interface NotNull{}
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
This annotation is for flagging parameters which may legally be
|
||||
null, noting that they may behave differently if passed null but
|
||||
are prepared to expect null as a value.
|
||||
|
||||
<p>This annotation is solely for the use by the classes in this
|
||||
package but is made public so that javadoc will link to it from the
|
||||
annotated functions. It is not part of the public API and
|
||||
client-level code must not rely on it.
|
||||
*/
|
||||
@java.lang.annotation.Documented
|
||||
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
|
||||
@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
|
||||
public @interface Nullable{}
|
||||
@@ -64,6 +64,27 @@ public final class OutputPointer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Output pointer for sqlite3_blob_open(). These
|
||||
pointers can only be set by the JNI layer, not by client-level
|
||||
code.
|
||||
*/
|
||||
public static final class sqlite3_blob {
|
||||
private org.sqlite.jni.sqlite3_blob value;
|
||||
/** Initializes with a null value. */
|
||||
public sqlite3_blob(){value = null;}
|
||||
/** Sets the current value to null. */
|
||||
public void clear(){value = null;}
|
||||
/** Returns the current value. */
|
||||
public final org.sqlite.jni.sqlite3_blob get(){return value;}
|
||||
/** Equivalent to calling get() then clear(). */
|
||||
public final org.sqlite.jni.sqlite3_blob take(){
|
||||
final org.sqlite.jni.sqlite3_blob v = value;
|
||||
value = null;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Output pointer for use with routines, such as sqlite3_prepare(),
|
||||
which return a statement handle via an output pointer. These
|
||||
@@ -108,6 +129,26 @@ public final class OutputPointer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Output pointer for use with native routines which return booleans
|
||||
via integer output pointers.
|
||||
*/
|
||||
public static final class Bool {
|
||||
/**
|
||||
This is public for ease of use. Accessors are provided for
|
||||
consistency with the higher-level types.
|
||||
*/
|
||||
public boolean value;
|
||||
/** Initializes with the value 0. */
|
||||
public Bool(){this(false);}
|
||||
/** Initializes with the value v. */
|
||||
public Bool(boolean v){value = v;}
|
||||
/** Returns the current value. */
|
||||
public final boolean get(){return value;}
|
||||
/** Sets the current value to v. */
|
||||
public final void set(boolean v){value = v;}
|
||||
}
|
||||
|
||||
/**
|
||||
Output pointer for use with native routines which return integers via
|
||||
output pointers.
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
** 2023-09-13
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback for use with {@link SQLite3Jni#sqlite3_prepare_multi}.
|
||||
*/
|
||||
public interface PrepareMultiCallback extends CallbackProxy {
|
||||
|
||||
/**
|
||||
Gets passed a sqlite3_stmt which it may handle in arbitrary ways,
|
||||
transfering ownership of it to this function.
|
||||
|
||||
sqlite3_prepare_multi() will _not_ finalize st - it is up
|
||||
to the call() implementation how st is handled.
|
||||
|
||||
Must return 0 on success or an SQLITE_... code on error.
|
||||
|
||||
See the {@link Finalize} class for a wrapper which finalizes the
|
||||
statement after calling a proxy PrepareMultiCallback.
|
||||
*/
|
||||
int call(sqlite3_stmt st);
|
||||
|
||||
/**
|
||||
A PrepareMultiCallback impl which wraps a separate impl and finalizes
|
||||
any sqlite3_stmt passed to its callback.
|
||||
*/
|
||||
public static final class Finalize implements PrepareMultiCallback {
|
||||
private PrepareMultiCallback p;
|
||||
/**
|
||||
p is the proxy to call() when this.call() is called.
|
||||
*/
|
||||
public Finalize( PrepareMultiCallback p ){
|
||||
this.p = p;
|
||||
}
|
||||
/**
|
||||
Calls the call() method of the proxied callback and either returns its
|
||||
result or propagates an exception. Either way, it passes its argument to
|
||||
sqlite3_finalize() before returning.
|
||||
*/
|
||||
@Override public int call(sqlite3_stmt st){
|
||||
try {
|
||||
return this.p.call(st);
|
||||
}finally{
|
||||
SQLite3Jni.sqlite3_finalize(st);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A PrepareMultiCallback impl which steps entirely through a result set,
|
||||
ignoring all non-error results.
|
||||
*/
|
||||
public static final class StepAll implements PrepareMultiCallback {
|
||||
public StepAll(){}
|
||||
/**
|
||||
Calls sqlite3_step() on st until it returns something other than
|
||||
SQLITE_ROW. If the final result is SQLITE_DONE then 0 is returned,
|
||||
else the result of the final step is returned.
|
||||
*/
|
||||
@Override public int call(sqlite3_stmt st){
|
||||
int rc = SQLite3Jni.SQLITE_DONE;
|
||||
while( SQLite3Jni.SQLITE_ROW == (rc = SQLite3Jni.sqlite3_step(st)) ){}
|
||||
return SQLite3Jni.SQLITE_DONE==rc ? 0 : rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,12 @@
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback for use with sqlite3_preupdate_hook().
|
||||
Callback for use with {@link SQLite3Jni#sqlite3_preupdate_hook}.
|
||||
*/
|
||||
public interface PreupdateHookCallback extends SQLite3CallbackProxy {
|
||||
public interface PreupdateHookCallback extends CallbackProxy {
|
||||
/**
|
||||
Must function as described for the C-level sqlite3_preupdate_hook()
|
||||
callback. Must not throw.
|
||||
callback.
|
||||
*/
|
||||
void call(sqlite3 db, int op, String dbName, String dbTable,
|
||||
long iKey1, long iKey2 );
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback for use with sqlite3_progress_handler()
|
||||
Callback for use with {@link SQLite3Jni#sqlite3_progress_handler}.
|
||||
*/
|
||||
public interface ProgressHandlerCallback extends SQLite3CallbackProxy {
|
||||
public interface ProgressHandlerCallback extends CallbackProxy {
|
||||
/**
|
||||
Works as documented for the C-level sqlite3_progress_handler() callback.
|
||||
|
||||
If it throws, the exception message is passed on to the db and
|
||||
<p>If it throws, the exception message is passed on to the db and
|
||||
the exception is suppressed.
|
||||
*/
|
||||
int call();
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback for use with sqlite3_rollback_hook()
|
||||
Callback for use with {@link SQLite3Jni#sqlite3_rollback_hook}.
|
||||
*/
|
||||
public interface RollbackHookCallback extends SQLite3CallbackProxy {
|
||||
public interface RollbackHookCallback extends CallbackProxy {
|
||||
/**
|
||||
Works as documented for the C-level sqlite3_rollback_hook()
|
||||
callback. Must not throw.
|
||||
callback.
|
||||
*/
|
||||
void call();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ package org.sqlite.jni;
|
||||
SQLFunction base class and the method names and signatures used by
|
||||
the UDF callback interfaces.
|
||||
*/
|
||||
public abstract class SQLFunction {
|
||||
public interface SQLFunction {
|
||||
|
||||
/**
|
||||
PerContextState assists aggregate and window functions in
|
||||
@@ -53,11 +53,11 @@ public abstract class SQLFunction {
|
||||
state, of a client-defined type (the T part of this class), which
|
||||
persists across a "matching set" of the UDF's callbacks.
|
||||
|
||||
<p>This class is a helper providing commonly-needed functionality -
|
||||
it is not required for use with aggregate or window functions.
|
||||
<p>This class is a helper providing commonly-needed functionality
|
||||
- it is not required for use with aggregate or window functions.
|
||||
Client UDFs are free to perform such mappings using custom
|
||||
approaches. The provided Aggregate<T> and Window<T> classes
|
||||
use this.
|
||||
approaches. The provided {@link AggregateFunction} and {@link
|
||||
WindowFunction} classes use this.
|
||||
*/
|
||||
public static final class PerContextState<T> {
|
||||
private final java.util.Map<Long,ValueHolder<T>> map
|
||||
|
||||
+51
-39
@@ -12,16 +12,13 @@
|
||||
** This file contains the main application entry pointer for the
|
||||
** SQLTester framework.
|
||||
*/
|
||||
package org.sqlite.jni.tester;
|
||||
package org.sqlite.jni;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.regex.*;
|
||||
import org.sqlite.jni.*;
|
||||
import static org.sqlite.jni.SQLite3Jni.*;
|
||||
import org.sqlite.jni.sqlite3;
|
||||
|
||||
|
||||
/**
|
||||
Modes for how to escape (or not) column values and names from
|
||||
@@ -71,7 +68,7 @@ class SQLTesterException extends RuntimeException {
|
||||
|
||||
class DbException extends SQLTesterException {
|
||||
DbException(sqlite3 db, int rc, boolean closeDb){
|
||||
super("DB error #"+rc+": "+sqlite3_errmsg(db),true);
|
||||
super("DB error #"+rc+": "+sqlite3_errmsg16(db),true);
|
||||
if( closeDb ) sqlite3_close_v2(db);
|
||||
}
|
||||
DbException(sqlite3 db, int rc){
|
||||
@@ -150,14 +147,17 @@ class Outer {
|
||||
}
|
||||
|
||||
/**
|
||||
This class provides an application which aims to implement the
|
||||
<p>This class provides an application which aims to implement the
|
||||
rudimentary SQL-driven test tool described in the accompanying
|
||||
test-script-interpreter.md.
|
||||
{@code test-script-interpreter.md}.
|
||||
|
||||
This is a work in progress.
|
||||
<p>This class is an internal testing tool, not part of the public
|
||||
interface but is (A) in the same package as the library because
|
||||
access permissions require it to be so and (B) the JDK8 javadoc
|
||||
offers no way to filter individual classes out of the doc
|
||||
generation process (it can only exclude packages, but see (A)).
|
||||
|
||||
|
||||
An instance of this application provides a core set of services
|
||||
<p>An instance of this application provides a core set of services
|
||||
which TestScript instances use for processing testing logic.
|
||||
TestScripts, in turn, delegate the concrete test work to Command
|
||||
objects, which the TestScript parses on their behalf.
|
||||
@@ -181,7 +181,7 @@ public class SQLTester {
|
||||
private int nTestFile = 0;
|
||||
//! Number of scripts which were aborted.
|
||||
private int nAbortedScript = 0;
|
||||
//! Per-script test counter.
|
||||
//! Incremented by test case handlers
|
||||
private int nTest = 0;
|
||||
//! True to enable column name output from execSql()
|
||||
private boolean emitColNames;
|
||||
@@ -270,7 +270,6 @@ public class SQLTester {
|
||||
final long timeEnd = System.currentTimeMillis();
|
||||
outln("🏁",(threw ? "❌" : "✅")," ",nTest," test(s) in ",
|
||||
(timeEnd-timeStart),"ms.");
|
||||
//ts.getFilename());
|
||||
}
|
||||
}
|
||||
final long tEnd = System.currentTimeMillis();
|
||||
@@ -336,7 +335,9 @@ public class SQLTester {
|
||||
}
|
||||
|
||||
sqlite3 setCurrentDb(int n) throws Exception{
|
||||
return affirmDbId(n).aDb[n];
|
||||
affirmDbId(n);
|
||||
iCurrentDb = n;
|
||||
return this.aDb[n];
|
||||
}
|
||||
|
||||
sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; }
|
||||
@@ -399,7 +400,7 @@ public class SQLTester {
|
||||
nullView = "nil";
|
||||
emitColNames = false;
|
||||
iCurrentDb = 0;
|
||||
dbInitSql.append("SELECT 1;");
|
||||
//dbInitSql.append("SELECT 1;");
|
||||
}
|
||||
|
||||
void setNullValue(String v){nullView = v;}
|
||||
@@ -457,7 +458,7 @@ public class SQLTester {
|
||||
|
||||
private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){
|
||||
sb.append(org.sqlite.jni.ResultCode.getEntryForInt(rc)).append(' ');
|
||||
final String msg = escapeSqlValue(sqlite3_errmsg(db));
|
||||
final String msg = escapeSqlValue(sqlite3_errmsg16(db));
|
||||
if( '{' == msg.charAt(0) ){
|
||||
sb.append(msg);
|
||||
}else{
|
||||
@@ -474,12 +475,12 @@ public class SQLTester {
|
||||
the db's result code.
|
||||
|
||||
appendMode specifies how/whether to append results to the result
|
||||
buffer. lineMode specifies whether to output all results in a
|
||||
buffer. rowMode specifies whether to output all results in a
|
||||
single line or one line per row. If appendMode is
|
||||
ResultBufferMode.NONE then lineMode is ignored and may be null.
|
||||
ResultBufferMode.NONE then rowMode is ignored and may be null.
|
||||
*/
|
||||
public int execSql(sqlite3 db, boolean throwOnError,
|
||||
ResultBufferMode appendMode, ResultRowMode lineMode,
|
||||
ResultBufferMode appendMode, ResultRowMode rowMode,
|
||||
String sql) throws SQLTesterException {
|
||||
if( null==db && null==aDb[0] ){
|
||||
// Delay opening of the initial db to enable tests to change its
|
||||
@@ -561,7 +562,7 @@ public class SQLTester {
|
||||
throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode);
|
||||
}
|
||||
}
|
||||
if( ResultRowMode.NEWLINE == lineMode ){
|
||||
if( ResultRowMode.NEWLINE == rowMode ){
|
||||
spacing = 0;
|
||||
sb.append('\n');
|
||||
}
|
||||
@@ -580,6 +581,10 @@ public class SQLTester {
|
||||
}
|
||||
}
|
||||
}finally{
|
||||
sqlite3_reset(stmt
|
||||
/* In order to trigger an exception in the
|
||||
INSERT...RETURNING locking scenario:
|
||||
https://sqlite.org/forum/forumpost/36f7a2e7494897df */);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
if( 0!=rc && throwOnError ){
|
||||
@@ -924,10 +929,10 @@ class RunCommand extends Command {
|
||||
final sqlite3 db = (1==argv.length)
|
||||
? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) );
|
||||
final String sql = t.takeInputBuffer();
|
||||
int rc = t.execSql(db, false, ResultBufferMode.NONE,
|
||||
ResultRowMode.ONELINE, sql);
|
||||
final int rc = t.execSql(db, false, ResultBufferMode.NONE,
|
||||
ResultRowMode.ONELINE, sql);
|
||||
if( 0!=rc && t.isVerbose() ){
|
||||
String msg = sqlite3_errmsg(db);
|
||||
String msg = sqlite3_errmsg16(db);
|
||||
ts.verbose1(argv[0]," non-fatal command error #",rc,": ",
|
||||
msg,"\nfor SQL:\n",sql);
|
||||
}
|
||||
@@ -948,8 +953,7 @@ class TableResultCommand extends Command {
|
||||
if( !body.endsWith("\n--end") ){
|
||||
ts.toss(argv[0], " must be terminated with --end.");
|
||||
}else{
|
||||
int n = body.length();
|
||||
body = body.substring(0, n-6);
|
||||
body = body.substring(0, body.length()-6);
|
||||
}
|
||||
final String[] globs = body.split("\\s*\\n\\s*");
|
||||
if( globs.length < 1 ){
|
||||
@@ -1124,8 +1128,9 @@ class TestScript {
|
||||
}
|
||||
|
||||
public String getOutputPrefix(){
|
||||
String rc = "["+(moduleName==null ? filename : moduleName)+"]";
|
||||
String rc = "["+(moduleName==null ? "<unnamed>" : moduleName)+"]";
|
||||
if( null!=testCaseName ) rc += "["+testCaseName+"]";
|
||||
if( null!=filename ) rc += "["+filename+"]";
|
||||
return rc + " line "+ cur.lineNo;
|
||||
}
|
||||
|
||||
@@ -1238,14 +1243,15 @@ class TestScript {
|
||||
final int oldPB = cur.putbackPos;
|
||||
final int oldPBL = cur.putbackLineNo;
|
||||
final int oldLine = cur.lineNo;
|
||||
final String rc = getLine();
|
||||
cur.peekedPos = cur.pos;
|
||||
cur.peekedLineNo = cur.lineNo;
|
||||
cur.pos = oldPos;
|
||||
cur.lineNo = oldLine;
|
||||
cur.putbackPos = oldPB;
|
||||
cur.putbackLineNo = oldPBL;
|
||||
return rc;
|
||||
try{ return getLine(); }
|
||||
finally{
|
||||
cur.peekedPos = cur.pos;
|
||||
cur.peekedLineNo = cur.lineNo;
|
||||
cur.pos = oldPos;
|
||||
cur.lineNo = oldLine;
|
||||
cur.putbackPos = oldPB;
|
||||
cur.putbackLineNo = oldPBL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1268,6 +1274,7 @@ class TestScript {
|
||||
}
|
||||
|
||||
private boolean checkRequiredProperties(SQLTester t, String[] props) throws SQLTesterException{
|
||||
if( true ) return false;
|
||||
int nOk = 0;
|
||||
for(String rp : props){
|
||||
verbose1("REQUIRED_PROPERTIES: ",rp);
|
||||
@@ -1288,6 +1295,12 @@ class TestScript {
|
||||
t.appendDbInitSql("pragma temp_store=0;");
|
||||
++nOk;
|
||||
break;
|
||||
case "AUTOVACUUM":
|
||||
t.appendDbInitSql("pragma auto_vacuum=full;");
|
||||
++nOk;
|
||||
case "INCRVACUUM":
|
||||
t.appendDbInitSql("pragma auto_vacuum=incremental;");
|
||||
++nOk;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -1326,9 +1339,9 @@ class TestScript {
|
||||
m = patternRequiredProperties.matcher(line);
|
||||
if( m.find() ){
|
||||
final String rp = m.group(1);
|
||||
//if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){
|
||||
throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp);
|
||||
//}
|
||||
if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){
|
||||
throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp);
|
||||
}
|
||||
}
|
||||
m = patternMixedModuleName.matcher(line);
|
||||
if( m.find() ){
|
||||
@@ -1372,11 +1385,10 @@ class TestScript {
|
||||
String line;
|
||||
while( (null != (line = peekLine())) ){
|
||||
checkForDirective(tester, line);
|
||||
if( !isCommandLine(line, true) ){
|
||||
if( isCommandLine(line, true) ) break;
|
||||
else {
|
||||
sb.append(line).append("\n");
|
||||
consumePeeked();
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
line = sb.toString();
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
** 2023-08-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 is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
/**
|
||||
This marker interface exists soley for use as a documentation and
|
||||
class-grouping tool. It should be applied to interfaces or
|
||||
classes which have a call() method implementing some specific
|
||||
callback interface on behalf of the C library.
|
||||
|
||||
<p>Callbacks of this style follow a common naming convention:
|
||||
|
||||
<p>1) They use the UpperCamelCase form of the C function they're
|
||||
proxying for, minus the sqlite3_ prefix, plus a Callback
|
||||
suffix. e.g. sqlite3_busy_handler()'s callback is named
|
||||
BusyHandlerCallback. Exceptions are made where that would
|
||||
potentially be ambiguous, e.g. ConfigSqllogCallback instead of
|
||||
config_callback because the sqlite3_config() interface may need to
|
||||
support more callback types in the future.
|
||||
|
||||
<p>2) They all have a call() method but its signature is
|
||||
callback-specific.
|
||||
*/
|
||||
public interface SQLite3CallbackProxy {}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,9 +15,9 @@ package org.sqlite.jni;
|
||||
|
||||
|
||||
/**
|
||||
SQLFunction subclass for creating scalar functions.
|
||||
A SQLFunction implementation for scalar functions.
|
||||
*/
|
||||
public abstract class ScalarFunction extends SQLFunction {
|
||||
public abstract class ScalarFunction implements SQLFunction {
|
||||
/**
|
||||
As for the xFunc() argument of the C API's
|
||||
sqlite3_create_function(). If this function throws, it is
|
||||
@@ -27,7 +27,7 @@ public abstract class ScalarFunction extends SQLFunction {
|
||||
|
||||
/**
|
||||
Optionally override to be notified when the UDF is finalized by
|
||||
SQLite.
|
||||
SQLite. This implementation does nothing.
|
||||
*/
|
||||
public void xDestroy() {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
** 2023-07-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.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
A wrapper object for use with sqlite3_table_column_metadata().
|
||||
They are populated only via that interface.
|
||||
*/
|
||||
public final class TableColumnMetadata {
|
||||
OutputPointer.Bool pNotNull = new OutputPointer.Bool();
|
||||
OutputPointer.Bool pPrimaryKey = new OutputPointer.Bool();
|
||||
OutputPointer.Bool pAutoinc = new OutputPointer.Bool();
|
||||
OutputPointer.String pzCollSeq = new OutputPointer.String();
|
||||
OutputPointer.String pzDataType = new OutputPointer.String();
|
||||
|
||||
public TableColumnMetadata(){
|
||||
}
|
||||
|
||||
public String getDataType(){ return pzDataType.value; }
|
||||
public String getCollation(){ return pzCollSeq.value; }
|
||||
public boolean isNotNull(){ return pNotNull.value; }
|
||||
public boolean isPrimaryKey(){ return pPrimaryKey.value; }
|
||||
public boolean isAutoincrement(){ return pAutoinc.value; }
|
||||
}
|
||||
@@ -30,6 +30,24 @@ import java.util.concurrent.Future;
|
||||
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
|
||||
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
|
||||
@interface ManualTest{}
|
||||
/**
|
||||
Annotation for Tester1 tests which mark those which must be skipped
|
||||
in multi-threaded mode.
|
||||
*/
|
||||
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
|
||||
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
|
||||
@interface SingleThreadOnly{}
|
||||
|
||||
/**
|
||||
A helper class which simply holds a single value. Its current use
|
||||
is for communicating values out of anonymous classes, as doing so
|
||||
requires a "final" reference.
|
||||
*/
|
||||
class ValueHolder<T> {
|
||||
public T value;
|
||||
public ValueHolder(){}
|
||||
public ValueHolder(T v){value = v;}
|
||||
}
|
||||
|
||||
public class Tester1 implements Runnable {
|
||||
//! True when running in multi-threaded mode.
|
||||
@@ -61,69 +79,74 @@ public class Tester1 implements Runnable {
|
||||
|
||||
static final Metrics metrics = new Metrics();
|
||||
|
||||
public synchronized static void outln(){
|
||||
public static synchronized void outln(){
|
||||
if( !quietMode ){
|
||||
System.out.println("");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized static void outln(Object val){
|
||||
public static synchronized void outPrefix(){
|
||||
if( !quietMode ){
|
||||
System.out.print(Thread.currentThread().getName()+": ");
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void outln(Object val){
|
||||
if( !quietMode ){
|
||||
outPrefix();
|
||||
System.out.println(val);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized static void out(Object val){
|
||||
public static synchronized void out(Object val){
|
||||
if( !quietMode ){
|
||||
System.out.print(val);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized static void out(Object... vals){
|
||||
public static synchronized void out(Object... vals){
|
||||
if( !quietMode ){
|
||||
System.out.print(Thread.currentThread().getName()+": ");
|
||||
outPrefix();
|
||||
for(Object v : vals) out(v);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized static void outln(Object... vals){
|
||||
public static synchronized void outln(Object... vals){
|
||||
if( !quietMode ){
|
||||
out(vals); out("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static volatile int affirmCount = 0;
|
||||
public synchronized static void affirm(Boolean v, String comment){
|
||||
public static synchronized int affirm(Boolean v, String comment){
|
||||
++affirmCount;
|
||||
if( false ) assert( v /* prefer assert over exception if it's enabled because
|
||||
the JNI layer sometimes has to suppress exceptions,
|
||||
so they might be squelched on their way back to the
|
||||
top. */);
|
||||
if( !v ) throw new RuntimeException(comment);
|
||||
return affirmCount;
|
||||
}
|
||||
|
||||
public static void affirm(Boolean v){
|
||||
affirm(v, "Affirmation failed.");
|
||||
}
|
||||
|
||||
@ManualTest /* because testing this for threading is pointless */
|
||||
@SingleThreadOnly /* because it's thread-agnostic */
|
||||
private void test1(){
|
||||
affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
|
||||
affirm(SQLITE_MAX_LENGTH > 0);
|
||||
affirm(SQLITE_MAX_TRIGGER_DEPTH>0);
|
||||
}
|
||||
|
||||
static sqlite3 createNewDb(){
|
||||
public static sqlite3 createNewDb(){
|
||||
final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
|
||||
int rc = sqlite3_open(":memory:", out);
|
||||
++metrics.dbOpen;
|
||||
sqlite3 db = out.take();
|
||||
if( 0!=rc ){
|
||||
final String msg =
|
||||
null==db ? sqlite3_errstr(rc) : sqlite3_errmsg(db);
|
||||
null==db ? sqlite3_errstr(rc) : sqlite3_errmsg16(db);
|
||||
sqlite3_close(db);
|
||||
throw new RuntimeException("Opening db failed: "+msg);
|
||||
}
|
||||
@@ -134,11 +157,11 @@ public class Tester1 implements Runnable {
|
||||
return db;
|
||||
}
|
||||
|
||||
static void execSql(sqlite3 db, String[] sql){
|
||||
public static void execSql(sqlite3 db, String[] sql){
|
||||
execSql(db, String.join("", sql));
|
||||
}
|
||||
|
||||
static int execSql(sqlite3 db, boolean throwOnError, String sql){
|
||||
public static int execSql(sqlite3 db, boolean throwOnError, String sql){
|
||||
OutputPointer.Int32 oTail = new OutputPointer.Int32();
|
||||
final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
|
||||
int pos = 0, n = 1;
|
||||
@@ -174,18 +197,18 @@ public class Tester1 implements Runnable {
|
||||
if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0;
|
||||
if( 0!=rc && throwOnError){
|
||||
throw new RuntimeException("db op failed with rc="
|
||||
+rc+": "+sqlite3_errmsg(db));
|
||||
+rc+": "+sqlite3_errmsg16(db));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void execSql(sqlite3 db, String sql){
|
||||
public static void execSql(sqlite3 db, String sql){
|
||||
execSql(db, true, sql);
|
||||
}
|
||||
|
||||
static sqlite3_stmt prepare(sqlite3 db, boolean throwOnError, String sql){
|
||||
public static sqlite3_stmt prepare(sqlite3 db, boolean throwOnError, String sql){
|
||||
final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
|
||||
int rc = sqlite3_prepare(db, sql, outStmt);
|
||||
int rc = sqlite3_prepare_v2(db, sql, outStmt);
|
||||
if( throwOnError ){
|
||||
affirm( 0 == rc );
|
||||
}
|
||||
@@ -196,9 +219,11 @@ public class Tester1 implements Runnable {
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
static sqlite3_stmt prepare(sqlite3 db, String sql){
|
||||
|
||||
public static sqlite3_stmt prepare(sqlite3 db, String sql){
|
||||
return prepare(db, true, sql);
|
||||
}
|
||||
|
||||
private void showCompileOption(){
|
||||
int i = 0;
|
||||
String optName;
|
||||
@@ -207,7 +232,15 @@ public class Tester1 implements Runnable {
|
||||
outln("\t"+optName+"\t (used="+
|
||||
sqlite3_compileoption_used(optName)+")");
|
||||
}
|
||||
}
|
||||
|
||||
private void testCompileOption(){
|
||||
int i = 0;
|
||||
String optName;
|
||||
for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){
|
||||
}
|
||||
affirm( i > 10 );
|
||||
affirm( null==sqlite3_compileoption_get(-1) );
|
||||
}
|
||||
|
||||
private void testOpenDb1(){
|
||||
@@ -253,10 +286,11 @@ public class Tester1 implements Runnable {
|
||||
affirm(0 == rc);
|
||||
sqlite3_stmt stmt = outStmt.take();
|
||||
affirm(0 != stmt.getNativePointer());
|
||||
affirm( !sqlite3_stmt_readonly(stmt) );
|
||||
affirm( db == sqlite3_db_handle(stmt) );
|
||||
rc = sqlite3_step(stmt);
|
||||
if( SQLITE_DONE != rc ){
|
||||
outln("step failed ??? ",rc, " ",sqlite3_errmsg(db));
|
||||
outln("step failed ??? ",rc, " ",sqlite3_errmsg16(db));
|
||||
}
|
||||
affirm(SQLITE_DONE == rc);
|
||||
sqlite3_finalize(stmt);
|
||||
@@ -312,7 +346,7 @@ public class Tester1 implements Runnable {
|
||||
stmt = sqlite3_prepare(db, "intentional error");
|
||||
affirm( null==stmt );
|
||||
affirm( 0!=sqlite3_errcode(db) );
|
||||
affirm( 0==sqlite3_errmsg(db).indexOf("near \"intentional\"") );
|
||||
affirm( 0==sqlite3_errmsg16(db).indexOf("near \"intentional\"") );
|
||||
sqlite3_finalize(stmt);
|
||||
stmt = sqlite3_prepare(db, "/* empty input*/\n-- comments only");
|
||||
affirm( null==stmt );
|
||||
@@ -328,6 +362,7 @@ public class Tester1 implements Runnable {
|
||||
affirm(1 == sqlite3_bind_parameter_count(stmt));
|
||||
final int paramNdx = sqlite3_bind_parameter_index(stmt, ":a");
|
||||
affirm(1 == paramNdx);
|
||||
affirm( ":a".equals(sqlite3_bind_parameter_name(stmt, paramNdx)));
|
||||
int total1 = 0;
|
||||
long rowid = -1;
|
||||
int changes = sqlite3_changes(db);
|
||||
@@ -353,6 +388,7 @@ public class Tester1 implements Runnable {
|
||||
affirm(sqlite3_changes64(db) > changes64);
|
||||
affirm(sqlite3_total_changes64(db) > changesT64);
|
||||
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
|
||||
affirm( sqlite3_stmt_readonly(stmt) );
|
||||
int total2 = 0;
|
||||
while( SQLITE_ROW == sqlite3_step(stmt) ){
|
||||
total2 += sqlite3_column_int(stmt, 0);
|
||||
@@ -368,57 +404,59 @@ public class Tester1 implements Runnable {
|
||||
}
|
||||
|
||||
private void testBindFetchInt64(){
|
||||
sqlite3 db = createNewDb();
|
||||
execSql(db, "CREATE TABLE t(a)");
|
||||
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
|
||||
long total1 = 0;
|
||||
for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){
|
||||
total1 += i;
|
||||
sqlite3_bind_int64(stmt, 1, i);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_reset(stmt);
|
||||
try (sqlite3 db = createNewDb()){
|
||||
execSql(db, "CREATE TABLE t(a)");
|
||||
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
|
||||
long total1 = 0;
|
||||
for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){
|
||||
total1 += i;
|
||||
sqlite3_bind_int64(stmt, 1, i);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
|
||||
long total2 = 0;
|
||||
while( SQLITE_ROW == sqlite3_step(stmt) ){
|
||||
total2 += sqlite3_column_int64(stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
affirm(total1 == total2);
|
||||
//sqlite3_close_v2(db);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
|
||||
long total2 = 0;
|
||||
while( SQLITE_ROW == sqlite3_step(stmt) ){
|
||||
total2 += sqlite3_column_int64(stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
affirm(total1 == total2);
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
private void testBindFetchDouble(){
|
||||
sqlite3 db = createNewDb();
|
||||
execSql(db, "CREATE TABLE t(a)");
|
||||
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
|
||||
double total1 = 0;
|
||||
for(double i = 1.5; i < 5.0; i = i + 1.0 ){
|
||||
total1 += i;
|
||||
sqlite3_bind_double(stmt, 1, i);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_reset(stmt);
|
||||
try (sqlite3 db = createNewDb()){
|
||||
execSql(db, "CREATE TABLE t(a)");
|
||||
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
|
||||
double total1 = 0;
|
||||
for(double i = 1.5; i < 5.0; i = i + 1.0 ){
|
||||
total1 += i;
|
||||
sqlite3_bind_double(stmt, 1, i);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
|
||||
double total2 = 0;
|
||||
int counter = 0;
|
||||
while( SQLITE_ROW == sqlite3_step(stmt) ){
|
||||
++counter;
|
||||
total2 += sqlite3_column_double(stmt, 0);
|
||||
}
|
||||
affirm(4 == counter);
|
||||
sqlite3_finalize(stmt);
|
||||
affirm(total2<=total1+0.01 && total2>=total1-0.01);
|
||||
//sqlite3_close_v2(db);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
|
||||
double total2 = 0;
|
||||
int counter = 0;
|
||||
while( SQLITE_ROW == sqlite3_step(stmt) ){
|
||||
++counter;
|
||||
total2 += sqlite3_column_double(stmt, 0);
|
||||
}
|
||||
affirm(4 == counter);
|
||||
sqlite3_finalize(stmt);
|
||||
affirm(total2<=total1+0.01 && total2>=total1-0.01);
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
private void testBindFetchText(){
|
||||
sqlite3 db = createNewDb();
|
||||
execSql(db, "CREATE TABLE t(a)");
|
||||
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
|
||||
String[] list1 = { "hell🤩", "w😃rld", "!" };
|
||||
String[] list1 = { "hell🤩", "w😃rld", "!🤩" };
|
||||
int rc;
|
||||
int n = 0;
|
||||
for( String e : list1 ){
|
||||
@@ -435,15 +473,45 @@ public class Tester1 implements Runnable {
|
||||
StringBuilder sbuf = new StringBuilder();
|
||||
n = 0;
|
||||
while( SQLITE_ROW == sqlite3_step(stmt) ){
|
||||
String txt = sqlite3_column_text16(stmt, 0);
|
||||
//outln("txt = "+txt);
|
||||
final sqlite3_value sv = sqlite3_value_dup(sqlite3_column_value(stmt,0));
|
||||
final String txt = sqlite3_column_text16(stmt, 0);
|
||||
sbuf.append( txt );
|
||||
affirm( txt.equals(new String(
|
||||
sqlite3_column_text(stmt, 0),
|
||||
StandardCharsets.UTF_8
|
||||
)) );
|
||||
affirm( txt.length() < sqlite3_value_bytes(sv) );
|
||||
affirm( txt.equals(new String(
|
||||
sqlite3_value_text(sv),
|
||||
StandardCharsets.UTF_8)) );
|
||||
affirm( txt.length() == sqlite3_value_bytes16(sv)/2 );
|
||||
affirm( txt.equals(sqlite3_value_text16(sv)) );
|
||||
sqlite3_value_free(sv);
|
||||
++n;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
affirm(3 == n);
|
||||
affirm("w😃rldhell🤩!".equals(sbuf.toString()));
|
||||
sqlite3_close_v2(db);
|
||||
affirm("w😃rldhell🤩!🤩".equals(sbuf.toString()));
|
||||
|
||||
try( sqlite3_stmt stmt2 = prepare(db, "SELECT ?, ?") ){
|
||||
rc = sqlite3_bind_text(stmt2, 1, "");
|
||||
affirm( 0==rc );
|
||||
rc = sqlite3_bind_text(stmt2, 2, (String)null);
|
||||
affirm( 0==rc );
|
||||
rc = sqlite3_step(stmt2);
|
||||
affirm( SQLITE_ROW==rc );
|
||||
byte[] colBa = sqlite3_column_text(stmt2, 0);
|
||||
affirm( 0==colBa.length );
|
||||
colBa = sqlite3_column_text(stmt2, 1);
|
||||
affirm( null==colBa );
|
||||
//sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
if(true){
|
||||
sqlite3_close_v2(db);
|
||||
}else{
|
||||
// Let the Object.finalize() override deal with it.
|
||||
}
|
||||
}
|
||||
|
||||
private void testBindFetchBlob(){
|
||||
@@ -562,7 +630,7 @@ public class Tester1 implements Runnable {
|
||||
affirm( 1 == xDestroyCalled.value );
|
||||
}
|
||||
|
||||
@ManualTest /* because threading is meaningless here */
|
||||
@SingleThreadOnly /* because it's thread-agnostic */
|
||||
private void testToUtf8(){
|
||||
/**
|
||||
https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
|
||||
@@ -668,7 +736,7 @@ public class Tester1 implements Runnable {
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
affirm( 0 != rc );
|
||||
affirm( sqlite3_errmsg(db).indexOf("an xFinal") > 0 );
|
||||
affirm( sqlite3_errmsg16(db).indexOf("an xFinal") > 0 );
|
||||
|
||||
SQLFunction funcSc = new ScalarFunction(){
|
||||
@Override public void xFunc(sqlite3_context cx, sqlite3_value[] args){
|
||||
@@ -682,25 +750,31 @@ public class Tester1 implements Runnable {
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
affirm( 0 != rc );
|
||||
affirm( sqlite3_errmsg(db).indexOf("an xFunc") > 0 );
|
||||
affirm( sqlite3_errmsg16(db).indexOf("an xFunc") > 0 );
|
||||
rc = sqlite3_create_function(db, "mysca", 1, -1, funcSc);
|
||||
affirm( SQLITE_FORMAT==rc, "invalid encoding value." );
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
@SingleThreadOnly
|
||||
private void testUdfJavaObject(){
|
||||
affirm( !mtMode );
|
||||
final sqlite3 db = createNewDb();
|
||||
final ValueHolder<sqlite3> testResult = new ValueHolder<>(db);
|
||||
final ValueHolder<Integer> boundObj = new ValueHolder<>(42);
|
||||
final SQLFunction func = new ScalarFunction(){
|
||||
public void xFunc(sqlite3_context cx, sqlite3_value args[]){
|
||||
sqlite3_result_java_object(cx, testResult.value);
|
||||
affirm( sqlite3_value_java_object(args[0]) == boundObj );
|
||||
}
|
||||
};
|
||||
int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
|
||||
affirm(0 == rc);
|
||||
final sqlite3_stmt stmt = prepare(db, "select myfunc()");
|
||||
sqlite3_stmt stmt = prepare(db, "select myfunc(?)");
|
||||
affirm( 0 != stmt.getNativePointer() );
|
||||
affirm( testResult.value == db );
|
||||
rc = sqlite3_bind_java_object(stmt, 1, boundObj);
|
||||
affirm( 0==rc );
|
||||
int n = 0;
|
||||
if( SQLITE_ROW == sqlite3_step(stmt) ){
|
||||
final sqlite3_value v = sqlite3_column_value(stmt, 0);
|
||||
@@ -714,6 +788,7 @@ public class Tester1 implements Runnable {
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
affirm( 1 == n );
|
||||
affirm( 0==sqlite3_db_release_memory(db) );
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
@@ -745,7 +820,7 @@ public class Tester1 implements Runnable {
|
||||
int rc = sqlite3_create_function(db, "myfunc", 1, SQLITE_UTF8, func);
|
||||
affirm(0 == rc);
|
||||
sqlite3_stmt stmt = prepare(db, "select myfunc(a), myfunc(a+10) from t");
|
||||
affirm( null != stmt );
|
||||
affirm( 0==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
|
||||
int n = 0;
|
||||
if( SQLITE_ROW == sqlite3_step(stmt) ){
|
||||
int v = sqlite3_column_int(stmt, 0);
|
||||
@@ -757,6 +832,7 @@ public class Tester1 implements Runnable {
|
||||
affirm( 1==n );
|
||||
affirm(!xFinalNull.value);
|
||||
sqlite3_reset(stmt);
|
||||
affirm( 1==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
|
||||
// Ensure that the accumulator is reset on subsequent calls...
|
||||
n = 0;
|
||||
if( SQLITE_ROW == sqlite3_step(stmt) ){
|
||||
@@ -923,8 +999,8 @@ public class Tester1 implements Runnable {
|
||||
affirm( 7 == counter.value );
|
||||
}
|
||||
|
||||
@ManualTest /* because threads inherently break this test */
|
||||
private void testBusy(){
|
||||
@SingleThreadOnly /* because threads inherently break this test */
|
||||
private static void testBusy(){
|
||||
final String dbName = "_busy-handler.db";
|
||||
final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3();
|
||||
final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
|
||||
@@ -1088,7 +1164,7 @@ public class Tester1 implements Runnable {
|
||||
This test is functionally identical to testUpdateHook(), only with a
|
||||
different callback type.
|
||||
*/
|
||||
private synchronized void testPreUpdateHook(){
|
||||
private void testPreUpdateHook(){
|
||||
if( !sqlite3_compileoption_used("ENABLE_PREUPDATE_HOOK") ){
|
||||
//outln("Skipping testPreUpdateHook(): no pre-update hook support.");
|
||||
return;
|
||||
@@ -1198,8 +1274,8 @@ public class Tester1 implements Runnable {
|
||||
it throws.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@ManualTest /* because the Fts5 parts are not yet known to be
|
||||
thread-safe */
|
||||
@SingleThreadOnly /* because the Fts5 parts are not yet known to be
|
||||
thread-safe */
|
||||
private void testFts5() throws Exception {
|
||||
if( !sqlite3_compileoption_used("ENABLE_FTS5") ){
|
||||
//outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests.");
|
||||
@@ -1210,7 +1286,10 @@ public class Tester1 implements Runnable {
|
||||
Class t = Class.forName("org.sqlite.jni.TesterFts5");
|
||||
java.lang.reflect.Constructor ctor = t.getConstructor();
|
||||
ctor.setAccessible(true);
|
||||
final long timeStart = System.currentTimeMillis();
|
||||
ctor.newInstance() /* will run all tests */;
|
||||
final long timeEnd = System.currentTimeMillis();
|
||||
outln("FTS5 Tests done in ",(timeEnd - timeStart),"ms");
|
||||
}catch(ClassNotFoundException e){
|
||||
outln("FTS5 classes not loaded.");
|
||||
err = e;
|
||||
@@ -1250,13 +1329,13 @@ public class Tester1 implements Runnable {
|
||||
sqlite3_close(db);
|
||||
}
|
||||
|
||||
@ManualTest/* because multiple threads legitimately make these
|
||||
results unpredictable */
|
||||
@SingleThreadOnly /* because multiple threads legitimately make these
|
||||
results unpredictable */
|
||||
private synchronized void testAutoExtension(){
|
||||
final ValueHolder<Integer> val = new ValueHolder<>(0);
|
||||
final ValueHolder<String> toss = new ValueHolder<>(null);
|
||||
final AutoExtensionCallback ax = new AutoExtensionCallback(){
|
||||
@Override public synchronized int call(sqlite3 db){
|
||||
@Override public int call(sqlite3 db){
|
||||
++val.value;
|
||||
if( null!=toss.value ){
|
||||
throw new RuntimeException(toss.value);
|
||||
@@ -1283,7 +1362,7 @@ public class Tester1 implements Runnable {
|
||||
|
||||
sqlite3 db = createNewDb();
|
||||
affirm( 4==val.value );
|
||||
execSql(db, "ATTACH ':memory' as foo");
|
||||
execSql(db, "ATTACH ':memory:' as foo");
|
||||
affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." );
|
||||
sqlite3_close(db);
|
||||
db = null;
|
||||
@@ -1342,7 +1421,219 @@ public class Tester1 implements Runnable {
|
||||
affirm( 8 == val.value );
|
||||
}
|
||||
|
||||
@ManualTest /* because we only want to run this test manually */
|
||||
|
||||
private void testColumnMetadata(){
|
||||
final sqlite3 db = createNewDb();
|
||||
execSql(db, new String[] {
|
||||
"CREATE TABLE t(a duck primary key not null collate noCase); ",
|
||||
"INSERT INTO t(a) VALUES(1),(2),(3);"
|
||||
});
|
||||
OutputPointer.Bool bNotNull = new OutputPointer.Bool();
|
||||
OutputPointer.Bool bPrimaryKey = new OutputPointer.Bool();
|
||||
OutputPointer.Bool bAutoinc = new OutputPointer.Bool();
|
||||
OutputPointer.String zCollSeq = new OutputPointer.String();
|
||||
OutputPointer.String zDataType = new OutputPointer.String();
|
||||
int rc = sqlite3_table_column_metadata(
|
||||
db, "main", "t", "a", zDataType, zCollSeq,
|
||||
bNotNull, bPrimaryKey, bAutoinc);
|
||||
affirm( 0==rc );
|
||||
affirm( bPrimaryKey.value );
|
||||
affirm( !bAutoinc.value );
|
||||
affirm( bNotNull.value );
|
||||
affirm( "noCase".equals(zCollSeq.value) );
|
||||
affirm( "duck".equals(zDataType.value) );
|
||||
|
||||
final TableColumnMetadata m =
|
||||
sqlite3_table_column_metadata(db, "main", "t", "a");
|
||||
affirm( null != m );
|
||||
affirm( bPrimaryKey.value == m.isPrimaryKey() );
|
||||
affirm( bAutoinc.value == m.isAutoincrement() );
|
||||
affirm( bNotNull.value == m.isNotNull() );
|
||||
affirm( zCollSeq.value.equals(m.getCollation()) );
|
||||
affirm( zDataType.value.equals(m.getDataType()) );
|
||||
|
||||
affirm( null == sqlite3_table_column_metadata(db, "nope", "t", "a") );
|
||||
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
private void testTxnState(){
|
||||
final sqlite3 db = createNewDb();
|
||||
affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
|
||||
affirm( sqlite3_get_autocommit(db) );
|
||||
execSql(db, "BEGIN;");
|
||||
affirm( !sqlite3_get_autocommit(db) );
|
||||
affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
|
||||
execSql(db, "SELECT * FROM sqlite_schema;");
|
||||
affirm( SQLITE_TXN_READ == sqlite3_txn_state(db, "main") );
|
||||
execSql(db, "CREATE TABLE t(a);");
|
||||
affirm( SQLITE_TXN_WRITE == sqlite3_txn_state(db, null) );
|
||||
execSql(db, "ROLLBACK;");
|
||||
affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
|
||||
private void testExplain(){
|
||||
final sqlite3 db = createNewDb();
|
||||
sqlite3_stmt stmt = prepare(db,"SELECT 1");
|
||||
|
||||
affirm( 0 == sqlite3_stmt_isexplain(stmt) );
|
||||
int rc = sqlite3_stmt_explain(stmt, 1);
|
||||
affirm( 1 == sqlite3_stmt_isexplain(stmt) );
|
||||
rc = sqlite3_stmt_explain(stmt, 2);
|
||||
affirm( 2 == sqlite3_stmt_isexplain(stmt) );
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
private void testLimit(){
|
||||
final sqlite3 db = createNewDb();
|
||||
int v;
|
||||
|
||||
v = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1);
|
||||
affirm( v > 0 );
|
||||
affirm( v == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, v-1) );
|
||||
affirm( v-1 == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1) );
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
private void testComplete(){
|
||||
affirm( 0==sqlite3_complete("select 1") );
|
||||
affirm( 0!=sqlite3_complete("select 1;") );
|
||||
affirm( 0!=sqlite3_complete("nope 'nope' 'nope' 1;"), "Yup" );
|
||||
}
|
||||
|
||||
private void testKeyword(){
|
||||
final int n = sqlite3_keyword_count();
|
||||
affirm( n>0 );
|
||||
affirm( !sqlite3_keyword_check("_nope_") );
|
||||
affirm( sqlite3_keyword_check("seLect") );
|
||||
affirm( null!=sqlite3_keyword_name(0) );
|
||||
affirm( null!=sqlite3_keyword_name(n-1) );
|
||||
affirm( null==sqlite3_keyword_name(n) );
|
||||
}
|
||||
|
||||
private void testBackup(){
|
||||
final sqlite3 dbDest = createNewDb();
|
||||
|
||||
try (sqlite3 dbSrc = createNewDb()) {
|
||||
execSql(dbSrc, new String[]{
|
||||
"pragma page_size=512; VACUUM;",
|
||||
"create table t(a);",
|
||||
"insert into t(a) values(1),(2),(3);"
|
||||
});
|
||||
affirm( null==sqlite3_backup_init(dbSrc,"main",dbSrc,"main") );
|
||||
try (sqlite3_backup b = sqlite3_backup_init(dbDest,"main",dbSrc,"main")) {
|
||||
affirm( null!=b );
|
||||
affirm( b.getNativePointer()!=0 );
|
||||
int rc;
|
||||
while( SQLITE_DONE!=(rc = sqlite3_backup_step(b, 1)) ){
|
||||
affirm( 0==rc );
|
||||
}
|
||||
affirm( sqlite3_backup_pagecount(b) > 0 );
|
||||
rc = sqlite3_backup_finish(b);
|
||||
affirm( 0==rc );
|
||||
affirm( b.getNativePointer()==0 );
|
||||
}
|
||||
}
|
||||
|
||||
try (sqlite3_stmt stmt = prepare(dbDest,"SELECT sum(a) from t")) {
|
||||
sqlite3_step(stmt);
|
||||
affirm( sqlite3_column_int(stmt,0) == 6 );
|
||||
}
|
||||
sqlite3_close_v2(dbDest);
|
||||
}
|
||||
|
||||
private void testRandomness(){
|
||||
byte[] foo = new byte[20];
|
||||
int i = 0;
|
||||
for( byte b : foo ){
|
||||
i += b;
|
||||
}
|
||||
affirm( i==0 );
|
||||
sqlite3_randomness(foo);
|
||||
for( byte b : foo ){
|
||||
if(b!=0) ++i;
|
||||
}
|
||||
affirm( i!=0, "There's a very slight chance that 0 is actually correct." );
|
||||
}
|
||||
|
||||
private void testBlobOpen(){
|
||||
final sqlite3 db = createNewDb();
|
||||
|
||||
execSql(db, "CREATE TABLE T(a BLOB);"
|
||||
+"INSERT INTO t(rowid,a) VALUES(1, 'def'),(2, 'XYZ');"
|
||||
);
|
||||
final OutputPointer.sqlite3_blob pOut = new OutputPointer.sqlite3_blob();
|
||||
int rc = sqlite3_blob_open(db, "main", "t", "a",
|
||||
sqlite3_last_insert_rowid(db), 1, pOut);
|
||||
affirm( 0==rc );
|
||||
sqlite3_blob b = pOut.take();
|
||||
affirm( null!=b );
|
||||
affirm( 0!=b.getNativePointer() );
|
||||
affirm( 3==sqlite3_blob_bytes(b) );
|
||||
rc = sqlite3_blob_write( b, new byte[] {100, 101, 102 /*"DEF"*/}, 0);
|
||||
affirm( 0==rc );
|
||||
rc = sqlite3_blob_close(b);
|
||||
affirm( 0==rc );
|
||||
rc = sqlite3_blob_close(b);
|
||||
affirm( 0!=rc );
|
||||
affirm( 0==b.getNativePointer() );
|
||||
sqlite3_stmt stmt = prepare(db,"SELECT length(a), a FROM t ORDER BY a");
|
||||
affirm( SQLITE_ROW == sqlite3_step(stmt) );
|
||||
affirm( 3 == sqlite3_column_int(stmt,0) );
|
||||
affirm( "def".equals(sqlite3_column_text16(stmt,1)) );
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
b = sqlite3_blob_open(db, "main", "t", "a",
|
||||
sqlite3_last_insert_rowid(db), 1);
|
||||
affirm( null!=b );
|
||||
rc = sqlite3_blob_reopen(b, 2);
|
||||
affirm( 0==rc );
|
||||
final byte[] tgt = new byte[3];
|
||||
rc = sqlite3_blob_read(b, tgt, 0);
|
||||
affirm( 0==rc );
|
||||
affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" );
|
||||
rc = sqlite3_blob_close(b);
|
||||
affirm( 0==rc );
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
private void testPrepareMulti(){
|
||||
final sqlite3 db = createNewDb();
|
||||
final String[] sql = {
|
||||
"create table t(","a)",
|
||||
"; insert into t(a) values(1),(2),(3);",
|
||||
"select a from t;"
|
||||
};
|
||||
final List<sqlite3_stmt> liStmt = new ArrayList<sqlite3_stmt>();
|
||||
final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll();
|
||||
PrepareMultiCallback m = new PrepareMultiCallback() {
|
||||
@Override public int call(sqlite3_stmt st){
|
||||
liStmt.add(st);
|
||||
return proxy.call(st);
|
||||
}
|
||||
};
|
||||
int rc = sqlite3_prepare_multi(db, sql, m);
|
||||
affirm( 0==rc );
|
||||
affirm( liStmt.size() == 3 );
|
||||
for( sqlite3_stmt st : liStmt ){
|
||||
sqlite3_finalize(st);
|
||||
}
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
/* Copy/paste/rename this to add new tests. */
|
||||
private void _testTemplate(){
|
||||
final sqlite3 db = createNewDb();
|
||||
sqlite3_stmt stmt = prepare(db,"SELECT 1");
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
|
||||
@ManualTest /* we really only want to run this test manually */
|
||||
private void testSleep(){
|
||||
out("Sleeping briefly... ");
|
||||
sqlite3_sleep(600);
|
||||
@@ -1385,8 +1676,6 @@ public class Tester1 implements Runnable {
|
||||
outln();
|
||||
}
|
||||
}
|
||||
testToUtf8();
|
||||
test1();
|
||||
for(java.lang.reflect.Method m : mlist){
|
||||
nap();
|
||||
try{
|
||||
@@ -1396,13 +1685,6 @@ public class Tester1 implements Runnable {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if( !fromThread ){
|
||||
testBusy();
|
||||
if( !mtMode ){
|
||||
testAutoExtension() /* threads rightfully muck up these results */;
|
||||
testFts5();
|
||||
}
|
||||
}
|
||||
synchronized( this.getClass() ){
|
||||
++nTestRuns;
|
||||
}
|
||||
@@ -1453,6 +1735,7 @@ public class Tester1 implements Runnable {
|
||||
Integer nRepeat = 1;
|
||||
boolean forceFail = false;
|
||||
boolean sqlLog = false;
|
||||
boolean configLog = false;
|
||||
boolean squelchTestOutput = false;
|
||||
for( int i = 0; i < args.length; ){
|
||||
String arg = args[i++];
|
||||
@@ -1473,6 +1756,8 @@ public class Tester1 implements Runnable {
|
||||
forceFail = true;
|
||||
}else if(arg.equals("sqllog")){
|
||||
sqlLog = true;
|
||||
}else if(arg.equals("configlog")){
|
||||
configLog = true;
|
||||
}else if(arg.equals("naps")){
|
||||
takeNaps = true;
|
||||
}else if(arg.equals("q") || arg.equals("quiet")){
|
||||
@@ -1483,26 +1768,9 @@ public class Tester1 implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Build list of tests to run from the methods named test*().
|
||||
testMethods = new ArrayList<>();
|
||||
for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
|
||||
final String name = m.getName();
|
||||
if( name.equals("testFail") ){
|
||||
if( forceFail ){
|
||||
testMethods.add(m);
|
||||
}
|
||||
}else if( !m.isAnnotationPresent( ManualTest.class ) ){
|
||||
if( name.startsWith("test") ){
|
||||
testMethods.add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( sqlLog ){
|
||||
if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){
|
||||
int rc = sqlite3_config( new ConfigSqllogCallback() {
|
||||
final ConfigSqllogCallback log = new ConfigSqllogCallback() {
|
||||
@Override public void call(sqlite3 db, String msg, int op){
|
||||
switch(op){
|
||||
case 0: outln("Opening db: ",db); break;
|
||||
@@ -1510,22 +1778,64 @@ public class Tester1 implements Runnable {
|
||||
case 2: outln("Closing db: ",db); break;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
int rc = sqlite3_config( log );
|
||||
affirm( 0==rc );
|
||||
rc = sqlite3_config( (ConfigSqllogCallback)null );
|
||||
affirm( 0==rc );
|
||||
rc = sqlite3_config( log );
|
||||
affirm( 0==rc );
|
||||
}else{
|
||||
outln("WARNING: -sqllog is not active because library was built ",
|
||||
"without SQLITE_ENABLE_SQLLOG.");
|
||||
}
|
||||
}
|
||||
if( configLog ){
|
||||
final ConfigLogCallback log = new ConfigLogCallback() {
|
||||
@Override public void call(int code, String msg){
|
||||
outln("ConfigLogCallback: ",ResultCode.getEntryForInt(code),": ", msg);
|
||||
};
|
||||
};
|
||||
int rc = sqlite3_config( log );
|
||||
affirm( 0==rc );
|
||||
rc = sqlite3_config( (ConfigLogCallback)null );
|
||||
affirm( 0==rc );
|
||||
rc = sqlite3_config( log );
|
||||
affirm( 0==rc );
|
||||
}
|
||||
|
||||
quietMode = squelchTestOutput;
|
||||
outln("If you just saw warning messages regarding CallStaticObjectMethod, ",
|
||||
"you are very likely seeing the side effects of a known openjdk8 ",
|
||||
"bug. It is unsightly but does not affect the library.");
|
||||
|
||||
{
|
||||
// Build list of tests to run from the methods named test*().
|
||||
testMethods = new ArrayList<>();
|
||||
int nSkipped = 0;
|
||||
for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
|
||||
final String name = m.getName();
|
||||
if( name.equals("testFail") ){
|
||||
if( forceFail ){
|
||||
testMethods.add(m);
|
||||
}
|
||||
}else if( !m.isAnnotationPresent( ManualTest.class ) ){
|
||||
if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){
|
||||
if( 0==nSkipped++ ){
|
||||
out("Skipping tests in multi-thread mode:");
|
||||
}
|
||||
out(" "+name+"()");
|
||||
}else if( name.startsWith("test") ){
|
||||
testMethods.add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
if( nSkipped>0 ) out("\n");
|
||||
}
|
||||
|
||||
final long timeStart = System.currentTimeMillis();
|
||||
int nLoop = 0;
|
||||
switch( SQLITE_THREADSAFE ){ /* Sanity checking */
|
||||
switch( sqlite3_threadsafe() ){ /* Sanity checking */
|
||||
case 0:
|
||||
affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
|
||||
"Could not switch to single-thread mode." );
|
||||
@@ -1551,19 +1861,21 @@ public class Tester1 implements Runnable {
|
||||
outln("libversion_number: ",
|
||||
sqlite3_libversion_number(),"\n",
|
||||
sqlite3_libversion(),"\n",SQLITE_SOURCE_ID,"\n",
|
||||
"SQLITE_THREADSAFE=",SQLITE_THREADSAFE);
|
||||
outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
|
||||
"SQLITE_THREADSAFE=",sqlite3_threadsafe());
|
||||
final boolean showLoopCount = (nRepeat>1 && nThread>1);
|
||||
if( showLoopCount ){
|
||||
outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
|
||||
}
|
||||
if( takeNaps ) outln("Napping between tests is enabled.");
|
||||
for( int n = 0; n < nRepeat; ++n ){
|
||||
++nLoop;
|
||||
if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop);
|
||||
if( nThread<=1 ){
|
||||
new Tester1(0).runTests(false);
|
||||
continue;
|
||||
}
|
||||
Tester1.mtMode = true;
|
||||
final ExecutorService ex = Executors.newFixedThreadPool( nThread );
|
||||
//final List<Future<?>> futures = new ArrayList<>();
|
||||
++nLoop;
|
||||
out((1==nLoop ? "" : " ")+nLoop);
|
||||
for( int i = 0; i < nThread; ++i ){
|
||||
ex.submit( new Tester1(i), i );
|
||||
}
|
||||
@@ -1586,7 +1898,7 @@ public class Tester1 implements Runnable {
|
||||
if( null!=err ) throw err;
|
||||
}
|
||||
}
|
||||
outln();
|
||||
if( showLoopCount ) outln();
|
||||
quietMode = false;
|
||||
|
||||
final long timeEnd = System.currentTimeMillis();
|
||||
@@ -1596,17 +1908,22 @@ public class Tester1 implements Runnable {
|
||||
if( doSomethingForDev ){
|
||||
sqlite3_jni_internal_details();
|
||||
}
|
||||
affirm( 0==sqlite3_release_memory(1) );
|
||||
sqlite3_shutdown();
|
||||
int nMethods = 0;
|
||||
int nNatives = 0;
|
||||
int nCanonical = 0;
|
||||
final java.lang.reflect.Method[] declaredMethods =
|
||||
SQLite3Jni.class.getDeclaredMethods();
|
||||
for(java.lang.reflect.Method m : declaredMethods){
|
||||
int mod = m.getModifiers();
|
||||
final int mod = m.getModifiers();
|
||||
if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){
|
||||
final String name = m.getName();
|
||||
if(name.startsWith("sqlite3_")){
|
||||
++nMethods;
|
||||
if( m.isAnnotationPresent( org.sqlite.jni.annotation.Canonical.class ) ){
|
||||
++nCanonical;
|
||||
}
|
||||
if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){
|
||||
++nNatives;
|
||||
}
|
||||
@@ -1614,8 +1931,11 @@ public class Tester1 implements Runnable {
|
||||
}
|
||||
}
|
||||
outln("\tSQLite3Jni.sqlite3_*() methods: "+
|
||||
nNatives+" native methods and "+
|
||||
(nMethods - nNatives)+" Java impls");
|
||||
nMethods+" total, with "+
|
||||
nNatives+" native, "+
|
||||
(nMethods - nNatives)+" Java, ",
|
||||
nCanonical," @Canonical"
|
||||
);
|
||||
outln("\tTotal test time = "
|
||||
+(timeEnd - timeStart)+"ms");
|
||||
}
|
||||
|
||||
@@ -14,11 +14,15 @@
|
||||
package org.sqlite.jni;
|
||||
import static org.sqlite.jni.SQLite3Jni.*;
|
||||
import static org.sqlite.jni.Tester1.*;
|
||||
import org.sqlite.jni.*;
|
||||
import org.sqlite.jni.fts5.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class TesterFts5 {
|
||||
|
||||
private static void test1(){
|
||||
Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance();
|
||||
final Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance();
|
||||
affirm( null != fea );
|
||||
affirm( fea.getNativePointer() != 0 );
|
||||
affirm( fea == Fts5ExtensionApi.getInstance() )/*singleton*/;
|
||||
@@ -35,55 +39,794 @@ public class TesterFts5 {
|
||||
});
|
||||
|
||||
final String pUserData = "This is pUserData";
|
||||
ValueHolder<Boolean> xDestroyCalled = new ValueHolder<>(false);
|
||||
ValueHolder<Integer> xFuncCount = new ValueHolder<>(0);
|
||||
final int outputs[] = {0, 0};
|
||||
final fts5_extension_function func = new fts5_extension_function(){
|
||||
public void xFunction(Fts5ExtensionApi ext, Fts5Context fCx,
|
||||
sqlite3_context pCx, sqlite3_value argv[]){
|
||||
int nCols = ext.xColumnCount(fCx);
|
||||
@Override public void call(Fts5ExtensionApi ext, Fts5Context fCx,
|
||||
sqlite3_context pCx, sqlite3_value argv[]){
|
||||
final int nCols = ext.xColumnCount(fCx);
|
||||
affirm( 2 == nCols );
|
||||
affirm( nCols == argv.length );
|
||||
affirm( ext.xUserData(fCx) == pUserData );
|
||||
if(true){
|
||||
OutputPointer.String op = new OutputPointer.String();
|
||||
for(int i = 0; i < nCols; ++i ){
|
||||
int rc = ext.xColumnText(fCx, i, op);
|
||||
affirm( 0 == rc );
|
||||
final String val = op.value;
|
||||
affirm( val.equals(sqlite3_value_text(argv[i])) );
|
||||
//outln("xFunction col "+i+": "+val);
|
||||
}
|
||||
final OutputPointer.String op = new OutputPointer.String();
|
||||
final OutputPointer.Int32 colsz = new OutputPointer.Int32();
|
||||
final OutputPointer.Int64 colTotalSz = new OutputPointer.Int64();
|
||||
for(int i = 0; i < nCols; ++i ){
|
||||
int rc = ext.xColumnText(fCx, i, op);
|
||||
affirm( 0 == rc );
|
||||
final String val = op.value;
|
||||
affirm( val.equals(sqlite3_value_text16(argv[i])) );
|
||||
rc = ext.xColumnSize(fCx, i, colsz);
|
||||
affirm( 0==rc );
|
||||
affirm( 3==sqlite3_value_bytes(argv[i]) );
|
||||
rc = ext.xColumnTotalSize(fCx, i, colTotalSz);
|
||||
affirm( 0==rc );
|
||||
}
|
||||
++xFuncCount.value;
|
||||
++outputs[0];
|
||||
}
|
||||
public void xDestroy(){
|
||||
xDestroyCalled.value = true;
|
||||
outputs[1] = 1;
|
||||
}
|
||||
};
|
||||
|
||||
int rc = fApi.xCreateFunction("myaux", pUserData, func);
|
||||
affirm( 0==rc );
|
||||
|
||||
affirm( 0==xFuncCount.value );
|
||||
affirm( 0==outputs[0] );
|
||||
execSql(db, "select myaux(ft,a,b) from ft;");
|
||||
affirm( 2==xFuncCount.value );
|
||||
affirm( !xDestroyCalled.value );
|
||||
affirm( 2==outputs[0] );
|
||||
affirm( 0==outputs[1] );
|
||||
sqlite3_close_v2(db);
|
||||
affirm( xDestroyCalled.value );
|
||||
affirm( 1==outputs[1] );
|
||||
}
|
||||
|
||||
public TesterFts5(boolean verbose){
|
||||
if(verbose){
|
||||
final long timeStart = System.currentTimeMillis();
|
||||
final int oldAffirmCount = Tester1.affirmCount;
|
||||
test1();
|
||||
final int affirmCount = Tester1.affirmCount - oldAffirmCount;
|
||||
final long timeEnd = System.currentTimeMillis();
|
||||
outln("FTS5 Tests done. Assertions checked = ",affirmCount,
|
||||
", Total time = ",(timeEnd - timeStart),"ms");
|
||||
}else{
|
||||
test1();
|
||||
/*
|
||||
** Argument sql is a string containing one or more SQL statements
|
||||
** separated by ";" characters. This function executes each of these
|
||||
** statements against the database passed as the first argument. If
|
||||
** no error occurs, the results of the SQL script are returned as
|
||||
** an array of strings. If an error does occur, a RuntimeException is
|
||||
** thrown.
|
||||
*/
|
||||
private static String[] sqlite3_exec(sqlite3 db, String sql) {
|
||||
List<String> aOut = new ArrayList<String>();
|
||||
|
||||
/* Iterate through the list of SQL statements. For each, step through
|
||||
** it and add any results to the aOut[] array. */
|
||||
int rc = sqlite3_prepare_multi(db, sql, new PrepareMultiCallback() {
|
||||
@Override public int call(sqlite3_stmt pStmt){
|
||||
while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
int ii;
|
||||
for(ii=0; ii<sqlite3_column_count(pStmt); ii++){
|
||||
aOut.add( sqlite3_column_text16(pStmt, ii) );
|
||||
}
|
||||
}
|
||||
return sqlite3_finalize(pStmt);
|
||||
}
|
||||
});
|
||||
if( rc!=SQLITE_OK ){
|
||||
throw new RuntimeException(sqlite3_errmsg16(db));
|
||||
}
|
||||
|
||||
/* Convert to array and return */
|
||||
String[] arr = new String[aOut.size()];
|
||||
return aOut.toArray(arr);
|
||||
}
|
||||
|
||||
/*
|
||||
** Execute the SQL script passed as the second parameter via
|
||||
** sqlite3_exec(). Then affirm() that the results, when converted to
|
||||
** a string, match the value of the 3rd parameter. Example:
|
||||
**
|
||||
** do_execsql_test(db, "SELECT 'abc'", "[abc]");
|
||||
**
|
||||
*/
|
||||
private static void do_execsql_test(sqlite3 db, String sql, String expect) {
|
||||
String res = Arrays.toString( sqlite3_exec(db, sql) );
|
||||
affirm( res.equals(expect),
|
||||
"got {" + res + "} expected {" + expect + "}"
|
||||
);
|
||||
}
|
||||
private static void do_execsql_test(sqlite3 db, String sql){
|
||||
do_execsql_test(db, sql, "[]");
|
||||
}
|
||||
|
||||
/*
|
||||
** Create the following custom SQL functions:
|
||||
**
|
||||
** fts5_rowid()
|
||||
** fts5_columncount()
|
||||
*/
|
||||
private static void create_test_functions(sqlite3 db){
|
||||
/*
|
||||
** A user-defined-function fts5_rowid() that uses xRowid()
|
||||
*/
|
||||
fts5_extension_function fts5_rowid = new fts5_extension_function(){
|
||||
@Override public void call(
|
||||
Fts5ExtensionApi ext,
|
||||
Fts5Context fCx,
|
||||
sqlite3_context pCx,
|
||||
sqlite3_value argv[]
|
||||
){
|
||||
long rowid = ext.xRowid(fCx);
|
||||
sqlite3_result_int64(pCx, rowid);
|
||||
}
|
||||
public void xDestroy(){ }
|
||||
};
|
||||
|
||||
/*
|
||||
** fts5_columncount() - xColumnCount()
|
||||
*/
|
||||
fts5_extension_function fts5_columncount = new fts5_extension_function(){
|
||||
@Override public void call(
|
||||
Fts5ExtensionApi ext,
|
||||
Fts5Context fCx,
|
||||
sqlite3_context pCx,
|
||||
sqlite3_value argv[]
|
||||
){
|
||||
int nCol = ext.xColumnCount(fCx);
|
||||
sqlite3_result_int(pCx, nCol);
|
||||
}
|
||||
public void xDestroy(){ }
|
||||
};
|
||||
|
||||
/*
|
||||
** fts5_columnsize() - xColumnSize()
|
||||
*/
|
||||
fts5_extension_function fts5_columnsize = new fts5_extension_function(){
|
||||
@Override public void call(
|
||||
Fts5ExtensionApi ext,
|
||||
Fts5Context fCx,
|
||||
sqlite3_context pCx,
|
||||
sqlite3_value argv[]
|
||||
){
|
||||
if( argv.length!=1 ){
|
||||
throw new RuntimeException("fts5_columncount: wrong number of args");
|
||||
}
|
||||
int iCol = sqlite3_value_int(argv[0]);
|
||||
|
||||
OutputPointer.Int32 piSz = new OutputPointer.Int32();
|
||||
int rc = ext.xColumnSize(fCx, iCol, piSz);
|
||||
if( rc!=SQLITE_OK ){
|
||||
throw new RuntimeException( sqlite3_errstr(rc) );
|
||||
}
|
||||
sqlite3_result_int(pCx, piSz.get());
|
||||
}
|
||||
public void xDestroy(){ }
|
||||
};
|
||||
|
||||
/*
|
||||
** fts5_columntext() - xColumnText()
|
||||
*/
|
||||
fts5_extension_function fts5_columntext = new fts5_extension_function(){
|
||||
@Override public void call(
|
||||
Fts5ExtensionApi ext,
|
||||
Fts5Context fCx,
|
||||
sqlite3_context pCx,
|
||||
sqlite3_value argv[]
|
||||
){
|
||||
if( argv.length!=1 ){
|
||||
throw new RuntimeException("fts5_columntext: wrong number of args");
|
||||
}
|
||||
int iCol = sqlite3_value_int(argv[0]);
|
||||
|
||||
OutputPointer.String pzText = new OutputPointer.String();
|
||||
int rc = ext.xColumnText(fCx, iCol, pzText);
|
||||
if( rc!=SQLITE_OK ){
|
||||
throw new RuntimeException( sqlite3_errstr(rc) );
|
||||
}
|
||||
sqlite3_result_text16(pCx, pzText.get());
|
||||
}
|
||||
public void xDestroy(){ }
|
||||
};
|
||||
|
||||
/*
|
||||
** fts5_columntotalsize() - xColumnTotalSize()
|
||||
*/
|
||||
fts5_extension_function fts5_columntsize = new fts5_extension_function(){
|
||||
@Override public void call(
|
||||
Fts5ExtensionApi ext,
|
||||
Fts5Context fCx,
|
||||
sqlite3_context pCx,
|
||||
sqlite3_value argv[]
|
||||
){
|
||||
if( argv.length!=1 ){
|
||||
throw new RuntimeException(
|
||||
"fts5_columntotalsize: wrong number of args"
|
||||
);
|
||||
}
|
||||
int iCol = sqlite3_value_int(argv[0]);
|
||||
|
||||
OutputPointer.Int64 piSz = new OutputPointer.Int64();
|
||||
int rc = ext.xColumnTotalSize(fCx, iCol, piSz);
|
||||
if( rc!=SQLITE_OK ){
|
||||
throw new RuntimeException( sqlite3_errstr(rc) );
|
||||
}
|
||||
sqlite3_result_int64(pCx, piSz.get());
|
||||
}
|
||||
public void xDestroy(){ }
|
||||
};
|
||||
|
||||
/*
|
||||
** fts5_aux(<fts>, <value>);
|
||||
*/
|
||||
class fts5_aux implements fts5_extension_function {
|
||||
@Override public void call(
|
||||
Fts5ExtensionApi ext,
|
||||
Fts5Context fCx,
|
||||
sqlite3_context pCx,
|
||||
sqlite3_value argv[]
|
||||
){
|
||||
if( argv.length>1 ){
|
||||
throw new RuntimeException("fts5_aux: wrong number of args");
|
||||
}
|
||||
|
||||
boolean bClear = (argv.length==1);
|
||||
Object obj = ext.xGetAuxdata(fCx, bClear);
|
||||
if( obj instanceof String ){
|
||||
sqlite3_result_text16(pCx, (String)obj);
|
||||
}
|
||||
|
||||
if( argv.length==1 ){
|
||||
String val = sqlite3_value_text16(argv[0]);
|
||||
if( !val.equals("") ){
|
||||
ext.xSetAuxdata(fCx, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void xDestroy(){ }
|
||||
};
|
||||
|
||||
/*
|
||||
** fts5_inst(<fts>);
|
||||
**
|
||||
** This is used to test the xInstCount() and xInst() APIs. It returns a
|
||||
** text value containing a Tcl list with xInstCount() elements. Each
|
||||
** element is itself a list of 3 integers - the phrase number, column
|
||||
** number and token offset returned by each call to xInst().
|
||||
*/
|
||||
fts5_extension_function fts5_inst = new fts5_extension_function(){
|
||||
@Override public void call(
|
||||
Fts5ExtensionApi ext,
|
||||
Fts5Context fCx,
|
||||
sqlite3_context pCx,
|
||||
sqlite3_value argv[]
|
||||
){
|
||||
if( argv.length!=0 ){
|
||||
throw new RuntimeException("fts5_inst: wrong number of args");
|
||||
}
|
||||
|
||||
OutputPointer.Int32 pnInst = new OutputPointer.Int32();
|
||||
OutputPointer.Int32 piPhrase = new OutputPointer.Int32();
|
||||
OutputPointer.Int32 piCol = new OutputPointer.Int32();
|
||||
OutputPointer.Int32 piOff = new OutputPointer.Int32();
|
||||
String ret = new String();
|
||||
|
||||
int rc = ext.xInstCount(fCx, pnInst);
|
||||
int nInst = pnInst.get();
|
||||
int ii;
|
||||
|
||||
for(ii=0; rc==SQLITE_OK && ii<nInst; ii++){
|
||||
ext.xInst(fCx, ii, piPhrase, piCol, piOff);
|
||||
if( ii>0 ) ret += " ";
|
||||
ret += "{"+piPhrase.get()+" "+piCol.get()+" "+piOff.get()+"}";
|
||||
}
|
||||
|
||||
sqlite3_result_text(pCx, ret);
|
||||
}
|
||||
public void xDestroy(){ }
|
||||
};
|
||||
|
||||
/*
|
||||
** fts5_pinst(<fts>);
|
||||
**
|
||||
** Like SQL function fts5_inst(), except using the following
|
||||
**
|
||||
** xPhraseCount
|
||||
** xPhraseFirst
|
||||
** xPhraseNext
|
||||
*/
|
||||
fts5_extension_function fts5_pinst = new fts5_extension_function(){
|
||||
@Override public void call(
|
||||
Fts5ExtensionApi ext,
|
||||
Fts5Context fCx,
|
||||
sqlite3_context pCx,
|
||||
sqlite3_value argv[]
|
||||
){
|
||||
if( argv.length!=0 ){
|
||||
throw new RuntimeException("fts5_pinst: wrong number of args");
|
||||
}
|
||||
|
||||
OutputPointer.Int32 piCol = new OutputPointer.Int32();
|
||||
OutputPointer.Int32 piOff = new OutputPointer.Int32();
|
||||
String ret = new String();
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
int nPhrase = ext.xPhraseCount(fCx);
|
||||
int ii;
|
||||
|
||||
for(ii=0; rc==SQLITE_OK && ii<nPhrase; ii++){
|
||||
Fts5PhraseIter pIter = new Fts5PhraseIter();
|
||||
for(rc = ext.xPhraseFirst(fCx, ii, pIter, piCol, piOff);
|
||||
rc==SQLITE_OK && piCol.get()>=0;
|
||||
ext.xPhraseNext(fCx, pIter, piCol, piOff)
|
||||
){
|
||||
if( !ret.equals("") ) ret += " ";
|
||||
ret += "{"+ii+" "+piCol.get()+" "+piOff.get()+"}";
|
||||
}
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
throw new RuntimeException("fts5_pinst: rc=" + rc);
|
||||
}else{
|
||||
sqlite3_result_text(pCx, ret);
|
||||
}
|
||||
}
|
||||
public void xDestroy(){ }
|
||||
};
|
||||
|
||||
/*
|
||||
** fts5_pcolinst(<fts>);
|
||||
**
|
||||
** Like SQL function fts5_pinst(), except using the following
|
||||
**
|
||||
** xPhraseFirstColumn
|
||||
** xPhraseNextColumn
|
||||
*/
|
||||
fts5_extension_function fts5_pcolinst = new fts5_extension_function(){
|
||||
@Override public void call(
|
||||
Fts5ExtensionApi ext,
|
||||
Fts5Context fCx,
|
||||
sqlite3_context pCx,
|
||||
sqlite3_value argv[]
|
||||
){
|
||||
if( argv.length!=0 ){
|
||||
throw new RuntimeException("fts5_pcolinst: wrong number of args");
|
||||
}
|
||||
|
||||
OutputPointer.Int32 piCol = new OutputPointer.Int32();
|
||||
String ret = new String();
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
int nPhrase = ext.xPhraseCount(fCx);
|
||||
int ii;
|
||||
|
||||
for(ii=0; rc==SQLITE_OK && ii<nPhrase; ii++){
|
||||
Fts5PhraseIter pIter = new Fts5PhraseIter();
|
||||
for(rc = ext.xPhraseFirstColumn(fCx, ii, pIter, piCol);
|
||||
rc==SQLITE_OK && piCol.get()>=0;
|
||||
ext.xPhraseNextColumn(fCx, pIter, piCol)
|
||||
){
|
||||
if( !ret.equals("") ) ret += " ";
|
||||
ret += "{"+ii+" "+piCol.get()+"}";
|
||||
}
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
throw new RuntimeException("fts5_pcolinst: rc=" + rc);
|
||||
}else{
|
||||
sqlite3_result_text(pCx, ret);
|
||||
}
|
||||
}
|
||||
public void xDestroy(){ }
|
||||
};
|
||||
|
||||
/*
|
||||
** fts5_rowcount(<fts>);
|
||||
*/
|
||||
fts5_extension_function fts5_rowcount = new fts5_extension_function(){
|
||||
@Override public void call(
|
||||
Fts5ExtensionApi ext,
|
||||
Fts5Context fCx,
|
||||
sqlite3_context pCx,
|
||||
sqlite3_value argv[]
|
||||
){
|
||||
if( argv.length!=0 ){
|
||||
throw new RuntimeException("fts5_rowcount: wrong number of args");
|
||||
}
|
||||
OutputPointer.Int64 pnRow = new OutputPointer.Int64();
|
||||
|
||||
int rc = ext.xRowCount(fCx, pnRow);
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_result_int64(pCx, pnRow.get());
|
||||
}else{
|
||||
throw new RuntimeException("fts5_rowcount: rc=" + rc);
|
||||
}
|
||||
}
|
||||
public void xDestroy(){ }
|
||||
};
|
||||
|
||||
/*
|
||||
** fts5_phrasesize(<fts>);
|
||||
*/
|
||||
fts5_extension_function fts5_phrasesize = new fts5_extension_function(){
|
||||
@Override public void call(
|
||||
Fts5ExtensionApi ext,
|
||||
Fts5Context fCx,
|
||||
sqlite3_context pCx,
|
||||
sqlite3_value argv[]
|
||||
){
|
||||
if( argv.length!=1 ){
|
||||
throw new RuntimeException("fts5_phrasesize: wrong number of args");
|
||||
}
|
||||
int iPhrase = sqlite3_value_int(argv[0]);
|
||||
|
||||
int sz = ext.xPhraseSize(fCx, iPhrase);
|
||||
sqlite3_result_int(pCx, sz);
|
||||
}
|
||||
public void xDestroy(){ }
|
||||
};
|
||||
|
||||
/*
|
||||
** fts5_phrasehits(<fts>, <phrase-number>);
|
||||
**
|
||||
** Use the xQueryPhrase() API to determine how many hits, in total,
|
||||
** there are for phrase <phrase-number> in the database.
|
||||
*/
|
||||
fts5_extension_function fts5_phrasehits = new fts5_extension_function(){
|
||||
@Override public void call(
|
||||
Fts5ExtensionApi ext,
|
||||
Fts5Context fCx,
|
||||
sqlite3_context pCx,
|
||||
sqlite3_value argv[]
|
||||
){
|
||||
if( argv.length!=1 ){
|
||||
throw new RuntimeException("fts5_phrasesize: wrong number of args");
|
||||
}
|
||||
int iPhrase = sqlite3_value_int(argv[0]);
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
class MyCallback implements Fts5ExtensionApi.XQueryPhraseCallback {
|
||||
public int nRet = 0;
|
||||
public int getRet() { return nRet; }
|
||||
|
||||
@Override
|
||||
public int call(Fts5ExtensionApi fapi, Fts5Context cx){
|
||||
OutputPointer.Int32 pnInst = new OutputPointer.Int32();
|
||||
int rc = fapi.xInstCount(cx, pnInst);
|
||||
nRet += pnInst.get();
|
||||
return rc;
|
||||
}
|
||||
};
|
||||
|
||||
MyCallback xCall = new MyCallback();
|
||||
rc = ext.xQueryPhrase(fCx, iPhrase, xCall);
|
||||
if( rc!=SQLITE_OK ){
|
||||
throw new RuntimeException("fts5_phrasehits: rc=" + rc);
|
||||
}
|
||||
sqlite3_result_int(pCx, xCall.getRet());
|
||||
}
|
||||
public void xDestroy(){ }
|
||||
};
|
||||
|
||||
/*
|
||||
** fts5_tokenize(<fts>, <text>)
|
||||
*/
|
||||
fts5_extension_function fts5_tokenize = new fts5_extension_function(){
|
||||
@Override public void call(
|
||||
Fts5ExtensionApi ext,
|
||||
Fts5Context fCx,
|
||||
sqlite3_context pCx,
|
||||
sqlite3_value argv[]
|
||||
){
|
||||
if( argv.length!=1 ){
|
||||
throw new RuntimeException("fts5_tokenize: wrong number of args");
|
||||
}
|
||||
byte[] utf8 = sqlite3_value_text(argv[0]);
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
class MyCallback implements XTokenizeCallback {
|
||||
private List<String> myList = new ArrayList<String>();
|
||||
|
||||
public String getval() {
|
||||
return String.join("+", myList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int call(int tFlags, byte[] txt, int iStart, int iEnd){
|
||||
try {
|
||||
String str = new String(txt, "UTF-8");
|
||||
myList.add(str);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
};
|
||||
|
||||
MyCallback xCall = new MyCallback();
|
||||
ext.xTokenize(fCx, utf8, xCall);
|
||||
sqlite3_result_text16(pCx, xCall.getval());
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
throw new RuntimeException("fts5_tokenize: rc=" + rc);
|
||||
}
|
||||
}
|
||||
public void xDestroy(){ }
|
||||
};
|
||||
|
||||
fts5_api api = fts5_api.getInstanceForDb(db);
|
||||
api.xCreateFunction("fts5_rowid", fts5_rowid);
|
||||
api.xCreateFunction("fts5_columncount", fts5_columncount);
|
||||
api.xCreateFunction("fts5_columnsize", fts5_columnsize);
|
||||
api.xCreateFunction("fts5_columntext", fts5_columntext);
|
||||
api.xCreateFunction("fts5_columntotalsize", fts5_columntsize);
|
||||
|
||||
api.xCreateFunction("fts5_aux1", new fts5_aux());
|
||||
api.xCreateFunction("fts5_aux2", new fts5_aux());
|
||||
|
||||
api.xCreateFunction("fts5_inst", fts5_inst);
|
||||
api.xCreateFunction("fts5_pinst", fts5_pinst);
|
||||
api.xCreateFunction("fts5_pcolinst", fts5_pcolinst);
|
||||
api.xCreateFunction("fts5_rowcount", fts5_rowcount);
|
||||
api.xCreateFunction("fts5_phrasesize", fts5_phrasesize);
|
||||
api.xCreateFunction("fts5_phrasehits", fts5_phrasehits);
|
||||
api.xCreateFunction("fts5_tokenize", fts5_tokenize);
|
||||
}
|
||||
/*
|
||||
** Test of various Fts5ExtensionApi methods
|
||||
*/
|
||||
private static void test2(){
|
||||
|
||||
/* Open db and populate an fts5 table */
|
||||
sqlite3 db = createNewDb();
|
||||
do_execsql_test(db,
|
||||
"CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
|
||||
"INSERT INTO ft(rowid, a, b) VALUES(-9223372036854775808, 'x', 'x');" +
|
||||
"INSERT INTO ft(rowid, a, b) VALUES(0, 'x', 'x');" +
|
||||
"INSERT INTO ft(rowid, a, b) VALUES(1, 'x y z', 'x y z');" +
|
||||
"INSERT INTO ft(rowid, a, b) VALUES(2, 'x y z', 'x z');" +
|
||||
"INSERT INTO ft(rowid, a, b) VALUES(3, 'x y z', 'x y z');" +
|
||||
"INSERT INTO ft(rowid, a, b) VALUES(9223372036854775807, 'x', 'x');"
|
||||
);
|
||||
|
||||
create_test_functions(db);
|
||||
|
||||
/* Test that fts5_rowid() seems to work */
|
||||
do_execsql_test(db,
|
||||
"SELECT rowid==fts5_rowid(ft) FROM ft('x')",
|
||||
"[1, 1, 1, 1, 1, 1]"
|
||||
);
|
||||
|
||||
/* Test fts5_columncount() */
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columncount(ft) FROM ft('x')",
|
||||
"[2, 2, 2, 2, 2, 2]"
|
||||
);
|
||||
|
||||
/* Test fts5_columnsize() */
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columnsize(ft, 0) FROM ft('x') ORDER BY rowid",
|
||||
"[1, 1, 3, 3, 3, 1]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columnsize(ft, 1) FROM ft('x') ORDER BY rowid",
|
||||
"[1, 1, 3, 2, 3, 1]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columnsize(ft, -1) FROM ft('x') ORDER BY rowid",
|
||||
"[2, 2, 6, 5, 6, 2]"
|
||||
);
|
||||
|
||||
/* Test that xColumnSize() returns SQLITE_RANGE if the column number
|
||||
** is out-of range */
|
||||
try {
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columnsize(ft, 2) FROM ft('x') ORDER BY rowid"
|
||||
);
|
||||
} catch( RuntimeException e ){
|
||||
affirm( e.getMessage().matches(".*column index out of range") );
|
||||
}
|
||||
|
||||
/* Test fts5_columntext() */
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columntext(ft, 0) FROM ft('x') ORDER BY rowid",
|
||||
"[x, x, x y z, x y z, x y z, x]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columntext(ft, 1) FROM ft('x') ORDER BY rowid",
|
||||
"[x, x, x y z, x z, x y z, x]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columntext(ft, 2) FROM ft('x') ORDER BY rowid",
|
||||
"[null, null, null, null, null, null]"
|
||||
);
|
||||
|
||||
/* Test fts5_columntotalsize() */
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columntotalsize(ft, 0) FROM ft('x') ORDER BY rowid",
|
||||
"[12, 12, 12, 12, 12, 12]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columntotalsize(ft, 1) FROM ft('x') ORDER BY rowid",
|
||||
"[11, 11, 11, 11, 11, 11]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columntotalsize(ft, -1) FROM ft('x') ORDER BY rowid",
|
||||
"[23, 23, 23, 23, 23, 23]"
|
||||
);
|
||||
|
||||
/* Test that xColumnTotalSize() returns SQLITE_RANGE if the column
|
||||
** number is out-of range */
|
||||
try {
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columntotalsize(ft, 2) FROM ft('x') ORDER BY rowid"
|
||||
);
|
||||
} catch( RuntimeException e ){
|
||||
affirm( e.getMessage().matches(".*column index out of range") );
|
||||
}
|
||||
|
||||
do_execsql_test(db,
|
||||
"SELECT rowid, fts5_rowcount(ft) FROM ft('z')",
|
||||
"[1, 6, 2, 6, 3, 6]"
|
||||
);
|
||||
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
/*
|
||||
** Test of various Fts5ExtensionApi methods
|
||||
*/
|
||||
private static void test3(){
|
||||
|
||||
/* Open db and populate an fts5 table */
|
||||
sqlite3 db = createNewDb();
|
||||
do_execsql_test(db,
|
||||
"CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
|
||||
"INSERT INTO ft(a, b) VALUES('the one', 1);" +
|
||||
"INSERT INTO ft(a, b) VALUES('the two', 2);" +
|
||||
"INSERT INTO ft(a, b) VALUES('the three', 3);" +
|
||||
"INSERT INTO ft(a, b) VALUES('the four', '');"
|
||||
);
|
||||
create_test_functions(db);
|
||||
|
||||
/* Test fts5_aux1() + fts5_aux2() - users of xGetAuxdata and xSetAuxdata */
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_aux1(ft, a) FROM ft('the')",
|
||||
"[null, the one, the two, the three]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_aux2(ft, b) FROM ft('the')",
|
||||
"[null, 1, 2, 3]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_aux1(ft, a), fts5_aux2(ft, b) FROM ft('the')",
|
||||
"[null, null, the one, 1, the two, 2, the three, 3]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_aux1(ft, b), fts5_aux1(ft) FROM ft('the')",
|
||||
"[null, 1, 1, 2, 2, 3, 3, null]"
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
** Test of various Fts5ExtensionApi methods
|
||||
*/
|
||||
private static void test4(){
|
||||
|
||||
/* Open db and populate an fts5 table */
|
||||
sqlite3 db = createNewDb();
|
||||
create_test_functions(db);
|
||||
do_execsql_test(db,
|
||||
"CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
|
||||
"INSERT INTO ft(a, b) VALUES('one two three', 'two three four');" +
|
||||
"INSERT INTO ft(a, b) VALUES('two three four', 'three four five');" +
|
||||
"INSERT INTO ft(a, b) VALUES('three four five', 'four five six');"
|
||||
);
|
||||
|
||||
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_inst(ft) FROM ft('two')",
|
||||
"[{0 0 1} {0 1 0}, {0 0 0}]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_inst(ft) FROM ft('four')",
|
||||
"[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]"
|
||||
);
|
||||
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_inst(ft) FROM ft('a OR b OR four')",
|
||||
"[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_inst(ft) FROM ft('two four')",
|
||||
"[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]"
|
||||
);
|
||||
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_pinst(ft) FROM ft('two')",
|
||||
"[{0 0 1} {0 1 0}, {0 0 0}]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_pinst(ft) FROM ft('four')",
|
||||
"[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_pinst(ft) FROM ft('a OR b OR four')",
|
||||
"[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_pinst(ft) FROM ft('two four')",
|
||||
"[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]"
|
||||
);
|
||||
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_pcolinst(ft) FROM ft('two')",
|
||||
"[{0 0} {0 1}, {0 0}]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_pcolinst(ft) FROM ft('four')",
|
||||
"[{0 1}, {0 0} {0 1}, {0 0} {0 1}]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_pcolinst(ft) FROM ft('a OR b OR four')",
|
||||
"[{2 1}, {2 0} {2 1}, {2 0} {2 1}]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_pcolinst(ft) FROM ft('two four')",
|
||||
"[{0 0} {0 1} {1 1}, {0 0} {1 0} {1 1}]"
|
||||
);
|
||||
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_phrasesize(ft, 0) FROM ft('four five six') LIMIT 1;",
|
||||
"[1]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_phrasesize(ft, 0) FROM ft('four + five + six') LIMIT 1;",
|
||||
"[3]"
|
||||
);
|
||||
|
||||
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
private static void test5(){
|
||||
/* Open db and populate an fts5 table */
|
||||
sqlite3 db = createNewDb();
|
||||
create_test_functions(db);
|
||||
do_execsql_test(db,
|
||||
"CREATE VIRTUAL TABLE ft USING fts5(x, b);" +
|
||||
"INSERT INTO ft(x) VALUES('one two three four five six seven eight');" +
|
||||
"INSERT INTO ft(x) VALUES('one two one four one six one eight');" +
|
||||
"INSERT INTO ft(x) VALUES('one two three four five six seven eight');"
|
||||
);
|
||||
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_phrasehits(ft, 0) FROM ft('one') LIMIT 1",
|
||||
"[6]"
|
||||
);
|
||||
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
private static void test6(){
|
||||
sqlite3 db = createNewDb();
|
||||
create_test_functions(db);
|
||||
do_execsql_test(db,
|
||||
"CREATE VIRTUAL TABLE ft USING fts5(x, b);" +
|
||||
"INSERT INTO ft(x) VALUES('one two three four five six seven eight');"
|
||||
);
|
||||
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_tokenize(ft, 'abc def ghi') FROM ft('one')",
|
||||
"[abc+def+ghi]"
|
||||
);
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_tokenize(ft, 'it''s BEEN a...') FROM ft('one')",
|
||||
"[it+s+been+a]"
|
||||
);
|
||||
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
private static synchronized void runTests(){
|
||||
test1();
|
||||
test2();
|
||||
test3();
|
||||
test4();
|
||||
test5();
|
||||
test6();
|
||||
}
|
||||
|
||||
public TesterFts5(){
|
||||
runTests();
|
||||
}
|
||||
public TesterFts5(){ this(false); }
|
||||
}
|
||||
|
||||
@@ -12,18 +12,19 @@
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
import org.sqlite.jni.annotation.Nullable;
|
||||
|
||||
/**
|
||||
Callback proxy for use with sqlite3_trace_v2().
|
||||
Callback for use with {@link SQLite3Jni#sqlite3_trace_v2}.
|
||||
*/
|
||||
public interface TraceV2Callback extends SQLite3CallbackProxy {
|
||||
public interface TraceV2Callback extends CallbackProxy {
|
||||
/**
|
||||
Called by sqlite3 for various tracing operations, as per
|
||||
sqlite3_trace_v2(). Note that this interface elides the 2nd
|
||||
argument to the native trace callback, as that role is better
|
||||
filled by instance-local state.
|
||||
|
||||
These callbacks may throw, in which case their exceptions are
|
||||
<p>These callbacks may throw, in which case their exceptions are
|
||||
converted to C-level error information.
|
||||
|
||||
<p>The 2nd argument to this function, if non-null, will be a an
|
||||
@@ -45,5 +46,5 @@ public interface TraceV2Callback extends SQLite3CallbackProxy {
|
||||
|
||||
<p>- SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null.
|
||||
*/
|
||||
int call(int traceFlag, Object pNative, Object pX);
|
||||
int call(int traceFlag, Object pNative, @Nullable Object pX);
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback for use with sqlite3_update_hook().
|
||||
Callback for use with {@link SQLite3Jni#sqlite3_update_hook}.
|
||||
*/
|
||||
public interface UpdateHookCallback extends SQLite3CallbackProxy {
|
||||
public interface UpdateHookCallback extends CallbackProxy {
|
||||
/**
|
||||
Must function as described for the C-level sqlite3_update_hook()
|
||||
callback. Must not throw.
|
||||
callback.
|
||||
*/
|
||||
void call(int opId, String dbName, String tableName, long rowId);
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ package org.sqlite.jni;
|
||||
|
||||
|
||||
/**
|
||||
An SQLFunction subclass for creating window functions. Note that
|
||||
Window<T> inherits from Aggregate<T> and each instance is
|
||||
required to implement the inherited abstract methods from that
|
||||
class. See Aggregate<T> for information on managing the UDF's
|
||||
invocation-specific state.
|
||||
A SQLFunction implementation for window functions. Note that
|
||||
WindowFunction inherits from {@link AggregateFunction} and each
|
||||
instance is required to implement the inherited abstract methods
|
||||
from that class. See {@link AggregateFunction} for information on
|
||||
managing the UDF's invocation-specific state.
|
||||
*/
|
||||
public abstract class WindowFunction<T> extends AggregateFunction<T> {
|
||||
|
||||
|
||||
@@ -24,6 +24,14 @@ public interface XDestroyCallback {
|
||||
Must perform any cleanup required by this object. Must not
|
||||
throw. Must not call back into the sqlite3 API, else it might
|
||||
invoke a deadlock.
|
||||
|
||||
WARNING: as a rule, it is never safe to register individual
|
||||
instances with this interface multiple times in the
|
||||
library. e.g., do not register the same CollationCallback with
|
||||
multiple arities or names using sqlite3_create_collation(). If
|
||||
this rule is violated, the library will eventually try to free
|
||||
each individual reference, leading to memory corruption or a
|
||||
crash via duplicate free().
|
||||
*/
|
||||
public void xDestroy();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
** 2023-09-27
|
||||
**
|
||||
** 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 houses the Canonical annotaion for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni.annotation;
|
||||
|
||||
/**
|
||||
This annotation is for marking functions as "canonical", meaning
|
||||
that they map directly to a function in the core sqlite3 C API. The
|
||||
intent is to distinguish them from functions added specifically to
|
||||
the Java API.
|
||||
|
||||
<p>Canonical functions, unless specifically documented, have the
|
||||
same semantics as their counterparts in <a
|
||||
href="https://sqlite.org/c3ref/intro.html">the C API
|
||||
documentation</a>, despite their signatures perhaps differing
|
||||
slightly. Canonical forms may be native or implemented in Java.
|
||||
Sometimes multiple overloads are labeled as Canonical because one
|
||||
or more of them are just type- or encoding-related conversion
|
||||
wrappers but provide identical semantics (e.g. from a String to a
|
||||
byte[]). The Java API adds a number of convenience overloads to
|
||||
simplify use, as well as a few Java-specific functions, and those
|
||||
are never flagged as @Canonical.
|
||||
|
||||
<p>In some cases, the canonical version of a function is private
|
||||
and exposed to Java via public overloads.
|
||||
|
||||
<p>The comment property can be used to add a comment.
|
||||
*/
|
||||
@java.lang.annotation.Documented
|
||||
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
|
||||
@java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD)
|
||||
public @interface Canonical{
|
||||
/**
|
||||
Brief comments about the binding, e.g. noting any major
|
||||
semantic differences.
|
||||
*/
|
||||
String comment() default "";
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
** 2023-09-27
|
||||
**
|
||||
** 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 houses the NotNull annotaion for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni.annotation;
|
||||
|
||||
/**
|
||||
This annotation is for flagging parameters which may not legally be
|
||||
null or point to closed/finalized C-side resources.
|
||||
|
||||
<p>In the case of Java types which map directly to C struct types
|
||||
(e.g. {@link org.sqlite.jni.sqlite3}, {@link
|
||||
org.sqlite.jni.sqlite3_stmt}, and {@link
|
||||
org.sqlite.jni.sqlite3_context}), a closed/finalized resource is
|
||||
also considered to be null for purposes this annotation because the
|
||||
C-side effect of passing such a handle is the same as if null is
|
||||
passed.</p>
|
||||
|
||||
<p>When used in the context of Java interfaces which are called
|
||||
from the C APIs, this annotation communicates that the C API will
|
||||
never pass a null value to the callback for that parameter.</p>
|
||||
|
||||
<p>Passing a null, for this annotation's definition of null, for
|
||||
any parameter marked with this annoation specifically invokes
|
||||
undefined behavior.</p>
|
||||
|
||||
<p>Passing 0 (i.e. C NULL) or a negative value for any long-type
|
||||
parameter marked with this annoation specifically invokes undefined
|
||||
behavior. Such values are treated as C pointers in the JNI
|
||||
layer.</p>
|
||||
|
||||
<p>Note that the C-style API does not throw any exceptions on its
|
||||
own because it has a no-throw policy in order to retain its C-style
|
||||
semantics, but it may trigger NullPointerExceptions (or similar) if
|
||||
passed a null for a parameter flagged with this annotation.</p>
|
||||
|
||||
<p>This annotation is informational only. No policy is in place to
|
||||
programmatically ensure that NotNull is conformed to in client
|
||||
code.</p>
|
||||
|
||||
<p>This annotation is solely for the use by the classes in the
|
||||
org.sqlite package and subpackages, but is made public so that
|
||||
javadoc will link to it from the annotated functions. It is not
|
||||
part of the public API and client-level code must not rely on
|
||||
it.</p>
|
||||
*/
|
||||
@java.lang.annotation.Documented
|
||||
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
|
||||
@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
|
||||
public @interface NotNull{}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
** 2023-09-27
|
||||
**
|
||||
** 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 houses the Nullable annotaion for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni.annotation;
|
||||
|
||||
/**
|
||||
This annotation is for flagging parameters which may legally be
|
||||
null, noting that they may behave differently if passed null but
|
||||
are prepared to expect null as a value. When used in the context of
|
||||
callback methods which are called into from the C APIs, this
|
||||
annotation communicates that the C API may pass a null value to the
|
||||
callback.
|
||||
|
||||
<p>This annotation is solely for the use by the classes in this
|
||||
package but is made public so that javadoc will link to it from the
|
||||
annotated functions. It is not part of the public API and
|
||||
client-level code must not rely on it.
|
||||
*/
|
||||
@java.lang.annotation.Documented
|
||||
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
|
||||
@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
|
||||
public @interface Nullable{}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
** 2023-09-27
|
||||
**
|
||||
** 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 package houses annotations specific to the JNI bindings of the
|
||||
SQLite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni.annotation;
|
||||
@@ -11,26 +11,18 @@
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
package org.sqlite.jni.fts5;
|
||||
|
||||
/**
|
||||
INCOMPLETE AND COMPLETELY UNTESTED.
|
||||
|
||||
A wrapper for communicating C-level (fts5_api*) instances with
|
||||
Java. These wrappers do not own their associated pointer, they
|
||||
simply provide a type-safe way to communicate it between Java and C
|
||||
via JNI.
|
||||
A utility object for holding FTS5-specific types and constants
|
||||
which are used by multiple FTS5 classes.
|
||||
*/
|
||||
public final class Fts5 {
|
||||
/* Not used */
|
||||
private Fts5(){}
|
||||
|
||||
/**
|
||||
Callback type for use with xTokenize() variants
|
||||
*/
|
||||
public static interface xTokenize_callback {
|
||||
int call(int tFlags, byte[] txt, int iStart, int iEnd);
|
||||
}
|
||||
|
||||
public static final int FTS5_TOKENIZE_QUERY = 0x0001;
|
||||
public static final int FTS5_TOKENIZE_PREFIX = 0x0002;
|
||||
+2
-1
@@ -11,7 +11,8 @@
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
package org.sqlite.jni.fts5;
|
||||
import org.sqlite.jni.*;
|
||||
|
||||
/**
|
||||
A wrapper for communicating C-level (Fts5Context*) instances with
|
||||
+41
-11
@@ -11,14 +11,12 @@
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
package org.sqlite.jni.fts5;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.sqlite.jni.*;
|
||||
import org.sqlite.jni.annotation.*;
|
||||
|
||||
/**
|
||||
ALMOST COMPLETELY UNTESTED.
|
||||
|
||||
FAR FROM COMPLETE and the feasibility of binding this to Java
|
||||
is still undetermined. This might be removed.
|
||||
*/
|
||||
public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi> {
|
||||
//! Only called from JNI
|
||||
@@ -26,8 +24,8 @@ public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi
|
||||
private final int iVersion = 2;
|
||||
|
||||
/* Callback type for used by xQueryPhrase(). */
|
||||
public static interface xQueryPhraseCallback {
|
||||
int xCallback(Fts5ExtensionApi fapi, Fts5Context cx);
|
||||
public static interface XQueryPhraseCallback {
|
||||
int call(Fts5ExtensionApi fapi, Fts5Context cx);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,40 +33,67 @@ public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi
|
||||
*/
|
||||
public static native Fts5ExtensionApi getInstance();
|
||||
|
||||
@Canonical
|
||||
public native int xColumnCount(@NotNull Fts5Context fcx);
|
||||
|
||||
@Canonical
|
||||
public native int xColumnSize(@NotNull Fts5Context cx, int iCol,
|
||||
@NotNull OutputPointer.Int32 pnToken);
|
||||
|
||||
@Canonical
|
||||
public native int xColumnText(@NotNull Fts5Context cx, int iCol,
|
||||
@NotNull OutputPointer.String txt);
|
||||
|
||||
@Canonical
|
||||
public native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol,
|
||||
@NotNull OutputPointer.Int64 pnToken);
|
||||
|
||||
@Canonical
|
||||
public native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt);
|
||||
|
||||
@Canonical
|
||||
public native int xInst(@NotNull Fts5Context cx, int iIdx,
|
||||
@NotNull OutputPointer.Int32 piPhrase,
|
||||
@NotNull OutputPointer.Int32 piCol,
|
||||
@NotNull OutputPointer.Int32 piOff);
|
||||
|
||||
@Canonical
|
||||
public native int xInstCount(@NotNull Fts5Context fcx,
|
||||
@NotNull OutputPointer.Int32 pnInst);
|
||||
|
||||
@Canonical
|
||||
public native int xPhraseCount(@NotNull Fts5Context fcx);
|
||||
|
||||
@Canonical
|
||||
public native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase,
|
||||
@NotNull Fts5PhraseIter iter,
|
||||
@NotNull OutputPointer.Int32 iCol,
|
||||
@NotNull OutputPointer.Int32 iOff);
|
||||
|
||||
@Canonical
|
||||
public native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase,
|
||||
@NotNull Fts5PhraseIter iter,
|
||||
@NotNull OutputPointer.Int32 iCol);
|
||||
@Canonical
|
||||
public native void xPhraseNext(@NotNull Fts5Context cx,
|
||||
@NotNull Fts5PhraseIter iter,
|
||||
@NotNull OutputPointer.Int32 iCol,
|
||||
@NotNull OutputPointer.Int32 iOff);
|
||||
@Canonical
|
||||
public native void xPhraseNextColumn(@NotNull Fts5Context cx,
|
||||
@NotNull Fts5PhraseIter iter,
|
||||
@NotNull OutputPointer.Int32 iCol);
|
||||
@Canonical
|
||||
public native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase);
|
||||
|
||||
@Canonical
|
||||
public native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase,
|
||||
@NotNull xQueryPhraseCallback callback);
|
||||
@NotNull XQueryPhraseCallback callback);
|
||||
@Canonical
|
||||
public native int xRowCount(@NotNull Fts5Context fcx,
|
||||
@NotNull OutputPointer.Int64 nRow);
|
||||
|
||||
@Canonical
|
||||
public native long xRowid(@NotNull Fts5Context cx);
|
||||
/* Note that the JNI binding lacks the C version's xDelete()
|
||||
callback argument. Instead, if pAux has an xDestroy() method, it
|
||||
@@ -76,10 +101,15 @@ public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi
|
||||
allocation of storage for the auxdata fails). Any reference to
|
||||
pAux held by the JNI layer will be relinquished regardless of
|
||||
whether pAux has an xDestroy() method. */
|
||||
public native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux);
|
||||
public native int xTokenize(@NotNull Fts5Context cx, @NotNull byte[] pText,
|
||||
@NotNull Fts5.xTokenize_callback callback);
|
||||
|
||||
@Canonical
|
||||
public native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux);
|
||||
|
||||
@Canonical
|
||||
public native int xTokenize(@NotNull Fts5Context cx, @NotNull byte[] pText,
|
||||
@NotNull XTokenizeCallback callback);
|
||||
|
||||
@Canonical
|
||||
public native Object xUserData(Fts5Context cx);
|
||||
//^^^ returns the pointer passed as the 3rd arg to the C-level
|
||||
// fts5_api::xCreateFunction().
|
||||
+2
-1
@@ -11,7 +11,8 @@
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
package org.sqlite.jni.fts5;
|
||||
import org.sqlite.jni.NativePointerHolder;
|
||||
|
||||
/**
|
||||
A wrapper for C-level Fts5PhraseIter. They are only modified and
|
||||
+2
-1
@@ -11,7 +11,8 @@
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
package org.sqlite.jni.fts5;
|
||||
import org.sqlite.jni.NativePointerHolder;
|
||||
|
||||
/**
|
||||
INCOMPLETE AND COMPLETELY UNTESTED.
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
** 2023-08-04
|
||||
**
|
||||
** 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 is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni.fts5;
|
||||
|
||||
|
||||
/**
|
||||
Callback type for use with xTokenize() variants.
|
||||
*/
|
||||
public interface XTokenizeCallback {
|
||||
int call(int tFlags, byte[] txt, int iStart, int iEnd);
|
||||
}
|
||||
+30
-22
@@ -11,11 +11,11 @@
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
package org.sqlite.jni.fts5;
|
||||
import org.sqlite.jni.annotation.*;
|
||||
import org.sqlite.jni.*;
|
||||
|
||||
/**
|
||||
INCOMPLETE AND COMPLETELY UNTESTED.
|
||||
|
||||
A wrapper for communicating C-level (fts5_api*) instances with
|
||||
Java. These wrappers do not own their associated pointer, they
|
||||
simply provide a type-safe way to communicate it between Java and C
|
||||
@@ -24,7 +24,8 @@ package org.sqlite.jni;
|
||||
public final class fts5_api extends NativePointerHolder<fts5_api> {
|
||||
/* Only invoked from JNI */
|
||||
private fts5_api(){}
|
||||
public final int iVersion = 2;
|
||||
|
||||
public static final int iVersion = 2;
|
||||
|
||||
/**
|
||||
Returns the fts5_api instance associated with the given db, or
|
||||
@@ -32,6 +33,31 @@ public final class fts5_api extends NativePointerHolder<fts5_api> {
|
||||
*/
|
||||
public static synchronized native fts5_api getInstanceForDb(@NotNull sqlite3 db);
|
||||
|
||||
@Canonical
|
||||
public synchronized native int xCreateFunction(@NotNull String name,
|
||||
@Nullable Object userData,
|
||||
@NotNull fts5_extension_function xFunction);
|
||||
|
||||
/**
|
||||
Convenience overload which passes null as the 2nd argument to the
|
||||
3-parameter form.
|
||||
*/
|
||||
public int xCreateFunction(@NotNull String name,
|
||||
@NotNull fts5_extension_function xFunction){
|
||||
return xCreateFunction(name, null, xFunction);
|
||||
}
|
||||
|
||||
// /* Create a new auxiliary function */
|
||||
// int (*xCreateFunction)(
|
||||
// fts5_api *pApi,
|
||||
// const char *zName,
|
||||
// void *pContext,
|
||||
// fts5_extension_function xFunction,
|
||||
// void (*xDestroy)(void*)
|
||||
// );
|
||||
|
||||
// Still potentially todo:
|
||||
|
||||
// int (*xCreateTokenizer)(
|
||||
// fts5_api *pApi,
|
||||
// const char *zName,
|
||||
@@ -48,22 +74,4 @@ public final class fts5_api extends NativePointerHolder<fts5_api> {
|
||||
// fts5_tokenizer *pTokenizer
|
||||
// );
|
||||
|
||||
// /* Create a new auxiliary function */
|
||||
// int (*xCreateFunction)(
|
||||
// fts5_api *pApi,
|
||||
// const char *zName,
|
||||
// void *pContext,
|
||||
// fts5_extension_function xFunction,
|
||||
// void (*xDestroy)(void*)
|
||||
// );
|
||||
|
||||
public synchronized native int xCreateFunction(@NotNull String name,
|
||||
@Nullable Object userData,
|
||||
@NotNull fts5_extension_function xFunction);
|
||||
|
||||
public int xCreateFunction(@NotNull String name,
|
||||
@NotNull fts5_extension_function xFunction){
|
||||
return xCreateFunction(name, null, xFunction);
|
||||
}
|
||||
|
||||
}
|
||||
+17
-7
@@ -11,13 +11,14 @@
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
package org.sqlite.jni.fts5;
|
||||
import org.sqlite.jni.sqlite3_context;
|
||||
import org.sqlite.jni.sqlite3_value;
|
||||
|
||||
/**
|
||||
JNI-level wrapper for C's fts5_extension_function type.
|
||||
|
||||
*/
|
||||
public abstract class fts5_extension_function {
|
||||
public interface fts5_extension_function {
|
||||
// typedef void (*fts5_extension_function)(
|
||||
// const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
|
||||
// Fts5Context *pFts, /* First arg to pass to pApi functions */
|
||||
@@ -30,8 +31,17 @@ public abstract class fts5_extension_function {
|
||||
The callback implementation, corresponding to the xFunction
|
||||
argument of C's fts5_api::xCreateFunction().
|
||||
*/
|
||||
public abstract void xFunction(Fts5ExtensionApi ext, Fts5Context fCx,
|
||||
sqlite3_context pCx, sqlite3_value argv[]);
|
||||
//! Optionally override
|
||||
public void xDestroy(){}
|
||||
void call(Fts5ExtensionApi ext, Fts5Context fCx,
|
||||
sqlite3_context pCx, sqlite3_value argv[]);
|
||||
/**
|
||||
Is called when this function is destroyed by sqlite3. Typically
|
||||
this function will be empty.
|
||||
*/
|
||||
void xDestroy();
|
||||
|
||||
public static abstract class Abstract implements fts5_extension_function {
|
||||
@Override public abstract void call(Fts5ExtensionApi ext, Fts5Context fCx,
|
||||
sqlite3_context pCx, sqlite3_value argv[]);
|
||||
@Override public void xDestroy(){}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -11,11 +11,11 @@
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
package org.sqlite.jni.fts5;
|
||||
import org.sqlite.jni.NativePointerHolder;
|
||||
import org.sqlite.jni.annotation.NotNull;
|
||||
|
||||
/**
|
||||
INCOMPLETE AND COMPLETELY UNTESTED.
|
||||
|
||||
A wrapper for communicating C-level (fts5_tokenizer*) instances with
|
||||
Java. These wrappers do not own their associated pointer, they
|
||||
simply provide a type-safe way to communicate it between Java and C
|
||||
@@ -30,7 +30,7 @@ public final class fts5_tokenizer extends NativePointerHolder<fts5_tokenizer> {
|
||||
|
||||
public native int xTokenize(@NotNull Fts5Tokenizer t, int tokFlags,
|
||||
@NotNull byte pText[],
|
||||
@NotNull Fts5.xTokenize_callback callback);
|
||||
@NotNull XTokenizeCallback callback);
|
||||
|
||||
|
||||
// int (*xTokenize)(Fts5Tokenizer*,
|
||||
@@ -1,14 +1,59 @@
|
||||
/**
|
||||
This package houses a JNI binding to the SQLite3 C API.
|
||||
|
||||
<p>The docs are in progress.
|
||||
<p>The primary interfaces are in {@link
|
||||
org.sqlite.jni.SQLite3Jni}.</p>
|
||||
|
||||
<p>The primary interfaces are in {@link org.sqlite.jni.SQLite3Jni}.
|
||||
<h1>API Goals and Requirements</h1>
|
||||
|
||||
<ul>
|
||||
|
||||
<li>A 1-to-1(-ish) mapping of the C API to Java via JNI, insofar
|
||||
as cross-language semantics allow for. A closely-related goal is
|
||||
that <a href='https://sqlite.org/c3ref/intro.html'>the C
|
||||
documentation</a> should be usable as-is, insofar as possible,
|
||||
for most of the JNI binding. As a rule, undocumented symbols in
|
||||
the Java interface behave as documented for their C API
|
||||
counterpart. Only semantic differences and Java-specific features
|
||||
are documented here.</li>
|
||||
|
||||
<li>Support Java as far back as version 8 (2014).</li>
|
||||
|
||||
<li>Environment-independent. Should work everywhere both Java and
|
||||
SQLite3 do.</li>
|
||||
|
||||
<li>No 3rd-party dependencies beyond the JDK. That includes no
|
||||
build-level dependencies for specific IDEs and toolchains. We
|
||||
welcome the addition of build files for arbitrary environments
|
||||
insofar as they neither interfere with each other nor become a
|
||||
maintenance burden for the sqlite developers.</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<h2>Non-Goals</h2>
|
||||
|
||||
<ul>
|
||||
|
||||
<li>Creation of high-level OO wrapper APIs. Clients are free to
|
||||
create them off of the C-style API.</li>
|
||||
|
||||
<li>Support for mixed-mode operation, where client code accesses
|
||||
SQLite both via the Java-side API and the C API via their own
|
||||
native code. In such cases, proxy functionalities (primarily
|
||||
callback handler wrappers of all sorts) may fail because the
|
||||
C-side use of the SQLite APIs will bypass those proxies.</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<h1>State of this API</h1>
|
||||
|
||||
<p>As of version 3.43, this software is in "tech preview" form. We
|
||||
tentatively plan to stamp it as stable with the 3.44 release.</p>
|
||||
|
||||
<h1>Threading Considerations</h1>
|
||||
|
||||
<p>This API is, if built with SQLITE_THREADSAFE set to 1 or 2,
|
||||
thread-safe, insofar as the C API guarantees, with some addenda:
|
||||
thread-safe, insofar as the C API guarantees, with some addenda:</p>
|
||||
|
||||
<ul>
|
||||
|
||||
@@ -34,10 +79,11 @@
|
||||
|
||||
<p>Any number of threads may, of course, create and use any number
|
||||
of database handles they wish. Care only needs to be taken when
|
||||
those handles or their associated resources cross threads, or...
|
||||
those handles or their associated resources cross threads, or...</p>
|
||||
|
||||
<p>When built with SQLITE_THREADSAFE=0 then no threading guarantees
|
||||
are provided and multi-threaded use of the library will provoke
|
||||
undefined behavior.
|
||||
undefined behavior.</p>
|
||||
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
@@ -19,19 +19,30 @@ package org.sqlite.jni;
|
||||
simply provide a type-safe way to communicate it between Java
|
||||
and C via JNI.
|
||||
*/
|
||||
public final class sqlite3 extends NativePointerHolder<sqlite3> {
|
||||
public final class sqlite3 extends NativePointerHolder<sqlite3>
|
||||
implements AutoCloseable {
|
||||
|
||||
// Only invoked from JNI
|
||||
private sqlite3(){}
|
||||
|
||||
public String toString(){
|
||||
long ptr = getNativePointer();
|
||||
final long ptr = getNativePointer();
|
||||
if( 0==ptr ){
|
||||
return sqlite3.class.getSimpleName()+"@null";
|
||||
}
|
||||
String fn = SQLite3Jni.sqlite3_db_filename(this, "main");
|
||||
final String fn = SQLite3Jni.sqlite3_db_filename(this, "main");
|
||||
return sqlite3.class.getSimpleName()
|
||||
+"@"+String.format("0x%08x",ptr)
|
||||
+"["+((null == fn) ? "<unnamed>" : fn)+"]"
|
||||
;
|
||||
}
|
||||
|
||||
@Override protected void finalize(){
|
||||
//System.out.println(this+".finalize()");
|
||||
SQLite3Jni.sqlite3_close_v2(this.clearNativePointer());
|
||||
}
|
||||
|
||||
@Override public void close(){
|
||||
SQLite3Jni.sqlite3_close_v2(this.clearNativePointer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
** 2023-09-03
|
||||
**
|
||||
** 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 is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
A wrapper for passing C-level (sqlite3_backup*) instances around in
|
||||
Java. These wrappers do not own their associated pointer, they
|
||||
simply provide a type-safe way to communicate it between Java and C
|
||||
via JNI.
|
||||
*/
|
||||
public final class sqlite3_backup extends NativePointerHolder<sqlite3_backup>
|
||||
implements AutoCloseable {
|
||||
// Only invoked from JNI.
|
||||
private sqlite3_backup(){}
|
||||
|
||||
@Override public void close(){
|
||||
SQLite3Jni.sqlite3_backup_finish(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
** 2023-09-03
|
||||
**
|
||||
** 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 is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
A wrapper for passing C-level (sqlite3_blob*) instances around in
|
||||
Java. These wrappers do not own their associated pointer, they
|
||||
simply provide a type-safe way to communicate it between Java and C
|
||||
via JNI.
|
||||
*/
|
||||
public final class sqlite3_blob extends NativePointerHolder<sqlite3_blob>
|
||||
implements AutoCloseable {
|
||||
// Only invoked from JNI.
|
||||
private sqlite3_blob(){}
|
||||
|
||||
@Override public void close(){
|
||||
SQLite3Jni.sqlite3_blob_close(this.clearNativePointer());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,7 +19,17 @@ package org.sqlite.jni;
|
||||
simply provide a type-safe way to communicate it between Java and C
|
||||
via JNI.
|
||||
*/
|
||||
public final class sqlite3_stmt extends NativePointerHolder<sqlite3_stmt> {
|
||||
public final class sqlite3_stmt extends NativePointerHolder<sqlite3_stmt>
|
||||
implements AutoCloseable {
|
||||
// Only invoked from JNI.
|
||||
private sqlite3_stmt(){}
|
||||
|
||||
//For as-yet-unknown reasons, this triggers a JVM crash.
|
||||
//@Override protected void finalize(){
|
||||
// SQLite3Jni.sqlite3_finalize(this.clearNativePointer());
|
||||
//}
|
||||
|
||||
@Override public void close(){
|
||||
SQLite3Jni.sqlite3_finalize(this.clearNativePointer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
** xMODULE_NAME: module-name
|
||||
** xREQUIRED_PROPERTIES: small fast reliable
|
||||
** xREQUIRED_PROPERTIES: RECURSIVE_TRIGGERS
|
||||
** xREQUIRED_PROPERTIES: TEMPSTORE_MEM TEMPSTORE_FILE
|
||||
** xREQUIRED_PROPERTIES: TEMPSTORE_FILE TEMPSTORE_MEM
|
||||
** xREQUIRED_PROPERTIES: AUTOVACUUM INCRVACUUM
|
||||
**
|
||||
*/
|
||||
--print starting up 😃
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
** SCRIPT_MODULE_NAME: fts5-sanity-checks
|
||||
** xREQUIRED_PROPERTIES: FTS5
|
||||
**
|
||||
*/
|
||||
|
||||
--testcase 1.0
|
||||
CREATE VIRTUAL TABLE email USING fts5(sender, title, body);
|
||||
insert into email values('fred','Help!','Dear Sir...');
|
||||
insert into email values('barney','Assistance','Dear Madam...');
|
||||
select * from email where email match 'assistance';
|
||||
--result barney Assistance {Dear Madam...}
|
||||
+5
-3
@@ -1256,20 +1256,23 @@ static int geopolyInit(
|
||||
/* Allocate the sqlite3_vtab structure */
|
||||
nDb = strlen(argv[1]);
|
||||
nName = strlen(argv[2]);
|
||||
pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2);
|
||||
pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName*2+8);
|
||||
if( !pRtree ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2);
|
||||
memset(pRtree, 0, sizeof(Rtree)+nDb+nName*2+8);
|
||||
pRtree->nBusy = 1;
|
||||
pRtree->base.pModule = &rtreeModule;
|
||||
pRtree->zDb = (char *)&pRtree[1];
|
||||
pRtree->zName = &pRtree->zDb[nDb+1];
|
||||
pRtree->zNodeName = &pRtree->zName[nName+1];
|
||||
pRtree->eCoordType = RTREE_COORD_REAL32;
|
||||
pRtree->nDim = 2;
|
||||
pRtree->nDim2 = 4;
|
||||
memcpy(pRtree->zDb, argv[1], nDb);
|
||||
memcpy(pRtree->zName, argv[2], nName);
|
||||
memcpy(pRtree->zNodeName, argv[2], nName);
|
||||
memcpy(&pRtree->zNodeName[nName], "_node", 6);
|
||||
|
||||
|
||||
/* Create/Connect to the underlying relational database schema. If
|
||||
@@ -1683,7 +1686,6 @@ static int geopolyUpdate(
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
int rc2;
|
||||
pRtree->iReinsertHeight = -1;
|
||||
rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0);
|
||||
rc2 = nodeRelease(pRtree, pLeaf);
|
||||
if( rc==SQLITE_OK ){
|
||||
|
||||
+93
-238
@@ -166,6 +166,7 @@ struct Rtree {
|
||||
int iDepth; /* Current depth of the r-tree structure */
|
||||
char *zDb; /* Name of database containing r-tree table */
|
||||
char *zName; /* Name of r-tree table */
|
||||
char *zNodeName; /* Name of the %_node table */
|
||||
u32 nBusy; /* Current number of users of this structure */
|
||||
i64 nRowEst; /* Estimated number of rows in this table */
|
||||
u32 nCursor; /* Number of open cursors */
|
||||
@@ -178,7 +179,6 @@ struct Rtree {
|
||||
** headed by the node (leaf nodes have RtreeNode.iNode==0).
|
||||
*/
|
||||
RtreeNode *pDeleted;
|
||||
int iReinsertHeight; /* Height of sub-trees Reinsert() has run on */
|
||||
|
||||
/* Blob I/O on xxx_node */
|
||||
sqlite3_blob *pNodeBlob;
|
||||
@@ -200,7 +200,7 @@ struct Rtree {
|
||||
/* Statement for writing to the "aux:" fields, if there are any */
|
||||
sqlite3_stmt *pWriteAux;
|
||||
|
||||
RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */
|
||||
RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */
|
||||
};
|
||||
|
||||
/* Possible values for Rtree.eCoordType: */
|
||||
@@ -475,15 +475,20 @@ struct RtreeMatchArg {
|
||||
** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined
|
||||
** at run-time.
|
||||
*/
|
||||
#ifndef SQLITE_BYTEORDER
|
||||
# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \
|
||||
#ifndef SQLITE_BYTEORDER /* Replicate changes at tag-20230904a */
|
||||
# if defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__
|
||||
# define SQLITE_BYTEORDER 4321
|
||||
# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__
|
||||
# define SQLITE_BYTEORDER 1234
|
||||
# elif defined(__BIG_ENDIAN__) && __BIG_ENDIAN__==1
|
||||
# define SQLITE_BYTEORDER 4321
|
||||
# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \
|
||||
defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
|
||||
defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
|
||||
defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64)
|
||||
# define SQLITE_BYTEORDER 1234
|
||||
# elif defined(sparc) || defined(__ppc__) || \
|
||||
defined(__ARMEB__) || defined(__AARCH64EB__)
|
||||
# define SQLITE_BYTEORDER 4321
|
||||
# define SQLITE_BYTEORDER 1234
|
||||
# elif defined(sparc) || defined(__ARMEB__) || defined(__AARCH64EB__)
|
||||
# define SQLITE_BYTEORDER 4321
|
||||
# else
|
||||
# define SQLITE_BYTEORDER 0
|
||||
# endif
|
||||
@@ -732,11 +737,9 @@ static int nodeAcquire(
|
||||
}
|
||||
}
|
||||
if( pRtree->pNodeBlob==0 ){
|
||||
char *zTab = sqlite3_mprintf("%s_node", pRtree->zName);
|
||||
if( zTab==0 ) return SQLITE_NOMEM;
|
||||
rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, zTab, "data", iNode, 0,
|
||||
rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, pRtree->zNodeName,
|
||||
"data", iNode, 0,
|
||||
&pRtree->pNodeBlob);
|
||||
sqlite3_free(zTab);
|
||||
}
|
||||
if( rc ){
|
||||
nodeBlobReset(pRtree);
|
||||
@@ -2077,8 +2080,12 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
|
||||
pIdxInfo->idxNum = 2;
|
||||
pIdxInfo->needToFreeIdxStr = 1;
|
||||
if( iIdx>0 && 0==(pIdxInfo->idxStr = sqlite3_mprintf("%s", zIdxStr)) ){
|
||||
return SQLITE_NOMEM;
|
||||
if( iIdx>0 ){
|
||||
pIdxInfo->idxStr = sqlite3_malloc( iIdx+1 );
|
||||
if( pIdxInfo->idxStr==0 ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
memcpy(pIdxInfo->idxStr, zIdxStr, iIdx+1);
|
||||
}
|
||||
|
||||
nRow = pRtree->nRowEst >> (iIdx/2);
|
||||
@@ -2157,31 +2164,22 @@ static void cellUnion(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){
|
||||
*/
|
||||
static int cellContains(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){
|
||||
int ii;
|
||||
int isInt = (pRtree->eCoordType==RTREE_COORD_INT32);
|
||||
for(ii=0; ii<pRtree->nDim2; ii+=2){
|
||||
RtreeCoord *a1 = &p1->aCoord[ii];
|
||||
RtreeCoord *a2 = &p2->aCoord[ii];
|
||||
if( (!isInt && (a2[0].f<a1[0].f || a2[1].f>a1[1].f))
|
||||
|| ( isInt && (a2[0].i<a1[0].i || a2[1].i>a1[1].i))
|
||||
){
|
||||
return 0;
|
||||
if( pRtree->eCoordType==RTREE_COORD_INT32 ){
|
||||
for(ii=0; ii<pRtree->nDim2; ii+=2){
|
||||
RtreeCoord *a1 = &p1->aCoord[ii];
|
||||
RtreeCoord *a2 = &p2->aCoord[ii];
|
||||
if( a2[0].i<a1[0].i || a2[1].i>a1[1].i ) return 0;
|
||||
}
|
||||
}else{
|
||||
for(ii=0; ii<pRtree->nDim2; ii+=2){
|
||||
RtreeCoord *a1 = &p1->aCoord[ii];
|
||||
RtreeCoord *a2 = &p2->aCoord[ii];
|
||||
if( a2[0].f<a1[0].f || a2[1].f>a1[1].f ) return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the amount cell p would grow by if it were unioned with pCell.
|
||||
*/
|
||||
static RtreeDValue cellGrowth(Rtree *pRtree, RtreeCell *p, RtreeCell *pCell){
|
||||
RtreeDValue area;
|
||||
RtreeCell cell;
|
||||
memcpy(&cell, p, sizeof(RtreeCell));
|
||||
area = cellArea(pRtree, &cell);
|
||||
cellUnion(pRtree, &cell, pCell);
|
||||
return (cellArea(pRtree, &cell)-area);
|
||||
}
|
||||
|
||||
static RtreeDValue cellOverlap(
|
||||
Rtree *pRtree,
|
||||
RtreeCell *p,
|
||||
@@ -2228,38 +2226,52 @@ static int ChooseLeaf(
|
||||
for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){
|
||||
int iCell;
|
||||
sqlite3_int64 iBest = 0;
|
||||
|
||||
int bFound = 0;
|
||||
RtreeDValue fMinGrowth = RTREE_ZERO;
|
||||
RtreeDValue fMinArea = RTREE_ZERO;
|
||||
|
||||
int nCell = NCELL(pNode);
|
||||
RtreeCell cell;
|
||||
RtreeNode *pChild = 0;
|
||||
|
||||
RtreeCell *aCell = 0;
|
||||
|
||||
/* Select the child node which will be enlarged the least if pCell
|
||||
** is inserted into it. Resolve ties by choosing the entry with
|
||||
** the smallest area.
|
||||
/* First check to see if there is are any cells in pNode that completely
|
||||
** contains pCell. If two or more cells in pNode completely contain pCell
|
||||
** then pick the smallest.
|
||||
*/
|
||||
for(iCell=0; iCell<nCell; iCell++){
|
||||
int bBest = 0;
|
||||
RtreeDValue growth;
|
||||
RtreeDValue area;
|
||||
RtreeCell cell;
|
||||
nodeGetCell(pRtree, pNode, iCell, &cell);
|
||||
growth = cellGrowth(pRtree, &cell, pCell);
|
||||
area = cellArea(pRtree, &cell);
|
||||
if( iCell==0||growth<fMinGrowth||(growth==fMinGrowth && area<fMinArea) ){
|
||||
bBest = 1;
|
||||
if( cellContains(pRtree, &cell, pCell) ){
|
||||
RtreeDValue area = cellArea(pRtree, &cell);
|
||||
if( bFound==0 || area<fMinArea ){
|
||||
iBest = cell.iRowid;
|
||||
fMinArea = area;
|
||||
bFound = 1;
|
||||
}
|
||||
}
|
||||
if( bBest ){
|
||||
fMinGrowth = growth;
|
||||
fMinArea = area;
|
||||
iBest = cell.iRowid;
|
||||
}
|
||||
if( !bFound ){
|
||||
/* No cells of pNode will completely contain pCell. So pick the
|
||||
** cell of pNode that grows by the least amount when pCell is added.
|
||||
** Break ties by selecting the smaller cell.
|
||||
*/
|
||||
for(iCell=0; iCell<nCell; iCell++){
|
||||
RtreeCell cell;
|
||||
RtreeDValue growth;
|
||||
RtreeDValue area;
|
||||
nodeGetCell(pRtree, pNode, iCell, &cell);
|
||||
area = cellArea(pRtree, &cell);
|
||||
cellUnion(pRtree, &cell, pCell);
|
||||
growth = cellArea(pRtree, &cell)-area;
|
||||
if( iCell==0
|
||||
|| growth<fMinGrowth
|
||||
|| (growth==fMinGrowth && area<fMinArea)
|
||||
){
|
||||
fMinGrowth = growth;
|
||||
fMinArea = area;
|
||||
iBest = cell.iRowid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_free(aCell);
|
||||
rc = nodeAcquire(pRtree, iBest, pNode, &pChild);
|
||||
nodeRelease(pRtree, pNode);
|
||||
pNode = pChild;
|
||||
@@ -2332,77 +2344,6 @@ static int parentWrite(Rtree *pRtree, sqlite3_int64 iNode, sqlite3_int64 iPar){
|
||||
static int rtreeInsertCell(Rtree *, RtreeNode *, RtreeCell *, int);
|
||||
|
||||
|
||||
/*
|
||||
** Arguments aIdx, aDistance and aSpare all point to arrays of size
|
||||
** nIdx. The aIdx array contains the set of integers from 0 to
|
||||
** (nIdx-1) in no particular order. This function sorts the values
|
||||
** in aIdx according to the indexed values in aDistance. For
|
||||
** example, assuming the inputs:
|
||||
**
|
||||
** aIdx = { 0, 1, 2, 3 }
|
||||
** aDistance = { 5.0, 2.0, 7.0, 6.0 }
|
||||
**
|
||||
** this function sets the aIdx array to contain:
|
||||
**
|
||||
** aIdx = { 0, 1, 2, 3 }
|
||||
**
|
||||
** The aSpare array is used as temporary working space by the
|
||||
** sorting algorithm.
|
||||
*/
|
||||
static void SortByDistance(
|
||||
int *aIdx,
|
||||
int nIdx,
|
||||
RtreeDValue *aDistance,
|
||||
int *aSpare
|
||||
){
|
||||
if( nIdx>1 ){
|
||||
int iLeft = 0;
|
||||
int iRight = 0;
|
||||
|
||||
int nLeft = nIdx/2;
|
||||
int nRight = nIdx-nLeft;
|
||||
int *aLeft = aIdx;
|
||||
int *aRight = &aIdx[nLeft];
|
||||
|
||||
SortByDistance(aLeft, nLeft, aDistance, aSpare);
|
||||
SortByDistance(aRight, nRight, aDistance, aSpare);
|
||||
|
||||
memcpy(aSpare, aLeft, sizeof(int)*nLeft);
|
||||
aLeft = aSpare;
|
||||
|
||||
while( iLeft<nLeft || iRight<nRight ){
|
||||
if( iLeft==nLeft ){
|
||||
aIdx[iLeft+iRight] = aRight[iRight];
|
||||
iRight++;
|
||||
}else if( iRight==nRight ){
|
||||
aIdx[iLeft+iRight] = aLeft[iLeft];
|
||||
iLeft++;
|
||||
}else{
|
||||
RtreeDValue fLeft = aDistance[aLeft[iLeft]];
|
||||
RtreeDValue fRight = aDistance[aRight[iRight]];
|
||||
if( fLeft<fRight ){
|
||||
aIdx[iLeft+iRight] = aLeft[iLeft];
|
||||
iLeft++;
|
||||
}else{
|
||||
aIdx[iLeft+iRight] = aRight[iRight];
|
||||
iRight++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* Check that the sort worked */
|
||||
{
|
||||
int jj;
|
||||
for(jj=1; jj<nIdx; jj++){
|
||||
RtreeDValue left = aDistance[aIdx[jj-1]];
|
||||
RtreeDValue right = aDistance[aIdx[jj]];
|
||||
assert( left<=right );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Arguments aIdx, aCell and aSpare all point to arrays of size
|
||||
@@ -2886,108 +2827,7 @@ static int deleteCell(Rtree *pRtree, RtreeNode *pNode, int iCell, int iHeight){
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int Reinsert(
|
||||
Rtree *pRtree,
|
||||
RtreeNode *pNode,
|
||||
RtreeCell *pCell,
|
||||
int iHeight
|
||||
){
|
||||
int *aOrder;
|
||||
int *aSpare;
|
||||
RtreeCell *aCell;
|
||||
RtreeDValue *aDistance;
|
||||
int nCell;
|
||||
RtreeDValue aCenterCoord[RTREE_MAX_DIMENSIONS];
|
||||
int iDim;
|
||||
int ii;
|
||||
int rc = SQLITE_OK;
|
||||
int n;
|
||||
|
||||
memset(aCenterCoord, 0, sizeof(RtreeDValue)*RTREE_MAX_DIMENSIONS);
|
||||
|
||||
nCell = NCELL(pNode)+1;
|
||||
n = (nCell+1)&(~1);
|
||||
|
||||
/* Allocate the buffers used by this operation. The allocation is
|
||||
** relinquished before this function returns.
|
||||
*/
|
||||
aCell = (RtreeCell *)sqlite3_malloc64(n * (
|
||||
sizeof(RtreeCell) + /* aCell array */
|
||||
sizeof(int) + /* aOrder array */
|
||||
sizeof(int) + /* aSpare array */
|
||||
sizeof(RtreeDValue) /* aDistance array */
|
||||
));
|
||||
if( !aCell ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
aOrder = (int *)&aCell[n];
|
||||
aSpare = (int *)&aOrder[n];
|
||||
aDistance = (RtreeDValue *)&aSpare[n];
|
||||
|
||||
for(ii=0; ii<nCell; ii++){
|
||||
if( ii==(nCell-1) ){
|
||||
memcpy(&aCell[ii], pCell, sizeof(RtreeCell));
|
||||
}else{
|
||||
nodeGetCell(pRtree, pNode, ii, &aCell[ii]);
|
||||
}
|
||||
aOrder[ii] = ii;
|
||||
for(iDim=0; iDim<pRtree->nDim; iDim++){
|
||||
aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2]);
|
||||
aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2+1]);
|
||||
}
|
||||
}
|
||||
for(iDim=0; iDim<pRtree->nDim; iDim++){
|
||||
aCenterCoord[iDim] = (aCenterCoord[iDim]/(nCell*(RtreeDValue)2));
|
||||
}
|
||||
|
||||
for(ii=0; ii<nCell; ii++){
|
||||
aDistance[ii] = RTREE_ZERO;
|
||||
for(iDim=0; iDim<pRtree->nDim; iDim++){
|
||||
RtreeDValue coord = (DCOORD(aCell[ii].aCoord[iDim*2+1]) -
|
||||
DCOORD(aCell[ii].aCoord[iDim*2]));
|
||||
aDistance[ii] += (coord-aCenterCoord[iDim])*(coord-aCenterCoord[iDim]);
|
||||
}
|
||||
}
|
||||
|
||||
SortByDistance(aOrder, nCell, aDistance, aSpare);
|
||||
nodeZero(pRtree, pNode);
|
||||
|
||||
for(ii=0; rc==SQLITE_OK && ii<(nCell-(RTREE_MINCELLS(pRtree)+1)); ii++){
|
||||
RtreeCell *p = &aCell[aOrder[ii]];
|
||||
nodeInsertCell(pRtree, pNode, p);
|
||||
if( p->iRowid==pCell->iRowid ){
|
||||
if( iHeight==0 ){
|
||||
rc = rowidWrite(pRtree, p->iRowid, pNode->iNode);
|
||||
}else{
|
||||
rc = parentWrite(pRtree, p->iRowid, pNode->iNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fixBoundingBox(pRtree, pNode);
|
||||
}
|
||||
for(; rc==SQLITE_OK && ii<nCell; ii++){
|
||||
/* Find a node to store this cell in. pNode->iNode currently contains
|
||||
** the height of the sub-tree headed by the cell.
|
||||
*/
|
||||
RtreeNode *pInsert;
|
||||
RtreeCell *p = &aCell[aOrder[ii]];
|
||||
rc = ChooseLeaf(pRtree, p, iHeight, &pInsert);
|
||||
if( rc==SQLITE_OK ){
|
||||
int rc2;
|
||||
rc = rtreeInsertCell(pRtree, pInsert, p, iHeight);
|
||||
rc2 = nodeRelease(pRtree, pInsert);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = rc2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_free(aCell);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Insert cell pCell into node pNode. Node pNode is the head of a
|
||||
** subtree iHeight high (leaf nodes have iHeight==0).
|
||||
@@ -3008,12 +2848,7 @@ static int rtreeInsertCell(
|
||||
}
|
||||
}
|
||||
if( nodeInsertCell(pRtree, pNode, pCell) ){
|
||||
if( iHeight<=pRtree->iReinsertHeight || pNode->iNode==1){
|
||||
rc = SplitNode(pRtree, pNode, pCell, iHeight);
|
||||
}else{
|
||||
pRtree->iReinsertHeight = iHeight;
|
||||
rc = Reinsert(pRtree, pNode, pCell, iHeight);
|
||||
}
|
||||
rc = SplitNode(pRtree, pNode, pCell, iHeight);
|
||||
}else{
|
||||
rc = AdjustTree(pRtree, pNode, pCell);
|
||||
if( ALWAYS(rc==SQLITE_OK) ){
|
||||
@@ -3356,7 +3191,6 @@ static int rtreeUpdate(
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
int rc2;
|
||||
pRtree->iReinsertHeight = -1;
|
||||
rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0);
|
||||
rc2 = nodeRelease(pRtree, pLeaf);
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -3497,8 +3331,11 @@ static int rtreeShadowName(const char *zName){
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Forward declaration */
|
||||
static int rtreeIntegrity(sqlite3_vtab*, char**);
|
||||
|
||||
static sqlite3_module rtreeModule = {
|
||||
3, /* iVersion */
|
||||
4, /* iVersion */
|
||||
rtreeCreate, /* xCreate - create a table */
|
||||
rtreeConnect, /* xConnect - connect to an existing table */
|
||||
rtreeBestIndex, /* xBestIndex - Determine search strategy */
|
||||
@@ -3521,7 +3358,8 @@ static sqlite3_module rtreeModule = {
|
||||
rtreeSavepoint, /* xSavepoint */
|
||||
0, /* xRelease */
|
||||
0, /* xRollbackTo */
|
||||
rtreeShadowName /* xShadowName */
|
||||
rtreeShadowName, /* xShadowName */
|
||||
rtreeIntegrity /* xIntegrity */
|
||||
};
|
||||
|
||||
static int rtreeSqlInit(
|
||||
@@ -3781,18 +3619,21 @@ static int rtreeInit(
|
||||
/* Allocate the sqlite3_vtab structure */
|
||||
nDb = (int)strlen(argv[1]);
|
||||
nName = (int)strlen(argv[2]);
|
||||
pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2);
|
||||
pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName*2+8);
|
||||
if( !pRtree ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2);
|
||||
memset(pRtree, 0, sizeof(Rtree)+nDb+nName*2+8);
|
||||
pRtree->nBusy = 1;
|
||||
pRtree->base.pModule = &rtreeModule;
|
||||
pRtree->zDb = (char *)&pRtree[1];
|
||||
pRtree->zName = &pRtree->zDb[nDb+1];
|
||||
pRtree->zNodeName = &pRtree->zName[nName+1];
|
||||
pRtree->eCoordType = (u8)eCoordType;
|
||||
memcpy(pRtree->zDb, argv[1], nDb);
|
||||
memcpy(pRtree->zName, argv[2], nName);
|
||||
memcpy(pRtree->zNodeName, argv[2], nName);
|
||||
memcpy(&pRtree->zNodeName[nName], "_node", 6);
|
||||
|
||||
|
||||
/* Create/Connect to the underlying relational database schema. If
|
||||
@@ -4355,6 +4196,20 @@ static int rtreeCheckTable(
|
||||
return check.rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of the xIntegrity method for Rtree.
|
||||
*/
|
||||
static int rtreeIntegrity(sqlite3_vtab *pVtab, char **pzErr){
|
||||
Rtree *pRtree = (Rtree*)pVtab;
|
||||
int rc;
|
||||
rc = rtreeCheckTable(pRtree->db, pRtree->zDb, pRtree->zName, pzErr);
|
||||
if( rc==SQLITE_OK && *pzErr ){
|
||||
*pzErr = sqlite3_mprintf("In RTree %s.%s:\n%z",
|
||||
pRtree->zDb, pRtree->zName, *pzErr);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Usage:
|
||||
**
|
||||
|
||||
@@ -75,7 +75,7 @@ do_test rtree8-1.2.2 { nested_select 1 } {51}
|
||||
#
|
||||
populate_t1 1500
|
||||
do_rtree_integrity_test rtree8-1.3.0 t1
|
||||
do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {164}
|
||||
do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {183}
|
||||
do_test rtree8-1.3.2 {
|
||||
set rowids [execsql {SELECT min(rowid) FROM t1_rowid GROUP BY nodeno}]
|
||||
set stmt_list [list]
|
||||
|
||||
@@ -113,7 +113,7 @@ do_execsql_test rtreeA-1.1.1 {
|
||||
SELECT rtreecheck('main', 't1')
|
||||
} {{Node 1 missing from database
|
||||
Wrong number of entries in %_rowid table - expected 0, actual 500
|
||||
Wrong number of entries in %_parent table - expected 0, actual 23}}
|
||||
Wrong number of entries in %_parent table - expected 0, actual 25}}
|
||||
|
||||
do_execsql_test rtreeA-1.2.0 { DROP TABLE t1_node } {}
|
||||
do_corruption_tests rtreeA-1.2 -error "database disk image is malformed" {
|
||||
@@ -191,7 +191,7 @@ do_execsql_test rtreeA-3.3.3.4 {
|
||||
SELECT rtreecheck('main', 't1')
|
||||
} {{Rtree depth out of range (65535)
|
||||
Wrong number of entries in %_rowid table - expected 0, actual 499
|
||||
Wrong number of entries in %_parent table - expected 0, actual 23}}
|
||||
Wrong number of entries in %_parent table - expected 0, actual 25}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Set the "number of entries" field on some nodes incorrectly.
|
||||
|
||||
@@ -192,6 +192,6 @@ proc rtree_treedump {db zTab} {
|
||||
}
|
||||
|
||||
proc do_rtree_integrity_test {tn tbl} {
|
||||
uplevel [list do_execsql_test $tn "SELECT rtreecheck('$tbl')" ok]
|
||||
uplevel [list do_execsql_test $tn.1 "SELECT rtreecheck('$tbl')" ok]
|
||||
uplevel [list do_execsql_test $tn.2 "PRAGMA integrity_check" ok]
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,11 @@ do_execsql_test 2.3 {
|
||||
SELECT rtreecheck('r1')
|
||||
} {{Dimension 0 of cell 0 on node 1 is corrupt
|
||||
Dimension 1 of cell 3 on node 1 is corrupt}}
|
||||
do_execsql_test 2.3b {
|
||||
PRAGMA integrity_check;
|
||||
} {{In RTree main.r1:
|
||||
Dimension 0 of cell 0 on node 1 is corrupt
|
||||
Dimension 1 of cell 3 on node 1 is corrupt}}
|
||||
|
||||
setup_simple_db
|
||||
do_execsql_test 2.4 {
|
||||
@@ -85,12 +90,21 @@ do_execsql_test 2.4 {
|
||||
SELECT rtreecheck('r1')
|
||||
} {{Mapping (3 -> 1) missing from %_rowid table
|
||||
Wrong number of entries in %_rowid table - expected 5, actual 4}}
|
||||
do_execsql_test 2.4b {
|
||||
PRAGMA integrity_check
|
||||
} {{In RTree main.r1:
|
||||
Mapping (3 -> 1) missing from %_rowid table
|
||||
Wrong number of entries in %_rowid table - expected 5, actual 4}}
|
||||
|
||||
setup_simple_db
|
||||
do_execsql_test 2.5 {
|
||||
UPDATE r1_rowid SET nodeno=2 WHERE rowid=3;
|
||||
SELECT rtreecheck('r1')
|
||||
} {{Found (3 -> 2) in %_rowid table, expected (3 -> 1)}}
|
||||
do_execsql_test 2.5b {
|
||||
PRAGMA integrity_check
|
||||
} {{In RTree main.r1:
|
||||
Found (3 -> 2) in %_rowid table, expected (3 -> 1)}}
|
||||
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
@@ -104,14 +118,16 @@ do_execsql_test 3.0 {
|
||||
INSERT INTO r1 VALUES(7, 5, 0x00000080);
|
||||
INSERT INTO r1 VALUES(8, 5, 0x40490fdb);
|
||||
INSERT INTO r1 VALUES(9, 0x7f800000, 0x7f900000);
|
||||
SELECT rtreecheck('r1')
|
||||
} {ok}
|
||||
SELECT rtreecheck('r1');
|
||||
PRAGMA integrity_check;
|
||||
} {ok ok}
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
CREATE VIRTUAL TABLE r2 USING rtree_i32(id, x1, x2);
|
||||
INSERT INTO r2 VALUES(2, -1*(1<<31), -1*(1<<31)+5);
|
||||
SELECT rtreecheck('r2')
|
||||
} {ok}
|
||||
SELECT rtreecheck('r2');
|
||||
PRAGMA integrity_check;
|
||||
} {ok ok}
|
||||
|
||||
sqlite3_db_config db DEFENSIVE 0
|
||||
do_execsql_test 3.2 {
|
||||
@@ -125,6 +141,11 @@ do_execsql_test 3.3 {
|
||||
UPDATE r2_node SET data = X'00001234';
|
||||
SELECT rtreecheck('r2')!='ok';
|
||||
} {1}
|
||||
do_execsql_test 3.4 {
|
||||
PRAGMA integrity_check;
|
||||
} {{In RTree main.r2:
|
||||
Node 1 is too small for cell count of 4660 (4 bytes)
|
||||
Wrong number of entries in %_rowid table - expected 0, actual 1}}
|
||||
|
||||
do_execsql_test 4.0 {
|
||||
CREATE TABLE notanrtree(i);
|
||||
@@ -181,4 +202,3 @@ if {[permutation]=="inmemory_journal"} {
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
+15
-5
@@ -601,11 +601,21 @@ do_execsql_test 2.5.2 {
|
||||
SELECT A.id FROM demo_index AS A, demo_index AS B
|
||||
WHERE A.maxX>=B.minX AND A.minX<=B.maxX
|
||||
AND A.maxY>=B.minY AND A.minY<=B.maxY
|
||||
AND B.id=28269;
|
||||
AND B.id=28269 ORDER BY +A.id;
|
||||
} {
|
||||
28293 28216 28322 28286 28269
|
||||
28215 28336 28262 28291 28320
|
||||
28313 28298 28287
|
||||
28215
|
||||
28216
|
||||
28262
|
||||
28269
|
||||
28286
|
||||
28287
|
||||
28291
|
||||
28293
|
||||
28298
|
||||
28313
|
||||
28320
|
||||
28322
|
||||
28336
|
||||
}
|
||||
|
||||
# EVIDENCE-OF: R-02723-34107 Note that it is not necessary for all
|
||||
@@ -1575,7 +1585,7 @@ execsql BEGIN
|
||||
do_test 3.6 {
|
||||
execsql { INSERT INTO rt2_parent VALUES(1000, 1000) }
|
||||
execsql { SELECT rtreecheck('rt2') }
|
||||
} {{Wrong number of entries in %_parent table - expected 9, actual 10}}
|
||||
} {{Wrong number of entries in %_parent table - expected 10, actual 11}}
|
||||
execsql ROLLBACK
|
||||
|
||||
|
||||
|
||||
@@ -1043,7 +1043,7 @@ do_test rtreefuzz001-500 {
|
||||
| end crash-2e81f5dce5cbd4.db}]
|
||||
execsql { PRAGMA writable_schema = 1;}
|
||||
catchsql {UPDATE t1 SET ex= ex ISNULL}
|
||||
} {1 {database disk image is malformed}}
|
||||
} {0 {}}
|
||||
|
||||
do_test rtreefuzz001-600 {
|
||||
sqlite3 db {}
|
||||
|
||||
@@ -3236,15 +3236,19 @@ static int sessionReadRecord(
|
||||
}
|
||||
}
|
||||
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
|
||||
sqlite3_int64 v = sessionGetI64(aVal);
|
||||
if( eType==SQLITE_INTEGER ){
|
||||
sqlite3VdbeMemSetInt64(apOut[i], v);
|
||||
if( (pIn->nData-pIn->iNext)<8 ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
}else{
|
||||
double d;
|
||||
memcpy(&d, &v, 8);
|
||||
sqlite3VdbeMemSetDouble(apOut[i], d);
|
||||
sqlite3_int64 v = sessionGetI64(aVal);
|
||||
if( eType==SQLITE_INTEGER ){
|
||||
sqlite3VdbeMemSetInt64(apOut[i], v);
|
||||
}else{
|
||||
double d;
|
||||
memcpy(&d, &v, 8);
|
||||
sqlite3VdbeMemSetDouble(apOut[i], d);
|
||||
}
|
||||
pIn->iNext += 8;
|
||||
}
|
||||
pIn->iNext += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -838,11 +838,11 @@ sqlite3-worker1-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-bundler-frien
|
||||
sqlite3-worker1-promiser-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-promiser-bundler-friendly.js
|
||||
$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1.js)))
|
||||
$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.js),\
|
||||
$(c-pp.D.bundler-friendly)))
|
||||
$(c-pp.D.sqlite3-bundler-friendly)))
|
||||
$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.js)))
|
||||
$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),\
|
||||
$(sqlite3-worker1-promiser-bundler-friendly.js),\
|
||||
$(c-pp.D.bundler-friendly)))
|
||||
$(c-pp.D.sqlite3-bundler-friendly)))
|
||||
$(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.js) \
|
||||
$(sqlite3-worker1-promiser-bundler-friendly.js)
|
||||
$(sqlite3.js) $(sqlite3.mjs): $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/this/is/make
|
||||
#
|
||||
# This makefile compiles SQLTester test files into something
|
||||
# we can readily import into JavaScript.
|
||||
all:
|
||||
|
||||
SHELL := $(shell which bash 2>/dev/null)
|
||||
MAKEFILE := $(lastword $(MAKEFILE_LIST))
|
||||
CLEAN_FILES :=
|
||||
DISTCLEAN_FILES := ./--dummy-- *~
|
||||
|
||||
test-list.mjs := test-list.mjs
|
||||
test-list.mjs.gz := $(test-list.mjs).gz
|
||||
CLEAN_FILES += $(test-list.mjs)
|
||||
|
||||
tests.dir := $(firstword $(wildcard tests ../../jni/src/tests))
|
||||
$(info test script dir=$(tests.dir))
|
||||
|
||||
tests.all := $(wildcard $(tests.dir)/*.test)
|
||||
|
||||
bin.touint8array := ./touint8array
|
||||
$(bin.touint8array): $(bin.touint8array).c $(MAKEFILE)
|
||||
$(CC) -o $@ $<
|
||||
CLEAN_FILES += $(bin.touint8array)
|
||||
|
||||
ifneq (,$(tests.all))
|
||||
$(test-list.mjs): $(bin.touint8array) $(tests.all) $(MAKEFILE)
|
||||
@{\
|
||||
echo 'export default ['; \
|
||||
sep=''; \
|
||||
for f in $(sort $(tests.all)); do \
|
||||
echo -en $$sep'{"name": "'$${f##*/}'", "content":'; \
|
||||
$(bin.touint8array) < $$f; \
|
||||
echo -n '}'; \
|
||||
sep=',\n'; \
|
||||
done; \
|
||||
echo '];'; \
|
||||
} > $@
|
||||
@echo "Created $@"
|
||||
$(test-list.mjs.gz): $(test-list.mjs)
|
||||
gzip -c $< > $@
|
||||
CLEAN_FILES += $(test-list.mjs.gz)
|
||||
all: $(test-list.mjs.gz)
|
||||
else
|
||||
@echo "Cannot build $(test-list.mjs) for lack of input test files."; \
|
||||
echo "Symlink ./tests to a directory containing SQLTester-format "; \
|
||||
echo "test scripts named *.test, then try again"; \
|
||||
exit 1
|
||||
endif
|
||||
|
||||
.PHONY: clean distclean
|
||||
clean:
|
||||
-rm -f $(CLEAN_FILES)
|
||||
distclean: clean
|
||||
-rm -f $(DISTCLEAN_FILES)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
** 2023-08-29
|
||||
**
|
||||
** 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 a test application for SQLTester.js.
|
||||
*/
|
||||
import {default as ns} from './SQLTester.mjs';
|
||||
import {default as allTests} from './test-list.mjs';
|
||||
|
||||
globalThis.sqlite3 = ns.sqlite3;
|
||||
const log = function f(...args){
|
||||
console.log('SQLTester.run:',...args);
|
||||
return f;
|
||||
};
|
||||
|
||||
const out = function f(...args){ return f.outer.out(...args) };
|
||||
out.outer = new ns.Outer();
|
||||
out.outer.getOutputPrefix = ()=>'SQLTester.run: ';
|
||||
const outln = (...args)=>{ return out.outer.outln(...args) };
|
||||
|
||||
const affirm = function(expr, msg){
|
||||
if( !expr ){
|
||||
throw new Error(arguments[1]
|
||||
? ("Assertion failed: "+arguments[1])
|
||||
: "Assertion failed");
|
||||
}
|
||||
}
|
||||
|
||||
let ts = new ns.TestScript('/foo.test',`
|
||||
/*
|
||||
** This is a comment. There are many like it but this one is mine.
|
||||
**
|
||||
** SCRIPT_MODULE_NAME: sanity-check-0
|
||||
** xMIXED_MODULE_NAME: mixed-module
|
||||
** xMODULE_NAME: module-name
|
||||
** xREQUIRED_PROPERTIES: small fast reliable
|
||||
** xREQUIRED_PROPERTIES: RECURSIVE_TRIGGERS
|
||||
** xREQUIRED_PROPERTIES: TEMPSTORE_FILE TEMPSTORE_MEM
|
||||
** xREQUIRED_PROPERTIES: AUTOVACUUM INCRVACUUM
|
||||
**
|
||||
*/
|
||||
/* --verbosity 3 */
|
||||
/* ---must-fail */
|
||||
/* # must fail */
|
||||
/* --verbosity 0 */
|
||||
--print Hello, world.
|
||||
--close all
|
||||
--oom
|
||||
--db 0
|
||||
--new my.db
|
||||
--null zilch
|
||||
--testcase 1.0
|
||||
SELECT 1, null;
|
||||
--result 1 zilch
|
||||
--glob *zil*
|
||||
--notglob *ZIL*
|
||||
SELECT 1, 2;
|
||||
intentional error;
|
||||
--run
|
||||
/* ---intentional-failure */
|
||||
--testcase json-1
|
||||
SELECT json_array(1,2,3)
|
||||
--json [1,2,3]
|
||||
--testcase tableresult-1
|
||||
select 1, 'a';
|
||||
select 2, 'b';
|
||||
--tableresult
|
||||
# [a-z]
|
||||
2 b
|
||||
--end
|
||||
--testcase json-block-1
|
||||
select json_array(1,2,3);
|
||||
select json_object('a',1,'b',2);
|
||||
--json-block
|
||||
[1,2,3]
|
||||
{"a":1,"b":2}
|
||||
--end
|
||||
--testcase col-names-on
|
||||
--column-names 1
|
||||
select 1 as 'a', 2 as 'b';
|
||||
--result a 1 b 2
|
||||
--testcase col-names-off
|
||||
--column-names 0
|
||||
select 1 as 'a', 2 as 'b';
|
||||
--result 1 2
|
||||
--close
|
||||
--print Until next time
|
||||
`);
|
||||
|
||||
const sqt = new ns.SQLTester()
|
||||
.setLogger(console.log.bind(console))
|
||||
.verbosity(1)
|
||||
.addTestScript(ts);
|
||||
sqt.outer().outputPrefix('');
|
||||
|
||||
const runTests = function(){
|
||||
try{
|
||||
if( 0 ){
|
||||
affirm( !sqt.getCurrentDb(), 'sqt.getCurrentDb()' );
|
||||
sqt.openDb('/foo.db', true);
|
||||
affirm( !!sqt.getCurrentDb(),'sqt.getCurrentDb()' );
|
||||
affirm( 'zilch' !== sqt.nullValue() );
|
||||
ts.run(sqt);
|
||||
affirm( 'zilch' === sqt.nullValue() );
|
||||
sqt.addTestScript(ts);
|
||||
sqt.runTests();
|
||||
}else{
|
||||
for(const t of allTests){
|
||||
sqt.addTestScript( new ns.TestScript(t) );
|
||||
}
|
||||
allTests.length = 0;
|
||||
sqt.runTests();
|
||||
}
|
||||
}finally{
|
||||
//log( "Metrics:", sqt.metrics );
|
||||
sqt.reset();
|
||||
}
|
||||
};
|
||||
|
||||
if( globalThis.WorkerGlobalScope ){
|
||||
const wPost = (type,payload)=>globalThis.postMessage({type, payload});
|
||||
globalThis.onmessage = function({data}){
|
||||
switch(data.type){
|
||||
case 'run-tests':{
|
||||
try{ runTests(); }
|
||||
finally{ wPost('tests-end', sqt.metrics); }
|
||||
break;
|
||||
}
|
||||
default:
|
||||
log("unhandled onmessage: ",data);
|
||||
break;
|
||||
}
|
||||
};
|
||||
sqt.setLogger((msg)=>{
|
||||
wPost('stdout', {message: msg});
|
||||
});
|
||||
wPost('is-ready');
|
||||
//globalThis.onmessage({data:{type:'run-tests'}});
|
||||
}else{
|
||||
runTests();
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
|
||||
<!--link rel="stylesheet" href="../common/emscripten.css"/-->
|
||||
<link rel="stylesheet" href="../common/testing.css"/>
|
||||
<title>SQLTester</title>
|
||||
</head>
|
||||
<style>
|
||||
fieldset {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-right: 1em;
|
||||
}
|
||||
fieldset > :not(.legend) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-right: 1em;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<h1>SQLTester for JS/WASM</h1>
|
||||
<p>This app reads in a build-time-defined set of SQLTester test
|
||||
scripts and runs them through the test suite.
|
||||
</p>
|
||||
<fieldset>
|
||||
<legend>Options</legend>
|
||||
<span class='input-wrapper'>
|
||||
<input type='checkbox' id='cb-log-reverse' checked>
|
||||
<label for='cb-log-reverse'>Reverse log order?</label>
|
||||
</span>
|
||||
<input type='button' id='btn-run-tests' value='Run tests'/>
|
||||
</fieldset>
|
||||
<div id='test-output'>Test output will go here.</div>
|
||||
<!--script src='SQLTester.run.mjs' type='module'></script-->
|
||||
<script>
|
||||
(async function(){
|
||||
const W = new Worker('SQLTester.run.mjs',{
|
||||
type: 'module'
|
||||
});
|
||||
const wPost = (type,payload)=>W.postMessage({type,payload});
|
||||
const mapToString = (v)=>{
|
||||
switch(typeof v){
|
||||
case 'string': return v;
|
||||
case 'number': case 'boolean':
|
||||
case 'undefined': case 'bigint':
|
||||
return ''+v;
|
||||
default: break;
|
||||
}
|
||||
if(null===v) return 'null';
|
||||
if(v instanceof Error){
|
||||
v = {
|
||||
message: v.message,
|
||||
stack: v.stack,
|
||||
errorClass: v.name
|
||||
};
|
||||
}
|
||||
return JSON.stringify(v,undefined,2);
|
||||
};
|
||||
const normalizeArgs = (args)=>args.map(mapToString);
|
||||
const logTarget = document.querySelector('#test-output');
|
||||
const logClass = function(cssClass,...args){
|
||||
const ln = document.createElement('div');
|
||||
if(cssClass){
|
||||
for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){
|
||||
ln.classList.add(c);
|
||||
}
|
||||
}
|
||||
ln.append(document.createTextNode(normalizeArgs(args).join(' ')));
|
||||
logTarget.append(ln);
|
||||
};
|
||||
{
|
||||
const cbReverse = document.querySelector('#cb-log-reverse');
|
||||
const cbReverseKey = 'SQLTester:cb-log-reverse';
|
||||
const cbReverseIt = ()=>{
|
||||
logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse');
|
||||
};
|
||||
cbReverse.addEventListener('change', cbReverseIt, true);
|
||||
cbReverseIt();
|
||||
}
|
||||
|
||||
const btnRun = document.querySelector('#btn-run-tests');
|
||||
const runTests = ()=>{
|
||||
btnRun.setAttribute('disabled','disabled');
|
||||
wPost('run-tests');
|
||||
logTarget.innerText = 'Running tests...';
|
||||
}
|
||||
btnRun.addEventListener('click', runTests);
|
||||
const log2 = function f(...args){
|
||||
logClass('', ...args);
|
||||
return f;
|
||||
};
|
||||
const log = function f(...args){
|
||||
logClass('','index.html:',...args);
|
||||
return f;
|
||||
};
|
||||
|
||||
const timerId = setTimeout( ()=>{
|
||||
logClass('error',"The SQLTester module is taking an unusually ",
|
||||
"long time to load. More information may be available",
|
||||
"in the dev console.");
|
||||
}, 3000 /* assuming localhost */ );
|
||||
|
||||
W.onmessage = function({data}){
|
||||
switch(data.type){
|
||||
case 'stdout': log2(data.payload.message); break;
|
||||
case 'tests-end':
|
||||
btnRun.removeAttribute('disabled');
|
||||
delete data.payload.nTest;
|
||||
log("test results:",data.payload);
|
||||
break;
|
||||
case 'is-ready':
|
||||
clearTimeout(timerId);
|
||||
runTests(); break;
|
||||
default:
|
||||
log("unhandled onmessage",data);
|
||||
break;
|
||||
}
|
||||
};
|
||||
//runTests()
|
||||
/* Inexplicably, */
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
** 2023-08-29
|
||||
**
|
||||
** 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 a tool for writing out the contents of stdin as
|
||||
** a comma-separated list of numbers, one per byte.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
int main(int argc, char const **argv){
|
||||
int i;
|
||||
int rc = 0, colWidth = 30;
|
||||
int ch;
|
||||
printf("[");
|
||||
for( i=0; EOF!=(ch = fgetc(stdin)); ++i ){
|
||||
if( 0!=i ) printf(",");
|
||||
if( i && 0==(i%colWidth) ) puts("");
|
||||
printf("%d",ch);
|
||||
}
|
||||
printf("]");
|
||||
return rc;
|
||||
}
|
||||
@@ -1135,7 +1135,23 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
|
||||
return 1===n
|
||||
? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptrSizeof)
|
||||
: wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptrSizeof);
|
||||
},
|
||||
|
||||
/**
|
||||
Records the current pstack position, calls the given function,
|
||||
passing it the sqlite3 object, then restores the pstack
|
||||
regardless of whether the function throws. Returns the result
|
||||
of the call or propagates an exception on error.
|
||||
|
||||
Added in 3.44.
|
||||
*/
|
||||
call: function(f){
|
||||
const stackPos = wasm.pstack.pointer;
|
||||
try{ return f(sqlite3) } finally{
|
||||
wasm.pstack.restore(stackPos);
|
||||
}
|
||||
}
|
||||
|
||||
})/*wasm.pstack*/;
|
||||
Object.defineProperties(wasm.pstack, {
|
||||
/**
|
||||
@@ -1543,6 +1559,26 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Converts SQL input from a variety of convenient formats
|
||||
to plain strings.
|
||||
|
||||
If v is a string, it is returned as-is. If it is-a Array, its
|
||||
join("") result is returned. If is is a Uint8Array, Int8Array,
|
||||
or ArrayBuffer, it is assumed to hold UTF-8-encoded text and is
|
||||
decoded to a string. If it looks like a WASM pointer,
|
||||
wasm.cstrToJs(sql) is returned. Else undefined is returned.
|
||||
|
||||
Added in 3.44
|
||||
*/
|
||||
capi.sqlite3_js_sql_to_string = (sql)=>{
|
||||
if('string' === typeof sql){
|
||||
return sql;
|
||||
}
|
||||
const x = flexibleString(v);
|
||||
return x===v ? undefined : x;
|
||||
}
|
||||
|
||||
if( util.isUIThread() ){
|
||||
/* Features specific to the main window thread... */
|
||||
|
||||
|
||||
@@ -895,7 +895,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
sah.read( header, {at: 0} );
|
||||
util.affirmDbHeader( header );
|
||||
}
|
||||
sah.write(new Uint8Array(2), {
|
||||
sah.write(new Uint8Array([1,1]), {
|
||||
at: HEADER_OFFSET_DATA + 18
|
||||
}/*force db out of WAL mode*/);
|
||||
}catch(e){
|
||||
@@ -927,7 +927,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
this.setAssociatedPath(sah, '', 0);
|
||||
toss("Expected to write "+n+" bytes but wrote "+nWrote+".");
|
||||
}else{
|
||||
sah.write(new Uint8Array([0,0]), {at: HEADER_OFFSET_DATA+18}
|
||||
sah.write(new Uint8Array([1,1]), {at: HEADER_OFFSET_DATA+18}
|
||||
/* force db out of WAL mode */);
|
||||
this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB);
|
||||
}
|
||||
|
||||
@@ -1197,7 +1197,7 @@ const installOpfsVfs = function callee(options){
|
||||
sah.read( header, {at: 0} );
|
||||
util.affirmDbHeader( header );
|
||||
}
|
||||
sah.write(new Uint8Array(2), {at: 18}/*force db out of WAL mode*/);
|
||||
sah.write(new Uint8Array([1,1]), {at: 18}/*force db out of WAL mode*/);
|
||||
return nWrote;
|
||||
}catch(e){
|
||||
await sah.close();
|
||||
@@ -1255,7 +1255,7 @@ const installOpfsVfs = function callee(options){
|
||||
if(nWrote != n){
|
||||
toss("Expected to write "+n+" bytes but wrote "+nWrote+".");
|
||||
}
|
||||
sah.write(new Uint8Array(2), {at: 18}) /* force db out of WAL mode */;
|
||||
sah.write(new Uint8Array([1,1]), {at: 18}) /* force db out of WAL mode */;
|
||||
return nWrote;
|
||||
}catch(e){
|
||||
if( sah ){ await sah.close(); sah = undefined; }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user