Merge branch 'sqlite-release' into v2-integration
Conflicts: Makefile.in
This commit is contained in:
commit
f003dfad13
15
Makefile.in
15
Makefile.in
@ -27,7 +27,7 @@ BCC = @BUILD_CC@ @BUILD_CFLAGS@
|
||||
# will run on the target platform. (BCC and TCC are usually the
|
||||
# same unless your are cross-compiling.)
|
||||
#
|
||||
TCC = @CC@ @CPPFLAGS@ @CFLAGS@ -I. -I${TOP}/src
|
||||
TCC = @CC@ @CPPFLAGS@ @CFLAGS@ -I. -I${TOP}/src -I${TOP}/ext/rtree
|
||||
|
||||
# Define this for the autoconf-based build, so that the code knows it can
|
||||
# include the generated config.h
|
||||
@ -60,10 +60,6 @@ LIBREADLINE = @TARGET_READLINE_LIBS@
|
||||
#
|
||||
TCC += -DSQLITE_THREADSAFE=@SQLITE_THREADSAFE@
|
||||
|
||||
# Do threads override each others locks by default (1), or do we test (-1)
|
||||
#
|
||||
TCC += -DSQLITE_THREAD_OVERRIDE_LOCK=@THREADSOVERRIDELOCKS@
|
||||
|
||||
# Any target libraries which libsqlite must be linked against
|
||||
#
|
||||
TLIBS = @LIBS@
|
||||
@ -177,7 +173,8 @@ USE_AMALGAMATION = @USE_AMALGAMATION@
|
||||
#
|
||||
LIBOBJS0 = alter.lo analyze.lo attach.lo auth.lo \
|
||||
backup.lo bitvec.lo btmutex.lo btree.lo build.lo \
|
||||
callback.lo complete.lo ctime.lo date.lo delete.lo expr.lo fault.lo fkey.lo \
|
||||
callback.lo complete.lo ctime.lo date.lo delete.lo \
|
||||
expr.lo fault.lo fkey.lo \
|
||||
fts3.lo fts3_expr.lo fts3_hash.lo fts3_icu.lo fts3_porter.lo \
|
||||
fts3_snippet.lo fts3_tokenizer.lo fts3_tokenizer1.lo fts3_write.lo \
|
||||
func.lo global.lo hash.lo \
|
||||
@ -374,12 +371,16 @@ TESTSRC = \
|
||||
$(TOP)/src/test_intarray.c \
|
||||
$(TOP)/src/test_journal.c \
|
||||
$(TOP)/src/test_malloc.c \
|
||||
$(TOP)/src/test_multiplex.c \
|
||||
$(TOP)/src/test_mutex.c \
|
||||
$(TOP)/src/test_onefile.c \
|
||||
$(TOP)/src/test_osinst.c \
|
||||
$(TOP)/src/test_pcache.c \
|
||||
$(TOP)/src/test_quota.c \
|
||||
$(TOP)/src/test_rtree.c \
|
||||
$(TOP)/src/test_schema.c \
|
||||
$(TOP)/src/test_server.c \
|
||||
$(TOP)/src/test_superlock.c \
|
||||
$(TOP)/src/test_stat.c \
|
||||
$(TOP)/src/test_tclvar.c \
|
||||
$(TOP)/src/test_thread.c \
|
||||
@ -471,6 +472,8 @@ EXTHDR += \
|
||||
$(TOP)/ext/rtree/rtree.h
|
||||
EXTHDR += \
|
||||
$(TOP)/ext/icu/sqliteicu.h
|
||||
EXTHDR += \
|
||||
$(TOP)/ext/rtree/sqlite3rtree.h
|
||||
|
||||
# This is the default Makefile target. The objects listed here
|
||||
# are what get build when you type just "make" with no arguments.
|
||||
|
||||
76
configure
vendored
76
configure
vendored
@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.62 for sqlite 3.7.2.
|
||||
# Generated by GNU Autoconf 2.62 for sqlite 3.7.5.
|
||||
#
|
||||
# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
|
||||
# 2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
|
||||
@ -743,8 +743,8 @@ SHELL=${CONFIG_SHELL-/bin/sh}
|
||||
# Identity of this package.
|
||||
PACKAGE_NAME='sqlite'
|
||||
PACKAGE_TARNAME='sqlite'
|
||||
PACKAGE_VERSION='3.7.2'
|
||||
PACKAGE_STRING='sqlite 3.7.2'
|
||||
PACKAGE_VERSION='3.7.5'
|
||||
PACKAGE_STRING='sqlite 3.7.5'
|
||||
PACKAGE_BUGREPORT=''
|
||||
|
||||
# Factoring default headers for most tests.
|
||||
@ -869,7 +869,6 @@ VERSION_NUMBER
|
||||
BUILD_CC
|
||||
SQLITE_THREADSAFE
|
||||
XTHREADCONNECT
|
||||
THREADSOVERRIDELOCKS
|
||||
ALLOWRELEASE
|
||||
TEMP_STORE
|
||||
BUILD_EXEEXT
|
||||
@ -912,7 +911,6 @@ enable_largefile
|
||||
with_hints
|
||||
enable_threadsafe
|
||||
enable_cross_thread_connections
|
||||
enable_threads_override_locks
|
||||
enable_releasemode
|
||||
enable_tempstore
|
||||
enable_tcl
|
||||
@ -1487,7 +1485,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.7.2 to adapt to many kinds of systems.
|
||||
\`configure' configures sqlite 3.7.5 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@ -1552,7 +1550,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of sqlite 3.7.2:";;
|
||||
short | recursive ) echo "Configuration of sqlite 3.7.5:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@ -1569,8 +1567,6 @@ Optional Features:
|
||||
--enable-threadsafe Support threadsafe operation
|
||||
--enable-cross-thread-connections
|
||||
Allow connection sharing across threads
|
||||
--enable-threads-override-locks
|
||||
Threads can override each others locks
|
||||
--enable-releasemode Support libtool link to release mode
|
||||
--enable-tempstore Use an in-ram database for temporary tables
|
||||
(never,no,yes,always)
|
||||
@ -1670,7 +1666,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
sqlite configure 3.7.2
|
||||
sqlite configure 3.7.5
|
||||
generated by GNU Autoconf 2.62
|
||||
|
||||
Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
|
||||
@ -1684,7 +1680,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.7.2, which was
|
||||
It was created by sqlite $as_me 3.7.5, which was
|
||||
generated by GNU Autoconf 2.62. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
@ -3738,13 +3734,13 @@ if test "${lt_cv_nm_interface+set}" = set; then
|
||||
else
|
||||
lt_cv_nm_interface="BSD nm"
|
||||
echo "int some_variable = 0;" > conftest.$ac_ext
|
||||
(eval echo "\"\$as_me:3741: $ac_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:3737: $ac_compile\"" >&5)
|
||||
(eval "$ac_compile" 2>conftest.err)
|
||||
cat conftest.err >&5
|
||||
(eval echo "\"\$as_me:3744: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
|
||||
(eval echo "\"\$as_me:3740: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
|
||||
(eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
|
||||
cat conftest.err >&5
|
||||
(eval echo "\"\$as_me:3747: output\"" >&5)
|
||||
(eval echo "\"\$as_me:3743: output\"" >&5)
|
||||
cat conftest.out >&5
|
||||
if $GREP 'External.*some_variable' conftest.out > /dev/null; then
|
||||
lt_cv_nm_interface="MS dumpbin"
|
||||
@ -4966,7 +4962,7 @@ ia64-*-hpux*)
|
||||
;;
|
||||
*-*-irix6*)
|
||||
# Find out which ABI we are using.
|
||||
echo '#line 4969 "configure"' > conftest.$ac_ext
|
||||
echo '#line 4965 "configure"' > conftest.$ac_ext
|
||||
if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
|
||||
(eval $ac_compile) 2>&5
|
||||
ac_status=$?
|
||||
@ -6835,11 +6831,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:6838: $lt_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:6834: $lt_compile\"" >&5)
|
||||
(eval "$lt_compile" 2>conftest.err)
|
||||
ac_status=$?
|
||||
cat conftest.err >&5
|
||||
echo "$as_me:6842: \$? = $ac_status" >&5
|
||||
echo "$as_me:6838: \$? = $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.
|
||||
@ -7174,11 +7170,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:7177: $lt_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:7173: $lt_compile\"" >&5)
|
||||
(eval "$lt_compile" 2>conftest.err)
|
||||
ac_status=$?
|
||||
cat conftest.err >&5
|
||||
echo "$as_me:7181: \$? = $ac_status" >&5
|
||||
echo "$as_me:7177: \$? = $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.
|
||||
@ -7279,11 +7275,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:7282: $lt_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:7278: $lt_compile\"" >&5)
|
||||
(eval "$lt_compile" 2>out/conftest.err)
|
||||
ac_status=$?
|
||||
cat out/conftest.err >&5
|
||||
echo "$as_me:7286: \$? = $ac_status" >&5
|
||||
echo "$as_me:7282: \$? = $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
|
||||
@ -7334,11 +7330,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:7337: $lt_compile\"" >&5)
|
||||
(eval echo "\"\$as_me:7333: $lt_compile\"" >&5)
|
||||
(eval "$lt_compile" 2>out/conftest.err)
|
||||
ac_status=$?
|
||||
cat out/conftest.err >&5
|
||||
echo "$as_me:7341: \$? = $ac_status" >&5
|
||||
echo "$as_me:7337: \$? = $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
|
||||
@ -10147,7 +10143,7 @@ else
|
||||
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
|
||||
lt_status=$lt_dlunknown
|
||||
cat > conftest.$ac_ext <<_LT_EOF
|
||||
#line 10150 "configure"
|
||||
#line 10146 "configure"
|
||||
#include "confdefs.h"
|
||||
|
||||
#if HAVE_DLFCN_H
|
||||
@ -10243,7 +10239,7 @@ else
|
||||
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
|
||||
lt_status=$lt_dlunknown
|
||||
cat > conftest.$ac_ext <<_LT_EOF
|
||||
#line 10246 "configure"
|
||||
#line 10242 "configure"
|
||||
#include "confdefs.h"
|
||||
|
||||
#if HAVE_DLFCN_H
|
||||
@ -12580,32 +12576,6 @@ $as_echo "yes" >&6; }
|
||||
fi
|
||||
|
||||
|
||||
##########
|
||||
# Do we want to set threadsOverrideEachOthersLocks variable to be 1 (true) by
|
||||
# default. Normally, a test at runtime is performed to determine the
|
||||
# appropriate value of this variable. Use this option only if you're sure that
|
||||
# threads can safely override each others locks in all runtime situations.
|
||||
#
|
||||
# Check whether --enable-threads-override-locks was given.
|
||||
if test "${enable_threads_override_locks+set}" = set; then
|
||||
enableval=$enable_threads_override_locks;
|
||||
else
|
||||
enable_threads_override_locks=no
|
||||
fi
|
||||
|
||||
{ $as_echo "$as_me:$LINENO: checking whether threads can override each others locks" >&5
|
||||
$as_echo_n "checking whether threads can override each others locks... " >&6; }
|
||||
if test "$enable_threads_override_locks" = "no"; then
|
||||
THREADSOVERRIDELOCKS='-1'
|
||||
{ $as_echo "$as_me:$LINENO: result: no" >&5
|
||||
$as_echo "no" >&6; }
|
||||
else
|
||||
THREADSOVERRIDELOCKS='1'
|
||||
{ $as_echo "$as_me:$LINENO: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
fi
|
||||
|
||||
|
||||
##########
|
||||
# Do we want to support release
|
||||
#
|
||||
@ -13972,7 +13942,7 @@ exec 6>&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.7.2, which was
|
||||
This file was extended by sqlite $as_me 3.7.5, which was
|
||||
generated by GNU Autoconf 2.62. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@ -14025,7 +13995,7 @@ Report bugs to <bug-autoconf@gnu.org>."
|
||||
_ACEOF
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_version="\\
|
||||
sqlite config.status 3.7.2
|
||||
sqlite config.status 3.7.5
|
||||
configured by $0, generated by GNU Autoconf 2.62,
|
||||
with options \\"`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"
|
||||
|
||||
|
||||
18
configure.ac
18
configure.ac
@ -268,24 +268,6 @@ else
|
||||
fi
|
||||
AC_SUBST(XTHREADCONNECT)
|
||||
|
||||
##########
|
||||
# Do we want to set threadsOverrideEachOthersLocks variable to be 1 (true) by
|
||||
# default. Normally, a test at runtime is performed to determine the
|
||||
# appropriate value of this variable. Use this option only if you're sure that
|
||||
# threads can safely override each others locks in all runtime situations.
|
||||
#
|
||||
AC_ARG_ENABLE(threads-override-locks,
|
||||
AC_HELP_STRING([--enable-threads-override-locks],[Threads can override each others locks]),,enable_threads_override_locks=no)
|
||||
AC_MSG_CHECKING([whether threads can override each others locks])
|
||||
if test "$enable_threads_override_locks" = "no"; then
|
||||
THREADSOVERRIDELOCKS='-1'
|
||||
AC_MSG_RESULT([no])
|
||||
else
|
||||
THREADSOVERRIDELOCKS='1'
|
||||
AC_MSG_RESULT([yes])
|
||||
fi
|
||||
AC_SUBST(THREADSOVERRIDELOCKS)
|
||||
|
||||
##########
|
||||
# Do we want to support release
|
||||
#
|
||||
|
||||
@ -306,8 +306,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "fts2.h"
|
||||
#include "fts2_hash.h"
|
||||
#include "fts2_tokenizer.h"
|
||||
@ -345,13 +343,13 @@ SQLITE_EXTENSION_INIT1
|
||||
*/
|
||||
/* TODO(shess) Is __isascii() a portable version of (c&0x80)==0? */
|
||||
static int safe_isspace(char c){
|
||||
return (c&0x80)==0 ? isspace(c) : 0;
|
||||
return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f';
|
||||
}
|
||||
static int safe_tolower(char c){
|
||||
return (c&0x80)==0 ? tolower(c) : c;
|
||||
return (c>='A' && c<='Z') ? (c - 'A' + 'a') : c;
|
||||
}
|
||||
static int safe_isalnum(char c){
|
||||
return (c&0x80)==0 ? isalnum(c) : 0;
|
||||
return (c>='0' && c<='9') || (c>='A' && c<='Z') || (c>='a' && c<='z');
|
||||
}
|
||||
|
||||
typedef enum DocListType {
|
||||
|
||||
@ -29,7 +29,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "fts2_tokenizer.h"
|
||||
|
||||
|
||||
@ -29,7 +29,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "fts2_tokenizer.h"
|
||||
|
||||
@ -89,7 +88,8 @@ static int simpleCreate(
|
||||
/* Mark non-alphanumeric ASCII characters as delimiters */
|
||||
int i;
|
||||
for(i=1; i<0x80; i++){
|
||||
t->delim[i] = !isalnum(i);
|
||||
t->delim[i] = !((i>='0' && i<='9') || (i>='A' && i<='Z') ||
|
||||
(i>='a' && i<='z'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,7 +191,7 @@ static int simpleNext(
|
||||
** case-insensitivity.
|
||||
*/
|
||||
unsigned char ch = p[iStartOffset+i];
|
||||
c->pToken[i] = ch<0x80 ? tolower(ch) : ch;
|
||||
c->pToken[i] = (ch>='A' && ch<='Z') ? (ch - 'A' + 'a') : ch;
|
||||
}
|
||||
*ppToken = c->pToken;
|
||||
*pnBytes = n;
|
||||
|
||||
1627
ext/fts3/fts3.c
1627
ext/fts3/fts3.c
File diff suppressed because it is too large
Load Diff
@ -77,8 +77,14 @@
|
||||
** Macros indicating that conditional expressions are always true or
|
||||
** false.
|
||||
*/
|
||||
#ifdef SQLITE_COVERAGE_TEST
|
||||
# define ALWAYS(x) (1)
|
||||
# define NEVER(X) (0)
|
||||
#else
|
||||
# define ALWAYS(x) (x)
|
||||
# define NEVER(X) (x)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Internal types used by SQLite.
|
||||
*/
|
||||
@ -96,8 +102,12 @@ typedef struct Fts3Table Fts3Table;
|
||||
typedef struct Fts3Cursor Fts3Cursor;
|
||||
typedef struct Fts3Expr Fts3Expr;
|
||||
typedef struct Fts3Phrase Fts3Phrase;
|
||||
typedef struct Fts3SegReader Fts3SegReader;
|
||||
typedef struct Fts3PhraseToken Fts3PhraseToken;
|
||||
|
||||
typedef struct Fts3SegFilter Fts3SegFilter;
|
||||
typedef struct Fts3DeferredToken Fts3DeferredToken;
|
||||
typedef struct Fts3SegReader Fts3SegReader;
|
||||
typedef struct Fts3SegReaderArray Fts3SegReaderArray;
|
||||
|
||||
/*
|
||||
** A connection to a fulltext index is an instance of the following
|
||||
@ -118,22 +128,14 @@ struct Fts3Table {
|
||||
/* Precompiled statements used by the implementation. Each of these
|
||||
** statements is run and reset within a single virtual table API call.
|
||||
*/
|
||||
sqlite3_stmt *aStmt[25];
|
||||
|
||||
/* Pointer to string containing the SQL:
|
||||
**
|
||||
** "SELECT block FROM %_segments WHERE blockid BETWEEN ? AND ?
|
||||
** ORDER BY blockid"
|
||||
*/
|
||||
char *zSelectLeaves;
|
||||
int nLeavesStmt; /* Valid statements in aLeavesStmt */
|
||||
int nLeavesTotal; /* Total number of prepared leaves stmts */
|
||||
int nLeavesAlloc; /* Allocated size of aLeavesStmt */
|
||||
sqlite3_stmt **aLeavesStmt; /* Array of prepared zSelectLeaves stmts */
|
||||
sqlite3_stmt *aStmt[24];
|
||||
|
||||
int nNodeSize; /* Soft limit for node size */
|
||||
u8 bHasContent; /* True if %_content table exists */
|
||||
u8 bHasStat; /* True if %_stat table exists */
|
||||
u8 bHasDocsize; /* True if %_docsize table exists */
|
||||
int nPgsz; /* Page size for host database */
|
||||
char *zSegmentsTbl; /* Name of %_segments table */
|
||||
sqlite3_blob *pSegments; /* Blob handle open on %_segments table */
|
||||
|
||||
/* The following hash table is used to buffer pending index updates during
|
||||
** transactions. Variable nPendingData estimates the memory size of the
|
||||
@ -160,14 +162,25 @@ struct Fts3Cursor {
|
||||
u8 isRequireSeek; /* True if must seek pStmt to %_content row */
|
||||
sqlite3_stmt *pStmt; /* Prepared statement in use by the cursor */
|
||||
Fts3Expr *pExpr; /* Parsed MATCH query string */
|
||||
int nPhrase; /* Number of matchable phrases in query */
|
||||
Fts3DeferredToken *pDeferred; /* Deferred search tokens, if any */
|
||||
sqlite3_int64 iPrevId; /* Previous id read from aDoclist */
|
||||
char *pNextId; /* Pointer into the body of aDoclist */
|
||||
char *aDoclist; /* List of docids for full-text queries */
|
||||
int nDoclist; /* Size of buffer at aDoclist */
|
||||
int eEvalmode; /* An FTS3_EVAL_XX constant */
|
||||
int nRowAvg; /* Average size of database rows, in pages */
|
||||
|
||||
int isMatchinfoNeeded; /* True when aMatchinfo[] needs filling in */
|
||||
u32 *aMatchinfo; /* Information about most recent match */
|
||||
int nMatchinfo; /* Number of elements in aMatchinfo[] */
|
||||
char *zMatchinfo; /* Matchinfo specification */
|
||||
};
|
||||
|
||||
#define FTS3_EVAL_FILTER 0
|
||||
#define FTS3_EVAL_NEXT 1
|
||||
#define FTS3_EVAL_MATCHINFO 2
|
||||
|
||||
/*
|
||||
** The Fts3Cursor.eSearch member is always set to one of the following.
|
||||
** Actualy, Fts3Cursor.eSearch can be greater than or equal to
|
||||
@ -190,18 +203,30 @@ struct Fts3Cursor {
|
||||
/*
|
||||
** A "phrase" is a sequence of one or more tokens that must match in
|
||||
** sequence. A single token is the base case and the most common case.
|
||||
** For a sequence of tokens contained in "...", nToken will be the number
|
||||
** of tokens in the string.
|
||||
** For a sequence of tokens contained in double-quotes (i.e. "one two three")
|
||||
** nToken will be the number of tokens in the string.
|
||||
**
|
||||
** The nDocMatch and nMatch variables contain data that may be used by the
|
||||
** matchinfo() function. They are populated when the full-text index is
|
||||
** queried for hits on the phrase. If one or more tokens in the phrase
|
||||
** are deferred, the nDocMatch and nMatch variables are populated based
|
||||
** on the assumption that the
|
||||
*/
|
||||
struct Fts3PhraseToken {
|
||||
char *z; /* Text of the token */
|
||||
int n; /* Number of bytes in buffer z */
|
||||
int isPrefix; /* True if token ends with a "*" character */
|
||||
int bFulltext; /* True if full-text index was used */
|
||||
Fts3SegReaderArray *pArray; /* Segment-reader for this token */
|
||||
Fts3DeferredToken *pDeferred; /* Deferred token object for this token */
|
||||
};
|
||||
|
||||
struct Fts3Phrase {
|
||||
/* Variables populated by fts3_expr.c when parsing a MATCH expression */
|
||||
int nToken; /* Number of tokens in the phrase */
|
||||
int iColumn; /* Index of column this phrase must match */
|
||||
int isNot; /* Phrase prefixed by unary not (-) operator */
|
||||
struct PhraseToken {
|
||||
char *z; /* Text of the token */
|
||||
int n; /* Number of bytes in buffer pointed to by z */
|
||||
int isPrefix; /* True if token ends in with a "*" character */
|
||||
} aToken[1]; /* One entry for each token in the phrase */
|
||||
Fts3PhraseToken aToken[1]; /* One entry for each token in the phrase */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -251,28 +276,34 @@ struct Fts3Expr {
|
||||
#define FTSQUERY_PHRASE 5
|
||||
|
||||
|
||||
/* fts3_init.c */
|
||||
int sqlite3Fts3DeleteVtab(int, sqlite3_vtab *);
|
||||
int sqlite3Fts3InitVtab(int, sqlite3*, void*, int, const char*const*,
|
||||
sqlite3_vtab **, char **);
|
||||
|
||||
/* fts3_write.c */
|
||||
int sqlite3Fts3UpdateMethod(sqlite3_vtab*,int,sqlite3_value**,sqlite3_int64*);
|
||||
int sqlite3Fts3PendingTermsFlush(Fts3Table *);
|
||||
void sqlite3Fts3PendingTermsClear(Fts3Table *);
|
||||
int sqlite3Fts3Optimize(Fts3Table *);
|
||||
int sqlite3Fts3SegReaderNew(Fts3Table *,int, sqlite3_int64,
|
||||
int sqlite3Fts3SegReaderNew(int, sqlite3_int64,
|
||||
sqlite3_int64, sqlite3_int64, const char *, int, Fts3SegReader**);
|
||||
int sqlite3Fts3SegReaderPending(Fts3Table*,const char*,int,int,Fts3SegReader**);
|
||||
void sqlite3Fts3SegReaderFree(Fts3Table *, Fts3SegReader *);
|
||||
void sqlite3Fts3SegReaderFree(Fts3SegReader *);
|
||||
int sqlite3Fts3SegReaderIterate(
|
||||
Fts3Table *, Fts3SegReader **, int, Fts3SegFilter *,
|
||||
int (*)(Fts3Table *, void *, char *, int, char *, int), void *
|
||||
);
|
||||
int sqlite3Fts3ReadBlock(Fts3Table*, sqlite3_int64, char const**, int*);
|
||||
int sqlite3Fts3SegReaderCost(Fts3Cursor *, Fts3SegReader *, int *);
|
||||
int sqlite3Fts3AllSegdirs(Fts3Table*, sqlite3_stmt **);
|
||||
int sqlite3Fts3MatchinfoDocsizeLocal(Fts3Cursor*, u32*);
|
||||
int sqlite3Fts3MatchinfoDocsizeGlobal(Fts3Cursor*, u32*);
|
||||
int sqlite3Fts3ReadLock(Fts3Table *);
|
||||
int sqlite3Fts3ReadBlock(Fts3Table*, sqlite3_int64, char **, int*);
|
||||
|
||||
int sqlite3Fts3SelectDoctotal(Fts3Table *, sqlite3_stmt **);
|
||||
int sqlite3Fts3SelectDocsize(Fts3Table *, sqlite3_int64, sqlite3_stmt **);
|
||||
|
||||
void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *);
|
||||
int sqlite3Fts3DeferToken(Fts3Cursor *, Fts3PhraseToken *, int);
|
||||
int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *);
|
||||
void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *);
|
||||
char *sqlite3Fts3DeferredDoclist(Fts3DeferredToken *, int *);
|
||||
|
||||
void sqlite3Fts3SegmentsClose(Fts3Table *);
|
||||
|
||||
/* Flags allowed as part of the 4th argument to SegmentReaderIterate() */
|
||||
#define FTS3_SEGMENT_REQUIRE_POS 0x00000001
|
||||
@ -296,22 +327,24 @@ int sqlite3Fts3VarintLen(sqlite3_uint64);
|
||||
void sqlite3Fts3Dequote(char *);
|
||||
|
||||
char *sqlite3Fts3FindPositions(Fts3Expr *, sqlite3_int64, int);
|
||||
int sqlite3Fts3ExprLoadDoclist(Fts3Table *, Fts3Expr *);
|
||||
int sqlite3Fts3ExprLoadDoclist(Fts3Cursor *, Fts3Expr *);
|
||||
int sqlite3Fts3ExprLoadFtDoclist(Fts3Cursor *, Fts3Expr *, char **, int *);
|
||||
int sqlite3Fts3ExprNearTrim(Fts3Expr *, Fts3Expr *, int);
|
||||
|
||||
/* fts3_tokenizer.c */
|
||||
const char *sqlite3Fts3NextToken(const char *, int *);
|
||||
int sqlite3Fts3InitHashTable(sqlite3 *, Fts3Hash *, const char *);
|
||||
int sqlite3Fts3InitTokenizer(Fts3Hash *pHash,
|
||||
const char *, sqlite3_tokenizer **, const char **, char **
|
||||
int sqlite3Fts3InitTokenizer(Fts3Hash *pHash, const char *,
|
||||
sqlite3_tokenizer **, char **
|
||||
);
|
||||
int sqlite3Fts3IsIdChar(char);
|
||||
|
||||
/* fts3_snippet.c */
|
||||
void sqlite3Fts3Offsets(sqlite3_context*, Fts3Cursor*);
|
||||
void sqlite3Fts3Snippet(sqlite3_context *, Fts3Cursor *, const char *,
|
||||
const char *, const char *, int, int
|
||||
);
|
||||
void sqlite3Fts3Matchinfo(sqlite3_context *, Fts3Cursor *);
|
||||
void sqlite3Fts3Matchinfo(sqlite3_context *, Fts3Cursor *, const char *);
|
||||
|
||||
/* fts3_expr.c */
|
||||
int sqlite3Fts3ExprParse(sqlite3_tokenizer *,
|
||||
|
||||
@ -106,6 +106,18 @@ static int fts3isspace(char c){
|
||||
return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f';
|
||||
}
|
||||
|
||||
/*
|
||||
** Allocate nByte bytes of memory using sqlite3_malloc(). If successful,
|
||||
** zero the memory before returning a pointer to it. If unsuccessful,
|
||||
** return NULL.
|
||||
*/
|
||||
static void *fts3MallocZero(int nByte){
|
||||
void *pRet = sqlite3_malloc(nByte);
|
||||
if( pRet ) memset(pRet, 0, nByte);
|
||||
return pRet;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Extract the next token from buffer z (length n) using the tokenizer
|
||||
** and other information (column names etc.) in pParse. Create an Fts3Expr
|
||||
@ -143,11 +155,10 @@ static int getNextToken(
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase) + nToken;
|
||||
pRet = (Fts3Expr *)sqlite3_malloc(nByte);
|
||||
pRet = (Fts3Expr *)fts3MallocZero(nByte);
|
||||
if( !pRet ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
memset(pRet, 0, nByte);
|
||||
pRet->eType = FTSQUERY_PHRASE;
|
||||
pRet->pPhrase = (Fts3Phrase *)&pRet[1];
|
||||
pRet->pPhrase->nToken = 1;
|
||||
@ -223,7 +234,7 @@ static int getNextString(
|
||||
rc = pModule->xNext(pCursor, &zToken, &nToken, &iBegin, &iEnd, &iPos);
|
||||
if( rc==SQLITE_OK ){
|
||||
int nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase);
|
||||
p = fts3ReallocOrFree(p, nByte+ii*sizeof(struct PhraseToken));
|
||||
p = fts3ReallocOrFree(p, nByte+ii*sizeof(Fts3PhraseToken));
|
||||
zTemp = fts3ReallocOrFree(zTemp, nTemp + nToken);
|
||||
if( !p || !zTemp ){
|
||||
goto no_mem;
|
||||
@ -233,6 +244,7 @@ static int getNextString(
|
||||
p->pPhrase = (Fts3Phrase *)&p[1];
|
||||
}
|
||||
p->pPhrase = (Fts3Phrase *)&p[1];
|
||||
memset(&p->pPhrase->aToken[ii], 0, sizeof(Fts3PhraseToken));
|
||||
p->pPhrase->nToken = ii+1;
|
||||
p->pPhrase->aToken[ii].n = nToken;
|
||||
memcpy(&zTemp[nTemp], zToken, nToken);
|
||||
@ -254,7 +266,7 @@ static int getNextString(
|
||||
char *zNew = NULL;
|
||||
int nNew = 0;
|
||||
int nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase);
|
||||
nByte += (p?(p->pPhrase->nToken-1):0) * sizeof(struct PhraseToken);
|
||||
nByte += (p?(p->pPhrase->nToken-1):0) * sizeof(Fts3PhraseToken);
|
||||
p = fts3ReallocOrFree(p, nByte + nTemp);
|
||||
if( !p ){
|
||||
goto no_mem;
|
||||
@ -372,11 +384,10 @@ static int getNextNode(
|
||||
if( fts3isspace(cNext)
|
||||
|| cNext=='"' || cNext=='(' || cNext==')' || cNext==0
|
||||
){
|
||||
pRet = (Fts3Expr *)sqlite3_malloc(sizeof(Fts3Expr));
|
||||
pRet = (Fts3Expr *)fts3MallocZero(sizeof(Fts3Expr));
|
||||
if( !pRet ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
memset(pRet, 0, sizeof(Fts3Expr));
|
||||
pRet->eType = pKey->eType;
|
||||
pRet->nNear = nNear;
|
||||
*ppExpr = pRet;
|
||||
@ -394,7 +405,6 @@ static int getNextNode(
|
||||
if( sqlite3_fts3_enable_parentheses ){
|
||||
if( *zInput=='(' ){
|
||||
int nConsumed;
|
||||
int rc;
|
||||
pParse->nNest++;
|
||||
rc = fts3ExprParse(pParse, &zInput[1], nInput-1, ppExpr, &nConsumed);
|
||||
if( rc==SQLITE_OK && !*ppExpr ){
|
||||
@ -552,13 +562,12 @@ static int fts3ExprParse(
|
||||
&& p->eType==FTSQUERY_PHRASE && p->pPhrase->isNot
|
||||
){
|
||||
/* Create an implicit NOT operator. */
|
||||
Fts3Expr *pNot = sqlite3_malloc(sizeof(Fts3Expr));
|
||||
Fts3Expr *pNot = fts3MallocZero(sizeof(Fts3Expr));
|
||||
if( !pNot ){
|
||||
sqlite3Fts3ExprFree(p);
|
||||
rc = SQLITE_NOMEM;
|
||||
goto exprparse_out;
|
||||
}
|
||||
memset(pNot, 0, sizeof(Fts3Expr));
|
||||
pNot->eType = FTSQUERY_NOT;
|
||||
pNot->pRight = p;
|
||||
if( pNotBranch ){
|
||||
@ -586,13 +595,12 @@ static int fts3ExprParse(
|
||||
/* Insert an implicit AND operator. */
|
||||
Fts3Expr *pAnd;
|
||||
assert( pRet && pPrev );
|
||||
pAnd = sqlite3_malloc(sizeof(Fts3Expr));
|
||||
pAnd = fts3MallocZero(sizeof(Fts3Expr));
|
||||
if( !pAnd ){
|
||||
sqlite3Fts3ExprFree(p);
|
||||
rc = SQLITE_NOMEM;
|
||||
goto exprparse_out;
|
||||
}
|
||||
memset(pAnd, 0, sizeof(Fts3Expr));
|
||||
pAnd->eType = FTSQUERY_AND;
|
||||
insertBinaryOperator(&pRet, pPrev, pAnd);
|
||||
pPrev = pAnd;
|
||||
@ -777,47 +785,53 @@ static int queryTestTokenizer(
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is part of the test interface for the query parser. It
|
||||
** writes a text representation of the query expression pExpr into the
|
||||
** buffer pointed to by argument zBuf. It is assumed that zBuf is large
|
||||
** enough to store the required text representation.
|
||||
** Return a pointer to a buffer containing a text representation of the
|
||||
** expression passed as the first argument. The buffer is obtained from
|
||||
** sqlite3_malloc(). It is the responsibility of the caller to use
|
||||
** sqlite3_free() to release the memory. If an OOM condition is encountered,
|
||||
** NULL is returned.
|
||||
**
|
||||
** If the second argument is not NULL, then its contents are prepended to
|
||||
** the returned expression text and then freed using sqlite3_free().
|
||||
*/
|
||||
static void exprToString(Fts3Expr *pExpr, char *zBuf){
|
||||
static char *exprToString(Fts3Expr *pExpr, char *zBuf){
|
||||
switch( pExpr->eType ){
|
||||
case FTSQUERY_PHRASE: {
|
||||
Fts3Phrase *pPhrase = pExpr->pPhrase;
|
||||
int i;
|
||||
zBuf += sprintf(zBuf, "PHRASE %d %d", pPhrase->iColumn, pPhrase->isNot);
|
||||
for(i=0; i<pPhrase->nToken; i++){
|
||||
zBuf += sprintf(zBuf," %.*s",pPhrase->aToken[i].n,pPhrase->aToken[i].z);
|
||||
zBuf += sprintf(zBuf,"%s", (pPhrase->aToken[i].isPrefix?"+":""));
|
||||
zBuf = sqlite3_mprintf(
|
||||
"%zPHRASE %d %d", zBuf, pPhrase->iColumn, pPhrase->isNot);
|
||||
for(i=0; zBuf && i<pPhrase->nToken; i++){
|
||||
zBuf = sqlite3_mprintf("%z %.*s%s", zBuf,
|
||||
pPhrase->aToken[i].n, pPhrase->aToken[i].z,
|
||||
(pPhrase->aToken[i].isPrefix?"+":"")
|
||||
);
|
||||
}
|
||||
return;
|
||||
return zBuf;
|
||||
}
|
||||
|
||||
case FTSQUERY_NEAR:
|
||||
zBuf += sprintf(zBuf, "NEAR/%d ", pExpr->nNear);
|
||||
zBuf = sqlite3_mprintf("%zNEAR/%d ", zBuf, pExpr->nNear);
|
||||
break;
|
||||
case FTSQUERY_NOT:
|
||||
zBuf += sprintf(zBuf, "NOT ");
|
||||
zBuf = sqlite3_mprintf("%zNOT ", zBuf);
|
||||
break;
|
||||
case FTSQUERY_AND:
|
||||
zBuf += sprintf(zBuf, "AND ");
|
||||
zBuf = sqlite3_mprintf("%zAND ", zBuf);
|
||||
break;
|
||||
case FTSQUERY_OR:
|
||||
zBuf += sprintf(zBuf, "OR ");
|
||||
zBuf = sqlite3_mprintf("%zOR ", zBuf);
|
||||
break;
|
||||
}
|
||||
|
||||
zBuf += sprintf(zBuf, "{");
|
||||
exprToString(pExpr->pLeft, zBuf);
|
||||
zBuf += strlen(zBuf);
|
||||
zBuf += sprintf(zBuf, "} ");
|
||||
if( zBuf ) zBuf = sqlite3_mprintf("%z{", zBuf);
|
||||
if( zBuf ) zBuf = exprToString(pExpr->pLeft, zBuf);
|
||||
if( zBuf ) zBuf = sqlite3_mprintf("%z} {", zBuf);
|
||||
|
||||
zBuf += sprintf(zBuf, "{");
|
||||
exprToString(pExpr->pRight, zBuf);
|
||||
zBuf += strlen(zBuf);
|
||||
zBuf += sprintf(zBuf, "}");
|
||||
if( zBuf ) zBuf = exprToString(pExpr->pRight, zBuf);
|
||||
if( zBuf ) zBuf = sqlite3_mprintf("%z}", zBuf);
|
||||
|
||||
return zBuf;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -848,6 +862,7 @@ static void fts3ExprTest(
|
||||
int nCol;
|
||||
int ii;
|
||||
Fts3Expr *pExpr;
|
||||
char *zBuf = 0;
|
||||
sqlite3 *db = sqlite3_context_db_handle(context);
|
||||
|
||||
if( argc<3 ){
|
||||
@ -890,18 +905,17 @@ static void fts3ExprTest(
|
||||
rc = sqlite3Fts3ExprParse(
|
||||
pTokenizer, azCol, nCol, nCol, zExpr, nExpr, &pExpr
|
||||
);
|
||||
if( rc==SQLITE_NOMEM ){
|
||||
sqlite3_result_error_nomem(context);
|
||||
goto exprtest_out;
|
||||
}else if( rc==SQLITE_OK ){
|
||||
char zBuf[4096];
|
||||
exprToString(pExpr, zBuf);
|
||||
sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
|
||||
sqlite3Fts3ExprFree(pExpr);
|
||||
}else{
|
||||
if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM ){
|
||||
sqlite3_result_error(context, "Error parsing expression", -1);
|
||||
}else if( rc==SQLITE_NOMEM || !(zBuf = exprToString(pExpr, 0)) ){
|
||||
sqlite3_result_error_nomem(context);
|
||||
}else{
|
||||
sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
|
||||
sqlite3_free(zBuf);
|
||||
}
|
||||
|
||||
sqlite3Fts3ExprFree(pExpr);
|
||||
|
||||
exprtest_out:
|
||||
if( pModule && pTokenizer ){
|
||||
rc = pModule->xDestroy(pTokenizer);
|
||||
|
||||
@ -343,7 +343,7 @@ static void porter_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){
|
||||
int i, j;
|
||||
char zReverse[28];
|
||||
char *z, *z2;
|
||||
if( nIn<3 || nIn>=sizeof(zReverse)-7 ){
|
||||
if( nIn<3 || nIn>=(int)sizeof(zReverse)-7 ){
|
||||
/* The word is too big or too small for the porter stemmer.
|
||||
** Fallback to the copy stemmer */
|
||||
copy_stemmer(zIn, nIn, zOut, pnOut);
|
||||
|
||||
@ -17,6 +17,22 @@
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
/*
|
||||
** Characters that may appear in the second argument to matchinfo().
|
||||
*/
|
||||
#define FTS3_MATCHINFO_NPHRASE 'p' /* 1 value */
|
||||
#define FTS3_MATCHINFO_NCOL 'c' /* 1 value */
|
||||
#define FTS3_MATCHINFO_NDOC 'n' /* 1 value */
|
||||
#define FTS3_MATCHINFO_AVGLENGTH 'a' /* nCol values */
|
||||
#define FTS3_MATCHINFO_LENGTH 'l' /* nCol values */
|
||||
#define FTS3_MATCHINFO_LCS 's' /* nCol values */
|
||||
#define FTS3_MATCHINFO_HITS 'x' /* 3*nCol*nPhrase values */
|
||||
|
||||
/*
|
||||
** The default value for the second argument to matchinfo().
|
||||
*/
|
||||
#define FTS3_MATCHINFO_DEFAULT "pcx"
|
||||
|
||||
|
||||
/*
|
||||
** Used as an fts3ExprIterate() context when loading phrase doclists to
|
||||
@ -24,7 +40,7 @@
|
||||
*/
|
||||
typedef struct LoadDoclistCtx LoadDoclistCtx;
|
||||
struct LoadDoclistCtx {
|
||||
Fts3Table *pTab; /* FTS3 Table */
|
||||
Fts3Cursor *pCsr; /* FTS3 Cursor */
|
||||
int nPhrase; /* Number of phrases seen so far */
|
||||
int nToken; /* Number of tokens seen so far */
|
||||
};
|
||||
@ -70,6 +86,8 @@ typedef struct MatchInfo MatchInfo;
|
||||
struct MatchInfo {
|
||||
Fts3Cursor *pCursor; /* FTS3 Cursor */
|
||||
int nCol; /* Number of columns in table */
|
||||
int nPhrase; /* Number of matchable phrases in query */
|
||||
sqlite3_int64 nDoc; /* Number of docs in database */
|
||||
u32 *aMatchinfo; /* Pre-allocated buffer */
|
||||
};
|
||||
|
||||
@ -208,7 +226,7 @@ static int fts3ExprNearTrim(Fts3Expr *pExpr){
|
||||
** for each phrase into Fts3Expr.aDoclist[]/nDoclist. See also
|
||||
** fts3ExprLoadDoclists().
|
||||
*/
|
||||
static int fts3ExprLoadDoclistsCb1(Fts3Expr *pExpr, int iPhrase, void *ctx){
|
||||
static int fts3ExprLoadDoclistsCb(Fts3Expr *pExpr, int iPhrase, void *ctx){
|
||||
int rc = SQLITE_OK;
|
||||
LoadDoclistCtx *p = (LoadDoclistCtx *)ctx;
|
||||
|
||||
@ -218,7 +236,7 @@ static int fts3ExprLoadDoclistsCb1(Fts3Expr *pExpr, int iPhrase, void *ctx){
|
||||
p->nToken += pExpr->pPhrase->nToken;
|
||||
|
||||
if( pExpr->isLoaded==0 ){
|
||||
rc = sqlite3Fts3ExprLoadDoclist(p->pTab, pExpr);
|
||||
rc = sqlite3Fts3ExprLoadDoclist(p->pCsr, pExpr);
|
||||
pExpr->isLoaded = 1;
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts3ExprNearTrim(pExpr);
|
||||
@ -228,22 +246,6 @@ static int fts3ExprLoadDoclistsCb1(Fts3Expr *pExpr, int iPhrase, void *ctx){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This is an fts3ExprIterate() callback used while loading the doclists
|
||||
** for each phrase into Fts3Expr.aDoclist[]/nDoclist. See also
|
||||
** fts3ExprLoadDoclists().
|
||||
*/
|
||||
static int fts3ExprLoadDoclistsCb2(Fts3Expr *pExpr, int iPhrase, void *ctx){
|
||||
UNUSED_PARAMETER(iPhrase);
|
||||
UNUSED_PARAMETER(ctx);
|
||||
if( pExpr->aDoclist ){
|
||||
pExpr->pCurrent = pExpr->aDoclist;
|
||||
pExpr->iCurrent = 0;
|
||||
pExpr->pCurrent += sqlite3Fts3GetVarint(pExpr->pCurrent, &pExpr->iCurrent);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Load the doclists for each phrase in the query associated with FTS3 cursor
|
||||
** pCsr.
|
||||
@ -261,16 +263,25 @@ static int fts3ExprLoadDoclists(
|
||||
){
|
||||
int rc; /* Return Code */
|
||||
LoadDoclistCtx sCtx = {0,0,0}; /* Context for fts3ExprIterate() */
|
||||
sCtx.pTab = (Fts3Table *)pCsr->base.pVtab;
|
||||
rc = fts3ExprIterate(pCsr->pExpr, fts3ExprLoadDoclistsCb1, (void *)&sCtx);
|
||||
if( rc==SQLITE_OK ){
|
||||
(void)fts3ExprIterate(pCsr->pExpr, fts3ExprLoadDoclistsCb2, 0);
|
||||
}
|
||||
sCtx.pCsr = pCsr;
|
||||
rc = fts3ExprIterate(pCsr->pExpr, fts3ExprLoadDoclistsCb, (void *)&sCtx);
|
||||
if( pnPhrase ) *pnPhrase = sCtx.nPhrase;
|
||||
if( pnToken ) *pnToken = sCtx.nToken;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int fts3ExprPhraseCountCb(Fts3Expr *pExpr, int iPhrase, void *ctx){
|
||||
(*(int *)ctx)++;
|
||||
UNUSED_PARAMETER(pExpr);
|
||||
UNUSED_PARAMETER(iPhrase);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
static int fts3ExprPhraseCount(Fts3Expr *pExpr){
|
||||
int nPhrase = 0;
|
||||
(void)fts3ExprIterate(pExpr, fts3ExprPhraseCountCb, (void *)&nPhrase);
|
||||
return nPhrase;
|
||||
}
|
||||
|
||||
/*
|
||||
** Advance the position list iterator specified by the first two
|
||||
** arguments so that it points to the first element with a value greater
|
||||
@ -783,38 +794,87 @@ static void fts3LoadColumnlistCounts(char **pp, u32 *aOut, int isGlobal){
|
||||
|
||||
/*
|
||||
** fts3ExprIterate() callback used to collect the "global" matchinfo stats
|
||||
** for a single query. The "global" stats are those elements of the matchinfo
|
||||
** array that are constant for all rows returned by the current query.
|
||||
** for a single query.
|
||||
**
|
||||
** fts3ExprIterate() callback to load the 'global' elements of a
|
||||
** FTS3_MATCHINFO_HITS matchinfo array. The global stats are those elements
|
||||
** of the matchinfo array that are constant for all rows returned by the
|
||||
** current query.
|
||||
**
|
||||
** Argument pCtx is actually a pointer to a struct of type MatchInfo. This
|
||||
** function populates Matchinfo.aMatchinfo[] as follows:
|
||||
**
|
||||
** for(iCol=0; iCol<nCol; iCol++){
|
||||
** aMatchinfo[3*iPhrase*nCol + 3*iCol + 1] = X;
|
||||
** aMatchinfo[3*iPhrase*nCol + 3*iCol + 2] = Y;
|
||||
** }
|
||||
**
|
||||
** where X is the number of matches for phrase iPhrase is column iCol of all
|
||||
** rows of the table. Y is the number of rows for which column iCol contains
|
||||
** at least one instance of phrase iPhrase.
|
||||
**
|
||||
** If the phrase pExpr consists entirely of deferred tokens, then all X and
|
||||
** Y values are set to nDoc, where nDoc is the number of documents in the
|
||||
** file system. This is done because the full-text index doclist is required
|
||||
** to calculate these values properly, and the full-text index doclist is
|
||||
** not available for deferred tokens.
|
||||
*/
|
||||
static int fts3ExprGlobalMatchinfoCb(
|
||||
static int fts3ExprGlobalHitsCb(
|
||||
Fts3Expr *pExpr, /* Phrase expression node */
|
||||
int iPhrase, /* Phrase number (numbered from zero) */
|
||||
void *pCtx /* Pointer to MatchInfo structure */
|
||||
){
|
||||
MatchInfo *p = (MatchInfo *)pCtx;
|
||||
char *pCsr;
|
||||
Fts3Cursor *pCsr = p->pCursor;
|
||||
char *pIter;
|
||||
char *pEnd;
|
||||
const int iStart = 2 + (iPhrase * p->nCol * 3) + 1;
|
||||
char *pFree = 0;
|
||||
u32 *aOut = &p->aMatchinfo[3*iPhrase*p->nCol];
|
||||
|
||||
assert( pExpr->isLoaded );
|
||||
assert( pExpr->eType==FTSQUERY_PHRASE );
|
||||
|
||||
/* Fill in the global hit count matrix row for this phrase. */
|
||||
pCsr = pExpr->aDoclist;
|
||||
pEnd = &pExpr->aDoclist[pExpr->nDoclist];
|
||||
while( pCsr<pEnd ){
|
||||
while( *pCsr++ & 0x80 ); /* Skip past docid. */
|
||||
fts3LoadColumnlistCounts(&pCsr, &p->aMatchinfo[iStart], 1);
|
||||
if( pCsr->pDeferred ){
|
||||
Fts3Phrase *pPhrase = pExpr->pPhrase;
|
||||
int ii;
|
||||
for(ii=0; ii<pPhrase->nToken; ii++){
|
||||
if( pPhrase->aToken[ii].bFulltext ) break;
|
||||
}
|
||||
if( ii<pPhrase->nToken ){
|
||||
int nFree = 0;
|
||||
int rc = sqlite3Fts3ExprLoadFtDoclist(pCsr, pExpr, &pFree, &nFree);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
pIter = pFree;
|
||||
pEnd = &pFree[nFree];
|
||||
}else{
|
||||
int iCol; /* Column index */
|
||||
for(iCol=0; iCol<p->nCol; iCol++){
|
||||
aOut[iCol*3 + 1] = (u32)p->nDoc;
|
||||
aOut[iCol*3 + 2] = (u32)p->nDoc;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
}else{
|
||||
pIter = pExpr->aDoclist;
|
||||
pEnd = &pExpr->aDoclist[pExpr->nDoclist];
|
||||
}
|
||||
|
||||
/* Fill in the global hit count matrix row for this phrase. */
|
||||
while( pIter<pEnd ){
|
||||
while( *pIter++ & 0x80 ); /* Skip past docid. */
|
||||
fts3LoadColumnlistCounts(&pIter, &aOut[1], 1);
|
||||
}
|
||||
|
||||
sqlite3_free(pFree);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** fts3ExprIterate() callback used to collect the "local" matchinfo stats
|
||||
** for a single query. The "local" stats are those elements of the matchinfo
|
||||
** fts3ExprIterate() callback used to collect the "local" part of the
|
||||
** FTS3_MATCHINFO_HITS array. The local stats are those elements of the
|
||||
** array that are different for each row returned by the query.
|
||||
*/
|
||||
static int fts3ExprLocalMatchinfoCb(
|
||||
static int fts3ExprLocalHitsCb(
|
||||
Fts3Expr *pExpr, /* Phrase expression node */
|
||||
int iPhrase, /* Phrase number */
|
||||
void *pCtx /* Pointer to MatchInfo structure */
|
||||
@ -823,7 +883,7 @@ static int fts3ExprLocalMatchinfoCb(
|
||||
|
||||
if( pExpr->aDoclist ){
|
||||
char *pCsr;
|
||||
int iStart = 2 + (iPhrase * p->nCol * 3);
|
||||
int iStart = iPhrase * p->nCol * 3;
|
||||
int i;
|
||||
|
||||
for(i=0; i<p->nCol; i++) p->aMatchinfo[iStart+i*3] = 0;
|
||||
@ -837,67 +897,400 @@ static int fts3ExprLocalMatchinfoCb(
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static int fts3MatchinfoCheck(
|
||||
Fts3Table *pTab,
|
||||
char cArg,
|
||||
char **pzErr
|
||||
){
|
||||
if( (cArg==FTS3_MATCHINFO_NPHRASE)
|
||||
|| (cArg==FTS3_MATCHINFO_NCOL)
|
||||
|| (cArg==FTS3_MATCHINFO_NDOC && pTab->bHasStat)
|
||||
|| (cArg==FTS3_MATCHINFO_AVGLENGTH && pTab->bHasStat)
|
||||
|| (cArg==FTS3_MATCHINFO_LENGTH && pTab->bHasDocsize)
|
||||
|| (cArg==FTS3_MATCHINFO_LCS)
|
||||
|| (cArg==FTS3_MATCHINFO_HITS)
|
||||
){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
*pzErr = sqlite3_mprintf("unrecognized matchinfo request: %c", cArg);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
static int fts3MatchinfoSize(MatchInfo *pInfo, char cArg){
|
||||
int nVal; /* Number of integers output by cArg */
|
||||
|
||||
switch( cArg ){
|
||||
case FTS3_MATCHINFO_NDOC:
|
||||
case FTS3_MATCHINFO_NPHRASE:
|
||||
case FTS3_MATCHINFO_NCOL:
|
||||
nVal = 1;
|
||||
break;
|
||||
|
||||
case FTS3_MATCHINFO_AVGLENGTH:
|
||||
case FTS3_MATCHINFO_LENGTH:
|
||||
case FTS3_MATCHINFO_LCS:
|
||||
nVal = pInfo->nCol;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert( cArg==FTS3_MATCHINFO_HITS );
|
||||
nVal = pInfo->nCol * pInfo->nPhrase * 3;
|
||||
break;
|
||||
}
|
||||
|
||||
return nVal;
|
||||
}
|
||||
|
||||
static int fts3MatchinfoSelectDoctotal(
|
||||
Fts3Table *pTab,
|
||||
sqlite3_stmt **ppStmt,
|
||||
sqlite3_int64 *pnDoc,
|
||||
const char **paLen
|
||||
){
|
||||
sqlite3_stmt *pStmt;
|
||||
const char *a;
|
||||
sqlite3_int64 nDoc;
|
||||
|
||||
if( !*ppStmt ){
|
||||
int rc = sqlite3Fts3SelectDoctotal(pTab, ppStmt);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}
|
||||
pStmt = *ppStmt;
|
||||
assert( sqlite3_data_count(pStmt)==1 );
|
||||
|
||||
a = sqlite3_column_blob(pStmt, 0);
|
||||
a += sqlite3Fts3GetVarint(a, &nDoc);
|
||||
*pnDoc = (u32)nDoc;
|
||||
|
||||
if( paLen ) *paLen = a;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** An instance of the following structure is used to store state while
|
||||
** iterating through a multi-column position-list corresponding to the
|
||||
** hits for a single phrase on a single row in order to calculate the
|
||||
** values for a matchinfo() FTS3_MATCHINFO_LCS request.
|
||||
*/
|
||||
typedef struct LcsIterator LcsIterator;
|
||||
struct LcsIterator {
|
||||
Fts3Expr *pExpr; /* Pointer to phrase expression */
|
||||
char *pRead; /* Cursor used to iterate through aDoclist */
|
||||
int iPosOffset; /* Tokens count up to end of this phrase */
|
||||
int iCol; /* Current column number */
|
||||
int iPos; /* Current position */
|
||||
};
|
||||
|
||||
/*
|
||||
** If LcsIterator.iCol is set to the following value, the iterator has
|
||||
** finished iterating through all offsets for all columns.
|
||||
*/
|
||||
#define LCS_ITERATOR_FINISHED 0x7FFFFFFF;
|
||||
|
||||
static int fts3MatchinfoLcsCb(
|
||||
Fts3Expr *pExpr, /* Phrase expression node */
|
||||
int iPhrase, /* Phrase number (numbered from zero) */
|
||||
void *pCtx /* Pointer to MatchInfo structure */
|
||||
){
|
||||
LcsIterator *aIter = (LcsIterator *)pCtx;
|
||||
aIter[iPhrase].pExpr = pExpr;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Advance the iterator passed as an argument to the next position. Return
|
||||
** 1 if the iterator is at EOF or if it now points to the start of the
|
||||
** position list for the next column.
|
||||
*/
|
||||
static int fts3LcsIteratorAdvance(LcsIterator *pIter){
|
||||
char *pRead = pIter->pRead;
|
||||
sqlite3_int64 iRead;
|
||||
int rc = 0;
|
||||
|
||||
pRead += sqlite3Fts3GetVarint(pRead, &iRead);
|
||||
if( iRead==0 ){
|
||||
pIter->iCol = LCS_ITERATOR_FINISHED;
|
||||
rc = 1;
|
||||
}else{
|
||||
if( iRead==1 ){
|
||||
pRead += sqlite3Fts3GetVarint(pRead, &iRead);
|
||||
pIter->iCol = (int)iRead;
|
||||
pIter->iPos = pIter->iPosOffset;
|
||||
pRead += sqlite3Fts3GetVarint(pRead, &iRead);
|
||||
rc = 1;
|
||||
}
|
||||
pIter->iPos += (int)(iRead-2);
|
||||
}
|
||||
|
||||
pIter->pRead = pRead;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function implements the FTS3_MATCHINFO_LCS matchinfo() flag.
|
||||
**
|
||||
** If the call is successful, the longest-common-substring lengths for each
|
||||
** column are written into the first nCol elements of the pInfo->aMatchinfo[]
|
||||
** array before returning. SQLITE_OK is returned in this case.
|
||||
**
|
||||
** Otherwise, if an error occurs, an SQLite error code is returned and the
|
||||
** data written to the first nCol elements of pInfo->aMatchinfo[] is
|
||||
** undefined.
|
||||
*/
|
||||
static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){
|
||||
LcsIterator *aIter;
|
||||
int i;
|
||||
int iCol;
|
||||
int nToken = 0;
|
||||
|
||||
/* Allocate and populate the array of LcsIterator objects. The array
|
||||
** contains one element for each matchable phrase in the query.
|
||||
**/
|
||||
aIter = sqlite3_malloc(sizeof(LcsIterator) * pCsr->nPhrase);
|
||||
if( !aIter ) return SQLITE_NOMEM;
|
||||
memset(aIter, 0, sizeof(LcsIterator) * pCsr->nPhrase);
|
||||
(void)fts3ExprIterate(pCsr->pExpr, fts3MatchinfoLcsCb, (void*)aIter);
|
||||
for(i=0; i<pInfo->nPhrase; i++){
|
||||
LcsIterator *pIter = &aIter[i];
|
||||
nToken -= pIter->pExpr->pPhrase->nToken;
|
||||
pIter->iPosOffset = nToken;
|
||||
pIter->pRead = sqlite3Fts3FindPositions(pIter->pExpr, pCsr->iPrevId, -1);
|
||||
if( pIter->pRead ){
|
||||
pIter->iPos = pIter->iPosOffset;
|
||||
fts3LcsIteratorAdvance(&aIter[i]);
|
||||
}else{
|
||||
pIter->iCol = LCS_ITERATOR_FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
for(iCol=0; iCol<pInfo->nCol; iCol++){
|
||||
int nLcs = 0; /* LCS value for this column */
|
||||
int nLive = 0; /* Number of iterators in aIter not at EOF */
|
||||
|
||||
/* Loop through the iterators in aIter[]. Set nLive to the number of
|
||||
** iterators that point to a position-list corresponding to column iCol.
|
||||
*/
|
||||
for(i=0; i<pInfo->nPhrase; i++){
|
||||
assert( aIter[i].iCol>=iCol );
|
||||
if( aIter[i].iCol==iCol ) nLive++;
|
||||
}
|
||||
|
||||
/* The following loop runs until all iterators in aIter[] have finished
|
||||
** iterating through positions in column iCol. Exactly one of the
|
||||
** iterators is advanced each time the body of the loop is run.
|
||||
*/
|
||||
while( nLive>0 ){
|
||||
LcsIterator *pAdv = 0; /* The iterator to advance by one position */
|
||||
int nThisLcs = 0; /* LCS for the current iterator positions */
|
||||
|
||||
for(i=0; i<pInfo->nPhrase; i++){
|
||||
LcsIterator *pIter = &aIter[i];
|
||||
if( iCol!=pIter->iCol ){
|
||||
/* This iterator is already at EOF for this column. */
|
||||
nThisLcs = 0;
|
||||
}else{
|
||||
if( pAdv==0 || pIter->iPos<pAdv->iPos ){
|
||||
pAdv = pIter;
|
||||
}
|
||||
if( nThisLcs==0 || pIter->iPos==pIter[-1].iPos ){
|
||||
nThisLcs++;
|
||||
}else{
|
||||
nThisLcs = 1;
|
||||
}
|
||||
if( nThisLcs>nLcs ) nLcs = nThisLcs;
|
||||
}
|
||||
}
|
||||
if( fts3LcsIteratorAdvance(pAdv) ) nLive--;
|
||||
}
|
||||
|
||||
pInfo->aMatchinfo[iCol] = nLcs;
|
||||
}
|
||||
|
||||
sqlite3_free(aIter);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Populate the buffer pInfo->aMatchinfo[] with an array of integers to
|
||||
** be returned by the matchinfo() function. Argument zArg contains the
|
||||
** format string passed as the second argument to matchinfo (or the
|
||||
** default value "pcx" if no second argument was specified). The format
|
||||
** string has already been validated and the pInfo->aMatchinfo[] array
|
||||
** is guaranteed to be large enough for the output.
|
||||
**
|
||||
** If bGlobal is true, then populate all fields of the matchinfo() output.
|
||||
** If it is false, then assume that those fields that do not change between
|
||||
** rows (i.e. FTS3_MATCHINFO_NPHRASE, NCOL, NDOC, AVGLENGTH and part of HITS)
|
||||
** have already been populated.
|
||||
**
|
||||
** Return SQLITE_OK if successful, or an SQLite error code if an error
|
||||
** occurs. If a value other than SQLITE_OK is returned, the state the
|
||||
** pInfo->aMatchinfo[] buffer is left in is undefined.
|
||||
*/
|
||||
static int fts3MatchinfoValues(
|
||||
Fts3Cursor *pCsr, /* FTS3 cursor object */
|
||||
int bGlobal, /* True to grab the global stats */
|
||||
MatchInfo *pInfo, /* Matchinfo context object */
|
||||
const char *zArg /* Matchinfo format string */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
int i;
|
||||
Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
|
||||
sqlite3_stmt *pSelect = 0;
|
||||
|
||||
for(i=0; rc==SQLITE_OK && zArg[i]; i++){
|
||||
|
||||
switch( zArg[i] ){
|
||||
case FTS3_MATCHINFO_NPHRASE:
|
||||
if( bGlobal ) pInfo->aMatchinfo[0] = pInfo->nPhrase;
|
||||
break;
|
||||
|
||||
case FTS3_MATCHINFO_NCOL:
|
||||
if( bGlobal ) pInfo->aMatchinfo[0] = pInfo->nCol;
|
||||
break;
|
||||
|
||||
case FTS3_MATCHINFO_NDOC:
|
||||
if( bGlobal ){
|
||||
sqlite3_int64 nDoc;
|
||||
rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, 0);
|
||||
pInfo->aMatchinfo[0] = (u32)nDoc;
|
||||
}
|
||||
break;
|
||||
|
||||
case FTS3_MATCHINFO_AVGLENGTH:
|
||||
if( bGlobal ){
|
||||
sqlite3_int64 nDoc; /* Number of rows in table */
|
||||
const char *a; /* Aggregate column length array */
|
||||
|
||||
rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, &a);
|
||||
if( rc==SQLITE_OK ){
|
||||
int iCol;
|
||||
for(iCol=0; iCol<pInfo->nCol; iCol++){
|
||||
sqlite3_int64 nToken;
|
||||
a += sqlite3Fts3GetVarint(a, &nToken);
|
||||
pInfo->aMatchinfo[iCol] = (u32)(((u32)(nToken&0xffffffff)+nDoc/2)/nDoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FTS3_MATCHINFO_LENGTH: {
|
||||
sqlite3_stmt *pSelectDocsize = 0;
|
||||
rc = sqlite3Fts3SelectDocsize(pTab, pCsr->iPrevId, &pSelectDocsize);
|
||||
if( rc==SQLITE_OK ){
|
||||
int iCol;
|
||||
const char *a = sqlite3_column_blob(pSelectDocsize, 0);
|
||||
for(iCol=0; iCol<pInfo->nCol; iCol++){
|
||||
sqlite3_int64 nToken;
|
||||
a += sqlite3Fts3GetVarint(a, &nToken);
|
||||
pInfo->aMatchinfo[iCol] = (u32)nToken;
|
||||
}
|
||||
}
|
||||
sqlite3_reset(pSelectDocsize);
|
||||
break;
|
||||
}
|
||||
|
||||
case FTS3_MATCHINFO_LCS:
|
||||
rc = fts3ExprLoadDoclists(pCsr, 0, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts3MatchinfoLcs(pCsr, pInfo);
|
||||
}
|
||||
break;
|
||||
|
||||
default: {
|
||||
Fts3Expr *pExpr;
|
||||
assert( zArg[i]==FTS3_MATCHINFO_HITS );
|
||||
pExpr = pCsr->pExpr;
|
||||
rc = fts3ExprLoadDoclists(pCsr, 0, 0);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
if( bGlobal ){
|
||||
if( pCsr->pDeferred ){
|
||||
rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &pInfo->nDoc, 0);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
}
|
||||
rc = fts3ExprIterate(pExpr, fts3ExprGlobalHitsCb,(void*)pInfo);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
}
|
||||
(void)fts3ExprIterate(pExpr, fts3ExprLocalHitsCb,(void*)pInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pInfo->aMatchinfo += fts3MatchinfoSize(pInfo, zArg[i]);
|
||||
}
|
||||
|
||||
sqlite3_reset(pSelect);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Populate pCsr->aMatchinfo[] with data for the current row. The
|
||||
** 'matchinfo' data is an array of 32-bit unsigned integers (C type u32).
|
||||
*/
|
||||
static int fts3GetMatchinfo(Fts3Cursor *pCsr){
|
||||
static int fts3GetMatchinfo(
|
||||
Fts3Cursor *pCsr, /* FTS3 Cursor object */
|
||||
const char *zArg /* Second argument to matchinfo() function */
|
||||
){
|
||||
MatchInfo sInfo;
|
||||
Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
|
||||
int rc = SQLITE_OK;
|
||||
int bGlobal = 0; /* Collect 'global' stats as well as local */
|
||||
|
||||
memset(&sInfo, 0, sizeof(MatchInfo));
|
||||
sInfo.pCursor = pCsr;
|
||||
sInfo.nCol = pTab->nColumn;
|
||||
|
||||
/* If there is cached matchinfo() data, but the format string for the
|
||||
** cache does not match the format string for this request, discard
|
||||
** the cached data. */
|
||||
if( pCsr->zMatchinfo && strcmp(pCsr->zMatchinfo, zArg) ){
|
||||
assert( pCsr->aMatchinfo );
|
||||
sqlite3_free(pCsr->aMatchinfo);
|
||||
pCsr->zMatchinfo = 0;
|
||||
pCsr->aMatchinfo = 0;
|
||||
}
|
||||
|
||||
/* If Fts3Cursor.aMatchinfo[] is NULL, then this is the first time the
|
||||
** matchinfo function has been called for this query. In this case
|
||||
** allocate the array used to accumulate the matchinfo data and
|
||||
** initialize those elements that are constant for every row.
|
||||
*/
|
||||
if( pCsr->aMatchinfo==0 ){
|
||||
/* If Fts3Cursor.aMatchinfo[] is NULL, then this is the first time the
|
||||
** matchinfo function has been called for this query. In this case
|
||||
** allocate the array used to accumulate the matchinfo data and
|
||||
** initialize those elements that are constant for every row.
|
||||
*/
|
||||
int nPhrase; /* Number of phrases */
|
||||
int nMatchinfo; /* Number of u32 elements in match-info */
|
||||
int nMatchinfo = 0; /* Number of u32 elements in match-info */
|
||||
int nArg; /* Bytes in zArg */
|
||||
int i; /* Used to iterate through zArg */
|
||||
|
||||
/* Load doclists for each phrase in the query. */
|
||||
rc = fts3ExprLoadDoclists(pCsr, &nPhrase, 0);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
nMatchinfo = 2 + 3*sInfo.nCol*nPhrase;
|
||||
if( pTab->bHasDocsize ){
|
||||
nMatchinfo += 1 + 2*pTab->nColumn;
|
||||
/* Determine the number of phrases in the query */
|
||||
pCsr->nPhrase = fts3ExprPhraseCount(pCsr->pExpr);
|
||||
sInfo.nPhrase = pCsr->nPhrase;
|
||||
|
||||
/* Determine the number of integers in the buffer returned by this call. */
|
||||
for(i=0; zArg[i]; i++){
|
||||
nMatchinfo += fts3MatchinfoSize(&sInfo, zArg[i]);
|
||||
}
|
||||
|
||||
sInfo.aMatchinfo = (u32 *)sqlite3_malloc(sizeof(u32)*nMatchinfo);
|
||||
if( !sInfo.aMatchinfo ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
memset(sInfo.aMatchinfo, 0, sizeof(u32)*nMatchinfo);
|
||||
/* Allocate space for Fts3Cursor.aMatchinfo[] and Fts3Cursor.zMatchinfo. */
|
||||
nArg = (int)strlen(zArg);
|
||||
pCsr->aMatchinfo = (u32 *)sqlite3_malloc(sizeof(u32)*nMatchinfo + nArg + 1);
|
||||
if( !pCsr->aMatchinfo ) return SQLITE_NOMEM;
|
||||
|
||||
|
||||
/* First element of match-info is the number of phrases in the query */
|
||||
sInfo.aMatchinfo[0] = nPhrase;
|
||||
sInfo.aMatchinfo[1] = sInfo.nCol;
|
||||
(void)fts3ExprIterate(pCsr->pExpr, fts3ExprGlobalMatchinfoCb,(void*)&sInfo);
|
||||
if( pTab->bHasDocsize ){
|
||||
int ofst = 2 + 3*sInfo.aMatchinfo[0]*sInfo.aMatchinfo[1];
|
||||
rc = sqlite3Fts3MatchinfoDocsizeGlobal(pCsr, &sInfo.aMatchinfo[ofst]);
|
||||
}
|
||||
pCsr->aMatchinfo = sInfo.aMatchinfo;
|
||||
pCsr->zMatchinfo = (char *)&pCsr->aMatchinfo[nMatchinfo];
|
||||
pCsr->nMatchinfo = nMatchinfo;
|
||||
memcpy(pCsr->zMatchinfo, zArg, nArg+1);
|
||||
memset(pCsr->aMatchinfo, 0, sizeof(u32)*nMatchinfo);
|
||||
pCsr->isMatchinfoNeeded = 1;
|
||||
bGlobal = 1;
|
||||
}
|
||||
|
||||
sInfo.aMatchinfo = pCsr->aMatchinfo;
|
||||
if( rc==SQLITE_OK && pCsr->isMatchinfoNeeded ){
|
||||
(void)fts3ExprIterate(pCsr->pExpr, fts3ExprLocalMatchinfoCb, (void*)&sInfo);
|
||||
if( pTab->bHasDocsize ){
|
||||
int ofst = 2 + 3*sInfo.aMatchinfo[0]*sInfo.aMatchinfo[1];
|
||||
rc = sqlite3Fts3MatchinfoDocsizeLocal(pCsr, &sInfo.aMatchinfo[ofst]);
|
||||
}
|
||||
sInfo.nPhrase = pCsr->nPhrase;
|
||||
if( pCsr->isMatchinfoNeeded ){
|
||||
rc = fts3MatchinfoValues(pCsr, bGlobal, &sInfo, zArg);
|
||||
pCsr->isMatchinfoNeeded = 0;
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -958,7 +1351,7 @@ void sqlite3Fts3Snippet(
|
||||
** columns of the FTS3 table. Otherwise, only column iCol is considered.
|
||||
*/
|
||||
for(iRead=0; iRead<pTab->nColumn; iRead++){
|
||||
SnippetFragment sF;
|
||||
SnippetFragment sF = {0, 0, 0, 0};
|
||||
int iS;
|
||||
if( iCol>=0 && iRead!=iCol ) continue;
|
||||
|
||||
@ -992,6 +1385,7 @@ void sqlite3Fts3Snippet(
|
||||
}
|
||||
|
||||
snippet_out:
|
||||
sqlite3Fts3SegmentsClose(pTab);
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3_result_error_code(pCtx, rc);
|
||||
sqlite3_free(res.z);
|
||||
@ -1171,6 +1565,7 @@ void sqlite3Fts3Offsets(
|
||||
offsets_out:
|
||||
sqlite3_free(sCtx.aTerm);
|
||||
assert( rc!=SQLITE_DONE );
|
||||
sqlite3Fts3SegmentsClose(pTab);
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3_result_error_code(pCtx, rc);
|
||||
sqlite3_free(res.z);
|
||||
@ -1183,21 +1578,43 @@ void sqlite3Fts3Offsets(
|
||||
/*
|
||||
** Implementation of matchinfo() function.
|
||||
*/
|
||||
void sqlite3Fts3Matchinfo(sqlite3_context *pContext, Fts3Cursor *pCsr){
|
||||
void sqlite3Fts3Matchinfo(
|
||||
sqlite3_context *pContext, /* Function call context */
|
||||
Fts3Cursor *pCsr, /* FTS3 table cursor */
|
||||
const char *zArg /* Second arg to matchinfo() function */
|
||||
){
|
||||
Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
|
||||
int rc;
|
||||
int i;
|
||||
const char *zFormat;
|
||||
|
||||
if( zArg ){
|
||||
for(i=0; zArg[i]; i++){
|
||||
char *zErr = 0;
|
||||
if( fts3MatchinfoCheck(pTab, zArg[i], &zErr) ){
|
||||
sqlite3_result_error(pContext, zErr, -1);
|
||||
sqlite3_free(zErr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
zFormat = zArg;
|
||||
}else{
|
||||
zFormat = FTS3_MATCHINFO_DEFAULT;
|
||||
}
|
||||
|
||||
if( !pCsr->pExpr ){
|
||||
sqlite3_result_blob(pContext, "", 0, SQLITE_STATIC);
|
||||
return;
|
||||
}
|
||||
rc = fts3GetMatchinfo(pCsr);
|
||||
|
||||
/* Retrieve matchinfo() data. */
|
||||
rc = fts3GetMatchinfo(pCsr, zFormat);
|
||||
sqlite3Fts3SegmentsClose(pTab);
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3_result_error_code(pContext, rc);
|
||||
}else{
|
||||
Fts3Table *pTab = (Fts3Table*)pCsr->base.pVtab;
|
||||
int n = sizeof(u32)*(2+pCsr->aMatchinfo[0]*pCsr->aMatchinfo[1]*3);
|
||||
if( pTab->bHasDocsize ){
|
||||
n += sizeof(u32)*(1 + 2*pTab->nColumn);
|
||||
}
|
||||
int n = pCsr->nMatchinfo * sizeof(u32);
|
||||
sqlite3_result_blob(pContext, pCsr->aMatchinfo, n, SQLITE_TRANSIENT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +97,7 @@ static void scalarFunc(
|
||||
sqlite3_result_blob(context, (void *)&pPtr, sizeof(pPtr), SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
static int fts3IsIdChar(char c){
|
||||
int sqlite3Fts3IsIdChar(char c){
|
||||
static const char isFtsIdChar[] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */
|
||||
@ -135,9 +135,9 @@ const char *sqlite3Fts3NextToken(const char *zStr, int *pn){
|
||||
break;
|
||||
|
||||
default:
|
||||
if( fts3IsIdChar(*z1) ){
|
||||
if( sqlite3Fts3IsIdChar(*z1) ){
|
||||
z2 = &z1[1];
|
||||
while( fts3IsIdChar(*z2) ) z2++;
|
||||
while( sqlite3Fts3IsIdChar(*z2) ) z2++;
|
||||
}else{
|
||||
z1++;
|
||||
}
|
||||
@ -150,9 +150,8 @@ const char *sqlite3Fts3NextToken(const char *zStr, int *pn){
|
||||
|
||||
int sqlite3Fts3InitTokenizer(
|
||||
Fts3Hash *pHash, /* Tokenizer hash table */
|
||||
const char *zArg, /* Possible tokenizer specification */
|
||||
const char *zArg, /* Tokenizer name */
|
||||
sqlite3_tokenizer **ppTok, /* OUT: Tokenizer (if applicable) */
|
||||
const char **pzTokenizer, /* OUT: Set to zArg if is tokenizer */
|
||||
char **pzErr /* OUT: Set to malloced error message */
|
||||
){
|
||||
int rc;
|
||||
@ -162,26 +161,15 @@ int sqlite3Fts3InitTokenizer(
|
||||
char *zEnd; /* Pointer to nul-term of zCopy */
|
||||
sqlite3_tokenizer_module *m;
|
||||
|
||||
if( !z ){
|
||||
zCopy = sqlite3_mprintf("simple");
|
||||
}else{
|
||||
if( sqlite3_strnicmp(z, "tokenize", 8) || fts3IsIdChar(z[8])){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
zCopy = sqlite3_mprintf("%s", &z[8]);
|
||||
*pzTokenizer = zArg;
|
||||
}
|
||||
if( !zCopy ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
zCopy = sqlite3_mprintf("%s", zArg);
|
||||
if( !zCopy ) return SQLITE_NOMEM;
|
||||
zEnd = &zCopy[strlen(zCopy)];
|
||||
|
||||
z = (char *)sqlite3Fts3NextToken(zCopy, &n);
|
||||
z[n] = '\0';
|
||||
sqlite3Fts3Dequote(z);
|
||||
|
||||
m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash, z, (int)strlen(z)+1);
|
||||
m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash,z,(int)strlen(z)+1);
|
||||
if( !m ){
|
||||
*pzErr = sqlite3_mprintf("unknown tokenizer: %s", z);
|
||||
rc = SQLITE_ERROR;
|
||||
@ -477,15 +465,23 @@ int sqlite3Fts3InitHashTable(
|
||||
}
|
||||
#endif
|
||||
|
||||
if( SQLITE_OK!=rc
|
||||
|| SQLITE_OK!=(rc = sqlite3_create_function(db, zName, 1, any, p, scalarFunc, 0, 0))
|
||||
|| SQLITE_OK!=(rc = sqlite3_create_function(db, zName, 2, any, p, scalarFunc, 0, 0))
|
||||
if( SQLITE_OK==rc ){
|
||||
rc = sqlite3_create_function(db, zName, 1, any, p, scalarFunc, 0, 0);
|
||||
}
|
||||
if( SQLITE_OK==rc ){
|
||||
rc = sqlite3_create_function(db, zName, 2, any, p, scalarFunc, 0, 0);
|
||||
}
|
||||
#ifdef SQLITE_TEST
|
||||
|| SQLITE_OK!=(rc = sqlite3_create_function(db, zTest, 2, any, p, testFunc, 0, 0))
|
||||
|| SQLITE_OK!=(rc = sqlite3_create_function(db, zTest, 3, any, p, testFunc, 0, 0))
|
||||
|| SQLITE_OK!=(rc = sqlite3_create_function(db, zTest2, 0, any, pdb, intTestFunc, 0, 0))
|
||||
if( SQLITE_OK==rc ){
|
||||
rc = sqlite3_create_function(db, zTest, 2, any, p, testFunc, 0, 0);
|
||||
}
|
||||
if( SQLITE_OK==rc ){
|
||||
rc = sqlite3_create_function(db, zTest, 3, any, p, testFunc, 0, 0);
|
||||
}
|
||||
if( SQLITE_OK==rc ){
|
||||
rc = sqlite3_create_function(db, zTest2, 0, any, pdb, intTestFunc, 0, 0);
|
||||
}
|
||||
#endif
|
||||
);
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
sqlite3_free(zTest);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
122
ext/fts3/fts3speed.tcl
Normal file
122
ext/fts3/fts3speed.tcl
Normal file
@ -0,0 +1,122 @@
|
||||
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# This script contains several sub-programs used to test FTS3/FTS4
|
||||
# performance. It does not run the queries directly, but generates SQL
|
||||
# scripts that can be run using the shell tool.
|
||||
#
|
||||
# The following cases are tested:
|
||||
#
|
||||
# 1. Inserting documents into an FTS3 table.
|
||||
# 2. Optimizing an FTS3 table (i.e. "INSERT INTO t1 VALUES('optimize')").
|
||||
# 3. Deleting documents from an FTS3 table.
|
||||
# 4. Querying FTS3 tables.
|
||||
#
|
||||
|
||||
# Number of tokens in vocabulary. And number of tokens in each document.
|
||||
#
|
||||
set VOCAB_SIZE 2000
|
||||
set DOC_SIZE 100
|
||||
|
||||
set NUM_INSERTS 100000
|
||||
set NUM_SELECTS 1000
|
||||
|
||||
# Force everything in this script to be deterministic.
|
||||
#
|
||||
expr {srand(0)}
|
||||
|
||||
proc usage {} {
|
||||
puts stderr "Usage: $::argv0 <rows> <selects>"
|
||||
exit -1
|
||||
}
|
||||
|
||||
proc sql {sql} {
|
||||
puts $::fd $sql
|
||||
}
|
||||
|
||||
|
||||
# Return a list of $nWord randomly generated tokens each between 2 and 10
|
||||
# characters in length.
|
||||
#
|
||||
proc build_vocab {nWord} {
|
||||
set ret [list]
|
||||
set chars [list a b c d e f g h i j k l m n o p q r s t u v w x y z]
|
||||
for {set i 0} {$i<$nWord} {incr i} {
|
||||
set len [expr {int((rand()*9.0)+2)}]
|
||||
set term ""
|
||||
for {set j 0} {$j<$len} {incr j} {
|
||||
append term [lindex $chars [expr {int(rand()*[llength $chars])}]]
|
||||
}
|
||||
lappend ret $term
|
||||
}
|
||||
set ret
|
||||
}
|
||||
|
||||
proc select_term {} {
|
||||
set n [llength $::vocab]
|
||||
set t [expr int(rand()*$n*3)]
|
||||
if {$t>=2*$n} { set t [expr {($t-2*$n)/100}] }
|
||||
if {$t>=$n} { set t [expr {($t-$n)/10}] }
|
||||
lindex $::vocab $t
|
||||
}
|
||||
|
||||
proc select_doc {nTerm} {
|
||||
set ret [list]
|
||||
for {set i 0} {$i<$nTerm} {incr i} {
|
||||
lappend ret [select_term]
|
||||
}
|
||||
set ret
|
||||
}
|
||||
|
||||
proc test_1 {nInsert} {
|
||||
sql "PRAGMA synchronous = OFF;"
|
||||
sql "DROP TABLE IF EXISTS t1;"
|
||||
sql "CREATE VIRTUAL TABLE t1 USING fts4;"
|
||||
for {set i 0} {$i < $nInsert} {incr i} {
|
||||
set doc [select_doc $::DOC_SIZE]
|
||||
sql "INSERT INTO t1 VALUES('$doc');"
|
||||
}
|
||||
}
|
||||
|
||||
proc test_2 {} {
|
||||
sql "INSERT INTO t1(t1) VALUES('optimize');"
|
||||
}
|
||||
|
||||
proc test_3 {nSelect} {
|
||||
for {set i 0} {$i < $nSelect} {incr i} {
|
||||
sql "SELECT count(*) FROM t1 WHERE t1 MATCH '[select_term]';"
|
||||
}
|
||||
}
|
||||
|
||||
proc test_4 {nSelect} {
|
||||
for {set i 0} {$i < $nSelect} {incr i} {
|
||||
sql "SELECT count(*) FROM t1 WHERE t1 MATCH '[select_term] [select_term]';"
|
||||
}
|
||||
}
|
||||
|
||||
if {[llength $argv]!=0} usage
|
||||
|
||||
set ::vocab [build_vocab $::VOCAB_SIZE]
|
||||
|
||||
set ::fd [open fts3speed_insert.sql w]
|
||||
test_1 $NUM_INSERTS
|
||||
close $::fd
|
||||
|
||||
set ::fd [open fts3speed_select.sql w]
|
||||
test_3 $NUM_SELECTS
|
||||
close $::fd
|
||||
|
||||
set ::fd [open fts3speed_select2.sql w]
|
||||
test_4 $NUM_SELECTS
|
||||
close $::fd
|
||||
|
||||
set ::fd [open fts3speed_optimize.sql w]
|
||||
test_2
|
||||
close $::fd
|
||||
|
||||
puts "Success. Created files:"
|
||||
puts " fts3speed_insert.sql"
|
||||
puts " fts3speed_select.sql"
|
||||
puts " fts3speed_select2.sql"
|
||||
puts " fts3speed_optimize.sql"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname $argv0] .. .. test]
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] rtree_util.tcl]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname $argv0] .. .. test]
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] rtree_util.tcl]
|
||||
source $testdir/tester.tcl
|
||||
@ -27,7 +27,7 @@ set ::NROW 1000
|
||||
set ::NDEL 10
|
||||
set ::NSELECT 100
|
||||
|
||||
if {[info exists ISQUICK] && $ISQUICK} {
|
||||
if {[info exists G(isquick)] && $G(isquick)} {
|
||||
set ::NROW 100
|
||||
set ::NSELECT 10
|
||||
}
|
||||
|
||||
@ -14,52 +14,93 @@
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname $argv0] .. .. test]
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
|
||||
source $testdir/malloc_common.tcl
|
||||
ifcapable !rtree {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
# Only run these tests if memory debugging is turned on.
|
||||
# Test summary:
|
||||
#
|
||||
source $testdir/malloc_common.tcl
|
||||
if {!$MEMDEBUG} {
|
||||
puts "Skipping malloc tests: not compiled with -DSQLITE_MEMDEBUG..."
|
||||
finish_test
|
||||
return
|
||||
# rtree3-1: Test OOM in simple CREATE TABLE, INSERT, DELETE and SELECT
|
||||
# commands on an almost empty table.
|
||||
#
|
||||
# rtree3-2: Test OOM in a DROP TABLE command.
|
||||
#
|
||||
# rtree3-3a: Test OOM during a transaction to insert 100 pseudo-random rows.
|
||||
#
|
||||
# rtree3-3b: Test OOM during a transaction deleting all entries in the
|
||||
# database constructed in [rtree3-3a] in pseudo-random order.
|
||||
#
|
||||
# rtree3-4a: OOM during "SELECT count(*) FROM ..." on a big table.
|
||||
#
|
||||
# rtree3-4b: OOM while deleting rows from a big table.
|
||||
#
|
||||
# rtree3-5: Test OOM while inserting rows into a big table.
|
||||
#
|
||||
# rtree3-6: Test OOM while deleting all rows of a table, one at a time.
|
||||
#
|
||||
# rtree3-7: OOM during an ALTER TABLE RENAME TABLE command.
|
||||
#
|
||||
# rtree3-8: Test OOM while registering the r-tree module with sqlite.
|
||||
#
|
||||
|
||||
do_faultsim_test rtree3-1 -faults oom* -prep {
|
||||
faultsim_delete_and_reopen
|
||||
} -body {
|
||||
execsql {
|
||||
BEGIN TRANSACTION;
|
||||
CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
|
||||
INSERT INTO rt VALUES(NULL, 3, 5, 7, 9);
|
||||
INSERT INTO rt VALUES(NULL, 13, 15, 17, 19);
|
||||
DELETE FROM rt WHERE ii = 1;
|
||||
SELECT * FROM rt;
|
||||
SELECT ii FROM rt WHERE ii = 2;
|
||||
COMMIT;
|
||||
}
|
||||
}
|
||||
|
||||
do_malloc_test rtree3-1 -sqlbody {
|
||||
BEGIN TRANSACTION;
|
||||
CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
|
||||
INSERT INTO rt VALUES(NULL, 3, 5, 7, 9);
|
||||
INSERT INTO rt VALUES(NULL, 13, 15, 17, 19);
|
||||
DELETE FROM rt WHERE ii = 1;
|
||||
SELECT * FROM rt;
|
||||
SELECT ii FROM rt WHERE ii = 2;
|
||||
COMMIT;
|
||||
}
|
||||
do_malloc_test rtree3-2 -sqlprep {
|
||||
CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
|
||||
INSERT INTO rt VALUES(NULL, 3, 5, 7, 9);
|
||||
} -sqlbody {
|
||||
DROP TABLE rt;
|
||||
}
|
||||
do_test rtree3-2.prep {
|
||||
faultsim_delete_and_reopen
|
||||
execsql {
|
||||
CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
|
||||
INSERT INTO rt VALUES(NULL, 3, 5, 7, 9);
|
||||
}
|
||||
faultsim_save_and_close
|
||||
} {}
|
||||
do_faultsim_test rtree3-2 -faults oom* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
execsql { DROP TABLE rt }
|
||||
}
|
||||
|
||||
do_malloc_test rtree3-3.prep {
|
||||
faultsim_delete_and_reopen
|
||||
execsql {
|
||||
CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
|
||||
INSERT INTO rt VALUES(NULL, 3, 5, 7, 9);
|
||||
}
|
||||
faultsim_save_and_close
|
||||
} {}
|
||||
|
||||
do_malloc_test rtree3-3 -sqlprep {
|
||||
CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
|
||||
INSERT INTO rt VALUES(NULL, 3, 5, 7, 9);
|
||||
} -tclbody {
|
||||
do_faultsim_test rtree3-3a -faults oom* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
db eval BEGIN
|
||||
for {set ii 0} {$ii < 100} {incr ii} {
|
||||
set f [expr rand()]
|
||||
db eval {INSERT INTO rt VALUES(NULL, $f*10.0, $f*10.0, $f*15.0, $f*15.0)}
|
||||
}
|
||||
db eval COMMIT
|
||||
}
|
||||
faultsim_save_and_close
|
||||
|
||||
do_faultsim_test rtree3-3b -faults oom* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
db eval BEGIN
|
||||
for {set ii 0} {$ii < 100} {incr ii} {
|
||||
set f [expr rand()]
|
||||
@ -68,4 +109,129 @@ do_malloc_test rtree3-3 -sqlprep {
|
||||
db eval COMMIT
|
||||
}
|
||||
|
||||
do_test rtree3-4.prep {
|
||||
faultsim_delete_and_reopen
|
||||
execsql {
|
||||
BEGIN;
|
||||
PRAGMA page_size = 512;
|
||||
CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
|
||||
}
|
||||
for {set i 0} {$i < 1500} {incr i} {
|
||||
execsql { INSERT INTO rt VALUES($i, $i, $i+1, $i, $i+1) }
|
||||
}
|
||||
execsql { COMMIT }
|
||||
faultsim_save_and_close
|
||||
} {}
|
||||
|
||||
do_faultsim_test rtree3-4a -faults oom-* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
db eval { SELECT count(*) FROM rt }
|
||||
} -test {
|
||||
faultsim_test_result {0 1500}
|
||||
}
|
||||
|
||||
do_faultsim_test rtree3-4b -faults oom-transient -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
db eval { DELETE FROM rt WHERE ii BETWEEN 1 AND 100 }
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
}
|
||||
|
||||
do_test rtree3-5.prep {
|
||||
faultsim_delete_and_reopen
|
||||
execsql {
|
||||
BEGIN;
|
||||
PRAGMA page_size = 512;
|
||||
CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
|
||||
}
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
execsql { INSERT INTO rt VALUES($i, $i, $i+1, $i, $i+1) }
|
||||
}
|
||||
execsql { COMMIT }
|
||||
faultsim_save_and_close
|
||||
} {}
|
||||
do_faultsim_test rtree3-5 -faults oom-* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
for {set i 100} {$i < 110} {incr i} {
|
||||
execsql { INSERT INTO rt VALUES($i, $i, $i+1, $i, $i+1) }
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
}
|
||||
|
||||
do_test rtree3-6.prep {
|
||||
faultsim_delete_and_reopen
|
||||
execsql {
|
||||
BEGIN;
|
||||
PRAGMA page_size = 512;
|
||||
CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
|
||||
}
|
||||
for {set i 0} {$i < 50} {incr i} {
|
||||
execsql { INSERT INTO rt VALUES($i, $i, $i+1, $i, $i+1) }
|
||||
}
|
||||
execsql { COMMIT }
|
||||
faultsim_save_and_close
|
||||
} {}
|
||||
do_faultsim_test rtree3-6 -faults oom-* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
execsql BEGIN
|
||||
for {set i 0} {$i < 50} {incr i} {
|
||||
execsql { DELETE FROM rt WHERE ii=$i }
|
||||
}
|
||||
execsql COMMIT
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
}
|
||||
|
||||
do_test rtree3-7.prep {
|
||||
faultsim_delete_and_reopen
|
||||
execsql { CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2) }
|
||||
faultsim_save_and_close
|
||||
} {}
|
||||
do_faultsim_test rtree3-7 -faults oom-* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
execsql { ALTER TABLE rt RENAME TO rt2 }
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
}
|
||||
|
||||
do_faultsim_test rtree3-8 -faults oom-* -prep {
|
||||
catch { db close }
|
||||
} -body {
|
||||
sqlite3 db test.db
|
||||
}
|
||||
|
||||
do_faultsim_test rtree3-9 -faults oom-* -prep {
|
||||
sqlite3 db :memory:
|
||||
} -body {
|
||||
set rc [register_cube_geom db]
|
||||
if {$rc != "SQLITE_OK"} { error $rc }
|
||||
} -test {
|
||||
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
|
||||
}
|
||||
|
||||
do_test rtree3-10.prep {
|
||||
faultsim_delete_and_reopen
|
||||
execsql {
|
||||
CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2, z1, z2);
|
||||
INSERT INTO rt VALUES(1, 10, 10, 10, 11, 11, 11);
|
||||
INSERT INTO rt VALUES(2, 5, 6, 6, 7, 7, 8);
|
||||
}
|
||||
faultsim_save_and_close
|
||||
} {}
|
||||
do_faultsim_test rtree3-10 -faults oom-* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
register_cube_geom db
|
||||
execsql { SELECT * FROM rt }
|
||||
} -body {
|
||||
execsql { SELECT ii FROM rt WHERE ii MATCH cube(4.5, 5.5, 6.5, 1, 1, 1) }
|
||||
} -test {
|
||||
faultsim_test_result {0 2}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname $argv0] .. .. test]
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
|
||||
@ -23,7 +23,7 @@ ifcapable !rtree {
|
||||
}
|
||||
|
||||
set ::NROW 2500
|
||||
if {[info exists ISQUICK] && $ISQUICK} {
|
||||
if {[info exists G(isquick)] && $G(isquick)} {
|
||||
set ::NROW 250
|
||||
}
|
||||
|
||||
@ -93,7 +93,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} {
|
||||
for {set i 1} {$i<$::NROW} {incr i} {
|
||||
# Do a random insert
|
||||
#
|
||||
do_test rtree-$nDim.2.$i.1 {
|
||||
do_test rtree4-$nDim.2.$i.1 {
|
||||
set vlist {}
|
||||
for {set j 0} {$j<$nDim} {incr j} {
|
||||
set mn [rand 10000]
|
||||
@ -113,7 +113,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} {
|
||||
lappend where mn$j>=$mn mx$j<=$mx
|
||||
}
|
||||
set where "WHERE [join $where { AND }]"
|
||||
do_test rtree-$nDim.2.$i.2 {
|
||||
do_test rtree4-$nDim.2.$i.2 {
|
||||
list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
|
||||
} [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
|
||||
|
||||
@ -126,7 +126,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} {
|
||||
lappend where mx$j>=$mn mn$j<=$mx
|
||||
}
|
||||
set where "WHERE [join $where { AND }]"
|
||||
do_test rtree-$nDim.2.$i.3 {
|
||||
do_test rtree4-$nDim.2.$i.3 {
|
||||
list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
|
||||
} [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
|
||||
|
||||
@ -143,7 +143,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} {
|
||||
lappend where mn$j>=$mn mx$j<=$mx
|
||||
}
|
||||
set where "WHERE [join $where { AND }]"
|
||||
do_test rtree-$nDim.2.$i.3 {
|
||||
do_test rtree4-$nDim.2.$i.3 {
|
||||
list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
|
||||
} [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
|
||||
|
||||
@ -160,7 +160,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} {
|
||||
lappend where mx$j>$mn mn$j<$mx
|
||||
}
|
||||
set where "WHERE [join $where { AND }]"
|
||||
do_test rtree-$nDim.2.$i.4 {
|
||||
do_test rtree4-$nDim.2.$i.4 {
|
||||
list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
|
||||
} [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
|
||||
|
||||
@ -176,7 +176,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} {
|
||||
lappend where mn$j>=-10000 mx$j<10000
|
||||
}
|
||||
set where "WHERE [join $where { AND }]"
|
||||
do_test rtree-$nDim.2.$i.5 {
|
||||
do_test rtree4-$nDim.2.$i.5 {
|
||||
list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
|
||||
} [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
|
||||
|
||||
@ -192,7 +192,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} {
|
||||
lappend where mx$j>-10000 mn$j<=10000
|
||||
}
|
||||
set where "WHERE [join $where { AND }]"
|
||||
do_test rtree-$nDim.2.$i.6 {
|
||||
do_test rtree4-$nDim.2.$i.6 {
|
||||
list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
|
||||
} [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
|
||||
|
||||
@ -208,7 +208,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} {
|
||||
lappend where mn$j>=$mn1 mn$j>$mn2 mx$j<$mx1 mx$j<=$mx2
|
||||
}
|
||||
set where "WHERE [join [scramble $where] { AND }]"
|
||||
do_test rtree-$nDim.2.$i.7 {
|
||||
do_test rtree4-$nDim.2.$i.7 {
|
||||
list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
|
||||
} [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
|
||||
|
||||
@ -224,7 +224,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} {
|
||||
lappend where mx$j>=$mn1 mx$j>$mn2 mn$j<$mx1 mn$j<=$mx2
|
||||
}
|
||||
set where "WHERE [join [scramble $where] { AND }]"
|
||||
do_test rtree-$nDim.2.$i.8 {
|
||||
do_test rtree4-$nDim.2.$i.8 {
|
||||
list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
|
||||
} [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname $argv0] .. .. test]
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname $argv0] .. .. test]
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
|
||||
@ -71,39 +71,39 @@ do_test rtree6-1.5 {
|
||||
rtree_strategy {SELECT * FROM t1,t2 WHERE k=+ii AND x1<10}
|
||||
} {Ca}
|
||||
|
||||
do_test rtree6.2.1 {
|
||||
query_plan {SELECT * FROM t1,t2 WHERE k=+ii AND x1<10}
|
||||
} [list \
|
||||
{TABLE t1 VIRTUAL TABLE INDEX 2:Ca} \
|
||||
{TABLE t2 USING PRIMARY KEY} \
|
||||
]
|
||||
do_eqp_test rtree6.2.1 {
|
||||
SELECT * FROM t1,t2 WHERE k=+ii AND x1<10
|
||||
} {
|
||||
0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2:Ca (~0 rows)}
|
||||
0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}
|
||||
}
|
||||
|
||||
do_test rtree6.2.2 {
|
||||
query_plan {SELECT * FROM t1,t2 WHERE k=ii AND x1<10}
|
||||
} [list \
|
||||
{TABLE t1 VIRTUAL TABLE INDEX 2:Ca} \
|
||||
{TABLE t2 USING PRIMARY KEY} \
|
||||
]
|
||||
do_eqp_test rtree6.2.2 {
|
||||
SELECT * FROM t1,t2 WHERE k=ii AND x1<10
|
||||
} {
|
||||
0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2:Ca (~0 rows)}
|
||||
0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}
|
||||
}
|
||||
|
||||
do_test rtree6.2.3 {
|
||||
query_plan {SELECT * FROM t1,t2 WHERE k=ii}
|
||||
} [list \
|
||||
{TABLE t1 VIRTUAL TABLE INDEX 2:} \
|
||||
{TABLE t2 USING PRIMARY KEY} \
|
||||
]
|
||||
do_eqp_test rtree6.2.3 {
|
||||
SELECT * FROM t1,t2 WHERE k=ii
|
||||
} {
|
||||
0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2: (~0 rows)}
|
||||
0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}
|
||||
}
|
||||
|
||||
do_test rtree6.2.4 {
|
||||
query_plan {SELECT * FROM t1,t2 WHERE v=10 and x1<10 and x2>10}
|
||||
} [list \
|
||||
{TABLE t1 VIRTUAL TABLE INDEX 2:CaEb} \
|
||||
{TABLE t2} \
|
||||
]
|
||||
do_eqp_test rtree6.2.4 {
|
||||
SELECT * FROM t1,t2 WHERE v=10 and x1<10 and x2>10
|
||||
} {
|
||||
0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2:CaEb (~0 rows)}
|
||||
0 1 1 {SCAN TABLE t2 (~100000 rows)}
|
||||
}
|
||||
|
||||
do_test rtree6.2.5 {
|
||||
query_plan {SELECT * FROM t1,t2 WHERE k=ii AND x1<v}
|
||||
} [list \
|
||||
{TABLE t1 VIRTUAL TABLE INDEX 2:} \
|
||||
{TABLE t2 USING PRIMARY KEY} \
|
||||
]
|
||||
do_eqp_test rtree6.2.5 {
|
||||
SELECT * FROM t1,t2 WHERE k=ii AND x1<v
|
||||
} {
|
||||
0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2: (~0 rows)}
|
||||
0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname $argv0] .. .. test]
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
|
||||
|
||||
171
ext/rtree/rtree8.test
Normal file
171
ext/rtree/rtree8.test
Normal file
@ -0,0 +1,171 @@
|
||||
# 2010 February 16
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !rtree { finish_test ; return }
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# The following block of tests - rtree8-1.* - feature reading and writing
|
||||
# an r-tree table while there exist open cursors on it.
|
||||
#
|
||||
proc populate_t1 {n} {
|
||||
execsql { DELETE FROM t1 }
|
||||
for {set i 1} {$i <= $n} {incr i} {
|
||||
execsql { INSERT INTO t1 VALUES($i, $i, $i+2) }
|
||||
}
|
||||
}
|
||||
|
||||
# A DELETE while a cursor is reading the table.
|
||||
#
|
||||
do_test rtree8-1.1.1 {
|
||||
execsql { PRAGMA page_size = 512 }
|
||||
execsql { CREATE VIRTUAL TABLE t1 USING rtree_i32(id, x1, x2) }
|
||||
populate_t1 5
|
||||
} {}
|
||||
do_test rtree8-1.1.2 {
|
||||
set res [list]
|
||||
db eval { SELECT * FROM t1 } {
|
||||
lappend res $x1 $x2
|
||||
if {$id==3} { db eval { DELETE FROM t1 WHERE id>3 } }
|
||||
}
|
||||
set res
|
||||
} {1 3 2 4 3 5}
|
||||
do_test rtree8-1.1.3 {
|
||||
execsql { SELECT * FROM t1 }
|
||||
} {1 1 3 2 2 4 3 3 5}
|
||||
|
||||
# Many SELECTs on the same small table.
|
||||
#
|
||||
proc nested_select {n} {
|
||||
set ::max $n
|
||||
db eval { SELECT * FROM t1 } {
|
||||
if {$id == $n} { nested_select [expr $n+1] }
|
||||
}
|
||||
return $::max
|
||||
}
|
||||
do_test rtree8-1.2.1 { populate_t1 50 } {}
|
||||
do_test rtree8-1.2.2 { nested_select 1 } {51}
|
||||
|
||||
# This test runs many SELECT queries simultaneously against a large
|
||||
# table, causing a collision in the hash-table used to store r-tree
|
||||
# nodes internally.
|
||||
#
|
||||
populate_t1 1500
|
||||
do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {164}
|
||||
do_test rtree8-1.3.2 {
|
||||
set rowids [execsql {SELECT min(rowid) FROM t1_rowid GROUP BY nodeno}]
|
||||
set stmt_list [list]
|
||||
foreach row $rowids {
|
||||
set stmt [sqlite3_prepare db "SELECT * FROM t1 WHERE id = $row" -1 tail]
|
||||
sqlite3_step $stmt
|
||||
lappend res_list [sqlite3_column_int $stmt 0]
|
||||
lappend stmt_list $stmt
|
||||
}
|
||||
} {}
|
||||
do_test rtree8-1.3.3 { set res_list } $rowids
|
||||
do_execsql_test rtree8-1.3.4 { SELECT count(*) FROM t1 } {1500}
|
||||
do_test rtree8-1.3.5 {
|
||||
foreach stmt $stmt_list { sqlite3_finalize $stmt }
|
||||
} {}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# The following block of tests - rtree8-2.* - test a couple of database
|
||||
# corruption cases. In this case things are not corrupted at the b-tree
|
||||
# level, but the contents of the various tables used internally by an
|
||||
# r-tree table are inconsistent.
|
||||
#
|
||||
populate_t1 50
|
||||
do_execsql_test rtree8-2.1.1 { SELECT max(nodeno) FROM t1_node } {5}
|
||||
do_execsql_test rtree8-2.1.2 { DELETE FROM t1_node } {}
|
||||
for {set i 1} {$i <= 50} {incr i} {
|
||||
do_catchsql_test rtree8-2.1.3.$i {
|
||||
SELECT * FROM t1 WHERE id = $i
|
||||
} {1 {database disk image is malformed}}
|
||||
}
|
||||
do_catchsql_test rtree8-2.1.4 {
|
||||
SELECT * FROM t1
|
||||
} {1 {database disk image is malformed}}
|
||||
do_catchsql_test rtree8-2.1.5 {
|
||||
DELETE FROM t1
|
||||
} {1 {database disk image is malformed}}
|
||||
|
||||
do_execsql_test rtree8-2.1.6 {
|
||||
DROP TABLE t1;
|
||||
CREATE VIRTUAL TABLE t1 USING rtree_i32(id, x1, x2);
|
||||
} {}
|
||||
|
||||
|
||||
populate_t1 50
|
||||
do_execsql_test rtree8-2.2.1 {
|
||||
DELETE FROM t1_parent
|
||||
} {}
|
||||
do_catchsql_test rtree8-2.2.2 {
|
||||
DELETE FROM t1 WHERE id=25
|
||||
} {1 {database disk image is malformed}}
|
||||
do_execsql_test rtree8-2.2.3 {
|
||||
DROP TABLE t1;
|
||||
CREATE VIRTUAL TABLE t1 USING rtree_i32(id, x1, x2);
|
||||
} {}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that trying to use the MATCH operator with the r-tree module does
|
||||
# not confuse it.
|
||||
#
|
||||
populate_t1 10
|
||||
do_catchsql_test rtree8-3.1 {
|
||||
SELECT * FROM t1 WHERE x1 MATCH '1234'
|
||||
} {1 {SQL logic error or missing database}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test a couple of invalid arguments to rtreedepth().
|
||||
#
|
||||
do_catchsql_test rtree8-4.1 {
|
||||
SELECT rtreedepth('hello world')
|
||||
} {1 {Invalid argument to rtreedepth()}}
|
||||
do_catchsql_test rtree8-4.2 {
|
||||
SELECT rtreedepth(X'00')
|
||||
} {1 {Invalid argument to rtreedepth()}}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Delete half of a lopsided tree.
|
||||
#
|
||||
do_execsql_test rtree8-5.1 {
|
||||
CREATE VIRTUAL TABLE t2 USING rtree_i32(id, x1, x2)
|
||||
} {}
|
||||
do_test rtree8-5.2 {
|
||||
execsql BEGIN
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
execsql { INSERT INTO t2 VALUES($i, 100, 101) }
|
||||
}
|
||||
for {set i 100} {$i < 200} {incr i} {
|
||||
execsql { INSERT INTO t2 VALUES($i, 1000, 1001) }
|
||||
}
|
||||
execsql COMMIT
|
||||
} {}
|
||||
do_test rtree8-5.3 {
|
||||
execsql BEGIN
|
||||
for {set i 0} {$i < 200} {incr i} {
|
||||
execsql { DELETE FROM t2 WHERE id = $i }
|
||||
}
|
||||
execsql COMMIT
|
||||
} {}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
125
ext/rtree/rtree9.test
Normal file
125
ext/rtree/rtree9.test
Normal file
@ -0,0 +1,125 @@
|
||||
# 2010 August 28
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file contains tests for the r-tree module. Specifically, it tests
|
||||
# that custom r-tree queries (geometry callbacks) work.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !rtree { finish_test ; return }
|
||||
|
||||
register_cube_geom db
|
||||
|
||||
do_execsql_test rtree9-1.1 {
|
||||
CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2, y1, y2, z1, z2);
|
||||
INSERT INTO rt VALUES(1, 1, 2, 1, 2, 1, 2);
|
||||
} {}
|
||||
do_execsql_test rtree9-1.2 {
|
||||
SELECT * FROM rt WHERE id MATCH cube(0, 0, 0, 2, 2, 2);
|
||||
} {1 1.0 2.0 1.0 2.0 1.0 2.0}
|
||||
do_execsql_test rtree9-1.3 {
|
||||
SELECT * FROM rt WHERE id MATCH cube(3, 3, 3, 2, 2, 2);
|
||||
} {}
|
||||
do_execsql_test rtree9-1.4 {
|
||||
DELETE FROM rt;
|
||||
} {}
|
||||
|
||||
|
||||
for {set i 0} {$i < 1000} {incr i} {
|
||||
set x [expr $i%10]
|
||||
set y [expr ($i/10)%10]
|
||||
set z [expr ($i/100)%10]
|
||||
execsql { INSERT INTO rt VALUES($i, $x, $x+1, $y, $y+1, $z, $z+1) }
|
||||
}
|
||||
do_execsql_test rtree9-2.1 {
|
||||
SELECT id FROM rt WHERE id MATCH cube(2.5, 2.5, 2.5, 1, 1, 1) ORDER BY id;
|
||||
} {222 223 232 233 322 323 332 333}
|
||||
do_execsql_test rtree9-2.2 {
|
||||
SELECT id FROM rt WHERE id MATCH cube(5.5, 5.5, 5.5, 1, 1, 1) ORDER BY id;
|
||||
} {555 556 565 566 655 656 665 666}
|
||||
|
||||
|
||||
do_execsql_test rtree9-3.1 {
|
||||
CREATE VIRTUAL TABLE rt32 USING rtree_i32(id, x1, x2, y1, y2, z1, z2);
|
||||
} {}
|
||||
for {set i 0} {$i < 1000} {incr i} {
|
||||
set x [expr $i%10]
|
||||
set y [expr ($i/10)%10]
|
||||
set z [expr ($i/100)%10]
|
||||
execsql { INSERT INTO rt32 VALUES($i, $x, $x+1, $y, $y+1, $z, $z+1) }
|
||||
}
|
||||
do_execsql_test rtree9-3.2 {
|
||||
SELECT id FROM rt32 WHERE id MATCH cube(3, 3, 3, 1, 1, 1) ORDER BY id;
|
||||
} {222 223 224 232 233 234 242 243 244 322 323 324 332 333 334 342 343 344 422 423 424 432 433 434 442 443 444}
|
||||
do_execsql_test rtree9-3.3 {
|
||||
SELECT id FROM rt32 WHERE id MATCH cube(5.5, 5.5, 5.5, 1, 1, 1) ORDER BY id;
|
||||
} {555 556 565 566 655 656 665 666}
|
||||
|
||||
|
||||
do_catchsql_test rtree9-4.1 {
|
||||
SELECT id FROM rt32 WHERE id MATCH cube(5.5, 5.5, 1, 1, 1) ORDER BY id;
|
||||
} {1 {SQL logic error or missing database}}
|
||||
for {set x 2} {$x<200} {incr x 2} {
|
||||
do_catchsql_test rtree9-4.2.[expr $x/2] {
|
||||
SELECT id FROM rt WHERE id MATCH randomblob($x)
|
||||
} {1 {SQL logic error or missing database}}
|
||||
}
|
||||
do_catchsql_test rtree9-4.3 {
|
||||
SELECT id FROM rt WHERE id MATCH CAST(
|
||||
(cube(5.5, 5.5, 5.5, 1, 1, 1) || X'1234567812345678') AS blob
|
||||
)
|
||||
} {1 {SQL logic error or missing database}}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test the example 2d "circle" geometry callback.
|
||||
#
|
||||
register_circle_geom db
|
||||
|
||||
breakpoint
|
||||
do_execsql_test rtree9-5.1 {
|
||||
CREATE VIRTUAL TABLE rt2 USING rtree(id, xmin, xmax, ymin, ymax);
|
||||
|
||||
INSERT INTO rt2 VALUES(1, 1, 2, 1, 2);
|
||||
INSERT INTO rt2 VALUES(2, 1, 2, -2, -1);
|
||||
INSERT INTO rt2 VALUES(3, -2, -1, -2, -1);
|
||||
INSERT INTO rt2 VALUES(4, -2, -1, 1, 2);
|
||||
|
||||
INSERT INTO rt2 VALUES(5, 2, 3, 2, 3);
|
||||
INSERT INTO rt2 VALUES(6, 2, 3, -3, -2);
|
||||
INSERT INTO rt2 VALUES(7, -3, -2, -3, -2);
|
||||
INSERT INTO rt2 VALUES(8, -3, -2, 2, 3);
|
||||
|
||||
INSERT INTO rt2 VALUES(9, 1.8, 3, 1.8, 3);
|
||||
INSERT INTO rt2 VALUES(10, 1.8, 3, -3, -1.8);
|
||||
INSERT INTO rt2 VALUES(11, -3, -1.8, -3, -1.8);
|
||||
INSERT INTO rt2 VALUES(12, -3, -1.8, 1.8, 3);
|
||||
|
||||
INSERT INTO rt2 VALUES(13, -15, 15, 1.8, 2.2);
|
||||
INSERT INTO rt2 VALUES(14, -15, 15, -2.2, -1.8);
|
||||
INSERT INTO rt2 VALUES(15, 1.8, 2.2, -15, 15);
|
||||
INSERT INTO rt2 VALUES(16, -2.2, -1.8, -15, 15);
|
||||
|
||||
INSERT INTO rt2 VALUES(17, -100, 100, -100, 100);
|
||||
} {}
|
||||
|
||||
do_execsql_test rtree9-5.2 {
|
||||
SELECT id FROM rt2 WHERE id MATCH circle(0.0, 0.0, 2.0);
|
||||
} {1 2 3 4 13 14 15 16 17}
|
||||
|
||||
do_execsql_test rtree9-5.3 {
|
||||
UPDATE rt2 SET xmin=xmin+5, ymin=ymin+5, xmax=xmax+5, ymax=ymax+5;
|
||||
SELECT id FROM rt2 WHERE id MATCH circle(5.0, 5.0, 2.0);
|
||||
} {1 2 3 4 13 14 15 16 17}
|
||||
|
||||
finish_test
|
||||
220
ext/rtree/rtreeA.test
Normal file
220
ext/rtree/rtreeA.test
Normal file
@ -0,0 +1,220 @@
|
||||
# 2010 September 22
|
||||
#
|
||||
# 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 tests for the r-tree module. Specifically, it tests
|
||||
# that corrupt or inconsistent databases do not cause crashes in the r-tree
|
||||
# module.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !rtree { finish_test ; return }
|
||||
|
||||
proc create_t1 {} {
|
||||
db close
|
||||
forcedelete test.db
|
||||
sqlite3 db test.db
|
||||
execsql {
|
||||
PRAGMA page_size = 1024;
|
||||
CREATE VIRTUAL TABLE t1 USING rtree(id, x1, x2, y1, y2);
|
||||
}
|
||||
}
|
||||
proc populate_t1 {} {
|
||||
execsql BEGIN
|
||||
for {set i 0} {$i < 500} {incr i} {
|
||||
set x2 [expr $i+5]
|
||||
set y2 [expr $i+5]
|
||||
execsql { INSERT INTO t1 VALUES($i, $i, $x2, $i, $y2) }
|
||||
}
|
||||
execsql COMMIT
|
||||
}
|
||||
|
||||
proc truncate_node {nodeno nTrunc} {
|
||||
set blob [db one {SELECT data FROM t1_node WHERE nodeno=$nodeno}]
|
||||
if {$nTrunc<0} {set nTrunc "end-$nTrunc"}
|
||||
set blob [string range $blob 0 $nTrunc]
|
||||
db eval { UPDATE t1_node SET data = $blob WHERE nodeno=$nodeno }
|
||||
}
|
||||
|
||||
proc set_tree_depth {tbl {newvalue ""}} {
|
||||
set blob [db one "SELECT data FROM ${tbl}_node WHERE nodeno=1"]
|
||||
|
||||
if {$newvalue == ""} {
|
||||
binary scan $blob Su oldvalue
|
||||
return $oldvalue
|
||||
}
|
||||
|
||||
set blob [binary format Sua* $newvalue [string range $blob 2 end]]
|
||||
db eval "UPDATE ${tbl}_node SET data = \$blob WHERE nodeno=1"
|
||||
return [set_tree_depth $tbl]
|
||||
}
|
||||
|
||||
proc set_entry_count {tbl nodeno {newvalue ""}} {
|
||||
set blob [db one "SELECT data FROM ${tbl}_node WHERE nodeno=$nodeno"]
|
||||
|
||||
if {$newvalue == ""} {
|
||||
binary scan [string range $blob 2 end] Su oldvalue
|
||||
return $oldvalue
|
||||
}
|
||||
|
||||
set blob [binary format a*Sua* \
|
||||
[string range $blob 0 1] $newvalue [string range $blob 4 end]
|
||||
]
|
||||
db eval "UPDATE ${tbl}_node SET data = \$blob WHERE nodeno=$nodeno"
|
||||
return [set_entry_count $tbl $nodeno]
|
||||
}
|
||||
|
||||
|
||||
proc do_corruption_tests {prefix args} {
|
||||
set testarray [lindex $args end]
|
||||
set errormsg {database disk image is malformed}
|
||||
|
||||
foreach {z value} [lrange $args 0 end-1] {
|
||||
set n [string length $z]
|
||||
if {$n>=2 && [string equal -length $n $z "-error"]} {
|
||||
set errormsg $value
|
||||
}
|
||||
}
|
||||
|
||||
foreach {tn sql} $testarray {
|
||||
do_catchsql_test $prefix.$tn $sql [list 1 $errormsg]
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test the libraries response if the %_node table is completely empty
|
||||
# (i.e. the root node is missing), or has been removed from the database
|
||||
# entirely.
|
||||
#
|
||||
create_t1
|
||||
populate_t1
|
||||
do_execsql_test rtreeA-1.0 {
|
||||
DELETE FROM t1_node;
|
||||
} {}
|
||||
|
||||
do_corruption_tests rtreeA-1.1 {
|
||||
1 "SELECT * FROM t1"
|
||||
2 "SELECT * FROM t1 WHERE rowid=5"
|
||||
3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
|
||||
4 "SELECT * FROM t1 WHERE x1<10 AND x2>12"
|
||||
}
|
||||
|
||||
do_execsql_test rtreeA-1.2.0 { DROP TABLE t1_node } {}
|
||||
do_corruption_tests rtreeA-1.2 -error "SQL logic error or missing database" {
|
||||
1 "SELECT * FROM t1"
|
||||
2 "SELECT * FROM t1 WHERE rowid=5"
|
||||
3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
|
||||
4 "SELECT * FROM t1 WHERE x1<10 AND x2>12"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test the libraries response if some of the entries in the %_node table
|
||||
# are the wrong size.
|
||||
#
|
||||
create_t1
|
||||
populate_t1
|
||||
do_test rtreeA-2.1.0 {
|
||||
set nodes [db eval {select nodeno FROM t1_node}]
|
||||
foreach {a b c} $nodes { truncate_node $c 200 }
|
||||
} {}
|
||||
do_corruption_tests rtreeA-2.1 {
|
||||
1 "SELECT * FROM t1"
|
||||
2 "SELECT * FROM t1 WHERE rowid=5"
|
||||
3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
|
||||
4 "SELECT * FROM t1 WHERE x1<10 AND x2>12"
|
||||
}
|
||||
|
||||
create_t1
|
||||
populate_t1
|
||||
do_test rtreeA-2.2.0 { truncate_node 1 200 } {}
|
||||
do_corruption_tests rtreeA-2.2 {
|
||||
1 "SELECT * FROM t1"
|
||||
2 "SELECT * FROM t1 WHERE rowid=5"
|
||||
3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
|
||||
4 "SELECT * FROM t1 WHERE x1<10 AND x2>12"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Set the "depth" of the tree stored on the root node incorrectly. Test
|
||||
# that this does not cause any problems.
|
||||
#
|
||||
create_t1
|
||||
populate_t1
|
||||
do_test rtreeA-3.1.0.1 { set_tree_depth t1 } {1}
|
||||
do_test rtreeA-3.1.0.2 { set_tree_depth t1 3 } {3}
|
||||
do_corruption_tests rtreeA-3.1 {
|
||||
1 "SELECT * FROM t1"
|
||||
2 "SELECT * FROM t1 WHERE rowid=5"
|
||||
3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
|
||||
}
|
||||
|
||||
do_test rtreeA-3.2.0 { set_tree_depth t1 1000 } {1000}
|
||||
do_corruption_tests rtreeA-3.2 {
|
||||
1 "SELECT * FROM t1"
|
||||
2 "SELECT * FROM t1 WHERE rowid=5"
|
||||
3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
|
||||
}
|
||||
|
||||
create_t1
|
||||
populate_t1
|
||||
do_test rtreeA-3.3.0 {
|
||||
execsql { DELETE FROM t1 WHERE rowid = 0 }
|
||||
set_tree_depth t1 65535
|
||||
} {65535}
|
||||
do_corruption_tests rtreeA-3.3 {
|
||||
1 "SELECT * FROM t1"
|
||||
2 "SELECT * FROM t1 WHERE rowid=5"
|
||||
3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Set the "number of entries" field on some nodes incorrectly.
|
||||
#
|
||||
create_t1
|
||||
populate_t1
|
||||
do_test rtreeA-4.1.0 {
|
||||
set_entry_count t1 1 4000
|
||||
} {4000}
|
||||
do_corruption_tests rtreeA-4.1 {
|
||||
1 "SELECT * FROM t1"
|
||||
2 "SELECT * FROM t1 WHERE rowid=5"
|
||||
3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
|
||||
4 "SELECT * FROM t1 WHERE x1<10 AND x2>12"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Remove entries from the %_parent table and check that this does not
|
||||
# cause a crash.
|
||||
#
|
||||
create_t1
|
||||
populate_t1
|
||||
do_execsql_test rtreeA-5.1.0 { DELETE FROM t1_parent } {}
|
||||
do_corruption_tests rtreeA-5.1 {
|
||||
1 "DELETE FROM t1 WHERE rowid = 5"
|
||||
2 "DELETE FROM t1"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Add some bad entries to the %_parent table.
|
||||
#
|
||||
create_t1
|
||||
populate_t1
|
||||
do_execsql_test rtreeA-6.1.0 {
|
||||
UPDATE t1_parent set parentnode = parentnode+1
|
||||
} {}
|
||||
do_corruption_tests rtreeA-6.1 {
|
||||
1 "DELETE FROM t1 WHERE rowid = 5"
|
||||
2 "UPDATE t1 SET x1=x1+1, x2=x2+1"
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
56
ext/rtree/sqlite3rtree.h
Normal file
56
ext/rtree/sqlite3rtree.h
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
** 2010 August 30
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef _SQLITE3RTREE_H_
|
||||
#define _SQLITE3RTREE_H_
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry;
|
||||
|
||||
/*
|
||||
** Register a geometry callback named zGeom that can be used as part of an
|
||||
** R-Tree geometry query as follows:
|
||||
**
|
||||
** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zGeom(... params ...)
|
||||
*/
|
||||
int sqlite3_rtree_geometry_callback(
|
||||
sqlite3 *db,
|
||||
const char *zGeom,
|
||||
int (*xGeom)(sqlite3_rtree_geometry *, int nCoord, double *aCoord, int *pRes),
|
||||
void *pContext
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
** A pointer to a structure of the following type is passed as the first
|
||||
** argument to callbacks registered using rtree_geometry_callback().
|
||||
*/
|
||||
struct sqlite3_rtree_geometry {
|
||||
void *pContext; /* Copy of pContext passed to s_r_g_c() */
|
||||
int nParam; /* Size of array aParam[] */
|
||||
double *aParam; /* Parameters passed to SQL geom function */
|
||||
void *pUser; /* Callback implementation user data */
|
||||
void (*xDelUser)(void *); /* Called by SQLite to clean up pUser */
|
||||
};
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* end of the 'extern "C"' block */
|
||||
#endif
|
||||
|
||||
#endif /* ifndef _SQLITE3RTREE_H_ */
|
||||
@ -13,7 +13,7 @@
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname $argv0] .. .. test]
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] rtree_util.tcl]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
19
main.mk
19
main.mk
@ -241,13 +241,17 @@ TESTSRC = \
|
||||
$(TOP)/src/test_intarray.c \
|
||||
$(TOP)/src/test_journal.c \
|
||||
$(TOP)/src/test_malloc.c \
|
||||
$(TOP)/src/test_multiplex.c \
|
||||
$(TOP)/src/test_mutex.c \
|
||||
$(TOP)/src/test_onefile.c \
|
||||
$(TOP)/src/test_osinst.c \
|
||||
$(TOP)/src/test_pcache.c \
|
||||
$(TOP)/src/test_quota.c \
|
||||
$(TOP)/src/test_rtree.c \
|
||||
$(TOP)/src/test_schema.c \
|
||||
$(TOP)/src/test_server.c \
|
||||
$(TOP)/src/test_stat.c \
|
||||
$(TOP)/src/test_superlock.c \
|
||||
$(TOP)/src/test_tclvar.c \
|
||||
$(TOP)/src/test_thread.c \
|
||||
$(TOP)/src/test_vfs.c \
|
||||
@ -367,7 +371,9 @@ target_source: $(SRC) $(TOP)/tool/vdbe-compress.tcl
|
||||
|
||||
sqlite3.c: target_source $(TOP)/tool/mksqlite3c.tcl
|
||||
tclsh $(TOP)/tool/mksqlite3c.tcl
|
||||
cp sqlite3.c tclsqlite3.c
|
||||
echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c
|
||||
cat sqlite3.c >>tclsqlite3.c
|
||||
echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c
|
||||
cat $(TOP)/src/tclsqlite.c >>tclsqlite3.c
|
||||
|
||||
fts2amal.c: target_source $(TOP)/ext/fts2/mkfts2amal.tcl
|
||||
@ -521,6 +527,17 @@ soaktest: testfixture$(EXE) sqlite3$(EXE)
|
||||
test: testfixture$(EXE) sqlite3$(EXE)
|
||||
./testfixture$(EXE) $(TOP)/test/veryquick.test
|
||||
|
||||
# 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.
|
||||
#
|
||||
threadtest3$(EXE): sqlite3.c $(TOP)/test/threadtest3.c
|
||||
$(TCCX) -O2 sqlite3.c $(TOP)/test/threadtest3.c \
|
||||
-o threadtest3$(EXE) $(THREADLIB)
|
||||
|
||||
threadtest: threadtest3$(EXE)
|
||||
./threadtest3$(EXE)
|
||||
|
||||
sqlite3_analyzer$(EXE): $(TOP)/src/tclsqlite.c sqlite3.c $(TESTSRC) \
|
||||
$(TOP)/tool/spaceanal.tcl
|
||||
sed \
|
||||
|
||||
416
manifest
416
manifest
@ -1,14 +1,14 @@
|
||||
-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA1
|
||||
|
||||
C Version\s3.7.2\srelease\scandidate\s1
|
||||
D 2010-08-23T18:52:01
|
||||
C SQLite\sversion\s3.7.5\srelease\scandidate\s2
|
||||
D 2011-01-28T17:03:50.592
|
||||
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
||||
F Makefile.in 543f91f24cd7fee774ecc0a61c19704c0c3e78fd
|
||||
F Makefile.in de6498556d536ae60bb8bb10e8c1ba011448658c
|
||||
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
||||
F Makefile.vxworks c85ec1d8597fe2f7bc225af12ac1666e21379151
|
||||
F README cd04a36fbc7ea56932a4052d7d0b7f09f27c33d6
|
||||
F VERSION 6062e0026a5ab33dabb4efae38604d40115819ec
|
||||
F VERSION de8d3477dbf0d6cc226ccc6e046273627eb55fc5
|
||||
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
|
||||
F addopcodes.awk 17dc593f791f874d2c23a0f9360850ded0286531
|
||||
F art/2005osaward.gif 0d1851b2a7c1c9d0ccce545f3e14bca42d7fd248
|
||||
@ -19,13 +19,14 @@ F art/SQLite_big.gif 2b8e4603b91ba2a2c7062a82ff570d945034bb30
|
||||
F art/nocopy.gif 716aa07d4bb7250d4e75756073bf8ef9f56bec8f
|
||||
F art/powered_by_sqlite.gif 7fbcd7d3675391fd3d21672c14c05f5999eb60d1
|
||||
F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2
|
||||
F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90
|
||||
F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2
|
||||
F art/src_logo.gif 9341ef09f0e53cd44c0c9b6fc3c16f7f3d6c2ad9
|
||||
F config.guess 226d9a188c6196f3033ffc651cbc9dcee1a42977
|
||||
F config.h.in 868fdb48c028421a203470e15c69ada15b9ba673
|
||||
F config.sub 9ebe4c3b3dab6431ece34f16828b594fb420da55
|
||||
F configure a5dab32df872ed3fe248c7f0cbc324595c169781 x
|
||||
F configure.ac 699040cc9abb7465dca5a2972bc89d227fd8f734
|
||||
F configure 0eb10c03a6536d8e5ce52ab70fda0a152d8a3262 x
|
||||
F configure.ac 87a3c71bbe9c925381c154413eea7f3cdc397244
|
||||
F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad
|
||||
F doc/lemon.html f0f682f50210928c07e562621c3b7e8ab912a538
|
||||
F doc/pager-invariants.txt 870107036470d7c419e93768676fae2f8749cf9e
|
||||
@ -50,53 +51,58 @@ F ext/fts1/simple_tokenizer.c 1844d72f7194c3fd3d7e4173053911bf0661b70d
|
||||
F ext/fts1/tokenizer.h 0c53421b832366d20d720d21ea3e1f6e66a36ef9
|
||||
F ext/fts2/README.tokenizers 21e3684ea5a095b55d70f6878b4ce6af5932dfb7
|
||||
F ext/fts2/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
|
||||
F ext/fts2/fts2.c 6cbd0fbdfe4699c108199e3c8f7e76f71d597335
|
||||
F ext/fts2/fts2.c 238e9e19158ef75fb4155613a870443394fbf7da
|
||||
F ext/fts2/fts2.h da5f76c65163301d1068a971fd32f4119e3c95fa
|
||||
F ext/fts2/fts2_hash.c 2689e42e1107ea67207f725cf69cf8972d00cf93
|
||||
F ext/fts2/fts2_hash.h 9a5b1be94664139f93217a0770d7144425cffb3a
|
||||
F ext/fts2/fts2_icu.c 1ea9993a39c9783c2e2d7446d055e9d64411dda0
|
||||
F ext/fts2/fts2_porter.c 8a6369b0fae98c04db95e4fa95fac7c03d7182ec
|
||||
F ext/fts2/fts2_porter.c 747056987951f743e955c8479f1df21a565720fe
|
||||
F ext/fts2/fts2_tokenizer.c 26e993a00b2bd5b6e73c155597361710b12ffe25
|
||||
F ext/fts2/fts2_tokenizer.h a7e46462d935a314b2682287f12f27530a3ee08e
|
||||
F ext/fts2/fts2_tokenizer1.c 8545ce12b41922004da46e91a7b023b92b76f94e
|
||||
F ext/fts2/fts2_tokenizer1.c 0123d21078e053bd98fd6186c5c6dc6d67969f2e
|
||||
F ext/fts2/mkfts2amal.tcl 974d5d438cb3f7c4a652639262f82418c1e4cff0
|
||||
F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
|
||||
F ext/fts3/README.tokenizers 998756696647400de63d5ba60e9655036cb966e9
|
||||
F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
|
||||
F ext/fts3/fts3.c e818310c473d7703f7818887a3537ec42ae0d528
|
||||
F ext/fts3/fts3.c 28ada7d1c700e57b072b2c95d70565b05925fa46
|
||||
F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
|
||||
F ext/fts3/fts3Int.h 70528ba8c33991699f96ecc64112122833cdbdb5
|
||||
F ext/fts3/fts3_expr.c 42d5697731cd30fbeabd081bb3e6d3df5531f606
|
||||
F ext/fts3/fts3Int.h a6c69c1c5e2c8c19172ddff42d262c087dcd7337
|
||||
F ext/fts3/fts3_expr.c 5f49e0deaf723724b08100bb3ff40aab02ad0c93
|
||||
F ext/fts3/fts3_hash.c 3c8f6387a4a7f5305588b203fa7c887d753e1f1c
|
||||
F ext/fts3/fts3_hash.h 8331fb2206c609f9fc4c4735b9ab5ad6137c88ec
|
||||
F ext/fts3/fts3_icu.c ac494aed69835008185299315403044664bda295
|
||||
F ext/fts3/fts3_porter.c 8df6f6efcc4e9e31f8bf73a4007c2e9abca1dfba
|
||||
F ext/fts3/fts3_snippet.c 2c4c921155e4b6befd272041fb903d999ac07d30
|
||||
F ext/fts3/fts3_tokenizer.c b4f2d01c24573852755bc92864816785dae39318
|
||||
F ext/fts3/fts3_porter.c d61cfd81fb0fd8fbcb25adcaee0ba671aefaa5c2
|
||||
F ext/fts3/fts3_snippet.c 196c5e6cde57bfc1907c2d60e9c29590e4f93fb6
|
||||
F ext/fts3/fts3_tokenizer.c 055f3dc7369585350b28db1ee0f3b214dca6724d
|
||||
F ext/fts3/fts3_tokenizer.h 13ffd9fcb397fec32a05ef5cd9e0fa659bf3dbd3
|
||||
F ext/fts3/fts3_tokenizer1.c 6e5cbaa588924ac578263a598e4fb9f5c9bb179d
|
||||
F ext/fts3/fts3_write.c 4b21a0c6f2772b261f14e3a2e80e1e3e849268b0
|
||||
F ext/fts3/fts3_write.c 3eea26b9ca4219e1711b0db74fd5a9d448a6afbb
|
||||
F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
|
||||
F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
|
||||
F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9
|
||||
F ext/icu/icu.c 850e9a36567bbcce6bd85a4b68243cad8e3c2de2
|
||||
F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
|
||||
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
|
||||
F ext/rtree/rtree.c f2fbb6470155316027a8b888e8623bc1819fe443
|
||||
F ext/rtree/rtree.c 05b293c85403cf39bb5af0e7c010b0cafeab5e47
|
||||
F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e
|
||||
F ext/rtree/rtree1.test 51bb0cd0405970501e63258401ae5ad235a4f468
|
||||
F ext/rtree/rtree2.test 7b665c44d25e51b3098068d983a39902b2e2d7a1
|
||||
F ext/rtree/rtree3.test dece988c363368af8c11862995c762071894918f
|
||||
F ext/rtree/rtree4.test 94fdd570ab5bc47244d87d4590023be43ac786bd
|
||||
F ext/rtree/rtree5.test 92508f5152a50110af6551fa5b769d1bbd7c4ef3
|
||||
F ext/rtree/rtree6.test 903720aaab819764c3693aaac0affe8174104ac8
|
||||
F ext/rtree/rtree7.test 6fd29fb8e13795c822f4ceeea92ab5d61c96976d
|
||||
F ext/rtree/rtree1.test dbd4250ac0ad367a262eb9676f7e3080b0368206
|
||||
F ext/rtree/rtree2.test acbb3a4ce0f4fbc2c304d2b4b784cfa161856bba
|
||||
F ext/rtree/rtree3.test a494da55c30ee0bc9b01a91c80c81b387b22d2dc
|
||||
F ext/rtree/rtree4.test 0061e6f464fd3dc6a79f82454c5a1c3dadbe42af
|
||||
F ext/rtree/rtree5.test ce3d7ccae2cfd9d2e1052b462424964c9bdcda12
|
||||
F ext/rtree/rtree6.test 309806a2a27ef5897d4dd6aee2e8006bf754cc22
|
||||
F ext/rtree/rtree7.test bcb647b42920b3b5d025846689147778485cc318
|
||||
F ext/rtree/rtree8.test 9772e16da71e17e02bdebf0a5188590f289ab37d
|
||||
F ext/rtree/rtree9.test df9843d1a9195249c8d3b4ea6aedda2d5c73e9c2
|
||||
F ext/rtree/rtreeA.test ace05e729a36e342d40cf94e9efc7b4723d9dcdf
|
||||
F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195
|
||||
F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea
|
||||
F ext/rtree/tkt3363.test 2bf324f7908084a5f463de3109db9c6e607feb1b
|
||||
F ext/rtree/sqlite3rtree.h 1af0899c63a688e272d69d8e746f24e76f10a3f0
|
||||
F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
|
||||
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F main.mk 26ad86cf0689940f19b3d608bbfdb3956b2fb9a7
|
||||
F main.mk 05d0f3475dd331896bd607cfb45c5e21b94589ad
|
||||
F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a
|
||||
F mkextu.sh 416f9b7089d80e5590a29692c9d9280a10dbad9f
|
||||
F mkextw.sh 4123480947681d9b434a5e7b1ee08135abe409ac
|
||||
@ -109,26 +115,26 @@ F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca
|
||||
F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
|
||||
F sqlite3.1 6be1ad09113570e1fc8dcaff84c9b0b337db5ffc
|
||||
F sqlite3.pc.in ae6f59a76e862f5c561eb32a380228a02afc3cad
|
||||
F src/alter.c 8dc27638e7e2553e80b2b621f232be5eb1e85ef3
|
||||
F src/analyze.c da65ce99bb159b10e85a1e460adbe53a88062500
|
||||
F src/attach.c 17bec1f18254d9341369f20f90ba24ce35d20d10
|
||||
F src/alter.c 6a0c176e64a34929a4436048066a84ef4f1445b3
|
||||
F src/analyze.c a038162344265ac21dfb24b3fcc06c666ebb9c07
|
||||
F src/attach.c 252c4f7e36cc219349451ed63e278c60e80b26f3
|
||||
F src/auth.c 523da7fb4979469955d822ff9298352d6b31de34
|
||||
F src/backup.c 8ff0b7018df253c7f30d3f9702b0b16f19209d5c
|
||||
F src/backup.c 6728d6d48d55b449af76a3e51c0808849cb32a2e
|
||||
F src/bitvec.c af50f1c8c0ff54d6bdb7a80e2fceca5a93670bef
|
||||
F src/btmutex.c 96a12f50f7a17475155971a241d85ec5171573ff
|
||||
F src/btree.c 5047fb303cdf6806a42676a6f513c57e15b7d69b
|
||||
F src/btree.h b4ba2fdf6b64c7c376bdfffa826af6b786b151d9
|
||||
F src/btreeInt.h 5b034ff54800046cc5870605d683ac1f9134bd99
|
||||
F src/build.c 0018d49629fc4807100c988dd191dd95e185bb38
|
||||
F src/callback.c da3c38d0ef5d7f04fae371e519bda61aa9cb1704
|
||||
F src/btree.c 9004c98fc576306eee4fc0562ffeb362ef53912c
|
||||
F src/btree.h 10f9296bf4edf034f5adce921b7b4383a56a1c90
|
||||
F src/btreeInt.h 20f73dc93b1eeb83afd7259fbc6bd7dcf2df7fe4
|
||||
F src/build.c 00a327120d81ace6267e714ae8010c997d55de5d
|
||||
F src/callback.c a1d1b1c9c85415dff013af033e2fed9c8382d33b
|
||||
F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac
|
||||
F src/ctime.c 4f3aadad62c6c9f0d4e5a96718516ac4e3c598df
|
||||
F src/date.c 5dd8448a0bfea8d31fb14cff487d0c06ff8c8b20
|
||||
F src/ctime.c 7deec4534f3b5a0c3b4a4cbadf809d321f64f9c4
|
||||
F src/date.c 1548fdac51377e4e7833251de878b4058c148e1b
|
||||
F src/delete.c 7ed8a8c8b5f748ece92df173d7e0f7810c899ebd
|
||||
F src/expr.c 9ee507c3dc6eaa5657cbd1dad026cdeda89c559f
|
||||
F src/expr.c 1810f3056b11de99cc10e24629edf00e5fbd3a75
|
||||
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
|
||||
F src/fkey.c 58bbf52c6ddd3f64ca40a3230f9e548a83a5cb16
|
||||
F src/func.c 464b0dc70618b896c402c574eb04bc5eacf35341
|
||||
F src/fkey.c 17950a28f28b23e8ad3feaac5fc88c324d2f600a
|
||||
F src/func.c cb41f614edc43b00bfeb030f9768e80eaff47edd
|
||||
F src/global.c 02335177cf6946fe5525c6f0755cf181140debf3
|
||||
F src/hash.c 458488dcc159c301b8e7686280ab209f1fb915af
|
||||
F src/hash.h 2894c932d84d9f892d4b4023a75e501f83050970
|
||||
@ -137,52 +143,52 @@ F src/insert.c a4995747c062256582a90b4f87f716e11b067050
|
||||
F src/journal.c 552839e54d1bf76fb8f7abe51868b66acacf6a0e
|
||||
F src/legacy.c a199d7683d60cef73089e892409113e69c23a99f
|
||||
F src/lempar.c 7f026423f4d71d989e719a743f98a1cbd4e6d99e
|
||||
F src/loadext.c 6d422ea91cf3d2d00408c5a8f2391cd458da85f8
|
||||
F src/main.c 99622181f36d68e9f2a851c7b34263b3dcd03470
|
||||
F src/malloc.c 19a468460c7df72de245f10c06bd0625777b7c83
|
||||
F src/loadext.c 8af9fcc75708d60b88636ccba38b4a7b3c155c3e
|
||||
F src/main.c 6653e46db7ecb5a7449d8a12900147192f748b97
|
||||
F src/malloc.c 92d59a007d7a42857d4e9454aa25b6b703286be1
|
||||
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
||||
F src/mem1.c 89d4ea8d5cdd55635cbaa48ad53132af6294cbb2
|
||||
F src/mem2.c 9e5f72e38573db9598fe60d3fa530d473cc8714e
|
||||
F src/mem1.c 00bd8265c81abb665c48fea1e0c234eb3b922206
|
||||
F src/mem2.c e307323e86b5da1853d7111b68fd6b84ad6f09cf
|
||||
F src/mem3.c 9b237d911ba9904142a804be727cc6664873f8a3
|
||||
F src/mem5.c eb7a5cb98915dd7a086fa415ce3a5a0f20d0acff
|
||||
F src/memjournal.c 4a93a25ad9f76c40afa070ffd7187eb3a5fd7aee
|
||||
F src/mem5.c 6fe00f46997bebb690397cb029719f711e7640e3
|
||||
F src/memjournal.c 0ebce851677a7ac035ba1512a7e65851b34530c6
|
||||
F src/mutex.c 6949180803ff05a7d0e2b9334a95b4fb5a00e23f
|
||||
F src/mutex.h 6fde601e55fa6c3fae768783c439797ab84c87c6
|
||||
F src/mutex.h fe2ef5e1c4dae531d5a544f9241f19c56d26803d
|
||||
F src/mutex_noop.c d5cfbca87168c661a0b118cd8e329a908e453151
|
||||
F src/mutex_os2.c 6a62583e374ba3ac1a3fcc0da2bfdac7d3942689
|
||||
F src/mutex_unix.c abb8c98a6c27c57280e71522d059e929c708d019
|
||||
F src/mutex_w32.c b7ed3366a1d44a62a17d4eaefdaa2e7c25f944c2
|
||||
F src/mutex_unix.c b4f4e923bb8de93ec3f251fadb50855f23df9579
|
||||
F src/mutex_w32.c 3ade5ae71449d1d023f0ebb3184c2ae6aa9307e4
|
||||
F src/notify.c 976dd0f6171d4588e89e874fcc765e92914b6d30
|
||||
F src/os.c 60178f518c4d6c0dcb59f7292232281d7bea2dcf
|
||||
F src/os.c 22ac61d06e72a0dac900400147333b07b13d8e1d
|
||||
F src/os.h 9dbed8c2b9c1f2f2ebabc09e49829d4777c26bf9
|
||||
F src/os_common.h a8f95b81eca8a1ab8593d23e94f8a35f35d4078f
|
||||
F src/os_os2.c 72d0b2e562952a2464308c4ce5f7913ac10bef3e
|
||||
F src/os_unix.c 11194cbcf6a57456e58022dc537ab8c3497d9bb9
|
||||
F src/os_win.c 51cb62f76262d961ea4249489383d714501315a7
|
||||
F src/pager.c a5f5d9787b11dfb0b6082e6f5846d00b459a8e19
|
||||
F src/pager.h ef8c8f71ab022cc2fff768a1175dd32355be9dcd
|
||||
F src/os_os2.c 2e452c9f2ca507623ad351c33a8a8b27849b1863
|
||||
F src/os_unix.c 1be46a35bad4bec5171e4de88aaff817260eb378
|
||||
F src/os_win.c 9abdcdd925416d854eabb0996c96debd92abfef5
|
||||
F src/pager.c b0fcbe3038fd08b111e1cf1deddd5f42418004d8
|
||||
F src/pager.h 0ea59db2a33bc6c2c02cae34de33367e1effdf76
|
||||
F src/parse.y 12b7ebd61ea54f0e1b1083ff69cc2c8ce9353d58
|
||||
F src/pcache.c 1e9aa2dbc0845b52e1b51cc39753b6d1e041cb07
|
||||
F src/pcache.c 09d38c44ab275db581f7a2f6ff8b9bc7f8c0faaa
|
||||
F src/pcache.h c683390d50f856d4cd8e24342ae62027d1bb6050
|
||||
F src/pcache1.c e921e8a1d52c93abde63cb6dad1fa39770410c52
|
||||
F src/pragma.c 8b24ce00a93de345b6c3bd1e1e2cfba9f63d2325
|
||||
F src/prepare.c ce4c35a2b1d5fe916e4a46b70d24a6e997d7c4c6
|
||||
F src/printf.c 8ae5082dd38a1b5456030c3755ec3a392cd51506
|
||||
F src/pcache1.c d548e31beafa792d1994b663a29a5303569efc4e
|
||||
F src/pragma.c 8a6cd3c787f882fa44f6490d2411fc26839ce8f3
|
||||
F src/prepare.c 395b3fab1b93f45b6aa194b23ebc201221c47b99
|
||||
F src/printf.c df2ff3bb5409e8958136933342c46464fbd017e7
|
||||
F src/random.c cd4a67b3953b88019f8cd4ccd81394a8ddfaba50
|
||||
F src/resolve.c 1c0f32b64f8e3f555fe1f732f9d6f501a7f05706
|
||||
F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697
|
||||
F src/select.c 8add6cab889fc02e1492eda8dba462ccf11f51dd
|
||||
F src/shell.c 8517fc1f9c59ae4007e6cc8b9af91ab231ea2056
|
||||
F src/sqlite.h.in 2d72a6242df41c517e38eec8791abcf5484a36f1
|
||||
F src/sqlite3ext.h 69dfb8116af51b84a029cddb3b35062354270c89
|
||||
F src/sqliteInt.h e33b15e8176442bf7484f0e716edfd1ce03b2979
|
||||
F src/select.c 8a7ba246b0b4bb45df7fbc52681728a0e3deaaa7
|
||||
F src/shell.c 83c6f0cc5a79a081c7b9ddfe4f557b47e0bad976
|
||||
F src/sqlite.h.in 76955fcd1c5371268ecc8afe0ce6c49ea750ae38
|
||||
F src/sqlite3ext.h c90bd5507099f62043832d73f6425d8d5c5da754
|
||||
F src/sqliteInt.h 45926deaf59b1ce3f55d21d5f91a8cecb6a7eb4c
|
||||
F src/sqliteLimit.h a17dcd3fb775d63b64a43a55c54cb282f9726f44
|
||||
F src/status.c 496913d4e8441195f6f2a75b1c95993a45b9b30b
|
||||
F src/status.c 4997380fbb915426fef9e500b4872e79c99267fc
|
||||
F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
|
||||
F src/tclsqlite.c cacee9482417b6fc6043f6bb831ff9496d46242d
|
||||
F src/test1.c 55005c9781b157b1d215ba145768783b9abae78c
|
||||
F src/tclsqlite.c 549859dc2c143f3deb6a92636a2d27973652c164
|
||||
F src/test1.c 771407a49ae199241f0efb7055634e4a1899c026
|
||||
F src/test2.c 80d323d11e909cf0eb1b6fbb4ac22276483bcf31
|
||||
F src/test3.c 4c21700c73a890a47fc685c1097bfb661346ac94
|
||||
F src/test3.c 056093cfef69ff4227a6bdb9108564dc7f45e4bc
|
||||
F src/test4.c 0528360b5025688002a5feb6be906ddce52eaaee
|
||||
F src/test5.c e1a19845625144caf038031234a12185e40d315c
|
||||
F src/test6.c c7256cc21d2409486d094277d5b017e8eced44ba
|
||||
@ -193,7 +199,7 @@ F src/test_async.c 0612a752896fad42d55c3999a5122af10dcf22ad
|
||||
F src/test_autoext.c 30e7bd98ab6d70a62bb9ba572e4c7df347fe645e
|
||||
F src/test_backup.c c129c91127e9b46e335715ae2e75756e25ba27de
|
||||
F src/test_btree.c 47cd771250f09cdc6e12dda5bc71bc0b3abc96e2
|
||||
F src/test_config.c 5a11c51af2156e2d07186930b36f2b8239a4393f
|
||||
F src/test_config.c 9f025a7f3686c94e82dc6d6bd3cbf0f89cd67487
|
||||
F src/test_demovfs.c 0aed671636735116fc872c5b03706fd5612488b5
|
||||
F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc
|
||||
F src/test_func.c 13b582345fb1185a93e46c53310fae8547dcce20
|
||||
@ -203,48 +209,53 @@ F src/test_intarray.c d879bbf8e4ce085ab966d1f3c896a7c8b4f5fc99
|
||||
F src/test_intarray.h 489edb9068bb926583445cb02589344961054207
|
||||
F src/test_journal.c 785edd54f963aefb3c1628124170a56697c68c70
|
||||
F src/test_loadext.c df586c27176e3c2cb2e099c78da67bf14379a56e
|
||||
F src/test_malloc.c 09a88f0c111201dc4f8c20470aa1b5f611d59200
|
||||
F src/test_mutex.c ce06b59aca168cd8c520b77159a24352a7469bd3
|
||||
F src/test_malloc.c fd6188b1501c0010fb4241ddc9f0d5ac402c688d
|
||||
F src/test_multiplex.c 5990431a852aa21c9a67da748f23d2cf1e21f8fc
|
||||
F src/test_mutex.c a6bd7b9cf6e19d989e31392b06ac8d189f0d573e
|
||||
F src/test_onefile.c 40cf9e212a377a6511469384a64b01e6e34b2eec
|
||||
F src/test_osinst.c f408c6a181f2fb04c56273afd5c3e1e82f60392c
|
||||
F src/test_pcache.c 7bf828972ac0d2403f5cfa4cd14da41f8ebe73d8
|
||||
F src/test_quota.c b5576f17d701af461effd7ca1e71f0d100071192
|
||||
F src/test_rtree.c 30c981837445a4e187ee850a49c4760d9642f7c3
|
||||
F src/test_schema.c 8c06ef9ddb240c7a0fcd31bc221a6a2aade58bf0
|
||||
F src/test_server.c bbba05c144b5fc4b52ff650a4328027b3fa5fcc6
|
||||
F src/test_stat.c f682704b5d1ba8e1d4e7e882a6d7922e2dcf066c
|
||||
F src/test_superlock.c 2b97936ca127d13962c3605dbc9a4ef269c424cd
|
||||
F src/test_tclvar.c f4dc67d5f780707210d6bb0eb6016a431c04c7fa
|
||||
F src/test_thread.c bedd05cad673dba53326f3aa468cc803038896c0
|
||||
F src/test_vfs.c 702e52636113f6b9721da90ef1bf26e07fff414d
|
||||
F src/test_vfs.c 2ed8853c1e51ac6f9ea091f7ce4e0d618bba8b86
|
||||
F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
|
||||
F src/tokenize.c 604607d6813e9551cf5189d899e0a25c12681080
|
||||
F src/trigger.c b8bedb9c0084ceb51a40f54fcca2ce048c8de852
|
||||
F src/update.c 1521162d20c2994af1fdc8833e1a88dae09052c8
|
||||
F src/update.c 227e6cd512108b84f69421fc6c7aa1b83d60d6e0
|
||||
F src/utf.c 1baeeac91707a4df97ccc6141ec0f808278af685
|
||||
F src/util.c 32aebf04c10e51ad3977a928b7416bed671b620b
|
||||
F src/vacuum.c 241a8386727c1497eba4955933356dfba6ff8c9f
|
||||
F src/vdbe.c 66c262a923915e596379b1d597178e04c5d719e4
|
||||
F src/util.c ab1c92426494f499f42b9e307537b03e923d75c1
|
||||
F src/vacuum.c 924bd1bcee2dfb05376f79845bd3b4cec7b54b2f
|
||||
F src/vdbe.c 5d310eaf1a4d8383602126fa82e01291ab7d3cf3
|
||||
F src/vdbe.h 4de0efb4b0fdaaa900cf419b35c458933ef1c6d2
|
||||
F src/vdbeInt.h ffd68c4d4229227a5089bec53a1c635146177abc
|
||||
F src/vdbeapi.c d0f4407e465f261780ad725c1caece7d66a6aa35
|
||||
F src/vdbeaux.c c73bcefcebfd3d2cf91bf6a41ef0fb0d884814c6
|
||||
F src/vdbeblob.c 258a6010ba7a82b72b327fb24c55790655689256
|
||||
F src/vdbemem.c e5673f81a2381b35c60e73ef0a8502be2ab1041e
|
||||
F src/vdbetrace.c 864cef96919323482ebd9986f2132435115e9cc2
|
||||
F src/vtab.c 0e8e0cb30dffb078367e843e84e37ef99236c7e4
|
||||
F src/wal.c 5ac2119e23ee4424599d4275b66dc88d612a0543
|
||||
F src/wal.h 96669b645e27cd5a111ba59f0cae7743a207bc3c
|
||||
F src/vdbeInt.h 6e6f28e9bccc6c703dca1372fd661c57b5c15fb0
|
||||
F src/vdbeapi.c 8e9324fd35eb70d0b5904bd1af40f2598744dc4d
|
||||
F src/vdbeaux.c 33448d23b857654dd69ed2103611f5c733606f68
|
||||
F src/vdbeblob.c 18955f0ee6b133cd08e1592010cb9a6b11e9984c
|
||||
F src/vdbemem.c 411649a35686f54268ccabeda175322c4697f5a6
|
||||
F src/vdbetrace.c 3ba13bc32bdf16d2bdea523245fd16736bed67b5
|
||||
F src/vtab.c b297e8fa656ab5e66244ab15680d68db0adbec30
|
||||
F src/wal.c dbca424f71678f663a286ab2a98f947af1d412a7
|
||||
F src/wal.h c1aac6593a0b02b15dc625987e619edeab39292e
|
||||
F src/walker.c 3112bb3afe1d85dc52317cb1d752055e9a781f8f
|
||||
F src/where.c 7db3e41c2a846f9deeb24f1bbb75461b4010b7b5
|
||||
F src/where.c af069e6b53234118014dabfece96a9515b69d76b
|
||||
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
|
||||
F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87
|
||||
F test/all.test 6745008c144bd2956d58864d21f7b304689c1cce
|
||||
F test/all.test 51756962d522e474338e9b2ebb26e7364d4aa125
|
||||
F test/alter.test 15f9224868b290d6bf7a63f31437f31aee070636
|
||||
F test/alter2.test 52096b711afe5f219e575c6db7a70f7a35df4f63
|
||||
F test/alter3.test 25b95a136708f22b87184fa6a4309eea03d65153
|
||||
F test/alter4.test 9386ffd1e9c7245f43eca412b2058d747509cc1f
|
||||
F test/alter2.test 75f731508f1bf27ba09a6075c66cd02216ba464b
|
||||
F test/alter3.test 8677e48d95536f7a6ed86a1a774744dadcc22b07
|
||||
F test/alter4.test 1e5dd6b951e9f65ca66422edff02e56df82dd403
|
||||
F test/altermalloc.test e81ac9657ed25c6c5bb09bebfa5a047cd8e4acfc
|
||||
F test/analyze.test bf692e7db414f268a136bade16c03a1bdbb9240c
|
||||
F test/analyze2.test 59dac6c399c0c5d1a90a11ee7cc606743fb6db93
|
||||
F test/analyze3.test 6d4f4b0929545a9d1af803a0608a0c51b92a3537
|
||||
F test/analyze.test c1eb87067fc16ece7c07e823d6395fd831b270c5
|
||||
F test/analyze2.test 3bde8f0879d9c1f2df3af21fcf42e706d8ee1e43
|
||||
F test/analyze3.test 820ddfb7591b49607fbaf77240c7955ac3cabb04
|
||||
F test/analyze4.test 757b37875cf9bb528d46f74497bc789c88365045
|
||||
F test/async.test ad4ba51b77cd118911a3fe1356b0809da9c108c3
|
||||
F test/async2.test bf5e2ca2c96763b4cba3d016249ad7259a5603b6
|
||||
F test/async3.test 93edaa9122f498e56ea98c36c72abc407f4fb11e
|
||||
@ -253,17 +264,17 @@ F test/async5.test f3592d79c84d6e83a5f50d3fd500445f7d97dfdf
|
||||
F test/attach.test ce9660e51768fab93cf129787be886c5d6c4fd81
|
||||
F test/attach2.test a295d2d7061adcee5884ef4a93c7c96a82765437
|
||||
F test/attach3.test bd9830bc3a0d22ed1310c9bff6896927937017dc
|
||||
F test/attachmalloc.test 38d2da5fdaf09ba0add57296967a3061e5842584
|
||||
F test/auth.test 8f21c160a4562f54f27618e85bac869efcecbcaf
|
||||
F test/attachmalloc.test 1d5b821a676f7bf0b00d87cc106b78966789ba57
|
||||
F test/auth.test 26cc6f219580191539bf335abe03e55e49310846
|
||||
F test/auth2.test 270baddc8b9c273682760cffba6739d907bd2882
|
||||
F test/auth3.test a4755e6a2a2fea547ffe63c874eb569e60a28eb5
|
||||
F test/autoinc.test 85ef3180a737e6580086a018c09c6f1a52759b46
|
||||
F test/autoindex1.test 7df441bf0e7a88644eb80993339dbf1db3a12c68
|
||||
F test/autoindex1.test 860fc83f4fefb0c68ad062afc3ff43faa1534fc4
|
||||
F test/autovacuum.test bb7c0885e6f8f1d633045de48f2b66082162766d
|
||||
F test/autovacuum_ioerr2.test 598b0663074d3673a9c1bc9a16e80971313bafe6
|
||||
F test/avtrans.test 0252654f4295ddda3b2cce0e894812259e655a85
|
||||
F test/backcompat.test 49bd844eb245f0b2b6f2a3f8bebad0065403a9a7
|
||||
F test/backup.test 200e64bd91244b73ca8094bc1e03dfc83cc94c2e
|
||||
F test/backcompat.test 541314d69ec9db3e03630b7616696ddc5048efb1
|
||||
F test/backup.test 004d3b78bffd990741ab50133ed4347c25c172b1
|
||||
F test/backup2.test b7c69f937c912e85ac8a5dbd1e1cf290302b2d49
|
||||
F test/backup_ioerr.test 1f012e692f42c0442ae652443258f70e9f20fa38
|
||||
F test/backup_malloc.test 7162d604ec2b4683c4b3799a48657fb8b5e2d450
|
||||
@ -284,12 +295,13 @@ F test/boundary3.test 56ef82096b4329aca2be74fa1e2b0f762ea0eb45
|
||||
F test/boundary4.tcl 0bb4b1a94f4fc5ae59b79b9a2b7a140c405e2983
|
||||
F test/boundary4.test 89e02fa66397b8a325d5eb102b5806f961f8ec4b
|
||||
F test/busy.test 76b4887f8b9160ba903c1ac22e8ff406ad6ae2f0
|
||||
F test/cache.test c4288607b54f2702858492fc4b92828336a1812f
|
||||
F test/capi2.test 00032d7504b9c14f1b36331670c5e7b0f73e3c5d
|
||||
F test/cache.test 754baab2f18089fc9bcba7afaeb4dc907c6c6de2
|
||||
F test/capi2.test 835d4cee9f542ea50fa8d01f3fe6de80b0627360
|
||||
F test/capi3.test 1945a2ba75e3f4c49d5beb8fc092115b6292d471
|
||||
F test/capi3b.test efb2b9cfd127efa84433cd7a2d72ce0454ae0dc4
|
||||
F test/capi3c.test bea67403a5e37a4b33230ee4723e315a2ffb31e7
|
||||
F test/capi3d.test 57d83b690d7364bde02cddbf8339a4b50d80ce23
|
||||
F test/capi3d.test cd36571f014f34bdc4421967f6453cbb597d5d16
|
||||
F test/capi3e.test 4fda47388ddfbfe807987aa62f46fcbceec9327f
|
||||
F test/cast.test 166951664a0b0a2e0f8fb5997a152490c6363932
|
||||
F test/check.test db2b29d557544347d28e25b8406f5d5ecc3d1bc3
|
||||
F test/coalesce.test cee0dccb9fbd2d494b77234bccf9dc6c6786eb91
|
||||
@ -305,7 +317,7 @@ F test/collate9.test 3adcc799229545940df2f25308dd1ad65869145a
|
||||
F test/collateA.test b8218ab90d1fa5c59dcf156efabb1b2599c580d6
|
||||
F test/colmeta.test 087c42997754b8c648819832241daf724f813322
|
||||
F test/colname.test 08948a4809d22817e0e5de89c7c0a8bd90cb551b
|
||||
F test/conflict.test f2f2b2950730a9532e11e468070cebf389f5c375
|
||||
F test/conflict.test cabc41f7616675df71b4fddabca3bd5d9221915a
|
||||
F test/corrupt.test 1a5bef8b2d178859af69814ecedcd37219a89968
|
||||
F test/corrupt2.test 808a28d0ca3b97e9aa8c91cd2b485ea2700b76d1
|
||||
F test/corrupt3.test a399dacccb91c732f6b071c913e70d195af8c058
|
||||
@ -333,8 +345,8 @@ F test/crashtest1.c 09c1c7d728ccf4feb9e481671e29dda5669bbcc2
|
||||
F test/createtab.test 199cf68f44e5d9e87a0b8afc7130fdeb4def3272
|
||||
F test/cse.test 277350a26264495e86b1785f34d2d0c8600e021c
|
||||
F test/ctime.test 7bd009071e242aac4f18521581536b652b789a47
|
||||
F test/date.test 6354b883f922c38046a8efbad187cc95df6da023
|
||||
F test/dbstatus.test 946e1399d4574fc5dac934cceedbc76924af3f5a
|
||||
F test/date.test a18a2ce81add84b17b06559e82ad7bb91bc6ddff
|
||||
F test/dbstatus.test 175b088308f2ce3f7afb8208f25c10878ee05921
|
||||
F test/default.test 6faf23ccb300114924353007795aa9a8ec0aa9dc
|
||||
F test/delete.test f7629d9eb245dfca170169cc5c7a735dec34aeb4
|
||||
F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa
|
||||
@ -344,22 +356,35 @@ F test/descidx2.test 9f1a0c83fd57f8667c82310ca21b30a350888b5d
|
||||
F test/descidx3.test fe720e8b37d59f4cef808b0bf4e1b391c2e56b6f
|
||||
F test/diskfull.test 0cede7ef9d8f415d9d3944005c76be7589bb5ebb
|
||||
F test/distinctagg.test 1a6ef9c87a58669438fc771450d7a72577417376
|
||||
F test/e_expr.test 8a35ce2718c61e871970bda09f4f3e549067c1ba
|
||||
F test/e_fkey.test 6721a741c6499b3ab7e5385923233343c8f1ad05
|
||||
F test/e_createtable.test b8f5286879315d5b7f4cc5ead1afda4846f0c0bb
|
||||
F test/e_delete.test 55d868b647acc091c261a10b9b0cb0ab660a6acb
|
||||
F test/e_droptrigger.test ddd4b28ed8a3d81bd5153fa0ab7559529a2ca03a
|
||||
F test/e_dropview.test b347bab30fc8de67b131594b3cd6f3d3bdaa753d
|
||||
F test/e_expr.test 4e004d1f5187d4bbc9ca3d55660a8d164dd59f4e
|
||||
F test/e_fkey.test 38039b840ab19331000b0f0eb1d82baa7208a67a
|
||||
F test/e_fts3.test 75bb0aee26384ef586165e21018a17f7cd843469
|
||||
F test/e_insert.test 7390c2da39f16a134dc9a439144768c727757d2c
|
||||
F test/e_reindex.test a064f0878b8f848fbca38f1f61f82f15a3000c64
|
||||
F test/e_resolve.test dcce9308fb13b934ce29591105d031d3e14fbba6
|
||||
F test/e_select.test bf385ae3aa0f014c4933ae66fd3e1302138493eb
|
||||
F test/e_select2.test 5c3d3da19c7b3e90ae444579db2b70098599ab92
|
||||
F test/e_update.test 963d6876064e65f318d1c93aaed36a02b9b389bf
|
||||
F test/e_vacuum.test 057cc29445746fc1d2542984ff0253d511a234bd
|
||||
F test/enc.test e54531cd6bf941ee6760be041dff19a104c7acea
|
||||
F test/enc2.test 6d91a5286f59add0cfcbb2d0da913b76f2242398
|
||||
F test/enc3.test 5c550d59ff31dccdba5d1a02ae11c7047d77c041
|
||||
F test/enc4.test 4b575ef09e0eff896e73bd24076f96c2aa6a42de
|
||||
F test/eqp.test 69670e7919030f21de29fb99bf1d68f97aedcbdb
|
||||
F test/eval.test bc269c365ba877554948441e91ad5373f9f91be3
|
||||
F test/exclusive.test b1f9012cabc124af947165d15ffa62ad20f63db8
|
||||
F test/exclusive2.test fcbb1c9ca9739292a0a22a3763243ad6d868086b
|
||||
F test/exclusive.test 53e1841b422e554cecf0160f937c473d6d0e3062
|
||||
F test/exclusive2.test b65264c3e76e1db6c6eda15c02000a40743f6541
|
||||
F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7
|
||||
F test/expr.test 9f521ae22f00e074959f72ce2e55d46b9ed23f68
|
||||
F test/expr.test 620a636cf7b7d4e5834a0b9d83a4da372e24a7b7
|
||||
F test/fallocate.test 43dc34b8c24be6baffadc3b4401ee15710ce83c6
|
||||
F test/filectrl.test 97003734290887566e01dded09dc9e99cb937e9e
|
||||
F test/filefmt.test f77c92141960b7933bc6691631d2ad62257ef40a
|
||||
F test/fkey1.test 01c7de578e11747e720c2d9aeef27f239853c4da
|
||||
F test/fkey2.test e028cd80aa0bd38541c99214e3ba2dfccadffe6f
|
||||
F test/fkey2.test 080969fe219b3b082b0e097ac18c6af2e5b0631f
|
||||
F test/fkey3.test 42f88d6048d8dc079e2a8cf7baad1cc1483a7620
|
||||
F test/fkey_malloc.test a5ede29bd2f6e56dea78c3d43fb86dd696c068c8
|
||||
F test/format4.test 1f0cac8ff3895e9359ed87e41aaabee982a812eb
|
||||
@ -399,37 +424,45 @@ F test/fts2r.test b154c30b63061d8725e320fba1a39e2201cadd5e
|
||||
F test/fts2token.test d8070b241a15ff13592a9ae4a8b7c171af6f445a
|
||||
F test/fts3.test 672a040ea57036fb4b6fdc09027c18d7d24ab654
|
||||
F test/fts3_common.tcl 4d8eec9db565fed9098f45c378f28e1657802011
|
||||
F test/fts3aa.test 5327d4c1d9b6c61021696746cc9a6cdc5bf159c0
|
||||
F test/fts3aa.test 909d5f530d30a8e36b9328d67285eae6537c79c0
|
||||
F test/fts3ab.test 09aeaa162aee6513d9ff336b6932211008b9d1f9
|
||||
F test/fts3ac.test 636ed7486043055d4f126a0e385f2d5a82ebbf63
|
||||
F test/fts3ad.test e40570cb6f74f059129ad48bcef3d7cbc20dda49
|
||||
F test/fts3ae.test ce32a13b34b0260928e4213b4481acf801533bda
|
||||
F test/fts3af.test d394978c534eabf22dd0837e718b913fd66b499c
|
||||
F test/fts3ag.test 0b7d303f61ae5d620c4efb5e825713ea34ff9441
|
||||
F test/fts3ah.test ba181d6a3dee0c929f0d69df67cac9c47cda6bff
|
||||
F test/fts3ah.test dc9f66c32c296f1bc8bcc4535126bddfeca62894
|
||||
F test/fts3ai.test d29cee6ed653e30de478066881cec8aa766531b2
|
||||
F test/fts3aj.test 584facbc9ac4381a7ec624bfde677340ffc2a5a4
|
||||
F test/fts3ak.test bd14deafe9d1586e8e9bf032411026ac4f8c925d
|
||||
F test/fts3al.test 07d64326e79bbdbab20ee87fc3328fbf01641c9f
|
||||
F test/fts3am.test 218aa6ba0dfc50c7c16b2022aac5c6be593d08d8
|
||||
F test/fts3an.test a49ccadc07a2f7d646ec1b81bc09da2d85a85b18
|
||||
F test/fts3ao.test 0aa29dd4fc1c8d46b1f7cfe5926f7ac97551bea9
|
||||
F test/fts3ao.test b83f99f70e9eec85f27d75801a974b3f820e01f9
|
||||
F test/fts3atoken.test 25c2070e1e8755d414bf9c8200427b277a9f99fa
|
||||
F test/fts3b.test e93bbb653e52afde110ad53bbd793f14fe7a8984
|
||||
F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958
|
||||
F test/fts3cov.test 3a9d8618a3107166530c447e808f8992372e0415
|
||||
F test/fts3corrupt.test d874ba27975aa8e5514bf58bf97b473404de0dbb
|
||||
F test/fts3corrupt2.test 6d96efae2f8a6af3eeaf283aba437e6d0e5447ba
|
||||
F test/fts3cov.test e0fb00d8b715ddae4a94c305992dfc3ef70353d7
|
||||
F test/fts3d.test 95fb3c862cbc4297c93fceb9a635543744e9ef52
|
||||
F test/fts3defer.test d6cb0db9b5997ecf863d96ff419f83f8f2c87f4f
|
||||
F test/fts3defer2.test da840efaedebfdd54293d04b36098e2d9872caa6
|
||||
F test/fts3e.test 1f6c6ac9cc8b772ca256e6b22aaeed50c9350851
|
||||
F test/fts3expr.test 5e745b2b6348499d9ef8d59015de3182072c564c
|
||||
F test/fts3expr2.test 18da930352e5693eaa163a3eacf96233b7290d1a
|
||||
F test/fts3malloc.test 059592c4f37ccd30138bbf8e3e5b7982cb5c8f2e
|
||||
F test/fts3fault.test f83e556465bb69dc8bc676339eca408dce4ca246
|
||||
F test/fts3malloc.test 9c8cc3f885bb4dfc66d0460c52f68f45e4710d1b
|
||||
F test/fts3matchinfo.test cc0b009edbbf575283d5fdb53271179e0d8019ba
|
||||
F test/fts3near.test 2e318ee434d32babd27c167142e2b94ddbab4844
|
||||
F test/fts3query.test 2468caf7938dbc3be2e049524320ce4faf2227b3
|
||||
F test/fts3rnd.test 707533ce943f490443ce5e696236bb1675a37635
|
||||
F test/fts3snippet.test 9f9a4a7e396c5d8ce2898be65ebabc429555430f
|
||||
F test/fts3query.test ef79d31fdb355d094baec1c1b24b60439a1fb8a2
|
||||
F test/fts3rnd.test 2b1a579be557ab8ac54a51b39caa4aa8043cc4ad
|
||||
F test/fts3shared.test 8bb266521d7c5495c0ae522bb4d376ad5387d4a2
|
||||
F test/fts3snippet.test a12f22a3ba4dd59751a57c79b031d07ab5f51ddd
|
||||
F test/fts4aa.test eadf85621c0a113d4c7ad3ccbf8441130e007b8f
|
||||
F test/func.test 6c5ce11e3a0021ca3c0649234e2d4454c89110ca
|
||||
F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f
|
||||
F test/func3.test 7ba2ca5a1e9bca900ba2c230cf04bd67184bc1bc
|
||||
F test/fuzz.test 77fd50afc12847af50fcf1941679d90adebadde6
|
||||
F test/fuzz2.test 207d0f9d06db3eaf47a6b7bfc835b8e2fc397167
|
||||
F test/fuzz3.test aec64345184d1662bd30e6a17851ff659d596dc5
|
||||
@ -437,20 +470,22 @@ F test/fuzz_common.tcl a87dfbb88c2a6b08a38e9a070dabd129e617b45b
|
||||
F test/fuzz_malloc.test dd7001ac86d09c154a7dff064f4739c60e2b312c
|
||||
F test/hook.test f04c3412463f8ec117c1c704c74ca0f627ce733a
|
||||
F test/icu.test 70df4faca133254c042d02ae342c0a141f2663f4
|
||||
F test/in.test d49419c6df515852f477fa513f3317181d46bc92
|
||||
F test/in.test 19b642bb134308980a92249750ea4ce3f6c75c2d
|
||||
F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75
|
||||
F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0
|
||||
F test/in4.test 64f3cc1acde1b9161ccdd8e5bde3daefdb5b2617
|
||||
F test/incrblob.test e557f262cd2cc088e6bb4d154575a1bbe242edcd
|
||||
F test/incrblob.test 76e787ca3301d9bfa6906031c626d26f8dd707de
|
||||
F test/incrblob2.test edc3a96e557bd61fb39acc8d2edd43371fbbaa19
|
||||
F test/incrblob3.test aedbb35ea1b6450c33b98f2b6ed98e5020be8dc7
|
||||
F test/incrblob_err.test c577c91d4ed9e8336cdb188b15d6ee2a6fe9604e
|
||||
F test/incrblobfault.test 917c0292224c64a56ef7215fd633a3a82f805be0
|
||||
F test/incrvacuum.test 453d1e490d8f5ad2c9b3a54282a0690d6ae56462
|
||||
F test/incrvacuum2.test 9e22a794899c91b7d8c8e12eaacac8df249faafe
|
||||
F test/incrvacuum_ioerr.test 57d2f5777ab13fa03b87b262a4ea1bad5cfc0291
|
||||
F test/index.test cbf301cdb2da43e4eac636c3400c2439af1834ad
|
||||
F test/index.test df7c00c6edd9504ab71c83a9514f1c5ca0fa54d8
|
||||
F test/index2.test ee83c6b5e3173a3d7137140d945d9a5d4fdfb9d6
|
||||
F test/index3.test 423a25c789fc8cc51aaf2a4370bbdde2d9e9eed7
|
||||
F test/indexedby.test 5a1180602f2e72c481467bd4cae05dae5dc36f47
|
||||
F test/indexedby.test d7367c5a0e8ed8db642824a68126753e0808c706
|
||||
F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7
|
||||
F test/insert.test aef273dd1cee84cc92407469e6bd1b3cdcb76908
|
||||
F test/insert2.test 4f3a04d168c728ed5ec2c88842e772606c7ce435
|
||||
@ -461,7 +496,7 @@ F test/intarray.test 066b7d7ac38d25bf96f87f1b017bfc687551cdd4
|
||||
F test/interrupt.test 42e7cf98646fd9cb4a3b131a93ed3c50b9e149f1
|
||||
F test/intpkey.test 537669fd535f62632ca64828e435b9e54e8d677f
|
||||
F test/io.test 1b895d6774491895cbc75659969f07ca01860c88
|
||||
F test/ioerr.test 390785ec65f10aa58a82b048ee12e9052d783fa8
|
||||
F test/ioerr.test 622aebd2f24779cafaf5dd3e3c2b349ce40ade3b
|
||||
F test/ioerr2.test 1b56cb80d5b0726ee3ba325ca175734541e32955
|
||||
F test/ioerr3.test d3cec5e1a11ad6d27527d0d38573fbff14c71bdd
|
||||
F test/ioerr4.test fc6eddfec2efc2f1ed217b9eae4c1c1d3516ce86
|
||||
@ -477,28 +512,28 @@ F test/journal2.test 50a3604768494d4a337f194f0a9480e7c57dcb72
|
||||
F test/journal3.test ff175219be1b02d2f7e54297ad7e491b7533edb6
|
||||
F test/jrnlmode.test e3fe6c4a2c3213d285650dc8e33aab7eaaa5ce53
|
||||
F test/jrnlmode2.test a19e28de1a6ec898067e46a122f1b71c9323bf00
|
||||
F test/jrnlmode3.test cfcdb12b90e640a23b92785a002d96c0624c8710
|
||||
F test/jrnlmode3.test c6522b276ba315fd1416198de6fc1da9e72409fb
|
||||
F test/keyword1.test a2400977a2e4fde43bf33754c2929fda34dbca05
|
||||
F test/lastinsert.test 474d519c68cb79d07ecae56a763aa7f322c72f51
|
||||
F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200
|
||||
F test/like.test 565d240313f15a8afa8d7098dc9fe303c1e2a496
|
||||
F test/like.test 0f64aeaed50b6e3ebaef3af0b3b8f894aed5acca
|
||||
F test/like2.test 3b2ee13149ba4a8a60b59756f4e5d345573852da
|
||||
F test/limit.test 2db7b3b34fb925b8e847d583d2eb67531d0ce67e
|
||||
F test/loadext.test 0393ce12d9616aa87597dd0ec88181de181f6db0
|
||||
F test/loadext2.test 0bcaeb4d81cd5b6e883fdfea3c1bdbe1f173cbca
|
||||
F test/lock.test 842e80b6be816c79525a20b098cca066989feed7
|
||||
F test/lock.test db74fdf5a73bad29ab3d862ea78bf1068972cc1d
|
||||
F test/lock2.test 5242d8ac4e2d59c403aebff606af449b455aceff
|
||||
F test/lock3.test f271375930711ae044080f4fe6d6eda930870d00
|
||||
F test/lock4.test c82268c031d39345d05efa672f80b025481b3ae5
|
||||
F test/lock5.test b2abb5e711bc59b0eae00f6c97a36ec9f458fada
|
||||
F test/lock6.test 8df56060f396151777390982422c800d026e1722
|
||||
F test/lock6.test ad5b387a3a8096afd3c68a55b9535056431b0cf5
|
||||
F test/lock7.test 64006c84c1c616657e237c7ad6532b765611cf64
|
||||
F test/lock_common.tcl 18c637fc89e12f1ac0d27d2186f12c3d3f789e3e
|
||||
F test/lookaside.test 382e7bc2fab23d902c8eafb1b9ed7ababfff75a6
|
||||
F test/lock_common.tcl d279887a0ab16cdb6d935c1203e64113c5a000e9
|
||||
F test/lookaside.test 93f07bac140c5bb1d49f3892d2684decafdc7af2
|
||||
F test/main.test 9d7bbfcc1b52c88ba7b2ba6554068ecf9939f252
|
||||
F test/make-where7.tcl 05c16b5d4f5d6512881dfec560cb793915932ef9
|
||||
F test/malloc.test 927e6c8668a1d48c23aa6189bda02aff5a1b83de
|
||||
F test/malloc3.test 4bc57f850b212f706f3e1b37c4eced1d5a727cd1
|
||||
F test/malloc3.test 4128b1e6ffa506103b278ad97af89174f310c7ca
|
||||
F test/malloc4.test 957337613002b7058a85116493a262f679f3a261
|
||||
F test/malloc5.test 4d16d1bb26d2deddd7c4f480deec341f9b2d0e22
|
||||
F test/malloc6.test 2f039d9821927eacae43e1831f815e157659a151
|
||||
@ -517,11 +552,11 @@ F test/mallocH.test 79b65aed612c9b3ed2dcdaa727c85895fd1bfbdb
|
||||
F test/mallocI.test a88c2b9627c8506bf4703d8397420043a786cdb6
|
||||
F test/mallocJ.test b5d1839da331d96223e5f458856f8ffe1366f62e
|
||||
F test/mallocK.test d79968641d1b70d88f6c01bdb9a7eb4a55582cc9
|
||||
F test/malloc_common.tcl f4a04b7a733eb114a3da16eb39035cde2c851220
|
||||
F test/malloc_common.tcl 660b82ab528521cc4a48ff6df05ca3b6a00d47c5
|
||||
F test/manydb.test b3d3bc4c25657e7f68d157f031eb4db7b3df0d3c
|
||||
F test/memdb.test 0825155b2290e900264daaaf0334b6dfe69ea498
|
||||
F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2
|
||||
F test/memsubsys1.test 8fb47b7e2523f94c100f5885c5697505524de4b9
|
||||
F test/memsubsys1.test 679db68394a5692791737b150852173b3e2fea10
|
||||
F test/memsubsys2.test 72a731225997ad5e8df89fdbeae9224616b6aecc
|
||||
F test/minmax.test 722d80816f7e096bf2c04f4111f1a6c1ba65453d
|
||||
F test/minmax2.test 33504c01a03bd99226144e4b03f7631a274d66e0
|
||||
@ -529,12 +564,13 @@ F test/minmax3.test 66a60eb0f20281b0753249d347c5de0766954cee
|
||||
F test/misc1.test e56baf44656dd68d6475a4b44521045a60241e9b
|
||||
F test/misc2.test a628db7b03e18973e5d446c67696b03de718c9fd
|
||||
F test/misc3.test 72c5dc87a78e7865c5ec7a969fc572913dbe96b6
|
||||
F test/misc4.test 91e8ed25c092c2bb4e0bb01864631e2930f8d7de
|
||||
F test/misc4.test 9c078510fbfff05a9869a0b6d8b86a623ad2c4f6
|
||||
F test/misc5.test 45b2e3ed5f79af2b4f38ae362eaf4c49674575bd
|
||||
F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91
|
||||
F test/misc7.test c5f4e6a82e04e71820c0f9f64f6733f04c8ae0ae
|
||||
F test/misc7.test 29032efcd3d826fbd409e2a7af873e7939f4a4e3
|
||||
F test/misuse.test 30b3a458e5a70c31e74c291937b6c82204c59f33
|
||||
F test/mutex1.test 5b71777fc127509cd257910c8db799de557a02de
|
||||
F test/multiplex.test 92a4839213fd8cba8b59f86d42b7a1da1857db39
|
||||
F test/mutex1.test 78b2b9bb320e51d156c4efdb71b99b051e7a4b41
|
||||
F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660
|
||||
F test/nan.test a44e04df1486fcfb02d32468cbcd3c8e1e433723
|
||||
F test/notify1.test 8433bc74bd952fb8a6e3f8d7a4c2b28dfd69e310
|
||||
@ -543,32 +579,36 @@ F test/notify3.test d60923e186e0900f4812a845fcdfd8eea096e33a
|
||||
F test/notnull.test cc7c78340328e6112a13c3e311a9ab3127114347
|
||||
F test/null.test a8b09b8ed87852742343b33441a9240022108993
|
||||
F test/openv2.test af02ed0a9cbc0d2a61b8f35171d4d117e588e4ec
|
||||
F test/pager1.test 6922029d71a8090169c71a67a141b6b94ad17d50
|
||||
F test/pager2.test 0fbb6b6dc40ce1fecfe758c555a748ad2e9beaa3
|
||||
F test/pager1.test 7006a8b5dd3df1fe0d51d7da014333d7dc099778
|
||||
F test/pager2.test 745b911dde3d1f24ae0870bd433dfa83d7c658c1
|
||||
F test/pager3.test 3856d9c80839be0668efee1b74811b1b7f7fc95f
|
||||
F test/pagerfault.test 84c6a4fcfe1a9e450fc1cec86f5baebfb017e53e
|
||||
F test/pagerfault.test 9de4d3e0c59970b4c6cb8dac511fa242f335d8a7
|
||||
F test/pagerfault2.test 1f79ea40d1133b2683a2f811b00f2399f7ec2401
|
||||
F test/pagerfault3.test 9b413f48a3e9a9a8c26968118f8db19fd7bfb8c7
|
||||
F test/pageropt.test 8146bf448cf09e87bb1867c2217b921fb5857806
|
||||
F test/pagesize.test 76aa9f23ecb0741a4ed9d2e16c5fa82671f28efb
|
||||
F test/pcache.test 4118a183908ecaed343a06fcef3ba82e87e0129d
|
||||
F test/pcache.test 065aa286e722ab24f2e51792c1f093bf60656b16
|
||||
F test/pcache2.test 0d85f2ab6963aee28c671d4c71bec038c00a1d16
|
||||
F test/permutations.test 17498d1219f922d5a6da893a94c4dc7766fb2426
|
||||
F test/pragma.test ed78d200f65c6998df51196cb8c39d5300570f24
|
||||
F test/permutations.test c0ce0f3b741dd92a6d4c2671dbacba4b92dd81eb
|
||||
F test/pragma.test fdfc09067ea104a0c247a1a79d8093b56656f850
|
||||
F test/pragma2.test 5364893491b9231dd170e3459bfc2e2342658b47
|
||||
F test/printf.test 05970cde31b1a9f54bd75af60597be75a5c54fea
|
||||
F test/progress.test 5b075c3c790c7b2a61419bc199db87aaf48b8301
|
||||
F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc
|
||||
F test/quick.test 1681febc928d686362d50057c642f77a02c62e57
|
||||
F test/quota.test ddafe133653093eb9a99ccd6264884ae43f9c9b8
|
||||
F test/quote.test 215897dbe8de1a6f701265836d6601cc6ed103e6
|
||||
F test/randexpr1.tcl 40dec52119ed3a2b8b2a773bce24b63a3a746459
|
||||
F test/randexpr1.test 1084050991e9ba22c1c10edd8d84673b501cc25a
|
||||
F test/rdonly.test c267d050a1d9a6a321de502b737daf28821a518d
|
||||
F test/reindex.test 44edd3966b474468b823d481eafef0c305022254
|
||||
F test/releasetest.mk 2eced2f9ae701fd0a29e714a241760503ccba25a
|
||||
F test/releasetest.tcl 627ccd04a113a193c375594bd5d6d051d8220658
|
||||
F test/rollback.test 1a83118ea6db4e7d8c10eaa63871b5e90502ffdc
|
||||
F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81
|
||||
F test/rowid.test e58e0acef38b527ed1b0b70d3ada588f804af287
|
||||
F test/rtree.test fb372aff108d4371bd0b5e63e106947587ff4310
|
||||
F test/savepoint.test 992d6429b6bce16ac172f7431975044ceaeb0803
|
||||
F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798
|
||||
F test/savepoint.test a1bef7ace82cc7922975fa96b06176e9bd5114cf
|
||||
F test/savepoint2.test 9b8543940572a2f01a18298c3135ad0c9f4f67d7
|
||||
F test/savepoint3.test e328085853b14898d78ceea00dfe7db18bb6a9ec
|
||||
F test/savepoint4.test c8f8159ade6d2acd9128be61e1230f1c1edc6cc0
|
||||
@ -577,13 +617,14 @@ F test/savepoint6.test 76d3948568b2cdc0c13a671cadcae75009b183d6
|
||||
F test/schema.test 8f7999be894260f151adf15c2c7540f1c6d6a481
|
||||
F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5
|
||||
F test/schema3.test 1bc1008e1f8cb5654b248c55f27249366eb7ed38
|
||||
F test/schema4.test e6a66e20cc69f0e306667c08be7fda3d11707dc5
|
||||
F test/securedel.test 328d2921c0ca49bdd3352e516b0377fc07143254
|
||||
F test/select1.test f67ca2dfc05df41c7b86eb32ca409b427a5f43b0
|
||||
F test/select2.test 352480e0e9c66eda9c3044e412abdf5be0215b56
|
||||
F test/select3.test 2ce595f8fb8e2ac10071d3b4e424cadd4634a054
|
||||
F test/select4.test 44aa6e7110592e18110b0b9cf5c024d37d23be17
|
||||
F test/select5.test e758b8ef94f69b111df4cb819008856655dcd535
|
||||
F test/select6.test 2b5e8500d8ec3dd4c8e0c99eb1431b3d11fcc24c
|
||||
F test/select6.test cc25a8650cf9a4d4f74e586c45a75f9836516b18
|
||||
F test/select7.test dad6f00f0d49728a879d6eb6451d4752db0b0abe
|
||||
F test/select8.test 391de11bdd52339c30580dabbbbe97e3e9a3c79d
|
||||
F test/select9.test 74c0fb2c6eecb0219cbed0cbe3df136f8fbf9343
|
||||
@ -592,7 +633,7 @@ F test/selectB.test f305cc6660804cb239aab4e2f26b0e288b59958b
|
||||
F test/selectC.test f9bf1bc4581b5b8158caa6e4e4f682acb379fb25
|
||||
F test/server1.test f5b790d4c0498179151ca8a7715a65a7802c859c
|
||||
F test/shared.test b9114eaea7e748a3a4c8ff7b9ca806c8f95cef3e
|
||||
F test/shared2.test d6ba4ca1827ea36a1ac23a99e3c36eeac9165450
|
||||
F test/shared2.test 7f6ad2d857d0f4e5d6a0b9a897b5e56a6b6ea18c
|
||||
F test/shared3.test d69bdd5f156580876c5345652d21dc2092e85962
|
||||
F test/shared4.test d0fadacb50bb6981b2fb9dc6d1da30fa1edddf83
|
||||
F test/shared6.test 990d2584b5db28e6e1f24742c711b26e59757b67
|
||||
@ -613,19 +654,20 @@ F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715
|
||||
F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa
|
||||
F test/speed4p.test 0e51908951677de5a969b723e03a27a1c45db38b
|
||||
F test/sqllimits1.test e90a0ed94452076f6a10209d378e06b5f75ef0a0
|
||||
F test/stat.test 70fe540ffb285947aead5533dfd0c8c12f17f14e
|
||||
F test/stmt.test 7915bd3e8380b956c095f40f41a775a30716e649
|
||||
F test/stat.test c7b20ea43003dc2dc33335e231c27be8284c4a2a
|
||||
F test/stmt.test 25d64e3dbf9a3ce89558667d7f39d966fe2a71b9
|
||||
F test/subquery.test b524f57c9574b2c0347045b4510ef795d4686796
|
||||
F test/subselect.test d24fd8757daf97dafd2e889c73ea4c4272dcf4e4
|
||||
F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a
|
||||
F test/superlock.test 8468e057d8a5531ff99e504e77fcc585a0291bf2
|
||||
F test/sync.test ded6b39d8d8ca3c0c5518516c6371b3316d3e3a3
|
||||
F test/table.test 04ba066432430657712d167ebf28080fe878d305
|
||||
F test/tableapi.test 7262a8cbaa9965d429f1cbd2747edc185fa56516
|
||||
F test/tclsqlite.test 8c154101e704170c2be10f137a5499ac2c6da8d3
|
||||
F test/tempdb.test 800c36623d67a2ad1f58784b9c5644e0405af6e6
|
||||
F test/tempdb.test 19d0f66e2e3eeffd68661a11c83ba5e6ace9128c
|
||||
F test/temptable.test f42121a0d29a62f00f93274464164177ab1cc24a
|
||||
F test/temptrigger.test b0273db072ce5f37cf19140ceb1f0d524bbe9f05
|
||||
F test/tester.tcl 6135019fcfac363ea0e11aee670cc97080ab578e
|
||||
F test/tester.tcl dafe0d30279f6d380d5d2a535781dda91b8cfc3f
|
||||
F test/thread001.test a3e6a7254d1cb057836cb3145b60c10bf5b7e60f
|
||||
F test/thread002.test afd20095e6e845b405df4f2c920cb93301ca69db
|
||||
F test/thread003.test b824d4f52b870ae39fc5bae4d8070eca73085dca
|
||||
@ -636,23 +678,30 @@ F test/thread2.test e08034b83fe9693ade77049732518e5b3d2d700d
|
||||
F test/thread_common.tcl 2aa6f2fdcd4d6e461169c3e5ca098eebf643b863
|
||||
F test/threadtest1.c 6029d9c5567db28e6dc908a0c63099c3ba6c383b
|
||||
F test/threadtest2.c ace893054fa134af3fc8d6e7cfecddb8e3acefb9
|
||||
F test/threadtest3.c 58df1e3c060f534fd7fb0702331b0acc41c381d8
|
||||
F test/threadtest3.c d6d209190c7110f9a7e6a8154bdc3260efdbf8b7
|
||||
F test/tkt-02a8e81d44.test 58494de77be2cf249228ada3f313fa399821c6ab
|
||||
F test/tkt-26ff0c2d1e.test 888324e751512972c6e0d1a09df740d8f5aaf660
|
||||
F test/tkt-2ea2425d34.test 1cf13e6f75d149b3209a0cb32927a82d3d79fb28
|
||||
F test/tkt-31338dca7e.test 5741cd48de500347a437ba1be58c8335e83c5a5e
|
||||
F test/tkt-313723c356.test c47f8a9330523e6f35698bf4489bcb29609b53ac
|
||||
F test/tkt-38cb5df375.test 9e9b19857dba0896a8efdaf334d405ba423492f2
|
||||
F test/tkt-3998683a16.test 6d1d04d551ed1704eb3396ca87bb9ccc8c5c1eb7
|
||||
F test/tkt-3fe897352e.test 10de1a67bd5c66b238a4c96abe55531b37bb4f00
|
||||
F test/tkt-4a03edc4c8.test 2865e4edbc075b954daa82f8da7cc973033ec76e
|
||||
F test/tkt-5d863f876e.test 884072c2de496ddbb90c387c9ebc0d4f44a91b8e
|
||||
F test/tkt-5e10420e8d.test 904d1687b3c06d43e5b3555bbcf6802e7c0ffd84
|
||||
F test/tkt-5ee23731f.test 3581260f2a71e51db94e1506ba6b0f7311d002a9
|
||||
F test/tkt-78e04e52ea.test fb5430c675e708f5cbafdf3e7e5593da5145a527
|
||||
F test/tkt-78e04e52ea.test ab52f0c1e2de6e46c910f4cc16b086bba05952b7
|
||||
F test/tkt-80ba201079.test a09684db1a0bd55b8838f606adccee456a51ddbf
|
||||
F test/tkt-80e031a00f.test 9a154173461a4dbe2de49cda73963e04842d52f7
|
||||
F test/tkt-8454a207b9.test c583a9f814a82a2b5ba95207f55001c9f0cd816c
|
||||
F test/tkt-94c04eaadb.test be5ea61cb04dfdc047d19b5c5a9e75fa3da67a7f
|
||||
F test/tkt-9d68c883.test 458f7d82a523d7644b54b497c986378a7d8c8b67
|
||||
F test/tkt-b351d95f9.test d14a503c414c5c58fdde3e80f9a3cfef986498c0
|
||||
F test/tkt-cbd054fa6b.test f14f97ea43662e6f70c9e63287081e8be5d9d589
|
||||
F test/tkt-d11f09d36e.test fb44f7961aa6d4b632fb7b9768239832210b5fc7
|
||||
F test/tkt-d82e3f3721.test 731359dfdcdb36fea0559cd33fec39dd0ceae8e6
|
||||
F test/tkt-f3e5abed55.test 91713833e266fbdc60f2030e05647ad4762073f6
|
||||
F test/tkt-f3e5abed55.test 19fb59268da6f20a69a181b9c14154132d1c65e3
|
||||
F test/tkt-f777251dc7a.test 6f24c053bc5cdb7e1e19be9a72c8887cf41d5e87
|
||||
F test/tkt-f973c7ac31.test 1da0ed15ec2c7749fb5ce2828cd69d07153ad9f4
|
||||
F test/tkt-fc62af4523.test 72825d3febdedcd5593a27989fc05accdbfc2bb4
|
||||
@ -668,7 +717,7 @@ F test/tkt1536.test 83ff7a7b6e248016f8d682d4f7a4ae114070d466
|
||||
F test/tkt1537.test e3a14332de9770be8ff14bd15c19a49cbec10808
|
||||
F test/tkt1567.test 18023cc3626a365f0118e17b66decedec93b1a6f
|
||||
F test/tkt1644.test 80b6a2bb17885f3cf1cb886d97cdad13232bb869
|
||||
F test/tkt1667.test c5e5a9f6c71634b5edd1c54eeb84008771526562
|
||||
F test/tkt1667.test 5d208e8d8cbcf82a446b315774290b66b464bc5f
|
||||
F test/tkt1873.test 255a002b9afdcf8b0fa3188984e2c964202340e9
|
||||
F test/tkt2141.test f543d96f50d5a5dc0bc744f7db74ea166720ce46
|
||||
F test/tkt2192.test ff40157e5f42e65f844255d220fc6b290470942f
|
||||
@ -704,7 +753,7 @@ F test/tkt3346.test 6f67c3ed7db94dfc5df4f5f0b63809a1f611e01a
|
||||
F test/tkt3357.test 77c37c6482b526fe89941ce951c22d011f5922ed
|
||||
F test/tkt3419.test 1bbf36d7ea03b638c15804251287c2391f5c1f6b
|
||||
F test/tkt3424.test 61f831bd2b071bd128fa5d00fbda57e656ca5812
|
||||
F test/tkt3442.test 89d7b41a4ec4d9d9b40ab8575d648579fb13cb4f
|
||||
F test/tkt3442.test 0adb70e9fe9cb750a702065a68ad647409dbc158
|
||||
F test/tkt3457.test edbf54b05cbe5165f00192becbd621038f1615e4
|
||||
F test/tkt3461.test 228ea328a5a21e8663f80ee3d212a6ad92549a19
|
||||
F test/tkt3493.test 1686cbde85f8721fc1bdc0ee72f2ef2f63139218
|
||||
@ -718,7 +767,7 @@ F test/tkt35xx.test ed9721bd9eb1693b3b4d3cf2a093fa7f92af0c93
|
||||
F test/tkt3630.test 929f64852103054125200bc825c316d5f75d42f7
|
||||
F test/tkt3718.test 3b59dcb5c4e7754dacd91e7fd353a61492cc402a
|
||||
F test/tkt3731.test 0c5f4cbffe102d43c3b2188af91a9e36348f974b
|
||||
F test/tkt3757.test 8f2208930655bbd4f92c14e19e72303a43e098ef
|
||||
F test/tkt3757.test 10cd679a88675c880533083fc79ac04324525595
|
||||
F test/tkt3761.test b95ea9c98f21cf91325f18a984887e62caceab33
|
||||
F test/tkt3762.test 2a9f3b03df44ec49ec0cfa8d5da6574c2a7853df
|
||||
F test/tkt3773.test 430b06567ce40285dfd2c4834a2a61816403efeb
|
||||
@ -741,6 +790,7 @@ F test/tkt3997.test a335fa41ca3985660a139df7b734a26ef53284bd
|
||||
F test/tkt4018.test 7c2c9ba4df489c676a0a7a0e809a1fb9b2185bd1
|
||||
F test/tokenize.test ce430a7aed48fc98301611429595883fdfcab5d7
|
||||
F test/trace.test 4b36a41a3e9c7842151af6da5998f5080cdad9e5
|
||||
F test/trace2.test 092bc2c5776272700450d60a36919921095bdc21
|
||||
F test/trans.test 6e1b4c6a42dba31bd65f8fa5e61a2708e08ddde6
|
||||
F test/trans2.test d5337e61de45e66b1fcbf9db833fa8c82e624b22
|
||||
F test/trans3.test d728abaa318ca364dc370e06576aa7e5fbed7e97
|
||||
@ -755,22 +805,22 @@ F test/trigger8.test 30cb0530bd7c4728055420e3f739aa00412eafa4
|
||||
F test/trigger9.test 5b0789f1c5c4600961f8e68511b825b87be53e31
|
||||
F test/triggerA.test eaf11a29db2a11967d2d4b49d37f92bce598194e
|
||||
F test/triggerB.test 56780c031b454abac2340dbb3b71ac5c56c3d7fe
|
||||
F test/triggerC.test 2a23edcc00684d084902ba5ec93e721775c3a70a
|
||||
F test/triggerC.test 8a691ff6dd47df2e57395bbec4b62101fac0f363
|
||||
F test/triggerD.test c6add3817351451e419f6ff9e9a259b02b6e2de7
|
||||
F test/types.test 9a825ec8eea4e965d7113b74c76a78bb5240f2ac
|
||||
F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff
|
||||
F test/types2.test 3555aacf8ed8dc883356e59efc314707e6247a84
|
||||
F test/types3.test a0f66bf12f80fad89493535474f7a6d16fa58150
|
||||
F test/unique.test 083c7fff74695bcc27a71d75699deba3595bc9c2
|
||||
F test/update.test 8bc86fd7ef1a00014f76dc6a6a7c974df4aef172
|
||||
F test/utf16align.test 54cd35a27c005a9b6e7815d887718780b6a462ae
|
||||
F test/vacuum.test 15ae6784e70428b8db64e95c92d84b19e507b719
|
||||
F test/vacuum2.test ec57f21d394b7b72249b11f8e4b5d487bab56539
|
||||
F test/vacuum.test 29b60e8cc9e573b39676df6c4a75fe9e02d04a09
|
||||
F test/vacuum2.test 2165164ed2463816e8c4648d0a779a863ce1a76c
|
||||
F test/vacuum3.test f39ad1428347c5808cd2da7578c470f186a4d0ce
|
||||
F test/vacuum4.test d3f8ecff345f166911568f397d2432c16d2867d9
|
||||
F test/varint.test ab7b110089a08b9926ed7390e7e97bdefeb74102
|
||||
F test/veryquick.test 7701bb609fe8bf6535514e8b849a309e8f00573b
|
||||
F test/view.test 45f518205ecdb6dd23a86dd4a99bb4ae945e625d
|
||||
F test/vtab1.test 9bc4a349a1989bcd064eb3b8fac2f06aca64297a
|
||||
F test/vtab1.test 7b79832824cbae37ff01a06ed155027f7c15bf9e
|
||||
F test/vtab2.test 7bcffc050da5c68f4f312e49e443063e2d391c0d
|
||||
F test/vtab3.test baad99fd27217f5d6db10660522e0b7192446de1
|
||||
F test/vtab4.test 942f8b8280b3ea8a41dae20e7822d065ca1cb275
|
||||
@ -788,31 +838,33 @@ F test/vtab_alter.test 9e374885248f69e251bdaacf480b04a197f125e5
|
||||
F test/vtab_err.test 0d4d8eb4def1d053ac7c5050df3024fd47a3fbd8
|
||||
F test/vtab_shared.test 0eff9ce4f19facbe0a3e693f6c14b80711a4222d
|
||||
F test/wal.test 70227190e713b3e7eb2a7d5ec3510b66db01f327
|
||||
F test/wal2.test 223f3e14d475730af772a7f5862d4bcfa7565c3a
|
||||
F test/wal3.test 695ea0f6c516423c611891df9a285aacd33344e3
|
||||
F test/wal2.test 3de797854de175323e7351b5f2514a30d1ee1410
|
||||
F test/wal3.test ac51126c36814bce334f66a0a4dbbfa56d429733
|
||||
F test/wal4.test 3404b048fa5e10605facaf70384e6d2943412e30
|
||||
F test/wal6.test 07aa31ca8892d0527f2c5c5a9a2a87aa421dfaa8
|
||||
F test/wal_common.tcl 895d76138043b86bdccf36494054bdabcf65837b
|
||||
F test/walbak.test 4df1c7369da0301caeb9a48fa45997fd592380e4
|
||||
F test/walbig.test e882bc1d014afffbfa2b6ba36e0f07d30a633ad0
|
||||
F test/walcksum.test a37b36375c595e61bdb7e1ec49b5f0979b6fc7ce
|
||||
F test/walcrash.test e763841551d6b23677ccb419797c1589dcbdbaf5
|
||||
F test/walcrash2.test 019d60b89d96c1937adb2b30b850ac7e86e5a142
|
||||
F test/walfault.test 05c470688d742688e455dd56816bd6bcffa298f8
|
||||
F test/walfault.test 81ed760def1c1573151d416b0d09178cf006f9fd
|
||||
F test/walhook.test ed00a40ba7255da22d6b66433ab61fab16a63483
|
||||
F test/walmode.test 4ecd37284f245247f7935896ab66f6943f0432a0
|
||||
F test/walshared.test 985b4a3406b2b2dace1d52a42d26a11dd6900981
|
||||
F test/walmode.test 22ddccd073c817ac9ead62b88ac446e8dedc7d2c
|
||||
F test/walnoshm.test a074428046408f4eb5c6a00e09df8cc97ff93317
|
||||
F test/walshared.test 6dda2293880c300baf5d791c307f653094585761
|
||||
F test/walslow.test d21625e2e99e11c032ce949e8a94661576548933
|
||||
F test/walthread.test a25a393c068a2b42b44333fa3fdaae9072f1617c
|
||||
F test/where.test de337a3fe0a459ec7c93db16a519657a90552330
|
||||
F test/where2.test 43d4becaf5a5df854e6c21d624a1cb84c6904554
|
||||
F test/where3.test 3bf8006d441b66a57bee02bb420423f84eb8fde3
|
||||
F test/where3.test 8ebedae552e13fc7f2b4e8df6cbe72a095347400
|
||||
F test/where4.test e9b9e2f2f98f00379e6031db6a6fca29bae782a2
|
||||
F test/where5.test fdf66f96d29a064b63eb543e28da4dfdccd81ad2
|
||||
F test/where6.test 5da5a98cec820d488e82708301b96cb8c18a258b
|
||||
F test/where7.test a0a92b8ce48d9c027fbdd7b764c7de1e1213575a
|
||||
F test/where7.test aa4cfcd6f66e2a4ef87b6717327325bf4d547502
|
||||
F test/where8.test a6c740fd286d7883e274e17b6230a9d672a7ab1f
|
||||
F test/where8m.test da346596e19d54f0aba35ebade032a7c47d79739
|
||||
F test/where9.test be19e1a92f80985c1a121b4678bf7d2123eaa623
|
||||
F test/where9.test 7ee38c3fd67e76789a6ec769f62f6433d3d4a5cf
|
||||
F test/whereA.test 24c234263c8fe358f079d5e57d884fb569d2da0a
|
||||
F test/whereB.test 0def95db3bdec220a731c7e4bec5930327c1d8c5
|
||||
F test/wherelimit.test 5e9fd41e79bb2b2d588ed999d641d9c965619b31
|
||||
@ -821,24 +873,24 @@ F tool/diffdb.c 7524b1b5df217c20cd0431f6789851a4e0cb191b
|
||||
F tool/fragck.tcl 5265a95126abcf6ab357f7efa544787e5963f439
|
||||
F tool/genfkey.README cf68fddd4643bbe3ff8e31b8b6d8b0a1b85e20f4
|
||||
F tool/genfkey.test 4196a8928b78f51d54ef58e99e99401ab2f0a7e5
|
||||
F tool/lemon.c fe890e2d8d2db1e3f57e2a22503dbb0f6843e517
|
||||
F tool/lemon.c dfd81a51b6e27e469ba21d01a75ddf092d429027
|
||||
F tool/lempar.c 01ca97f87610d1dac6d8cd96ab109ab1130e76dc
|
||||
F tool/mkkeywordhash.c d2e6b4a5965e23afb80fbe74bb54648cd371f309
|
||||
F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e
|
||||
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
|
||||
F tool/mksqlite3c.tcl aff0d53f0e84cf919922c0d02e767bdf5eeafb90
|
||||
F tool/mksqlite3h.tcl eb100dce83f24b501b325b340f8b5eb8e5106b3b
|
||||
F tool/mksqlite3c.tcl e0db70c2c52b0e3d0867ca931229e5b90ffe7837
|
||||
F tool/mksqlite3h.tcl d76c226a5e8e1f3b5f6593bcabe5e98b3b1ec9ff
|
||||
F tool/mksqlite3internalh.tcl 7b43894e21bcb1bb39e11547ce7e38a063357e87
|
||||
F tool/omittest.tcl 27d6f6e3b1e95aeb26a1c140e6eb57771c6d794a
|
||||
F tool/opcodeDoc.awk b3a2a3d5d3075b8bd90b7afe24283efdd586659c
|
||||
F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a
|
||||
F tool/shell1.test 930444cadb71ce9ce78bc6cd14ec21e6b69776ea
|
||||
F tool/shell1.test c31b0814a9c543db51ca0cc63edb5e77ea532303
|
||||
F tool/shell2.test 5dc76b8005b465f420fed8241621da7513060ff3
|
||||
F tool/shell3.test 4fad469e8003938426355afdf34155f08c587836
|
||||
F tool/shell4.test 35f9c3d452b4e76d5013c63e1fd07478a62f14ce
|
||||
F tool/shell5.test 62bfaf9267296da1b91e4b1c03e44e7b393f6a94
|
||||
F tool/showdb.c c7a978cf525ef0f8bc2fd29cd52108dd1dfa605a
|
||||
F tool/showjournal.c ec3b171be148656827c4949fbfb8ab4370822f87
|
||||
F tool/showdb.c 471c0f8fa472e71bb7654500096a5bdb4ea1fb2a
|
||||
F tool/showjournal.c b62cecaab86a4053d944c276bb5232e4d17ece02
|
||||
F tool/showwal.c f09e5a80a293919290ec85a6a37c85a5ddcf37d9
|
||||
F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe
|
||||
F tool/space_used.tcl f714c41a59e326b8b9042f415b628b561bafa06b
|
||||
@ -849,14 +901,14 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
|
||||
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
|
||||
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
|
||||
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
|
||||
P 21a1e5961bba148fda50cc0b7d472ca74f90808a
|
||||
R aa9cc30f9dbcb7ea427137065cf4e4be
|
||||
P 682fe41efd3578e8c9abc7138b61f361c3adbe95
|
||||
R 7fccaf9f7c013ddef8926255f6aeb773
|
||||
U drh
|
||||
Z e73ee740c6593b4458c2d23b6fa7b923
|
||||
Z 4223173e58f25d45d236e232b9c90989
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GnuPG v1.4.6 (GNU/Linux)
|
||||
|
||||
iD8DBQFMcsNUoxKgR168RlERApyRAKCOHvk0Gn1uE2F8YVm3k/w13/3UugCeOh16
|
||||
bApiXjM1a7FP+Qr1HX5kWTQ=
|
||||
=L2Lf
|
||||
iD8DBQFNQvb6oxKgR168RlERAtOTAJ9BaewewKXL3RGZUy5ycaHwjeEmJACeOPbw
|
||||
/JKqOugR+37RH7HnLCo9DBk=
|
||||
=kgPx
|
||||
-----END PGP SIGNATURE-----
|
||||
|
||||
@ -1 +1 @@
|
||||
42537b60566f288167f1b5864a5435986838e3a3
|
||||
ed759d5a9edb3bba5f48f243df47be29e3fe8cd7
|
||||
|
||||
@ -313,6 +313,11 @@ static char *whereTempTriggers(Parse *pParse, Table *pTab){
|
||||
}
|
||||
}
|
||||
}
|
||||
if( zWhere ){
|
||||
char *zNew = sqlite3MPrintf(pParse->db, "type='trigger' AND (%s)", zWhere);
|
||||
sqlite3DbFree(pParse->db, zWhere);
|
||||
zWhere = zNew;
|
||||
}
|
||||
return zWhere;
|
||||
}
|
||||
|
||||
|
||||
110
src/analyze.c
110
src/analyze.c
@ -113,7 +113,8 @@ static void analyzeOneTable(
|
||||
int i; /* Loop counter */
|
||||
int topOfLoop; /* The top of the loop */
|
||||
int endOfLoop; /* The end of the loop */
|
||||
int addr; /* The address of an instruction */
|
||||
int addr = 0; /* The address of an instruction */
|
||||
int jZeroRows = 0; /* Jump from here if number of rows is zero */
|
||||
int iDb; /* Index of database containing pTab */
|
||||
int regTabname = iMem++; /* Register containing table name */
|
||||
int regIdxname = iMem++; /* Register containing index name */
|
||||
@ -132,8 +133,15 @@ static void analyzeOneTable(
|
||||
#endif
|
||||
|
||||
v = sqlite3GetVdbe(pParse);
|
||||
if( v==0 || NEVER(pTab==0) || pTab->pIndex==0 ){
|
||||
/* Do no analysis for tables that have no indices */
|
||||
if( v==0 || NEVER(pTab==0) ){
|
||||
return;
|
||||
}
|
||||
if( pTab->tnum==0 ){
|
||||
/* Do not gather statistics on views or virtual tables */
|
||||
return;
|
||||
}
|
||||
if( memcmp(pTab->zName, "sqlite_", 7)==0 ){
|
||||
/* Do not gather statistics on system tables */
|
||||
return;
|
||||
}
|
||||
assert( sqlite3BtreeHoldsAllMutexes(db) );
|
||||
@ -150,6 +158,7 @@ static void analyzeOneTable(
|
||||
sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
|
||||
|
||||
iIdxCur = pParse->nTab++;
|
||||
sqlite3VdbeAddOp4(v, OP_String8, 0, regTabname, 0, pTab->zName, 0);
|
||||
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
|
||||
int nCol = pIdx->nColumn;
|
||||
KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx);
|
||||
@ -164,10 +173,7 @@ static void analyzeOneTable(
|
||||
(char *)pKey, P4_KEYINFO_HANDOFF);
|
||||
VdbeComment((v, "%s", pIdx->zName));
|
||||
|
||||
/* Populate the registers containing the table and index names. */
|
||||
if( pTab->pIndex==pIdx ){
|
||||
sqlite3VdbeAddOp4(v, OP_String8, 0, regTabname, 0, pTab->zName, 0);
|
||||
}
|
||||
/* Populate the register containing the index name. */
|
||||
sqlite3VdbeAddOp4(v, OP_String8, 0, regIdxname, 0, pIdx->zName, 0);
|
||||
|
||||
#ifdef SQLITE_ENABLE_STAT2
|
||||
@ -227,9 +233,10 @@ static void analyzeOneTable(
|
||||
sqlite3VdbeAddOp2(v, OP_AddImm, iMem, 1);
|
||||
|
||||
for(i=0; i<nCol; i++){
|
||||
CollSeq *pColl;
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regCol);
|
||||
#ifdef SQLITE_ENABLE_STAT2
|
||||
if( i==0 ){
|
||||
#ifdef SQLITE_ENABLE_STAT2
|
||||
/* Check if the record that cursor iIdxCur points to contains a
|
||||
** value that should be stored in the sqlite_stat2 table. If so,
|
||||
** store it. */
|
||||
@ -258,12 +265,17 @@ static void analyzeOneTable(
|
||||
|
||||
sqlite3VdbeJumpHere(v, ne);
|
||||
sqlite3VdbeAddOp2(v, OP_AddImm, regRecno, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
sqlite3VdbeAddOp3(v, OP_Ne, regCol, 0, iMem+nCol+i+1);
|
||||
/**** TODO: add collating sequence *****/
|
||||
sqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL);
|
||||
/* Always record the very first row */
|
||||
sqlite3VdbeAddOp1(v, OP_IfNot, iMem+1);
|
||||
}
|
||||
assert( pIdx->azColl!=0 );
|
||||
assert( pIdx->azColl[i]!=0 );
|
||||
pColl = sqlite3LocateCollSeq(pParse, pIdx->azColl[i]);
|
||||
sqlite3VdbeAddOp4(v, OP_Ne, regCol, 0, iMem+nCol+i+1,
|
||||
(char*)pColl, P4_COLLSEQ);
|
||||
sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
|
||||
}
|
||||
if( db->mallocFailed ){
|
||||
/* If a malloc failure has occurred, then the result of the expression
|
||||
@ -274,7 +286,11 @@ static void analyzeOneTable(
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, endOfLoop);
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-(nCol*2));
|
||||
int addr2 = sqlite3VdbeCurrentAddr(v) - (nCol*2);
|
||||
if( i==0 ){
|
||||
sqlite3VdbeJumpHere(v, addr2-1); /* Set jump dest for the OP_IfNot */
|
||||
}
|
||||
sqlite3VdbeJumpHere(v, addr2); /* Set jump dest for the OP_Ne */
|
||||
sqlite3VdbeAddOp2(v, OP_AddImm, iMem+i+1, 1);
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, iMem+nCol+i+1);
|
||||
}
|
||||
@ -302,8 +318,10 @@ static void analyzeOneTable(
|
||||
** If K>0 then it is always the case the D>0 so division by zero
|
||||
** is never possible.
|
||||
*/
|
||||
addr = sqlite3VdbeAddOp1(v, OP_IfNot, iMem);
|
||||
sqlite3VdbeAddOp2(v, OP_SCopy, iMem, regSampleno);
|
||||
if( jZeroRows==0 ){
|
||||
jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, iMem);
|
||||
}
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3VdbeAddOp4(v, OP_String8, 0, regTemp, 0, " ", 0);
|
||||
sqlite3VdbeAddOp3(v, OP_Concat, regTemp, regSampleno, regSampleno);
|
||||
@ -317,13 +335,35 @@ static void analyzeOneTable(
|
||||
sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regRowid);
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regRowid);
|
||||
sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
|
||||
}
|
||||
|
||||
/* If the table has no indices, create a single sqlite_stat1 entry
|
||||
** containing NULL as the index name and the row count as the content.
|
||||
*/
|
||||
if( pTab->pIndex==0 ){
|
||||
sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pTab->tnum, iDb);
|
||||
VdbeComment((v, "%s", pTab->zName));
|
||||
sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regSampleno);
|
||||
sqlite3VdbeAddOp1(v, OP_Close, iIdxCur);
|
||||
}else{
|
||||
assert( jZeroRows>0 );
|
||||
addr = sqlite3VdbeAddOp0(v, OP_Goto);
|
||||
sqlite3VdbeJumpHere(v, jZeroRows);
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname);
|
||||
sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regRec, "aaa", 0);
|
||||
sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regRowid);
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regRowid);
|
||||
sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
|
||||
if( pParse->nMem<regRec ) pParse->nMem = regRec;
|
||||
if( jZeroRows ){
|
||||
sqlite3VdbeJumpHere(v, addr);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate code that will cause the most recent index analysis to
|
||||
** be laoded into internal hash tables where is can be used.
|
||||
** be loaded into internal hash tables where is can be used.
|
||||
*/
|
||||
static void loadAnalysis(Parse *pParse, int iDb){
|
||||
Vdbe *v = sqlite3GetVdbe(pParse);
|
||||
@ -453,33 +493,46 @@ struct analysisInfo {
|
||||
** This callback is invoked once for each index when reading the
|
||||
** sqlite_stat1 table.
|
||||
**
|
||||
** argv[0] = name of the index
|
||||
** argv[1] = results of analysis - on integer for each column
|
||||
** argv[0] = name of the table
|
||||
** argv[1] = name of the index (might be NULL)
|
||||
** argv[2] = results of analysis - on integer for each column
|
||||
**
|
||||
** Entries for which argv[1]==NULL simply record the number of rows in
|
||||
** the table.
|
||||
*/
|
||||
static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){
|
||||
analysisInfo *pInfo = (analysisInfo*)pData;
|
||||
Index *pIndex;
|
||||
int i, c;
|
||||
Table *pTable;
|
||||
int i, c, n;
|
||||
unsigned int v;
|
||||
const char *z;
|
||||
|
||||
assert( argc==2 );
|
||||
assert( argc==3 );
|
||||
UNUSED_PARAMETER2(NotUsed, argc);
|
||||
|
||||
if( argv==0 || argv[0]==0 || argv[1]==0 ){
|
||||
if( argv==0 || argv[0]==0 || argv[2]==0 ){
|
||||
return 0;
|
||||
}
|
||||
pIndex = sqlite3FindIndex(pInfo->db, argv[0], pInfo->zDatabase);
|
||||
if( pIndex==0 ){
|
||||
pTable = sqlite3FindTable(pInfo->db, argv[0], pInfo->zDatabase);
|
||||
if( pTable==0 ){
|
||||
return 0;
|
||||
}
|
||||
z = argv[1];
|
||||
for(i=0; *z && i<=pIndex->nColumn; i++){
|
||||
if( argv[1] ){
|
||||
pIndex = sqlite3FindIndex(pInfo->db, argv[1], pInfo->zDatabase);
|
||||
}else{
|
||||
pIndex = 0;
|
||||
}
|
||||
n = pIndex ? pIndex->nColumn : 0;
|
||||
z = argv[2];
|
||||
for(i=0; *z && i<=n; i++){
|
||||
v = 0;
|
||||
while( (c=z[0])>='0' && c<='9' ){
|
||||
v = v*10 + c - '0';
|
||||
z++;
|
||||
}
|
||||
if( i==0 ) pTable->nRowEst = v;
|
||||
if( pIndex==0 ) break;
|
||||
pIndex->aiRowEst[i] = v;
|
||||
if( *z==' ' ) z++;
|
||||
}
|
||||
@ -555,7 +608,7 @@ int sqlite3AnalysisLoad(sqlite3 *db, int iDb){
|
||||
|
||||
/* Load new statistics out of the sqlite_stat1 table */
|
||||
zSql = sqlite3MPrintf(db,
|
||||
"SELECT idx, stat FROM %Q.sqlite_stat1", sInfo.zDatabase);
|
||||
"SELECT tbl, idx, stat FROM %Q.sqlite_stat1", sInfo.zDatabase);
|
||||
if( zSql==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
@ -583,8 +636,11 @@ int sqlite3AnalysisLoad(sqlite3 *db, int iDb){
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
while( sqlite3_step(pStmt)==SQLITE_ROW ){
|
||||
char *zIndex = (char *)sqlite3_column_text(pStmt, 0);
|
||||
Index *pIdx = sqlite3FindIndex(db, zIndex, sInfo.zDatabase);
|
||||
char *zIndex; /* Index name */
|
||||
Index *pIdx; /* Pointer to the index object */
|
||||
|
||||
zIndex = (char *)sqlite3_column_text(pStmt, 0);
|
||||
pIdx = zIndex ? sqlite3FindIndex(db, zIndex, sInfo.zDatabase) : 0;
|
||||
if( pIdx ){
|
||||
int iSample = sqlite3_column_int(pStmt, 1);
|
||||
if( iSample<SQLITE_INDEX_SAMPLES && iSample>=0 ){
|
||||
|
||||
11
src/attach.c
11
src/attach.c
@ -124,9 +124,8 @@ static void attachFunc(
|
||||
** it to obtain the database schema. At this point the schema may
|
||||
** or may not be initialised.
|
||||
*/
|
||||
rc = sqlite3BtreeFactory(db, zFile, 0, SQLITE_DEFAULT_CACHE_SIZE,
|
||||
db->openFlags | SQLITE_OPEN_MAIN_DB,
|
||||
&aNew->pBt);
|
||||
rc = sqlite3BtreeOpen(zFile, db, &aNew->pBt, 0,
|
||||
db->openFlags | SQLITE_OPEN_MAIN_DB);
|
||||
db->nDb++;
|
||||
if( rc==SQLITE_CONSTRAINT ){
|
||||
rc = SQLITE_ERROR;
|
||||
@ -367,7 +366,8 @@ void sqlite3Detach(Parse *pParse, Expr *pDbname){
|
||||
0, /* xStep */
|
||||
0, /* xFinalize */
|
||||
"sqlite_detach", /* zName */
|
||||
0 /* pHash */
|
||||
0, /* pHash */
|
||||
0 /* pDestructor */
|
||||
};
|
||||
codeAttach(pParse, SQLITE_DETACH, &detach_func, pDbname, 0, 0, pDbname);
|
||||
}
|
||||
@ -388,7 +388,8 @@ void sqlite3Attach(Parse *pParse, Expr *p, Expr *pDbname, Expr *pKey){
|
||||
0, /* xStep */
|
||||
0, /* xFinalize */
|
||||
"sqlite_attach", /* zName */
|
||||
0 /* pHash */
|
||||
0, /* pHash */
|
||||
0 /* pDestructor */
|
||||
};
|
||||
codeAttach(pParse, SQLITE_ATTACH, &attach_func, p, p, pDbname, pKey);
|
||||
}
|
||||
|
||||
77
src/backup.c
77
src/backup.c
@ -117,6 +117,16 @@ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){
|
||||
return pDb->aDb[i].pBt;
|
||||
}
|
||||
|
||||
/*
|
||||
** Attempt to set the page size of the destination to match the page size
|
||||
** of the source.
|
||||
*/
|
||||
static int setDestPgsz(sqlite3_backup *p){
|
||||
int rc;
|
||||
rc = sqlite3BtreeSetPageSize(p->pDest,sqlite3BtreeGetPageSize(p->pSrc),-1,0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Create an sqlite3_backup process to copy the contents of zSrcDb from
|
||||
** connection handle pSrcDb to zDestDb in pDestDb. If successful, return
|
||||
@ -150,7 +160,10 @@ sqlite3_backup *sqlite3_backup_init(
|
||||
);
|
||||
p = 0;
|
||||
}else {
|
||||
/* Allocate space for a new sqlite3_backup object */
|
||||
/* Allocate space for a new sqlite3_backup object...
|
||||
** EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a
|
||||
** call to sqlite3_backup_init() and is destroyed by a call to
|
||||
** sqlite3_backup_finish(). */
|
||||
p = (sqlite3_backup *)sqlite3_malloc(sizeof(sqlite3_backup));
|
||||
if( !p ){
|
||||
sqlite3Error(pDestDb, SQLITE_NOMEM, 0);
|
||||
@ -167,10 +180,11 @@ sqlite3_backup *sqlite3_backup_init(
|
||||
p->iNext = 1;
|
||||
p->isAttached = 0;
|
||||
|
||||
if( 0==p->pSrc || 0==p->pDest ){
|
||||
/* One (or both) of the named databases did not exist. An error has
|
||||
** already been written into the pDestDb handle. All that is left
|
||||
** to do here is free the sqlite3_backup structure.
|
||||
if( 0==p->pSrc || 0==p->pDest || setDestPgsz(p)==SQLITE_NOMEM ){
|
||||
/* One (or both) of the named databases did not exist or an OOM
|
||||
** error was hit. The error has already been written into the
|
||||
** pDestDb handle. All that is left to do here is free the
|
||||
** sqlite3_backup structure.
|
||||
*/
|
||||
sqlite3_free(p);
|
||||
p = 0;
|
||||
@ -427,32 +441,46 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
|
||||
*/
|
||||
const i64 iSize = (i64)pgszSrc * (i64)nSrcPage;
|
||||
sqlite3_file * const pFile = sqlite3PagerFile(pDestPager);
|
||||
i64 iOff;
|
||||
i64 iEnd;
|
||||
|
||||
assert( pFile );
|
||||
assert( (i64)nDestTruncate*(i64)pgszDest >= iSize || (
|
||||
nDestTruncate==(int)(PENDING_BYTE_PAGE(p->pDest->pBt)-1)
|
||||
&& iSize>=PENDING_BYTE && iSize<=PENDING_BYTE+pgszDest
|
||||
));
|
||||
if( SQLITE_OK==(rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 1))
|
||||
&& SQLITE_OK==(rc = backupTruncateFile(pFile, iSize))
|
||||
&& SQLITE_OK==(rc = sqlite3PagerSync(pDestPager))
|
||||
|
||||
/* This call ensures that all data required to recreate the original
|
||||
** database has been stored in the journal for pDestPager and the
|
||||
** journal synced to disk. So at this point we may safely modify
|
||||
** the database file in any way, knowing that if a power failure
|
||||
** occurs, the original database will be reconstructed from the
|
||||
** journal file. */
|
||||
rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 1);
|
||||
|
||||
/* Write the extra pages and truncate the database file as required. */
|
||||
iEnd = MIN(PENDING_BYTE + pgszDest, iSize);
|
||||
for(
|
||||
iOff=PENDING_BYTE+pgszSrc;
|
||||
rc==SQLITE_OK && iOff<iEnd;
|
||||
iOff+=pgszSrc
|
||||
){
|
||||
i64 iOff;
|
||||
i64 iEnd = MIN(PENDING_BYTE + pgszDest, iSize);
|
||||
for(
|
||||
iOff=PENDING_BYTE+pgszSrc;
|
||||
rc==SQLITE_OK && iOff<iEnd;
|
||||
iOff+=pgszSrc
|
||||
){
|
||||
PgHdr *pSrcPg = 0;
|
||||
const Pgno iSrcPg = (Pgno)((iOff/pgszSrc)+1);
|
||||
rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg);
|
||||
if( rc==SQLITE_OK ){
|
||||
u8 *zData = sqlite3PagerGetData(pSrcPg);
|
||||
rc = sqlite3OsWrite(pFile, zData, pgszSrc, iOff);
|
||||
}
|
||||
sqlite3PagerUnref(pSrcPg);
|
||||
PgHdr *pSrcPg = 0;
|
||||
const Pgno iSrcPg = (Pgno)((iOff/pgszSrc)+1);
|
||||
rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg);
|
||||
if( rc==SQLITE_OK ){
|
||||
u8 *zData = sqlite3PagerGetData(pSrcPg);
|
||||
rc = sqlite3OsWrite(pFile, zData, pgszSrc, iOff);
|
||||
}
|
||||
sqlite3PagerUnref(pSrcPg);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = backupTruncateFile(pFile, iSize);
|
||||
}
|
||||
|
||||
/* Sync the database file to disk. */
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3PagerSync(pDestPager);
|
||||
}
|
||||
}else{
|
||||
rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 0);
|
||||
@ -533,6 +561,9 @@ int sqlite3_backup_finish(sqlite3_backup *p){
|
||||
}
|
||||
sqlite3BtreeLeave(p->pSrc);
|
||||
if( p->pDestDb ){
|
||||
/* EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a
|
||||
** call to sqlite3_backup_init() and is destroyed by a call to
|
||||
** sqlite3_backup_finish(). */
|
||||
sqlite3_free(p);
|
||||
}
|
||||
sqlite3_mutex_leave(mutex);
|
||||
|
||||
94
src/btree.c
94
src/btree.c
@ -918,14 +918,9 @@ static void btreeParseCellPtr(
|
||||
/* This is the (easy) common case where the entire payload fits
|
||||
** on the local page. No overflow is required.
|
||||
*/
|
||||
int nSize; /* Total size of cell content in bytes */
|
||||
nSize = nPayload + n;
|
||||
if( (pInfo->nSize = (u16)(n+nPayload))<4 ) pInfo->nSize = 4;
|
||||
pInfo->nLocal = (u16)nPayload;
|
||||
pInfo->iOverflow = 0;
|
||||
if( (nSize & ~3)==0 ){
|
||||
nSize = 4; /* Minimum cell size is 4 */
|
||||
}
|
||||
pInfo->nSize = (u16)nSize;
|
||||
}else{
|
||||
/* If the payload will not fit completely on the local page, we have
|
||||
** to decide how much to store locally and how much to spill onto
|
||||
@ -1672,11 +1667,20 @@ static int btreeInvokeBusyHandler(void *pArg){
|
||||
** Open a database file.
|
||||
**
|
||||
** zFilename is the name of the database file. If zFilename is NULL
|
||||
** a new database with a random name is created. This randomly named
|
||||
** database file will be deleted when sqlite3BtreeClose() is called.
|
||||
** then an ephemeral database is created. The ephemeral database might
|
||||
** be exclusively in memory, or it might use a disk-based memory cache.
|
||||
** Either way, the ephemeral database will be automatically deleted
|
||||
** when sqlite3BtreeClose() is called.
|
||||
**
|
||||
** If zFilename is ":memory:" then an in-memory database is created
|
||||
** that is automatically destroyed when it is closed.
|
||||
**
|
||||
** The "flags" parameter is a bitmask that might contain bits
|
||||
** BTREE_OMIT_JOURNAL and/or BTREE_NO_READLOCK. The BTREE_NO_READLOCK
|
||||
** bit is also set if the SQLITE_NoReadlock flags is set in db->flags.
|
||||
** These flags are passed through into sqlite3PagerOpen() and must
|
||||
** be the same values as PAGER_OMIT_JOURNAL and PAGER_NO_READLOCK.
|
||||
**
|
||||
** If the database is already opened in the same database connection
|
||||
** and we are in shared cache mode, then the open will fail with an
|
||||
** SQLITE_CONSTRAINT error. We cannot allow two or more BtShared
|
||||
@ -1698,22 +1702,38 @@ int sqlite3BtreeOpen(
|
||||
u8 nReserve; /* Byte of unused space on each page */
|
||||
unsigned char zDbHeader[100]; /* Database header content */
|
||||
|
||||
/* True if opening an ephemeral, temporary database */
|
||||
const int isTempDb = zFilename==0 || zFilename[0]==0;
|
||||
|
||||
/* Set the variable isMemdb to true for an in-memory database, or
|
||||
** false for a file-based database. This symbol is only required if
|
||||
** either of the shared-data or autovacuum features are compiled
|
||||
** into the library.
|
||||
** false for a file-based database.
|
||||
*/
|
||||
#if !defined(SQLITE_OMIT_SHARED_CACHE) || !defined(SQLITE_OMIT_AUTOVACUUM)
|
||||
#ifdef SQLITE_OMIT_MEMORYDB
|
||||
const int isMemdb = 0;
|
||||
#else
|
||||
const int isMemdb = zFilename && !strcmp(zFilename, ":memory:");
|
||||
#endif
|
||||
#ifdef SQLITE_OMIT_MEMORYDB
|
||||
const int isMemdb = 0;
|
||||
#else
|
||||
const int isMemdb = (zFilename && strcmp(zFilename, ":memory:")==0)
|
||||
|| (isTempDb && sqlite3TempInMemory(db));
|
||||
#endif
|
||||
|
||||
assert( db!=0 );
|
||||
assert( sqlite3_mutex_held(db->mutex) );
|
||||
assert( (flags&0xff)==flags ); /* flags fit in 8 bits */
|
||||
|
||||
/* Only a BTREE_SINGLE database can be BTREE_UNORDERED */
|
||||
assert( (flags & BTREE_UNORDERED)==0 || (flags & BTREE_SINGLE)!=0 );
|
||||
|
||||
/* A BTREE_SINGLE database is always a temporary and/or ephemeral */
|
||||
assert( (flags & BTREE_SINGLE)==0 || isTempDb );
|
||||
|
||||
if( db->flags & SQLITE_NoReadlock ){
|
||||
flags |= BTREE_NO_READLOCK;
|
||||
}
|
||||
if( isMemdb ){
|
||||
flags |= BTREE_MEMORY;
|
||||
}
|
||||
if( (vfsFlags & SQLITE_OPEN_MAIN_DB)!=0 && (isMemdb || isTempDb) ){
|
||||
vfsFlags = (vfsFlags & ~SQLITE_OPEN_MAIN_DB) | SQLITE_OPEN_TEMP_DB;
|
||||
}
|
||||
pVfs = db->pVfs;
|
||||
p = sqlite3MallocZero(sizeof(Btree));
|
||||
if( !p ){
|
||||
@ -1731,7 +1751,7 @@ int sqlite3BtreeOpen(
|
||||
** If this Btree is a candidate for shared cache, try to find an
|
||||
** existing BtShared object that we can share with
|
||||
*/
|
||||
if( isMemdb==0 && zFilename && zFilename[0] ){
|
||||
if( isMemdb==0 && isTempDb==0 ){
|
||||
if( vfsFlags & SQLITE_OPEN_SHAREDCACHE ){
|
||||
int nFullPathname = pVfs->mxPathname+1;
|
||||
char *zFullPathname = sqlite3Malloc(nFullPathname);
|
||||
@ -1806,6 +1826,7 @@ int sqlite3BtreeOpen(
|
||||
if( rc!=SQLITE_OK ){
|
||||
goto btree_open_out;
|
||||
}
|
||||
pBt->openFlags = (u8)flags;
|
||||
pBt->db = db;
|
||||
sqlite3PagerSetBusyhandler(pBt->pPager, btreeInvokeBusyHandler, pBt);
|
||||
p->pBt = pBt;
|
||||
@ -1910,6 +1931,14 @@ btree_open_out:
|
||||
sqlite3_free(pBt);
|
||||
sqlite3_free(p);
|
||||
*ppBtree = 0;
|
||||
}else{
|
||||
/* If the B-Tree was successfully opened, set the pager-cache size to the
|
||||
** default value. Except, when opening on an existing shared pager-cache,
|
||||
** do not change the pager-cache size.
|
||||
*/
|
||||
if( sqlite3BtreeSchema(p, 0, 0)==0 ){
|
||||
sqlite3PagerSetCachesize(p->pBt->pPager, SQLITE_DEFAULT_CACHE_SIZE);
|
||||
}
|
||||
}
|
||||
if( mutexOpen ){
|
||||
assert( sqlite3_mutex_held(mutexOpen) );
|
||||
@ -2067,11 +2096,17 @@ int sqlite3BtreeSetCacheSize(Btree *p, int mxPage){
|
||||
** probability of damage to near zero but with a write performance reduction.
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_PAGER_PRAGMAS
|
||||
int sqlite3BtreeSetSafetyLevel(Btree *p, int level, int fullSync){
|
||||
int sqlite3BtreeSetSafetyLevel(
|
||||
Btree *p, /* The btree to set the safety level on */
|
||||
int level, /* PRAGMA synchronous. 1=OFF, 2=NORMAL, 3=FULL */
|
||||
int fullSync, /* PRAGMA fullfsync. */
|
||||
int ckptFullSync /* PRAGMA checkpoint_fullfync */
|
||||
){
|
||||
BtShared *pBt = p->pBt;
|
||||
assert( sqlite3_mutex_held(p->db->mutex) );
|
||||
assert( level>=1 && level<=3 );
|
||||
sqlite3BtreeEnter(p);
|
||||
sqlite3PagerSetSafetyLevel(pBt->pPager, level, fullSync);
|
||||
sqlite3PagerSetSafetyLevel(pBt->pPager, level, fullSync, ckptFullSync);
|
||||
sqlite3BtreeLeave(p);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@ -2346,7 +2381,7 @@ static int lockBtree(BtShared *pBt){
|
||||
pageSize-usableSize);
|
||||
return rc;
|
||||
}
|
||||
if( nPageHeader>nPageFile ){
|
||||
if( (pBt->db->flags & SQLITE_RecoveryMode)==0 && nPageHeader>nPageFile ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
goto page1_init_failed;
|
||||
}
|
||||
@ -3129,8 +3164,8 @@ static void btreeEndTransaction(Btree *p){
|
||||
** are no active cursors, it also releases the read lock.
|
||||
*/
|
||||
int sqlite3BtreeCommitPhaseTwo(Btree *p){
|
||||
BtShared *pBt = p->pBt;
|
||||
|
||||
if( p->inTrans==TRANS_NONE ) return SQLITE_OK;
|
||||
sqlite3BtreeEnter(p);
|
||||
btreeIntegrity(p);
|
||||
|
||||
@ -3139,6 +3174,7 @@ int sqlite3BtreeCommitPhaseTwo(Btree *p){
|
||||
*/
|
||||
if( p->inTrans==TRANS_WRITE ){
|
||||
int rc;
|
||||
BtShared *pBt = p->pBt;
|
||||
assert( pBt->inTransaction==TRANS_WRITE );
|
||||
assert( pBt->nTransaction>0 );
|
||||
rc = sqlite3PagerCommitPhaseTwo(pBt->pPager);
|
||||
@ -6860,11 +6896,12 @@ int sqlite3BtreeDelete(BtCursor *pCur){
|
||||
** BTREE_INTKEY|BTREE_LEAFDATA Used for SQL tables with rowid keys
|
||||
** BTREE_ZERODATA Used for SQL indices
|
||||
*/
|
||||
static int btreeCreateTable(Btree *p, int *piTable, int flags){
|
||||
static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){
|
||||
BtShared *pBt = p->pBt;
|
||||
MemPage *pRoot;
|
||||
Pgno pgnoRoot;
|
||||
int rc;
|
||||
int ptfFlags; /* Page-type flage for the root page of new table */
|
||||
|
||||
assert( sqlite3BtreeHoldsMutex(p) );
|
||||
assert( pBt->inTransaction==TRANS_WRITE );
|
||||
@ -6983,8 +7020,14 @@ static int btreeCreateTable(Btree *p, int *piTable, int flags){
|
||||
}
|
||||
#endif
|
||||
assert( sqlite3PagerIswriteable(pRoot->pDbPage) );
|
||||
zeroPage(pRoot, flags | PTF_LEAF);
|
||||
if( createTabFlags & BTREE_INTKEY ){
|
||||
ptfFlags = PTF_INTKEY | PTF_LEAFDATA | PTF_LEAF;
|
||||
}else{
|
||||
ptfFlags = PTF_ZERODATA | PTF_LEAF;
|
||||
}
|
||||
zeroPage(pRoot, ptfFlags);
|
||||
sqlite3PagerUnref(pRoot->pDbPage);
|
||||
assert( (pBt->openFlags & BTREE_SINGLE)==0 || pgnoRoot==2 );
|
||||
*piTable = (int)pgnoRoot;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@ -8050,8 +8093,7 @@ int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){
|
||||
void sqlite3BtreeCacheOverflow(BtCursor *pCur){
|
||||
assert( cursorHoldsMutex(pCur) );
|
||||
assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
|
||||
assert(!pCur->isIncrblobHandle);
|
||||
assert(!pCur->aOverflow);
|
||||
invalidateOverflowCache(pCur);
|
||||
pCur->isIncrblobHandle = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
23
src/btree.h
23
src/btree.h
@ -67,16 +67,15 @@ int sqlite3BtreeOpen(
|
||||
** NOTE: These values must match the corresponding PAGER_ values in
|
||||
** pager.h.
|
||||
*/
|
||||
#define BTREE_OMIT_JOURNAL 1 /* Do not use journal. No argument */
|
||||
#define BTREE_OMIT_JOURNAL 1 /* Do not create or use a rollback journal */
|
||||
#define BTREE_NO_READLOCK 2 /* Omit readlocks on readonly files */
|
||||
#define BTREE_MEMORY 4 /* In-memory DB. No argument */
|
||||
#define BTREE_READONLY 8 /* Open the database in read-only mode */
|
||||
#define BTREE_READWRITE 16 /* Open for both reading and writing */
|
||||
#define BTREE_CREATE 32 /* Create the database if it does not exist */
|
||||
#define BTREE_MEMORY 4 /* This is an in-memory DB */
|
||||
#define BTREE_SINGLE 8 /* The file contains at most 1 b-tree */
|
||||
#define BTREE_UNORDERED 16 /* Use of a hash implementation is OK */
|
||||
|
||||
int sqlite3BtreeClose(Btree*);
|
||||
int sqlite3BtreeSetCacheSize(Btree*,int);
|
||||
int sqlite3BtreeSetSafetyLevel(Btree*,int,int);
|
||||
int sqlite3BtreeSetSafetyLevel(Btree*,int,int,int);
|
||||
int sqlite3BtreeSyncDisabled(Btree*);
|
||||
int sqlite3BtreeSetPageSize(Btree *p, int nPagesize, int nReserve, int eFix);
|
||||
int sqlite3BtreeGetPageSize(Btree*);
|
||||
@ -108,11 +107,17 @@ int sqlite3BtreeCopyFile(Btree *, Btree *);
|
||||
int sqlite3BtreeIncrVacuum(Btree *);
|
||||
|
||||
/* The flags parameter to sqlite3BtreeCreateTable can be the bitwise OR
|
||||
** of the following flags:
|
||||
** of the flags shown below.
|
||||
**
|
||||
** Every SQLite table must have either BTREE_INTKEY or BTREE_BLOBKEY set.
|
||||
** With BTREE_INTKEY, the table key is a 64-bit integer and arbitrary data
|
||||
** is stored in the leaves. (BTREE_INTKEY is used for SQL tables.) With
|
||||
** BTREE_BLOBKEY, the key is an arbitrary BLOB and no content is stored
|
||||
** anywhere - the key is the content. (BTREE_BLOBKEY is used for SQL
|
||||
** indices.)
|
||||
*/
|
||||
#define BTREE_INTKEY 1 /* Table has only 64-bit signed integer keys */
|
||||
#define BTREE_ZERODATA 2 /* Table has keys only - no data */
|
||||
#define BTREE_LEAFDATA 4 /* Data stored in leaves only. Implies INTKEY */
|
||||
#define BTREE_BLOBKEY 2 /* Table has keys only - no data */
|
||||
|
||||
int sqlite3BtreeDropTable(Btree*, int, int*);
|
||||
int sqlite3BtreeClearTable(Btree*, int, int*);
|
||||
|
||||
@ -409,16 +409,17 @@ struct BtShared {
|
||||
u8 pageSizeFixed; /* True if the page size can no longer be changed */
|
||||
u8 secureDelete; /* True if secure_delete is enabled */
|
||||
u8 initiallyEmpty; /* Database is empty at start of transaction */
|
||||
u8 openFlags; /* Flags to sqlite3BtreeOpen() */
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
u8 autoVacuum; /* True if auto-vacuum is enabled */
|
||||
u8 incrVacuum; /* True if incr-vacuum is enabled */
|
||||
#endif
|
||||
u8 inTransaction; /* Transaction state */
|
||||
u8 doNotUseWAL; /* If true, do not open write-ahead-log file */
|
||||
u16 maxLocal; /* Maximum local payload in non-LEAFDATA tables */
|
||||
u16 minLocal; /* Minimum local payload in non-LEAFDATA tables */
|
||||
u16 maxLeaf; /* Maximum local payload in a LEAFDATA table */
|
||||
u16 minLeaf; /* Minimum local payload in a LEAFDATA table */
|
||||
u8 inTransaction; /* Transaction state */
|
||||
u8 doNotUseWAL; /* If true, do not open write-ahead-log file */
|
||||
u32 pageSize; /* Total number of bytes on a page */
|
||||
u32 usableSize; /* Number of usable bytes on each page */
|
||||
int nTransaction; /* Number of open transactions (read + write) */
|
||||
@ -445,8 +446,8 @@ struct BtShared {
|
||||
*/
|
||||
typedef struct CellInfo CellInfo;
|
||||
struct CellInfo {
|
||||
u8 *pCell; /* Pointer to the start of cell content */
|
||||
i64 nKey; /* The key for INTKEY tables, or number of bytes in key */
|
||||
u8 *pCell; /* Pointer to the start of cell content */
|
||||
u32 nData; /* Number of bytes of data */
|
||||
u32 nPayload; /* Total amount of payload */
|
||||
u16 nHeader; /* Size of the cell content header in bytes */
|
||||
@ -488,20 +489,20 @@ struct BtCursor {
|
||||
Pgno pgnoRoot; /* The root page of this tree */
|
||||
sqlite3_int64 cachedRowid; /* Next rowid cache. 0 means not valid */
|
||||
CellInfo info; /* A parse of the cell we are pointing at */
|
||||
i64 nKey; /* Size of pKey, or last integer key */
|
||||
void *pKey; /* Saved key that was cursor's last known position */
|
||||
int skipNext; /* Prev() is noop if negative. Next() is noop if positive */
|
||||
u8 wrFlag; /* True if writable */
|
||||
u8 atLast; /* Cursor pointing to the last entry */
|
||||
u8 validNKey; /* True if info.nKey is valid */
|
||||
u8 eState; /* One of the CURSOR_XXX constants (see below) */
|
||||
void *pKey; /* Saved key that was cursor's last known position */
|
||||
i64 nKey; /* Size of pKey, or last integer key */
|
||||
int skipNext; /* Prev() is noop if negative. Next() is noop if positive */
|
||||
#ifndef SQLITE_OMIT_INCRBLOB
|
||||
u8 isIncrblobHandle; /* True if this cursor is an incr. io handle */
|
||||
Pgno *aOverflow; /* Cache of overflow page locations */
|
||||
u8 isIncrblobHandle; /* True if this cursor is an incr. io handle */
|
||||
#endif
|
||||
i16 iPage; /* Index of current page in apPage */
|
||||
MemPage *apPage[BTCURSOR_MAX_DEPTH]; /* Pages from root to current page */
|
||||
u16 aiIdx[BTCURSOR_MAX_DEPTH]; /* Current index in apPage[i] */
|
||||
MemPage *apPage[BTCURSOR_MAX_DEPTH]; /* Pages from root to current page */
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
36
src/build.c
36
src/build.c
@ -726,8 +726,9 @@ void sqlite3StartTable(
|
||||
*/
|
||||
iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName);
|
||||
if( iDb<0 ) return;
|
||||
if( !OMIT_TEMPDB && isTemp && iDb>1 ){
|
||||
/* If creating a temp table, the name may not be qualified */
|
||||
if( !OMIT_TEMPDB && isTemp && pName2->n>0 && iDb!=1 ){
|
||||
/* If creating a temp table, the name may not be qualified. Unless
|
||||
** the database name is "temp" anyway. */
|
||||
sqlite3ErrorMsg(pParse, "temporary table name must be unqualified");
|
||||
return;
|
||||
}
|
||||
@ -775,17 +776,18 @@ void sqlite3StartTable(
|
||||
** collisions.
|
||||
*/
|
||||
if( !IN_DECLARE_VTAB ){
|
||||
char *zDb = db->aDb[iDb].zName;
|
||||
if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
|
||||
goto begin_table_error;
|
||||
}
|
||||
pTable = sqlite3FindTable(db, zName, db->aDb[iDb].zName);
|
||||
pTable = sqlite3FindTable(db, zName, zDb);
|
||||
if( pTable ){
|
||||
if( !noErr ){
|
||||
sqlite3ErrorMsg(pParse, "table %T already exists", pName);
|
||||
}
|
||||
goto begin_table_error;
|
||||
}
|
||||
if( sqlite3FindIndex(db, zName, 0)!=0 && (iDb==0 || !db->init.busy) ){
|
||||
if( sqlite3FindIndex(db, zName, zDb)!=0 ){
|
||||
sqlite3ErrorMsg(pParse, "there is already an index named %s", zName);
|
||||
goto begin_table_error;
|
||||
}
|
||||
@ -802,6 +804,7 @@ void sqlite3StartTable(
|
||||
pTable->iPKey = -1;
|
||||
pTable->pSchema = db->aDb[iDb].pSchema;
|
||||
pTable->nRef = 1;
|
||||
pTable->nRowEst = 1000000;
|
||||
assert( pParse->pNewTable==0 );
|
||||
pParse->pNewTable = pTable;
|
||||
|
||||
@ -1648,12 +1651,10 @@ void sqlite3CreateView(
|
||||
}
|
||||
sqlite3StartTable(pParse, pName1, pName2, isTemp, 1, 0, noErr);
|
||||
p = pParse->pNewTable;
|
||||
if( p==0 ){
|
||||
if( p==0 || pParse->nErr ){
|
||||
sqlite3SelectDelete(db, pSelect);
|
||||
return;
|
||||
}
|
||||
assert( pParse->nErr==0 ); /* If sqlite3StartTable return non-NULL then
|
||||
** there could not have been an error */
|
||||
sqlite3TwoPartName(pParse, pName1, pName2, &pName);
|
||||
iDb = sqlite3SchemaToIndex(db, p->pSchema);
|
||||
if( sqlite3FixInit(&sFix, pParse, iDb, "view", pName)
|
||||
@ -2771,7 +2772,8 @@ Index *sqlite3CreateIndex(
|
||||
sqlite3RefillIndex(pParse, pIndex, iMem);
|
||||
sqlite3ChangeCookie(pParse, iDb);
|
||||
sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 0, 0,
|
||||
sqlite3MPrintf(db, "name='%q'", pIndex->zName), P4_DYNAMIC);
|
||||
sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName),
|
||||
P4_DYNAMIC);
|
||||
sqlite3VdbeAddOp1(v, OP_Expire, 0);
|
||||
}
|
||||
}
|
||||
@ -2832,14 +2834,14 @@ exit_create_index:
|
||||
void sqlite3DefaultRowEst(Index *pIdx){
|
||||
unsigned *a = pIdx->aiRowEst;
|
||||
int i;
|
||||
unsigned n;
|
||||
assert( a!=0 );
|
||||
a[0] = 1000000;
|
||||
for(i=pIdx->nColumn; i>=5; i--){
|
||||
a[i] = 5;
|
||||
}
|
||||
while( i>=1 ){
|
||||
a[i] = 11 - i;
|
||||
i--;
|
||||
a[0] = pIdx->pTable->nRowEst;
|
||||
if( a[0]<10 ) a[0] = 10;
|
||||
n = 10;
|
||||
for(i=1; i<=pIdx->nColumn; i++){
|
||||
a[i] = n;
|
||||
if( n>5 ) n--;
|
||||
}
|
||||
if( pIdx->onError!=OE_None ){
|
||||
a[pIdx->nColumn] = 1;
|
||||
@ -2899,7 +2901,7 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){
|
||||
if( v ){
|
||||
sqlite3BeginWriteOperation(pParse, 1, iDb);
|
||||
sqlite3NestedParse(pParse,
|
||||
"DELETE FROM %Q.%s WHERE name=%Q",
|
||||
"DELETE FROM %Q.%s WHERE name=%Q AND type='index'",
|
||||
db->aDb[iDb].zName, SCHEMA_TABLE(iDb),
|
||||
pIndex->zName
|
||||
);
|
||||
@ -3391,7 +3393,7 @@ int sqlite3OpenTempDatabase(Parse *pParse){
|
||||
SQLITE_OPEN_DELETEONCLOSE |
|
||||
SQLITE_OPEN_TEMP_DB;
|
||||
|
||||
rc = sqlite3BtreeFactory(db, 0, 0, SQLITE_DEFAULT_CACHE_SIZE, flags, &pBt);
|
||||
rc = sqlite3BtreeOpen(0, db, &pBt, 0, flags);
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3ErrorMsg(pParse, "unable to open a temporary database "
|
||||
"file for storing temporary tables");
|
||||
|
||||
@ -358,7 +358,7 @@ FuncDef *sqlite3FindFunction(
|
||||
** priority to built-in functions.
|
||||
**
|
||||
** Except, if createFlag is true, that means that we are trying to
|
||||
** install a new function. Whatever FuncDef structure is returned will
|
||||
** install a new function. Whatever FuncDef structure is returned it will
|
||||
** have fields overwritten with new information appropriate for the
|
||||
** new function. But the FuncDefs for built-in functions are read-only.
|
||||
** So we must not search for built-ins when creating a new function.
|
||||
|
||||
@ -174,6 +174,9 @@ static const char * const azCompileOpt[] = {
|
||||
#ifdef SQLITE_OMIT_AUTOMATIC_INDEX
|
||||
"OMIT_AUTOMATIC_INDEX",
|
||||
#endif
|
||||
#ifdef SQLITE_OMIT_AUTORESET
|
||||
"OMIT_AUTORESET",
|
||||
#endif
|
||||
#ifdef SQLITE_OMIT_AUTOVACUUM
|
||||
"OMIT_AUTOVACUUM",
|
||||
#endif
|
||||
|
||||
24
src/date.c
24
src/date.c
@ -133,12 +133,6 @@ end_getDigits:
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read text from z[] and convert into a floating point number. Return
|
||||
** the number of digits converted.
|
||||
*/
|
||||
#define getValue sqlite3AtoF
|
||||
|
||||
/*
|
||||
** Parse a timezone extension on the end of a date-time.
|
||||
** The extension is of the form:
|
||||
@ -340,7 +334,7 @@ static int parseDateOrTime(
|
||||
const char *zDate,
|
||||
DateTime *p
|
||||
){
|
||||
int isRealNum; /* Return from sqlite3IsNumber(). Not used */
|
||||
double r;
|
||||
if( parseYyyyMmDd(zDate,p)==0 ){
|
||||
return 0;
|
||||
}else if( parseHhMmSs(zDate, p)==0 ){
|
||||
@ -348,9 +342,7 @@ static int parseDateOrTime(
|
||||
}else if( sqlite3StrICmp(zDate,"now")==0){
|
||||
setDateTimeToCurrent(context, p);
|
||||
return 0;
|
||||
}else if( sqlite3IsNumber(zDate, &isRealNum, SQLITE_UTF8) ){
|
||||
double r;
|
||||
getValue(zDate, &r);
|
||||
}else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8) ){
|
||||
p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5);
|
||||
p->validJD = 1;
|
||||
return 0;
|
||||
@ -571,8 +563,9 @@ static int parseModifier(const char *zMod, DateTime *p){
|
||||
** weekday N where 0==Sunday, 1==Monday, and so forth. If the
|
||||
** date is already on the appropriate weekday, this is a no-op.
|
||||
*/
|
||||
if( strncmp(z, "weekday ", 8)==0 && getValue(&z[8],&r)>0
|
||||
&& (n=(int)r)==r && n>=0 && r<7 ){
|
||||
if( strncmp(z, "weekday ", 8)==0
|
||||
&& sqlite3AtoF(&z[8], &r, sqlite3Strlen30(&z[8]), SQLITE_UTF8)
|
||||
&& (n=(int)r)==r && n>=0 && r<7 ){
|
||||
sqlite3_int64 Z;
|
||||
computeYMD_HMS(p);
|
||||
p->validTZ = 0;
|
||||
@ -627,8 +620,11 @@ static int parseModifier(const char *zMod, DateTime *p){
|
||||
case '8':
|
||||
case '9': {
|
||||
double rRounder;
|
||||
n = getValue(z, &r);
|
||||
assert( n>=1 );
|
||||
for(n=1; z[n] && z[n]!=':' && !sqlite3Isspace(z[n]); n++){}
|
||||
if( !sqlite3AtoF(z, &r, n, SQLITE_UTF8) ){
|
||||
rc = 1;
|
||||
break;
|
||||
}
|
||||
if( z[n]==':' ){
|
||||
/* A modifier of the form (+|-)HH:MM:SS.FFF adds (or subtracts) the
|
||||
** specified number of hours, minutes, seconds, and fractional seconds
|
||||
|
||||
161
src/expr.c
161
src/expr.c
@ -484,6 +484,9 @@ Expr *sqlite3PExpr(
|
||||
){
|
||||
Expr *p = sqlite3ExprAlloc(pParse->db, op, pToken, 1);
|
||||
sqlite3ExprAttachSubtrees(pParse->db, p, pLeft, pRight);
|
||||
if( p ) {
|
||||
sqlite3ExprCheckHeight(pParse, p->nHeight);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
@ -555,7 +558,7 @@ void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){
|
||||
/* Wildcard of the form "?nnn". Convert "nnn" to an integer and
|
||||
** use it as the variable number */
|
||||
i64 i;
|
||||
int bOk = sqlite3Atoi64(&z[1], &i);
|
||||
int bOk = 0==sqlite3Atoi64(&z[1], &i, sqlite3Strlen30(&z[1]), SQLITE_UTF8);
|
||||
pExpr->iColumn = (ynVar)i;
|
||||
testcase( i==0 );
|
||||
testcase( i==1 );
|
||||
@ -1535,8 +1538,8 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, int *prNotFound){
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Generate code for scalar subqueries used as an expression
|
||||
** and IN operators. Examples:
|
||||
** Generate code for scalar subqueries used as a subquery expression, EXISTS,
|
||||
** or IN operators. Examples:
|
||||
**
|
||||
** (SELECT a FROM b) -- subquery
|
||||
** EXISTS (SELECT a FROM b) -- EXISTS subquery
|
||||
@ -1597,12 +1600,22 @@ int sqlite3CodeSubselect(
|
||||
assert( testAddr>0 || pParse->db->mallocFailed );
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_EXPLAIN
|
||||
if( pParse->explain==2 ){
|
||||
char *zMsg = sqlite3MPrintf(
|
||||
pParse->db, "EXECUTE %s%s SUBQUERY %d", testAddr?"":"CORRELATED ",
|
||||
pExpr->op==TK_IN?"LIST":"SCALAR", pParse->iNextSelectId
|
||||
);
|
||||
sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC);
|
||||
}
|
||||
#endif
|
||||
|
||||
switch( pExpr->op ){
|
||||
case TK_IN: {
|
||||
char affinity;
|
||||
KeyInfo keyInfo;
|
||||
int addr; /* Address of OP_OpenEphemeral instruction */
|
||||
Expr *pLeft = pExpr->pLeft;
|
||||
char affinity; /* Affinity of the LHS of the IN */
|
||||
KeyInfo keyInfo; /* Keyinfo for the generated table */
|
||||
int addr; /* Address of OP_OpenEphemeral instruction */
|
||||
Expr *pLeft = pExpr->pLeft; /* the LHS of the IN operator */
|
||||
|
||||
if( rMayHaveNull ){
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, rMayHaveNull);
|
||||
@ -1625,6 +1638,7 @@ int sqlite3CodeSubselect(
|
||||
*/
|
||||
pExpr->iTable = pParse->nTab++;
|
||||
addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pExpr->iTable, !isRowid);
|
||||
if( rMayHaveNull==0 ) sqlite3VdbeChangeP5(v, BTREE_UNORDERED);
|
||||
memset(&keyInfo, 0, sizeof(keyInfo));
|
||||
keyInfo.nField = 1;
|
||||
|
||||
@ -1641,6 +1655,7 @@ int sqlite3CodeSubselect(
|
||||
sqlite3SelectDestInit(&dest, SRT_Set, pExpr->iTable);
|
||||
dest.affinity = (u8)affinity;
|
||||
assert( (pExpr->iTable&0x0000FFFF)==pExpr->iTable );
|
||||
pExpr->x.pSelect->iLimit = 0;
|
||||
if( sqlite3Select(pParse, pExpr->x.pSelect, &dest) ){
|
||||
return 0;
|
||||
}
|
||||
@ -1741,6 +1756,7 @@ int sqlite3CodeSubselect(
|
||||
sqlite3ExprDelete(pParse->db, pSel->pLimit);
|
||||
pSel->pLimit = sqlite3PExpr(pParse, TK_INTEGER, 0, 0,
|
||||
&sqlite3IntTokens[1]);
|
||||
pSel->iLimit = 0;
|
||||
if( sqlite3Select(pParse, pSel, &dest) ){
|
||||
return 0;
|
||||
}
|
||||
@ -1917,7 +1933,7 @@ static void codeReal(Vdbe *v, const char *z, int negateFlag, int iMem){
|
||||
if( ALWAYS(z!=0) ){
|
||||
double value;
|
||||
char *zV;
|
||||
sqlite3AtoF(z, &value);
|
||||
sqlite3AtoF(z, &value, sqlite3Strlen30(z), SQLITE_UTF8);
|
||||
assert( !sqlite3IsNaN(value) ); /* The new AtoF never returns NaN */
|
||||
if( negateFlag ) value = -value;
|
||||
zV = dup8bytes(v, (char*)&value);
|
||||
@ -1931,9 +1947,7 @@ static void codeReal(Vdbe *v, const char *z, int negateFlag, int iMem){
|
||||
** Generate an instruction that will put the integer describe by
|
||||
** text z[0..n-1] into register iMem.
|
||||
**
|
||||
** The z[] string will probably not be zero-terminated. But the
|
||||
** z[n] character is guaranteed to be something that does not look
|
||||
** like the continuation of the number.
|
||||
** Expr.u.zToken is always UTF8 and zero-terminated.
|
||||
*/
|
||||
static void codeInteger(Parse *pParse, Expr *pExpr, int negFlag, int iMem){
|
||||
Vdbe *v = pParse->pVdbe;
|
||||
@ -1942,13 +1956,14 @@ static void codeInteger(Parse *pParse, Expr *pExpr, int negFlag, int iMem){
|
||||
if( negFlag ) i = -i;
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, i, iMem);
|
||||
}else{
|
||||
int c;
|
||||
i64 value;
|
||||
const char *z = pExpr->u.zToken;
|
||||
assert( z!=0 );
|
||||
if( sqlite3FitsIn64Bits(z, negFlag) ){
|
||||
i64 value;
|
||||
c = sqlite3Atoi64(z, &value, sqlite3Strlen30(z), SQLITE_UTF8);
|
||||
if( c==0 || (c==2 && negFlag) ){
|
||||
char *zV;
|
||||
sqlite3Atoi64(z, &value);
|
||||
if( negFlag ) value = -value;
|
||||
if( negFlag ){ value = -value; }
|
||||
zV = dup8bytes(v, (char*)&value);
|
||||
sqlite3VdbeAddOp4(v, OP_Int64, 0, iMem, 0, zV, P4_INT64);
|
||||
}else{
|
||||
@ -2233,73 +2248,6 @@ static int usedAsColumnCache(Parse *pParse, int iFrom, int iTo){
|
||||
}
|
||||
#endif /* SQLITE_DEBUG || SQLITE_COVERAGE_TEST */
|
||||
|
||||
/*
|
||||
** If the last instruction coded is an ephemeral copy of any of
|
||||
** the registers in the nReg registers beginning with iReg, then
|
||||
** convert the last instruction from OP_SCopy to OP_Copy.
|
||||
*/
|
||||
void sqlite3ExprHardCopy(Parse *pParse, int iReg, int nReg){
|
||||
VdbeOp *pOp;
|
||||
Vdbe *v;
|
||||
|
||||
assert( pParse->db->mallocFailed==0 );
|
||||
v = pParse->pVdbe;
|
||||
assert( v!=0 );
|
||||
pOp = sqlite3VdbeGetOp(v, -1);
|
||||
assert( pOp!=0 );
|
||||
if( pOp->opcode==OP_SCopy && pOp->p1>=iReg && pOp->p1<iReg+nReg ){
|
||||
pOp->opcode = OP_Copy;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate code to store the value of the iAlias-th alias in register
|
||||
** target. The first time this is called, pExpr is evaluated to compute
|
||||
** the value of the alias. The value is stored in an auxiliary register
|
||||
** and the number of that register is returned. On subsequent calls,
|
||||
** the register number is returned without generating any code.
|
||||
**
|
||||
** Note that in order for this to work, code must be generated in the
|
||||
** same order that it is executed.
|
||||
**
|
||||
** Aliases are numbered starting with 1. So iAlias is in the range
|
||||
** of 1 to pParse->nAlias inclusive.
|
||||
**
|
||||
** pParse->aAlias[iAlias-1] records the register number where the value
|
||||
** of the iAlias-th alias is stored. If zero, that means that the
|
||||
** alias has not yet been computed.
|
||||
*/
|
||||
static int codeAlias(Parse *pParse, int iAlias, Expr *pExpr, int target){
|
||||
#if 0
|
||||
sqlite3 *db = pParse->db;
|
||||
int iReg;
|
||||
if( pParse->nAliasAlloc<pParse->nAlias ){
|
||||
pParse->aAlias = sqlite3DbReallocOrFree(db, pParse->aAlias,
|
||||
sizeof(pParse->aAlias[0])*pParse->nAlias );
|
||||
testcase( db->mallocFailed && pParse->nAliasAlloc>0 );
|
||||
if( db->mallocFailed ) return 0;
|
||||
memset(&pParse->aAlias[pParse->nAliasAlloc], 0,
|
||||
(pParse->nAlias-pParse->nAliasAlloc)*sizeof(pParse->aAlias[0]));
|
||||
pParse->nAliasAlloc = pParse->nAlias;
|
||||
}
|
||||
assert( iAlias>0 && iAlias<=pParse->nAlias );
|
||||
iReg = pParse->aAlias[iAlias-1];
|
||||
if( iReg==0 ){
|
||||
if( pParse->iCacheLevel>0 ){
|
||||
iReg = sqlite3ExprCodeTarget(pParse, pExpr, target);
|
||||
}else{
|
||||
iReg = ++pParse->nMem;
|
||||
sqlite3ExprCode(pParse, pExpr, iReg);
|
||||
pParse->aAlias[iAlias-1] = iReg;
|
||||
}
|
||||
}
|
||||
return iReg;
|
||||
#else
|
||||
UNUSED_PARAMETER(iAlias);
|
||||
return sqlite3ExprCodeTarget(pParse, pExpr, target);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate code into the current Vdbe to evaluate the given
|
||||
** expression. Attempt to store the results in register "target".
|
||||
@ -2408,7 +2356,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
|
||||
break;
|
||||
}
|
||||
case TK_AS: {
|
||||
inReg = codeAlias(pParse, pExpr->iTable, pExpr->pLeft, target);
|
||||
inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
|
||||
break;
|
||||
}
|
||||
#ifndef SQLITE_OMIT_CAST
|
||||
@ -2840,6 +2788,11 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
|
||||
opCompare.op = TK_EQ;
|
||||
opCompare.pLeft = &cacheX;
|
||||
pTest = &opCompare;
|
||||
/* Ticket b351d95f9cd5ef17e9d9dbae18f5ca8611190001:
|
||||
** The value in regFree1 might get SCopy-ed into the file result.
|
||||
** So make sure that the regFree1 register is not reused for other
|
||||
** purposes and possibly overwritten. */
|
||||
regFree1 = 0;
|
||||
}
|
||||
for(i=0; i<nExpr; i=i+2){
|
||||
sqlite3ExprCachePush(pParse);
|
||||
@ -2933,10 +2886,14 @@ int sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){
|
||||
int inReg;
|
||||
|
||||
assert( target>0 && target<=pParse->nMem );
|
||||
inReg = sqlite3ExprCodeTarget(pParse, pExpr, target);
|
||||
assert( pParse->pVdbe || pParse->db->mallocFailed );
|
||||
if( inReg!=target && pParse->pVdbe ){
|
||||
sqlite3VdbeAddOp2(pParse->pVdbe, OP_SCopy, inReg, target);
|
||||
if( pExpr && pExpr->op==TK_REGISTER ){
|
||||
sqlite3VdbeAddOp2(pParse->pVdbe, OP_Copy, pExpr->iTable, target);
|
||||
}else{
|
||||
inReg = sqlite3ExprCodeTarget(pParse, pExpr, target);
|
||||
assert( pParse->pVdbe || pParse->db->mallocFailed );
|
||||
if( inReg!=target && pParse->pVdbe ){
|
||||
sqlite3VdbeAddOp2(pParse->pVdbe, OP_SCopy, inReg, target);
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
@ -3083,9 +3040,22 @@ static int evalConstExpr(Walker *pWalker, Expr *pExpr){
|
||||
** Preevaluate constant subexpressions within pExpr and store the
|
||||
** results in registers. Modify pExpr so that the constant subexpresions
|
||||
** are TK_REGISTER opcodes that refer to the precomputed values.
|
||||
**
|
||||
** This routine is a no-op if the jump to the cookie-check code has
|
||||
** already occur. Since the cookie-check jump is generated prior to
|
||||
** any other serious processing, this check ensures that there is no
|
||||
** way to accidently bypass the constant initializations.
|
||||
**
|
||||
** This routine is also a no-op if the SQLITE_FactorOutConst optimization
|
||||
** is disabled via the sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS)
|
||||
** interface. This allows test logic to verify that the same answer is
|
||||
** obtained for queries regardless of whether or not constants are
|
||||
** precomputed into registers or if they are inserted in-line.
|
||||
*/
|
||||
void sqlite3ExprCodeConstants(Parse *pParse, Expr *pExpr){
|
||||
Walker w;
|
||||
if( pParse->cookieGoto ) return;
|
||||
if( (pParse->db->flags & SQLITE_FactorOutConst)!=0 ) return;
|
||||
w.xExprCallback = evalConstExpr;
|
||||
w.xSelectCallback = 0;
|
||||
w.pParse = pParse;
|
||||
@ -3109,19 +3079,14 @@ int sqlite3ExprCodeExprList(
|
||||
int i, n;
|
||||
assert( pList!=0 );
|
||||
assert( target>0 );
|
||||
assert( pParse->pVdbe!=0 ); /* Never gets this far otherwise */
|
||||
n = pList->nExpr;
|
||||
for(pItem=pList->a, i=0; i<n; i++, pItem++){
|
||||
if( pItem->iAlias ){
|
||||
int iReg = codeAlias(pParse, pItem->iAlias, pItem->pExpr, target+i);
|
||||
Vdbe *v = sqlite3GetVdbe(pParse);
|
||||
if( iReg!=target+i ){
|
||||
sqlite3VdbeAddOp2(v, OP_SCopy, iReg, target+i);
|
||||
}
|
||||
}else{
|
||||
sqlite3ExprCode(pParse, pItem->pExpr, target+i);
|
||||
}
|
||||
if( doHardCopy && !pParse->db->mallocFailed ){
|
||||
sqlite3ExprHardCopy(pParse, target, n);
|
||||
Expr *pExpr = pItem->pExpr;
|
||||
int inReg = sqlite3ExprCodeTarget(pParse, pExpr, target+i);
|
||||
if( inReg!=target+i ){
|
||||
sqlite3VdbeAddOp2(pParse->pVdbe, doHardCopy ? OP_Copy : OP_SCopy,
|
||||
inReg, target+i);
|
||||
}
|
||||
}
|
||||
return n;
|
||||
|
||||
@ -380,7 +380,7 @@ static void fkLookupParent(
|
||||
sqlite3VdbeAddOp3(v, OP_OpenRead, iCur, pIdx->tnum, iDb);
|
||||
sqlite3VdbeChangeP4(v, -1, (char*)pKey, P4_KEYINFO_HANDOFF);
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3VdbeAddOp2(v, OP_SCopy, aiCol[i]+1+regData, regTemp+i);
|
||||
sqlite3VdbeAddOp2(v, OP_Copy, aiCol[i]+1+regData, regTemp+i);
|
||||
}
|
||||
|
||||
/* If the parent table is the same as the child table, and we are about
|
||||
|
||||
16
src/func.c
16
src/func.c
@ -285,7 +285,7 @@ static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
|
||||
sqlite3_result_error_nomem(context);
|
||||
return;
|
||||
}
|
||||
sqlite3AtoF(zBuf, &r);
|
||||
sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8);
|
||||
sqlite3_free(zBuf);
|
||||
}
|
||||
sqlite3_result_double(context, r);
|
||||
@ -1450,10 +1450,10 @@ void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive){
|
||||
}else{
|
||||
pInfo = (struct compareInfo*)&likeInfoNorm;
|
||||
}
|
||||
sqlite3CreateFunc(db, "like", 2, SQLITE_ANY, pInfo, likeFunc, 0, 0);
|
||||
sqlite3CreateFunc(db, "like", 3, SQLITE_ANY, pInfo, likeFunc, 0, 0);
|
||||
sqlite3CreateFunc(db, "glob", 2, SQLITE_ANY,
|
||||
(struct compareInfo*)&globInfo, likeFunc, 0,0);
|
||||
sqlite3CreateFunc(db, "like", 2, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0);
|
||||
sqlite3CreateFunc(db, "like", 3, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0);
|
||||
sqlite3CreateFunc(db, "glob", 2, SQLITE_UTF8,
|
||||
(struct compareInfo*)&globInfo, likeFunc, 0, 0, 0);
|
||||
setLikeOptFlag(db, "glob", SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE);
|
||||
setLikeOptFlag(db, "like",
|
||||
caseSensitive ? (SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE) : SQLITE_FUNC_LIKE);
|
||||
@ -1537,10 +1537,10 @@ void sqlite3RegisterGlobalFunctions(void){
|
||||
FUNCTION(coalesce, 1, 0, 0, 0 ),
|
||||
FUNCTION(coalesce, 0, 0, 0, 0 ),
|
||||
/* FUNCTION(coalesce, -1, 0, 0, ifnullFunc ), */
|
||||
{-1,SQLITE_UTF8,SQLITE_FUNC_COALESCE,0,0,ifnullFunc,0,0,"coalesce",0},
|
||||
{-1,SQLITE_UTF8,SQLITE_FUNC_COALESCE,0,0,ifnullFunc,0,0,"coalesce",0,0},
|
||||
FUNCTION(hex, 1, 0, 0, hexFunc ),
|
||||
/* FUNCTION(ifnull, 2, 0, 0, ifnullFunc ), */
|
||||
{2,SQLITE_UTF8,SQLITE_FUNC_COALESCE,0,0,ifnullFunc,0,0,"ifnull",0},
|
||||
{2,SQLITE_UTF8,SQLITE_FUNC_COALESCE,0,0,ifnullFunc,0,0,"ifnull",0,0},
|
||||
FUNCTION(random, 0, 0, 0, randomFunc ),
|
||||
FUNCTION(randomblob, 1, 0, 0, randomBlob ),
|
||||
FUNCTION(nullif, 2, 0, 1, nullifFunc ),
|
||||
@ -1567,7 +1567,7 @@ void sqlite3RegisterGlobalFunctions(void){
|
||||
AGGREGATE(total, 1, 0, 0, sumStep, totalFinalize ),
|
||||
AGGREGATE(avg, 1, 0, 0, sumStep, avgFinalize ),
|
||||
/* AGGREGATE(count, 0, 0, 0, countStep, countFinalize ), */
|
||||
{0,SQLITE_UTF8,SQLITE_FUNC_COUNT,0,0,0,countStep,countFinalize,"count",0},
|
||||
{0,SQLITE_UTF8,SQLITE_FUNC_COUNT,0,0,0,countStep,countFinalize,"count",0,0},
|
||||
AGGREGATE(count, 1, 0, 0, countStep, countFinalize ),
|
||||
AGGREGATE(group_concat, 1, 0, 0, groupConcatStep, groupConcatFinalize),
|
||||
AGGREGATE(group_concat, 2, 0, 0, groupConcatStep, groupConcatFinalize),
|
||||
|
||||
@ -327,6 +327,46 @@ static const sqlite3_api_routines sqlite3Apis = {
|
||||
sqlite3_next_stmt,
|
||||
sqlite3_sql,
|
||||
sqlite3_status,
|
||||
|
||||
/*
|
||||
** Added for 3.7.4
|
||||
*/
|
||||
sqlite3_backup_finish,
|
||||
sqlite3_backup_init,
|
||||
sqlite3_backup_pagecount,
|
||||
sqlite3_backup_remaining,
|
||||
sqlite3_backup_step,
|
||||
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
|
||||
sqlite3_compileoption_get,
|
||||
sqlite3_compileoption_used,
|
||||
#else
|
||||
0,
|
||||
0,
|
||||
#endif
|
||||
sqlite3_create_function_v2,
|
||||
sqlite3_db_config,
|
||||
sqlite3_db_mutex,
|
||||
sqlite3_db_status,
|
||||
sqlite3_extended_errcode,
|
||||
sqlite3_log,
|
||||
sqlite3_soft_heap_limit64,
|
||||
sqlite3_sourceid,
|
||||
sqlite3_stmt_status,
|
||||
sqlite3_strnicmp,
|
||||
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
|
||||
sqlite3_unlock_notify,
|
||||
#else
|
||||
0,
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
sqlite3_wal_autocheckpoint,
|
||||
sqlite3_wal_checkpoint,
|
||||
sqlite3_wal_hook,
|
||||
#else
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
277
src/main.c
277
src/main.c
@ -26,15 +26,33 @@
|
||||
# include "sqliteicu.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
** The version of the library
|
||||
*/
|
||||
#ifndef SQLITE_AMALGAMATION
|
||||
/* IMPLEMENTATION-OF: R-46656-45156 The sqlite3_version[] string constant
|
||||
** contains the text of SQLITE_VERSION macro.
|
||||
*/
|
||||
const char sqlite3_version[] = SQLITE_VERSION;
|
||||
#endif
|
||||
|
||||
/* IMPLEMENTATION-OF: R-53536-42575 The sqlite3_libversion() function returns
|
||||
** a pointer to the to the sqlite3_version[] string constant.
|
||||
*/
|
||||
const char *sqlite3_libversion(void){ return sqlite3_version; }
|
||||
|
||||
/* IMPLEMENTATION-OF: R-63124-39300 The sqlite3_sourceid() function returns a
|
||||
** pointer to a string constant whose value is the same as the
|
||||
** SQLITE_SOURCE_ID C preprocessor macro.
|
||||
*/
|
||||
const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
|
||||
|
||||
/* IMPLEMENTATION-OF: R-35210-63508 The sqlite3_libversion_number() function
|
||||
** returns an integer equal to SQLITE_VERSION_NUMBER.
|
||||
*/
|
||||
int sqlite3_libversion_number(void){ return SQLITE_VERSION_NUMBER; }
|
||||
|
||||
/* IMPLEMENTATION-OF: R-54823-41343 The sqlite3_threadsafe() function returns
|
||||
** zero if and only if SQLite was compiled mutexing code omitted due to
|
||||
** the SQLITE_THREADSAFE compile-time option being set to 0.
|
||||
*/
|
||||
int sqlite3_threadsafe(void){ return SQLITE_THREADSAFE; }
|
||||
|
||||
#if !defined(SQLITE_OMIT_TRACE) && defined(SQLITE_ENABLE_IOTRACE)
|
||||
@ -155,6 +173,13 @@ int sqlite3_initialize(void){
|
||||
** sqlite3_initialize(). The recursive calls normally come through
|
||||
** sqlite3_os_init() when it invokes sqlite3_vfs_register(), but other
|
||||
** recursive calls might also be possible.
|
||||
**
|
||||
** IMPLEMENTATION-OF: R-00140-37445 SQLite automatically serializes calls
|
||||
** to the xInit method, so the xInit method need not be threadsafe.
|
||||
**
|
||||
** The following mutex is what serializes access to the appdef pcache xInit
|
||||
** methods. The sqlite3_pcache_methods.xInit() all is embedded in the
|
||||
** call to sqlite3PcacheInitialize().
|
||||
*/
|
||||
sqlite3_mutex_enter(sqlite3GlobalConfig.pInitMutex);
|
||||
if( sqlite3GlobalConfig.isInit==0 && sqlite3GlobalConfig.inProgress==0 ){
|
||||
@ -435,12 +460,12 @@ static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){
|
||||
sz = 0;
|
||||
pStart = 0;
|
||||
}else if( pBuf==0 ){
|
||||
sz = ROUND8(sz);
|
||||
sz = ROUNDDOWN8(sz); /* IMP: R-33038-09382 */
|
||||
sqlite3BeginBenignMalloc();
|
||||
pStart = sqlite3Malloc( sz*cnt );
|
||||
pStart = sqlite3Malloc( sz*cnt ); /* IMP: R-61949-35727 */
|
||||
sqlite3EndBenignMalloc();
|
||||
}else{
|
||||
sz = ROUNDDOWN8(sz);
|
||||
sz = ROUNDDOWN8(sz); /* IMP: R-33038-09382 */
|
||||
pStart = pBuf;
|
||||
}
|
||||
db->lookaside.pStart = pStart;
|
||||
@ -483,14 +508,14 @@ int sqlite3_db_config(sqlite3 *db, int op, ...){
|
||||
va_start(ap, op);
|
||||
switch( op ){
|
||||
case SQLITE_DBCONFIG_LOOKASIDE: {
|
||||
void *pBuf = va_arg(ap, void*);
|
||||
int sz = va_arg(ap, int);
|
||||
int cnt = va_arg(ap, int);
|
||||
void *pBuf = va_arg(ap, void*); /* IMP: R-21112-12275 */
|
||||
int sz = va_arg(ap, int); /* IMP: R-47871-25994 */
|
||||
int cnt = va_arg(ap, int); /* IMP: R-04460-53386 */
|
||||
rc = setupLookaside(db, pBuf, sz, cnt);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
rc = SQLITE_ERROR;
|
||||
rc = SQLITE_ERROR; /* IMP: R-42790-23372 */
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -595,11 +620,28 @@ void sqlite3CloseSavepoints(sqlite3 *db){
|
||||
db->isTransactionSavepoint = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Invoke the destructor function associated with FuncDef p, if any. Except,
|
||||
** if this is not the last copy of the function, do not invoke it. Multiple
|
||||
** copies of a single function are created when create_function() is called
|
||||
** with SQLITE_ANY as the encoding.
|
||||
*/
|
||||
static void functionDestroy(sqlite3 *db, FuncDef *p){
|
||||
FuncDestructor *pDestructor = p->pDestructor;
|
||||
if( pDestructor ){
|
||||
pDestructor->nRef--;
|
||||
if( pDestructor->nRef==0 ){
|
||||
pDestructor->xDestroy(pDestructor->pUserData);
|
||||
sqlite3DbFree(db, pDestructor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Close an existing SQLite database
|
||||
*/
|
||||
int sqlite3_close(sqlite3 *db){
|
||||
HashElem *i;
|
||||
HashElem *i; /* Hash table iterator */
|
||||
int j;
|
||||
|
||||
if( !db ){
|
||||
@ -667,6 +709,7 @@ int sqlite3_close(sqlite3 *db){
|
||||
for(p=db->aFunc.a[j]; p; p=pHash){
|
||||
pHash = p->pHash;
|
||||
while( p ){
|
||||
functionDestroy(db, p);
|
||||
pNext = p->pNext;
|
||||
sqlite3DbFree(db, p);
|
||||
p = pNext;
|
||||
@ -773,7 +816,7 @@ const char *sqlite3ErrStr(int rc){
|
||||
/* SQLITE_INTERRUPT */ "interrupted",
|
||||
/* SQLITE_IOERR */ "disk I/O error",
|
||||
/* SQLITE_CORRUPT */ "database disk image is malformed",
|
||||
/* SQLITE_NOTFOUND */ 0,
|
||||
/* SQLITE_NOTFOUND */ "unknown operation",
|
||||
/* SQLITE_FULL */ "database or disk is full",
|
||||
/* SQLITE_CANTOPEN */ "unable to open database file",
|
||||
/* SQLITE_PROTOCOL */ "locking protocol",
|
||||
@ -941,7 +984,8 @@ int sqlite3CreateFunc(
|
||||
void *pUserData,
|
||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value **),
|
||||
void (*xStep)(sqlite3_context*,int,sqlite3_value **),
|
||||
void (*xFinal)(sqlite3_context*)
|
||||
void (*xFinal)(sqlite3_context*),
|
||||
FuncDestructor *pDestructor
|
||||
){
|
||||
FuncDef *p;
|
||||
int nName;
|
||||
@ -969,10 +1013,10 @@ int sqlite3CreateFunc(
|
||||
}else if( enc==SQLITE_ANY ){
|
||||
int rc;
|
||||
rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF8,
|
||||
pUserData, xFunc, xStep, xFinal);
|
||||
pUserData, xFunc, xStep, xFinal, pDestructor);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF16LE,
|
||||
pUserData, xFunc, xStep, xFinal);
|
||||
pUserData, xFunc, xStep, xFinal, pDestructor);
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
@ -1005,6 +1049,15 @@ int sqlite3CreateFunc(
|
||||
if( !p ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
/* If an older version of the function with a configured destructor is
|
||||
** being replaced invoke the destructor function here. */
|
||||
functionDestroy(db, p);
|
||||
|
||||
if( pDestructor ){
|
||||
pDestructor->nRef++;
|
||||
}
|
||||
p->pDestructor = pDestructor;
|
||||
p->flags = 0;
|
||||
p->xFunc = xFunc;
|
||||
p->xStep = xStep;
|
||||
@ -1019,7 +1072,7 @@ int sqlite3CreateFunc(
|
||||
*/
|
||||
int sqlite3_create_function(
|
||||
sqlite3 *db,
|
||||
const char *zFunctionName,
|
||||
const char *zFunc,
|
||||
int nArg,
|
||||
int enc,
|
||||
void *p,
|
||||
@ -1027,9 +1080,41 @@ int sqlite3_create_function(
|
||||
void (*xStep)(sqlite3_context*,int,sqlite3_value **),
|
||||
void (*xFinal)(sqlite3_context*)
|
||||
){
|
||||
int rc;
|
||||
return sqlite3_create_function_v2(db, zFunc, nArg, enc, p, xFunc, xStep,
|
||||
xFinal, 0);
|
||||
}
|
||||
|
||||
int sqlite3_create_function_v2(
|
||||
sqlite3 *db,
|
||||
const char *zFunc,
|
||||
int nArg,
|
||||
int enc,
|
||||
void *p,
|
||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value **),
|
||||
void (*xStep)(sqlite3_context*,int,sqlite3_value **),
|
||||
void (*xFinal)(sqlite3_context*),
|
||||
void (*xDestroy)(void *)
|
||||
){
|
||||
int rc = SQLITE_ERROR;
|
||||
FuncDestructor *pArg = 0;
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
rc = sqlite3CreateFunc(db, zFunctionName, nArg, enc, p, xFunc, xStep, xFinal);
|
||||
if( xDestroy ){
|
||||
pArg = (FuncDestructor *)sqlite3DbMallocZero(db, sizeof(FuncDestructor));
|
||||
if( !pArg ){
|
||||
xDestroy(p);
|
||||
goto out;
|
||||
}
|
||||
pArg->xDestroy = xDestroy;
|
||||
pArg->pUserData = p;
|
||||
}
|
||||
rc = sqlite3CreateFunc(db, zFunc, nArg, enc, p, xFunc, xStep, xFinal, pArg);
|
||||
if( pArg && pArg->nRef==0 ){
|
||||
assert( rc!=SQLITE_OK );
|
||||
xDestroy(p);
|
||||
sqlite3DbFree(db, pArg);
|
||||
}
|
||||
|
||||
out:
|
||||
rc = sqlite3ApiExit(db, rc);
|
||||
sqlite3_mutex_leave(db->mutex);
|
||||
return rc;
|
||||
@ -1051,7 +1136,7 @@ int sqlite3_create_function16(
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
assert( !db->mallocFailed );
|
||||
zFunc8 = sqlite3Utf16to8(db, zFunctionName, -1, SQLITE_UTF16NATIVE);
|
||||
rc = sqlite3CreateFunc(db, zFunc8, nArg, eTextRep, p, xFunc, xStep, xFinal);
|
||||
rc = sqlite3CreateFunc(db, zFunc8, nArg, eTextRep, p, xFunc, xStep, xFinal,0);
|
||||
sqlite3DbFree(db, zFunc8);
|
||||
rc = sqlite3ApiExit(db, rc);
|
||||
sqlite3_mutex_leave(db->mutex);
|
||||
@ -1082,7 +1167,7 @@ int sqlite3_overload_function(
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
if( sqlite3FindFunction(db, zName, nName, nArg, SQLITE_UTF8, 0)==0 ){
|
||||
sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8,
|
||||
0, sqlite3InvalidFunction, 0, 0);
|
||||
0, sqlite3InvalidFunction, 0, 0, 0);
|
||||
}
|
||||
rc = sqlite3ApiExit(db, SQLITE_OK);
|
||||
sqlite3_mutex_leave(db->mutex);
|
||||
@ -1220,7 +1305,10 @@ int sqlite3WalDefaultHook(
|
||||
** configured by this function.
|
||||
*/
|
||||
int sqlite3_wal_autocheckpoint(sqlite3 *db, int nFrame){
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
#ifdef SQLITE_OMIT_WAL
|
||||
UNUSED_PARAMETER(db);
|
||||
UNUSED_PARAMETER(nFrame);
|
||||
#else
|
||||
if( nFrame>0 ){
|
||||
sqlite3_wal_hook(db, sqlite3WalDefaultHook, SQLITE_INT_TO_PTR(nFrame));
|
||||
}else{
|
||||
@ -1350,60 +1438,6 @@ int sqlite3TempInMemory(const sqlite3 *db){
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
** This routine is called to create a connection to a database BTree
|
||||
** driver. If zFilename is the name of a file, then that file is
|
||||
** opened and used. If zFilename is the magic name ":memory:" then
|
||||
** the database is stored in memory (and is thus forgotten as soon as
|
||||
** the connection is closed.) If zFilename is NULL then the database
|
||||
** is a "virtual" database for transient use only and is deleted as
|
||||
** soon as the connection is closed.
|
||||
**
|
||||
** A virtual database can be either a disk file (that is automatically
|
||||
** deleted when the file is closed) or it an be held entirely in memory.
|
||||
** The sqlite3TempInMemory() function is used to determine which.
|
||||
*/
|
||||
int sqlite3BtreeFactory(
|
||||
sqlite3 *db, /* Main database when opening aux otherwise 0 */
|
||||
const char *zFilename, /* Name of the file containing the BTree database */
|
||||
int omitJournal, /* if TRUE then do not journal this file */
|
||||
int nCache, /* How many pages in the page cache */
|
||||
int vfsFlags, /* Flags passed through to vfsOpen */
|
||||
Btree **ppBtree /* Pointer to new Btree object written here */
|
||||
){
|
||||
int btFlags = 0;
|
||||
int rc;
|
||||
|
||||
assert( sqlite3_mutex_held(db->mutex) );
|
||||
assert( ppBtree != 0);
|
||||
if( omitJournal ){
|
||||
btFlags |= BTREE_OMIT_JOURNAL;
|
||||
}
|
||||
if( db->flags & SQLITE_NoReadlock ){
|
||||
btFlags |= BTREE_NO_READLOCK;
|
||||
}
|
||||
#ifndef SQLITE_OMIT_MEMORYDB
|
||||
if( zFilename==0 && sqlite3TempInMemory(db) ){
|
||||
zFilename = ":memory:";
|
||||
}
|
||||
#endif
|
||||
|
||||
if( (vfsFlags & SQLITE_OPEN_MAIN_DB)!=0 && (zFilename==0 || *zFilename==0) ){
|
||||
vfsFlags = (vfsFlags & ~SQLITE_OPEN_MAIN_DB) | SQLITE_OPEN_TEMP_DB;
|
||||
}
|
||||
rc = sqlite3BtreeOpen(zFilename, (sqlite3 *)db, ppBtree, btFlags, vfsFlags);
|
||||
|
||||
/* If the B-Tree was successfully opened, set the pager-cache size to the
|
||||
** default value. Except, if the call to BtreeOpen() returned a handle
|
||||
** open on an existing shared pager-cache, do not change the pager-cache
|
||||
** size.
|
||||
*/
|
||||
if( rc==SQLITE_OK && 0==sqlite3BtreeSchema(*ppBtree, 0, 0) ){
|
||||
sqlite3BtreeSetCacheSize(*ppBtree, nCache);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return UTF-8 encoded English language explanation of the most recent
|
||||
** error.
|
||||
@ -1568,13 +1602,12 @@ static int createCollation(
|
||||
}
|
||||
|
||||
pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, 1);
|
||||
if( pColl ){
|
||||
pColl->xCmp = xCompare;
|
||||
pColl->pUser = pCtx;
|
||||
pColl->xDel = xDel;
|
||||
pColl->enc = (u8)(enc2 | (enc & SQLITE_UTF16_ALIGNED));
|
||||
pColl->type = collType;
|
||||
}
|
||||
if( pColl==0 ) return SQLITE_NOMEM;
|
||||
pColl->xCmp = xCompare;
|
||||
pColl->pUser = pCtx;
|
||||
pColl->xDel = xDel;
|
||||
pColl->enc = (u8)(enc2 | (enc & SQLITE_UTF16_ALIGNED));
|
||||
pColl->type = collType;
|
||||
sqlite3Error(db, SQLITE_OK, 0);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@ -1646,17 +1679,39 @@ static const int aHardLimit[] = {
|
||||
*/
|
||||
int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){
|
||||
int oldLimit;
|
||||
|
||||
|
||||
/* EVIDENCE-OF: R-30189-54097 For each limit category SQLITE_LIMIT_NAME
|
||||
** there is a hard upper bound set at compile-time by a C preprocessor
|
||||
** macro called SQLITE_MAX_NAME. (The "_LIMIT_" in the name is changed to
|
||||
** "_MAX_".)
|
||||
*/
|
||||
assert( aHardLimit[SQLITE_LIMIT_LENGTH]==SQLITE_MAX_LENGTH );
|
||||
assert( aHardLimit[SQLITE_LIMIT_SQL_LENGTH]==SQLITE_MAX_SQL_LENGTH );
|
||||
assert( aHardLimit[SQLITE_LIMIT_COLUMN]==SQLITE_MAX_COLUMN );
|
||||
assert( aHardLimit[SQLITE_LIMIT_EXPR_DEPTH]==SQLITE_MAX_EXPR_DEPTH );
|
||||
assert( aHardLimit[SQLITE_LIMIT_COMPOUND_SELECT]==SQLITE_MAX_COMPOUND_SELECT);
|
||||
assert( aHardLimit[SQLITE_LIMIT_VDBE_OP]==SQLITE_MAX_VDBE_OP );
|
||||
assert( aHardLimit[SQLITE_LIMIT_FUNCTION_ARG]==SQLITE_MAX_FUNCTION_ARG );
|
||||
assert( aHardLimit[SQLITE_LIMIT_ATTACHED]==SQLITE_MAX_ATTACHED );
|
||||
assert( aHardLimit[SQLITE_LIMIT_LIKE_PATTERN_LENGTH]==
|
||||
SQLITE_MAX_LIKE_PATTERN_LENGTH );
|
||||
assert( aHardLimit[SQLITE_LIMIT_VARIABLE_NUMBER]==SQLITE_MAX_VARIABLE_NUMBER);
|
||||
assert( aHardLimit[SQLITE_LIMIT_TRIGGER_DEPTH]==SQLITE_MAX_TRIGGER_DEPTH );
|
||||
assert( SQLITE_LIMIT_TRIGGER_DEPTH==(SQLITE_N_LIMIT-1) );
|
||||
|
||||
|
||||
if( limitId<0 || limitId>=SQLITE_N_LIMIT ){
|
||||
return -1;
|
||||
}
|
||||
oldLimit = db->aLimit[limitId];
|
||||
if( newLimit>=0 ){
|
||||
if( newLimit>=0 ){ /* IMP: R-52476-28732 */
|
||||
if( newLimit>aHardLimit[limitId] ){
|
||||
newLimit = aHardLimit[limitId];
|
||||
newLimit = aHardLimit[limitId]; /* IMP: R-51463-25634 */
|
||||
}
|
||||
db->aLimit[limitId] = newLimit;
|
||||
}
|
||||
return oldLimit;
|
||||
return oldLimit; /* IMP: R-53341-35419 */
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1680,6 +1735,24 @@ static int openDatabase(
|
||||
if( rc ) return rc;
|
||||
#endif
|
||||
|
||||
/* Only allow sensible combinations of bits in the flags argument.
|
||||
** Throw an error if any non-sense combination is used. If we
|
||||
** do not block illegal combinations here, it could trigger
|
||||
** assert() statements in deeper layers. Sensible combinations
|
||||
** are:
|
||||
**
|
||||
** 1: SQLITE_OPEN_READONLY
|
||||
** 2: SQLITE_OPEN_READWRITE
|
||||
** 6: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
|
||||
*/
|
||||
assert( SQLITE_OPEN_READONLY == 0x01 );
|
||||
assert( SQLITE_OPEN_READWRITE == 0x02 );
|
||||
assert( SQLITE_OPEN_CREATE == 0x04 );
|
||||
testcase( (1<<(flags&7))==0x02 ); /* READONLY */
|
||||
testcase( (1<<(flags&7))==0x04 ); /* READWRITE */
|
||||
testcase( (1<<(flags&7))==0x40 ); /* READWRITE | CREATE */
|
||||
if( ((1<<(flags&7)) & 0x46)==0 ) return SQLITE_MISUSE;
|
||||
|
||||
if( sqlite3GlobalConfig.bCoreMutex==0 ){
|
||||
isThreadsafe = 0;
|
||||
}else if( flags & SQLITE_OPEN_NOMUTEX ){
|
||||
@ -1713,7 +1786,8 @@ static int openDatabase(
|
||||
SQLITE_OPEN_SUBJOURNAL |
|
||||
SQLITE_OPEN_MASTER_JOURNAL |
|
||||
SQLITE_OPEN_NOMUTEX |
|
||||
SQLITE_OPEN_FULLMUTEX
|
||||
SQLITE_OPEN_FULLMUTEX |
|
||||
SQLITE_OPEN_WAL
|
||||
);
|
||||
|
||||
/* Allocate the sqlite data structure */
|
||||
@ -1747,6 +1821,9 @@ static int openDatabase(
|
||||
#endif
|
||||
#if SQLITE_DEFAULT_RECURSIVE_TRIGGERS
|
||||
| SQLITE_RecTriggers
|
||||
#endif
|
||||
#if defined(SQLITE_DEFAULT_FOREIGN_KEYS) && SQLITE_DEFAULT_FOREIGN_KEYS
|
||||
| SQLITE_ForeignKeys
|
||||
#endif
|
||||
;
|
||||
sqlite3HashInit(&db->aCollSeq);
|
||||
@ -1785,9 +1862,8 @@ static int openDatabase(
|
||||
|
||||
/* Open the backend database driver */
|
||||
db->openFlags = flags;
|
||||
rc = sqlite3BtreeFactory(db, zFilename, 0, SQLITE_DEFAULT_CACHE_SIZE,
|
||||
flags | SQLITE_OPEN_MAIN_DB,
|
||||
&db->aDb[0].pBt);
|
||||
rc = sqlite3BtreeOpen(zFilename, db, &db->aDb[0].pBt, 0,
|
||||
flags | SQLITE_OPEN_MAIN_DB);
|
||||
if( rc!=SQLITE_OK ){
|
||||
if( rc==SQLITE_IOERR_NOMEM ){
|
||||
rc = SQLITE_NOMEM;
|
||||
@ -2283,8 +2359,13 @@ int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, void *pArg){
|
||||
assert( pPager!=0 );
|
||||
fd = sqlite3PagerFile(pPager);
|
||||
assert( fd!=0 );
|
||||
if( fd->pMethods ){
|
||||
if( op==SQLITE_FCNTL_FILE_POINTER ){
|
||||
*(sqlite3_file**)pArg = fd;
|
||||
rc = SQLITE_OK;
|
||||
}else if( fd->pMethods ){
|
||||
rc = sqlite3OsFileControl(fd, op, pArg);
|
||||
}else{
|
||||
rc = SQLITE_NOTFOUND;
|
||||
}
|
||||
sqlite3BtreeLeave(pBtree);
|
||||
}
|
||||
@ -2494,6 +2575,22 @@ int sqlite3_test_control(int op, ...){
|
||||
break;
|
||||
}
|
||||
|
||||
/* sqlite3_test_control(SQLITE_TESTCTRL_SCRATCHMALLOC, sz, &pNew, pFree);
|
||||
**
|
||||
** Pass pFree into sqlite3ScratchFree().
|
||||
** If sz>0 then allocate a scratch buffer into pNew.
|
||||
*/
|
||||
case SQLITE_TESTCTRL_SCRATCHMALLOC: {
|
||||
void *pFree, **ppNew;
|
||||
int sz;
|
||||
sz = va_arg(ap, int);
|
||||
ppNew = va_arg(ap, void**);
|
||||
pFree = va_arg(ap, void*);
|
||||
if( sz ) *ppNew = sqlite3ScratchMalloc(sz);
|
||||
sqlite3ScratchFree(pFree);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
va_end(ap);
|
||||
#endif /* SQLITE_OMIT_BUILTIN_TEST */
|
||||
|
||||
399
src/malloc.c
399
src/malloc.c
@ -15,6 +15,66 @@
|
||||
#include "sqliteInt.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
/*
|
||||
** Attempt to release up to n bytes of non-essential memory currently
|
||||
** held by SQLite. An example of non-essential memory is memory used to
|
||||
** cache database pages that are not currently in use.
|
||||
*/
|
||||
int sqlite3_release_memory(int n){
|
||||
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
|
||||
return sqlite3PcacheReleaseMemory(n);
|
||||
#else
|
||||
/* IMPLEMENTATION-OF: R-34391-24921 The sqlite3_release_memory() routine
|
||||
** is a no-op returning zero if SQLite is not compiled with
|
||||
** SQLITE_ENABLE_MEMORY_MANAGEMENT. */
|
||||
UNUSED_PARAMETER(n);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
** An instance of the following object records the location of
|
||||
** each unused scratch buffer.
|
||||
*/
|
||||
typedef struct ScratchFreeslot {
|
||||
struct ScratchFreeslot *pNext; /* Next unused scratch buffer */
|
||||
} ScratchFreeslot;
|
||||
|
||||
/*
|
||||
** State information local to the memory allocation subsystem.
|
||||
*/
|
||||
static SQLITE_WSD struct Mem0Global {
|
||||
sqlite3_mutex *mutex; /* Mutex to serialize access */
|
||||
|
||||
/*
|
||||
** The alarm callback and its arguments. The mem0.mutex lock will
|
||||
** be held while the callback is running. Recursive calls into
|
||||
** the memory subsystem are allowed, but no new callbacks will be
|
||||
** issued.
|
||||
*/
|
||||
sqlite3_int64 alarmThreshold;
|
||||
void (*alarmCallback)(void*, sqlite3_int64,int);
|
||||
void *alarmArg;
|
||||
|
||||
/*
|
||||
** Pointers to the end of sqlite3GlobalConfig.pScratch memory
|
||||
** (so that a range test can be used to determine if an allocation
|
||||
** being freed came from pScratch) and a pointer to the list of
|
||||
** unused scratch allocations.
|
||||
*/
|
||||
void *pScratchEnd;
|
||||
ScratchFreeslot *pScratchFree;
|
||||
u32 nScratchFree;
|
||||
|
||||
/*
|
||||
** True if heap is nearly "full" where "full" is defined by the
|
||||
** sqlite3_soft_heap_limit() setting.
|
||||
*/
|
||||
int nearlyFull;
|
||||
} mem0 = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
#define mem0 GLOBAL(struct Mem0Global, mem0)
|
||||
|
||||
/*
|
||||
** This routine runs when the memory allocator sees that the
|
||||
** total memory allocation is about to exceed the soft heap
|
||||
@ -29,79 +89,67 @@ static void softHeapLimitEnforcer(
|
||||
sqlite3_release_memory(allocSize);
|
||||
}
|
||||
|
||||
/*
|
||||
** Change the alarm callback
|
||||
*/
|
||||
static int sqlite3MemoryAlarm(
|
||||
void(*xCallback)(void *pArg, sqlite3_int64 used,int N),
|
||||
void *pArg,
|
||||
sqlite3_int64 iThreshold
|
||||
){
|
||||
int nUsed;
|
||||
sqlite3_mutex_enter(mem0.mutex);
|
||||
mem0.alarmCallback = xCallback;
|
||||
mem0.alarmArg = pArg;
|
||||
mem0.alarmThreshold = iThreshold;
|
||||
nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED);
|
||||
mem0.nearlyFull = (iThreshold>0 && iThreshold<=nUsed);
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_DEPRECATED
|
||||
/*
|
||||
** Deprecated external interface. Internal/core SQLite code
|
||||
** should call sqlite3MemoryAlarm.
|
||||
*/
|
||||
int sqlite3_memory_alarm(
|
||||
void(*xCallback)(void *pArg, sqlite3_int64 used,int N),
|
||||
void *pArg,
|
||||
sqlite3_int64 iThreshold
|
||||
){
|
||||
return sqlite3MemoryAlarm(xCallback, pArg, iThreshold);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Set the soft heap-size limit for the library. Passing a zero or
|
||||
** negative value indicates no limit.
|
||||
*/
|
||||
void sqlite3_soft_heap_limit(int n){
|
||||
sqlite3_uint64 iLimit;
|
||||
int overage;
|
||||
if( n<0 ){
|
||||
iLimit = 0;
|
||||
}else{
|
||||
iLimit = n;
|
||||
}
|
||||
sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 n){
|
||||
sqlite3_int64 priorLimit;
|
||||
sqlite3_int64 excess;
|
||||
#ifndef SQLITE_OMIT_AUTOINIT
|
||||
sqlite3_initialize();
|
||||
#endif
|
||||
if( iLimit>0 ){
|
||||
sqlite3MemoryAlarm(softHeapLimitEnforcer, 0, iLimit);
|
||||
sqlite3_mutex_enter(mem0.mutex);
|
||||
priorLimit = mem0.alarmThreshold;
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
if( n<0 ) return priorLimit;
|
||||
if( n>0 ){
|
||||
sqlite3MemoryAlarm(softHeapLimitEnforcer, 0, n);
|
||||
}else{
|
||||
sqlite3MemoryAlarm(0, 0, 0);
|
||||
}
|
||||
overage = (int)(sqlite3_memory_used() - (i64)n);
|
||||
if( overage>0 ){
|
||||
sqlite3_release_memory(overage);
|
||||
}
|
||||
excess = sqlite3_memory_used() - n;
|
||||
if( excess>0 ) sqlite3_release_memory((int)(excess & 0x7fffffff));
|
||||
return priorLimit;
|
||||
}
|
||||
|
||||
/*
|
||||
** Attempt to release up to n bytes of non-essential memory currently
|
||||
** held by SQLite. An example of non-essential memory is memory used to
|
||||
** cache database pages that are not currently in use.
|
||||
*/
|
||||
int sqlite3_release_memory(int n){
|
||||
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
|
||||
int nRet = 0;
|
||||
nRet += sqlite3PcacheReleaseMemory(n-nRet);
|
||||
return nRet;
|
||||
#else
|
||||
UNUSED_PARAMETER(n);
|
||||
return SQLITE_OK;
|
||||
#endif
|
||||
void sqlite3_soft_heap_limit(int n){
|
||||
if( n<0 ) n = 0;
|
||||
sqlite3_soft_heap_limit64(n);
|
||||
}
|
||||
|
||||
/*
|
||||
** State information local to the memory allocation subsystem.
|
||||
*/
|
||||
static SQLITE_WSD struct Mem0Global {
|
||||
/* Number of free pages for scratch and page-cache memory */
|
||||
u32 nScratchFree;
|
||||
u32 nPageFree;
|
||||
|
||||
sqlite3_mutex *mutex; /* Mutex to serialize access */
|
||||
|
||||
/*
|
||||
** The alarm callback and its arguments. The mem0.mutex lock will
|
||||
** be held while the callback is running. Recursive calls into
|
||||
** the memory subsystem are allowed, but no new callbacks will be
|
||||
** issued.
|
||||
*/
|
||||
sqlite3_int64 alarmThreshold;
|
||||
void (*alarmCallback)(void*, sqlite3_int64,int);
|
||||
void *alarmArg;
|
||||
|
||||
/*
|
||||
** Pointers to the end of sqlite3GlobalConfig.pScratch and
|
||||
** sqlite3GlobalConfig.pPage to a block of memory that records
|
||||
** which pages are available.
|
||||
*/
|
||||
u32 *aScratchFree;
|
||||
u32 *aPageFree;
|
||||
} mem0 = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
#define mem0 GLOBAL(struct Mem0Global, mem0)
|
||||
|
||||
/*
|
||||
** Initialize the memory allocation subsystem.
|
||||
*/
|
||||
@ -114,36 +162,45 @@ int sqlite3MallocInit(void){
|
||||
mem0.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM);
|
||||
}
|
||||
if( sqlite3GlobalConfig.pScratch && sqlite3GlobalConfig.szScratch>=100
|
||||
&& sqlite3GlobalConfig.nScratch>=0 ){
|
||||
int i;
|
||||
sqlite3GlobalConfig.szScratch = ROUNDDOWN8(sqlite3GlobalConfig.szScratch-4);
|
||||
mem0.aScratchFree = (u32*)&((char*)sqlite3GlobalConfig.pScratch)
|
||||
[sqlite3GlobalConfig.szScratch*sqlite3GlobalConfig.nScratch];
|
||||
for(i=0; i<sqlite3GlobalConfig.nScratch; i++){ mem0.aScratchFree[i] = i; }
|
||||
mem0.nScratchFree = sqlite3GlobalConfig.nScratch;
|
||||
&& sqlite3GlobalConfig.nScratch>0 ){
|
||||
int i, n, sz;
|
||||
ScratchFreeslot *pSlot;
|
||||
sz = ROUNDDOWN8(sqlite3GlobalConfig.szScratch);
|
||||
sqlite3GlobalConfig.szScratch = sz;
|
||||
pSlot = (ScratchFreeslot*)sqlite3GlobalConfig.pScratch;
|
||||
n = sqlite3GlobalConfig.nScratch;
|
||||
mem0.pScratchFree = pSlot;
|
||||
mem0.nScratchFree = n;
|
||||
for(i=0; i<n-1; i++){
|
||||
pSlot->pNext = (ScratchFreeslot*)(sz+(char*)pSlot);
|
||||
pSlot = pSlot->pNext;
|
||||
}
|
||||
pSlot->pNext = 0;
|
||||
mem0.pScratchEnd = (void*)&pSlot[1];
|
||||
}else{
|
||||
mem0.pScratchEnd = 0;
|
||||
sqlite3GlobalConfig.pScratch = 0;
|
||||
sqlite3GlobalConfig.szScratch = 0;
|
||||
sqlite3GlobalConfig.nScratch = 0;
|
||||
}
|
||||
if( sqlite3GlobalConfig.pPage && sqlite3GlobalConfig.szPage>=512
|
||||
&& sqlite3GlobalConfig.nPage>=1 ){
|
||||
int i;
|
||||
int overhead;
|
||||
int sz = ROUNDDOWN8(sqlite3GlobalConfig.szPage);
|
||||
int n = sqlite3GlobalConfig.nPage;
|
||||
overhead = (4*n + sz - 1)/sz;
|
||||
sqlite3GlobalConfig.nPage -= overhead;
|
||||
mem0.aPageFree = (u32*)&((char*)sqlite3GlobalConfig.pPage)
|
||||
[sqlite3GlobalConfig.szPage*sqlite3GlobalConfig.nPage];
|
||||
for(i=0; i<sqlite3GlobalConfig.nPage; i++){ mem0.aPageFree[i] = i; }
|
||||
mem0.nPageFree = sqlite3GlobalConfig.nPage;
|
||||
}else{
|
||||
if( sqlite3GlobalConfig.pPage==0 || sqlite3GlobalConfig.szPage<512
|
||||
|| sqlite3GlobalConfig.nPage<1 ){
|
||||
sqlite3GlobalConfig.pPage = 0;
|
||||
sqlite3GlobalConfig.szPage = 0;
|
||||
sqlite3GlobalConfig.nPage = 0;
|
||||
}
|
||||
return sqlite3GlobalConfig.m.xInit(sqlite3GlobalConfig.m.pAppData);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if the heap is currently under memory pressure - in other
|
||||
** words if the amount of heap used is close to the limit set by
|
||||
** sqlite3_soft_heap_limit().
|
||||
*/
|
||||
int sqlite3HeapNearlyFull(void){
|
||||
return mem0.nearlyFull;
|
||||
}
|
||||
|
||||
/*
|
||||
** Deinitialize the memory allocation subsystem.
|
||||
*/
|
||||
@ -178,36 +235,6 @@ sqlite3_int64 sqlite3_memory_highwater(int resetFlag){
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
** Change the alarm callback
|
||||
*/
|
||||
int sqlite3MemoryAlarm(
|
||||
void(*xCallback)(void *pArg, sqlite3_int64 used,int N),
|
||||
void *pArg,
|
||||
sqlite3_int64 iThreshold
|
||||
){
|
||||
sqlite3_mutex_enter(mem0.mutex);
|
||||
mem0.alarmCallback = xCallback;
|
||||
mem0.alarmArg = pArg;
|
||||
mem0.alarmThreshold = iThreshold;
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_DEPRECATED
|
||||
/*
|
||||
** Deprecated external interface. Internal/core SQLite code
|
||||
** should call sqlite3MemoryAlarm.
|
||||
*/
|
||||
int sqlite3_memory_alarm(
|
||||
void(*xCallback)(void *pArg, sqlite3_int64 used,int N),
|
||||
void *pArg,
|
||||
sqlite3_int64 iThreshold
|
||||
){
|
||||
return sqlite3MemoryAlarm(xCallback, pArg, iThreshold);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Trigger the alarm
|
||||
*/
|
||||
@ -240,14 +267,19 @@ static int mallocWithAlarm(int n, void **pp){
|
||||
if( mem0.alarmCallback!=0 ){
|
||||
int nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED);
|
||||
if( nUsed+nFull >= mem0.alarmThreshold ){
|
||||
mem0.nearlyFull = 1;
|
||||
sqlite3MallocAlarm(nFull);
|
||||
}else{
|
||||
mem0.nearlyFull = 0;
|
||||
}
|
||||
}
|
||||
p = sqlite3GlobalConfig.m.xMalloc(nFull);
|
||||
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
|
||||
if( p==0 && mem0.alarmCallback ){
|
||||
sqlite3MallocAlarm(nFull);
|
||||
p = sqlite3GlobalConfig.m.xMalloc(nFull);
|
||||
}
|
||||
#endif
|
||||
if( p ){
|
||||
nFull = sqlite3MallocSize(p);
|
||||
sqlite3StatusAdd(SQLITE_STATUS_MEMORY_USED, nFull);
|
||||
@ -263,7 +295,9 @@ static int mallocWithAlarm(int n, void **pp){
|
||||
*/
|
||||
void *sqlite3Malloc(int n){
|
||||
void *p;
|
||||
if( n<=0 || n>=0x7fffff00 ){
|
||||
if( n<=0 /* IMP: R-65312-04917 */
|
||||
|| n>=0x7fffff00
|
||||
){
|
||||
/* A memory allocation of a number of bytes which is near the maximum
|
||||
** signed integer value might cause an integer overflow inside of the
|
||||
** xMalloc(). Hence we limit the maximum size to 0x7fffff00, giving
|
||||
@ -277,6 +311,7 @@ void *sqlite3Malloc(int n){
|
||||
}else{
|
||||
p = sqlite3GlobalConfig.m.xMalloc(n);
|
||||
}
|
||||
assert( EIGHT_BYTE_ALIGNMENT(p) ); /* IMP: R-04675-44850 */
|
||||
return p;
|
||||
}
|
||||
|
||||
@ -315,59 +350,65 @@ void *sqlite3ScratchMalloc(int n){
|
||||
void *p;
|
||||
assert( n>0 );
|
||||
|
||||
sqlite3_mutex_enter(mem0.mutex);
|
||||
if( mem0.nScratchFree && sqlite3GlobalConfig.szScratch>=n ){
|
||||
p = mem0.pScratchFree;
|
||||
mem0.pScratchFree = mem0.pScratchFree->pNext;
|
||||
mem0.nScratchFree--;
|
||||
sqlite3StatusAdd(SQLITE_STATUS_SCRATCH_USED, 1);
|
||||
sqlite3StatusSet(SQLITE_STATUS_SCRATCH_SIZE, n);
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
}else{
|
||||
if( sqlite3GlobalConfig.bMemstat ){
|
||||
sqlite3StatusSet(SQLITE_STATUS_SCRATCH_SIZE, n);
|
||||
n = mallocWithAlarm(n, &p);
|
||||
if( p ) sqlite3StatusAdd(SQLITE_STATUS_SCRATCH_OVERFLOW, n);
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
}else{
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
p = sqlite3GlobalConfig.m.xMalloc(n);
|
||||
}
|
||||
sqlite3MemdebugSetType(p, MEMTYPE_SCRATCH);
|
||||
}
|
||||
assert( sqlite3_mutex_notheld(mem0.mutex) );
|
||||
|
||||
|
||||
#if SQLITE_THREADSAFE==0 && !defined(NDEBUG)
|
||||
/* Verify that no more than two scratch allocation per thread
|
||||
** is outstanding at one time. (This is only checked in the
|
||||
/* Verify that no more than two scratch allocations per thread
|
||||
** are outstanding at one time. (This is only checked in the
|
||||
** single-threaded case since checking in the multi-threaded case
|
||||
** would be much more complicated.) */
|
||||
assert( scratchAllocOut<=1 );
|
||||
#endif
|
||||
|
||||
if( sqlite3GlobalConfig.szScratch<n ){
|
||||
goto scratch_overflow;
|
||||
}else{
|
||||
sqlite3_mutex_enter(mem0.mutex);
|
||||
if( mem0.nScratchFree==0 ){
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
goto scratch_overflow;
|
||||
}else{
|
||||
int i;
|
||||
i = mem0.aScratchFree[--mem0.nScratchFree];
|
||||
i *= sqlite3GlobalConfig.szScratch;
|
||||
sqlite3StatusAdd(SQLITE_STATUS_SCRATCH_USED, 1);
|
||||
sqlite3StatusSet(SQLITE_STATUS_SCRATCH_SIZE, n);
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
p = (void*)&((char*)sqlite3GlobalConfig.pScratch)[i];
|
||||
assert( (((u8*)p - (u8*)0) & 7)==0 );
|
||||
}
|
||||
}
|
||||
#if SQLITE_THREADSAFE==0 && !defined(NDEBUG)
|
||||
scratchAllocOut = p!=0;
|
||||
if( p ) scratchAllocOut++;
|
||||
#endif
|
||||
|
||||
return p;
|
||||
|
||||
scratch_overflow:
|
||||
if( sqlite3GlobalConfig.bMemstat ){
|
||||
sqlite3_mutex_enter(mem0.mutex);
|
||||
sqlite3StatusSet(SQLITE_STATUS_SCRATCH_SIZE, n);
|
||||
n = mallocWithAlarm(n, &p);
|
||||
if( p ) sqlite3StatusAdd(SQLITE_STATUS_SCRATCH_OVERFLOW, n);
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
}else{
|
||||
p = sqlite3GlobalConfig.m.xMalloc(n);
|
||||
}
|
||||
sqlite3MemdebugSetType(p, MEMTYPE_SCRATCH);
|
||||
#if SQLITE_THREADSAFE==0 && !defined(NDEBUG)
|
||||
scratchAllocOut = p!=0;
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
void sqlite3ScratchFree(void *p){
|
||||
if( p ){
|
||||
if( sqlite3GlobalConfig.pScratch==0
|
||||
|| p<sqlite3GlobalConfig.pScratch
|
||||
|| p>=(void*)mem0.aScratchFree ){
|
||||
|
||||
#if SQLITE_THREADSAFE==0 && !defined(NDEBUG)
|
||||
/* Verify that no more than two scratch allocation per thread
|
||||
** is outstanding at one time. (This is only checked in the
|
||||
** single-threaded case since checking in the multi-threaded case
|
||||
** would be much more complicated.) */
|
||||
assert( scratchAllocOut>=1 && scratchAllocOut<=2 );
|
||||
scratchAllocOut--;
|
||||
#endif
|
||||
|
||||
if( p>=sqlite3GlobalConfig.pScratch && p<mem0.pScratchEnd ){
|
||||
/* Release memory from the SQLITE_CONFIG_SCRATCH allocation */
|
||||
ScratchFreeslot *pSlot;
|
||||
pSlot = (ScratchFreeslot*)p;
|
||||
sqlite3_mutex_enter(mem0.mutex);
|
||||
pSlot->pNext = mem0.pScratchFree;
|
||||
mem0.pScratchFree = pSlot;
|
||||
mem0.nScratchFree++;
|
||||
assert( mem0.nScratchFree<=sqlite3GlobalConfig.nScratch );
|
||||
sqlite3StatusAdd(SQLITE_STATUS_SCRATCH_USED, -1);
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
}else{
|
||||
/* Release memory back to the heap */
|
||||
assert( sqlite3MemdebugHasType(p, MEMTYPE_SCRATCH) );
|
||||
assert( sqlite3MemdebugNoType(p, ~MEMTYPE_SCRATCH) );
|
||||
sqlite3MemdebugSetType(p, MEMTYPE_HEAP);
|
||||
@ -382,26 +423,6 @@ void sqlite3ScratchFree(void *p){
|
||||
}else{
|
||||
sqlite3GlobalConfig.m.xFree(p);
|
||||
}
|
||||
}else{
|
||||
int i;
|
||||
i = (int)((u8*)p - (u8*)sqlite3GlobalConfig.pScratch);
|
||||
i /= sqlite3GlobalConfig.szScratch;
|
||||
assert( i>=0 && i<sqlite3GlobalConfig.nScratch );
|
||||
sqlite3_mutex_enter(mem0.mutex);
|
||||
assert( mem0.nScratchFree<(u32)sqlite3GlobalConfig.nScratch );
|
||||
mem0.aScratchFree[mem0.nScratchFree++] = i;
|
||||
sqlite3StatusAdd(SQLITE_STATUS_SCRATCH_USED, -1);
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
|
||||
#if SQLITE_THREADSAFE==0 && !defined(NDEBUG)
|
||||
/* Verify that no more than two scratch allocation per thread
|
||||
** is outstanding at one time. (This is only checked in the
|
||||
** single-threaded case since checking in the multi-threaded case
|
||||
** would be much more complicated.) */
|
||||
assert( scratchAllocOut>=1 && scratchAllocOut<=2 );
|
||||
scratchAllocOut = 0;
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -442,7 +463,7 @@ int sqlite3DbMallocSize(sqlite3 *db, void *p){
|
||||
** Free memory previously obtained from sqlite3Malloc().
|
||||
*/
|
||||
void sqlite3_free(void *p){
|
||||
if( p==0 ) return;
|
||||
if( p==0 ) return; /* IMP: R-49053-54554 */
|
||||
assert( sqlite3MemdebugNoType(p, MEMTYPE_DB) );
|
||||
assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
|
||||
if( sqlite3GlobalConfig.bMemstat ){
|
||||
@ -489,10 +510,10 @@ void *sqlite3Realloc(void *pOld, int nBytes){
|
||||
int nOld, nNew;
|
||||
void *pNew;
|
||||
if( pOld==0 ){
|
||||
return sqlite3Malloc(nBytes);
|
||||
return sqlite3Malloc(nBytes); /* IMP: R-28354-25769 */
|
||||
}
|
||||
if( nBytes<=0 ){
|
||||
sqlite3_free(pOld);
|
||||
sqlite3_free(pOld); /* IMP: R-31593-10574 */
|
||||
return 0;
|
||||
}
|
||||
if( nBytes>=0x7fffff00 ){
|
||||
@ -500,6 +521,9 @@ void *sqlite3Realloc(void *pOld, int nBytes){
|
||||
return 0;
|
||||
}
|
||||
nOld = sqlite3MallocSize(pOld);
|
||||
/* IMPLEMENTATION-OF: R-46199-30249 SQLite guarantees that the second
|
||||
** argument to xRealloc is always a value returned by a prior call to
|
||||
** xRoundup. */
|
||||
nNew = sqlite3GlobalConfig.m.xRoundup(nBytes);
|
||||
if( nOld==nNew ){
|
||||
pNew = pOld;
|
||||
@ -525,6 +549,7 @@ void *sqlite3Realloc(void *pOld, int nBytes){
|
||||
}else{
|
||||
pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew);
|
||||
}
|
||||
assert( EIGHT_BYTE_ALIGNMENT(pNew) ); /* IMP: R-04675-44850 */
|
||||
return pNew;
|
||||
}
|
||||
|
||||
@ -591,14 +616,20 @@ void *sqlite3DbMallocRaw(sqlite3 *db, int n){
|
||||
if( db->mallocFailed ){
|
||||
return 0;
|
||||
}
|
||||
if( db->lookaside.bEnabled && n<=db->lookaside.sz
|
||||
&& (pBuf = db->lookaside.pFree)!=0 ){
|
||||
db->lookaside.pFree = pBuf->pNext;
|
||||
db->lookaside.nOut++;
|
||||
if( db->lookaside.nOut>db->lookaside.mxOut ){
|
||||
db->lookaside.mxOut = db->lookaside.nOut;
|
||||
if( db->lookaside.bEnabled ){
|
||||
if( n>db->lookaside.sz ){
|
||||
db->lookaside.anStat[1]++;
|
||||
}else if( (pBuf = db->lookaside.pFree)==0 ){
|
||||
db->lookaside.anStat[2]++;
|
||||
}else{
|
||||
db->lookaside.pFree = pBuf->pNext;
|
||||
db->lookaside.nOut++;
|
||||
db->lookaside.anStat[0]++;
|
||||
if( db->lookaside.nOut>db->lookaside.mxOut ){
|
||||
db->lookaside.mxOut = db->lookaside.nOut;
|
||||
}
|
||||
return (void*)pBuf;
|
||||
}
|
||||
return (void*)pBuf;
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
@ -89,7 +89,7 @@ static int sqlite3MemSize(void *pPrior){
|
||||
static void *sqlite3MemRealloc(void *pPrior, int nByte){
|
||||
sqlite3_int64 *p = (sqlite3_int64*)pPrior;
|
||||
assert( pPrior!=0 && nByte>0 );
|
||||
nByte = ROUND8(nByte);
|
||||
assert( nByte==ROUND8(nByte) ); /* EV: R-46199-30249 */
|
||||
p--;
|
||||
p = realloc(p, nByte+8 );
|
||||
if( p ){
|
||||
|
||||
@ -344,6 +344,7 @@ static void *sqlite3MemRealloc(void *pPrior, int nByte){
|
||||
struct MemBlockHdr *pOldHdr;
|
||||
void *pNew;
|
||||
assert( mem.disallow==0 );
|
||||
assert( (nByte & 7)==0 ); /* EV: R-46199-30249 */
|
||||
pOldHdr = sqlite3MemsysGetHeader(pPrior);
|
||||
pNew = sqlite3MemMalloc(nByte);
|
||||
if( pNew ){
|
||||
|
||||
@ -395,7 +395,7 @@ static void *memsys5Realloc(void *pPrior, int nBytes){
|
||||
int nOld;
|
||||
void *p;
|
||||
assert( pPrior!=0 );
|
||||
assert( (nBytes&(nBytes-1))==0 );
|
||||
assert( (nBytes&(nBytes-1))==0 ); /* EV: R-46199-30249 */
|
||||
assert( nBytes>=0 );
|
||||
if( nBytes==0 ){
|
||||
return 0;
|
||||
|
||||
@ -252,8 +252,7 @@ int sqlite3IsMemJournal(sqlite3_file *pJfd){
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the number of bytes required to store a MemJournal that uses vfs
|
||||
** pVfs to create the underlying on-disk files.
|
||||
** Return the number of bytes required to store a MemJournal file descriptor.
|
||||
*/
|
||||
int sqlite3MemJournalSize(void){
|
||||
return sizeof(MemJournal);
|
||||
|
||||
@ -63,8 +63,8 @@
|
||||
#define sqlite3_mutex_enter(X)
|
||||
#define sqlite3_mutex_try(X) SQLITE_OK
|
||||
#define sqlite3_mutex_leave(X)
|
||||
#define sqlite3_mutex_held(X) 1
|
||||
#define sqlite3_mutex_notheld(X) 1
|
||||
#define sqlite3_mutex_held(X) ((void)(X),1)
|
||||
#define sqlite3_mutex_notheld(X) ((void)(X),1)
|
||||
#define sqlite3MutexAlloc(X) ((sqlite3_mutex*)8)
|
||||
#define sqlite3MutexInit() SQLITE_OK
|
||||
#define sqlite3MutexEnd()
|
||||
|
||||
@ -99,7 +99,7 @@ static int pthreadMutexEnd(void){ return SQLITE_OK; }
|
||||
** <li> SQLITE_MUTEX_STATIC_MEM2
|
||||
** <li> SQLITE_MUTEX_STATIC_PRNG
|
||||
** <li> SQLITE_MUTEX_STATIC_LRU
|
||||
** <li> SQLITE_MUTEX_STATIC_LRU2
|
||||
** <li> SQLITE_MUTEX_STATIC_PMEM
|
||||
** </ul>
|
||||
**
|
||||
** The first two constants cause sqlite3_mutex_alloc() to create
|
||||
|
||||
@ -156,7 +156,7 @@ static int winMutexEnd(void){
|
||||
** <li> SQLITE_MUTEX_STATIC_MEM2
|
||||
** <li> SQLITE_MUTEX_STATIC_PRNG
|
||||
** <li> SQLITE_MUTEX_STATIC_LRU
|
||||
** <li> SQLITE_MUTEX_STATIC_LRU2
|
||||
** <li> SQLITE_MUTEX_STATIC_PMEM
|
||||
** </ul>
|
||||
**
|
||||
** The first two constants cause sqlite3_mutex_alloc() to create
|
||||
|
||||
6
src/os.c
6
src/os.c
@ -183,6 +183,12 @@ int sqlite3OsSleep(sqlite3_vfs *pVfs, int nMicro){
|
||||
}
|
||||
int sqlite3OsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){
|
||||
int rc;
|
||||
/* IMPLEMENTATION-OF: R-49045-42493 SQLite will use the xCurrentTimeInt64()
|
||||
** method to get the current date and time if that method is available
|
||||
** (if iVersion is 2 or greater and the function pointer is not NULL) and
|
||||
** will fall back to xCurrentTime() if xCurrentTimeInt64() is
|
||||
** unavailable.
|
||||
*/
|
||||
if( pVfs->iVersion>=2 && pVfs->xCurrentTimeInt64 ){
|
||||
rc = pVfs->xCurrentTimeInt64(pVfs, pTimeOut);
|
||||
}else{
|
||||
|
||||
@ -533,7 +533,7 @@ static int os2FileControl(sqlite3_file *id, int op, void *pArg){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
}
|
||||
return SQLITE_ERROR;
|
||||
return SQLITE_NOTFOUND;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@ -119,7 +119,9 @@
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <errno.h>
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#if SQLITE_ENABLE_LOCKING_STYLE
|
||||
# include <sys/ioctl.h>
|
||||
@ -3133,8 +3135,11 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){
|
||||
return proxyFileControl(id,op,pArg);
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) */
|
||||
case SQLITE_FCNTL_SYNC_OMITTED: {
|
||||
return SQLITE_OK; /* A no-op */
|
||||
}
|
||||
}
|
||||
return SQLITE_ERROR;
|
||||
return SQLITE_NOTFOUND;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -3559,7 +3564,7 @@ static int unixShmMap(
|
||||
pShmNode->apRegion = apNew;
|
||||
while(pShmNode->nRegion<=iRegion){
|
||||
void *pMem = mmap(0, szRegion, PROT_READ|PROT_WRITE,
|
||||
MAP_SHARED, pShmNode->h, iRegion*szRegion
|
||||
MAP_SHARED, pShmNode->h, pShmNode->nRegion*szRegion
|
||||
);
|
||||
if( pMem==MAP_FAILED ){
|
||||
rc = SQLITE_IOERR;
|
||||
@ -4077,11 +4082,21 @@ static int fillInUnixFile(
|
||||
*/
|
||||
UNUSED_PARAMETER(isDelete);
|
||||
|
||||
/* Usually the path zFilename should not be a relative pathname. The
|
||||
** exception is when opening the proxy "conch" file in builds that
|
||||
** include the special Apple locking styles.
|
||||
*/
|
||||
#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE
|
||||
assert( zFilename==0 || zFilename[0]=='/'
|
||||
|| pVfs->pAppData==(void*)&autolockIoFinder );
|
||||
#else
|
||||
assert( zFilename==0 || zFilename[0]=='/' );
|
||||
#endif
|
||||
|
||||
OSTRACE(("OPEN %-3d %s\n", h, zFilename));
|
||||
pNew->h = h;
|
||||
pNew->dirfd = dirfd;
|
||||
pNew->fileFlags = 0;
|
||||
assert( zFilename==0 || zFilename[0]=='/' ); /* Never a relative pathname */
|
||||
pNew->zPath = zFilename;
|
||||
|
||||
#if OS_VXWORKS
|
||||
@ -4421,9 +4436,24 @@ static int findCreateFileMode(
|
||||
int nDb; /* Number of valid bytes in zDb */
|
||||
struct stat sStat; /* Output of stat() on database file */
|
||||
|
||||
nDb = sqlite3Strlen30(zPath) - ((flags & SQLITE_OPEN_WAL) ? 4 : 8);
|
||||
/* zPath is a path to a WAL or journal file. The following block derives
|
||||
** the path to the associated database file from zPath. This block handles
|
||||
** the following naming conventions:
|
||||
**
|
||||
** "<path to db>-journal"
|
||||
** "<path to db>-wal"
|
||||
** "<path to db>-journal-NNNN"
|
||||
** "<path to db>-wal-NNNN"
|
||||
**
|
||||
** where NNNN is a 4 digit decimal number. The NNNN naming schemes are
|
||||
** used by the test_multiplex.c module.
|
||||
*/
|
||||
nDb = sqlite3Strlen30(zPath) - 1;
|
||||
while( nDb>0 && zPath[nDb]!='l' ) nDb--;
|
||||
nDb -= ((flags & SQLITE_OPEN_WAL) ? 3 : 7);
|
||||
memcpy(zDb, zPath, nDb);
|
||||
zDb[nDb] = '\0';
|
||||
|
||||
if( 0==stat(zDb, &sStat) ){
|
||||
*pMode = sStat.st_mode & 0777;
|
||||
}else{
|
||||
@ -4838,7 +4868,7 @@ static void *unixDlOpen(sqlite3_vfs *NotUsed, const char *zFilename){
|
||||
** error message.
|
||||
*/
|
||||
static void unixDlError(sqlite3_vfs *NotUsed, int nBuf, char *zBufOut){
|
||||
char *zErr;
|
||||
const char *zErr;
|
||||
UNUSED_PARAMETER(NotUsed);
|
||||
unixEnterMutex();
|
||||
zErr = dlerror();
|
||||
@ -4975,7 +5005,7 @@ static int unixCurrentTimeInt64(sqlite3_vfs *NotUsed, sqlite3_int64 *piNow){
|
||||
#if defined(NO_GETTOD)
|
||||
time_t t;
|
||||
time(&t);
|
||||
*piNow = ((sqlite3_int64)i)*1000 + unixEpoch;
|
||||
*piNow = ((sqlite3_int64)t)*1000 + unixEpoch;
|
||||
#elif OS_VXWORKS
|
||||
struct timespec sNow;
|
||||
clock_gettime(CLOCK_REALTIME, &sNow);
|
||||
@ -5378,17 +5408,21 @@ extern int gethostuuid(uuid_t id, const struct timespec *wait);
|
||||
** bytes of writable memory.
|
||||
*/
|
||||
static int proxyGetHostID(unsigned char *pHostID, int *pError){
|
||||
struct timespec timeout = {1, 0}; /* 1 sec timeout */
|
||||
|
||||
assert(PROXY_HOSTIDLEN == sizeof(uuid_t));
|
||||
memset(pHostID, 0, PROXY_HOSTIDLEN);
|
||||
if( gethostuuid(pHostID, &timeout) ){
|
||||
int err = errno;
|
||||
if( pError ){
|
||||
*pError = err;
|
||||
#if defined(__MAX_OS_X_VERSION_MIN_REQUIRED)\
|
||||
&& __MAC_OS_X_VERSION_MIN_REQUIRED<1050
|
||||
{
|
||||
static const struct timespec timeout = {1, 0}; /* 1 sec timeout */
|
||||
if( gethostuuid(pHostID, &timeout) ){
|
||||
int err = errno;
|
||||
if( pError ){
|
||||
*pError = err;
|
||||
}
|
||||
return SQLITE_IOERR;
|
||||
}
|
||||
return SQLITE_IOERR;
|
||||
}
|
||||
#endif
|
||||
#ifdef SQLITE_TEST
|
||||
/* simulate multiple hosts by creating unique hostid file paths */
|
||||
if( sqlite3_hostid_num != 0){
|
||||
@ -5429,27 +5463,27 @@ static int proxyBreakConchLock(unixFile *pFile, uuid_t myHostID){
|
||||
pathLen = strlcpy(tPath, cPath, MAXPATHLEN);
|
||||
if( pathLen>MAXPATHLEN || pathLen<6 ||
|
||||
(strlcpy(&tPath[pathLen-5], "break", 6) != 5) ){
|
||||
sprintf(errmsg, "path error (len %d)", (int)pathLen);
|
||||
sqlite3_snprintf(sizeof(errmsg),errmsg,"path error (len %d)",(int)pathLen);
|
||||
goto end_breaklock;
|
||||
}
|
||||
/* read the conch content */
|
||||
readLen = pread(conchFile->h, buf, PROXY_MAXCONCHLEN, 0);
|
||||
if( readLen<PROXY_PATHINDEX ){
|
||||
sprintf(errmsg, "read error (len %d)", (int)readLen);
|
||||
sqlite3_snprintf(sizeof(errmsg),errmsg,"read error (len %d)",(int)readLen);
|
||||
goto end_breaklock;
|
||||
}
|
||||
/* write it out to the temporary break file */
|
||||
fd = open(tPath, (O_RDWR|O_CREAT|O_EXCL), SQLITE_DEFAULT_FILE_PERMISSIONS);
|
||||
if( fd<0 ){
|
||||
sprintf(errmsg, "create failed (%d)", errno);
|
||||
sqlite3_snprintf(sizeof(errmsg), errmsg, "create failed (%d)", errno);
|
||||
goto end_breaklock;
|
||||
}
|
||||
if( pwrite(fd, buf, readLen, 0) != (ssize_t)readLen ){
|
||||
sprintf(errmsg, "write failed (%d)", errno);
|
||||
sqlite3_snprintf(sizeof(errmsg), errmsg, "write failed (%d)", errno);
|
||||
goto end_breaklock;
|
||||
}
|
||||
if( rename(tPath, cPath) ){
|
||||
sprintf(errmsg, "rename failed (%d)", errno);
|
||||
sqlite3_snprintf(sizeof(errmsg), errmsg, "rename failed (%d)", errno);
|
||||
goto end_breaklock;
|
||||
}
|
||||
rc = 0;
|
||||
|
||||
138
src/os_win.c
138
src/os_win.c
@ -1183,8 +1183,11 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){
|
||||
SimulateIOErrorBenign(0);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
case SQLITE_FCNTL_SYNC_OMITTED: {
|
||||
return SQLITE_OK;
|
||||
}
|
||||
}
|
||||
return SQLITE_ERROR;
|
||||
return SQLITE_NOTFOUND;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1212,6 +1215,14 @@ static int winDeviceCharacteristics(sqlite3_file *id){
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
|
||||
/*
|
||||
** Windows will only let you create file view mappings
|
||||
** on allocation size granularity boundaries.
|
||||
** During sqlite3_os_init() we do a GetSystemInfo()
|
||||
** to get the granularity size.
|
||||
*/
|
||||
SYSTEM_INFO winSysInfo;
|
||||
|
||||
/*
|
||||
** Helper functions to obtain and relinquish the global mutex. The
|
||||
** global mutex is used to protect the winLockInfo objects used by
|
||||
@ -1380,6 +1391,7 @@ static int winDelete(sqlite3_vfs *,const char*,int);
|
||||
static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){
|
||||
winShmNode **pp;
|
||||
winShmNode *p;
|
||||
BOOL bRc;
|
||||
assert( winShmMutexHeld() );
|
||||
pp = &winShmNodeList;
|
||||
while( (p = *pp)!=0 ){
|
||||
@ -1387,8 +1399,14 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){
|
||||
int i;
|
||||
if( p->mutex ) sqlite3_mutex_free(p->mutex);
|
||||
for(i=0; i<p->nRegion; i++){
|
||||
UnmapViewOfFile(p->aRegion[i].pMap);
|
||||
CloseHandle(p->aRegion[i].hMap);
|
||||
bRc = UnmapViewOfFile(p->aRegion[i].pMap);
|
||||
OSTRACE(("SHM-PURGE pid-%d unmap region=%d %s\n",
|
||||
(int)GetCurrentProcessId(), i,
|
||||
bRc ? "ok" : "failed"));
|
||||
bRc = CloseHandle(p->aRegion[i].hMap);
|
||||
OSTRACE(("SHM-PURGE pid-%d close region=%d %s\n",
|
||||
(int)GetCurrentProcessId(), i,
|
||||
bRc ? "ok" : "failed"));
|
||||
}
|
||||
if( p->hFile.h != INVALID_HANDLE_VALUE ){
|
||||
SimulateIOErrorBenign(1);
|
||||
@ -1465,10 +1483,11 @@ static int winOpenSharedMemory(winFile *pDbFd){
|
||||
rc = SQLITE_NOMEM;
|
||||
goto shm_open_err;
|
||||
}
|
||||
|
||||
rc = winOpen(pDbFd->pVfs,
|
||||
pShmNode->zFilename, /* Name of the file (UTF-8) */
|
||||
(sqlite3_file*)&pShmNode->hFile, /* File handle here */
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, /* Mode flags */
|
||||
SQLITE_OPEN_WAL | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, /* Mode flags */
|
||||
0);
|
||||
if( SQLITE_OK!=rc ){
|
||||
rc = SQLITE_CANTOPEN_BKPT;
|
||||
@ -1776,10 +1795,18 @@ static int winShmMap(
|
||||
hMap = CreateFileMapping(pShmNode->hFile.h,
|
||||
NULL, PAGE_READWRITE, 0, nByte, NULL
|
||||
);
|
||||
OSTRACE(("SHM-MAP pid-%d create region=%d nbyte=%d %s\n",
|
||||
(int)GetCurrentProcessId(), pShmNode->nRegion, nByte,
|
||||
hMap ? "ok" : "failed"));
|
||||
if( hMap ){
|
||||
int iOffset = pShmNode->nRegion*szRegion;
|
||||
int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity;
|
||||
pMap = MapViewOfFile(hMap, FILE_MAP_WRITE | FILE_MAP_READ,
|
||||
0, 0, nByte
|
||||
0, iOffset - iOffsetShift, szRegion + iOffsetShift
|
||||
);
|
||||
OSTRACE(("SHM-MAP pid-%d map region=%d offset=%d size=%d %s\n",
|
||||
(int)GetCurrentProcessId(), pShmNode->nRegion, iOffset, szRegion,
|
||||
pMap ? "ok" : "failed"));
|
||||
}
|
||||
if( !pMap ){
|
||||
pShmNode->lastErrno = GetLastError();
|
||||
@ -1796,8 +1823,10 @@ static int winShmMap(
|
||||
|
||||
shmpage_out:
|
||||
if( pShmNode->nRegion>iRegion ){
|
||||
int iOffset = iRegion*szRegion;
|
||||
int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity;
|
||||
char *p = (char *)pShmNode->aRegion[iRegion].pMap;
|
||||
*pp = (void *)&p[iRegion*szRegion];
|
||||
*pp = (void *)&p[iOffsetShift];
|
||||
}else{
|
||||
*pp = 0;
|
||||
}
|
||||
@ -2024,9 +2053,60 @@ static int winOpen(
|
||||
int isTemp = 0;
|
||||
#endif
|
||||
winFile *pFile = (winFile*)id;
|
||||
void *zConverted; /* Filename in OS encoding */
|
||||
const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */
|
||||
char zTmpname[MAX_PATH+1]; /* Buffer used to create temp filename */
|
||||
void *zConverted; /* Filename in OS encoding */
|
||||
const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */
|
||||
|
||||
/* If argument zPath is a NULL pointer, this function is required to open
|
||||
** a temporary file. Use this buffer to store the file name in.
|
||||
*/
|
||||
char zTmpname[MAX_PATH+1]; /* Buffer used to create temp filename */
|
||||
|
||||
int rc = SQLITE_OK; /* Function Return Code */
|
||||
#if !defined(NDEBUG) || SQLITE_OS_WINCE
|
||||
int eType = flags&0xFFFFFF00; /* Type of file to open */
|
||||
#endif
|
||||
|
||||
int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE);
|
||||
int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE);
|
||||
int isCreate = (flags & SQLITE_OPEN_CREATE);
|
||||
#ifndef NDEBUG
|
||||
int isReadonly = (flags & SQLITE_OPEN_READONLY);
|
||||
#endif
|
||||
int isReadWrite = (flags & SQLITE_OPEN_READWRITE);
|
||||
|
||||
#ifndef NDEBUG
|
||||
int isOpenJournal = (isCreate && (
|
||||
eType==SQLITE_OPEN_MASTER_JOURNAL
|
||||
|| eType==SQLITE_OPEN_MAIN_JOURNAL
|
||||
|| eType==SQLITE_OPEN_WAL
|
||||
));
|
||||
#endif
|
||||
|
||||
/* Check the following statements are true:
|
||||
**
|
||||
** (a) Exactly one of the READWRITE and READONLY flags must be set, and
|
||||
** (b) if CREATE is set, then READWRITE must also be set, and
|
||||
** (c) if EXCLUSIVE is set, then CREATE must also be set.
|
||||
** (d) if DELETEONCLOSE is set, then CREATE must also be set.
|
||||
*/
|
||||
assert((isReadonly==0 || isReadWrite==0) && (isReadWrite || isReadonly));
|
||||
assert(isCreate==0 || isReadWrite);
|
||||
assert(isExclusive==0 || isCreate);
|
||||
assert(isDelete==0 || isCreate);
|
||||
|
||||
/* The main DB, main journal, WAL file and master journal are never
|
||||
** automatically deleted. Nor are they ever temporary files. */
|
||||
assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_DB );
|
||||
assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_JOURNAL );
|
||||
assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MASTER_JOURNAL );
|
||||
assert( (!isDelete && zName) || eType!=SQLITE_OPEN_WAL );
|
||||
|
||||
/* Assert that the upper layer has set one of the "file-type" flags. */
|
||||
assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB
|
||||
|| eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL
|
||||
|| eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_MASTER_JOURNAL
|
||||
|| eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL
|
||||
);
|
||||
|
||||
assert( id!=0 );
|
||||
UNUSED_PARAMETER(pVfs);
|
||||
@ -2037,7 +2117,8 @@ static int winOpen(
|
||||
** temporary file name to use
|
||||
*/
|
||||
if( !zUtf8Name ){
|
||||
int rc = getTempname(MAX_PATH+1, zTmpname);
|
||||
assert(isDelete && !isOpenJournal);
|
||||
rc = getTempname(MAX_PATH+1, zTmpname);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
@ -2050,29 +2131,31 @@ static int winOpen(
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
if( flags & SQLITE_OPEN_READWRITE ){
|
||||
if( isReadWrite ){
|
||||
dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
|
||||
}else{
|
||||
dwDesiredAccess = GENERIC_READ;
|
||||
}
|
||||
|
||||
/* SQLITE_OPEN_EXCLUSIVE is used to make sure that a new file is
|
||||
** created. SQLite doesn't use it to indicate "exclusive access"
|
||||
** as it is usually understood.
|
||||
*/
|
||||
assert(!(flags & SQLITE_OPEN_EXCLUSIVE) || (flags & SQLITE_OPEN_CREATE));
|
||||
if( flags & SQLITE_OPEN_EXCLUSIVE ){
|
||||
if( isExclusive ){
|
||||
/* Creates a new file, only if it does not already exist. */
|
||||
/* If the file exists, it fails. */
|
||||
dwCreationDisposition = CREATE_NEW;
|
||||
}else if( flags & SQLITE_OPEN_CREATE ){
|
||||
}else if( isCreate ){
|
||||
/* Open existing file, or create if it doesn't exist */
|
||||
dwCreationDisposition = OPEN_ALWAYS;
|
||||
}else{
|
||||
/* Opens a file, only if it exists. */
|
||||
dwCreationDisposition = OPEN_EXISTING;
|
||||
}
|
||||
|
||||
dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
|
||||
if( flags & SQLITE_OPEN_DELETEONCLOSE ){
|
||||
|
||||
if( isDelete ){
|
||||
#if SQLITE_OS_WINCE
|
||||
dwFlagsAndAttributes = FILE_ATTRIBUTE_HIDDEN;
|
||||
isTemp = 1;
|
||||
@ -2089,6 +2172,7 @@ static int winOpen(
|
||||
#if SQLITE_OS_WINCE
|
||||
dwFlagsAndAttributes |= FILE_FLAG_RANDOM_ACCESS;
|
||||
#endif
|
||||
|
||||
if( isNT() ){
|
||||
h = CreateFileW((WCHAR*)zConverted,
|
||||
dwDesiredAccess,
|
||||
@ -2114,26 +2198,30 @@ static int winOpen(
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
OSTRACE(("OPEN %d %s 0x%lx %s\n",
|
||||
h, zName, dwDesiredAccess,
|
||||
h==INVALID_HANDLE_VALUE ? "failed" : "ok"));
|
||||
|
||||
if( h==INVALID_HANDLE_VALUE ){
|
||||
pFile->lastErrno = GetLastError();
|
||||
free(zConverted);
|
||||
if( flags & SQLITE_OPEN_READWRITE ){
|
||||
if( isReadWrite ){
|
||||
return winOpen(pVfs, zName, id,
|
||||
((flags|SQLITE_OPEN_READONLY)&~SQLITE_OPEN_READWRITE), pOutFlags);
|
||||
((flags|SQLITE_OPEN_READONLY)&~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)), pOutFlags);
|
||||
}else{
|
||||
return SQLITE_CANTOPEN_BKPT;
|
||||
}
|
||||
}
|
||||
|
||||
if( pOutFlags ){
|
||||
if( flags & SQLITE_OPEN_READWRITE ){
|
||||
if( isReadWrite ){
|
||||
*pOutFlags = SQLITE_OPEN_READWRITE;
|
||||
}else{
|
||||
*pOutFlags = SQLITE_OPEN_READONLY;
|
||||
}
|
||||
}
|
||||
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
pFile->pMethod = &winIoMethod;
|
||||
pFile->h = h;
|
||||
@ -2142,9 +2230,9 @@ static int winOpen(
|
||||
pFile->pShm = 0;
|
||||
pFile->zPath = zName;
|
||||
pFile->sectorSize = getSectorSize(pVfs, zUtf8Name);
|
||||
|
||||
#if SQLITE_OS_WINCE
|
||||
if( (flags & (SQLITE_OPEN_READWRITE|SQLITE_OPEN_MAIN_DB)) ==
|
||||
(SQLITE_OPEN_READWRITE|SQLITE_OPEN_MAIN_DB)
|
||||
if( isReadWrite && eType==SQLITE_OPEN_MAIN_DB
|
||||
&& !winceCreateLock(zName, pFile)
|
||||
){
|
||||
CloseHandle(h);
|
||||
@ -2158,8 +2246,9 @@ static int winOpen(
|
||||
{
|
||||
free(zConverted);
|
||||
}
|
||||
|
||||
OpenCounter(+1);
|
||||
return SQLITE_OK;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2678,6 +2767,13 @@ int sqlite3_os_init(void){
|
||||
winCurrentTimeInt64, /* xCurrentTimeInt64 */
|
||||
};
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
/* get memory map allocation granularity */
|
||||
memset(&winSysInfo, 0, sizeof(SYSTEM_INFO));
|
||||
GetSystemInfo(&winSysInfo);
|
||||
assert(winSysInfo.dwAllocationGranularity > 0);
|
||||
#endif
|
||||
|
||||
sqlite3_vfs_register(&winVfs, 1);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
344
src/pager.c
344
src/pager.c
@ -615,7 +615,8 @@ struct Pager {
|
||||
u8 noReadlock; /* Do not bother to obtain readlocks */
|
||||
u8 noSync; /* Do not sync the journal if true */
|
||||
u8 fullSync; /* Do extra syncs of the journal for robustness */
|
||||
u8 sync_flags; /* One of SYNC_NORMAL or SYNC_FULL */
|
||||
u8 ckptSyncFlags; /* SYNC_NORMAL or SYNC_FULL for checkpoint */
|
||||
u8 syncFlags; /* SYNC_NORMAL or SYNC_FULL otherwise */
|
||||
u8 tempFile; /* zFilename is a temporary file */
|
||||
u8 readOnly; /* True for a read-only database */
|
||||
u8 memDb; /* True to inhibit all file I/O */
|
||||
@ -926,7 +927,9 @@ static int assert_pager_state(Pager *p){
|
||||
|
||||
return 1;
|
||||
}
|
||||
#endif /* ifndef NDEBUG */
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
/*
|
||||
** Return a pointer to a human readable string in a static buffer
|
||||
** containing the state of the Pager object passed as an argument. This
|
||||
@ -1050,7 +1053,7 @@ static int write32bits(sqlite3_file *fd, i64 offset, u32 val){
|
||||
static int pagerUnlockDb(Pager *pPager, int eLock){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
assert( !pPager->exclusiveMode );
|
||||
assert( !pPager->exclusiveMode || pPager->eLock==eLock );
|
||||
assert( eLock==NO_LOCK || eLock==SHARED_LOCK );
|
||||
assert( eLock!=NO_LOCK || pagerUseWal(pPager)==0 );
|
||||
if( isOpen(pPager->fd) ){
|
||||
@ -1297,7 +1300,7 @@ static int zeroJournalHdr(Pager *pPager, int doTruncate){
|
||||
rc = sqlite3OsWrite(pPager->jfd, zeroHdr, sizeof(zeroHdr), 0);
|
||||
}
|
||||
if( rc==SQLITE_OK && !pPager->noSync ){
|
||||
rc = sqlite3OsSync(pPager->jfd, SQLITE_SYNC_DATAONLY|pPager->sync_flags);
|
||||
rc = sqlite3OsSync(pPager->jfd, SQLITE_SYNC_DATAONLY|pPager->syncFlags);
|
||||
}
|
||||
|
||||
/* At this point the transaction is committed but the write lock
|
||||
@ -2474,15 +2477,21 @@ static int pager_truncate(Pager *pPager, Pgno nPage){
|
||||
&& (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN)
|
||||
){
|
||||
i64 currentSize, newSize;
|
||||
int szPage = pPager->pageSize;
|
||||
assert( pPager->eLock==EXCLUSIVE_LOCK );
|
||||
/* TODO: Is it safe to use Pager.dbFileSize here? */
|
||||
rc = sqlite3OsFileSize(pPager->fd, ¤tSize);
|
||||
newSize = pPager->pageSize*(i64)nPage;
|
||||
newSize = szPage*(i64)nPage;
|
||||
if( rc==SQLITE_OK && currentSize!=newSize ){
|
||||
if( currentSize>newSize ){
|
||||
rc = sqlite3OsTruncate(pPager->fd, newSize);
|
||||
}else{
|
||||
rc = sqlite3OsWrite(pPager->fd, "", 1, newSize-1);
|
||||
char *pTmp = pPager->pTmpSpace;
|
||||
memset(pTmp, 0, szPage);
|
||||
testcase( (newSize-szPage) < currentSize );
|
||||
testcase( (newSize-szPage) == currentSize );
|
||||
testcase( (newSize-szPage) > currentSize );
|
||||
rc = sqlite3OsWrite(pPager->fd, pTmp, szPage, newSize-szPage);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
pPager->dbFileSize = nPage;
|
||||
@ -2746,10 +2755,10 @@ end_playback:
|
||||
rc = readMasterJournal(pPager->jfd, zMaster, pPager->pVfs->mxPathname+1);
|
||||
testcase( rc!=SQLITE_OK );
|
||||
}
|
||||
if( rc==SQLITE_OK && !pPager->noSync
|
||||
if( rc==SQLITE_OK
|
||||
&& (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN)
|
||||
){
|
||||
rc = sqlite3OsSync(pPager->fd, pPager->sync_flags);
|
||||
rc = sqlite3PagerSync(pPager);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = pager_end_transaction(pPager, zMaster[0]!='\0');
|
||||
@ -2912,24 +2921,61 @@ static int pagerRollbackWal(Pager *pPager){
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Update the value of the change-counter at offsets 24 and 92 in
|
||||
** the header and the sqlite version number at offset 96.
|
||||
**
|
||||
** This is an unconditional update. See also the pager_incr_changecounter()
|
||||
** routine which only updates the change-counter if the update is actually
|
||||
** needed, as determined by the pPager->changeCountDone state variable.
|
||||
*/
|
||||
static void pager_write_changecounter(PgHdr *pPg){
|
||||
u32 change_counter;
|
||||
|
||||
/* Increment the value just read and write it back to byte 24. */
|
||||
change_counter = sqlite3Get4byte((u8*)pPg->pPager->dbFileVers)+1;
|
||||
put32bits(((char*)pPg->pData)+24, change_counter);
|
||||
|
||||
/* Also store the SQLite version number in bytes 96..99 and in
|
||||
** bytes 92..95 store the change counter for which the version number
|
||||
** is valid. */
|
||||
put32bits(((char*)pPg->pData)+92, change_counter);
|
||||
put32bits(((char*)pPg->pData)+96, SQLITE_VERSION_NUMBER);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a wrapper around sqlite3WalFrames(). As well as logging
|
||||
** the contents of the list of pages headed by pList (connected by pDirty),
|
||||
** this function notifies any active backup processes that the pages have
|
||||
** changed.
|
||||
** changed.
|
||||
**
|
||||
** The list of pages passed into this routine is always sorted by page number.
|
||||
** Hence, if page 1 appears anywhere on the list, it will be the first page.
|
||||
*/
|
||||
static int pagerWalFrames(
|
||||
Pager *pPager, /* Pager object */
|
||||
PgHdr *pList, /* List of frames to log */
|
||||
Pgno nTruncate, /* Database size after this commit */
|
||||
int isCommit, /* True if this is a commit */
|
||||
int sync_flags /* Flags to pass to OsSync() (or 0) */
|
||||
int syncFlags /* Flags to pass to OsSync() (or 0) */
|
||||
){
|
||||
int rc; /* Return code */
|
||||
#if defined(SQLITE_DEBUG) || defined(SQLITE_CHECK_PAGES)
|
||||
PgHdr *p; /* For looping over pages */
|
||||
#endif
|
||||
|
||||
assert( pPager->pWal );
|
||||
#ifdef SQLITE_DEBUG
|
||||
/* Verify that the page list is in accending order */
|
||||
for(p=pList; p && p->pDirty; p=p->pDirty){
|
||||
assert( p->pgno < p->pDirty->pgno );
|
||||
}
|
||||
#endif
|
||||
|
||||
if( pList->pgno==1 ) pager_write_changecounter(pList);
|
||||
rc = sqlite3WalFrames(pPager->pWal,
|
||||
pPager->pageSize, pList, nTruncate, isCommit, sync_flags
|
||||
pPager->pageSize, pList, nTruncate, isCommit, syncFlags
|
||||
);
|
||||
if( rc==SQLITE_OK && pPager->pBackup ){
|
||||
PgHdr *p;
|
||||
@ -2939,9 +2985,8 @@ static int pagerWalFrames(
|
||||
}
|
||||
|
||||
#ifdef SQLITE_CHECK_PAGES
|
||||
{
|
||||
PgHdr *p;
|
||||
for(p=pList; p; p=p->pDirty) pager_set_pagehash(p);
|
||||
for(p=pList; p; p=p->pDirty){
|
||||
pager_set_pagehash(p);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -2971,12 +3016,13 @@ static int pagerBeginReadTransaction(Pager *pPager){
|
||||
sqlite3WalEndReadTransaction(pPager->pWal);
|
||||
|
||||
rc = sqlite3WalBeginReadTransaction(pPager->pWal, &changed);
|
||||
if( rc==SQLITE_OK && changed ){
|
||||
if( rc!=SQLITE_OK || changed ){
|
||||
pager_reset(pPager);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** This function is called as part of the transition from PAGER_OPEN
|
||||
@ -3033,7 +3079,7 @@ static int pagerPagecount(Pager *pPager, Pgno *pnPage){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
/*
|
||||
** Check if the *-wal file that corresponds to the database opened by pPager
|
||||
** exists if the database is not empy, or verify that the *-wal file does
|
||||
@ -3258,14 +3304,49 @@ void sqlite3PagerSetCachesize(Pager *pPager, int mxPage){
|
||||
** assurance that the journal will not be corrupted to the
|
||||
** point of causing damage to the database during rollback.
|
||||
**
|
||||
** The above is for a rollback-journal mode. For WAL mode, OFF continues
|
||||
** to mean that no syncs ever occur. NORMAL means that the WAL is synced
|
||||
** prior to the start of checkpoint and that the database file is synced
|
||||
** at the conclusion of the checkpoint if the entire content of the WAL
|
||||
** was written back into the database. But no sync operations occur for
|
||||
** an ordinary commit in NORMAL mode with WAL. FULL means that the WAL
|
||||
** file is synced following each commit operation, in addition to the
|
||||
** syncs associated with NORMAL.
|
||||
**
|
||||
** Do not confuse synchronous=FULL with SQLITE_SYNC_FULL. The
|
||||
** SQLITE_SYNC_FULL macro means to use the MacOSX-style full-fsync
|
||||
** using fcntl(F_FULLFSYNC). SQLITE_SYNC_NORMAL means to do an
|
||||
** ordinary fsync() call. There is no difference between SQLITE_SYNC_FULL
|
||||
** and SQLITE_SYNC_NORMAL on platforms other than MacOSX. But the
|
||||
** synchronous=FULL versus synchronous=NORMAL setting determines when
|
||||
** the xSync primitive is called and is relevant to all platforms.
|
||||
**
|
||||
** Numeric values associated with these states are OFF==1, NORMAL=2,
|
||||
** and FULL=3.
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_PAGER_PRAGMAS
|
||||
void sqlite3PagerSetSafetyLevel(Pager *pPager, int level, int bFullFsync){
|
||||
void sqlite3PagerSetSafetyLevel(
|
||||
Pager *pPager, /* The pager to set safety level for */
|
||||
int level, /* PRAGMA synchronous. 1=OFF, 2=NORMAL, 3=FULL */
|
||||
int bFullFsync, /* PRAGMA fullfsync */
|
||||
int bCkptFullFsync /* PRAGMA checkpoint_fullfsync */
|
||||
){
|
||||
assert( level>=1 && level<=3 );
|
||||
pPager->noSync = (level==1 || pPager->tempFile) ?1:0;
|
||||
pPager->fullSync = (level==3 && !pPager->tempFile) ?1:0;
|
||||
pPager->sync_flags = (bFullFsync?SQLITE_SYNC_FULL:SQLITE_SYNC_NORMAL);
|
||||
if( pPager->noSync ){
|
||||
pPager->syncFlags = 0;
|
||||
pPager->ckptSyncFlags = 0;
|
||||
}else if( bFullFsync ){
|
||||
pPager->syncFlags = SQLITE_SYNC_FULL;
|
||||
pPager->ckptSyncFlags = SQLITE_SYNC_FULL;
|
||||
}else if( bCkptFullFsync ){
|
||||
pPager->syncFlags = SQLITE_SYNC_NORMAL;
|
||||
pPager->ckptSyncFlags = SQLITE_SYNC_FULL;
|
||||
}else{
|
||||
pPager->syncFlags = SQLITE_SYNC_NORMAL;
|
||||
pPager->ckptSyncFlags = SQLITE_SYNC_NORMAL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -3444,9 +3525,8 @@ int sqlite3PagerMaxPageCount(Pager *pPager, int mxPage){
|
||||
if( mxPage>0 ){
|
||||
pPager->mxPgno = mxPage;
|
||||
}
|
||||
if( pPager->eState!=PAGER_OPEN && pPager->mxPgno<pPager->dbSize ){
|
||||
pPager->mxPgno = pPager->dbSize;
|
||||
}
|
||||
assert( pPager->eState!=PAGER_OPEN ); /* Called only by OP_MaxPgcnt */
|
||||
assert( pPager->mxPgno>=pPager->dbSize ); /* OP_MaxPgcnt enforces this */
|
||||
return pPager->mxPgno;
|
||||
}
|
||||
|
||||
@ -3651,10 +3731,7 @@ int sqlite3PagerClose(Pager *pPager){
|
||||
/* pPager->errCode = 0; */
|
||||
pPager->exclusiveMode = 0;
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
sqlite3WalClose(pPager->pWal,
|
||||
(pPager->noSync ? 0 : pPager->sync_flags),
|
||||
pPager->pageSize, pTmp
|
||||
);
|
||||
sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags, pPager->pageSize, pTmp);
|
||||
pPager->pWal = 0;
|
||||
#endif
|
||||
pager_reset(pPager);
|
||||
@ -3820,7 +3897,7 @@ static int syncJournal(Pager *pPager, int newHdr){
|
||||
if( pPager->fullSync && 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){
|
||||
PAGERTRACE(("SYNC journal of %d\n", PAGERID(pPager)));
|
||||
IOTRACE(("JSYNC %p\n", pPager))
|
||||
rc = sqlite3OsSync(pPager->jfd, pPager->sync_flags);
|
||||
rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}
|
||||
IOTRACE(("JHDR %p %lld\n", pPager, pPager->journalHdr));
|
||||
@ -3832,8 +3909,8 @@ static int syncJournal(Pager *pPager, int newHdr){
|
||||
if( 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){
|
||||
PAGERTRACE(("SYNC journal of %d\n", PAGERID(pPager)));
|
||||
IOTRACE(("JSYNC %p\n", pPager))
|
||||
rc = sqlite3OsSync(pPager->jfd, pPager->sync_flags|
|
||||
(pPager->sync_flags==SQLITE_SYNC_FULL?SQLITE_SYNC_DATAONLY:0)
|
||||
rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags|
|
||||
(pPager->syncFlags==SQLITE_SYNC_FULL?SQLITE_SYNC_DATAONLY:0)
|
||||
);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}
|
||||
@ -3934,6 +4011,7 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){
|
||||
char *pData; /* Data to write */
|
||||
|
||||
assert( (pList->flags&PGHDR_NEED_SYNC)==0 );
|
||||
if( pList->pgno==1 ) pager_write_changecounter(pList);
|
||||
|
||||
/* Encode the database */
|
||||
CODEC2(pPager, pList->pData, pgno, 6, return SQLITE_NOMEM, pData);
|
||||
@ -4226,6 +4304,13 @@ int sqlite3PagerOpen(
|
||||
/* Set the output variable to NULL in case an error occurs. */
|
||||
*ppPager = 0;
|
||||
|
||||
#ifndef SQLITE_OMIT_MEMORYDB
|
||||
if( flags & PAGER_MEMORY ){
|
||||
memDb = 1;
|
||||
zFilename = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Compute and store the full pathname in an allocated buffer pointed
|
||||
** to by zPathname, length nPathname. Or, if this is a temporary file,
|
||||
** leave both nPathname and zPathname set to 0.
|
||||
@ -4236,17 +4321,8 @@ int sqlite3PagerOpen(
|
||||
if( zPathname==0 ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
#ifndef SQLITE_OMIT_MEMORYDB
|
||||
if( strcmp(zFilename,":memory:")==0 ){
|
||||
memDb = 1;
|
||||
zPathname[0] = 0;
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
zPathname[0] = 0; /* Make sure initialized even if FullPathname() fails */
|
||||
rc = sqlite3OsFullPathname(pVfs, zFilename, nPathname, zPathname);
|
||||
}
|
||||
|
||||
zPathname[0] = 0; /* Make sure initialized even if FullPathname() fails */
|
||||
rc = sqlite3OsFullPathname(pVfs, zFilename, nPathname, zPathname);
|
||||
nPathname = sqlite3Strlen30(zPathname);
|
||||
if( rc==SQLITE_OK && nPathname+8>pVfs->mxPathname ){
|
||||
/* This branch is taken when the journal path required by
|
||||
@ -4301,19 +4377,15 @@ int sqlite3PagerOpen(
|
||||
|
||||
/* Fill in the Pager.zFilename and Pager.zJournal buffers, if required. */
|
||||
if( zPathname ){
|
||||
assert( nPathname>0 );
|
||||
pPager->zJournal = (char*)(pPtr += nPathname + 1);
|
||||
memcpy(pPager->zFilename, zPathname, nPathname);
|
||||
memcpy(pPager->zJournal, zPathname, nPathname);
|
||||
memcpy(&pPager->zJournal[nPathname], "-journal", 8);
|
||||
if( pPager->zFilename[0]==0 ){
|
||||
pPager->zJournal[0] = 0;
|
||||
}
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
else{
|
||||
pPager->zWal = &pPager->zJournal[nPathname+8+1];
|
||||
memcpy(pPager->zWal, zPathname, nPathname);
|
||||
memcpy(&pPager->zWal[nPathname], "-wal", 4);
|
||||
}
|
||||
pPager->zWal = &pPager->zJournal[nPathname+8+1];
|
||||
memcpy(pPager->zWal, zPathname, nPathname);
|
||||
memcpy(&pPager->zWal[nPathname], "-wal", 4);
|
||||
#endif
|
||||
sqlite3_free(zPathname);
|
||||
}
|
||||
@ -4322,9 +4394,10 @@ int sqlite3PagerOpen(
|
||||
|
||||
/* Open the pager file.
|
||||
*/
|
||||
if( zFilename && zFilename[0] && !memDb ){
|
||||
if( zFilename && zFilename[0] ){
|
||||
int fout = 0; /* VFS flags returned by xOpen() */
|
||||
rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout);
|
||||
assert( !memDb );
|
||||
readOnly = (fout&SQLITE_OPEN_READONLY);
|
||||
|
||||
/* If the file was successfully opened for read/write access,
|
||||
@ -4428,7 +4501,8 @@ int sqlite3PagerOpen(
|
||||
assert( useJournal || pPager->tempFile );
|
||||
pPager->noSync = pPager->tempFile;
|
||||
pPager->fullSync = pPager->noSync ?0:1;
|
||||
pPager->sync_flags = SQLITE_SYNC_NORMAL;
|
||||
pPager->syncFlags = pPager->noSync ? 0 : SQLITE_SYNC_NORMAL;
|
||||
pPager->ckptSyncFlags = pPager->syncFlags;
|
||||
/* pPager->pFirst = 0; */
|
||||
/* pPager->pFirstSynced = 0; */
|
||||
/* pPager->pLast = 0; */
|
||||
@ -4528,7 +4602,7 @@ static int hasHotJournal(Pager *pPager, int *pExists){
|
||||
sqlite3BeginBenignMalloc();
|
||||
if( pagerLockDb(pPager, RESERVED_LOCK)==SQLITE_OK ){
|
||||
sqlite3OsDelete(pVfs, pPager->zJournal, 0);
|
||||
pagerUnlockDb(pPager, SHARED_LOCK);
|
||||
if( !pPager->exclusiveMode ) pagerUnlockDb(pPager, SHARED_LOCK);
|
||||
}
|
||||
sqlite3EndBenignMalloc();
|
||||
}else{
|
||||
@ -4778,7 +4852,9 @@ int sqlite3PagerSharedLock(Pager *pPager){
|
||||
** mode. Otherwise, the following function call is a no-op.
|
||||
*/
|
||||
rc = pagerOpenWalIfPresent(pPager);
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
assert( pPager->pWal==0 || rc==SQLITE_OK );
|
||||
#endif
|
||||
}
|
||||
|
||||
if( pagerUseWal(pPager) ){
|
||||
@ -5207,29 +5283,29 @@ static int pager_write(PgHdr *pPg){
|
||||
|
||||
CHECK_PAGE(pPg);
|
||||
|
||||
/* The journal file needs to be opened. Higher level routines have already
|
||||
** obtained the necessary locks to begin the write-transaction, but the
|
||||
** rollback journal might not yet be open. Open it now if this is the case.
|
||||
**
|
||||
** This is done before calling sqlite3PcacheMakeDirty() on the page.
|
||||
** Otherwise, if it were done after calling sqlite3PcacheMakeDirty(), then
|
||||
** an error might occur and the pager would end up in WRITER_LOCKED state
|
||||
** with pages marked as dirty in the cache.
|
||||
*/
|
||||
if( pPager->eState==PAGER_WRITER_LOCKED ){
|
||||
rc = pager_open_journal(pPager);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}
|
||||
assert( pPager->eState>=PAGER_WRITER_CACHEMOD );
|
||||
assert( assert_pager_state(pPager) );
|
||||
|
||||
/* Mark the page as dirty. If the page has already been written
|
||||
** to the journal then we can return right away.
|
||||
*/
|
||||
sqlite3PcacheMakeDirty(pPg);
|
||||
if( pageInJournal(pPg) && !subjRequiresPage(pPg) ){
|
||||
assert( !pagerUseWal(pPager) );
|
||||
assert( pPager->eState>=PAGER_WRITER_CACHEMOD );
|
||||
}else{
|
||||
|
||||
/* If we get this far, it means that the page needs to be
|
||||
** written to the transaction journal or the checkpoint journal
|
||||
** or both.
|
||||
**
|
||||
** Higher level routines have already obtained the necessary locks
|
||||
** to begin the write-transaction, but the rollback journal might not
|
||||
** yet be open. Open it now if this is the case.
|
||||
*/
|
||||
if( pPager->eState==PAGER_WRITER_LOCKED ){
|
||||
rc = pager_open_journal(pPager);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}
|
||||
assert( pPager->eState>=PAGER_WRITER_CACHEMOD );
|
||||
assert( assert_pager_state(pPager) );
|
||||
|
||||
/* The transaction journal now exists and we have a RESERVED or an
|
||||
** EXCLUSIVE lock on the main database file. Write the current page to
|
||||
@ -5456,7 +5532,13 @@ void sqlite3PagerDontWrite(PgHdr *pPg){
|
||||
/*
|
||||
** This routine is called to increment the value of the database file
|
||||
** change-counter, stored as a 4-byte big-endian integer starting at
|
||||
** byte offset 24 of the pager file.
|
||||
** byte offset 24 of the pager file. The secondary change counter at
|
||||
** 92 is also updated, as is the SQLite version number at offset 96.
|
||||
**
|
||||
** But this only happens if the pPager->changeCountDone flag is false.
|
||||
** To avoid excess churning of page 1, the update only happens once.
|
||||
** See also the pager_write_changecounter() routine that does an
|
||||
** unconditional update of the change counters.
|
||||
**
|
||||
** If the isDirectMode flag is zero, then this is done by calling
|
||||
** sqlite3PagerWrite() on page 1, then modifying the contents of the
|
||||
@ -5497,7 +5579,6 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
|
||||
|
||||
if( !pPager->changeCountDone && pPager->dbSize>0 ){
|
||||
PgHdr *pPgHdr; /* Reference to page 1 */
|
||||
u32 change_counter; /* Initial value of change-counter field */
|
||||
|
||||
assert( !pPager->tempFile && isOpen(pPager->fd) );
|
||||
|
||||
@ -5515,16 +5596,8 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
/* Increment the value just read and write it back to byte 24. */
|
||||
change_counter = sqlite3Get4byte((u8*)pPager->dbFileVers);
|
||||
change_counter++;
|
||||
put32bits(((char*)pPgHdr->pData)+24, change_counter);
|
||||
|
||||
/* Also store the SQLite version number in bytes 96..99 and in
|
||||
** bytes 92..95 store the change counter for which the version number
|
||||
** is valid. */
|
||||
put32bits(((char*)pPgHdr->pData)+92, change_counter);
|
||||
put32bits(((char*)pPgHdr->pData)+96, SQLITE_VERSION_NUMBER);
|
||||
/* Actually do the update of the change counter */
|
||||
pager_write_changecounter(pPgHdr);
|
||||
|
||||
/* If running in direct mode, write the contents of page 1 to the file. */
|
||||
if( DIRECT_MODE ){
|
||||
@ -5549,19 +5622,20 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
|
||||
}
|
||||
|
||||
/*
|
||||
** Sync the pager file to disk. This is a no-op for in-memory files
|
||||
** Sync the database file to disk. This is a no-op for in-memory databases
|
||||
** or pages with the Pager.noSync flag set.
|
||||
**
|
||||
** If successful, or called on a pager for which it is a no-op, this
|
||||
** If successful, or if called on a pager for which it is a no-op, this
|
||||
** function returns SQLITE_OK. Otherwise, an IO error code is returned.
|
||||
*/
|
||||
int sqlite3PagerSync(Pager *pPager){
|
||||
int rc; /* Return code */
|
||||
assert( !MEMDB );
|
||||
if( pPager->noSync ){
|
||||
rc = SQLITE_OK;
|
||||
}else{
|
||||
rc = sqlite3OsSync(pPager->fd, pPager->sync_flags);
|
||||
int rc = SQLITE_OK;
|
||||
if( !pPager->noSync ){
|
||||
assert( !MEMDB );
|
||||
rc = sqlite3OsSync(pPager->fd, pPager->syncFlags);
|
||||
}else if( isOpen(pPager->fd) ){
|
||||
assert( !MEMDB );
|
||||
sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_SYNC_OMITTED, (void *)&rc);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@ -5650,7 +5724,7 @@ int sqlite3PagerCommitPhaseOne(
|
||||
PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache);
|
||||
if( pList ){
|
||||
rc = pagerWalFrames(pPager, pList, pPager->dbSize, 1,
|
||||
(pPager->fullSync ? pPager->sync_flags : 0)
|
||||
(pPager->fullSync ? pPager->syncFlags : 0)
|
||||
);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
@ -5780,8 +5854,8 @@ int sqlite3PagerCommitPhaseOne(
|
||||
}
|
||||
|
||||
/* Finally, sync the database file. */
|
||||
if( !pPager->noSync && !noSync ){
|
||||
rc = sqlite3OsSync(pPager->fd, pPager->sync_flags);
|
||||
if( !noSync ){
|
||||
rc = sqlite3PagerSync(pPager);
|
||||
}
|
||||
IOTRACE(("DBSYNC %p\n", pPager))
|
||||
}
|
||||
@ -5893,7 +5967,17 @@ int sqlite3PagerRollback(Pager *pPager){
|
||||
rc2 = pager_end_transaction(pPager, pPager->setMaster);
|
||||
if( rc==SQLITE_OK ) rc = rc2;
|
||||
}else if( !isOpen(pPager->jfd) || pPager->eState==PAGER_WRITER_LOCKED ){
|
||||
int eState = pPager->eState;
|
||||
rc = pager_end_transaction(pPager, 0);
|
||||
if( !MEMDB && eState>PAGER_WRITER_LOCKED ){
|
||||
/* This can happen using journal_mode=off. Move the pager to the error
|
||||
** state to indicate that the contents of the cache may not be trusted.
|
||||
** Any active readers will get SQLITE_ABORT.
|
||||
*/
|
||||
pPager->errCode = SQLITE_ABORT;
|
||||
pPager->eState = PAGER_ERROR;
|
||||
return rc;
|
||||
}
|
||||
}else{
|
||||
rc = pager_playback(pPager, 0);
|
||||
}
|
||||
@ -6352,7 +6436,8 @@ int sqlite3PagerLockingMode(Pager *pPager, int eMode){
|
||||
|| eMode==PAGER_LOCKINGMODE_EXCLUSIVE );
|
||||
assert( PAGER_LOCKINGMODE_QUERY<0 );
|
||||
assert( PAGER_LOCKINGMODE_NORMAL>=0 && PAGER_LOCKINGMODE_EXCLUSIVE>=0 );
|
||||
if( eMode>=0 && !pPager->tempFile ){
|
||||
assert( pPager->exclusiveMode || 0==sqlite3WalHeapMemory(pPager->pWal) );
|
||||
if( eMode>=0 && !pPager->tempFile && !sqlite3WalHeapMemory(pPager->pWal) ){
|
||||
pPager->exclusiveMode = (u8)eMode;
|
||||
}
|
||||
return (int)pPager->exclusiveMode;
|
||||
@ -6521,10 +6606,8 @@ int sqlite3PagerCheckpoint(Pager *pPager){
|
||||
int rc = SQLITE_OK;
|
||||
if( pPager->pWal ){
|
||||
u8 *zBuf = (u8 *)pPager->pTmpSpace;
|
||||
rc = sqlite3WalCheckpoint(pPager->pWal,
|
||||
(pPager->noSync ? 0 : pPager->sync_flags),
|
||||
pPager->pageSize, zBuf
|
||||
);
|
||||
rc = sqlite3WalCheckpoint(pPager->pWal, pPager->ckptSyncFlags,
|
||||
pPager->pageSize, zBuf);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@ -6539,9 +6622,61 @@ int sqlite3PagerWalCallback(Pager *pPager){
|
||||
*/
|
||||
int sqlite3PagerWalSupported(Pager *pPager){
|
||||
const sqlite3_io_methods *pMethods = pPager->fd->pMethods;
|
||||
return pMethods->iVersion>=2 && pMethods->xShmMap!=0;
|
||||
return pPager->exclusiveMode || (pMethods->iVersion>=2 && pMethods->xShmMap);
|
||||
}
|
||||
|
||||
/*
|
||||
** Attempt to take an exclusive lock on the database file. If a PENDING lock
|
||||
** is obtained instead, immediately release it.
|
||||
*/
|
||||
static int pagerExclusiveLock(Pager *pPager){
|
||||
int rc; /* Return code */
|
||||
|
||||
assert( pPager->eLock==SHARED_LOCK || pPager->eLock==EXCLUSIVE_LOCK );
|
||||
rc = pagerLockDb(pPager, EXCLUSIVE_LOCK);
|
||||
if( rc!=SQLITE_OK ){
|
||||
/* If the attempt to grab the pending lock failed, release the
|
||||
** exclusive lock that may have been obtained instead. */
|
||||
pagerUnlockDb(pPager, SHARED_LOCK);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Call sqlite3WalOpen() to open the WAL handle. If the pager is in
|
||||
** exclusive-locking mode when this function is called, take an EXCLUSIVE
|
||||
** lock on the database file and use heap-memory to store the wal-index
|
||||
** in. Otherwise, use the normal shared-memory.
|
||||
*/
|
||||
static int pagerOpenWal(Pager *pPager){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
assert( pPager->pWal==0 && pPager->tempFile==0 );
|
||||
assert( pPager->eLock==SHARED_LOCK || pPager->eLock==EXCLUSIVE_LOCK || pPager->noReadlock);
|
||||
|
||||
/* If the pager is already in exclusive-mode, the WAL module will use
|
||||
** heap-memory for the wal-index instead of the VFS shared-memory
|
||||
** implementation. Take the exclusive lock now, before opening the WAL
|
||||
** file, to make sure this is safe.
|
||||
*/
|
||||
if( pPager->exclusiveMode ){
|
||||
rc = pagerExclusiveLock(pPager);
|
||||
}
|
||||
|
||||
/* Open the connection to the log file. If this operation fails,
|
||||
** (e.g. due to malloc() failure), return an error code.
|
||||
*/
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3WalOpen(pPager->pVfs,
|
||||
pPager->fd, pPager->zWal, pPager->exclusiveMode, &pPager->pWal
|
||||
);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** The caller must be holding a SHARED lock on the database file to call
|
||||
** this function.
|
||||
@ -6575,11 +6710,7 @@ int sqlite3PagerOpenWal(
|
||||
/* Close any rollback journal previously open */
|
||||
sqlite3OsClose(pPager->jfd);
|
||||
|
||||
/* Open the connection to the log file. If this operation fails,
|
||||
** (e.g. due to malloc() failure), unlock the database file and
|
||||
** return an error code.
|
||||
*/
|
||||
rc = sqlite3WalOpen(pPager->pVfs, pPager->fd, pPager->zWal, &pPager->pWal);
|
||||
rc = pagerOpenWal(pPager);
|
||||
if( rc==SQLITE_OK ){
|
||||
pPager->journalMode = PAGER_JOURNALMODE_WAL;
|
||||
pPager->eState = PAGER_OPEN;
|
||||
@ -6618,8 +6749,7 @@ int sqlite3PagerCloseWal(Pager *pPager){
|
||||
);
|
||||
}
|
||||
if( rc==SQLITE_OK && logexists ){
|
||||
rc = sqlite3WalOpen(pPager->pVfs, pPager->fd,
|
||||
pPager->zWal, &pPager->pWal);
|
||||
rc = pagerOpenWal(pPager);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6627,17 +6757,11 @@ int sqlite3PagerCloseWal(Pager *pPager){
|
||||
** the database file, the log and log-summary files will be deleted.
|
||||
*/
|
||||
if( rc==SQLITE_OK && pPager->pWal ){
|
||||
rc = pagerLockDb(pPager, EXCLUSIVE_LOCK);
|
||||
rc = pagerExclusiveLock(pPager);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3WalClose(pPager->pWal,
|
||||
(pPager->noSync ? 0 : pPager->sync_flags),
|
||||
pPager->pageSize, (u8*)pPager->pTmpSpace
|
||||
);
|
||||
rc = sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags,
|
||||
pPager->pageSize, (u8*)pPager->pTmpSpace);
|
||||
pPager->pWal = 0;
|
||||
}else{
|
||||
/* If we cannot get an EXCLUSIVE lock, downgrade the PENDING lock
|
||||
** that we did get back to SHARED. */
|
||||
pagerUnlockDb(pPager, SQLITE_LOCK_SHARED);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
|
||||
@ -59,6 +59,7 @@ typedef struct PgHdr DbPage;
|
||||
*/
|
||||
#define PAGER_OMIT_JOURNAL 0x0001 /* Do not use a rollback journal */
|
||||
#define PAGER_NO_READLOCK 0x0002 /* Omit readlocks on readonly files */
|
||||
#define PAGER_MEMORY 0x0004 /* In-memory database */
|
||||
|
||||
/*
|
||||
** Valid values for the second argument to sqlite3PagerLockingMode().
|
||||
@ -102,7 +103,7 @@ void sqlite3PagerSetBusyhandler(Pager*, int(*)(void *), void *);
|
||||
int sqlite3PagerSetPagesize(Pager*, u32*, int);
|
||||
int sqlite3PagerMaxPageCount(Pager*, int);
|
||||
void sqlite3PagerSetCachesize(Pager*, int);
|
||||
void sqlite3PagerSetSafetyLevel(Pager*,int,int);
|
||||
void sqlite3PagerSetSafetyLevel(Pager*,int,int,int);
|
||||
int sqlite3PagerLockingMode(Pager *, int);
|
||||
int sqlite3PagerSetJournalMode(Pager *, int);
|
||||
int sqlite3PagerGetJournalMode(Pager*);
|
||||
|
||||
@ -142,12 +142,16 @@ static void pcacheUnpin(PgHdr *p){
|
||||
*/
|
||||
int sqlite3PcacheInitialize(void){
|
||||
if( sqlite3GlobalConfig.pcache.xInit==0 ){
|
||||
/* IMPLEMENTATION-OF: R-26801-64137 If the xInit() method is NULL, then the
|
||||
** built-in default page cache is used instead of the application defined
|
||||
** page cache. */
|
||||
sqlite3PCacheSetDefault();
|
||||
}
|
||||
return sqlite3GlobalConfig.pcache.xInit(sqlite3GlobalConfig.pcache.pArg);
|
||||
}
|
||||
void sqlite3PcacheShutdown(void){
|
||||
if( sqlite3GlobalConfig.pcache.xShutdown ){
|
||||
/* IMPLEMENTATION-OF: R-26000-56589 The xShutdown() method may be NULL. */
|
||||
sqlite3GlobalConfig.pcache.xShutdown(sqlite3GlobalConfig.pcache.pArg);
|
||||
}
|
||||
}
|
||||
|
||||
433
src/pcache1.c
433
src/pcache1.c
@ -22,24 +22,62 @@
|
||||
typedef struct PCache1 PCache1;
|
||||
typedef struct PgHdr1 PgHdr1;
|
||||
typedef struct PgFreeslot PgFreeslot;
|
||||
typedef struct PGroup PGroup;
|
||||
|
||||
/* Pointers to structures of this type are cast and returned as
|
||||
** opaque sqlite3_pcache* handles
|
||||
/* Each page cache (or PCache) belongs to a PGroup. A PGroup is a set
|
||||
** of one or more PCaches that are able to recycle each others unpinned
|
||||
** pages when they are under memory pressure. A PGroup is an instance of
|
||||
** the following object.
|
||||
**
|
||||
** This page cache implementation works in one of two modes:
|
||||
**
|
||||
** (1) Every PCache is the sole member of its own PGroup. There is
|
||||
** one PGroup per PCache.
|
||||
**
|
||||
** (2) There is a single global PGroup that all PCaches are a member
|
||||
** of.
|
||||
**
|
||||
** Mode 1 uses more memory (since PCache instances are not able to rob
|
||||
** unused pages from other PCaches) but it also operates without a mutex,
|
||||
** and is therefore often faster. Mode 2 requires a mutex in order to be
|
||||
** threadsafe, but is able recycle pages more efficient.
|
||||
**
|
||||
** For mode (1), PGroup.mutex is NULL. For mode (2) there is only a single
|
||||
** PGroup which is the pcache1.grp global variable and its mutex is
|
||||
** SQLITE_MUTEX_STATIC_LRU.
|
||||
*/
|
||||
struct PGroup {
|
||||
sqlite3_mutex *mutex; /* MUTEX_STATIC_LRU or NULL */
|
||||
int nMaxPage; /* Sum of nMax for purgeable caches */
|
||||
int nMinPage; /* Sum of nMin for purgeable caches */
|
||||
int mxPinned; /* nMaxpage + 10 - nMinPage */
|
||||
int nCurrentPage; /* Number of purgeable pages allocated */
|
||||
PgHdr1 *pLruHead, *pLruTail; /* LRU list of unpinned pages */
|
||||
};
|
||||
|
||||
/* Each page cache is an instance of the following object. Every
|
||||
** open database file (including each in-memory database and each
|
||||
** temporary or transient database) has a single page cache which
|
||||
** is an instance of this object.
|
||||
**
|
||||
** Pointers to structures of this type are cast and returned as
|
||||
** opaque sqlite3_pcache* handles.
|
||||
*/
|
||||
struct PCache1 {
|
||||
/* Cache configuration parameters. Page size (szPage) and the purgeable
|
||||
** flag (bPurgeable) are set when the cache is created. nMax may be
|
||||
** modified at any time by a call to the pcache1CacheSize() method.
|
||||
** The global mutex must be held when accessing nMax.
|
||||
** The PGroup mutex must be held when accessing nMax.
|
||||
*/
|
||||
PGroup *pGroup; /* PGroup this cache belongs to */
|
||||
int szPage; /* Size of allocated pages in bytes */
|
||||
int bPurgeable; /* True if cache is purgeable */
|
||||
unsigned int nMin; /* Minimum number of pages reserved */
|
||||
unsigned int nMax; /* Configured "cache_size" value */
|
||||
unsigned int n90pct; /* nMax*9/10 */
|
||||
|
||||
/* Hash table of all pages. The following variables may only be accessed
|
||||
** when the accessor is holding the global mutex (see pcache1EnterMutex()
|
||||
** and pcache1LeaveMutex()).
|
||||
** when the accessor is holding the PGroup mutex.
|
||||
*/
|
||||
unsigned int nRecyclable; /* Number of pages in the LRU list */
|
||||
unsigned int nPage; /* Total number of pages in apHash */
|
||||
@ -75,18 +113,27 @@ struct PgFreeslot {
|
||||
** Global data used by this cache.
|
||||
*/
|
||||
static SQLITE_WSD struct PCacheGlobal {
|
||||
sqlite3_mutex *mutex; /* static mutex MUTEX_STATIC_LRU */
|
||||
PGroup grp; /* The global PGroup for mode (2) */
|
||||
|
||||
int nMaxPage; /* Sum of nMaxPage for purgeable caches */
|
||||
int nMinPage; /* Sum of nMinPage for purgeable caches */
|
||||
int nCurrentPage; /* Number of purgeable pages allocated */
|
||||
PgHdr1 *pLruHead, *pLruTail; /* LRU list of unpinned pages */
|
||||
|
||||
/* Variables related to SQLITE_CONFIG_PAGECACHE settings. */
|
||||
int szSlot; /* Size of each free slot */
|
||||
void *pStart, *pEnd; /* Bounds of pagecache malloc range */
|
||||
PgFreeslot *pFree; /* Free page blocks */
|
||||
int isInit; /* True if initialized */
|
||||
/* Variables related to SQLITE_CONFIG_PAGECACHE settings. The
|
||||
** szSlot, nSlot, pStart, pEnd, nReserve, and isInit values are all
|
||||
** fixed at sqlite3_initialize() time and do not require mutex protection.
|
||||
** The nFreeSlot and pFree values do require mutex protection.
|
||||
*/
|
||||
int isInit; /* True if initialized */
|
||||
int szSlot; /* Size of each free slot */
|
||||
int nSlot; /* The number of pcache slots */
|
||||
int nReserve; /* Try to keep nFreeSlot above this */
|
||||
void *pStart, *pEnd; /* Bounds of pagecache malloc range */
|
||||
/* Above requires no mutex. Use mutex below for variable that follow. */
|
||||
sqlite3_mutex *mutex; /* Mutex for accessing the following: */
|
||||
int nFreeSlot; /* Number of unused pcache slots */
|
||||
PgFreeslot *pFree; /* Free page blocks */
|
||||
/* The following value requires a mutex to change. We skip the mutex on
|
||||
** reading because (1) most platforms read a 32-bit integer atomically and
|
||||
** (2) even if an incorrect value is read, no great harm is done since this
|
||||
** is really just an optimization. */
|
||||
int bUnderPressure; /* True if low on PAGECACHE memory */
|
||||
} pcache1_g;
|
||||
|
||||
/*
|
||||
@ -112,10 +159,10 @@ static SQLITE_WSD struct PCacheGlobal {
|
||||
#define PAGE_TO_PGHDR1(c, p) (PgHdr1*)(((char*)p) + c->szPage)
|
||||
|
||||
/*
|
||||
** Macros to enter and leave the global LRU mutex.
|
||||
** Macros to enter and leave the PCache LRU mutex.
|
||||
*/
|
||||
#define pcache1EnterMutex() sqlite3_mutex_enter(pcache1.mutex)
|
||||
#define pcache1LeaveMutex() sqlite3_mutex_leave(pcache1.mutex)
|
||||
#define pcache1EnterMutex(X) sqlite3_mutex_enter((X)->mutex)
|
||||
#define pcache1LeaveMutex(X) sqlite3_mutex_leave((X)->mutex)
|
||||
|
||||
/******************************************************************************/
|
||||
/******** Page Allocation/SQLITE_CONFIG_PCACHE Related Functions **************/
|
||||
@ -125,14 +172,20 @@ static SQLITE_WSD struct PCacheGlobal {
|
||||
** supplied to use for the page-cache by passing the SQLITE_CONFIG_PAGECACHE
|
||||
** verb to sqlite3_config(). Parameter pBuf points to an allocation large
|
||||
** enough to contain 'n' buffers of 'sz' bytes each.
|
||||
**
|
||||
** This routine is called from sqlite3_initialize() and so it is guaranteed
|
||||
** to be serialized already. There is no need for further mutexing.
|
||||
*/
|
||||
void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n){
|
||||
if( pcache1.isInit ){
|
||||
PgFreeslot *p;
|
||||
sz = ROUNDDOWN8(sz);
|
||||
pcache1.szSlot = sz;
|
||||
pcache1.nSlot = pcache1.nFreeSlot = n;
|
||||
pcache1.nReserve = n>90 ? 10 : (n/10 + 1);
|
||||
pcache1.pStart = pBuf;
|
||||
pcache1.pFree = 0;
|
||||
pcache1.bUnderPressure = 0;
|
||||
while( n-- ){
|
||||
p = (PgFreeslot*)pBuf;
|
||||
p->pNext = pcache1.pFree;
|
||||
@ -148,30 +201,36 @@ void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n){
|
||||
** configured using sqlite3_config(SQLITE_CONFIG_PAGECACHE) option. If no
|
||||
** such buffer exists or there is no space left in it, this function falls
|
||||
** back to sqlite3Malloc().
|
||||
**
|
||||
** Multiple threads can run this routine at the same time. Global variables
|
||||
** in pcache1 need to be protected via mutex.
|
||||
*/
|
||||
static void *pcache1Alloc(int nByte){
|
||||
void *p;
|
||||
assert( sqlite3_mutex_held(pcache1.mutex) );
|
||||
void *p = 0;
|
||||
assert( sqlite3_mutex_notheld(pcache1.grp.mutex) );
|
||||
sqlite3StatusSet(SQLITE_STATUS_PAGECACHE_SIZE, nByte);
|
||||
if( nByte<=pcache1.szSlot && pcache1.pFree ){
|
||||
assert( pcache1.isInit );
|
||||
if( nByte<=pcache1.szSlot ){
|
||||
sqlite3_mutex_enter(pcache1.mutex);
|
||||
p = (PgHdr1 *)pcache1.pFree;
|
||||
pcache1.pFree = pcache1.pFree->pNext;
|
||||
sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, 1);
|
||||
}else{
|
||||
|
||||
/* Allocate a new buffer using sqlite3Malloc. Before doing so, exit the
|
||||
** global pcache mutex and unlock the pager-cache object pCache. This is
|
||||
** so that if the attempt to allocate a new buffer causes the the
|
||||
** configured soft-heap-limit to be breached, it will be possible to
|
||||
** reclaim memory from this pager-cache.
|
||||
if( p ){
|
||||
pcache1.pFree = pcache1.pFree->pNext;
|
||||
pcache1.nFreeSlot--;
|
||||
pcache1.bUnderPressure = pcache1.nFreeSlot<pcache1.nReserve;
|
||||
assert( pcache1.nFreeSlot>=0 );
|
||||
sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, 1);
|
||||
}
|
||||
sqlite3_mutex_leave(pcache1.mutex);
|
||||
}
|
||||
if( p==0 ){
|
||||
/* Memory is not available in the SQLITE_CONFIG_PAGECACHE pool. Get
|
||||
** it from sqlite3Malloc instead.
|
||||
*/
|
||||
pcache1LeaveMutex();
|
||||
p = sqlite3Malloc(nByte);
|
||||
pcache1EnterMutex();
|
||||
if( p ){
|
||||
int sz = sqlite3MallocSize(p);
|
||||
sqlite3_mutex_enter(pcache1.mutex);
|
||||
sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz);
|
||||
sqlite3_mutex_leave(pcache1.mutex);
|
||||
}
|
||||
sqlite3MemdebugSetType(p, MEMTYPE_PCACHE);
|
||||
}
|
||||
@ -182,30 +241,35 @@ static void *pcache1Alloc(int nByte){
|
||||
** Free an allocated buffer obtained from pcache1Alloc().
|
||||
*/
|
||||
static void pcache1Free(void *p){
|
||||
assert( sqlite3_mutex_held(pcache1.mutex) );
|
||||
if( p==0 ) return;
|
||||
if( p>=pcache1.pStart && p<pcache1.pEnd ){
|
||||
PgFreeslot *pSlot;
|
||||
sqlite3_mutex_enter(pcache1.mutex);
|
||||
sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, -1);
|
||||
pSlot = (PgFreeslot*)p;
|
||||
pSlot->pNext = pcache1.pFree;
|
||||
pcache1.pFree = pSlot;
|
||||
pcache1.nFreeSlot++;
|
||||
pcache1.bUnderPressure = pcache1.nFreeSlot<pcache1.nReserve;
|
||||
assert( pcache1.nFreeSlot<=pcache1.nSlot );
|
||||
sqlite3_mutex_leave(pcache1.mutex);
|
||||
}else{
|
||||
int iSize;
|
||||
assert( sqlite3MemdebugHasType(p, MEMTYPE_PCACHE) );
|
||||
sqlite3MemdebugSetType(p, MEMTYPE_HEAP);
|
||||
iSize = sqlite3MallocSize(p);
|
||||
sqlite3_mutex_enter(pcache1.mutex);
|
||||
sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, -iSize);
|
||||
sqlite3_mutex_leave(pcache1.mutex);
|
||||
sqlite3_free(p);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
|
||||
/*
|
||||
** Return the size of a pache allocation
|
||||
** Return the size of a pcache allocation
|
||||
*/
|
||||
static int pcache1MemSize(void *p){
|
||||
assert( sqlite3_mutex_held(pcache1.mutex) );
|
||||
if( p>=pcache1.pStart && p<pcache1.pEnd ){
|
||||
return pcache1.szSlot;
|
||||
}else{
|
||||
@ -229,7 +293,7 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache){
|
||||
if( pPg ){
|
||||
p = PAGE_TO_PGHDR1(pCache, pPg);
|
||||
if( pCache->bPurgeable ){
|
||||
pcache1.nCurrentPage++;
|
||||
pCache->pGroup->nCurrentPage++;
|
||||
}
|
||||
}else{
|
||||
p = 0;
|
||||
@ -246,8 +310,9 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache){
|
||||
*/
|
||||
static void pcache1FreePage(PgHdr1 *p){
|
||||
if( ALWAYS(p) ){
|
||||
if( p->pCache->bPurgeable ){
|
||||
pcache1.nCurrentPage--;
|
||||
PCache1 *pCache = p->pCache;
|
||||
if( pCache->bPurgeable ){
|
||||
pCache->pGroup->nCurrentPage--;
|
||||
}
|
||||
pcache1Free(PGHDR1_TO_PAGE(p));
|
||||
}
|
||||
@ -259,20 +324,39 @@ static void pcache1FreePage(PgHdr1 *p){
|
||||
** exists, this function falls back to sqlite3Malloc().
|
||||
*/
|
||||
void *sqlite3PageMalloc(int sz){
|
||||
void *p;
|
||||
pcache1EnterMutex();
|
||||
p = pcache1Alloc(sz);
|
||||
pcache1LeaveMutex();
|
||||
return p;
|
||||
return pcache1Alloc(sz);
|
||||
}
|
||||
|
||||
/*
|
||||
** Free an allocated buffer obtained from sqlite3PageMalloc().
|
||||
*/
|
||||
void sqlite3PageFree(void *p){
|
||||
pcache1EnterMutex();
|
||||
pcache1Free(p);
|
||||
pcache1LeaveMutex();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Return true if it desirable to avoid allocating a new page cache
|
||||
** entry.
|
||||
**
|
||||
** If memory was allocated specifically to the page cache using
|
||||
** SQLITE_CONFIG_PAGECACHE but that memory has all been used, then
|
||||
** it is desirable to avoid allocating a new page cache entry because
|
||||
** presumably SQLITE_CONFIG_PAGECACHE was suppose to be sufficient
|
||||
** for all page cache needs and we should not need to spill the
|
||||
** allocation onto the heap.
|
||||
**
|
||||
** Or, the heap is used for all page cache memory put the heap is
|
||||
** under memory pressure, then again it is desirable to avoid
|
||||
** allocating a new page cache entry in order to avoid stressing
|
||||
** the heap even further.
|
||||
*/
|
||||
static int pcache1UnderMemoryPressure(PCache1 *pCache){
|
||||
if( pcache1.nSlot && pCache->szPage<=pcache1.szSlot ){
|
||||
return pcache1.bUnderPressure;
|
||||
}else{
|
||||
return sqlite3HeapNearlyFull();
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
@ -282,25 +366,25 @@ void sqlite3PageFree(void *p){
|
||||
** This function is used to resize the hash table used by the cache passed
|
||||
** as the first argument.
|
||||
**
|
||||
** The global mutex must be held when this function is called.
|
||||
** The PCache mutex must be held when this function is called.
|
||||
*/
|
||||
static int pcache1ResizeHash(PCache1 *p){
|
||||
PgHdr1 **apNew;
|
||||
unsigned int nNew;
|
||||
unsigned int i;
|
||||
|
||||
assert( sqlite3_mutex_held(pcache1.mutex) );
|
||||
assert( sqlite3_mutex_held(p->pGroup->mutex) );
|
||||
|
||||
nNew = p->nHash*2;
|
||||
if( nNew<256 ){
|
||||
nNew = 256;
|
||||
}
|
||||
|
||||
pcache1LeaveMutex();
|
||||
pcache1LeaveMutex(p->pGroup);
|
||||
if( p->nHash ){ sqlite3BeginBenignMalloc(); }
|
||||
apNew = (PgHdr1 **)sqlite3_malloc(sizeof(PgHdr1 *)*nNew);
|
||||
if( p->nHash ){ sqlite3EndBenignMalloc(); }
|
||||
pcache1EnterMutex();
|
||||
pcache1EnterMutex(p->pGroup);
|
||||
if( apNew ){
|
||||
memset(apNew, 0, sizeof(PgHdr1 *)*nNew);
|
||||
for(i=0; i<p->nHash; i++){
|
||||
@ -323,25 +407,33 @@ static int pcache1ResizeHash(PCache1 *p){
|
||||
|
||||
/*
|
||||
** This function is used internally to remove the page pPage from the
|
||||
** global LRU list, if is part of it. If pPage is not part of the global
|
||||
** PGroup LRU list, if is part of it. If pPage is not part of the PGroup
|
||||
** LRU list, then this function is a no-op.
|
||||
**
|
||||
** The global mutex must be held when this function is called.
|
||||
** The PGroup mutex must be held when this function is called.
|
||||
**
|
||||
** If pPage is NULL then this routine is a no-op.
|
||||
*/
|
||||
static void pcache1PinPage(PgHdr1 *pPage){
|
||||
assert( sqlite3_mutex_held(pcache1.mutex) );
|
||||
if( pPage && (pPage->pLruNext || pPage==pcache1.pLruTail) ){
|
||||
PCache1 *pCache;
|
||||
PGroup *pGroup;
|
||||
|
||||
if( pPage==0 ) return;
|
||||
pCache = pPage->pCache;
|
||||
pGroup = pCache->pGroup;
|
||||
assert( sqlite3_mutex_held(pGroup->mutex) );
|
||||
if( pPage->pLruNext || pPage==pGroup->pLruTail ){
|
||||
if( pPage->pLruPrev ){
|
||||
pPage->pLruPrev->pLruNext = pPage->pLruNext;
|
||||
}
|
||||
if( pPage->pLruNext ){
|
||||
pPage->pLruNext->pLruPrev = pPage->pLruPrev;
|
||||
}
|
||||
if( pcache1.pLruHead==pPage ){
|
||||
pcache1.pLruHead = pPage->pLruNext;
|
||||
if( pGroup->pLruHead==pPage ){
|
||||
pGroup->pLruHead = pPage->pLruNext;
|
||||
}
|
||||
if( pcache1.pLruTail==pPage ){
|
||||
pcache1.pLruTail = pPage->pLruPrev;
|
||||
if( pGroup->pLruTail==pPage ){
|
||||
pGroup->pLruTail = pPage->pLruPrev;
|
||||
}
|
||||
pPage->pLruNext = 0;
|
||||
pPage->pLruPrev = 0;
|
||||
@ -354,13 +446,14 @@ static void pcache1PinPage(PgHdr1 *pPage){
|
||||
** Remove the page supplied as an argument from the hash table
|
||||
** (PCache1.apHash structure) that it is currently stored in.
|
||||
**
|
||||
** The global mutex must be held when this function is called.
|
||||
** The PGroup mutex must be held when this function is called.
|
||||
*/
|
||||
static void pcache1RemoveFromHash(PgHdr1 *pPage){
|
||||
unsigned int h;
|
||||
PCache1 *pCache = pPage->pCache;
|
||||
PgHdr1 **pp;
|
||||
|
||||
assert( sqlite3_mutex_held(pCache->pGroup->mutex) );
|
||||
h = pPage->iKey % pCache->nHash;
|
||||
for(pp=&pCache->apHash[h]; (*pp)!=pPage; pp=&(*pp)->pNext);
|
||||
*pp = (*pp)->pNext;
|
||||
@ -369,13 +462,14 @@ static void pcache1RemoveFromHash(PgHdr1 *pPage){
|
||||
}
|
||||
|
||||
/*
|
||||
** If there are currently more than pcache.nMaxPage pages allocated, try
|
||||
** to recycle pages to reduce the number allocated to pcache.nMaxPage.
|
||||
** If there are currently more than nMaxPage pages allocated, try
|
||||
** to recycle pages to reduce the number allocated to nMaxPage.
|
||||
*/
|
||||
static void pcache1EnforceMaxPage(void){
|
||||
assert( sqlite3_mutex_held(pcache1.mutex) );
|
||||
while( pcache1.nCurrentPage>pcache1.nMaxPage && pcache1.pLruTail ){
|
||||
PgHdr1 *p = pcache1.pLruTail;
|
||||
static void pcache1EnforceMaxPage(PGroup *pGroup){
|
||||
assert( sqlite3_mutex_held(pGroup->mutex) );
|
||||
while( pGroup->nCurrentPage>pGroup->nMaxPage && pGroup->pLruTail ){
|
||||
PgHdr1 *p = pGroup->pLruTail;
|
||||
assert( p->pCache->pGroup==pGroup );
|
||||
pcache1PinPage(p);
|
||||
pcache1RemoveFromHash(p);
|
||||
pcache1FreePage(p);
|
||||
@ -387,15 +481,15 @@ static void pcache1EnforceMaxPage(void){
|
||||
** greater than or equal to iLimit. Any pinned pages that meet this
|
||||
** criteria are unpinned before they are discarded.
|
||||
**
|
||||
** The global mutex must be held when this function is called.
|
||||
** The PCache mutex must be held when this function is called.
|
||||
*/
|
||||
static void pcache1TruncateUnsafe(
|
||||
PCache1 *pCache,
|
||||
unsigned int iLimit
|
||||
PCache1 *pCache, /* The cache to truncate */
|
||||
unsigned int iLimit /* Drop pages with this pgno or larger */
|
||||
){
|
||||
TESTONLY( unsigned int nPage = 0; ) /* Used to assert pCache->nPage is correct */
|
||||
TESTONLY( unsigned int nPage = 0; ) /* To assert pCache->nPage is correct */
|
||||
unsigned int h;
|
||||
assert( sqlite3_mutex_held(pcache1.mutex) );
|
||||
assert( sqlite3_mutex_held(pCache->pGroup->mutex) );
|
||||
for(h=0; h<pCache->nHash; h++){
|
||||
PgHdr1 **pp = &pCache->apHash[h];
|
||||
PgHdr1 *pPage;
|
||||
@ -425,8 +519,10 @@ static int pcache1Init(void *NotUsed){
|
||||
assert( pcache1.isInit==0 );
|
||||
memset(&pcache1, 0, sizeof(pcache1));
|
||||
if( sqlite3GlobalConfig.bCoreMutex ){
|
||||
pcache1.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU);
|
||||
pcache1.grp.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU);
|
||||
pcache1.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_PMEM);
|
||||
}
|
||||
pcache1.grp.mxPinned = 10;
|
||||
pcache1.isInit = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@ -448,18 +544,47 @@ static void pcache1Shutdown(void *NotUsed){
|
||||
** Allocate a new cache.
|
||||
*/
|
||||
static sqlite3_pcache *pcache1Create(int szPage, int bPurgeable){
|
||||
PCache1 *pCache;
|
||||
PCache1 *pCache; /* The newly created page cache */
|
||||
PGroup *pGroup; /* The group the new page cache will belong to */
|
||||
int sz; /* Bytes of memory required to allocate the new cache */
|
||||
|
||||
pCache = (PCache1 *)sqlite3_malloc(sizeof(PCache1));
|
||||
/*
|
||||
** The seperateCache variable is true if each PCache has its own private
|
||||
** PGroup. In other words, separateCache is true for mode (1) where no
|
||||
** mutexing is required.
|
||||
**
|
||||
** * Always use a unified cache (mode-2) if ENABLE_MEMORY_MANAGEMENT
|
||||
**
|
||||
** * Always use a unified cache in single-threaded applications
|
||||
**
|
||||
** * Otherwise (if multi-threaded and ENABLE_MEMORY_MANAGEMENT is off)
|
||||
** use separate caches (mode-1)
|
||||
*/
|
||||
#if defined(SQLITE_ENABLE_MEMORY_MANAGEMENT) || SQLITE_THREADSAFE==0
|
||||
const int separateCache = 0;
|
||||
#else
|
||||
int separateCache = sqlite3GlobalConfig.bCoreMutex>0;
|
||||
#endif
|
||||
|
||||
sz = sizeof(PCache1) + sizeof(PGroup)*separateCache;
|
||||
pCache = (PCache1 *)sqlite3_malloc(sz);
|
||||
if( pCache ){
|
||||
memset(pCache, 0, sizeof(PCache1));
|
||||
memset(pCache, 0, sz);
|
||||
if( separateCache ){
|
||||
pGroup = (PGroup*)&pCache[1];
|
||||
pGroup->mxPinned = 10;
|
||||
}else{
|
||||
pGroup = &pcache1_g.grp;
|
||||
}
|
||||
pCache->pGroup = pGroup;
|
||||
pCache->szPage = szPage;
|
||||
pCache->bPurgeable = (bPurgeable ? 1 : 0);
|
||||
if( bPurgeable ){
|
||||
pCache->nMin = 10;
|
||||
pcache1EnterMutex();
|
||||
pcache1.nMinPage += pCache->nMin;
|
||||
pcache1LeaveMutex();
|
||||
pcache1EnterMutex(pGroup);
|
||||
pGroup->nMinPage += pCache->nMin;
|
||||
pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage;
|
||||
pcache1LeaveMutex(pGroup);
|
||||
}
|
||||
}
|
||||
return (sqlite3_pcache *)pCache;
|
||||
@ -473,11 +598,14 @@ static sqlite3_pcache *pcache1Create(int szPage, int bPurgeable){
|
||||
static void pcache1Cachesize(sqlite3_pcache *p, int nMax){
|
||||
PCache1 *pCache = (PCache1 *)p;
|
||||
if( pCache->bPurgeable ){
|
||||
pcache1EnterMutex();
|
||||
pcache1.nMaxPage += (nMax - pCache->nMax);
|
||||
PGroup *pGroup = pCache->pGroup;
|
||||
pcache1EnterMutex(pGroup);
|
||||
pGroup->nMaxPage += (nMax - pCache->nMax);
|
||||
pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage;
|
||||
pCache->nMax = nMax;
|
||||
pcache1EnforceMaxPage();
|
||||
pcache1LeaveMutex();
|
||||
pCache->n90pct = pCache->nMax*9/10;
|
||||
pcache1EnforceMaxPage(pGroup);
|
||||
pcache1LeaveMutex(pGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@ -486,9 +614,10 @@ static void pcache1Cachesize(sqlite3_pcache *p, int nMax){
|
||||
*/
|
||||
static int pcache1Pagecount(sqlite3_pcache *p){
|
||||
int n;
|
||||
pcache1EnterMutex();
|
||||
n = ((PCache1 *)p)->nPage;
|
||||
pcache1LeaveMutex();
|
||||
PCache1 *pCache = (PCache1*)p;
|
||||
pcache1EnterMutex(pCache->pGroup);
|
||||
n = pCache->nPage;
|
||||
pcache1LeaveMutex(pCache->pGroup);
|
||||
return n;
|
||||
}
|
||||
|
||||
@ -516,14 +645,16 @@ static int pcache1Pagecount(sqlite3_pcache *p){
|
||||
** 2. If createFlag==0 and the page is not already in the cache, NULL is
|
||||
** returned.
|
||||
**
|
||||
** 3. If createFlag is 1, and the page is not already in the cache,
|
||||
** and if either of the following are true, return NULL:
|
||||
** 3. If createFlag is 1, and the page is not already in the cache, then
|
||||
** return NULL (do not allocate a new page) if any of the following
|
||||
** conditions are true:
|
||||
**
|
||||
** (a) the number of pages pinned by the cache is greater than
|
||||
** PCache1.nMax, or
|
||||
**
|
||||
** (b) the number of pages pinned by the cache is greater than
|
||||
** the sum of nMax for all purgeable caches, less the sum of
|
||||
** nMin for all other purgeable caches.
|
||||
** nMin for all other purgeable caches, or
|
||||
**
|
||||
** 4. If none of the first three conditions apply and the cache is marked
|
||||
** as purgeable, and if one of the following is true:
|
||||
@ -535,6 +666,9 @@ static int pcache1Pagecount(sqlite3_pcache *p){
|
||||
** already equal to or greater than the sum of nMax for all
|
||||
** purgeable caches,
|
||||
**
|
||||
** (c) The system is under memory pressure and wants to avoid
|
||||
** unnecessary pages cache entry allocations
|
||||
**
|
||||
** then attempt to recycle a page from the LRU list. If it is the right
|
||||
** size, return the recycled buffer. Otherwise, free the buffer and
|
||||
** proceed to step 5.
|
||||
@ -542,30 +676,50 @@ static int pcache1Pagecount(sqlite3_pcache *p){
|
||||
** 5. Otherwise, allocate and return a new page buffer.
|
||||
*/
|
||||
static void *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag){
|
||||
unsigned int nPinned;
|
||||
int nPinned;
|
||||
PCache1 *pCache = (PCache1 *)p;
|
||||
PGroup *pGroup;
|
||||
PgHdr1 *pPage = 0;
|
||||
|
||||
assert( pCache->bPurgeable || createFlag!=1 );
|
||||
pcache1EnterMutex();
|
||||
if( createFlag==1 ) sqlite3BeginBenignMalloc();
|
||||
assert( pCache->bPurgeable || pCache->nMin==0 );
|
||||
assert( pCache->bPurgeable==0 || pCache->nMin==10 );
|
||||
assert( pCache->nMin==0 || pCache->bPurgeable );
|
||||
pcache1EnterMutex(pGroup = pCache->pGroup);
|
||||
|
||||
/* Search the hash table for an existing entry. */
|
||||
/* Step 1: Search the hash table for an existing entry. */
|
||||
if( pCache->nHash>0 ){
|
||||
unsigned int h = iKey % pCache->nHash;
|
||||
for(pPage=pCache->apHash[h]; pPage&&pPage->iKey!=iKey; pPage=pPage->pNext);
|
||||
}
|
||||
|
||||
/* Step 2: Abort if no existing page is found and createFlag is 0 */
|
||||
if( pPage || createFlag==0 ){
|
||||
pcache1PinPage(pPage);
|
||||
goto fetch_out;
|
||||
}
|
||||
|
||||
/* Step 3 of header comment. */
|
||||
/* The pGroup local variable will normally be initialized by the
|
||||
** pcache1EnterMutex() macro above. But if SQLITE_MUTEX_OMIT is defined,
|
||||
** then pcache1EnterMutex() is a no-op, so we have to initialize the
|
||||
** local variable here. Delaying the initialization of pGroup is an
|
||||
** optimization: The common case is to exit the module before reaching
|
||||
** this point.
|
||||
*/
|
||||
#ifdef SQLITE_MUTEX_OMIT
|
||||
pGroup = pCache->pGroup;
|
||||
#endif
|
||||
|
||||
|
||||
/* Step 3: Abort if createFlag is 1 but the cache is nearly full */
|
||||
nPinned = pCache->nPage - pCache->nRecyclable;
|
||||
assert( nPinned>=0 );
|
||||
assert( pGroup->mxPinned == pGroup->nMaxPage + 10 - pGroup->nMinPage );
|
||||
assert( pCache->n90pct == pCache->nMax*9/10 );
|
||||
if( createFlag==1 && (
|
||||
nPinned>=(pcache1.nMaxPage+pCache->nMin-pcache1.nMinPage)
|
||||
|| nPinned>=(pCache->nMax * 9 / 10)
|
||||
nPinned>=pGroup->mxPinned
|
||||
|| nPinned>=(int)pCache->n90pct
|
||||
|| pcache1UnderMemoryPressure(pCache)
|
||||
)){
|
||||
goto fetch_out;
|
||||
}
|
||||
@ -574,18 +728,22 @@ static void *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag){
|
||||
goto fetch_out;
|
||||
}
|
||||
|
||||
/* Step 4. Try to recycle a page buffer if appropriate. */
|
||||
if( pCache->bPurgeable && pcache1.pLruTail && (
|
||||
(pCache->nPage+1>=pCache->nMax) || pcache1.nCurrentPage>=pcache1.nMaxPage
|
||||
/* Step 4. Try to recycle a page. */
|
||||
if( pCache->bPurgeable && pGroup->pLruTail && (
|
||||
(pCache->nPage+1>=pCache->nMax)
|
||||
|| pGroup->nCurrentPage>=pGroup->nMaxPage
|
||||
|| pcache1UnderMemoryPressure(pCache)
|
||||
)){
|
||||
pPage = pcache1.pLruTail;
|
||||
PCache1 *pOtherCache;
|
||||
pPage = pGroup->pLruTail;
|
||||
pcache1RemoveFromHash(pPage);
|
||||
pcache1PinPage(pPage);
|
||||
if( pPage->pCache->szPage!=pCache->szPage ){
|
||||
if( (pOtherCache = pPage->pCache)->szPage!=pCache->szPage ){
|
||||
pcache1FreePage(pPage);
|
||||
pPage = 0;
|
||||
}else{
|
||||
pcache1.nCurrentPage -= (pPage->pCache->bPurgeable - pCache->bPurgeable);
|
||||
pGroup->nCurrentPage -=
|
||||
(pOtherCache->bPurgeable - pCache->bPurgeable);
|
||||
}
|
||||
}
|
||||
|
||||
@ -593,7 +751,11 @@ static void *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag){
|
||||
** attempt to allocate a new one.
|
||||
*/
|
||||
if( !pPage ){
|
||||
if( createFlag==1 ) sqlite3BeginBenignMalloc();
|
||||
pcache1LeaveMutex(pGroup);
|
||||
pPage = pcache1AllocPage(pCache);
|
||||
pcache1EnterMutex(pGroup);
|
||||
if( createFlag==1 ) sqlite3EndBenignMalloc();
|
||||
}
|
||||
|
||||
if( pPage ){
|
||||
@ -612,8 +774,7 @@ fetch_out:
|
||||
if( pPage && iKey>pCache->iMaxKey ){
|
||||
pCache->iMaxKey = iKey;
|
||||
}
|
||||
if( createFlag==1 ) sqlite3EndBenignMalloc();
|
||||
pcache1LeaveMutex();
|
||||
pcache1LeaveMutex(pGroup);
|
||||
return (pPage ? PGHDR1_TO_PAGE(pPage) : 0);
|
||||
}
|
||||
|
||||
@ -626,37 +787,34 @@ fetch_out:
|
||||
static void pcache1Unpin(sqlite3_pcache *p, void *pPg, int reuseUnlikely){
|
||||
PCache1 *pCache = (PCache1 *)p;
|
||||
PgHdr1 *pPage = PAGE_TO_PGHDR1(pCache, pPg);
|
||||
PGroup *pGroup = pCache->pGroup;
|
||||
|
||||
assert( pPage->pCache==pCache );
|
||||
pcache1EnterMutex();
|
||||
pcache1EnterMutex(pGroup);
|
||||
|
||||
/* It is an error to call this function if the page is already
|
||||
** part of the global LRU list.
|
||||
** part of the PGroup LRU list.
|
||||
*/
|
||||
assert( pPage->pLruPrev==0 && pPage->pLruNext==0 );
|
||||
assert( pcache1.pLruHead!=pPage && pcache1.pLruTail!=pPage );
|
||||
assert( pGroup->pLruHead!=pPage && pGroup->pLruTail!=pPage );
|
||||
|
||||
if( reuseUnlikely || pcache1.nCurrentPage>pcache1.nMaxPage ){
|
||||
if( reuseUnlikely || pGroup->nCurrentPage>pGroup->nMaxPage ){
|
||||
pcache1RemoveFromHash(pPage);
|
||||
pcache1FreePage(pPage);
|
||||
}else{
|
||||
/* Add the page to the global LRU list. Normally, the page is added to
|
||||
** the head of the list (last page to be recycled). However, if the
|
||||
** reuseUnlikely flag passed to this function is true, the page is added
|
||||
** to the tail of the list (first page to be recycled).
|
||||
*/
|
||||
if( pcache1.pLruHead ){
|
||||
pcache1.pLruHead->pLruPrev = pPage;
|
||||
pPage->pLruNext = pcache1.pLruHead;
|
||||
pcache1.pLruHead = pPage;
|
||||
/* Add the page to the PGroup LRU list. */
|
||||
if( pGroup->pLruHead ){
|
||||
pGroup->pLruHead->pLruPrev = pPage;
|
||||
pPage->pLruNext = pGroup->pLruHead;
|
||||
pGroup->pLruHead = pPage;
|
||||
}else{
|
||||
pcache1.pLruTail = pPage;
|
||||
pcache1.pLruHead = pPage;
|
||||
pGroup->pLruTail = pPage;
|
||||
pGroup->pLruHead = pPage;
|
||||
}
|
||||
pCache->nRecyclable++;
|
||||
}
|
||||
|
||||
pcache1LeaveMutex();
|
||||
pcache1LeaveMutex(pCache->pGroup);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -675,7 +833,7 @@ static void pcache1Rekey(
|
||||
assert( pPage->iKey==iOld );
|
||||
assert( pPage->pCache==pCache );
|
||||
|
||||
pcache1EnterMutex();
|
||||
pcache1EnterMutex(pCache->pGroup);
|
||||
|
||||
h = iOld%pCache->nHash;
|
||||
pp = &pCache->apHash[h];
|
||||
@ -692,7 +850,7 @@ static void pcache1Rekey(
|
||||
pCache->iMaxKey = iNew;
|
||||
}
|
||||
|
||||
pcache1LeaveMutex();
|
||||
pcache1LeaveMutex(pCache->pGroup);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -704,12 +862,12 @@ static void pcache1Rekey(
|
||||
*/
|
||||
static void pcache1Truncate(sqlite3_pcache *p, unsigned int iLimit){
|
||||
PCache1 *pCache = (PCache1 *)p;
|
||||
pcache1EnterMutex();
|
||||
pcache1EnterMutex(pCache->pGroup);
|
||||
if( iLimit<=pCache->iMaxKey ){
|
||||
pcache1TruncateUnsafe(pCache, iLimit);
|
||||
pCache->iMaxKey = iLimit-1;
|
||||
}
|
||||
pcache1LeaveMutex();
|
||||
pcache1LeaveMutex(pCache->pGroup);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -719,12 +877,15 @@ static void pcache1Truncate(sqlite3_pcache *p, unsigned int iLimit){
|
||||
*/
|
||||
static void pcache1Destroy(sqlite3_pcache *p){
|
||||
PCache1 *pCache = (PCache1 *)p;
|
||||
pcache1EnterMutex();
|
||||
PGroup *pGroup = pCache->pGroup;
|
||||
assert( pCache->bPurgeable || (pCache->nMax==0 && pCache->nMin==0) );
|
||||
pcache1EnterMutex(pGroup);
|
||||
pcache1TruncateUnsafe(pCache, 0);
|
||||
pcache1.nMaxPage -= pCache->nMax;
|
||||
pcache1.nMinPage -= pCache->nMin;
|
||||
pcache1EnforceMaxPage();
|
||||
pcache1LeaveMutex();
|
||||
pGroup->nMaxPage -= pCache->nMax;
|
||||
pGroup->nMinPage -= pCache->nMin;
|
||||
pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage;
|
||||
pcache1EnforceMaxPage(pGroup);
|
||||
pcache1LeaveMutex(pGroup);
|
||||
sqlite3_free(pCache->apHash);
|
||||
sqlite3_free(pCache);
|
||||
}
|
||||
@ -763,16 +924,18 @@ void sqlite3PCacheSetDefault(void){
|
||||
*/
|
||||
int sqlite3PcacheReleaseMemory(int nReq){
|
||||
int nFree = 0;
|
||||
assert( sqlite3_mutex_notheld(pcache1.grp.mutex) );
|
||||
assert( sqlite3_mutex_notheld(pcache1.mutex) );
|
||||
if( pcache1.pStart==0 ){
|
||||
PgHdr1 *p;
|
||||
pcache1EnterMutex();
|
||||
while( (nReq<0 || nFree<nReq) && (p=pcache1.pLruTail) ){
|
||||
pcache1EnterMutex(&pcache1.grp);
|
||||
while( (nReq<0 || nFree<nReq) && ((p=pcache1.grp.pLruTail)!=0) ){
|
||||
nFree += pcache1MemSize(PGHDR1_TO_PAGE(p));
|
||||
pcache1PinPage(p);
|
||||
pcache1RemoveFromHash(p);
|
||||
pcache1FreePage(p);
|
||||
}
|
||||
pcache1LeaveMutex();
|
||||
pcache1LeaveMutex(&pcache1.grp);
|
||||
}
|
||||
return nFree;
|
||||
}
|
||||
@ -791,12 +954,12 @@ void sqlite3PcacheStats(
|
||||
){
|
||||
PgHdr1 *p;
|
||||
int nRecyclable = 0;
|
||||
for(p=pcache1.pLruHead; p; p=p->pLruNext){
|
||||
for(p=pcache1.grp.pLruHead; p; p=p->pLruNext){
|
||||
nRecyclable++;
|
||||
}
|
||||
*pnCurrent = pcache1.nCurrentPage;
|
||||
*pnMax = pcache1.nMaxPage;
|
||||
*pnMin = pcache1.nMinPage;
|
||||
*pnCurrent = pcache1.grp.nCurrentPage;
|
||||
*pnMax = pcache1.grp.nMaxPage;
|
||||
*pnMin = pcache1.grp.nMinPage;
|
||||
*pnRecyclable = nRecyclable;
|
||||
}
|
||||
#endif
|
||||
|
||||
65
src/pragma.c
65
src/pragma.c
@ -35,7 +35,7 @@ static u8 getSafetyLevel(const char *z){
|
||||
static const u8 iValue[] = {1, 0, 0, 0, 1, 1, 2};
|
||||
int i, n;
|
||||
if( sqlite3Isdigit(*z) ){
|
||||
return (u8)atoi(z);
|
||||
return (u8)sqlite3Atoi(z);
|
||||
}
|
||||
n = sqlite3Strlen30(z);
|
||||
for(i=0; i<ArraySize(iLength); i++){
|
||||
@ -76,7 +76,7 @@ static int getAutoVacuum(const char *z){
|
||||
if( 0==sqlite3StrICmp(z, "none") ) return BTREE_AUTOVACUUM_NONE;
|
||||
if( 0==sqlite3StrICmp(z, "full") ) return BTREE_AUTOVACUUM_FULL;
|
||||
if( 0==sqlite3StrICmp(z, "incremental") ) return BTREE_AUTOVACUUM_INCR;
|
||||
i = atoi(z);
|
||||
i = sqlite3Atoi(z);
|
||||
return (u8)((i>=0&&i<=2)?i:0);
|
||||
}
|
||||
#endif /* ifndef SQLITE_OMIT_AUTOVACUUM */
|
||||
@ -172,6 +172,7 @@ static int flagPragma(Parse *pParse, const char *zLeft, const char *zRight){
|
||||
{ "empty_result_callbacks", SQLITE_NullCallback },
|
||||
{ "legacy_file_format", SQLITE_LegacyFileFmt },
|
||||
{ "fullfsync", SQLITE_FullFSync },
|
||||
{ "checkpoint_fullfsync", SQLITE_CkptFullFSync },
|
||||
{ "reverse_unordered_selects", SQLITE_ReverseOrder },
|
||||
#ifndef SQLITE_OMIT_AUTOMATIC_INDEX
|
||||
{ "automatic_index", SQLITE_AutoIndex },
|
||||
@ -383,7 +384,7 @@ void sqlite3Pragma(
|
||||
sqlite3VdbeChangeP1(v, addr+1, iDb);
|
||||
sqlite3VdbeChangeP1(v, addr+6, SQLITE_DEFAULT_CACHE_SIZE);
|
||||
}else{
|
||||
int size = atoi(zRight);
|
||||
int size = sqlite3Atoi(zRight);
|
||||
if( size<0 ) size = -size;
|
||||
sqlite3BeginWriteOperation(pParse, 0, iDb);
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, size, 1);
|
||||
@ -412,35 +413,13 @@ void sqlite3Pragma(
|
||||
/* Malloc may fail when setting the page-size, as there is an internal
|
||||
** buffer that the pager module resizes using sqlite3_realloc().
|
||||
*/
|
||||
db->nextPagesize = atoi(zRight);
|
||||
db->nextPagesize = sqlite3Atoi(zRight);
|
||||
if( SQLITE_NOMEM==sqlite3BtreeSetPageSize(pBt, db->nextPagesize, -1, 0) ){
|
||||
db->mallocFailed = 1;
|
||||
}
|
||||
}
|
||||
}else
|
||||
|
||||
/*
|
||||
** PRAGMA [database.]max_page_count
|
||||
** PRAGMA [database.]max_page_count=N
|
||||
**
|
||||
** The first form reports the current setting for the
|
||||
** maximum number of pages in the database file. The
|
||||
** second form attempts to change this setting. Both
|
||||
** forms return the current setting.
|
||||
*/
|
||||
if( sqlite3StrICmp(zLeft,"max_page_count")==0 ){
|
||||
Btree *pBt = pDb->pBt;
|
||||
int newMax = 0;
|
||||
assert( pBt!=0 );
|
||||
if( zRight ){
|
||||
newMax = atoi(zRight);
|
||||
}
|
||||
if( ALWAYS(pBt) ){
|
||||
newMax = sqlite3BtreeMaxPageCount(pBt, newMax);
|
||||
}
|
||||
returnSingleInt(pParse, "max_page_count", newMax);
|
||||
}else
|
||||
|
||||
/*
|
||||
** PRAGMA [database.]secure_delete
|
||||
** PRAGMA [database.]secure_delete=ON/OFF
|
||||
@ -467,19 +446,33 @@ void sqlite3Pragma(
|
||||
}else
|
||||
|
||||
/*
|
||||
** PRAGMA [database.]max_page_count
|
||||
** PRAGMA [database.]max_page_count=N
|
||||
**
|
||||
** The first form reports the current setting for the
|
||||
** maximum number of pages in the database file. The
|
||||
** second form attempts to change this setting. Both
|
||||
** forms return the current setting.
|
||||
**
|
||||
** PRAGMA [database.]page_count
|
||||
**
|
||||
** Return the number of pages in the specified database.
|
||||
*/
|
||||
if( sqlite3StrICmp(zLeft,"page_count")==0 ){
|
||||
if( sqlite3StrICmp(zLeft,"page_count")==0
|
||||
|| sqlite3StrICmp(zLeft,"max_page_count")==0
|
||||
){
|
||||
int iReg;
|
||||
if( sqlite3ReadSchema(pParse) ) goto pragma_out;
|
||||
sqlite3CodeVerifySchema(pParse, iDb);
|
||||
iReg = ++pParse->nMem;
|
||||
sqlite3VdbeAddOp2(v, OP_Pagecount, iDb, iReg);
|
||||
if( zLeft[0]=='p' ){
|
||||
sqlite3VdbeAddOp2(v, OP_Pagecount, iDb, iReg);
|
||||
}else{
|
||||
sqlite3VdbeAddOp3(v, OP_MaxPgcnt, iDb, iReg, sqlite3Atoi(zRight));
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_ResultRow, iReg, 1);
|
||||
sqlite3VdbeSetNumCols(v, 1);
|
||||
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "page_count", SQLITE_STATIC);
|
||||
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLeft, SQLITE_TRANSIENT);
|
||||
}else
|
||||
|
||||
/*
|
||||
@ -587,7 +580,7 @@ void sqlite3Pragma(
|
||||
Pager *pPager = sqlite3BtreePager(pDb->pBt);
|
||||
i64 iLimit = -2;
|
||||
if( zRight ){
|
||||
sqlite3Atoi64(zRight, &iLimit);
|
||||
sqlite3Atoi64(zRight, &iLimit, 1000000, SQLITE_UTF8);
|
||||
if( iLimit<-1 ) iLimit = -1;
|
||||
}
|
||||
iLimit = sqlite3PagerJournalSizeLimit(pPager, iLimit);
|
||||
@ -701,7 +694,7 @@ void sqlite3Pragma(
|
||||
if( !zRight ){
|
||||
returnSingleInt(pParse, "cache_size", pDb->pSchema->cache_size);
|
||||
}else{
|
||||
int size = atoi(zRight);
|
||||
int size = sqlite3Atoi(zRight);
|
||||
if( size<0 ) size = -size;
|
||||
pDb->pSchema->cache_size = size;
|
||||
sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
|
||||
@ -1094,7 +1087,7 @@ void sqlite3Pragma(
|
||||
/* Set the maximum error count */
|
||||
mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX;
|
||||
if( zRight ){
|
||||
mxErr = atoi(zRight);
|
||||
sqlite3GetInt32(zRight, &mxErr);
|
||||
if( mxErr<=0 ){
|
||||
mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX;
|
||||
}
|
||||
@ -1351,7 +1344,7 @@ void sqlite3Pragma(
|
||||
};
|
||||
int addr = sqlite3VdbeAddOpList(v, ArraySize(setCookie), setCookie);
|
||||
sqlite3VdbeChangeP1(v, addr, iDb);
|
||||
sqlite3VdbeChangeP1(v, addr+1, atoi(zRight));
|
||||
sqlite3VdbeChangeP1(v, addr+1, sqlite3Atoi(zRight));
|
||||
sqlite3VdbeChangeP1(v, addr+2, iDb);
|
||||
sqlite3VdbeChangeP2(v, addr+2, iCookie);
|
||||
}else{
|
||||
@ -1412,8 +1405,7 @@ void sqlite3Pragma(
|
||||
*/
|
||||
if( sqlite3StrICmp(zLeft, "wal_autocheckpoint")==0 ){
|
||||
if( zRight ){
|
||||
int nAuto = atoi(zRight);
|
||||
sqlite3_wal_autocheckpoint(db, nAuto);
|
||||
sqlite3_wal_autocheckpoint(db, sqlite3Atoi(zRight));
|
||||
}
|
||||
returnSingleInt(pParse, "wal_autocheckpoint",
|
||||
db->xWalCallback==sqlite3WalDefaultHook ?
|
||||
@ -1533,7 +1525,8 @@ void sqlite3Pragma(
|
||||
#ifndef SQLITE_OMIT_PAGER_PRAGMAS
|
||||
if( db->autoCommit ){
|
||||
sqlite3BtreeSetSafetyLevel(pDb->pBt, pDb->safety_level,
|
||||
(db->flags&SQLITE_FullFSync)!=0);
|
||||
(db->flags&SQLITE_FullFSync)!=0,
|
||||
(db->flags&SQLITE_CkptFullFSync)!=0);
|
||||
}
|
||||
#endif
|
||||
pragma_out:
|
||||
|
||||
@ -79,7 +79,7 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){
|
||||
|
||||
assert( db->init.busy );
|
||||
db->init.iDb = iDb;
|
||||
db->init.newTnum = atoi(argv[1]);
|
||||
db->init.newTnum = sqlite3Atoi(argv[1]);
|
||||
db->init.orphanTrigger = 0;
|
||||
TESTONLY(rcp = ) sqlite3_prepare(db, argv[2], -1, &pStmt, 0);
|
||||
rc = db->errCode;
|
||||
@ -628,13 +628,13 @@ static int sqlite3Prepare(
|
||||
if( rc==SQLITE_OK && pParse->pVdbe && pParse->explain ){
|
||||
static const char * const azColName[] = {
|
||||
"addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment",
|
||||
"order", "from", "detail"
|
||||
"selectid", "order", "from", "detail"
|
||||
};
|
||||
int iFirst, mx;
|
||||
if( pParse->explain==2 ){
|
||||
sqlite3VdbeSetNumCols(pParse->pVdbe, 3);
|
||||
sqlite3VdbeSetNumCols(pParse->pVdbe, 4);
|
||||
iFirst = 8;
|
||||
mx = 11;
|
||||
mx = 12;
|
||||
}else{
|
||||
sqlite3VdbeSetNumCols(pParse->pVdbe, 8);
|
||||
iFirst = 0;
|
||||
@ -784,7 +784,7 @@ int sqlite3_prepare_v2(
|
||||
*/
|
||||
static int sqlite3Prepare16(
|
||||
sqlite3 *db, /* Database handle. */
|
||||
const void *zSql, /* UTF-8 encoded SQL statement. */
|
||||
const void *zSql, /* UTF-16 encoded SQL statement. */
|
||||
int nBytes, /* Length of zSql in bytes. */
|
||||
int saveSqlFlag, /* True to save SQL text into the sqlite3_stmt */
|
||||
sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
|
||||
@ -834,7 +834,7 @@ static int sqlite3Prepare16(
|
||||
*/
|
||||
int sqlite3_prepare16(
|
||||
sqlite3 *db, /* Database handle. */
|
||||
const void *zSql, /* UTF-8 encoded SQL statement. */
|
||||
const void *zSql, /* UTF-16 encoded SQL statement. */
|
||||
int nBytes, /* Length of zSql in bytes. */
|
||||
sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
|
||||
const void **pzTail /* OUT: End of parsed string */
|
||||
@ -846,7 +846,7 @@ int sqlite3_prepare16(
|
||||
}
|
||||
int sqlite3_prepare16_v2(
|
||||
sqlite3 *db, /* Database handle. */
|
||||
const void *zSql, /* UTF-8 encoded SQL statement. */
|
||||
const void *zSql, /* UTF-16 encoded SQL statement. */
|
||||
int nBytes, /* Length of zSql in bytes. */
|
||||
sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
|
||||
const void **pzTail /* OUT: End of parsed string */
|
||||
|
||||
33
src/printf.c
33
src/printf.c
@ -763,6 +763,7 @@ void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N){
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
char *zOld = (p->zText==p->zBase ? 0 : p->zText);
|
||||
i64 szNew = p->nChar;
|
||||
szNew += N + 1;
|
||||
if( szNew > p->mxAlloc ){
|
||||
@ -773,13 +774,12 @@ void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N){
|
||||
p->nAlloc = (int)szNew;
|
||||
}
|
||||
if( p->useMalloc==1 ){
|
||||
zNew = sqlite3DbMallocRaw(p->db, p->nAlloc );
|
||||
zNew = sqlite3DbRealloc(p->db, zOld, p->nAlloc);
|
||||
}else{
|
||||
zNew = sqlite3_malloc(p->nAlloc);
|
||||
zNew = sqlite3_realloc(zOld, p->nAlloc);
|
||||
}
|
||||
if( zNew ){
|
||||
memcpy(zNew, p->zText, p->nChar);
|
||||
sqlite3StrAccumReset(p);
|
||||
if( zOld==0 ) memcpy(zNew, p->zText, p->nChar);
|
||||
p->zText = zNew;
|
||||
}else{
|
||||
p->mallocFailed = 1;
|
||||
@ -934,21 +934,28 @@ char *sqlite3_mprintf(const char *zFormat, ...){
|
||||
** current locale settings. This is important for SQLite because we
|
||||
** are not able to use a "," as the decimal point in place of "." as
|
||||
** specified by some locales.
|
||||
**
|
||||
** Oops: The first two arguments of sqlite3_snprintf() are backwards
|
||||
** from the snprintf() standard. Unfortunately, it is too late to change
|
||||
** this without breaking compatibility, so we just have to live with the
|
||||
** mistake.
|
||||
**
|
||||
** sqlite3_vsnprintf() is the varargs version.
|
||||
*/
|
||||
char *sqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_list ap){
|
||||
StrAccum acc;
|
||||
if( n<=0 ) return zBuf;
|
||||
sqlite3StrAccumInit(&acc, zBuf, n, 0);
|
||||
acc.useMalloc = 0;
|
||||
sqlite3VXPrintf(&acc, 0, zFormat, ap);
|
||||
return sqlite3StrAccumFinish(&acc);
|
||||
}
|
||||
char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){
|
||||
char *z;
|
||||
va_list ap;
|
||||
StrAccum acc;
|
||||
|
||||
if( n<=0 ){
|
||||
return zBuf;
|
||||
}
|
||||
sqlite3StrAccumInit(&acc, zBuf, n, 0);
|
||||
acc.useMalloc = 0;
|
||||
va_start(ap,zFormat);
|
||||
sqlite3VXPrintf(&acc, 0, zFormat, ap);
|
||||
z = sqlite3_vsnprintf(n, zBuf, zFormat, ap);
|
||||
va_end(ap);
|
||||
z = sqlite3StrAccumFinish(&acc);
|
||||
return z;
|
||||
}
|
||||
|
||||
|
||||
210
src/select.c
210
src/select.c
@ -442,7 +442,6 @@ static void pushOntoSorter(
|
||||
sqlite3VdbeAddOp1(v, OP_Last, pOrderBy->iECursor);
|
||||
sqlite3VdbeAddOp1(v, OP_Delete, pOrderBy->iECursor);
|
||||
sqlite3VdbeJumpHere(v, addr2);
|
||||
pSelect->iLimit = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -491,11 +490,13 @@ static void codeDistinct(
|
||||
sqlite3ReleaseTempReg(pParse, r1);
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_SUBQUERY
|
||||
/*
|
||||
** Generate an error message when a SELECT is used within a subexpression
|
||||
** (example: "a IN (SELECT * FROM table)") but it has more than 1 result
|
||||
** column. We do this in a subroutine because the error occurs in multiple
|
||||
** places.
|
||||
** column. We do this in a subroutine because the error used to occur
|
||||
** in multiple places. (The error only occurs in one place now, but we
|
||||
** retain the subroutine to minimize code disruption.)
|
||||
*/
|
||||
static int checkForMultiColumnSelectError(
|
||||
Parse *pParse, /* Parse context. */
|
||||
@ -511,6 +512,7 @@ static int checkForMultiColumnSelectError(
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** This routine generates the code for the inside of the inner loop
|
||||
@ -590,10 +592,6 @@ static void selectInnerLoop(
|
||||
}
|
||||
}
|
||||
|
||||
if( checkForMultiColumnSelectError(pParse, pDest, pEList->nExpr) ){
|
||||
return;
|
||||
}
|
||||
|
||||
switch( eDest ){
|
||||
/* In this mode, write each query result to the key of the temporary
|
||||
** table iParm.
|
||||
@ -722,11 +720,11 @@ static void selectInnerLoop(
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Jump to the end of the loop if the LIMIT is reached.
|
||||
/* Jump to the end of the loop if the LIMIT is reached. Except, if
|
||||
** there is a sorter, in which case the sorter has already limited
|
||||
** the output for us.
|
||||
*/
|
||||
if( p->iLimit ){
|
||||
assert( pOrderBy==0 ); /* If there is an ORDER BY, the call to
|
||||
** pushOntoSorter() would have cleared p->iLimit */
|
||||
if( pOrderBy==0 && p->iLimit ){
|
||||
sqlite3VdbeAddOp3(v, OP_IfZero, p->iLimit, iBreak, -1);
|
||||
}
|
||||
}
|
||||
@ -773,6 +771,88 @@ static KeyInfo *keyInfoFromExprList(Parse *pParse, ExprList *pList){
|
||||
return pInfo;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_COMPOUND_SELECT
|
||||
/*
|
||||
** Name of the connection operator, used for error messages.
|
||||
*/
|
||||
static const char *selectOpName(int id){
|
||||
char *z;
|
||||
switch( id ){
|
||||
case TK_ALL: z = "UNION ALL"; break;
|
||||
case TK_INTERSECT: z = "INTERSECT"; break;
|
||||
case TK_EXCEPT: z = "EXCEPT"; break;
|
||||
default: z = "UNION"; break;
|
||||
}
|
||||
return z;
|
||||
}
|
||||
#endif /* SQLITE_OMIT_COMPOUND_SELECT */
|
||||
|
||||
#ifndef SQLITE_OMIT_EXPLAIN
|
||||
/*
|
||||
** Unless an "EXPLAIN QUERY PLAN" command is being processed, this function
|
||||
** is a no-op. Otherwise, it adds a single row of output to the EQP result,
|
||||
** where the caption is of the form:
|
||||
**
|
||||
** "USE TEMP B-TREE FOR xxx"
|
||||
**
|
||||
** where xxx is one of "DISTINCT", "ORDER BY" or "GROUP BY". Exactly which
|
||||
** is determined by the zUsage argument.
|
||||
*/
|
||||
static void explainTempTable(Parse *pParse, const char *zUsage){
|
||||
if( pParse->explain==2 ){
|
||||
Vdbe *v = pParse->pVdbe;
|
||||
char *zMsg = sqlite3MPrintf(pParse->db, "USE TEMP B-TREE FOR %s", zUsage);
|
||||
sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Unless an "EXPLAIN QUERY PLAN" command is being processed, this function
|
||||
** is a no-op. Otherwise, it adds a single row of output to the EQP result,
|
||||
** where the caption is of one of the two forms:
|
||||
**
|
||||
** "COMPOSITE SUBQUERIES iSub1 and iSub2 (op)"
|
||||
** "COMPOSITE SUBQUERIES iSub1 and iSub2 USING TEMP B-TREE (op)"
|
||||
**
|
||||
** where iSub1 and iSub2 are the integers passed as the corresponding
|
||||
** function parameters, and op is the text representation of the parameter
|
||||
** of the same name. The parameter "op" must be one of TK_UNION, TK_EXCEPT,
|
||||
** TK_INTERSECT or TK_ALL. The first form is used if argument bUseTmp is
|
||||
** false, or the second form if it is true.
|
||||
*/
|
||||
static void explainComposite(
|
||||
Parse *pParse, /* Parse context */
|
||||
int op, /* One of TK_UNION, TK_EXCEPT etc. */
|
||||
int iSub1, /* Subquery id 1 */
|
||||
int iSub2, /* Subquery id 2 */
|
||||
int bUseTmp /* True if a temp table was used */
|
||||
){
|
||||
assert( op==TK_UNION || op==TK_EXCEPT || op==TK_INTERSECT || op==TK_ALL );
|
||||
if( pParse->explain==2 ){
|
||||
Vdbe *v = pParse->pVdbe;
|
||||
char *zMsg = sqlite3MPrintf(
|
||||
pParse->db, "COMPOUND SUBQUERIES %d AND %d %s(%s)", iSub1, iSub2,
|
||||
bUseTmp?"USING TEMP B-TREE ":"", selectOpName(op)
|
||||
);
|
||||
sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Assign expression b to lvalue a. A second, no-op, version of this macro
|
||||
** is provided when SQLITE_OMIT_EXPLAIN is defined. This allows the code
|
||||
** in sqlite3Select() to assign values to structure member variables that
|
||||
** only exist if SQLITE_OMIT_EXPLAIN is not defined without polluting the
|
||||
** code with #ifndef directives.
|
||||
*/
|
||||
# define explainSetInteger(a, b) a = b
|
||||
|
||||
#else
|
||||
/* No-op versions of the explainXXX() functions and macros. */
|
||||
# define explainTempTable(y,z)
|
||||
# define explainComposite(v,w,x,y,z)
|
||||
# define explainSetInteger(y,z)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** If the inner loop was generated using a non-null pOrderBy argument,
|
||||
@ -861,10 +941,6 @@ static void generateSortTail(
|
||||
sqlite3ReleaseTempReg(pParse, regRow);
|
||||
sqlite3ReleaseTempReg(pParse, regRowid);
|
||||
|
||||
/* LIMIT has been implemented by the pushOntoSorter() routine.
|
||||
*/
|
||||
assert( p->iLimit==0 );
|
||||
|
||||
/* The bottom of the loop
|
||||
*/
|
||||
sqlite3VdbeResolveLabel(v, addrContinue);
|
||||
@ -1124,22 +1200,6 @@ static void generateColumnNames(
|
||||
generateColumnTypes(pParse, pTabList, pEList);
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_COMPOUND_SELECT
|
||||
/*
|
||||
** Name of the connection operator, used for error messages.
|
||||
*/
|
||||
static const char *selectOpName(int id){
|
||||
char *z;
|
||||
switch( id ){
|
||||
case TK_ALL: z = "UNION ALL"; break;
|
||||
case TK_INTERSECT: z = "INTERSECT"; break;
|
||||
case TK_EXCEPT: z = "EXCEPT"; break;
|
||||
default: z = "UNION"; break;
|
||||
}
|
||||
return z;
|
||||
}
|
||||
#endif /* SQLITE_OMIT_COMPOUND_SELECT */
|
||||
|
||||
/*
|
||||
** Given a an expression list (which is really the list of expressions
|
||||
** that form the result set of a SELECT statement) compute appropriate
|
||||
@ -1302,6 +1362,7 @@ Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect){
|
||||
assert( db->lookaside.bEnabled==0 );
|
||||
pTab->nRef = 1;
|
||||
pTab->zName = 0;
|
||||
pTab->nRowEst = 1000000;
|
||||
selectColumnsFromExprList(pParse, pSelect->pEList, &pTab->nCol, &pTab->aCol);
|
||||
selectAddColumnTypeAndCollation(pParse, pTab->nCol, pTab->aCol, pSelect);
|
||||
pTab->iPKey = -1;
|
||||
@ -1372,6 +1433,8 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){
|
||||
VdbeComment((v, "LIMIT counter"));
|
||||
if( n==0 ){
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, iBreak);
|
||||
}else{
|
||||
if( p->nSelectRow > (double)n ) p->nSelectRow = (double)n;
|
||||
}
|
||||
}else{
|
||||
sqlite3ExprCode(pParse, p->pLimit, iLimit);
|
||||
@ -1472,6 +1535,10 @@ static int multiSelect(
|
||||
SelectDest dest; /* Alternative data destination */
|
||||
Select *pDelete = 0; /* Chain of simple selects to delete */
|
||||
sqlite3 *db; /* Database connection */
|
||||
#ifndef SQLITE_OMIT_EXPLAIN
|
||||
int iSub1; /* EQP id of left-hand query */
|
||||
int iSub2; /* EQP id of right-hand query */
|
||||
#endif
|
||||
|
||||
/* Make sure there is no ORDER BY or LIMIT clause on prior SELECTs. Only
|
||||
** the last (right-most) SELECT in the series may have an ORDER BY or LIMIT.
|
||||
@ -1503,6 +1570,7 @@ static int multiSelect(
|
||||
if( dest.eDest==SRT_EphemTab ){
|
||||
assert( p->pEList );
|
||||
sqlite3VdbeAddOp2(v, OP_OpenEphemeral, dest.iParm, p->pEList->nExpr);
|
||||
sqlite3VdbeChangeP5(v, BTREE_UNORDERED);
|
||||
dest.eDest = SRT_Table;
|
||||
}
|
||||
|
||||
@ -1528,9 +1596,11 @@ static int multiSelect(
|
||||
switch( p->op ){
|
||||
case TK_ALL: {
|
||||
int addr = 0;
|
||||
int nLimit;
|
||||
assert( !pPrior->pLimit );
|
||||
pPrior->pLimit = p->pLimit;
|
||||
pPrior->pOffset = p->pOffset;
|
||||
explainSetInteger(iSub1, pParse->iNextSelectId);
|
||||
rc = sqlite3Select(pParse, pPrior, &dest);
|
||||
p->pLimit = 0;
|
||||
p->pOffset = 0;
|
||||
@ -1544,10 +1614,18 @@ static int multiSelect(
|
||||
addr = sqlite3VdbeAddOp1(v, OP_IfZero, p->iLimit);
|
||||
VdbeComment((v, "Jump ahead if LIMIT reached"));
|
||||
}
|
||||
explainSetInteger(iSub2, pParse->iNextSelectId);
|
||||
rc = sqlite3Select(pParse, p, &dest);
|
||||
testcase( rc!=SQLITE_OK );
|
||||
pDelete = p->pPrior;
|
||||
p->pPrior = pPrior;
|
||||
p->nSelectRow += pPrior->nSelectRow;
|
||||
if( pPrior->pLimit
|
||||
&& sqlite3ExprIsInteger(pPrior->pLimit, &nLimit)
|
||||
&& p->nSelectRow > (double)nLimit
|
||||
){
|
||||
p->nSelectRow = (double)nLimit;
|
||||
}
|
||||
if( addr ){
|
||||
sqlite3VdbeJumpHere(v, addr);
|
||||
}
|
||||
@ -1591,6 +1669,7 @@ static int multiSelect(
|
||||
*/
|
||||
assert( !pPrior->pOrderBy );
|
||||
sqlite3SelectDestInit(&uniondest, priorOp, unionTab);
|
||||
explainSetInteger(iSub1, pParse->iNextSelectId);
|
||||
rc = sqlite3Select(pParse, pPrior, &uniondest);
|
||||
if( rc ){
|
||||
goto multi_select_end;
|
||||
@ -1610,6 +1689,7 @@ static int multiSelect(
|
||||
pOffset = p->pOffset;
|
||||
p->pOffset = 0;
|
||||
uniondest.eDest = op;
|
||||
explainSetInteger(iSub2, pParse->iNextSelectId);
|
||||
rc = sqlite3Select(pParse, p, &uniondest);
|
||||
testcase( rc!=SQLITE_OK );
|
||||
/* Query flattening in sqlite3Select() might refill p->pOrderBy.
|
||||
@ -1618,6 +1698,7 @@ static int multiSelect(
|
||||
pDelete = p->pPrior;
|
||||
p->pPrior = pPrior;
|
||||
p->pOrderBy = 0;
|
||||
if( p->op==TK_UNION ) p->nSelectRow += pPrior->nSelectRow;
|
||||
sqlite3ExprDelete(db, p->pLimit);
|
||||
p->pLimit = pLimit;
|
||||
p->pOffset = pOffset;
|
||||
@ -1675,6 +1756,7 @@ static int multiSelect(
|
||||
/* Code the SELECTs to our left into temporary table "tab1".
|
||||
*/
|
||||
sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1);
|
||||
explainSetInteger(iSub1, pParse->iNextSelectId);
|
||||
rc = sqlite3Select(pParse, pPrior, &intersectdest);
|
||||
if( rc ){
|
||||
goto multi_select_end;
|
||||
@ -1691,10 +1773,12 @@ static int multiSelect(
|
||||
pOffset = p->pOffset;
|
||||
p->pOffset = 0;
|
||||
intersectdest.iParm = tab2;
|
||||
explainSetInteger(iSub2, pParse->iNextSelectId);
|
||||
rc = sqlite3Select(pParse, p, &intersectdest);
|
||||
testcase( rc!=SQLITE_OK );
|
||||
pDelete = p->pPrior;
|
||||
p->pPrior = pPrior;
|
||||
if( p->nSelectRow>pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow;
|
||||
sqlite3ExprDelete(db, p->pLimit);
|
||||
p->pLimit = pLimit;
|
||||
p->pOffset = pOffset;
|
||||
@ -1727,6 +1811,8 @@ static int multiSelect(
|
||||
}
|
||||
}
|
||||
|
||||
explainComposite(pParse, p->op, iSub1, iSub2, p->op!=TK_ALL);
|
||||
|
||||
/* Compute collating sequences used by
|
||||
** temporary tables needed to implement the compound select.
|
||||
** Attach the KeyInfo structure to all temporary tables.
|
||||
@ -2070,6 +2156,10 @@ static int multiSelectOrderBy(
|
||||
ExprList *pOrderBy; /* The ORDER BY clause */
|
||||
int nOrderBy; /* Number of terms in the ORDER BY clause */
|
||||
int *aPermute; /* Mapping from ORDER BY terms to result set columns */
|
||||
#ifndef SQLITE_OMIT_EXPLAIN
|
||||
int iSub1; /* EQP id of left-hand query */
|
||||
int iSub2; /* EQP id of right-hand query */
|
||||
#endif
|
||||
|
||||
assert( p->pOrderBy!=0 );
|
||||
assert( pKeyDup==0 ); /* "Managed" code needs this. Ticket #3382. */
|
||||
@ -2181,7 +2271,6 @@ static int multiSelectOrderBy(
|
||||
/* Separate the left and the right query from one another
|
||||
*/
|
||||
p->pPrior = 0;
|
||||
pPrior->pRightmost = 0;
|
||||
sqlite3ResolveOrderGroupBy(pParse, p, p->pOrderBy, "ORDER");
|
||||
if( pPrior->pPrior==0 ){
|
||||
sqlite3ResolveOrderGroupBy(pParse, pPrior, pPrior->pOrderBy, "ORDER");
|
||||
@ -2224,6 +2313,7 @@ static int multiSelectOrderBy(
|
||||
*/
|
||||
VdbeNoopComment((v, "Begin coroutine for left SELECT"));
|
||||
pPrior->iLimit = regLimitA;
|
||||
explainSetInteger(iSub1, pParse->iNextSelectId);
|
||||
sqlite3Select(pParse, pPrior, &destA);
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 1, regEofA);
|
||||
sqlite3VdbeAddOp1(v, OP_Yield, regAddrA);
|
||||
@ -2238,6 +2328,7 @@ static int multiSelectOrderBy(
|
||||
savedOffset = p->iOffset;
|
||||
p->iLimit = regLimitB;
|
||||
p->iOffset = 0;
|
||||
explainSetInteger(iSub2, pParse->iNextSelectId);
|
||||
sqlite3Select(pParse, p, &destB);
|
||||
p->iLimit = savedLimit;
|
||||
p->iOffset = savedOffset;
|
||||
@ -2274,6 +2365,7 @@ static int multiSelectOrderBy(
|
||||
sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB);
|
||||
sqlite3VdbeAddOp1(v, OP_Yield, regAddrB);
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, addrEofA);
|
||||
p->nSelectRow += pPrior->nSelectRow;
|
||||
}
|
||||
|
||||
/* Generate a subroutine to run when the results from select B
|
||||
@ -2281,6 +2373,7 @@ static int multiSelectOrderBy(
|
||||
*/
|
||||
if( op==TK_INTERSECT ){
|
||||
addrEofB = addrEofA;
|
||||
if( p->nSelectRow > pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow;
|
||||
}else{
|
||||
VdbeNoopComment((v, "eof-B subroutine"));
|
||||
addrEofB = sqlite3VdbeAddOp2(v, OP_If, regEofA, labelEnd);
|
||||
@ -2368,6 +2461,7 @@ static int multiSelectOrderBy(
|
||||
|
||||
/*** TBD: Insert subroutine calls to close cursors on incomplete
|
||||
**** subqueries ****/
|
||||
explainComposite(pParse, p->op, iSub1, iSub2, 0);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
#endif
|
||||
@ -3101,6 +3195,7 @@ static int selectExpander(Walker *pWalker, Select *p){
|
||||
while( pSel->pPrior ){ pSel = pSel->pPrior; }
|
||||
selectColumnsFromExprList(pParse, pSel->pEList, &pTab->nCol, &pTab->aCol);
|
||||
pTab->iPKey = -1;
|
||||
pTab->nRowEst = 1000000;
|
||||
pTab->tabFlags |= TF_Ephemeral;
|
||||
#endif
|
||||
}else{
|
||||
@ -3465,7 +3560,7 @@ static void updateAccumulator(Parse *pParse, AggInfo *pAggInfo){
|
||||
if( pList ){
|
||||
nArg = pList->nExpr;
|
||||
regAgg = sqlite3GetTempRange(pParse, nArg);
|
||||
sqlite3ExprCodeExprList(pParse, pList, regAgg, 0);
|
||||
sqlite3ExprCodeExprList(pParse, pList, regAgg, 1);
|
||||
}else{
|
||||
nArg = 0;
|
||||
regAgg = 0;
|
||||
@ -3594,6 +3689,11 @@ int sqlite3Select(
|
||||
int iEnd; /* Address of the end of the query */
|
||||
sqlite3 *db; /* The database connection */
|
||||
|
||||
#ifndef SQLITE_OMIT_EXPLAIN
|
||||
int iRestoreSelectId = pParse->iSelectId;
|
||||
pParse->iSelectId = pParse->iNextSelectId++;
|
||||
#endif
|
||||
|
||||
db = pParse->db;
|
||||
if( p==0 || db->mallocFailed || pParse->nErr ){
|
||||
return 1;
|
||||
@ -3625,6 +3725,15 @@ int sqlite3Select(
|
||||
v = sqlite3GetVdbe(pParse);
|
||||
if( v==0 ) goto select_end;
|
||||
|
||||
/* If writing to memory or generating a set
|
||||
** only a single column may be output.
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_SUBQUERY
|
||||
if( checkForMultiColumnSelectError(pParse, pDest, pEList->nExpr) ){
|
||||
goto select_end;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Generate code for all sub-queries in the FROM clause
|
||||
*/
|
||||
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
|
||||
@ -3656,8 +3765,10 @@ int sqlite3Select(
|
||||
}else{
|
||||
sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor);
|
||||
assert( pItem->isPopulated==0 );
|
||||
explainSetInteger(pItem->iSelectId, (u8)pParse->iNextSelectId);
|
||||
sqlite3Select(pParse, pSub, &dest);
|
||||
pItem->isPopulated = 1;
|
||||
pItem->pTab->nRowEst = (unsigned)pSub->nSelectRow;
|
||||
}
|
||||
if( /*pParse->nErr ||*/ db->mallocFailed ){
|
||||
goto select_end;
|
||||
@ -3691,19 +3802,12 @@ int sqlite3Select(
|
||||
mxSelect = db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT];
|
||||
if( mxSelect && cnt>mxSelect ){
|
||||
sqlite3ErrorMsg(pParse, "too many terms in compound SELECT");
|
||||
return 1;
|
||||
goto select_end;
|
||||
}
|
||||
}
|
||||
return multiSelect(pParse, p, pDest);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* If writing to memory or generating a set
|
||||
** only a single column may be output.
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_SUBQUERY
|
||||
if( checkForMultiColumnSelectError(pParse, pDest, pEList->nExpr) ){
|
||||
goto select_end;
|
||||
rc = multiSelect(pParse, p, pDest);
|
||||
explainSetInteger(pParse->iSelectId, iRestoreSelectId);
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -3715,7 +3819,6 @@ int sqlite3Select(
|
||||
p->pGroupBy = sqlite3ExprListDup(db, p->pEList, 0);
|
||||
pGroupBy = p->pGroupBy;
|
||||
p->selFlags &= ~SF_Distinct;
|
||||
isDistinct = 0;
|
||||
}
|
||||
|
||||
/* If there is both a GROUP BY and an ORDER BY clause and they are
|
||||
@ -3758,17 +3861,19 @@ int sqlite3Select(
|
||||
/* Set the limiter.
|
||||
*/
|
||||
iEnd = sqlite3VdbeMakeLabel(v);
|
||||
p->nSelectRow = (double)LARGEST_INT64;
|
||||
computeLimitRegisters(pParse, p, iEnd);
|
||||
|
||||
/* Open a virtual index to use for the distinct set.
|
||||
*/
|
||||
if( isDistinct ){
|
||||
if( p->selFlags & SF_Distinct ){
|
||||
KeyInfo *pKeyInfo;
|
||||
assert( isAgg || pGroupBy );
|
||||
distinct = pParse->nTab++;
|
||||
pKeyInfo = keyInfoFromExprList(pParse, p->pEList);
|
||||
sqlite3VdbeAddOp4(v, OP_OpenEphemeral, distinct, 0, 0,
|
||||
(char*)pKeyInfo, P4_KEYINFO_HANDOFF);
|
||||
sqlite3VdbeChangeP5(v, BTREE_UNORDERED);
|
||||
}else{
|
||||
distinct = -1;
|
||||
}
|
||||
@ -3780,6 +3885,7 @@ int sqlite3Select(
|
||||
*/
|
||||
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, &pOrderBy, 0);
|
||||
if( pWInfo==0 ) goto select_end;
|
||||
if( pWInfo->nRowOut < p->nSelectRow ) p->nSelectRow = pWInfo->nRowOut;
|
||||
|
||||
/* If sorting index that was created by a prior OP_OpenEphemeral
|
||||
** instruction ended up not being needed, then change the OP_OpenEphemeral
|
||||
@ -3824,6 +3930,9 @@ int sqlite3Select(
|
||||
for(k=pGroupBy->nExpr, pItem=pGroupBy->a; k>0; k--, pItem++){
|
||||
pItem->iAlias = 0;
|
||||
}
|
||||
if( p->nSelectRow>(double)100 ) p->nSelectRow = (double)100;
|
||||
}else{
|
||||
p->nSelectRow = (double)1;
|
||||
}
|
||||
|
||||
|
||||
@ -3920,6 +4029,9 @@ int sqlite3Select(
|
||||
int nCol;
|
||||
int nGroupBy;
|
||||
|
||||
explainTempTable(pParse,
|
||||
isDistinct && !(p->selFlags&SF_Distinct)?"DISTINCT":"GROUP BY");
|
||||
|
||||
groupBySort = 1;
|
||||
nGroupBy = pGroupBy->nExpr;
|
||||
nCol = nGroupBy + 1;
|
||||
@ -4181,10 +4293,15 @@ int sqlite3Select(
|
||||
|
||||
} /* endif aggregate query */
|
||||
|
||||
if( distinct>=0 ){
|
||||
explainTempTable(pParse, "DISTINCT");
|
||||
}
|
||||
|
||||
/* If there is an ORDER BY clause, then we need to sort the results
|
||||
** and send them to the callback one by one.
|
||||
*/
|
||||
if( pOrderBy ){
|
||||
explainTempTable(pParse, "ORDER BY");
|
||||
generateSortTail(pParse, p, v, pEList->nExpr, pDest);
|
||||
}
|
||||
|
||||
@ -4201,6 +4318,7 @@ int sqlite3Select(
|
||||
** successful coding of the SELECT.
|
||||
*/
|
||||
select_end:
|
||||
explainSetInteger(pParse->iSelectId, iRestoreSelectId);
|
||||
|
||||
/* Identify column names if results of the SELECT are to be output.
|
||||
*/
|
||||
|
||||
39
src/shell.c
39
src/shell.c
@ -38,10 +38,14 @@
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_EDITLINE
|
||||
# include <editline/editline.h>
|
||||
#endif
|
||||
#if defined(HAVE_READLINE) && HAVE_READLINE==1
|
||||
# include <readline/readline.h>
|
||||
# include <readline/history.h>
|
||||
#else
|
||||
#endif
|
||||
#if !defined(HAVE_EDITLINE) && (!defined(HAVE_READLINE) || HAVE_READLINE!=1)
|
||||
# define readline(p) local_getline(p,stdin)
|
||||
# define add_history(X)
|
||||
# define read_history(X)
|
||||
@ -980,7 +984,7 @@ static int display_stats(
|
||||
fprintf(pArg->out, "Memory Used: %d (max %d) bytes\n", iCur, iHiwtr);
|
||||
iHiwtr = iCur = -1;
|
||||
sqlite3_status(SQLITE_STATUS_MALLOC_COUNT, &iCur, &iHiwtr, bReset);
|
||||
fprintf(pArg->out, "Number of Allocations: %d (max %d)\n", iCur, iHiwtr);
|
||||
fprintf(pArg->out, "Number of Outstanding Allocations: %d (max %d)\n", iCur, iHiwtr);
|
||||
/*
|
||||
** Not currently used by the CLI.
|
||||
** iHiwtr = iCur = -1;
|
||||
@ -1019,6 +1023,12 @@ static int display_stats(
|
||||
iHiwtr = iCur = -1;
|
||||
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED, &iCur, &iHiwtr, bReset);
|
||||
fprintf(pArg->out, "Lookaside Slots Used: %d (max %d)\n", iCur, iHiwtr);
|
||||
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &iCur, &iHiwtr, bReset);
|
||||
fprintf(pArg->out, "Successful lookaside attempts: %d\n", iHiwtr);
|
||||
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, &iCur, &iHiwtr, bReset);
|
||||
fprintf(pArg->out, "Lookaside failures due to size: %d\n", iHiwtr);
|
||||
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, &iCur, &iHiwtr, bReset);
|
||||
fprintf(pArg->out, "Lookaside failures due to OOM: %d\n", iHiwtr);
|
||||
iHiwtr = iCur = -1;
|
||||
sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset);
|
||||
fprintf(pArg->out, "Pager Heap Usage: %d bytes\n", iCur);
|
||||
@ -2538,6 +2548,7 @@ int main(int argc, char **argv){
|
||||
|
||||
/* Do an initial pass through the command-line argument to locate
|
||||
** the name of the database file, the name of the initialization file,
|
||||
** the size of the alternative malloc heap,
|
||||
** and the first command to execute.
|
||||
*/
|
||||
for(i=1; i<argc-1; i++){
|
||||
@ -2556,6 +2567,22 @@ int main(int argc, char **argv){
|
||||
*/
|
||||
}else if( strcmp(argv[i],"-batch")==0 ){
|
||||
stdin_is_interactive = 0;
|
||||
}else if( strcmp(argv[i],"-heap")==0 ){
|
||||
int j, c;
|
||||
const char *zSize;
|
||||
sqlite3_int64 szHeap;
|
||||
|
||||
zSize = argv[++i];
|
||||
szHeap = atoi(zSize);
|
||||
for(j=0; (c = zSize[j])!=0; j++){
|
||||
if( c=='M' ){ szHeap *= 1000000; break; }
|
||||
if( c=='K' ){ szHeap *= 1000; break; }
|
||||
if( c=='G' ){ szHeap *= 1000000000; break; }
|
||||
}
|
||||
if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000;
|
||||
#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
|
||||
sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if( i<argc ){
|
||||
@ -2662,6 +2689,8 @@ int main(int argc, char **argv){
|
||||
stdin_is_interactive = 1;
|
||||
}else if( strcmp(z,"-batch")==0 ){
|
||||
stdin_is_interactive = 0;
|
||||
}else if( strcmp(z,"-heap")==0 ){
|
||||
i++;
|
||||
}else if( strcmp(z,"-help")==0 || strcmp(z, "--help")==0 ){
|
||||
usage(1);
|
||||
}else{
|
||||
@ -2723,11 +2752,7 @@ int main(int argc, char **argv){
|
||||
}
|
||||
set_table_name(&data, 0);
|
||||
if( data.db ){
|
||||
if( sqlite3_close(data.db)!=SQLITE_OK ){
|
||||
fprintf(stderr,"Error: cannot close database \"%s\"\n",
|
||||
sqlite3_errmsg(db));
|
||||
rc++;
|
||||
}
|
||||
sqlite3_close(data.db);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
852
src/sqlite.h.in
852
src/sqlite.h.in
File diff suppressed because it is too large
Load Diff
@ -191,6 +191,27 @@ struct sqlite3_api_routines {
|
||||
sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*);
|
||||
const char *(*sql)(sqlite3_stmt*);
|
||||
int (*status)(int,int*,int*,int);
|
||||
int (*backup_finish)(sqlite3_backup*);
|
||||
sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*);
|
||||
int (*backup_pagecount)(sqlite3_backup*);
|
||||
int (*backup_remaining)(sqlite3_backup*);
|
||||
int (*backup_step)(sqlite3_backup*,int);
|
||||
const char *(*compileoption_get)(int);
|
||||
int (*compileoption_used)(const char*);
|
||||
int (*create_function_v2)(sqlite3*,const char*,int,int,void*,void (*xFunc)(sqlite3_context*,int,sqlite3_value**),void (*xStep)(sqlite3_context*,int,sqlite3_value**),void (*xFinal)(sqlite3_context*),void(*xDestroy)(void*));
|
||||
int (*db_config)(sqlite3*,int,...);
|
||||
sqlite3_mutex *(*db_mutex)(sqlite3*);
|
||||
int (*db_status)(sqlite3*,int,int*,int*,int);
|
||||
int (*extended_errcode)(sqlite3*);
|
||||
void (*log)(int,const char*,...);
|
||||
sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64);
|
||||
const char *(*sourceid)(void);
|
||||
int (*stmt_status)(sqlite3_stmt*,int,int);
|
||||
int (*strnicmp)(const char*,const char*,int);
|
||||
int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*);
|
||||
int (*wal_autocheckpoint)(sqlite3*,int);
|
||||
int (*wal_checkpoint)(sqlite3*,const char*);
|
||||
void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*);
|
||||
};
|
||||
|
||||
/*
|
||||
@ -370,6 +391,27 @@ struct sqlite3_api_routines {
|
||||
#define sqlite3_next_stmt sqlite3_api->next_stmt
|
||||
#define sqlite3_sql sqlite3_api->sql
|
||||
#define sqlite3_status sqlite3_api->status
|
||||
#define sqlite3_backup_finish sqlite3_api->backup_finish
|
||||
#define sqlite3_backup_init sqlite3_api->backup_init
|
||||
#define sqlite3_backup_pagecount sqlite3_api->backup_pagecount
|
||||
#define sqlite3_backup_remaining sqlite3_api->backup_remaining
|
||||
#define sqlite3_backup_step sqlite3_api->backup_step
|
||||
#define sqlite3_compileoption_get sqlite3_api->compileoption_get
|
||||
#define sqlite3_compileoption_used sqlite3_api->compileoption_used
|
||||
#define sqlite3_create_function_v2 sqlite3_api->create_function_v2
|
||||
#define sqlite3_db_config sqlite3_api->db_config
|
||||
#define sqlite3_db_mutex sqlite3_api->db_mutex
|
||||
#define sqlite3_db_status sqlite3_api->db_status
|
||||
#define sqlite3_extended_errcode sqlite3_api->extended_errcode
|
||||
#define sqlite3_log sqlite3_api->log
|
||||
#define sqlite3_soft_heap_limit64 sqlite3_api->soft_heap_limit64
|
||||
#define sqlite3_sourceid sqlite3_api->sourceid
|
||||
#define sqlite3_stmt_status sqlite3_api->stmt_status
|
||||
#define sqlite3_strnicmp sqlite3_api->strnicmp
|
||||
#define sqlite3_unlock_notify sqlite3_api->unlock_notify
|
||||
#define sqlite3_wal_autocheckpoint sqlite3_api->wal_autocheckpoint
|
||||
#define sqlite3_wal_checkpoint sqlite3_api->wal_checkpoint
|
||||
#define sqlite3_wal_hook sqlite3_api->wal_hook
|
||||
#endif /* SQLITE_CORE */
|
||||
|
||||
#define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api = 0;
|
||||
|
||||
@ -114,15 +114,21 @@
|
||||
#endif
|
||||
|
||||
/*
|
||||
** The SQLITE_THREADSAFE macro must be defined as either 0 or 1.
|
||||
** The SQLITE_THREADSAFE macro must be defined as 0, 1, or 2.
|
||||
** 0 means mutexes are permanently disable and the library is never
|
||||
** threadsafe. 1 means the library is serialized which is the highest
|
||||
** level of threadsafety. 2 means the libary is multithreaded - multiple
|
||||
** threads can use SQLite as long as no two threads try to use the same
|
||||
** database connection at the same time.
|
||||
**
|
||||
** Older versions of SQLite used an optional THREADSAFE macro.
|
||||
** We support that for legacy
|
||||
** We support that for legacy.
|
||||
*/
|
||||
#if !defined(SQLITE_THREADSAFE)
|
||||
#if defined(THREADSAFE)
|
||||
# define SQLITE_THREADSAFE THREADSAFE
|
||||
#else
|
||||
# define SQLITE_THREADSAFE 1
|
||||
# define SQLITE_THREADSAFE 1 /* IMP: R-07272-22309 */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -600,6 +606,7 @@ typedef struct Expr Expr;
|
||||
typedef struct ExprList ExprList;
|
||||
typedef struct ExprSpan ExprSpan;
|
||||
typedef struct FKey FKey;
|
||||
typedef struct FuncDestructor FuncDestructor;
|
||||
typedef struct FuncDef FuncDef;
|
||||
typedef struct FuncDefHash FuncDefHash;
|
||||
typedef struct IdList IdList;
|
||||
@ -730,6 +737,7 @@ struct Lookaside {
|
||||
u8 bMalloced; /* True if pStart obtained from sqlite3_malloc() */
|
||||
int nOut; /* Number of buffers currently checked out */
|
||||
int mxOut; /* Highwater mark for nOut */
|
||||
int anStat[3]; /* 0: hits. 1: size misses. 2: full misses */
|
||||
LookasideSlot *pFree; /* List of available buffers */
|
||||
void *pStart; /* First byte of available memory space */
|
||||
void *pEnd; /* First byte past end of available space */
|
||||
@ -808,6 +816,7 @@ struct sqlite3 {
|
||||
struct Vdbe *pVdbe; /* List of active virtual machines */
|
||||
int activeVdbeCnt; /* Number of VDBEs currently executing */
|
||||
int writeVdbeCnt; /* Number of active VDBEs that are writing */
|
||||
int vdbeExecCnt; /* Number of nested calls to VdbeExec() */
|
||||
void (*xTrace)(void*,const char*); /* Trace function */
|
||||
void *pTraceArg; /* Argument to the trace function */
|
||||
void (*xProfile)(void*,const char*,u64); /* Profiling function */
|
||||
@ -907,13 +916,14 @@ struct sqlite3 {
|
||||
#define SQLITE_ReadUncommitted 0x0080000 /* For shared-cache mode */
|
||||
#define SQLITE_LegacyFileFmt 0x00100000 /* Create new databases in format 1 */
|
||||
#define SQLITE_FullFSync 0x00200000 /* Use full fsync on the backend */
|
||||
#define SQLITE_LoadExtension 0x00400000 /* Enable load_extension */
|
||||
#define SQLITE_CkptFullFSync 0x00400000 /* Use full fsync for checkpoint */
|
||||
#define SQLITE_RecoveryMode 0x00800000 /* Ignore schema errors */
|
||||
#define SQLITE_ReverseOrder 0x01000000 /* Reverse unordered SELECTs */
|
||||
#define SQLITE_RecTriggers 0x02000000 /* Enable recursive triggers */
|
||||
#define SQLITE_ForeignKeys 0x04000000 /* Enforce foreign key constraints */
|
||||
#define SQLITE_AutoIndex 0x08000000 /* Enable automatic indexes */
|
||||
#define SQLITE_PreferBuiltin 0x10000000 /* Preference to built-in funcs */
|
||||
#define SQLITE_LoadExtension 0x20000000 /* Enable load_extension */
|
||||
|
||||
/*
|
||||
** Bits of the sqlite3.flags field that are used by the
|
||||
@ -926,6 +936,7 @@ struct sqlite3 {
|
||||
#define SQLITE_IndexSearch 0x08 /* Disable indexes for searching */
|
||||
#define SQLITE_IndexCover 0x10 /* Disable index covering table */
|
||||
#define SQLITE_GroupByOrder 0x20 /* Disable GROUPBY cover of ORDERBY */
|
||||
#define SQLITE_FactorOutConst 0x40 /* Disable factoring out constants */
|
||||
#define SQLITE_OptMask 0xff /* Mask of all disablable opts */
|
||||
|
||||
/*
|
||||
@ -956,6 +967,27 @@ struct FuncDef {
|
||||
void (*xFinalize)(sqlite3_context*); /* Aggregate finalizer */
|
||||
char *zName; /* SQL name of the function. */
|
||||
FuncDef *pHash; /* Next with a different name but the same hash */
|
||||
FuncDestructor *pDestructor; /* Reference counted destructor function */
|
||||
};
|
||||
|
||||
/*
|
||||
** This structure encapsulates a user-function destructor callback (as
|
||||
** configured using create_function_v2()) and a reference counter. When
|
||||
** create_function_v2() is called to create a function with a destructor,
|
||||
** a single object of this type is allocated. FuncDestructor.nRef is set to
|
||||
** the number of FuncDef objects created (either 1 or 3, depending on whether
|
||||
** or not the specified encoding is SQLITE_ANY). The FuncDef.pDestructor
|
||||
** member of each of the new FuncDef objects is set to point to the allocated
|
||||
** FuncDestructor.
|
||||
**
|
||||
** Thereafter, when one of the FuncDef objects is deleted, the reference
|
||||
** count on this object is decremented. When it reaches 0, the destructor
|
||||
** is invoked and the FuncDestructor structure freed.
|
||||
*/
|
||||
struct FuncDestructor {
|
||||
int nRef;
|
||||
void (*xDestroy)(void *);
|
||||
void *pUserData;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -996,15 +1028,15 @@ struct FuncDef {
|
||||
*/
|
||||
#define FUNCTION(zName, nArg, iArg, bNC, xFunc) \
|
||||
{nArg, SQLITE_UTF8, bNC*SQLITE_FUNC_NEEDCOLL, \
|
||||
SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0}
|
||||
SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0}
|
||||
#define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \
|
||||
{nArg, SQLITE_UTF8, bNC*SQLITE_FUNC_NEEDCOLL, \
|
||||
pArg, 0, xFunc, 0, 0, #zName, 0}
|
||||
pArg, 0, xFunc, 0, 0, #zName, 0, 0}
|
||||
#define LIKEFUNC(zName, nArg, arg, flags) \
|
||||
{nArg, SQLITE_UTF8, flags, (void *)arg, 0, likeFunc, 0, 0, #zName, 0}
|
||||
{nArg, SQLITE_UTF8, flags, (void *)arg, 0, likeFunc, 0, 0, #zName, 0, 0}
|
||||
#define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal) \
|
||||
{nArg, SQLITE_UTF8, nc*SQLITE_FUNC_NEEDCOLL, \
|
||||
SQLITE_INT_TO_PTR(arg), 0, 0, xStep,xFinal,#zName,0}
|
||||
SQLITE_INT_TO_PTR(arg), 0, 0, xStep,xFinal,#zName,0,0}
|
||||
|
||||
/*
|
||||
** All current savepoints are stored in a linked list starting at
|
||||
@ -1224,6 +1256,7 @@ struct Table {
|
||||
Column *aCol; /* Information about each column */
|
||||
Index *pIndex; /* List of SQL indexes on this table. */
|
||||
int tnum; /* Root BTree node for this table (see note above) */
|
||||
unsigned nRowEst; /* Estimated rows in table - from sqlite_stat1 table */
|
||||
Select *pSelect; /* NULL for tables. Points to definition if a view. */
|
||||
u16 nRef; /* Number of pointers to this Table */
|
||||
u8 tabFlags; /* Mask of TF_* values */
|
||||
@ -1792,6 +1825,9 @@ struct SrcList {
|
||||
u8 isPopulated; /* Temporary table associated with SELECT is populated */
|
||||
u8 jointype; /* Type of join between this able and the previous */
|
||||
u8 notIndexed; /* True if there is a NOT INDEXED clause */
|
||||
#ifndef SQLITE_OMIT_EXPLAIN
|
||||
u8 iSelectId; /* If pSelect!=0, the id of the sub-select in EQP */
|
||||
#endif
|
||||
int iCursor; /* The VDBE cursor number used to access this table */
|
||||
Expr *pOn; /* The ON clause of a join */
|
||||
IdList *pUsing; /* The USING clause of a join */
|
||||
@ -1830,6 +1866,7 @@ struct SrcList {
|
||||
struct WherePlan {
|
||||
u32 wsFlags; /* WHERE_* flags that describe the strategy */
|
||||
u32 nEq; /* Number of == constraints */
|
||||
double nRow; /* Estimated number of rows (for EQP) */
|
||||
union {
|
||||
Index *pIdx; /* Index when WHERE_INDEXED is true */
|
||||
struct WhereTerm *pTerm; /* WHERE clause term for OR-search */
|
||||
@ -1914,6 +1951,7 @@ struct WhereInfo {
|
||||
int nLevel; /* Number of nested loop */
|
||||
struct WhereClause *pWC; /* Decomposition of the WHERE clause */
|
||||
double savedNQueryLoop; /* pParse->nQueryLoop outside the WHERE loop */
|
||||
double nRowOut; /* Estimated number of output rows */
|
||||
WhereLevel a[1]; /* Information about each nest loop in WHERE */
|
||||
};
|
||||
|
||||
@ -1989,6 +2027,7 @@ struct Select {
|
||||
Expr *pOffset; /* OFFSET expression. NULL means not used. */
|
||||
int iLimit, iOffset; /* Memory registers holding LIMIT & OFFSET counters */
|
||||
int addrOpenEphm[3]; /* OP_OpenEphem opcodes related to this select */
|
||||
double nSelectRow; /* Estimated number of result rows */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -2184,6 +2223,11 @@ struct Parse {
|
||||
int nHeight; /* Expression tree height of current sub-select */
|
||||
Table *pZombieTab; /* List of Table objects to delete after code gen */
|
||||
TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */
|
||||
|
||||
#ifndef SQLITE_OMIT_EXPLAIN
|
||||
int iSelectId;
|
||||
int iNextSelectId;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef SQLITE_OMIT_VIRTUALTABLE
|
||||
@ -2478,7 +2522,6 @@ int sqlite3CantopenError(int);
|
||||
** Internal function prototypes
|
||||
*/
|
||||
int sqlite3StrICmp(const char *, const char *);
|
||||
int sqlite3IsNumber(const char*, int*, u8);
|
||||
int sqlite3Strlen30(const char*);
|
||||
#define sqlite3StrNICmp sqlite3_strnicmp
|
||||
|
||||
@ -2502,7 +2545,7 @@ void *sqlite3PageMalloc(int);
|
||||
void sqlite3PageFree(void*);
|
||||
void sqlite3MemSetDefault(void);
|
||||
void sqlite3BenignMallocHooks(void (*)(void), void (*)(void));
|
||||
int sqlite3MemoryAlarm(void (*)(void*, sqlite3_int64, int), void*, sqlite3_int64);
|
||||
int sqlite3HeapNearlyFull(void);
|
||||
|
||||
/*
|
||||
** On systems with ample stack space and that support alloca(), make
|
||||
@ -2673,7 +2716,6 @@ void sqlite3ExprCachePop(Parse*, int);
|
||||
void sqlite3ExprCacheRemove(Parse*, int, int);
|
||||
void sqlite3ExprCacheClear(Parse*);
|
||||
void sqlite3ExprCacheAffinityChange(Parse*, int, int);
|
||||
void sqlite3ExprHardCopy(Parse*,int,int);
|
||||
int sqlite3ExprCode(Parse*, Expr*, int);
|
||||
int sqlite3ExprCodeTemp(Parse*, Expr*, int*);
|
||||
int sqlite3ExprCodeTarget(Parse*, Expr*, int);
|
||||
@ -2793,17 +2835,15 @@ void sqlite3DeferForeignKey(Parse*, int);
|
||||
#endif
|
||||
void sqlite3Attach(Parse*, Expr*, Expr*, Expr*);
|
||||
void sqlite3Detach(Parse*, Expr*);
|
||||
int sqlite3BtreeFactory(sqlite3 *db, const char *zFilename,
|
||||
int omitJournal, int nCache, int flags, Btree **ppBtree);
|
||||
int sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*);
|
||||
int sqlite3FixSrcList(DbFixer*, SrcList*);
|
||||
int sqlite3FixSelect(DbFixer*, Select*);
|
||||
int sqlite3FixExpr(DbFixer*, Expr*);
|
||||
int sqlite3FixExprList(DbFixer*, ExprList*);
|
||||
int sqlite3FixTriggerStep(DbFixer*, TriggerStep*);
|
||||
int sqlite3AtoF(const char *z, double*);
|
||||
int sqlite3AtoF(const char *z, double*, int, u8);
|
||||
int sqlite3GetInt32(const char *, int*);
|
||||
int sqlite3FitsIn64Bits(const char *, int);
|
||||
int sqlite3Atoi(const char*);
|
||||
int sqlite3Utf16ByteLen(const void *pData, int nChar);
|
||||
int sqlite3Utf8CharLen(const char *pData, int nByte);
|
||||
int sqlite3Utf8Read(const u8*, const u8**);
|
||||
@ -2849,7 +2889,7 @@ void sqlite3TableAffinityStr(Vdbe *, Table *);
|
||||
char sqlite3CompareAffinity(Expr *pExpr, char aff2);
|
||||
int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity);
|
||||
char sqlite3ExprAffinity(Expr *pExpr);
|
||||
int sqlite3Atoi64(const char*, i64*);
|
||||
int sqlite3Atoi64(const char*, i64*, int, u8);
|
||||
void sqlite3Error(sqlite3*, int, const char*,...);
|
||||
void *sqlite3HexToBlob(sqlite3*, const char *z, int n);
|
||||
int sqlite3TwoPartName(Parse *, Token *, Token *, Token **);
|
||||
@ -2920,7 +2960,9 @@ int sqlite3SchemaToIndex(sqlite3 *db, Schema *);
|
||||
KeyInfo *sqlite3IndexKeyinfo(Parse *, Index *);
|
||||
int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *,
|
||||
void (*)(sqlite3_context*,int,sqlite3_value **),
|
||||
void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*));
|
||||
void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*),
|
||||
FuncDestructor *pDestructor
|
||||
);
|
||||
int sqlite3ApiExit(sqlite3 *db, int);
|
||||
int sqlite3OpenTempDatabase(Parse *);
|
||||
|
||||
|
||||
16
src/status.c
16
src/status.c
@ -116,6 +116,22 @@ int sqlite3_db_status(
|
||||
break;
|
||||
}
|
||||
|
||||
case SQLITE_DBSTATUS_LOOKASIDE_HIT:
|
||||
case SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE:
|
||||
case SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL: {
|
||||
testcase( op==SQLITE_DBSTATUS_LOOKASIDE_HIT );
|
||||
testcase( op==SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE );
|
||||
testcase( op==SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL );
|
||||
assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)>=0 );
|
||||
assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)<3 );
|
||||
*pCurrent = 0;
|
||||
*pHighwater = db->lookaside.anStat[op - SQLITE_DBSTATUS_LOOKASIDE_HIT];
|
||||
if( resetFlag ){
|
||||
db->lookaside.anStat[op - SQLITE_DBSTATUS_LOOKASIDE_HIT] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return an approximation for the amount of memory currently used
|
||||
** by all pagers associated with the given database connection. The
|
||||
|
||||
@ -670,6 +670,7 @@ static void DbUpdateHandler(
|
||||
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zTbl, -1));
|
||||
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewWideIntObj(rowid));
|
||||
Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT);
|
||||
Tcl_DecrRefCount(pCmd);
|
||||
}
|
||||
|
||||
static void tclCollateNeeded(
|
||||
@ -3014,22 +3015,31 @@ static int DbMain(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
||||
** if the extension only supplies one new name!) The "sqlite" command is
|
||||
** used to open a new SQLite database. See the DbMain() routine above
|
||||
** for additional information.
|
||||
**
|
||||
** The EXTERN macros are required by TCL in order to work on windows.
|
||||
*/
|
||||
int Sqlite3_Init(Tcl_Interp *interp){
|
||||
EXTERN int Sqlite3_Init(Tcl_Interp *interp){
|
||||
Tcl_InitStubs(interp, "8.4", 0);
|
||||
Tcl_CreateObjCommand(interp, "sqlite3", (Tcl_ObjCmdProc*)DbMain, 0, 0);
|
||||
Tcl_PkgProvide(interp, "sqlite3", PACKAGE_VERSION);
|
||||
|
||||
#ifndef SQLITE_3_SUFFIX_ONLY
|
||||
/* The "sqlite" alias is undocumented. It is here only to support
|
||||
** legacy scripts. All new scripts should use only the "sqlite3"
|
||||
** command.
|
||||
*/
|
||||
Tcl_CreateObjCommand(interp, "sqlite", (Tcl_ObjCmdProc*)DbMain, 0, 0);
|
||||
Tcl_PkgProvide(interp, "sqlite", PACKAGE_VERSION);
|
||||
#endif
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
int Tclsqlite3_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); }
|
||||
int Sqlite3_SafeInit(Tcl_Interp *interp){ return TCL_OK; }
|
||||
int Tclsqlite3_SafeInit(Tcl_Interp *interp){ return TCL_OK; }
|
||||
int Sqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; }
|
||||
int Tclsqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; }
|
||||
int Sqlite3_SafeUnload(Tcl_Interp *interp, int flags){ return TCL_OK; }
|
||||
int Tclsqlite3_SafeUnload(Tcl_Interp *interp, int flags){ return TCL_OK;}
|
||||
EXTERN int Tclsqlite3_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); }
|
||||
EXTERN int Sqlite3_SafeInit(Tcl_Interp *interp){ return TCL_OK; }
|
||||
EXTERN int Tclsqlite3_SafeInit(Tcl_Interp *interp){ return TCL_OK; }
|
||||
EXTERN int Sqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; }
|
||||
EXTERN int Tclsqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; }
|
||||
EXTERN int Sqlite3_SafeUnload(Tcl_Interp *interp, int flags){ return TCL_OK; }
|
||||
EXTERN int Tclsqlite3_SafeUnload(Tcl_Interp *interp, int flags){ return TCL_OK;}
|
||||
|
||||
|
||||
#ifndef SQLITE_3_SUFFIX_ONLY
|
||||
@ -3567,6 +3577,15 @@ static void init_all(Tcl_Interp *interp){
|
||||
extern int Sqlitetestintarray_Init(Tcl_Interp*);
|
||||
extern int Sqlitetestvfs_Init(Tcl_Interp *);
|
||||
extern int SqlitetestStat_Init(Tcl_Interp*);
|
||||
extern int Sqlitetestrtree_Init(Tcl_Interp*);
|
||||
extern int Sqlitequota_Init(Tcl_Interp*);
|
||||
extern int Sqlitemultiplex_Init(Tcl_Interp*);
|
||||
extern int SqliteSuperlock_Init(Tcl_Interp*);
|
||||
|
||||
#ifdef SQLITE_ENABLE_ZIPVFS
|
||||
extern int Zipvfs_Init(Tcl_Interp*);
|
||||
Zipvfs_Init(interp);
|
||||
#endif
|
||||
|
||||
Sqliteconfig_Init(interp);
|
||||
Sqlitetest1_Init(interp);
|
||||
@ -3595,6 +3614,10 @@ static void init_all(Tcl_Interp *interp){
|
||||
Sqlitetestintarray_Init(interp);
|
||||
Sqlitetestvfs_Init(interp);
|
||||
SqlitetestStat_Init(interp);
|
||||
Sqlitetestrtree_Init(interp);
|
||||
Sqlitequota_Init(interp);
|
||||
Sqlitemultiplex_Init(interp);
|
||||
SqliteSuperlock_Init(interp);
|
||||
|
||||
Tcl_CreateObjCommand(interp,"load_testfixture_extensions",init_all_cmd,0,0);
|
||||
|
||||
|
||||
440
src/test1.c
440
src/test1.c
@ -1235,7 +1235,7 @@ static int sqlite3_mprintf_int64(
|
||||
return TCL_ERROR;
|
||||
}
|
||||
for(i=2; i<5; i++){
|
||||
if( !sqlite3Atoi64(argv[i], &a[i-2]) ){
|
||||
if( sqlite3Atoi64(argv[i], &a[i-2], 1000000, SQLITE_UTF8) ){
|
||||
Tcl_AppendResult(interp, "argument is not a valid 64-bit integer", 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
@ -1590,6 +1590,81 @@ static int test_table_column_metadata(
|
||||
|
||||
#ifndef SQLITE_OMIT_INCRBLOB
|
||||
|
||||
static int blobHandleFromObj(
|
||||
Tcl_Interp *interp,
|
||||
Tcl_Obj *pObj,
|
||||
sqlite3_blob **ppBlob
|
||||
){
|
||||
char *z;
|
||||
int n;
|
||||
|
||||
z = Tcl_GetStringFromObj(pObj, &n);
|
||||
if( n==0 ){
|
||||
*ppBlob = 0;
|
||||
}else{
|
||||
int notUsed;
|
||||
Tcl_Channel channel;
|
||||
ClientData instanceData;
|
||||
|
||||
channel = Tcl_GetChannel(interp, z, ¬Used);
|
||||
if( !channel ) return TCL_ERROR;
|
||||
|
||||
Tcl_Flush(channel);
|
||||
Tcl_Seek(channel, 0, SEEK_SET);
|
||||
|
||||
instanceData = Tcl_GetChannelInstanceData(channel);
|
||||
*ppBlob = *((sqlite3_blob **)instanceData);
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** sqlite3_blob_bytes CHANNEL
|
||||
*/
|
||||
static int test_blob_bytes(
|
||||
ClientData clientData, /* Not used */
|
||||
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
|
||||
int objc, /* Number of arguments */
|
||||
Tcl_Obj *CONST objv[] /* Command arguments */
|
||||
){
|
||||
sqlite3_blob *pBlob;
|
||||
int nByte;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "CHANNEL");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR;
|
||||
nByte = sqlite3_blob_bytes(pBlob);
|
||||
Tcl_SetObjResult(interp, Tcl_NewIntObj(nByte));
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** sqlite3_blob_close CHANNEL
|
||||
*/
|
||||
static int test_blob_close(
|
||||
ClientData clientData, /* Not used */
|
||||
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
|
||||
int objc, /* Number of arguments */
|
||||
Tcl_Obj *CONST objv[] /* Command arguments */
|
||||
){
|
||||
sqlite3_blob *pBlob;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "CHANNEL");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR;
|
||||
sqlite3_blob_close(pBlob);
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** sqlite3_blob_read CHANNEL OFFSET N
|
||||
**
|
||||
@ -1611,13 +1686,10 @@ static int test_blob_read(
|
||||
int objc, /* Number of arguments */
|
||||
Tcl_Obj *CONST objv[] /* Command arguments */
|
||||
){
|
||||
Tcl_Channel channel;
|
||||
ClientData instanceData;
|
||||
sqlite3_blob *pBlob;
|
||||
int notUsed;
|
||||
int nByte;
|
||||
int iOffset;
|
||||
unsigned char *zBuf;
|
||||
unsigned char *zBuf = 0;
|
||||
int rc;
|
||||
|
||||
if( objc!=4 ){
|
||||
@ -1625,19 +1697,16 @@ static int test_blob_read(
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
channel = Tcl_GetChannel(interp, Tcl_GetString(objv[1]), ¬Used);
|
||||
if( !channel
|
||||
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iOffset)
|
||||
if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR;
|
||||
if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iOffset)
|
||||
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &nByte)
|
||||
|| nByte<0 || iOffset<0
|
||||
){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
instanceData = Tcl_GetChannelInstanceData(channel);
|
||||
pBlob = *((sqlite3_blob **)instanceData);
|
||||
|
||||
zBuf = (unsigned char *)Tcl_Alloc(nByte);
|
||||
if( nByte>0 ){
|
||||
zBuf = (unsigned char *)Tcl_Alloc(nByte);
|
||||
}
|
||||
rc = sqlite3_blob_read(pBlob, zBuf, nByte, iOffset);
|
||||
if( rc==SQLITE_OK ){
|
||||
Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(zBuf, nByte));
|
||||
@ -1669,10 +1738,7 @@ static int test_blob_write(
|
||||
int objc, /* Number of arguments */
|
||||
Tcl_Obj *CONST objv[] /* Command arguments */
|
||||
){
|
||||
Tcl_Channel channel;
|
||||
ClientData instanceData;
|
||||
sqlite3_blob *pBlob;
|
||||
int notUsed;
|
||||
int iOffset;
|
||||
int rc;
|
||||
|
||||
@ -1684,14 +1750,11 @@ static int test_blob_write(
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
channel = Tcl_GetChannel(interp, Tcl_GetString(objv[1]), ¬Used);
|
||||
if( !channel || TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iOffset) ){
|
||||
if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR;
|
||||
if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iOffset) ){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
instanceData = Tcl_GetChannelInstanceData(channel);
|
||||
pBlob = *((sqlite3_blob **)instanceData);
|
||||
|
||||
zBuf = Tcl_GetByteArrayFromObj(objv[3], &nBuf);
|
||||
if( objc==5 && Tcl_GetIntFromObj(interp, objv[4], &nBuf) ){
|
||||
return TCL_ERROR;
|
||||
@ -1703,6 +1766,33 @@ static int test_blob_write(
|
||||
|
||||
return (rc==SQLITE_OK ? TCL_OK : TCL_ERROR);
|
||||
}
|
||||
|
||||
static int test_blob_reopen(
|
||||
ClientData clientData, /* Not used */
|
||||
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
|
||||
int objc, /* Number of arguments */
|
||||
Tcl_Obj *CONST objv[] /* Command arguments */
|
||||
){
|
||||
Tcl_WideInt iRowid;
|
||||
sqlite3_blob *pBlob;
|
||||
int rc;
|
||||
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "CHANNEL ROWID");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR;
|
||||
if( Tcl_GetWideIntFromObj(interp, objv[2], &iRowid) ) return TCL_ERROR;
|
||||
|
||||
rc = sqlite3_blob_reopen(pBlob, iRowid);
|
||||
if( rc!=SQLITE_OK ){
|
||||
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_VOLATILE);
|
||||
}
|
||||
|
||||
return (rc==SQLITE_OK ? TCL_OK : TCL_ERROR);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
@ -1790,6 +1880,129 @@ static int test_create_collation_v2(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** USAGE: sqlite3_create_function_v2 DB NAME NARG ENC ?SWITCHES?
|
||||
**
|
||||
** Available switches are:
|
||||
**
|
||||
** -func SCRIPT
|
||||
** -step SCRIPT
|
||||
** -final SCRIPT
|
||||
** -destroy SCRIPT
|
||||
*/
|
||||
typedef struct CreateFunctionV2 CreateFunctionV2;
|
||||
struct CreateFunctionV2 {
|
||||
Tcl_Interp *interp;
|
||||
Tcl_Obj *pFunc; /* Script for function invocation */
|
||||
Tcl_Obj *pStep; /* Script for agg. step invocation */
|
||||
Tcl_Obj *pFinal; /* Script for agg. finalization invocation */
|
||||
Tcl_Obj *pDestroy; /* Destructor script */
|
||||
};
|
||||
static void cf2Func(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){
|
||||
}
|
||||
static void cf2Step(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){
|
||||
}
|
||||
static void cf2Final(sqlite3_context *ctx){
|
||||
}
|
||||
static void cf2Destroy(void *pUser){
|
||||
CreateFunctionV2 *p = (CreateFunctionV2 *)pUser;
|
||||
|
||||
if( p->interp && p->pDestroy ){
|
||||
int rc = Tcl_EvalObjEx(p->interp, p->pDestroy, 0);
|
||||
if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp);
|
||||
}
|
||||
|
||||
if( p->pFunc ) Tcl_DecrRefCount(p->pFunc);
|
||||
if( p->pStep ) Tcl_DecrRefCount(p->pStep);
|
||||
if( p->pFinal ) Tcl_DecrRefCount(p->pFinal);
|
||||
if( p->pDestroy ) Tcl_DecrRefCount(p->pDestroy);
|
||||
sqlite3_free(p);
|
||||
}
|
||||
static int test_create_function_v2(
|
||||
ClientData clientData, /* Not used */
|
||||
Tcl_Interp *interp, /* The invoking TCL interpreter */
|
||||
int objc, /* Number of arguments */
|
||||
Tcl_Obj *CONST objv[] /* Command arguments */
|
||||
){
|
||||
sqlite3 *db;
|
||||
const char *zFunc;
|
||||
int nArg;
|
||||
int enc;
|
||||
CreateFunctionV2 *p;
|
||||
int i;
|
||||
int rc;
|
||||
|
||||
struct EncTable {
|
||||
const char *zEnc;
|
||||
int enc;
|
||||
} aEnc[] = {
|
||||
{"utf8", SQLITE_UTF8 },
|
||||
{"utf16", SQLITE_UTF16 },
|
||||
{"utf16le", SQLITE_UTF16LE },
|
||||
{"utf16be", SQLITE_UTF16BE },
|
||||
{"any", SQLITE_ANY },
|
||||
{"0", 0 }
|
||||
};
|
||||
|
||||
if( objc<5 || (objc%2)==0 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB NAME NARG ENC SWITCHES...");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
|
||||
zFunc = Tcl_GetString(objv[2]);
|
||||
if( Tcl_GetIntFromObj(interp, objv[3], &nArg) ) return TCL_ERROR;
|
||||
if( Tcl_GetIndexFromObjStruct(interp, objv[4], aEnc, sizeof(aEnc[0]),
|
||||
"encoding", 0, &enc)
|
||||
){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
enc = aEnc[enc].enc;
|
||||
|
||||
p = sqlite3_malloc(sizeof(CreateFunctionV2));
|
||||
assert( p );
|
||||
memset(p, 0, sizeof(CreateFunctionV2));
|
||||
p->interp = interp;
|
||||
|
||||
for(i=5; i<objc; i+=2){
|
||||
int iSwitch;
|
||||
const char *azSwitch[] = {"-func", "-step", "-final", "-destroy", 0};
|
||||
if( Tcl_GetIndexFromObj(interp, objv[i], azSwitch, "switch", 0, &iSwitch) ){
|
||||
sqlite3_free(p);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
switch( iSwitch ){
|
||||
case 0: p->pFunc = objv[i+1]; break;
|
||||
case 1: p->pStep = objv[i+1]; break;
|
||||
case 2: p->pFinal = objv[i+1]; break;
|
||||
case 3: p->pDestroy = objv[i+1]; break;
|
||||
}
|
||||
}
|
||||
if( p->pFunc ) p->pFunc = Tcl_DuplicateObj(p->pFunc);
|
||||
if( p->pStep ) p->pStep = Tcl_DuplicateObj(p->pStep);
|
||||
if( p->pFinal ) p->pFinal = Tcl_DuplicateObj(p->pFinal);
|
||||
if( p->pDestroy ) p->pDestroy = Tcl_DuplicateObj(p->pDestroy);
|
||||
|
||||
if( p->pFunc ) Tcl_IncrRefCount(p->pFunc);
|
||||
if( p->pStep ) Tcl_IncrRefCount(p->pStep);
|
||||
if( p->pFinal ) Tcl_IncrRefCount(p->pFinal);
|
||||
if( p->pDestroy ) Tcl_IncrRefCount(p->pDestroy);
|
||||
|
||||
rc = sqlite3_create_function_v2(db, zFunc, nArg, enc, (void *)p,
|
||||
(p->pFunc ? cf2Func : 0),
|
||||
(p->pStep ? cf2Step : 0),
|
||||
(p->pFinal ? cf2Final : 0),
|
||||
cf2Destroy
|
||||
);
|
||||
if( rc!=SQLITE_OK ){
|
||||
Tcl_ResetResult(interp);
|
||||
Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Usage: sqlite3_load_extension DB-HANDLE FILE ?PROC?
|
||||
*/
|
||||
@ -2086,6 +2299,33 @@ static int test_next_stmt(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Usage: sqlite3_stmt_readonly STMT
|
||||
**
|
||||
** Return true if STMT is a NULL pointer or a pointer to a statement
|
||||
** that is guaranteed to leave the database unmodified.
|
||||
*/
|
||||
static int test_stmt_readonly(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3_stmt *pStmt;
|
||||
int rc;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_AppendResult(interp, "wrong # args: should be \"",
|
||||
Tcl_GetStringFromObj(objv[0], 0), " STMT", 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
|
||||
rc = sqlite3_stmt_readonly(pStmt);
|
||||
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(rc));
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Usage: sqlite3_reset STMT
|
||||
@ -2291,7 +2531,7 @@ static int test_collate_func(
|
||||
int nB, const void *zB
|
||||
){
|
||||
Tcl_Interp *i = pTestCollateInterp;
|
||||
int encin = (int)pCtx;
|
||||
int encin = SQLITE_PTR_TO_INT(pCtx);
|
||||
int res;
|
||||
int n;
|
||||
|
||||
@ -2420,7 +2660,7 @@ static void test_collate_needed_cb(
|
||||
}
|
||||
zNeededCollation[i] = 0;
|
||||
sqlite3_create_collation(
|
||||
db, "test_collate", ENC(db), (void *)enc, test_collate_func);
|
||||
db, "test_collate", ENC(db), SQLITE_INT_TO_PTR(enc), test_collate_func);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2469,8 +2709,8 @@ static int alignmentCollFunc(
|
||||
){
|
||||
int rc, n;
|
||||
n = nKey1<nKey2 ? nKey1 : nKey2;
|
||||
if( nKey1>0 && 1==(1&(int)pKey1) ) unaligned_string_counter++;
|
||||
if( nKey2>0 && 1==(1&(int)pKey2) ) unaligned_string_counter++;
|
||||
if( nKey1>0 && 1==(1&(SQLITE_PTR_TO_INT(pKey1))) ) unaligned_string_counter++;
|
||||
if( nKey2>0 && 1==(1&(SQLITE_PTR_TO_INT(pKey2))) ) unaligned_string_counter++;
|
||||
rc = memcmp(pKey1, pKey2, n);
|
||||
if( rc==0 ){
|
||||
rc = nKey1 - nKey2;
|
||||
@ -4266,20 +4506,17 @@ static int test_soft_heap_limit(
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
static int softHeapLimit = 0;
|
||||
int amt;
|
||||
sqlite3_int64 amt;
|
||||
sqlite3_int64 N = -1;
|
||||
if( objc!=1 && objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "?N?");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
amt = softHeapLimit;
|
||||
if( objc==2 ){
|
||||
int N;
|
||||
if( Tcl_GetIntFromObj(interp, objv[1], &N) ) return TCL_ERROR;
|
||||
sqlite3_soft_heap_limit(N);
|
||||
softHeapLimit = N;
|
||||
if( Tcl_GetWideIntFromObj(interp, objv[1], &N) ) return TCL_ERROR;
|
||||
}
|
||||
Tcl_SetObjResult(interp, Tcl_NewIntObj(amt));
|
||||
amt = sqlite3_soft_heap_limit64(N);
|
||||
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(amt));
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
@ -4564,13 +4801,13 @@ static int file_control_test(
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
|
||||
rc = sqlite3_file_control(db, 0, 0, &iArg);
|
||||
assert( rc==SQLITE_ERROR );
|
||||
assert( rc==SQLITE_NOTFOUND );
|
||||
rc = sqlite3_file_control(db, "notadatabase", SQLITE_FCNTL_LOCKSTATE, &iArg);
|
||||
assert( rc==SQLITE_ERROR );
|
||||
rc = sqlite3_file_control(db, "main", -1, &iArg);
|
||||
assert( rc==SQLITE_ERROR );
|
||||
assert( rc==SQLITE_NOTFOUND );
|
||||
rc = sqlite3_file_control(db, "temp", -1, &iArg);
|
||||
assert( rc==SQLITE_ERROR );
|
||||
assert( rc==SQLITE_NOTFOUND || rc==SQLITE_ERROR );
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
@ -5021,6 +5258,122 @@ static int runAsObjProc(
|
||||
return cmdInfo.objProc(cmdInfo.objClientData, interp, objc-1, objv+1);
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_EXPLAIN
|
||||
/*
|
||||
** WARNING: The following function, printExplainQueryPlan() is an exact
|
||||
** copy of example code from eqp.in (eqp.html). If this code is modified,
|
||||
** then the documentation copy needs to be modified as well.
|
||||
*/
|
||||
/*
|
||||
** Argument pStmt is a prepared SQL statement. This function compiles
|
||||
** an EXPLAIN QUERY PLAN command to report on the prepared statement,
|
||||
** and prints the report to stdout using printf().
|
||||
*/
|
||||
int printExplainQueryPlan(sqlite3_stmt *pStmt){
|
||||
const char *zSql; /* Input SQL */
|
||||
char *zExplain; /* SQL with EXPLAIN QUERY PLAN prepended */
|
||||
sqlite3_stmt *pExplain; /* Compiled EXPLAIN QUERY PLAN command */
|
||||
int rc; /* Return code from sqlite3_prepare_v2() */
|
||||
|
||||
zSql = sqlite3_sql(pStmt);
|
||||
if( zSql==0 ) return SQLITE_ERROR;
|
||||
|
||||
zExplain = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zSql);
|
||||
if( zExplain==0 ) return SQLITE_NOMEM;
|
||||
|
||||
rc = sqlite3_prepare_v2(sqlite3_db_handle(pStmt), zExplain, -1, &pExplain, 0);
|
||||
sqlite3_free(zExplain);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
while( SQLITE_ROW==sqlite3_step(pExplain) ){
|
||||
int iSelectid = sqlite3_column_int(pExplain, 0);
|
||||
int iOrder = sqlite3_column_int(pExplain, 1);
|
||||
int iFrom = sqlite3_column_int(pExplain, 2);
|
||||
const char *zDetail = (const char *)sqlite3_column_text(pExplain, 3);
|
||||
|
||||
printf("%d %d %d %s\n", iSelectid, iOrder, iFrom, zDetail);
|
||||
}
|
||||
|
||||
return sqlite3_finalize(pExplain);
|
||||
}
|
||||
|
||||
static int test_print_eqp(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int rc;
|
||||
sqlite3_stmt *pStmt;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "STMT");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
|
||||
rc = printExplainQueryPlan(pStmt);
|
||||
Tcl_SetResult(interp, (char *)t1ErrorName(rc), 0);
|
||||
return TCL_OK;
|
||||
}
|
||||
#endif /* SQLITE_OMIT_EXPLAIN */
|
||||
|
||||
/*
|
||||
** optimization_control DB OPT BOOLEAN
|
||||
**
|
||||
** Enable or disable query optimizations using the sqlite3_test_control()
|
||||
** interface. Disable if BOOLEAN is false and enable if BOOLEAN is true.
|
||||
** OPT is the name of the optimization to be disabled.
|
||||
*/
|
||||
static int optimization_control(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int i;
|
||||
sqlite3 *db;
|
||||
const char *zOpt;
|
||||
int onoff;
|
||||
int mask;
|
||||
static const struct {
|
||||
const char *zOptName;
|
||||
int mask;
|
||||
} aOpt[] = {
|
||||
{ "all", SQLITE_OptMask },
|
||||
{ "query-flattener", SQLITE_QueryFlattener },
|
||||
{ "column-cache", SQLITE_ColumnCache },
|
||||
{ "index-sort", SQLITE_IndexSort },
|
||||
{ "index-search", SQLITE_IndexSearch },
|
||||
{ "index-cover", SQLITE_IndexCover },
|
||||
{ "groupby-order", SQLITE_GroupByOrder },
|
||||
{ "factor-constants", SQLITE_FactorOutConst },
|
||||
};
|
||||
|
||||
if( objc!=4 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB OPT BOOLEAN");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
|
||||
if( Tcl_GetBooleanFromObj(interp, objv[3], &onoff) ) return TCL_ERROR;
|
||||
zOpt = Tcl_GetString(objv[2]);
|
||||
for(i=0; i<sizeof(aOpt)/sizeof(aOpt[0]); i++){
|
||||
if( strcmp(zOpt, aOpt[i].zOptName)==0 ){
|
||||
mask = aOpt[i].mask;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( onoff ) mask = ~mask;
|
||||
if( i>=sizeof(aOpt)/sizeof(aOpt[0]) ){
|
||||
Tcl_AppendResult(interp, "unknown optimization - should be one of:",
|
||||
(char*)0);
|
||||
for(i=0; i<sizeof(aOpt)/sizeof(aOpt[0]); i++){
|
||||
Tcl_AppendResult(interp, " ", aOpt[i].zOptName);
|
||||
}
|
||||
return TCL_ERROR;
|
||||
}
|
||||
sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, db, mask);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Register commands with the TCL interpreter.
|
||||
@ -5124,6 +5477,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
|
||||
{ "sqlite3_step", test_step ,0 },
|
||||
{ "sqlite3_sql", test_sql ,0 },
|
||||
{ "sqlite3_next_stmt", test_next_stmt ,0 },
|
||||
{ "sqlite3_stmt_readonly", test_stmt_readonly ,0 },
|
||||
|
||||
{ "sqlite3_release_memory", test_release_memory, 0},
|
||||
{ "sqlite3_soft_heap_limit", test_soft_heap_limit, 0},
|
||||
@ -5138,6 +5492,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
|
||||
{ "save_prng_state", save_prng_state, 0 },
|
||||
{ "restore_prng_state", restore_prng_state, 0 },
|
||||
{ "reset_prng_state", reset_prng_state, 0 },
|
||||
{ "optimization_control", optimization_control,0},
|
||||
{ "tcl_objproc", runAsObjProc, 0 },
|
||||
|
||||
/* sqlite3_column_*() API */
|
||||
@ -5187,6 +5542,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
|
||||
{ "file_control_lockproxy_test", file_control_lockproxy_test, 0 },
|
||||
{ "file_control_chunksize_test", file_control_chunksize_test, 0 },
|
||||
{ "sqlite3_vfs_list", vfs_list, 0 },
|
||||
{ "sqlite3_create_function_v2", test_create_function_v2, 0 },
|
||||
|
||||
/* Functions from os.h */
|
||||
#ifndef SQLITE_OMIT_UTF16
|
||||
@ -5205,15 +5561,19 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
|
||||
{ "sqlite3_table_column_metadata", test_table_column_metadata, 0 },
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_INCRBLOB
|
||||
{ "sqlite3_blob_read", test_blob_read, 0 },
|
||||
{ "sqlite3_blob_write", test_blob_write, 0 },
|
||||
{ "sqlite3_blob_read", test_blob_read, 0 },
|
||||
{ "sqlite3_blob_write", test_blob_write, 0 },
|
||||
{ "sqlite3_blob_reopen", test_blob_reopen, 0 },
|
||||
{ "sqlite3_blob_bytes", test_blob_bytes, 0 },
|
||||
{ "sqlite3_blob_close", test_blob_close, 0 },
|
||||
#endif
|
||||
{ "pcache_stats", test_pcache_stats, 0 },
|
||||
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
|
||||
{ "sqlite3_unlock_notify", test_unlock_notify, 0 },
|
||||
#endif
|
||||
{ "sqlite3_wal_checkpoint", test_wal_checkpoint, 0 },
|
||||
{ "test_sqlite3_log", test_sqlite3_log, 0 },
|
||||
{ "sqlite3_wal_checkpoint", test_wal_checkpoint, 0 },
|
||||
{ "test_sqlite3_log", test_sqlite3_log, 0 },
|
||||
{ "print_explain_query_plan", test_print_eqp, 0 },
|
||||
};
|
||||
static int bitmask_size = sizeof(Bitmask)*8;
|
||||
int i;
|
||||
|
||||
@ -53,7 +53,7 @@ static sqlite3 sDb;
|
||||
static int nRefSqlite3 = 0;
|
||||
|
||||
/*
|
||||
** Usage: btree_open FILENAME NCACHE FLAGS
|
||||
** Usage: btree_open FILENAME NCACHE
|
||||
**
|
||||
** Open a new database
|
||||
*/
|
||||
@ -64,22 +64,21 @@ static int btree_open(
|
||||
const char **argv /* Text of each argument */
|
||||
){
|
||||
Btree *pBt;
|
||||
int rc, nCache, flags;
|
||||
int rc, nCache;
|
||||
char zBuf[100];
|
||||
if( argc!=4 ){
|
||||
if( argc!=3 ){
|
||||
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
|
||||
" FILENAME NCACHE FLAGS\"", 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( Tcl_GetInt(interp, argv[2], &nCache) ) return TCL_ERROR;
|
||||
if( Tcl_GetInt(interp, argv[3], &flags) ) return TCL_ERROR;
|
||||
nRefSqlite3++;
|
||||
if( nRefSqlite3==1 ){
|
||||
sDb.pVfs = sqlite3_vfs_find(0);
|
||||
sDb.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE);
|
||||
sqlite3_mutex_enter(sDb.mutex);
|
||||
}
|
||||
rc = sqlite3BtreeOpen(argv[1], &sDb, &pBt, flags,
|
||||
rc = sqlite3BtreeOpen(argv[1], &sDb, &pBt, 0,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB);
|
||||
if( rc!=SQLITE_OK ){
|
||||
Tcl_AppendResult(interp, errorName(rc), 0);
|
||||
|
||||
@ -133,6 +133,12 @@ static void set_options(Tcl_Interp *interp){
|
||||
Tcl_SetVar2(interp, "sqlite_options", "autoindex", "1", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_OMIT_AUTORESET
|
||||
Tcl_SetVar2(interp, "sqlite_options", "autoreset", "0", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
Tcl_SetVar2(interp, "sqlite_options", "autoreset", "1", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_OMIT_AUTOVACUUM
|
||||
Tcl_SetVar2(interp, "sqlite_options", "autovacuum", "0", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
@ -523,6 +529,12 @@ Tcl_SetVar2(interp, "sqlite_options", "long_double",
|
||||
Tcl_SetVar2(interp, "sqlite_options", "secure_delete", "0", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_MULTIPLEX_EXT_OVWR
|
||||
Tcl_SetVar2(interp, "sqlite_options", "multiplex_ext_overwrite", "1", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
Tcl_SetVar2(interp, "sqlite_options", "multiplex_ext_overwrite", "0", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifdef YYTRACKMAXSTACKDEPTH
|
||||
Tcl_SetVar2(interp, "sqlite_options", "yytrackmaxstackdepth", "1", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
|
||||
@ -1290,10 +1290,13 @@ static int test_db_status(
|
||||
const char *zName;
|
||||
int op;
|
||||
} aOp[] = {
|
||||
{ "SQLITE_DBSTATUS_LOOKASIDE_USED", SQLITE_DBSTATUS_LOOKASIDE_USED },
|
||||
{ "SQLITE_DBSTATUS_CACHE_USED", SQLITE_DBSTATUS_CACHE_USED },
|
||||
{ "SQLITE_DBSTATUS_SCHEMA_USED", SQLITE_DBSTATUS_SCHEMA_USED },
|
||||
{ "SQLITE_DBSTATUS_STMT_USED", SQLITE_DBSTATUS_STMT_USED }
|
||||
{ "LOOKASIDE_USED", SQLITE_DBSTATUS_LOOKASIDE_USED },
|
||||
{ "CACHE_USED", SQLITE_DBSTATUS_CACHE_USED },
|
||||
{ "SCHEMA_USED", SQLITE_DBSTATUS_SCHEMA_USED },
|
||||
{ "STMT_USED", SQLITE_DBSTATUS_STMT_USED },
|
||||
{ "LOOKASIDE_HIT", SQLITE_DBSTATUS_LOOKASIDE_HIT },
|
||||
{ "LOOKASIDE_MISS_SIZE", SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE },
|
||||
{ "LOOKASIDE_MISS_FULL", SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL }
|
||||
};
|
||||
Tcl_Obj *pResult;
|
||||
if( objc!=4 ){
|
||||
@ -1302,6 +1305,8 @@ static int test_db_status(
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
|
||||
zOpName = Tcl_GetString(objv[2]);
|
||||
if( memcmp(zOpName, "SQLITE_", 7)==0 ) zOpName += 7;
|
||||
if( memcmp(zOpName, "DBSTATUS_", 9)==0 ) zOpName += 9;
|
||||
for(i=0; i<ArraySize(aOp); i++){
|
||||
if( strcmp(aOp[i].zName, zOpName)==0 ){
|
||||
op = aOp[i].op;
|
||||
|
||||
973
src/test_multiplex.c
Normal file
973
src/test_multiplex.c
Normal file
@ -0,0 +1,973 @@
|
||||
/*
|
||||
** 2010 October 28
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
**
|
||||
** This file contains a VFS "shim" - a layer that sits in between the
|
||||
** pager and the real VFS.
|
||||
**
|
||||
** This particular shim enforces a multiplex system on DB files.
|
||||
** This shim shards/partitions a single DB file into smaller
|
||||
** "chunks" such that the total DB file size may exceed the maximum
|
||||
** file size of the underlying file system.
|
||||
**
|
||||
*/
|
||||
#include "sqlite3.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "sqliteInt.h"
|
||||
|
||||
/************************ Shim Definitions ******************************/
|
||||
|
||||
/* This is the limit on the chunk size. It may be changed by calling
|
||||
** the sqlite3_multiplex_set() interface.
|
||||
*/
|
||||
#define SQLITE_MULTIPLEX_CHUNK_SIZE 0x40000000
|
||||
/* Default limit on number of chunks. Care should be taken
|
||||
** so that values for chunks numbers fit in the SQLITE_MULTIPLEX_EXT_FMT
|
||||
** format specifier. It may be changed by calling
|
||||
** the sqlite3_multiplex_set() interface.
|
||||
*/
|
||||
#define SQLITE_MULTIPLEX_MAX_CHUNKS 32
|
||||
|
||||
/* If SQLITE_MULTIPLEX_EXT_OVWR is defined, the
|
||||
** last SQLITE_MULTIPLEX_EXT_SZ characters of the
|
||||
** filename will be overwritten, otherwise, the
|
||||
** multiplex extension is simply appended to the filename.
|
||||
** Ex. (undefined) test.db -> test.db01
|
||||
** (defined) test.db -> test.01
|
||||
** Chunk 0 does not have a modified extension.
|
||||
*/
|
||||
#define SQLITE_MULTIPLEX_EXT_FMT "%02d"
|
||||
#define SQLITE_MULTIPLEX_EXT_SZ 2
|
||||
|
||||
/************************ Object Definitions ******************************/
|
||||
|
||||
/* Forward declaration of all object types */
|
||||
typedef struct multiplexGroup multiplexGroup;
|
||||
typedef struct multiplexConn multiplexConn;
|
||||
|
||||
/*
|
||||
** A "multiplex group" is a collection of files that collectively
|
||||
** makeup a single SQLite DB file. This allows the size of the DB
|
||||
** to exceed the limits imposed by the file system.
|
||||
**
|
||||
** There is an instance of the following object for each defined multiplex
|
||||
** group.
|
||||
*/
|
||||
struct multiplexGroup {
|
||||
sqlite3_file **pReal; /* Handles to each chunk */
|
||||
char *bOpen; /* 0 if chunk not opened */
|
||||
char *zName; /* Base filename of this group */
|
||||
int nName; /* Length of base filename */
|
||||
int flags; /* Flags used for original opening */
|
||||
multiplexGroup *pNext, *pPrev; /* Doubly linked list of all group objects */
|
||||
};
|
||||
|
||||
/*
|
||||
** An instance of the following object represents each open connection
|
||||
** to a file that is multiplex'ed. This object is a
|
||||
** subclass of sqlite3_file. The sqlite3_file object for the underlying
|
||||
** VFS is appended to this structure.
|
||||
*/
|
||||
struct multiplexConn {
|
||||
sqlite3_file base; /* Base class - must be first */
|
||||
multiplexGroup *pGroup; /* The underlying group of files */
|
||||
};
|
||||
|
||||
/************************* Global Variables **********************************/
|
||||
/*
|
||||
** All global variables used by this file are containing within the following
|
||||
** gMultiplex structure.
|
||||
*/
|
||||
static struct {
|
||||
/* The pOrigVfs is the real, original underlying VFS implementation.
|
||||
** Most operations pass-through to the real VFS. This value is read-only
|
||||
** during operation. It is only modified at start-time and thus does not
|
||||
** require a mutex.
|
||||
*/
|
||||
sqlite3_vfs *pOrigVfs;
|
||||
|
||||
/* The sThisVfs is the VFS structure used by this shim. It is initialized
|
||||
** at start-time and thus does not require a mutex
|
||||
*/
|
||||
sqlite3_vfs sThisVfs;
|
||||
|
||||
/* The sIoMethods defines the methods used by sqlite3_file objects
|
||||
** associated with this shim. It is initialized at start-time and does
|
||||
** not require a mutex.
|
||||
**
|
||||
** When the underlying VFS is called to open a file, it might return
|
||||
** either a version 1 or a version 2 sqlite3_file object. This shim
|
||||
** has to create a wrapper sqlite3_file of the same version. Hence
|
||||
** there are two I/O method structures, one for version 1 and the other
|
||||
** for version 2.
|
||||
*/
|
||||
sqlite3_io_methods sIoMethodsV1;
|
||||
sqlite3_io_methods sIoMethodsV2;
|
||||
|
||||
/* True when this shim has been initialized.
|
||||
*/
|
||||
int isInitialized;
|
||||
|
||||
/* For run-time access any of the other global data structures in this
|
||||
** shim, the following mutex must be held.
|
||||
*/
|
||||
sqlite3_mutex *pMutex;
|
||||
|
||||
/* List of multiplexGroup objects.
|
||||
*/
|
||||
multiplexGroup *pGroups;
|
||||
|
||||
/* Chunk params.
|
||||
*/
|
||||
int nChunkSize;
|
||||
int nMaxChunks;
|
||||
|
||||
/* Storage for temp file names. Allocated during
|
||||
** initialization to the max pathname of the underlying VFS.
|
||||
*/
|
||||
char *zName;
|
||||
|
||||
} gMultiplex;
|
||||
|
||||
/************************* Utility Routines *********************************/
|
||||
/*
|
||||
** Acquire and release the mutex used to serialize access to the
|
||||
** list of multiplexGroups.
|
||||
*/
|
||||
static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); }
|
||||
static void multiplexLeave(void){ sqlite3_mutex_leave(gMultiplex.pMutex); }
|
||||
|
||||
/* Translate an sqlite3_file* that is really a multiplexGroup* into
|
||||
** the sqlite3_file* for the underlying original VFS.
|
||||
*/
|
||||
static sqlite3_file *multiplexSubOpen(multiplexConn *pConn, int iChunk, int *rc, int *pOutFlags){
|
||||
multiplexGroup *pGroup = pConn->pGroup;
|
||||
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */
|
||||
if( iChunk<gMultiplex.nMaxChunks ){
|
||||
sqlite3_file *pSubOpen = pGroup->pReal[iChunk]; /* Real file descriptor */
|
||||
if( !pGroup->bOpen[iChunk] ){
|
||||
memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
|
||||
if( iChunk ){
|
||||
#ifdef SQLITE_MULTIPLEX_EXT_OVWR
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, iChunk);
|
||||
#else
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, iChunk);
|
||||
#endif
|
||||
}
|
||||
*rc = pOrigVfs->xOpen(pOrigVfs, gMultiplex.zName, pSubOpen, pGroup->flags, pOutFlags);
|
||||
if( *rc==SQLITE_OK ){
|
||||
pGroup->bOpen[iChunk] = -1;
|
||||
return pSubOpen;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
*rc = SQLITE_OK;
|
||||
return pSubOpen;
|
||||
}
|
||||
*rc = SQLITE_FULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/************************* VFS Method Wrappers *****************************/
|
||||
|
||||
/*
|
||||
** This is the xOpen method used for the "multiplex" VFS.
|
||||
**
|
||||
** Most of the work is done by the underlying original VFS. This method
|
||||
** simply links the new file into the appropriate multiplex group if it is a
|
||||
** file that needs to be tracked.
|
||||
*/
|
||||
static int multiplexOpen(
|
||||
sqlite3_vfs *pVfs, /* The multiplex VFS */
|
||||
const char *zName, /* Name of file to be opened */
|
||||
sqlite3_file *pConn, /* Fill in this file descriptor */
|
||||
int flags, /* Flags to control the opening */
|
||||
int *pOutFlags /* Flags showing results of opening */
|
||||
){
|
||||
int rc; /* Result code */
|
||||
multiplexConn *pMultiplexOpen; /* The new multiplex file descriptor */
|
||||
multiplexGroup *pGroup; /* Corresponding multiplexGroup object */
|
||||
sqlite3_file *pSubOpen; /* Real file descriptor */
|
||||
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */
|
||||
int nName = sqlite3Strlen30(zName);
|
||||
int i;
|
||||
int sz;
|
||||
|
||||
UNUSED_PARAMETER(pVfs);
|
||||
|
||||
/* We need to create a group structure and manage
|
||||
** access to this group of files.
|
||||
*/
|
||||
multiplexEnter();
|
||||
pMultiplexOpen = (multiplexConn*)pConn;
|
||||
/* allocate space for group */
|
||||
sz = sizeof(multiplexGroup) /* multiplexGroup */
|
||||
+ (sizeof(sqlite3_file *)*gMultiplex.nMaxChunks) /* pReal[] */
|
||||
+ (pOrigVfs->szOsFile*gMultiplex.nMaxChunks) /* *pReal */
|
||||
+ gMultiplex.nMaxChunks /* bOpen[] */
|
||||
+ nName + 1; /* zName */
|
||||
#ifndef SQLITE_MULTIPLEX_EXT_OVWR
|
||||
sz += SQLITE_MULTIPLEX_EXT_SZ;
|
||||
assert(nName+SQLITE_MULTIPLEX_EXT_SZ < pOrigVfs->mxPathname);
|
||||
#else
|
||||
assert(nName >= SQLITE_MULTIPLEX_EXT_SZ);
|
||||
assert(nName < pOrigVfs->mxPathname);
|
||||
#endif
|
||||
pGroup = sqlite3_malloc( sz );
|
||||
if( pGroup==0 ){
|
||||
rc=SQLITE_NOMEM;
|
||||
}else{
|
||||
/* assign pointers to extra space allocated */
|
||||
char *p = (char *)&pGroup[1];
|
||||
pMultiplexOpen->pGroup = pGroup;
|
||||
memset(pGroup, 0, sz);
|
||||
pGroup->pReal = (sqlite3_file **)p;
|
||||
p += (sizeof(sqlite3_file *)*gMultiplex.nMaxChunks);
|
||||
for(i=0; i<gMultiplex.nMaxChunks; i++){
|
||||
pGroup->pReal[i] = (sqlite3_file *)p;
|
||||
p += pOrigVfs->szOsFile;
|
||||
}
|
||||
pGroup->bOpen = p;
|
||||
p += gMultiplex.nMaxChunks;
|
||||
pGroup->zName = p;
|
||||
/* save off base filename, name length, and original open flags */
|
||||
memcpy(pGroup->zName, zName, nName+1);
|
||||
pGroup->nName = nName;
|
||||
pGroup->flags = flags;
|
||||
pSubOpen = multiplexSubOpen(pMultiplexOpen, 0, &rc, pOutFlags);
|
||||
if( pSubOpen ){
|
||||
if( pSubOpen->pMethods->iVersion==1 ){
|
||||
pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV1;
|
||||
}else{
|
||||
pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV2;
|
||||
}
|
||||
/* place this group at the head of our list */
|
||||
pGroup->pNext = gMultiplex.pGroups;
|
||||
if( gMultiplex.pGroups ) gMultiplex.pGroups->pPrev = pGroup;
|
||||
gMultiplex.pGroups = pGroup;
|
||||
}else{
|
||||
sqlite3_free(pGroup);
|
||||
}
|
||||
}
|
||||
multiplexLeave();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This is the xDelete method used for the "multiplex" VFS.
|
||||
** It attempts to delete the filename specified, as well
|
||||
** as additional files with the SQLITE_MULTIPLEX_EXT_FMT extension.
|
||||
*/
|
||||
static int multiplexDelete(
|
||||
sqlite3_vfs *pVfs, /* The multiplex VFS */
|
||||
const char *zName, /* Name of file to delete */
|
||||
int syncDir
|
||||
){
|
||||
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */
|
||||
int rc = SQLITE_OK;
|
||||
int nName = sqlite3Strlen30(zName);
|
||||
int i;
|
||||
|
||||
UNUSED_PARAMETER(pVfs);
|
||||
|
||||
multiplexEnter();
|
||||
memcpy(gMultiplex.zName, zName, nName+1);
|
||||
for(i=0; i<gMultiplex.nMaxChunks; i++){
|
||||
int rc2;
|
||||
int exists = 0;
|
||||
if( i ){
|
||||
#ifdef SQLITE_MULTIPLEX_EXT_OVWR
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#else
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+nName, SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#endif
|
||||
}
|
||||
rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, SQLITE_ACCESS_EXISTS, &exists);
|
||||
if( rc2==SQLITE_OK && exists){
|
||||
/* if it exists, delete it */
|
||||
rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, syncDir);
|
||||
if( rc2!=SQLITE_OK ) rc = rc2;
|
||||
}else{
|
||||
/* stop at first "gap" */
|
||||
break;
|
||||
}
|
||||
}
|
||||
multiplexLeave();
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int multiplexAccess(sqlite3_vfs *a, const char *b, int c, int *d){
|
||||
return gMultiplex.pOrigVfs->xAccess(gMultiplex.pOrigVfs, b, c, d);
|
||||
}
|
||||
static int multiplexFullPathname(sqlite3_vfs *a, const char *b, int c, char *d){
|
||||
return gMultiplex.pOrigVfs->xFullPathname(gMultiplex.pOrigVfs, b, c, d);
|
||||
}
|
||||
static void *multiplexDlOpen(sqlite3_vfs *a, const char *b){
|
||||
return gMultiplex.pOrigVfs->xDlOpen(gMultiplex.pOrigVfs, b);
|
||||
}
|
||||
static void multiplexDlError(sqlite3_vfs *a, int b, char *c){
|
||||
gMultiplex.pOrigVfs->xDlError(gMultiplex.pOrigVfs, b, c);
|
||||
}
|
||||
static void (*multiplexDlSym(sqlite3_vfs *a, void *b, const char *c))(void){
|
||||
return gMultiplex.pOrigVfs->xDlSym(gMultiplex.pOrigVfs, b, c);
|
||||
}
|
||||
static void multiplexDlClose(sqlite3_vfs *a, void *b){
|
||||
gMultiplex.pOrigVfs->xDlClose(gMultiplex.pOrigVfs, b);
|
||||
}
|
||||
static int multiplexRandomness(sqlite3_vfs *a, int b, char *c){
|
||||
return gMultiplex.pOrigVfs->xRandomness(gMultiplex.pOrigVfs, b, c);
|
||||
}
|
||||
static int multiplexSleep(sqlite3_vfs *a, int b){
|
||||
return gMultiplex.pOrigVfs->xSleep(gMultiplex.pOrigVfs, b);
|
||||
}
|
||||
static int multiplexCurrentTime(sqlite3_vfs *a, double *b){
|
||||
return gMultiplex.pOrigVfs->xCurrentTime(gMultiplex.pOrigVfs, b);
|
||||
}
|
||||
static int multiplexGetLastError(sqlite3_vfs *a, int b, char *c){
|
||||
return gMultiplex.pOrigVfs->xGetLastError(gMultiplex.pOrigVfs, b, c);
|
||||
}
|
||||
static int multiplexCurrentTimeInt64(sqlite3_vfs *a, sqlite3_int64 *b){
|
||||
return gMultiplex.pOrigVfs->xCurrentTimeInt64(gMultiplex.pOrigVfs, b);
|
||||
}
|
||||
|
||||
/************************ I/O Method Wrappers *******************************/
|
||||
|
||||
/* xClose requests get passed through to the original VFS.
|
||||
** We loop over all open chunk handles and close them.
|
||||
** The group structure for this file is unlinked from
|
||||
** our list of groups and freed.
|
||||
*/
|
||||
static int multiplexClose(sqlite3_file *pConn){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
multiplexGroup *pGroup = p->pGroup;
|
||||
int rc = SQLITE_OK;
|
||||
int i;
|
||||
multiplexEnter();
|
||||
/* close any open handles */
|
||||
for(i=0; i<gMultiplex.nMaxChunks; i++){
|
||||
if( pGroup->bOpen[i] ){
|
||||
sqlite3_file *pSubOpen = pGroup->pReal[i];
|
||||
int rc2 = pSubOpen->pMethods->xClose(pSubOpen);
|
||||
if( rc2!=SQLITE_OK ) rc = rc2;
|
||||
pGroup->bOpen[i] = 0;
|
||||
}
|
||||
}
|
||||
/* remove from linked list */
|
||||
if( pGroup->pNext ) pGroup->pNext->pPrev = pGroup->pPrev;
|
||||
if( pGroup->pPrev ){
|
||||
pGroup->pPrev->pNext = pGroup->pNext;
|
||||
}else{
|
||||
gMultiplex.pGroups = pGroup->pNext;
|
||||
}
|
||||
sqlite3_free(pGroup);
|
||||
multiplexLeave();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Pass xRead requests thru to the original VFS after
|
||||
** determining the correct chunk to operate on.
|
||||
** Break up reads across chunk boundaries.
|
||||
*/
|
||||
static int multiplexRead(
|
||||
sqlite3_file *pConn,
|
||||
void *pBuf,
|
||||
int iAmt,
|
||||
sqlite3_int64 iOfst
|
||||
){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
int rc = SQLITE_OK;
|
||||
multiplexEnter();
|
||||
while( iAmt > 0 ){
|
||||
int i = (int)(iOfst/gMultiplex.nChunkSize);
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize;
|
||||
if( extra<0 ) extra = 0;
|
||||
iAmt -= extra;
|
||||
rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
pBuf = (char *)pBuf + iAmt;
|
||||
iOfst += iAmt;
|
||||
iAmt = extra;
|
||||
}else{
|
||||
rc = SQLITE_IOERR_READ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
multiplexLeave();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Pass xWrite requests thru to the original VFS after
|
||||
** determining the correct chunk to operate on.
|
||||
** Break up writes across chunk boundaries.
|
||||
*/
|
||||
static int multiplexWrite(
|
||||
sqlite3_file *pConn,
|
||||
const void *pBuf,
|
||||
int iAmt,
|
||||
sqlite3_int64 iOfst
|
||||
){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
int rc = SQLITE_OK;
|
||||
multiplexEnter();
|
||||
while( iAmt > 0 ){
|
||||
int i = (int)(iOfst/gMultiplex.nChunkSize);
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize;
|
||||
if( extra<0 ) extra = 0;
|
||||
iAmt -= extra;
|
||||
rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
pBuf = (char *)pBuf + iAmt;
|
||||
iOfst += iAmt;
|
||||
iAmt = extra;
|
||||
}else{
|
||||
rc = SQLITE_IOERR_WRITE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
multiplexLeave();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Pass xTruncate requests thru to the original VFS after
|
||||
** determining the correct chunk to operate on. Delete any
|
||||
** chunks above the truncate mark.
|
||||
*/
|
||||
static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
multiplexGroup *pGroup = p->pGroup;
|
||||
int rc = SQLITE_OK;
|
||||
int rc2;
|
||||
int i;
|
||||
sqlite3_file *pSubOpen;
|
||||
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */
|
||||
multiplexEnter();
|
||||
memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
|
||||
/* delete the chunks above the truncate limit */
|
||||
for(i=(int)(size/gMultiplex.nChunkSize)+1; i<gMultiplex.nMaxChunks; i++){
|
||||
/* close any open chunks before deleting them */
|
||||
if( pGroup->bOpen[i] ){
|
||||
pSubOpen = pGroup->pReal[i];
|
||||
rc2 = pSubOpen->pMethods->xClose(pSubOpen);
|
||||
if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE;
|
||||
pGroup->bOpen[i] = 0;
|
||||
}
|
||||
#ifdef SQLITE_MULTIPLEX_EXT_OVWR
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#else
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#endif
|
||||
rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, 0);
|
||||
if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE;
|
||||
}
|
||||
pSubOpen = multiplexSubOpen(p, (int)(size/gMultiplex.nChunkSize), &rc2, NULL);
|
||||
if( pSubOpen ){
|
||||
rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size%gMultiplex.nChunkSize);
|
||||
if( rc2!=SQLITE_OK ) rc = rc2;
|
||||
}else{
|
||||
rc = SQLITE_IOERR_TRUNCATE;
|
||||
}
|
||||
multiplexLeave();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Pass xSync requests through to the original VFS without change
|
||||
*/
|
||||
static int multiplexSync(sqlite3_file *pConn, int flags){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
multiplexGroup *pGroup = p->pGroup;
|
||||
int rc = SQLITE_OK;
|
||||
int i;
|
||||
multiplexEnter();
|
||||
for(i=0; i<gMultiplex.nMaxChunks; i++){
|
||||
/* if we don't have it open, we don't need to sync it */
|
||||
if( pGroup->bOpen[i] ){
|
||||
sqlite3_file *pSubOpen = pGroup->pReal[i];
|
||||
int rc2 = pSubOpen->pMethods->xSync(pSubOpen, flags);
|
||||
if( rc2!=SQLITE_OK ) rc = rc2;
|
||||
}
|
||||
}
|
||||
multiplexLeave();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Pass xFileSize requests through to the original VFS.
|
||||
** Aggregate the size of all the chunks before returning.
|
||||
*/
|
||||
static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
multiplexGroup *pGroup = p->pGroup;
|
||||
int rc = SQLITE_OK;
|
||||
int rc2;
|
||||
int i;
|
||||
multiplexEnter();
|
||||
*pSize = 0;
|
||||
for(i=0; i<gMultiplex.nMaxChunks; i++){
|
||||
sqlite3_file *pSubOpen = NULL;
|
||||
/* if not opened already, check to see if the chunk exists */
|
||||
if( pGroup->bOpen[i] ){
|
||||
pSubOpen = pGroup->pReal[i];
|
||||
}else{
|
||||
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */
|
||||
int exists = 0;
|
||||
memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
|
||||
if( i ){
|
||||
#ifdef SQLITE_MULTIPLEX_EXT_OVWR
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#else
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#endif
|
||||
}
|
||||
rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, SQLITE_ACCESS_EXISTS, &exists);
|
||||
if( rc2==SQLITE_OK && exists){
|
||||
/* if it exists, open it */
|
||||
pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
|
||||
}else{
|
||||
/* stop at first "gap" */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( pSubOpen ){
|
||||
sqlite3_int64 sz;
|
||||
rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
|
||||
if( rc2!=SQLITE_OK ){
|
||||
rc = rc2;
|
||||
}else{
|
||||
if( sz>gMultiplex.nChunkSize ){
|
||||
rc = SQLITE_IOERR_FSTAT;
|
||||
}
|
||||
*pSize += sz;
|
||||
}
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
multiplexLeave();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Pass xLock requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int multiplexLock(sqlite3_file *pConn, int lock){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
int rc;
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
return pSubOpen->pMethods->xLock(pSubOpen, lock);
|
||||
}
|
||||
return SQLITE_BUSY;
|
||||
}
|
||||
|
||||
/* Pass xUnlock requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int multiplexUnlock(sqlite3_file *pConn, int lock){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
int rc;
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
return pSubOpen->pMethods->xUnlock(pSubOpen, lock);
|
||||
}
|
||||
return SQLITE_IOERR_UNLOCK;
|
||||
}
|
||||
|
||||
/* Pass xCheckReservedLock requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
int rc;
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);
|
||||
}
|
||||
return SQLITE_IOERR_CHECKRESERVEDLOCK;
|
||||
}
|
||||
|
||||
/* Pass xFileControl requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
int rc;
|
||||
sqlite3_file *pSubOpen;
|
||||
if ( op==SQLITE_FCNTL_SIZE_HINT || op==SQLITE_FCNTL_CHUNK_SIZE ) return SQLITE_OK;
|
||||
pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
|
||||
}
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
/* Pass xSectorSize requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int multiplexSectorSize(sqlite3_file *pConn){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
int rc;
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
return pSubOpen->pMethods->xSectorSize(pSubOpen);
|
||||
}
|
||||
return SQLITE_DEFAULT_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
/* Pass xDeviceCharacteristics requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int multiplexDeviceCharacteristics(sqlite3_file *pConn){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
int rc;
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Pass xShmMap requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int multiplexShmMap(
|
||||
sqlite3_file *pConn, /* Handle open on database file */
|
||||
int iRegion, /* Region to retrieve */
|
||||
int szRegion, /* Size of regions */
|
||||
int bExtend, /* True to extend file if necessary */
|
||||
void volatile **pp /* OUT: Mapped memory */
|
||||
){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
int rc;
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp);
|
||||
}
|
||||
return SQLITE_IOERR;
|
||||
}
|
||||
|
||||
/* Pass xShmLock requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int multiplexShmLock(
|
||||
sqlite3_file *pConn, /* Database file holding the shared memory */
|
||||
int ofst, /* First lock to acquire or release */
|
||||
int n, /* Number of locks to acquire or release */
|
||||
int flags /* What to do with the lock */
|
||||
){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
int rc;
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags);
|
||||
}
|
||||
return SQLITE_BUSY;
|
||||
}
|
||||
|
||||
/* Pass xShmBarrier requests through to the original VFS unchanged.
|
||||
*/
|
||||
static void multiplexShmBarrier(sqlite3_file *pConn){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
int rc;
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
pSubOpen->pMethods->xShmBarrier(pSubOpen);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pass xShmUnmap requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
int rc;
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/************************** Public Interfaces *****************************/
|
||||
/*
|
||||
** Initialize the multiplex VFS shim. Use the VFS named zOrigVfsName
|
||||
** as the VFS that does the actual work. Use the default if
|
||||
** zOrigVfsName==NULL.
|
||||
**
|
||||
** The multiplex VFS shim is named "multiplex". It will become the default
|
||||
** VFS if makeDefault is non-zero.
|
||||
**
|
||||
** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once
|
||||
** during start-up.
|
||||
*/
|
||||
int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){
|
||||
sqlite3_vfs *pOrigVfs;
|
||||
if( gMultiplex.isInitialized ) return SQLITE_MISUSE;
|
||||
pOrigVfs = sqlite3_vfs_find(zOrigVfsName);
|
||||
if( pOrigVfs==0 ) return SQLITE_ERROR;
|
||||
assert( pOrigVfs!=&gMultiplex.sThisVfs );
|
||||
gMultiplex.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
|
||||
if( !gMultiplex.pMutex ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
gMultiplex.zName = sqlite3_malloc(pOrigVfs->mxPathname);
|
||||
if( !gMultiplex.zName ){
|
||||
sqlite3_mutex_free(gMultiplex.pMutex);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
gMultiplex.nChunkSize = SQLITE_MULTIPLEX_CHUNK_SIZE;
|
||||
gMultiplex.nMaxChunks = SQLITE_MULTIPLEX_MAX_CHUNKS;
|
||||
gMultiplex.pGroups = NULL;
|
||||
gMultiplex.isInitialized = 1;
|
||||
gMultiplex.pOrigVfs = pOrigVfs;
|
||||
gMultiplex.sThisVfs = *pOrigVfs;
|
||||
gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn);
|
||||
gMultiplex.sThisVfs.zName = "multiplex";
|
||||
gMultiplex.sThisVfs.xOpen = multiplexOpen;
|
||||
gMultiplex.sThisVfs.xDelete = multiplexDelete;
|
||||
gMultiplex.sThisVfs.xAccess = multiplexAccess;
|
||||
gMultiplex.sThisVfs.xFullPathname = multiplexFullPathname;
|
||||
gMultiplex.sThisVfs.xDlOpen = multiplexDlOpen;
|
||||
gMultiplex.sThisVfs.xDlError = multiplexDlError;
|
||||
gMultiplex.sThisVfs.xDlSym = multiplexDlSym;
|
||||
gMultiplex.sThisVfs.xDlClose = multiplexDlClose;
|
||||
gMultiplex.sThisVfs.xRandomness = multiplexRandomness;
|
||||
gMultiplex.sThisVfs.xSleep = multiplexSleep;
|
||||
gMultiplex.sThisVfs.xCurrentTime = multiplexCurrentTime;
|
||||
gMultiplex.sThisVfs.xGetLastError = multiplexGetLastError;
|
||||
gMultiplex.sThisVfs.xCurrentTimeInt64 = multiplexCurrentTimeInt64;
|
||||
|
||||
gMultiplex.sIoMethodsV1.iVersion = 1;
|
||||
gMultiplex.sIoMethodsV1.xClose = multiplexClose;
|
||||
gMultiplex.sIoMethodsV1.xRead = multiplexRead;
|
||||
gMultiplex.sIoMethodsV1.xWrite = multiplexWrite;
|
||||
gMultiplex.sIoMethodsV1.xTruncate = multiplexTruncate;
|
||||
gMultiplex.sIoMethodsV1.xSync = multiplexSync;
|
||||
gMultiplex.sIoMethodsV1.xFileSize = multiplexFileSize;
|
||||
gMultiplex.sIoMethodsV1.xLock = multiplexLock;
|
||||
gMultiplex.sIoMethodsV1.xUnlock = multiplexUnlock;
|
||||
gMultiplex.sIoMethodsV1.xCheckReservedLock = multiplexCheckReservedLock;
|
||||
gMultiplex.sIoMethodsV1.xFileControl = multiplexFileControl;
|
||||
gMultiplex.sIoMethodsV1.xSectorSize = multiplexSectorSize;
|
||||
gMultiplex.sIoMethodsV1.xDeviceCharacteristics = multiplexDeviceCharacteristics;
|
||||
gMultiplex.sIoMethodsV2 = gMultiplex.sIoMethodsV1;
|
||||
gMultiplex.sIoMethodsV2.iVersion = 2;
|
||||
gMultiplex.sIoMethodsV2.xShmMap = multiplexShmMap;
|
||||
gMultiplex.sIoMethodsV2.xShmLock = multiplexShmLock;
|
||||
gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier;
|
||||
gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap;
|
||||
sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Shutdown the multiplex system.
|
||||
**
|
||||
** All SQLite database connections must be closed before calling this
|
||||
** routine.
|
||||
**
|
||||
** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while
|
||||
** shutting down in order to free all remaining multiplex groups.
|
||||
*/
|
||||
int sqlite3_multiplex_shutdown(void){
|
||||
if( gMultiplex.isInitialized==0 ) return SQLITE_MISUSE;
|
||||
if( gMultiplex.pGroups ) return SQLITE_MISUSE;
|
||||
gMultiplex.isInitialized = 0;
|
||||
sqlite3_free(gMultiplex.zName);
|
||||
sqlite3_mutex_free(gMultiplex.pMutex);
|
||||
sqlite3_vfs_unregister(&gMultiplex.sThisVfs);
|
||||
memset(&gMultiplex, 0, sizeof(gMultiplex));
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Adjust chunking params. VFS should be initialized first.
|
||||
** No files should be open. Re-intializing will reset these
|
||||
** to the default.
|
||||
*/
|
||||
int sqlite3_multiplex_set(
|
||||
int nChunkSize, /* Max chunk size */
|
||||
int nMaxChunks /* Max number of chunks */
|
||||
){
|
||||
if( !gMultiplex.isInitialized ) return SQLITE_MISUSE;
|
||||
if( gMultiplex.pGroups ) return SQLITE_MISUSE;
|
||||
if( nChunkSize<32 ) return SQLITE_MISUSE;
|
||||
if( nMaxChunks<1 ) return SQLITE_MISUSE;
|
||||
if( nMaxChunks>99 ) return SQLITE_MISUSE;
|
||||
multiplexEnter();
|
||||
gMultiplex.nChunkSize = nChunkSize;
|
||||
gMultiplex.nMaxChunks = nMaxChunks;
|
||||
multiplexLeave();
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/***************************** Test Code ***********************************/
|
||||
#ifdef SQLITE_TEST
|
||||
#include <tcl.h>
|
||||
|
||||
extern const char *sqlite3TestErrorName(int);
|
||||
|
||||
|
||||
/*
|
||||
** tclcmd: sqlite3_multiplex_initialize NAME MAKEDEFAULT
|
||||
*/
|
||||
static int test_multiplex_initialize(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
const char *zName; /* Name of new multiplex VFS */
|
||||
int makeDefault; /* True to make the new VFS the default */
|
||||
int rc; /* Value returned by multiplex_initialize() */
|
||||
|
||||
UNUSED_PARAMETER(clientData);
|
||||
|
||||
/* Process arguments */
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
zName = Tcl_GetString(objv[1]);
|
||||
if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR;
|
||||
if( zName[0]=='\0' ) zName = 0;
|
||||
|
||||
/* Call sqlite3_multiplex_initialize() */
|
||||
rc = sqlite3_multiplex_initialize(zName, makeDefault);
|
||||
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: sqlite3_multiplex_shutdown
|
||||
*/
|
||||
static int test_multiplex_shutdown(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int rc; /* Value returned by multiplex_shutdown() */
|
||||
|
||||
UNUSED_PARAMETER(clientData);
|
||||
|
||||
if( objc!=1 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
/* Call sqlite3_multiplex_shutdown() */
|
||||
rc = sqlite3_multiplex_shutdown();
|
||||
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: sqlite3_multiplex_set CHUNK_SIZE MAX_CHUNKS
|
||||
*/
|
||||
static int test_multiplex_set(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int nChunkSize; /* Max chunk size */
|
||||
int nMaxChunks; /* Max number of chunks */
|
||||
int rc; /* Value returned by sqlite3_multiplex_set() */
|
||||
|
||||
UNUSED_PARAMETER(clientData);
|
||||
|
||||
/* Process arguments */
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "CHUNK_SIZE MAX_CHUNKS");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( Tcl_GetIntFromObj(interp, objv[1], &nChunkSize) ) return TCL_ERROR;
|
||||
if( Tcl_GetIntFromObj(interp, objv[2], &nMaxChunks) ) return TCL_ERROR;
|
||||
|
||||
/* Invoke sqlite3_multiplex_set() */
|
||||
rc = sqlite3_multiplex_set(nChunkSize, nMaxChunks);
|
||||
|
||||
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: sqlite3_multiplex_dump
|
||||
*/
|
||||
static int test_multiplex_dump(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
Tcl_Obj *pResult;
|
||||
Tcl_Obj *pGroupTerm;
|
||||
multiplexGroup *pGroup;
|
||||
int i;
|
||||
int nChunks = 0;
|
||||
|
||||
UNUSED_PARAMETER(clientData);
|
||||
UNUSED_PARAMETER(objc);
|
||||
UNUSED_PARAMETER(objv);
|
||||
|
||||
pResult = Tcl_NewObj();
|
||||
multiplexEnter();
|
||||
for(pGroup=gMultiplex.pGroups; pGroup; pGroup=pGroup->pNext){
|
||||
pGroupTerm = Tcl_NewObj();
|
||||
|
||||
pGroup->zName[pGroup->nName] = '\0';
|
||||
Tcl_ListObjAppendElement(interp, pGroupTerm,
|
||||
Tcl_NewStringObj(pGroup->zName, -1));
|
||||
Tcl_ListObjAppendElement(interp, pGroupTerm,
|
||||
Tcl_NewIntObj(pGroup->nName));
|
||||
Tcl_ListObjAppendElement(interp, pGroupTerm,
|
||||
Tcl_NewIntObj(pGroup->flags));
|
||||
|
||||
/* count number of chunks with open handles */
|
||||
for(i=0; i<gMultiplex.nMaxChunks; i++){
|
||||
if( pGroup->bOpen[i] ) nChunks++;
|
||||
}
|
||||
Tcl_ListObjAppendElement(interp, pGroupTerm,
|
||||
Tcl_NewIntObj(nChunks));
|
||||
|
||||
Tcl_ListObjAppendElement(interp, pGroupTerm,
|
||||
Tcl_NewIntObj(gMultiplex.nChunkSize));
|
||||
Tcl_ListObjAppendElement(interp, pGroupTerm,
|
||||
Tcl_NewIntObj(gMultiplex.nMaxChunks));
|
||||
|
||||
Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
|
||||
}
|
||||
multiplexLeave();
|
||||
Tcl_SetObjResult(interp, pResult);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** This routine registers the custom TCL commands defined in this
|
||||
** module. This should be the only procedure visible from outside
|
||||
** of this module.
|
||||
*/
|
||||
int Sqlitemultiplex_Init(Tcl_Interp *interp){
|
||||
static struct {
|
||||
char *zName;
|
||||
Tcl_ObjCmdProc *xProc;
|
||||
} aCmd[] = {
|
||||
{ "sqlite3_multiplex_initialize", test_multiplex_initialize },
|
||||
{ "sqlite3_multiplex_shutdown", test_multiplex_shutdown },
|
||||
{ "sqlite3_multiplex_set", test_multiplex_set },
|
||||
{ "sqlite3_multiplex_dump", test_multiplex_dump },
|
||||
};
|
||||
int i;
|
||||
|
||||
for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
|
||||
Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
#endif
|
||||
@ -247,7 +247,7 @@ static int test_read_mutex_counters(
|
||||
int ii;
|
||||
char *aName[8] = {
|
||||
"fast", "recursive", "static_master", "static_mem",
|
||||
"static_open", "static_prng", "static_lru", "static_lru2"
|
||||
"static_open", "static_prng", "static_lru", "static_pmem"
|
||||
};
|
||||
|
||||
if( objc!=1 ){
|
||||
|
||||
952
src/test_quota.c
Normal file
952
src/test_quota.c
Normal file
@ -0,0 +1,952 @@
|
||||
/*
|
||||
** 2010 September 31
|
||||
**
|
||||
** 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 VFS "shim" - a layer that sits in between the
|
||||
** pager and the real VFS.
|
||||
**
|
||||
** This particular shim enforces a quota system on files. One or more
|
||||
** database files are in a "quota group" that is defined by a GLOB
|
||||
** pattern. A quota is set for the combined size of all files in the
|
||||
** the group. A quota of zero means "no limit". If the total size
|
||||
** of all files in the quota group is greater than the limit, then
|
||||
** write requests that attempt to enlarge a file fail with SQLITE_FULL.
|
||||
**
|
||||
** However, before returning SQLITE_FULL, the write requests invoke
|
||||
** a callback function that is configurable for each quota group.
|
||||
** This callback has the opportunity to enlarge the quota. If the
|
||||
** callback does enlarge the quota such that the total size of all
|
||||
** files within the group is less than the new quota, then the write
|
||||
** continues as if nothing had happened.
|
||||
*/
|
||||
#include "sqlite3.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
/*
|
||||
** For an build without mutexes, no-op the mutex calls.
|
||||
*/
|
||||
#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
|
||||
#define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8)
|
||||
#define sqlite3_mutex_free(X)
|
||||
#define sqlite3_mutex_enter(X)
|
||||
#define sqlite3_mutex_try(X) SQLITE_OK
|
||||
#define sqlite3_mutex_leave(X)
|
||||
#define sqlite3_mutex_held(X) ((void)(X),1)
|
||||
#define sqlite3_mutex_notheld(X) ((void)(X),1)
|
||||
#endif /* SQLITE_THREADSAFE==0 */
|
||||
|
||||
|
||||
/************************ Object Definitions ******************************/
|
||||
|
||||
/* Forward declaration of all object types */
|
||||
typedef struct quotaGroup quotaGroup;
|
||||
typedef struct quotaConn quotaConn;
|
||||
typedef struct quotaFile quotaFile;
|
||||
|
||||
/*
|
||||
** A "quota group" is a collection of files whose collective size we want
|
||||
** to limit. Each quota group is defined by a GLOB pattern.
|
||||
**
|
||||
** There is an instance of the following object for each defined quota
|
||||
** group. This object records the GLOB pattern that defines which files
|
||||
** belong to the quota group. The object also remembers the size limit
|
||||
** for the group (the quota) and the callback to be invoked when the
|
||||
** sum of the sizes of the files within the group goes over the limit.
|
||||
**
|
||||
** A quota group must be established (using sqlite3_quota_set(...))
|
||||
** prior to opening any of the database connections that access files
|
||||
** within the quota group.
|
||||
*/
|
||||
struct quotaGroup {
|
||||
const char *zPattern; /* Filename pattern to be quotaed */
|
||||
sqlite3_int64 iLimit; /* Upper bound on total file size */
|
||||
sqlite3_int64 iSize; /* Current size of all files */
|
||||
void (*xCallback)( /* Callback invoked when going over quota */
|
||||
const char *zFilename, /* Name of file whose size increases */
|
||||
sqlite3_int64 *piLimit, /* IN/OUT: The current limit */
|
||||
sqlite3_int64 iSize, /* Total size of all files in the group */
|
||||
void *pArg /* Client data */
|
||||
);
|
||||
void *pArg; /* Third argument to the xCallback() */
|
||||
void (*xDestroy)(void*); /* Optional destructor for pArg */
|
||||
quotaGroup *pNext, **ppPrev; /* Doubly linked list of all quota objects */
|
||||
quotaFile *pFiles; /* Files within this group */
|
||||
};
|
||||
|
||||
/*
|
||||
** An instance of this structure represents a single file that is part
|
||||
** of a quota group. A single file can be opened multiple times. In
|
||||
** order keep multiple openings of the same file from causing the size
|
||||
** of the file to count against the quota multiple times, each file
|
||||
** has a unique instance of this object and multiple open connections
|
||||
** to the same file each point to a single instance of this object.
|
||||
*/
|
||||
struct quotaFile {
|
||||
char *zFilename; /* Name of this file */
|
||||
quotaGroup *pGroup; /* Quota group to which this file belongs */
|
||||
sqlite3_int64 iSize; /* Current size of this file */
|
||||
int nRef; /* Number of times this file is open */
|
||||
quotaFile *pNext, **ppPrev; /* Linked list of files in the same group */
|
||||
};
|
||||
|
||||
/*
|
||||
** An instance of the following object represents each open connection
|
||||
** to a file that participates in quota tracking. This object is a
|
||||
** subclass of sqlite3_file. The sqlite3_file object for the underlying
|
||||
** VFS is appended to this structure.
|
||||
*/
|
||||
struct quotaConn {
|
||||
sqlite3_file base; /* Base class - must be first */
|
||||
quotaFile *pFile; /* The underlying file */
|
||||
/* The underlying VFS sqlite3_file is appended to this object */
|
||||
};
|
||||
|
||||
/************************* Global Variables **********************************/
|
||||
/*
|
||||
** All global variables used by this file are containing within the following
|
||||
** gQuota structure.
|
||||
*/
|
||||
static struct {
|
||||
/* The pOrigVfs is the real, original underlying VFS implementation.
|
||||
** Most operations pass-through to the real VFS. This value is read-only
|
||||
** during operation. It is only modified at start-time and thus does not
|
||||
** require a mutex.
|
||||
*/
|
||||
sqlite3_vfs *pOrigVfs;
|
||||
|
||||
/* The sThisVfs is the VFS structure used by this shim. It is initialized
|
||||
** at start-time and thus does not require a mutex
|
||||
*/
|
||||
sqlite3_vfs sThisVfs;
|
||||
|
||||
/* The sIoMethods defines the methods used by sqlite3_file objects
|
||||
** associated with this shim. It is initialized at start-time and does
|
||||
** not require a mutex.
|
||||
**
|
||||
** When the underlying VFS is called to open a file, it might return
|
||||
** either a version 1 or a version 2 sqlite3_file object. This shim
|
||||
** has to create a wrapper sqlite3_file of the same version. Hence
|
||||
** there are two I/O method structures, one for version 1 and the other
|
||||
** for version 2.
|
||||
*/
|
||||
sqlite3_io_methods sIoMethodsV1;
|
||||
sqlite3_io_methods sIoMethodsV2;
|
||||
|
||||
/* True when this shim as been initialized.
|
||||
*/
|
||||
int isInitialized;
|
||||
|
||||
/* For run-time access any of the other global data structures in this
|
||||
** shim, the following mutex must be held.
|
||||
*/
|
||||
sqlite3_mutex *pMutex;
|
||||
|
||||
/* List of quotaGroup objects.
|
||||
*/
|
||||
quotaGroup *pGroup;
|
||||
|
||||
} gQuota;
|
||||
|
||||
/************************* Utility Routines *********************************/
|
||||
/*
|
||||
** Acquire and release the mutex used to serialize access to the
|
||||
** list of quotaGroups.
|
||||
*/
|
||||
static void quotaEnter(void){ sqlite3_mutex_enter(gQuota.pMutex); }
|
||||
static void quotaLeave(void){ sqlite3_mutex_leave(gQuota.pMutex); }
|
||||
|
||||
|
||||
/* If the reference count and threshold for a quotaGroup are both
|
||||
** zero, then destroy the quotaGroup.
|
||||
*/
|
||||
static void quotaGroupDeref(quotaGroup *pGroup){
|
||||
if( pGroup->pFiles==0 && pGroup->iLimit==0 ){
|
||||
*pGroup->ppPrev = pGroup->pNext;
|
||||
if( pGroup->pNext ) pGroup->pNext->ppPrev = pGroup->ppPrev;
|
||||
if( pGroup->xDestroy ) pGroup->xDestroy(pGroup->pArg);
|
||||
sqlite3_free(pGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Return TRUE if string z matches glob pattern zGlob.
|
||||
**
|
||||
** Globbing rules:
|
||||
**
|
||||
** '*' Matches any sequence of zero or more characters.
|
||||
**
|
||||
** '?' Matches exactly one character.
|
||||
**
|
||||
** [...] Matches one character from the enclosed list of
|
||||
** characters.
|
||||
**
|
||||
** [^...] Matches one character not in the enclosed list.
|
||||
**
|
||||
*/
|
||||
static int quotaStrglob(const char *zGlob, const char *z){
|
||||
int c, c2;
|
||||
int invert;
|
||||
int seen;
|
||||
|
||||
while( (c = (*(zGlob++)))!=0 ){
|
||||
if( c=='*' ){
|
||||
while( (c=(*(zGlob++))) == '*' || c=='?' ){
|
||||
if( c=='?' && (*(z++))==0 ) return 0;
|
||||
}
|
||||
if( c==0 ){
|
||||
return 1;
|
||||
}else if( c=='[' ){
|
||||
while( *z && quotaStrglob(zGlob-1,z)==0 ){
|
||||
z++;
|
||||
}
|
||||
return (*z)!=0;
|
||||
}
|
||||
while( (c2 = (*(z++)))!=0 ){
|
||||
while( c2!=c ){
|
||||
c2 = *(z++);
|
||||
if( c2==0 ) return 0;
|
||||
}
|
||||
if( quotaStrglob(zGlob,z) ) return 1;
|
||||
}
|
||||
return 0;
|
||||
}else if( c=='?' ){
|
||||
if( (*(z++))==0 ) return 0;
|
||||
}else if( c=='[' ){
|
||||
int prior_c = 0;
|
||||
seen = 0;
|
||||
invert = 0;
|
||||
c = *(z++);
|
||||
if( c==0 ) return 0;
|
||||
c2 = *(zGlob++);
|
||||
if( c2=='^' ){
|
||||
invert = 1;
|
||||
c2 = *(zGlob++);
|
||||
}
|
||||
if( c2==']' ){
|
||||
if( c==']' ) seen = 1;
|
||||
c2 = *(zGlob++);
|
||||
}
|
||||
while( c2 && c2!=']' ){
|
||||
if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
|
||||
c2 = *(zGlob++);
|
||||
if( c>=prior_c && c<=c2 ) seen = 1;
|
||||
prior_c = 0;
|
||||
}else{
|
||||
if( c==c2 ){
|
||||
seen = 1;
|
||||
}
|
||||
prior_c = c2;
|
||||
}
|
||||
c2 = *(zGlob++);
|
||||
}
|
||||
if( c2==0 || (seen ^ invert)==0 ) return 0;
|
||||
}else{
|
||||
if( c!=(*(z++)) ) return 0;
|
||||
}
|
||||
}
|
||||
return *z==0;
|
||||
}
|
||||
|
||||
|
||||
/* Find a quotaGroup given the filename.
|
||||
**
|
||||
** Return a pointer to the quotaGroup object. Return NULL if not found.
|
||||
*/
|
||||
static quotaGroup *quotaGroupFind(const char *zFilename){
|
||||
quotaGroup *p;
|
||||
for(p=gQuota.pGroup; p && quotaStrglob(p->zPattern, zFilename)==0;
|
||||
p=p->pNext){}
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Translate an sqlite3_file* that is really a quotaConn* into
|
||||
** the sqlite3_file* for the underlying original VFS.
|
||||
*/
|
||||
static sqlite3_file *quotaSubOpen(sqlite3_file *pConn){
|
||||
quotaConn *p = (quotaConn*)pConn;
|
||||
return (sqlite3_file*)&p[1];
|
||||
}
|
||||
|
||||
/************************* VFS Method Wrappers *****************************/
|
||||
/*
|
||||
** This is the xOpen method used for the "quota" VFS.
|
||||
**
|
||||
** Most of the work is done by the underlying original VFS. This method
|
||||
** simply links the new file into the appropriate quota group if it is a
|
||||
** file that needs to be tracked.
|
||||
*/
|
||||
static int quotaOpen(
|
||||
sqlite3_vfs *pVfs, /* The quota VFS */
|
||||
const char *zName, /* Name of file to be opened */
|
||||
sqlite3_file *pConn, /* Fill in this file descriptor */
|
||||
int flags, /* Flags to control the opening */
|
||||
int *pOutFlags /* Flags showing results of opening */
|
||||
){
|
||||
int rc; /* Result code */
|
||||
quotaConn *pQuotaOpen; /* The new quota file descriptor */
|
||||
quotaFile *pFile; /* Corresponding quotaFile obj */
|
||||
quotaGroup *pGroup; /* The group file belongs to */
|
||||
sqlite3_file *pSubOpen; /* Real file descriptor */
|
||||
sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */
|
||||
|
||||
/* If the file is not a main database file or a WAL, then use the
|
||||
** normal xOpen method.
|
||||
*/
|
||||
if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL))==0 ){
|
||||
return pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags);
|
||||
}
|
||||
|
||||
/* If the name of the file does not match any quota group, then
|
||||
** use the normal xOpen method.
|
||||
*/
|
||||
quotaEnter();
|
||||
pGroup = quotaGroupFind(zName);
|
||||
if( pGroup==0 ){
|
||||
rc = pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags);
|
||||
}else{
|
||||
/* If we get to this point, it means the file needs to be quota tracked.
|
||||
*/
|
||||
pQuotaOpen = (quotaConn*)pConn;
|
||||
pSubOpen = quotaSubOpen(pConn);
|
||||
rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubOpen, flags, pOutFlags);
|
||||
if( rc==SQLITE_OK ){
|
||||
for(pFile=pGroup->pFiles; pFile && strcmp(pFile->zFilename, zName);
|
||||
pFile=pFile->pNext){}
|
||||
if( pFile==0 ){
|
||||
int nName = strlen(zName);
|
||||
pFile = sqlite3_malloc( sizeof(*pFile) + nName + 1 );
|
||||
if( pFile==0 ){
|
||||
quotaLeave();
|
||||
pSubOpen->pMethods->xClose(pSubOpen);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
pFile->zFilename = (char*)&pFile[1];
|
||||
memcpy(pFile->zFilename, zName, nName+1);
|
||||
pFile->pNext = pGroup->pFiles;
|
||||
if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext;
|
||||
pFile->ppPrev = &pGroup->pFiles;
|
||||
pGroup->pFiles = pFile;
|
||||
pFile->pGroup = pGroup;
|
||||
}
|
||||
pFile->nRef++;
|
||||
pQuotaOpen->pFile = pFile;
|
||||
if( pSubOpen->pMethods->iVersion==1 ){
|
||||
pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV1;
|
||||
}else{
|
||||
pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV2;
|
||||
}
|
||||
}
|
||||
}
|
||||
quotaLeave();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/************************ I/O Method Wrappers *******************************/
|
||||
|
||||
/* xClose requests get passed through to the original VFS. But we
|
||||
** also have to unlink the quotaConn from the quotaFile and quotaGroup.
|
||||
** The quotaFile and/or quotaGroup are freed if they are no longer in use.
|
||||
*/
|
||||
static int quotaClose(sqlite3_file *pConn){
|
||||
quotaConn *p = (quotaConn*)pConn;
|
||||
quotaFile *pFile = p->pFile;
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
int rc;
|
||||
rc = pSubOpen->pMethods->xClose(pSubOpen);
|
||||
quotaEnter();
|
||||
pFile->nRef--;
|
||||
if( pFile->nRef==0 ){
|
||||
quotaGroup *pGroup = pFile->pGroup;
|
||||
pGroup->iSize -= pFile->iSize;
|
||||
if( pFile->pNext ) pFile->pNext->ppPrev = pFile->ppPrev;
|
||||
*pFile->ppPrev = pFile->pNext;
|
||||
quotaGroupDeref(pGroup);
|
||||
sqlite3_free(pFile);
|
||||
}
|
||||
quotaLeave();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Pass xRead requests directory thru to the original VFS without
|
||||
** further processing.
|
||||
*/
|
||||
static int quotaRead(
|
||||
sqlite3_file *pConn,
|
||||
void *pBuf,
|
||||
int iAmt,
|
||||
sqlite3_int64 iOfst
|
||||
){
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
return pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst);
|
||||
}
|
||||
|
||||
/* Check xWrite requests to see if they expand the file. If they do,
|
||||
** the perform a quota check before passing them through to the
|
||||
** original VFS.
|
||||
*/
|
||||
static int quotaWrite(
|
||||
sqlite3_file *pConn,
|
||||
const void *pBuf,
|
||||
int iAmt,
|
||||
sqlite3_int64 iOfst
|
||||
){
|
||||
quotaConn *p = (quotaConn*)pConn;
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
sqlite3_int64 iEnd = iOfst+iAmt;
|
||||
quotaGroup *pGroup;
|
||||
quotaFile *pFile = p->pFile;
|
||||
sqlite3_int64 szNew;
|
||||
|
||||
if( pFile->iSize<iEnd ){
|
||||
pGroup = pFile->pGroup;
|
||||
quotaEnter();
|
||||
szNew = pGroup->iSize - pFile->iSize + iEnd;
|
||||
if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
|
||||
if( pGroup->xCallback ){
|
||||
pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew,
|
||||
pGroup->pArg);
|
||||
}
|
||||
if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
|
||||
quotaLeave();
|
||||
return SQLITE_FULL;
|
||||
}
|
||||
}
|
||||
pGroup->iSize = szNew;
|
||||
pFile->iSize = iEnd;
|
||||
quotaLeave();
|
||||
}
|
||||
return pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst);
|
||||
}
|
||||
|
||||
/* Pass xTruncate requests thru to the original VFS. If the
|
||||
** success, update the file size.
|
||||
*/
|
||||
static int quotaTruncate(sqlite3_file *pConn, sqlite3_int64 size){
|
||||
quotaConn *p = (quotaConn*)pConn;
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
int rc = pSubOpen->pMethods->xTruncate(pSubOpen, size);
|
||||
quotaFile *pFile = p->pFile;
|
||||
quotaGroup *pGroup;
|
||||
if( rc==SQLITE_OK ){
|
||||
quotaEnter();
|
||||
pGroup = pFile->pGroup;
|
||||
pGroup->iSize -= pFile->iSize;
|
||||
pFile->iSize = size;
|
||||
pGroup->iSize += size;
|
||||
quotaLeave();
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Pass xSync requests through to the original VFS without change
|
||||
*/
|
||||
static int quotaSync(sqlite3_file *pConn, int flags){
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
return pSubOpen->pMethods->xSync(pSubOpen, flags);
|
||||
}
|
||||
|
||||
/* Pass xFileSize requests through to the original VFS but then
|
||||
** update the quotaGroup with the new size before returning.
|
||||
*/
|
||||
static int quotaFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){
|
||||
quotaConn *p = (quotaConn*)pConn;
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
quotaFile *pFile = p->pFile;
|
||||
quotaGroup *pGroup;
|
||||
sqlite3_int64 sz;
|
||||
int rc;
|
||||
|
||||
rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
|
||||
if( rc==SQLITE_OK ){
|
||||
quotaEnter();
|
||||
pGroup = pFile->pGroup;
|
||||
pGroup->iSize -= pFile->iSize;
|
||||
pFile->iSize = sz;
|
||||
pGroup->iSize += sz;
|
||||
quotaLeave();
|
||||
*pSize = sz;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Pass xLock requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int quotaLock(sqlite3_file *pConn, int lock){
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
return pSubOpen->pMethods->xLock(pSubOpen, lock);
|
||||
}
|
||||
|
||||
/* Pass xUnlock requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int quotaUnlock(sqlite3_file *pConn, int lock){
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
return pSubOpen->pMethods->xUnlock(pSubOpen, lock);
|
||||
}
|
||||
|
||||
/* Pass xCheckReservedLock requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int quotaCheckReservedLock(sqlite3_file *pConn, int *pResOut){
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);
|
||||
}
|
||||
|
||||
/* Pass xFileControl requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int quotaFileControl(sqlite3_file *pConn, int op, void *pArg){
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
|
||||
}
|
||||
|
||||
/* Pass xSectorSize requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int quotaSectorSize(sqlite3_file *pConn){
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
return pSubOpen->pMethods->xSectorSize(pSubOpen);
|
||||
}
|
||||
|
||||
/* Pass xDeviceCharacteristics requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int quotaDeviceCharacteristics(sqlite3_file *pConn){
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen);
|
||||
}
|
||||
|
||||
/* Pass xShmMap requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int quotaShmMap(
|
||||
sqlite3_file *pConn, /* Handle open on database file */
|
||||
int iRegion, /* Region to retrieve */
|
||||
int szRegion, /* Size of regions */
|
||||
int bExtend, /* True to extend file if necessary */
|
||||
void volatile **pp /* OUT: Mapped memory */
|
||||
){
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp);
|
||||
}
|
||||
|
||||
/* Pass xShmLock requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int quotaShmLock(
|
||||
sqlite3_file *pConn, /* Database file holding the shared memory */
|
||||
int ofst, /* First lock to acquire or release */
|
||||
int n, /* Number of locks to acquire or release */
|
||||
int flags /* What to do with the lock */
|
||||
){
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags);
|
||||
}
|
||||
|
||||
/* Pass xShmBarrier requests through to the original VFS unchanged.
|
||||
*/
|
||||
static void quotaShmBarrier(sqlite3_file *pConn){
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
pSubOpen->pMethods->xShmBarrier(pSubOpen);
|
||||
}
|
||||
|
||||
/* Pass xShmUnmap requests through to the original VFS unchanged.
|
||||
*/
|
||||
static int quotaShmUnmap(sqlite3_file *pConn, int deleteFlag){
|
||||
sqlite3_file *pSubOpen = quotaSubOpen(pConn);
|
||||
return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag);
|
||||
}
|
||||
|
||||
/************************** Public Interfaces *****************************/
|
||||
/*
|
||||
** Initialize the quota VFS shim. Use the VFS named zOrigVfsName
|
||||
** as the VFS that does the actual work. Use the default if
|
||||
** zOrigVfsName==NULL.
|
||||
**
|
||||
** The quota VFS shim is named "quota". It will become the default
|
||||
** VFS if makeDefault is non-zero.
|
||||
**
|
||||
** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once
|
||||
** during start-up.
|
||||
*/
|
||||
int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault){
|
||||
sqlite3_vfs *pOrigVfs;
|
||||
if( gQuota.isInitialized ) return SQLITE_MISUSE;
|
||||
pOrigVfs = sqlite3_vfs_find(zOrigVfsName);
|
||||
if( pOrigVfs==0 ) return SQLITE_ERROR;
|
||||
assert( pOrigVfs!=&gQuota.sThisVfs );
|
||||
gQuota.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
|
||||
if( !gQuota.pMutex ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
gQuota.isInitialized = 1;
|
||||
gQuota.pOrigVfs = pOrigVfs;
|
||||
gQuota.sThisVfs = *pOrigVfs;
|
||||
gQuota.sThisVfs.xOpen = quotaOpen;
|
||||
gQuota.sThisVfs.szOsFile += sizeof(quotaConn);
|
||||
gQuota.sThisVfs.zName = "quota";
|
||||
gQuota.sIoMethodsV1.iVersion = 1;
|
||||
gQuota.sIoMethodsV1.xClose = quotaClose;
|
||||
gQuota.sIoMethodsV1.xRead = quotaRead;
|
||||
gQuota.sIoMethodsV1.xWrite = quotaWrite;
|
||||
gQuota.sIoMethodsV1.xTruncate = quotaTruncate;
|
||||
gQuota.sIoMethodsV1.xSync = quotaSync;
|
||||
gQuota.sIoMethodsV1.xFileSize = quotaFileSize;
|
||||
gQuota.sIoMethodsV1.xLock = quotaLock;
|
||||
gQuota.sIoMethodsV1.xUnlock = quotaUnlock;
|
||||
gQuota.sIoMethodsV1.xCheckReservedLock = quotaCheckReservedLock;
|
||||
gQuota.sIoMethodsV1.xFileControl = quotaFileControl;
|
||||
gQuota.sIoMethodsV1.xSectorSize = quotaSectorSize;
|
||||
gQuota.sIoMethodsV1.xDeviceCharacteristics = quotaDeviceCharacteristics;
|
||||
gQuota.sIoMethodsV2 = gQuota.sIoMethodsV1;
|
||||
gQuota.sIoMethodsV2.iVersion = 2;
|
||||
gQuota.sIoMethodsV2.xShmMap = quotaShmMap;
|
||||
gQuota.sIoMethodsV2.xShmLock = quotaShmLock;
|
||||
gQuota.sIoMethodsV2.xShmBarrier = quotaShmBarrier;
|
||||
gQuota.sIoMethodsV2.xShmUnmap = quotaShmUnmap;
|
||||
sqlite3_vfs_register(&gQuota.sThisVfs, makeDefault);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Shutdown the quota system.
|
||||
**
|
||||
** All SQLite database connections must be closed before calling this
|
||||
** routine.
|
||||
**
|
||||
** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly one while
|
||||
** shutting down in order to free all remaining quota groups.
|
||||
*/
|
||||
int sqlite3_quota_shutdown(void){
|
||||
quotaGroup *pGroup;
|
||||
if( gQuota.isInitialized==0 ) return SQLITE_MISUSE;
|
||||
for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){
|
||||
if( pGroup->pFiles ) return SQLITE_MISUSE;
|
||||
}
|
||||
while( gQuota.pGroup ){
|
||||
pGroup = gQuota.pGroup;
|
||||
gQuota.pGroup = pGroup->pNext;
|
||||
pGroup->iLimit = 0;
|
||||
quotaGroupDeref(pGroup);
|
||||
}
|
||||
gQuota.isInitialized = 0;
|
||||
sqlite3_mutex_free(gQuota.pMutex);
|
||||
sqlite3_vfs_unregister(&gQuota.sThisVfs);
|
||||
memset(&gQuota, 0, sizeof(gQuota));
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Create or destroy a quota group.
|
||||
**
|
||||
** The quota group is defined by the zPattern. When calling this routine
|
||||
** with a zPattern for a quota group that already exists, this routine
|
||||
** merely updates the iLimit, xCallback, and pArg values for that quota
|
||||
** group. If zPattern is new, then a new quota group is created.
|
||||
**
|
||||
** If the iLimit for a quota group is set to zero, then the quota group
|
||||
** is disabled and will be deleted when the last database connection using
|
||||
** the quota group is closed.
|
||||
**
|
||||
** Calling this routine on a zPattern that does not exist and with a
|
||||
** zero iLimit is a no-op.
|
||||
**
|
||||
** A quota group must exist with a non-zero iLimit prior to opening
|
||||
** database connections if those connections are to participate in the
|
||||
** quota group. Creating a quota group does not affect database connections
|
||||
** that are already open.
|
||||
*/
|
||||
int sqlite3_quota_set(
|
||||
const char *zPattern, /* The filename pattern */
|
||||
sqlite3_int64 iLimit, /* New quota to set for this quota group */
|
||||
void (*xCallback)( /* Callback invoked when going over quota */
|
||||
const char *zFilename, /* Name of file whose size increases */
|
||||
sqlite3_int64 *piLimit, /* IN/OUT: The current limit */
|
||||
sqlite3_int64 iSize, /* Total size of all files in the group */
|
||||
void *pArg /* Client data */
|
||||
),
|
||||
void *pArg, /* client data passed thru to callback */
|
||||
void (*xDestroy)(void*) /* Optional destructor for pArg */
|
||||
){
|
||||
quotaGroup *pGroup;
|
||||
quotaEnter();
|
||||
pGroup = gQuota.pGroup;
|
||||
while( pGroup && strcmp(pGroup->zPattern, zPattern)!=0 ){
|
||||
pGroup = pGroup->pNext;
|
||||
}
|
||||
if( pGroup==0 ){
|
||||
int nPattern = strlen(zPattern);
|
||||
if( iLimit<=0 ){
|
||||
quotaLeave();
|
||||
return SQLITE_OK;
|
||||
}
|
||||
pGroup = sqlite3_malloc( sizeof(*pGroup) + nPattern + 1 );
|
||||
if( pGroup==0 ){
|
||||
quotaLeave();
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
memset(pGroup, 0, sizeof(*pGroup));
|
||||
pGroup->zPattern = (char*)&pGroup[1];
|
||||
memcpy((char *)pGroup->zPattern, zPattern, nPattern+1);
|
||||
if( gQuota.pGroup ) gQuota.pGroup->ppPrev = &pGroup->pNext;
|
||||
pGroup->pNext = gQuota.pGroup;
|
||||
pGroup->ppPrev = &gQuota.pGroup;
|
||||
gQuota.pGroup = pGroup;
|
||||
}
|
||||
pGroup->iLimit = iLimit;
|
||||
pGroup->xCallback = xCallback;
|
||||
if( pGroup->xDestroy && pGroup->pArg!=pArg ){
|
||||
pGroup->xDestroy(pGroup->pArg);
|
||||
}
|
||||
pGroup->pArg = pArg;
|
||||
pGroup->xDestroy = xDestroy;
|
||||
quotaGroupDeref(pGroup);
|
||||
quotaLeave();
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
/***************************** Test Code ***********************************/
|
||||
#ifdef SQLITE_TEST
|
||||
#include <tcl.h>
|
||||
|
||||
/*
|
||||
** Argument passed to a TCL quota-over-limit callback.
|
||||
*/
|
||||
typedef struct TclQuotaCallback TclQuotaCallback;
|
||||
struct TclQuotaCallback {
|
||||
Tcl_Interp *interp; /* Interpreter in which to run the script */
|
||||
Tcl_Obj *pScript; /* Script to be run */
|
||||
};
|
||||
|
||||
extern const char *sqlite3TestErrorName(int);
|
||||
|
||||
|
||||
/*
|
||||
** This is the callback from a quota-over-limit.
|
||||
*/
|
||||
static void tclQuotaCallback(
|
||||
const char *zFilename, /* Name of file whose size increases */
|
||||
sqlite3_int64 *piLimit, /* IN/OUT: The current limit */
|
||||
sqlite3_int64 iSize, /* Total size of all files in the group */
|
||||
void *pArg /* Client data */
|
||||
){
|
||||
TclQuotaCallback *p; /* Callback script object */
|
||||
Tcl_Obj *pEval; /* Script to evaluate */
|
||||
Tcl_Obj *pVarname; /* Name of variable to pass as 2nd arg */
|
||||
unsigned int rnd; /* Random part of pVarname */
|
||||
int rc; /* Tcl error code */
|
||||
|
||||
p = (TclQuotaCallback *)pArg;
|
||||
if( p==0 ) return;
|
||||
|
||||
pVarname = Tcl_NewStringObj("::piLimit_", -1);
|
||||
Tcl_IncrRefCount(pVarname);
|
||||
sqlite3_randomness(sizeof(rnd), (void *)&rnd);
|
||||
Tcl_AppendObjToObj(pVarname, Tcl_NewIntObj((int)(rnd&0x7FFFFFFF)));
|
||||
Tcl_ObjSetVar2(p->interp, pVarname, 0, Tcl_NewWideIntObj(*piLimit), 0);
|
||||
|
||||
pEval = Tcl_DuplicateObj(p->pScript);
|
||||
Tcl_IncrRefCount(pEval);
|
||||
Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zFilename, -1));
|
||||
Tcl_ListObjAppendElement(0, pEval, pVarname);
|
||||
Tcl_ListObjAppendElement(0, pEval, Tcl_NewWideIntObj(iSize));
|
||||
rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
|
||||
|
||||
if( rc==TCL_OK ){
|
||||
Tcl_Obj *pLimit = Tcl_ObjGetVar2(p->interp, pVarname, 0, 0);
|
||||
rc = Tcl_GetWideIntFromObj(p->interp, pLimit, piLimit);
|
||||
Tcl_UnsetVar(p->interp, Tcl_GetString(pVarname), 0);
|
||||
}
|
||||
|
||||
Tcl_DecrRefCount(pEval);
|
||||
Tcl_DecrRefCount(pVarname);
|
||||
if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp);
|
||||
}
|
||||
|
||||
/*
|
||||
** Destructor for a TCL quota-over-limit callback.
|
||||
*/
|
||||
static void tclCallbackDestructor(void *pObj){
|
||||
TclQuotaCallback *p = (TclQuotaCallback*)pObj;
|
||||
if( p ){
|
||||
Tcl_DecrRefCount(p->pScript);
|
||||
sqlite3_free((char *)p);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: sqlite3_quota_initialize NAME MAKEDEFAULT
|
||||
*/
|
||||
static int test_quota_initialize(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
const char *zName; /* Name of new quota VFS */
|
||||
int makeDefault; /* True to make the new VFS the default */
|
||||
int rc; /* Value returned by quota_initialize() */
|
||||
|
||||
/* Process arguments */
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
zName = Tcl_GetString(objv[1]);
|
||||
if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR;
|
||||
if( zName[0]=='\0' ) zName = 0;
|
||||
|
||||
/* Call sqlite3_quota_initialize() */
|
||||
rc = sqlite3_quota_initialize(zName, makeDefault);
|
||||
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: sqlite3_quota_shutdown
|
||||
*/
|
||||
static int test_quota_shutdown(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int rc; /* Value returned by quota_shutdown() */
|
||||
|
||||
if( objc!=1 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
/* Call sqlite3_quota_shutdown() */
|
||||
rc = sqlite3_quota_shutdown();
|
||||
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: sqlite3_quota_set PATTERN LIMIT SCRIPT
|
||||
*/
|
||||
static int test_quota_set(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
const char *zPattern; /* File pattern to configure */
|
||||
sqlite3_int64 iLimit; /* Initial quota in bytes */
|
||||
Tcl_Obj *pScript; /* Tcl script to invoke to increase quota */
|
||||
int rc; /* Value returned by quota_set() */
|
||||
TclQuotaCallback *p; /* Callback object */
|
||||
int nScript; /* Length of callback script */
|
||||
void (*xDestroy)(void*); /* Optional destructor for pArg */
|
||||
void (*xCallback)(const char *, sqlite3_int64 *, sqlite3_int64, void *);
|
||||
|
||||
/* Process arguments */
|
||||
if( objc!=4 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "PATTERN LIMIT SCRIPT");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
zPattern = Tcl_GetString(objv[1]);
|
||||
if( Tcl_GetWideIntFromObj(interp, objv[2], &iLimit) ) return TCL_ERROR;
|
||||
pScript = objv[3];
|
||||
Tcl_GetStringFromObj(pScript, &nScript);
|
||||
|
||||
if( nScript>0 ){
|
||||
/* Allocate a TclQuotaCallback object */
|
||||
p = (TclQuotaCallback *)sqlite3_malloc(sizeof(TclQuotaCallback));
|
||||
if( !p ){
|
||||
Tcl_SetResult(interp, (char *)"SQLITE_NOMEM", TCL_STATIC);
|
||||
return TCL_OK;
|
||||
}
|
||||
memset(p, 0, sizeof(TclQuotaCallback));
|
||||
p->interp = interp;
|
||||
Tcl_IncrRefCount(pScript);
|
||||
p->pScript = pScript;
|
||||
xDestroy = tclCallbackDestructor;
|
||||
xCallback = tclQuotaCallback;
|
||||
}else{
|
||||
p = 0;
|
||||
xDestroy = 0;
|
||||
xCallback = 0;
|
||||
}
|
||||
|
||||
/* Invoke sqlite3_quota_set() */
|
||||
rc = sqlite3_quota_set(zPattern, iLimit, xCallback, (void*)p, xDestroy);
|
||||
|
||||
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: sqlite3_quota_dump
|
||||
*/
|
||||
static int test_quota_dump(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
Tcl_Obj *pResult;
|
||||
Tcl_Obj *pGroupTerm;
|
||||
Tcl_Obj *pFileTerm;
|
||||
quotaGroup *pGroup;
|
||||
quotaFile *pFile;
|
||||
|
||||
pResult = Tcl_NewObj();
|
||||
quotaEnter();
|
||||
for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){
|
||||
pGroupTerm = Tcl_NewObj();
|
||||
Tcl_ListObjAppendElement(interp, pGroupTerm,
|
||||
Tcl_NewStringObj(pGroup->zPattern, -1));
|
||||
Tcl_ListObjAppendElement(interp, pGroupTerm,
|
||||
Tcl_NewWideIntObj(pGroup->iLimit));
|
||||
Tcl_ListObjAppendElement(interp, pGroupTerm,
|
||||
Tcl_NewWideIntObj(pGroup->iSize));
|
||||
for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){
|
||||
pFileTerm = Tcl_NewObj();
|
||||
Tcl_ListObjAppendElement(interp, pFileTerm,
|
||||
Tcl_NewStringObj(pFile->zFilename, -1));
|
||||
Tcl_ListObjAppendElement(interp, pFileTerm,
|
||||
Tcl_NewWideIntObj(pFile->iSize));
|
||||
Tcl_ListObjAppendElement(interp, pFileTerm,
|
||||
Tcl_NewWideIntObj(pFile->nRef));
|
||||
Tcl_ListObjAppendElement(interp, pGroupTerm, pFileTerm);
|
||||
}
|
||||
Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
|
||||
}
|
||||
quotaLeave();
|
||||
Tcl_SetObjResult(interp, pResult);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** This routine registers the custom TCL commands defined in this
|
||||
** module. This should be the only procedure visible from outside
|
||||
** of this module.
|
||||
*/
|
||||
int Sqlitequota_Init(Tcl_Interp *interp){
|
||||
static struct {
|
||||
char *zName;
|
||||
Tcl_ObjCmdProc *xProc;
|
||||
} aCmd[] = {
|
||||
{ "sqlite3_quota_initialize", test_quota_initialize },
|
||||
{ "sqlite3_quota_shutdown", test_quota_shutdown },
|
||||
{ "sqlite3_quota_set", test_quota_set },
|
||||
{ "sqlite3_quota_dump", test_quota_dump },
|
||||
};
|
||||
int i;
|
||||
|
||||
for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
|
||||
Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
#endif
|
||||
294
src/test_rtree.c
Normal file
294
src/test_rtree.c
Normal file
@ -0,0 +1,294 @@
|
||||
/*
|
||||
** 2010 August 28
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** Code for testing all sorts of SQLite interfaces. This code
|
||||
** is not included in the SQLite library.
|
||||
*/
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
/* Solely for the UNUSED_PARAMETER() macro. */
|
||||
#include "sqliteInt.h"
|
||||
|
||||
/*
|
||||
** Type used to cache parameter information for the "circle" r-tree geometry
|
||||
** callback.
|
||||
*/
|
||||
typedef struct Circle Circle;
|
||||
struct Circle {
|
||||
struct Box {
|
||||
double xmin;
|
||||
double xmax;
|
||||
double ymin;
|
||||
double ymax;
|
||||
} aBox[2];
|
||||
double centerx;
|
||||
double centery;
|
||||
double radius;
|
||||
};
|
||||
|
||||
/*
|
||||
** Destructor function for Circle objects allocated by circle_geom().
|
||||
*/
|
||||
static void circle_del(void *p){
|
||||
sqlite3_free(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of "circle" r-tree geometry callback.
|
||||
*/
|
||||
static int circle_geom(
|
||||
sqlite3_rtree_geometry *p,
|
||||
int nCoord,
|
||||
double *aCoord,
|
||||
int *pRes
|
||||
){
|
||||
int i; /* Iterator variable */
|
||||
Circle *pCircle; /* Structure defining circular region */
|
||||
double xmin, xmax; /* X dimensions of box being tested */
|
||||
double ymin, ymax; /* X dimensions of box being tested */
|
||||
|
||||
if( p->pUser==0 ){
|
||||
/* If pUser is still 0, then the parameter values have not been tested
|
||||
** for correctness or stored into a Circle structure yet. Do this now. */
|
||||
|
||||
/* This geometry callback is for use with a 2-dimensional r-tree table.
|
||||
** Return an error if the table does not have exactly 2 dimensions. */
|
||||
if( nCoord!=4 ) return SQLITE_ERROR;
|
||||
|
||||
/* Test that the correct number of parameters (3) have been supplied,
|
||||
** and that the parameters are in range (that the radius of the circle
|
||||
** radius is greater than zero). */
|
||||
if( p->nParam!=3 || p->aParam[2]<0.0 ) return SQLITE_ERROR;
|
||||
|
||||
/* Allocate a structure to cache parameter data in. Return SQLITE_NOMEM
|
||||
** if the allocation fails. */
|
||||
pCircle = (Circle *)(p->pUser = sqlite3_malloc(sizeof(Circle)));
|
||||
if( !pCircle ) return SQLITE_NOMEM;
|
||||
p->xDelUser = circle_del;
|
||||
|
||||
/* Record the center and radius of the circular region. One way that
|
||||
** tested bounding boxes that intersect the circular region are detected
|
||||
** is by testing if each corner of the bounding box lies within radius
|
||||
** units of the center of the circle. */
|
||||
pCircle->centerx = p->aParam[0];
|
||||
pCircle->centery = p->aParam[1];
|
||||
pCircle->radius = p->aParam[2];
|
||||
|
||||
/* Define two bounding box regions. The first, aBox[0], extends to
|
||||
** infinity in the X dimension. It covers the same range of the Y dimension
|
||||
** as the circular region. The second, aBox[1], extends to infinity in
|
||||
** the Y dimension and is constrained to the range of the circle in the
|
||||
** X dimension.
|
||||
**
|
||||
** Then imagine each box is split in half along its short axis by a line
|
||||
** that intersects the center of the circular region. A bounding box
|
||||
** being tested can be said to intersect the circular region if it contains
|
||||
** points from each half of either of the two infinite bounding boxes.
|
||||
*/
|
||||
pCircle->aBox[0].xmin = pCircle->centerx;
|
||||
pCircle->aBox[0].xmax = pCircle->centerx;
|
||||
pCircle->aBox[0].ymin = pCircle->centery + pCircle->radius;
|
||||
pCircle->aBox[0].ymax = pCircle->centery - pCircle->radius;
|
||||
pCircle->aBox[1].xmin = pCircle->centerx + pCircle->radius;
|
||||
pCircle->aBox[1].xmax = pCircle->centerx - pCircle->radius;
|
||||
pCircle->aBox[1].ymin = pCircle->centery;
|
||||
pCircle->aBox[1].ymax = pCircle->centery;
|
||||
}
|
||||
|
||||
pCircle = (Circle *)p->pUser;
|
||||
xmin = aCoord[0];
|
||||
xmax = aCoord[1];
|
||||
ymin = aCoord[2];
|
||||
ymax = aCoord[3];
|
||||
|
||||
/* Check if any of the 4 corners of the bounding-box being tested lie
|
||||
** inside the circular region. If they do, then the bounding-box does
|
||||
** intersect the region of interest. Set the output variable to true and
|
||||
** return SQLITE_OK in this case. */
|
||||
for(i=0; i<4; i++){
|
||||
double x = (i&0x01) ? xmax : xmin;
|
||||
double y = (i&0x02) ? ymax : ymin;
|
||||
double d2;
|
||||
|
||||
d2 = (x-pCircle->centerx)*(x-pCircle->centerx);
|
||||
d2 += (y-pCircle->centery)*(y-pCircle->centery);
|
||||
if( d2<(pCircle->radius*pCircle->radius) ){
|
||||
*pRes = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the bounding box covers any other part of the circular region.
|
||||
** See comments above for a description of how this test works. If it does
|
||||
** cover part of the circular region, set the output variable to true
|
||||
** and return SQLITE_OK. */
|
||||
for(i=0; i<2; i++){
|
||||
if( xmin<=pCircle->aBox[i].xmin
|
||||
&& xmax>=pCircle->aBox[i].xmax
|
||||
&& ymin<=pCircle->aBox[i].ymin
|
||||
&& ymax>=pCircle->aBox[i].ymax
|
||||
){
|
||||
*pRes = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* The specified bounding box does not intersect the circular region. Set
|
||||
** the output variable to zero and return SQLITE_OK. */
|
||||
*pRes = 0;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/* END of implementation of "circle" geometry callback.
|
||||
**************************************************************************
|
||||
*************************************************************************/
|
||||
|
||||
#include <assert.h>
|
||||
#include "tcl.h"
|
||||
|
||||
typedef struct Cube Cube;
|
||||
struct Cube {
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
double width;
|
||||
double height;
|
||||
double depth;
|
||||
};
|
||||
|
||||
static void cube_context_free(void *p){
|
||||
sqlite3_free(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** The context pointer registered along with the 'cube' callback is
|
||||
** always ((void *)&gHere). This is just to facilitate testing, it is not
|
||||
** actually used for anything.
|
||||
*/
|
||||
static int gHere = 42;
|
||||
|
||||
/*
|
||||
** Implementation of a simple r-tree geom callback to test for intersection
|
||||
** of r-tree rows with a "cube" shape. Cubes are defined by six scalar
|
||||
** coordinates as follows:
|
||||
**
|
||||
** cube(x, y, z, width, height, depth)
|
||||
**
|
||||
** The width, height and depth parameters must all be greater than zero.
|
||||
*/
|
||||
static int cube_geom(
|
||||
sqlite3_rtree_geometry *p,
|
||||
int nCoord,
|
||||
double *aCoord,
|
||||
int *piRes
|
||||
){
|
||||
Cube *pCube = (Cube *)p->pUser;
|
||||
|
||||
assert( p->pContext==(void *)&gHere );
|
||||
|
||||
if( pCube==0 ){
|
||||
if( p->nParam!=6 || nCoord!=6
|
||||
|| p->aParam[3]<=0.0 || p->aParam[4]<=0.0 || p->aParam[5]<=0.0
|
||||
){
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
pCube = (Cube *)sqlite3_malloc(sizeof(Cube));
|
||||
if( !pCube ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
pCube->x = p->aParam[0];
|
||||
pCube->y = p->aParam[1];
|
||||
pCube->z = p->aParam[2];
|
||||
pCube->width = p->aParam[3];
|
||||
pCube->height = p->aParam[4];
|
||||
pCube->depth = p->aParam[5];
|
||||
|
||||
p->pUser = (void *)pCube;
|
||||
p->xDelUser = cube_context_free;
|
||||
}
|
||||
|
||||
assert( nCoord==6 );
|
||||
*piRes = 0;
|
||||
if( aCoord[0]<=(pCube->x+pCube->width)
|
||||
&& aCoord[1]>=pCube->x
|
||||
&& aCoord[2]<=(pCube->y+pCube->height)
|
||||
&& aCoord[3]>=pCube->y
|
||||
&& aCoord[4]<=(pCube->z+pCube->depth)
|
||||
&& aCoord[5]>=pCube->z
|
||||
){
|
||||
*piRes = 1;
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static int register_cube_geom(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
#ifndef SQLITE_ENABLE_RTREE
|
||||
UNUSED_PARAMETER(clientData);
|
||||
UNUSED_PARAMETER(interp);
|
||||
UNUSED_PARAMETER(objc);
|
||||
UNUSED_PARAMETER(objv);
|
||||
#else
|
||||
extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
|
||||
extern const char *sqlite3TestErrorName(int);
|
||||
sqlite3 *db;
|
||||
int rc;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
|
||||
rc = sqlite3_rtree_geometry_callback(db, "cube", cube_geom, (void *)&gHere);
|
||||
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
|
||||
#endif
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int register_circle_geom(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
#ifndef SQLITE_ENABLE_RTREE
|
||||
UNUSED_PARAMETER(clientData);
|
||||
UNUSED_PARAMETER(interp);
|
||||
UNUSED_PARAMETER(objc);
|
||||
UNUSED_PARAMETER(objv);
|
||||
#else
|
||||
extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
|
||||
extern const char *sqlite3TestErrorName(int);
|
||||
sqlite3 *db;
|
||||
int rc;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
|
||||
rc = sqlite3_rtree_geometry_callback(db, "circle", circle_geom, 0);
|
||||
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
|
||||
#endif
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
int Sqlitetestrtree_Init(Tcl_Interp *interp){
|
||||
Tcl_CreateObjCommand(interp, "register_cube_geom", register_cube_geom, 0, 0);
|
||||
Tcl_CreateObjCommand(interp, "register_circle_geom",register_circle_geom,0,0);
|
||||
return TCL_OK;
|
||||
}
|
||||
356
src/test_superlock.c
Normal file
356
src/test_superlock.c
Normal file
@ -0,0 +1,356 @@
|
||||
/*
|
||||
** 2010 November 19
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** Example code for obtaining an exclusive lock on an SQLite database
|
||||
** file. This method is complicated, but works for both WAL and rollback
|
||||
** mode database files. The interface to the example code in this file
|
||||
** consists of the following two functions:
|
||||
**
|
||||
** sqlite3demo_superlock()
|
||||
** sqlite3demo_superunlock()
|
||||
*/
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <string.h> /* memset(), strlen() */
|
||||
#include <assert.h> /* assert() */
|
||||
|
||||
/*
|
||||
** A structure to collect a busy-handler callback and argument and a count
|
||||
** of the number of times it has been invoked.
|
||||
*/
|
||||
struct SuperlockBusy {
|
||||
int (*xBusy)(void*,int); /* Pointer to busy-handler function */
|
||||
void *pBusyArg; /* First arg to pass to xBusy */
|
||||
int nBusy; /* Number of times xBusy has been invoked */
|
||||
};
|
||||
typedef struct SuperlockBusy SuperlockBusy;
|
||||
|
||||
/*
|
||||
** An instance of the following structure is allocated for each active
|
||||
** superlock. The opaque handle returned by sqlite3demo_superlock() is
|
||||
** actually a pointer to an instance of this structure.
|
||||
*/
|
||||
struct Superlock {
|
||||
sqlite3 *db; /* Database handle used to lock db */
|
||||
int bWal; /* True if db is a WAL database */
|
||||
};
|
||||
typedef struct Superlock Superlock;
|
||||
|
||||
/*
|
||||
** The pCtx pointer passed to this function is actually a pointer to a
|
||||
** SuperlockBusy structure. Invoke the busy-handler function encapsulated
|
||||
** by the structure and return the result.
|
||||
*/
|
||||
static int superlockBusyHandler(void *pCtx, int UNUSED){
|
||||
SuperlockBusy *pBusy = (SuperlockBusy *)pCtx;
|
||||
if( pBusy->xBusy==0 ) return 0;
|
||||
return pBusy->xBusy(pBusy->pBusyArg, pBusy->nBusy++);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is used to determine if the main database file for
|
||||
** connection db is open in WAL mode or not. If no error occurs and the
|
||||
** database file is in WAL mode, set *pbWal to true and return SQLITE_OK.
|
||||
** If it is not in WAL mode, set *pbWal to false.
|
||||
**
|
||||
** If an error occurs, return an SQLite error code. The value of *pbWal
|
||||
** is undefined in this case.
|
||||
*/
|
||||
static int superlockIsWal(Superlock *pLock){
|
||||
int rc; /* Return Code */
|
||||
sqlite3_stmt *pStmt; /* Compiled PRAGMA journal_mode statement */
|
||||
|
||||
rc = sqlite3_prepare(pLock->db, "PRAGMA main.journal_mode", -1, &pStmt, 0);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
pLock->bWal = 0;
|
||||
if( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
const char *zMode = (const char *)sqlite3_column_text(pStmt, 0);
|
||||
if( zMode && strlen(zMode)==3 && sqlite3_strnicmp("wal", zMode, 3)==0 ){
|
||||
pLock->bWal = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return sqlite3_finalize(pStmt);
|
||||
}
|
||||
|
||||
/*
|
||||
** Obtain an exclusive shm-lock on nByte bytes starting at offset idx
|
||||
** of the file fd. If the lock cannot be obtained immediately, invoke
|
||||
** the busy-handler until either it is obtained or the busy-handler
|
||||
** callback returns 0.
|
||||
*/
|
||||
static int superlockShmLock(
|
||||
sqlite3_file *fd, /* Database file handle */
|
||||
int idx, /* Offset of shm-lock to obtain */
|
||||
int nByte, /* Number of consective bytes to lock */
|
||||
SuperlockBusy *pBusy /* Busy-handler wrapper object */
|
||||
){
|
||||
int rc;
|
||||
int (*xShmLock)(sqlite3_file*, int, int, int) = fd->pMethods->xShmLock;
|
||||
do {
|
||||
rc = xShmLock(fd, idx, nByte, SQLITE_SHM_LOCK|SQLITE_SHM_EXCLUSIVE);
|
||||
}while( rc==SQLITE_BUSY && superlockBusyHandler((void *)pBusy, 0) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Obtain the extra locks on the database file required for WAL databases.
|
||||
** Invoke the supplied busy-handler as required.
|
||||
*/
|
||||
static int superlockWalLock(
|
||||
sqlite3 *db, /* Database handle open on WAL database */
|
||||
SuperlockBusy *pBusy /* Busy handler wrapper object */
|
||||
){
|
||||
int rc; /* Return code */
|
||||
sqlite3_file *fd = 0; /* Main database file handle */
|
||||
void volatile *p = 0; /* Pointer to first page of shared memory */
|
||||
|
||||
/* Obtain a pointer to the sqlite3_file object open on the main db file. */
|
||||
rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
/* Obtain the "recovery" lock. Normally, this lock is only obtained by
|
||||
** clients running database recovery.
|
||||
*/
|
||||
rc = superlockShmLock(fd, 2, 1, pBusy);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
/* Zero the start of the first shared-memory page. This means that any
|
||||
** clients that open read or write transactions from this point on will
|
||||
** have to run recovery before proceeding. Since they need the "recovery"
|
||||
** lock that this process is holding to do that, no new read or write
|
||||
** transactions may now be opened. Nor can a checkpoint be run, for the
|
||||
** same reason.
|
||||
*/
|
||||
rc = fd->pMethods->xShmMap(fd, 0, 32*1024, 1, &p);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
memset((void *)p, 0, 32);
|
||||
|
||||
/* Obtain exclusive locks on all the "read-lock" slots. Once these locks
|
||||
** are held, it is guaranteed that there are no active reader, writer or
|
||||
** checkpointer clients.
|
||||
*/
|
||||
rc = superlockShmLock(fd, 3, SQLITE_SHM_NLOCK-3, pBusy);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Release a superlock held on a database file. The argument passed to
|
||||
** this function must have been obtained from a successful call to
|
||||
** sqlite3demo_superlock().
|
||||
*/
|
||||
void sqlite3demo_superunlock(void *pLock){
|
||||
Superlock *p = (Superlock *)pLock;
|
||||
if( p->bWal ){
|
||||
int rc; /* Return code */
|
||||
int flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE;
|
||||
sqlite3_file *fd = 0;
|
||||
rc = sqlite3_file_control(p->db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd);
|
||||
if( rc==SQLITE_OK ){
|
||||
fd->pMethods->xShmLock(fd, 2, 1, flags);
|
||||
fd->pMethods->xShmLock(fd, 3, SQLITE_SHM_NLOCK-3, flags);
|
||||
}
|
||||
}
|
||||
sqlite3_close(p->db);
|
||||
sqlite3_free(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** Obtain a superlock on the database file identified by zPath, using the
|
||||
** locking primitives provided by VFS zVfs. If successful, SQLITE_OK is
|
||||
** returned and output variable *ppLock is populated with an opaque handle
|
||||
** that may be used with sqlite3demo_superunlock() to release the lock.
|
||||
**
|
||||
** If an error occurs, *ppLock is set to 0 and an SQLite error code
|
||||
** (e.g. SQLITE_BUSY) is returned.
|
||||
**
|
||||
** If a required lock cannot be obtained immediately and the xBusy parameter
|
||||
** to this function is not NULL, then xBusy is invoked in the same way
|
||||
** as a busy-handler registered with SQLite (using sqlite3_busy_handler())
|
||||
** until either the lock can be obtained or the busy-handler function returns
|
||||
** 0 (indicating "give up").
|
||||
*/
|
||||
int sqlite3demo_superlock(
|
||||
const char *zPath, /* Path to database file to lock */
|
||||
const char *zVfs, /* VFS to use to access database file */
|
||||
int (*xBusy)(void*,int), /* Busy handler callback */
|
||||
void *pBusyArg, /* Context arg for busy handler */
|
||||
void **ppLock /* OUT: Context to pass to superunlock() */
|
||||
){
|
||||
SuperlockBusy busy = {0, 0, 0}; /* Busy handler wrapper object */
|
||||
int rc; /* Return code */
|
||||
Superlock *pLock;
|
||||
|
||||
pLock = sqlite3_malloc(sizeof(Superlock));
|
||||
if( !pLock ) return SQLITE_NOMEM;
|
||||
memset(pLock, 0, sizeof(Superlock));
|
||||
|
||||
/* Open a database handle on the file to superlock. */
|
||||
rc = sqlite3_open_v2(
|
||||
zPath, &pLock->db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs
|
||||
);
|
||||
|
||||
/* Install a busy-handler and execute a BEGIN EXCLUSIVE. If this is not
|
||||
** a WAL database, this is all we need to do.
|
||||
**
|
||||
** A wrapper function is used to invoke the busy-handler instead of
|
||||
** registering the busy-handler function supplied by the user directly
|
||||
** with SQLite. This is because the same busy-handler function may be
|
||||
** invoked directly later on when attempting to obtain the extra locks
|
||||
** required in WAL mode. By using the wrapper, we are able to guarantee
|
||||
** that the "nBusy" integer parameter passed to the users busy-handler
|
||||
** represents the total number of busy-handler invocations made within
|
||||
** this call to sqlite3demo_superlock(), including any made during the
|
||||
** "BEGIN EXCLUSIVE".
|
||||
*/
|
||||
if( rc==SQLITE_OK ){
|
||||
busy.xBusy = xBusy;
|
||||
busy.pBusyArg = pBusyArg;
|
||||
sqlite3_busy_handler(pLock->db, superlockBusyHandler, (void *)&busy);
|
||||
rc = sqlite3_exec(pLock->db, "BEGIN EXCLUSIVE", 0, 0, 0);
|
||||
}
|
||||
|
||||
/* If the BEGIN EXCLUSIVE was executed successfully and this is a WAL
|
||||
** database, call superlockWalLock() to obtain the extra locks required
|
||||
** to prevent readers, writers and/or checkpointers from accessing the
|
||||
** db while this process is holding the superlock.
|
||||
**
|
||||
** Before attempting any WAL locks, commit the transaction started above
|
||||
** to drop the WAL read and write locks currently held. Otherwise, the
|
||||
** new WAL locks may conflict with the old.
|
||||
*/
|
||||
if( rc==SQLITE_OK ){
|
||||
if( SQLITE_OK==(rc = superlockIsWal(pLock)) && pLock->bWal ){
|
||||
rc = sqlite3_exec(pLock->db, "COMMIT", 0, 0, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = superlockWalLock(pLock->db, &busy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3demo_superunlock(pLock);
|
||||
*ppLock = 0;
|
||||
}else{
|
||||
*ppLock = pLock;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** End of example code. Everything below here is the test harness.
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
*************************************************************************/
|
||||
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
|
||||
#include <tcl.h>
|
||||
|
||||
struct InterpAndScript {
|
||||
Tcl_Interp *interp;
|
||||
Tcl_Obj *pScript;
|
||||
};
|
||||
typedef struct InterpAndScript InterpAndScript;
|
||||
|
||||
static void superunlock_del(ClientData cd){
|
||||
sqlite3demo_superunlock((void *)cd);
|
||||
}
|
||||
|
||||
static int superunlock_cmd(
|
||||
ClientData cd,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
if( objc!=1 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int superlock_busy(void *pCtx, int nBusy){
|
||||
InterpAndScript *p = (InterpAndScript *)pCtx;
|
||||
Tcl_Obj *pEval; /* Script to evaluate */
|
||||
int iVal = 0; /* Value to return */
|
||||
|
||||
pEval = Tcl_DuplicateObj(p->pScript);
|
||||
Tcl_IncrRefCount(pEval);
|
||||
Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(nBusy));
|
||||
Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
|
||||
Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iVal);
|
||||
Tcl_DecrRefCount(pEval);
|
||||
|
||||
return iVal;
|
||||
}
|
||||
|
||||
/*
|
||||
** Tclcmd: sqlite3demo_superlock CMDNAME PATH VFS BUSY-HANDLER-SCRIPT
|
||||
*/
|
||||
static int superlock_cmd(
|
||||
ClientData cd,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
void *pLock; /* Lock context */
|
||||
char *zPath;
|
||||
char *zVfs = 0;
|
||||
InterpAndScript busy = {0, 0};
|
||||
int (*xBusy)(void*,int) = 0; /* Busy handler callback */
|
||||
int rc; /* Return code from sqlite3demo_superlock() */
|
||||
|
||||
if( objc<3 || objc>5 ){
|
||||
Tcl_WrongNumArgs(
|
||||
interp, 1, objv, "CMDNAME PATH ?VFS? ?BUSY-HANDLER-SCRIPT?");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
zPath = Tcl_GetString(objv[2]);
|
||||
|
||||
if( objc>3 ){
|
||||
zVfs = Tcl_GetString(objv[3]);
|
||||
if( strlen(zVfs)==0 ) zVfs = 0;
|
||||
}
|
||||
if( objc>4 ){
|
||||
busy.interp = interp;
|
||||
busy.pScript = objv[4];
|
||||
xBusy = superlock_busy;
|
||||
}
|
||||
|
||||
rc = sqlite3demo_superlock(zPath, zVfs, xBusy, &busy, &pLock);
|
||||
assert( rc==SQLITE_OK || pLock==0 );
|
||||
assert( rc!=SQLITE_OK || pLock!=0 );
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
extern const char *sqlite3ErrStr(int);
|
||||
Tcl_ResetResult(interp);
|
||||
Tcl_AppendResult(interp, sqlite3ErrStr(rc), 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
Tcl_CreateObjCommand(
|
||||
interp, Tcl_GetString(objv[1]), superunlock_cmd, pLock, superunlock_del
|
||||
);
|
||||
Tcl_SetObjResult(interp, objv[1]);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
int SqliteSuperlock_Init(Tcl_Interp *interp){
|
||||
Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0);
|
||||
return TCL_OK;
|
||||
}
|
||||
#endif
|
||||
@ -26,6 +26,7 @@
|
||||
** -default BOOLEAN (True to make the vfs default. Default false)
|
||||
** -szosfile INTEGER (Value for sqlite3_vfs.szOsFile)
|
||||
** -mxpathname INTEGER (Value for sqlite3_vfs.mxPathname)
|
||||
** -iversion INTEGER (Value for sqlite3_vfs.iVersion)
|
||||
*/
|
||||
|
||||
#include "sqlite3.h"
|
||||
@ -539,6 +540,7 @@ static int tvfsOpen(
|
||||
pFd->zFilename = zName;
|
||||
pFd->pVfs = pVfs;
|
||||
pFd->pReal = (sqlite3_file *)&pFd[1];
|
||||
memset(pTestfile, 0, sizeof(TestvfsFile));
|
||||
pTestfile->pFd = pFd;
|
||||
|
||||
/* Evaluate the Tcl script:
|
||||
|
||||
@ -638,6 +638,7 @@ static void updateVirtualTable(
|
||||
assert( v );
|
||||
ephemTab = pParse->nTab++;
|
||||
sqlite3VdbeAddOp2(v, OP_OpenEphemeral, ephemTab, pTab->nCol+1+(pRowid!=0));
|
||||
sqlite3VdbeChangeP5(v, BTREE_UNORDERED);
|
||||
|
||||
/* fill the ephemeral table
|
||||
*/
|
||||
|
||||
274
src/util.c
274
src/util.c
@ -215,6 +215,12 @@ int sqlite3Dequote(char *z){
|
||||
/*
|
||||
** Some systems have stricmp(). Others have strcasecmp(). Because
|
||||
** there is no consistency, we will define our own.
|
||||
**
|
||||
** IMPLEMENTATION-OF: R-20522-24639 The sqlite3_strnicmp() API allows
|
||||
** applications and extensions to compare the contents of two buffers
|
||||
** containing UTF-8 strings in a case-independent fashion, using the same
|
||||
** definition of case independence that SQLite uses internally when
|
||||
** comparing identifiers.
|
||||
*/
|
||||
int sqlite3StrICmp(const char *zLeft, const char *zRight){
|
||||
register unsigned char *a, *b;
|
||||
@ -232,121 +238,111 @@ int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){
|
||||
}
|
||||
|
||||
/*
|
||||
** Return TRUE if z is a pure numeric string. Return FALSE and leave
|
||||
** *realnum unchanged if the string contains any character which is not
|
||||
** part of a number.
|
||||
** The string z[] is an text representation of a real number.
|
||||
** Convert this string to a double and write it into *pResult.
|
||||
**
|
||||
** If the string is pure numeric, set *realnum to TRUE if the string
|
||||
** contains the '.' character or an "E+000" style exponentiation suffix.
|
||||
** Otherwise set *realnum to FALSE. Note that just becaue *realnum is
|
||||
** false does not mean that the number can be successfully converted into
|
||||
** an integer - it might be too big.
|
||||
** The string z[] is length bytes in length (bytes, not characters) and
|
||||
** uses the encoding enc. The string is not necessarily zero-terminated.
|
||||
**
|
||||
** An empty string is considered non-numeric.
|
||||
** Return TRUE if the result is a valid real number (or integer) and FALSE
|
||||
** if the string is empty or contains extraneous text. Valid numbers
|
||||
** are in one of these formats:
|
||||
**
|
||||
** [+-]digits[E[+-]digits]
|
||||
** [+-]digits.[digits][E[+-]digits]
|
||||
** [+-].digits[E[+-]digits]
|
||||
**
|
||||
** Leading and trailing whitespace is ignored for the purpose of determining
|
||||
** validity.
|
||||
**
|
||||
** If some prefix of the input string is a valid number, this routine
|
||||
** returns FALSE but it still converts the prefix and writes the result
|
||||
** into *pResult.
|
||||
*/
|
||||
int sqlite3IsNumber(const char *z, int *realnum, u8 enc){
|
||||
int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){
|
||||
#ifndef SQLITE_OMIT_FLOATING_POINT
|
||||
int incr = (enc==SQLITE_UTF8?1:2);
|
||||
if( enc==SQLITE_UTF16BE ) z++;
|
||||
if( *z=='-' || *z=='+' ) z += incr;
|
||||
if( !sqlite3Isdigit(*z) ){
|
||||
return 0;
|
||||
}
|
||||
z += incr;
|
||||
*realnum = 0;
|
||||
while( sqlite3Isdigit(*z) ){ z += incr; }
|
||||
#ifndef SQLITE_OMIT_FLOATING_POINT
|
||||
if( *z=='.' ){
|
||||
z += incr;
|
||||
if( !sqlite3Isdigit(*z) ) return 0;
|
||||
while( sqlite3Isdigit(*z) ){ z += incr; }
|
||||
*realnum = 1;
|
||||
}
|
||||
if( *z=='e' || *z=='E' ){
|
||||
z += incr;
|
||||
if( *z=='+' || *z=='-' ) z += incr;
|
||||
if( !sqlite3Isdigit(*z) ) return 0;
|
||||
while( sqlite3Isdigit(*z) ){ z += incr; }
|
||||
*realnum = 1;
|
||||
}
|
||||
#endif
|
||||
return *z==0;
|
||||
}
|
||||
|
||||
/*
|
||||
** The string z[] is an ASCII representation of a real number.
|
||||
** Convert this string to a double.
|
||||
**
|
||||
** This routine assumes that z[] really is a valid number. If it
|
||||
** is not, the result is undefined.
|
||||
**
|
||||
** This routine is used instead of the library atof() function because
|
||||
** the library atof() might want to use "," as the decimal point instead
|
||||
** of "." depending on how locale is set. But that would cause problems
|
||||
** for SQL. So this routine always uses "." regardless of locale.
|
||||
*/
|
||||
int sqlite3AtoF(const char *z, double *pResult){
|
||||
#ifndef SQLITE_OMIT_FLOATING_POINT
|
||||
const char *zBegin = z;
|
||||
const char *zEnd = z + length;
|
||||
/* sign * significand * (10 ^ (esign * exponent)) */
|
||||
int sign = 1; /* sign of significand */
|
||||
i64 s = 0; /* significand */
|
||||
int d = 0; /* adjust exponent for shifting decimal point */
|
||||
int esign = 1; /* sign of exponent */
|
||||
int e = 0; /* exponent */
|
||||
int sign = 1; /* sign of significand */
|
||||
i64 s = 0; /* significand */
|
||||
int d = 0; /* adjust exponent for shifting decimal point */
|
||||
int esign = 1; /* sign of exponent */
|
||||
int e = 0; /* exponent */
|
||||
int eValid = 1; /* True exponent is either not used or is well-formed */
|
||||
double result;
|
||||
int nDigits = 0;
|
||||
|
||||
*pResult = 0.0; /* Default return value, in case of an error */
|
||||
|
||||
if( enc==SQLITE_UTF16BE ) z++;
|
||||
|
||||
/* skip leading spaces */
|
||||
while( sqlite3Isspace(*z) ) z++;
|
||||
while( z<zEnd && sqlite3Isspace(*z) ) z+=incr;
|
||||
if( z>=zEnd ) return 0;
|
||||
|
||||
/* get sign of significand */
|
||||
if( *z=='-' ){
|
||||
sign = -1;
|
||||
z++;
|
||||
z+=incr;
|
||||
}else if( *z=='+' ){
|
||||
z++;
|
||||
z+=incr;
|
||||
}
|
||||
|
||||
/* skip leading zeroes */
|
||||
while( z[0]=='0' ) z++, nDigits++;
|
||||
while( z<zEnd && z[0]=='0' ) z+=incr, nDigits++;
|
||||
|
||||
/* copy max significant digits to significand */
|
||||
while( sqlite3Isdigit(*z) && s<((LARGEST_INT64-9)/10) ){
|
||||
while( z<zEnd && sqlite3Isdigit(*z) && s<((LARGEST_INT64-9)/10) ){
|
||||
s = s*10 + (*z - '0');
|
||||
z++, nDigits++;
|
||||
z+=incr, nDigits++;
|
||||
}
|
||||
|
||||
/* skip non-significant significand digits
|
||||
** (increase exponent by d to shift decimal left) */
|
||||
while( sqlite3Isdigit(*z) ) z++, nDigits++, d++;
|
||||
while( z<zEnd && sqlite3Isdigit(*z) ) z+=incr, nDigits++, d++;
|
||||
if( z>=zEnd ) goto do_atof_calc;
|
||||
|
||||
/* if decimal point is present */
|
||||
if( *z=='.' ){
|
||||
z++;
|
||||
z+=incr;
|
||||
/* copy digits from after decimal to significand
|
||||
** (decrease exponent by d to shift decimal right) */
|
||||
while( sqlite3Isdigit(*z) && s<((LARGEST_INT64-9)/10) ){
|
||||
while( z<zEnd && sqlite3Isdigit(*z) && s<((LARGEST_INT64-9)/10) ){
|
||||
s = s*10 + (*z - '0');
|
||||
z++, nDigits++, d--;
|
||||
z+=incr, nDigits++, d--;
|
||||
}
|
||||
/* skip non-significant digits */
|
||||
while( sqlite3Isdigit(*z) ) z++, nDigits++;
|
||||
while( z<zEnd && sqlite3Isdigit(*z) ) z+=incr, nDigits++;
|
||||
}
|
||||
if( z>=zEnd ) goto do_atof_calc;
|
||||
|
||||
/* if exponent is present */
|
||||
if( *z=='e' || *z=='E' ){
|
||||
z++;
|
||||
z+=incr;
|
||||
eValid = 0;
|
||||
if( z>=zEnd ) goto do_atof_calc;
|
||||
/* get sign of exponent */
|
||||
if( *z=='-' ){
|
||||
esign = -1;
|
||||
z++;
|
||||
z+=incr;
|
||||
}else if( *z=='+' ){
|
||||
z++;
|
||||
z+=incr;
|
||||
}
|
||||
/* copy digits to exponent */
|
||||
while( sqlite3Isdigit(*z) ){
|
||||
while( z<zEnd && sqlite3Isdigit(*z) ){
|
||||
e = e*10 + (*z - '0');
|
||||
z++;
|
||||
z+=incr;
|
||||
eValid = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* skip trailing spaces */
|
||||
if( nDigits && eValid ){
|
||||
while( z<zEnd && sqlite3Isspace(*z) ) z+=incr;
|
||||
}
|
||||
|
||||
do_atof_calc:
|
||||
/* adjust exponent by d, and update sign */
|
||||
e = (e*esign) + d;
|
||||
if( e<0 ) {
|
||||
@ -405,10 +401,10 @@ int sqlite3AtoF(const char *z, double *pResult){
|
||||
/* store the result */
|
||||
*pResult = result;
|
||||
|
||||
/* return number of characters used */
|
||||
return (int)(z - zBegin);
|
||||
/* return true if number and no extra non-whitespace chracters after */
|
||||
return z>=zEnd && nDigits>0 && eValid;
|
||||
#else
|
||||
return sqlite3Atoi64(z, pResult);
|
||||
return !sqlite3Atoi64(z, pResult, length, enc);
|
||||
#endif /* SQLITE_OMIT_FLOATING_POINT */
|
||||
}
|
||||
|
||||
@ -416,20 +412,26 @@ int sqlite3AtoF(const char *z, double *pResult){
|
||||
** Compare the 19-character string zNum against the text representation
|
||||
** value 2^63: 9223372036854775808. Return negative, zero, or positive
|
||||
** if zNum is less than, equal to, or greater than the string.
|
||||
** Note that zNum must contain exactly 19 characters.
|
||||
**
|
||||
** Unlike memcmp() this routine is guaranteed to return the difference
|
||||
** in the values of the last digit if the only difference is in the
|
||||
** last digit. So, for example,
|
||||
**
|
||||
** compare2pow63("9223372036854775800")
|
||||
** compare2pow63("9223372036854775800", 1)
|
||||
**
|
||||
** will return -8.
|
||||
*/
|
||||
static int compare2pow63(const char *zNum){
|
||||
int c;
|
||||
c = memcmp(zNum,"922337203685477580",18)*10;
|
||||
static int compare2pow63(const char *zNum, int incr){
|
||||
int c = 0;
|
||||
int i;
|
||||
/* 012345678901234567 */
|
||||
const char *pow63 = "922337203685477580";
|
||||
for(i=0; c==0 && i<18; i++){
|
||||
c = (zNum[i*incr]-pow63[i])*10;
|
||||
}
|
||||
if( c==0 ){
|
||||
c = zNum[18] - '8';
|
||||
c = zNum[18*incr] - '8';
|
||||
testcase( c==(-1) );
|
||||
testcase( c==0 );
|
||||
testcase( c==(+1) );
|
||||
@ -439,94 +441,60 @@ static int compare2pow63(const char *zNum){
|
||||
|
||||
|
||||
/*
|
||||
** Return TRUE if zNum is a 64-bit signed integer and write
|
||||
** the value of the integer into *pNum. If zNum is not an integer
|
||||
** or is an integer that is too large to be expressed with 64 bits,
|
||||
** then return false.
|
||||
** Convert zNum to a 64-bit signed integer and write
|
||||
** the value of the integer into *pNum.
|
||||
** If zNum is exactly 9223372036854665808, return 2.
|
||||
** This is a special case as the context will determine
|
||||
** if it is too big (used as a negative).
|
||||
** If zNum is not an integer or is an integer that
|
||||
** is too large to be expressed with 64 bits,
|
||||
** then return 1. Otherwise return 0.
|
||||
**
|
||||
** When this routine was originally written it dealt with only
|
||||
** 32-bit numbers. At that time, it was much faster than the
|
||||
** atoi() library routine in RedHat 7.2.
|
||||
** length is the number of bytes in the string (bytes, not characters).
|
||||
** The string is not necessarily zero-terminated. The encoding is
|
||||
** given by enc.
|
||||
*/
|
||||
int sqlite3Atoi64(const char *zNum, i64 *pNum){
|
||||
int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc){
|
||||
int incr = (enc==SQLITE_UTF8?1:2);
|
||||
i64 v = 0;
|
||||
int neg;
|
||||
int i, c;
|
||||
int neg = 0; /* assume positive */
|
||||
int i;
|
||||
int c = 0;
|
||||
const char *zStart;
|
||||
while( sqlite3Isspace(*zNum) ) zNum++;
|
||||
const char *zEnd = zNum + length;
|
||||
if( enc==SQLITE_UTF16BE ) zNum++;
|
||||
while( zNum<zEnd && sqlite3Isspace(*zNum) ) zNum+=incr;
|
||||
if( zNum>=zEnd ) goto do_atoi_calc;
|
||||
if( *zNum=='-' ){
|
||||
neg = 1;
|
||||
zNum++;
|
||||
zNum+=incr;
|
||||
}else if( *zNum=='+' ){
|
||||
neg = 0;
|
||||
zNum++;
|
||||
}else{
|
||||
neg = 0;
|
||||
zNum+=incr;
|
||||
}
|
||||
do_atoi_calc:
|
||||
zStart = zNum;
|
||||
while( zNum[0]=='0' ){ zNum++; } /* Skip over leading zeros. Ticket #2454 */
|
||||
for(i=0; (c=zNum[i])>='0' && c<='9'; i++){
|
||||
while( zNum<zEnd && zNum[0]=='0' ){ zNum+=incr; } /* Skip leading zeros. */
|
||||
for(i=0; &zNum[i]<zEnd && (c=zNum[i])>='0' && c<='9'; i+=incr){
|
||||
v = v*10 + c - '0';
|
||||
}
|
||||
*pNum = neg ? -v : v;
|
||||
testcase( i==18 );
|
||||
testcase( i==19 );
|
||||
testcase( i==20 );
|
||||
if( c!=0 || (i==0 && zStart==zNum) || i>19 ){
|
||||
if( (c!=0 && &zNum[i]<zEnd) || (i==0 && zStart==zNum) || i>19*incr ){
|
||||
/* zNum is empty or contains non-numeric text or is longer
|
||||
** than 19 digits (thus guaranting that it is too large) */
|
||||
return 0;
|
||||
}else if( i<19 ){
|
||||
/* Less than 19 digits, so we know that it fits in 64 bits */
|
||||
** than 19 digits (thus guaranteeing that it is too large) */
|
||||
return 1;
|
||||
}else if( i<19*incr ){
|
||||
/* Less than 19 digits, so we know that it fits in 64 bits */
|
||||
return 0;
|
||||
}else{
|
||||
/* 19-digit numbers must be no larger than 9223372036854775807 if positive
|
||||
** or 9223372036854775808 if negative. Note that 9223372036854665808
|
||||
** is 2^63. */
|
||||
return compare2pow63(zNum)<neg;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** The string zNum represents an unsigned integer. The zNum string
|
||||
** consists of one or more digit characters and is terminated by
|
||||
** a zero character. Any stray characters in zNum result in undefined
|
||||
** behavior.
|
||||
**
|
||||
** If the unsigned integer that zNum represents will fit in a
|
||||
** 64-bit signed integer, return TRUE. Otherwise return FALSE.
|
||||
**
|
||||
** If the negFlag parameter is true, that means that zNum really represents
|
||||
** a negative number. (The leading "-" is omitted from zNum.) This
|
||||
** parameter is needed to determine a boundary case. A string
|
||||
** of "9223373036854775808" returns false if negFlag is false or true
|
||||
** if negFlag is true.
|
||||
**
|
||||
** Leading zeros are ignored.
|
||||
*/
|
||||
int sqlite3FitsIn64Bits(const char *zNum, int negFlag){
|
||||
int i;
|
||||
int neg = 0;
|
||||
|
||||
assert( zNum[0]>='0' && zNum[0]<='9' ); /* zNum is an unsigned number */
|
||||
|
||||
if( negFlag ) neg = 1-neg;
|
||||
while( *zNum=='0' ){
|
||||
zNum++; /* Skip leading zeros. Ticket #2454 */
|
||||
}
|
||||
for(i=0; zNum[i]; i++){ assert( zNum[i]>='0' && zNum[i]<='9' ); }
|
||||
testcase( i==18 );
|
||||
testcase( i==19 );
|
||||
testcase( i==20 );
|
||||
if( i<19 ){
|
||||
/* Guaranteed to fit if less than 19 digits */
|
||||
return 1;
|
||||
}else if( i>19 ){
|
||||
/* Guaranteed to be too big if greater than 19 digits */
|
||||
return 0;
|
||||
}else{
|
||||
/* Compare against 2^63. */
|
||||
return compare2pow63(zNum)<neg;
|
||||
** is 2^63. Return 1 if to large */
|
||||
c=compare2pow63(zNum, incr);
|
||||
if( c==0 && neg==0 ) return 2; /* too big, exactly 9223372036854665808 */
|
||||
return c<neg ? 0 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -573,6 +541,16 @@ int sqlite3GetInt32(const char *zNum, int *pValue){
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a 32-bit integer value extracted from a string. If the
|
||||
** string is not an integer, just return 0.
|
||||
*/
|
||||
int sqlite3Atoi(const char *z){
|
||||
int x = 0;
|
||||
if( z ) sqlite3GetInt32(z, &x);
|
||||
return x;
|
||||
}
|
||||
|
||||
/*
|
||||
** The variable-length integer encoding is as follows:
|
||||
**
|
||||
|
||||
@ -110,6 +110,10 @@ int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){
|
||||
sqlite3SetString(pzErrMsg, db, "cannot VACUUM from within a transaction");
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
if( db->activeVdbeCnt>1 ){
|
||||
sqlite3SetString(pzErrMsg, db,"cannot VACUUM - SQL statements in progress");
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
/* Save the current value of the database flags so that it can be
|
||||
** restored before returning. Then set the writable-schema flag, and
|
||||
|
||||
242
src/vdbe.c
242
src/vdbe.c
@ -46,6 +46,17 @@
|
||||
#include "sqliteInt.h"
|
||||
#include "vdbeInt.h"
|
||||
|
||||
/*
|
||||
** Invoke this macro on memory cells just prior to changing the
|
||||
** value of the cell. This macro verifies that shallow copies are
|
||||
** not misused.
|
||||
*/
|
||||
#ifdef SQLITE_DEBUG
|
||||
# define memAboutToChange(P,M) sqlite3VdbeMemPrepareToChange(P,M)
|
||||
#else
|
||||
# define memAboutToChange(P,M)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** The following global variable is incremented every time a cursor
|
||||
** moves, either by the OP_SeekXX, OP_Next, or OP_Prev opcodes. The test
|
||||
@ -238,31 +249,17 @@ static VdbeCursor *allocateCursor(
|
||||
*/
|
||||
static void applyNumericAffinity(Mem *pRec){
|
||||
if( (pRec->flags & (MEM_Real|MEM_Int))==0 ){
|
||||
int realnum;
|
||||
double rValue;
|
||||
i64 iValue;
|
||||
u8 enc = pRec->enc;
|
||||
sqlite3VdbeMemNulTerminate(pRec);
|
||||
if( (pRec->flags&MEM_Str) && sqlite3IsNumber(pRec->z, &realnum, enc) ){
|
||||
i64 value;
|
||||
char *zUtf8 = pRec->z;
|
||||
#ifndef SQLITE_OMIT_UTF16
|
||||
if( enc!=SQLITE_UTF8 ){
|
||||
assert( pRec->db );
|
||||
zUtf8 = sqlite3Utf16to8(pRec->db, pRec->z, pRec->n, enc);
|
||||
if( !zUtf8 ) return;
|
||||
}
|
||||
#endif
|
||||
if( !realnum && sqlite3Atoi64(zUtf8, &value) ){
|
||||
pRec->u.i = value;
|
||||
MemSetTypeFlag(pRec, MEM_Int);
|
||||
}else{
|
||||
sqlite3AtoF(zUtf8, &pRec->r);
|
||||
MemSetTypeFlag(pRec, MEM_Real);
|
||||
}
|
||||
#ifndef SQLITE_OMIT_UTF16
|
||||
if( enc!=SQLITE_UTF8 ){
|
||||
sqlite3DbFree(pRec->db, zUtf8);
|
||||
}
|
||||
#endif
|
||||
if( (pRec->flags&MEM_Str)==0 ) return;
|
||||
if( sqlite3AtoF(pRec->z, &rValue, pRec->n, enc)==0 ) return;
|
||||
if( 0==sqlite3Atoi64(pRec->z, &iValue, pRec->n, enc) ){
|
||||
pRec->u.i = iValue;
|
||||
pRec->flags |= MEM_Int;
|
||||
}else{
|
||||
pRec->r = rValue;
|
||||
pRec->flags |= MEM_Real;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -314,13 +311,13 @@ static void applyAffinity(
|
||||
** into a numeric representation. Use either INTEGER or REAL whichever
|
||||
** is appropriate. But only do the conversion if it is possible without
|
||||
** loss of information and return the revised type of the argument.
|
||||
**
|
||||
** This is an EXPERIMENTAL api and is subject to change or removal.
|
||||
*/
|
||||
int sqlite3_value_numeric_type(sqlite3_value *pVal){
|
||||
Mem *pMem = (Mem*)pVal;
|
||||
applyNumericAffinity(pMem);
|
||||
sqlite3VdbeMemStoreType(pMem);
|
||||
if( pMem->type==SQLITE_TEXT ){
|
||||
applyNumericAffinity(pMem);
|
||||
sqlite3VdbeMemStoreType(pMem);
|
||||
}
|
||||
return pMem->type;
|
||||
}
|
||||
|
||||
@ -667,6 +664,7 @@ int sqlite3VdbeExec(
|
||||
assert( pOp->p2>0 );
|
||||
assert( pOp->p2<=p->nMem );
|
||||
pOut = &aMem[pOp->p2];
|
||||
memAboutToChange(p, pOut);
|
||||
sqlite3VdbeMemReleaseExternal(pOut);
|
||||
pOut->flags = MEM_Int;
|
||||
}
|
||||
@ -676,25 +674,30 @@ int sqlite3VdbeExec(
|
||||
if( (pOp->opflags & OPFLG_IN1)!=0 ){
|
||||
assert( pOp->p1>0 );
|
||||
assert( pOp->p1<=p->nMem );
|
||||
assert( memIsValid(&aMem[pOp->p1]) );
|
||||
REGISTER_TRACE(pOp->p1, &aMem[pOp->p1]);
|
||||
}
|
||||
if( (pOp->opflags & OPFLG_IN2)!=0 ){
|
||||
assert( pOp->p2>0 );
|
||||
assert( pOp->p2<=p->nMem );
|
||||
assert( memIsValid(&aMem[pOp->p2]) );
|
||||
REGISTER_TRACE(pOp->p2, &aMem[pOp->p2]);
|
||||
}
|
||||
if( (pOp->opflags & OPFLG_IN3)!=0 ){
|
||||
assert( pOp->p3>0 );
|
||||
assert( pOp->p3<=p->nMem );
|
||||
assert( memIsValid(&aMem[pOp->p3]) );
|
||||
REGISTER_TRACE(pOp->p3, &aMem[pOp->p3]);
|
||||
}
|
||||
if( (pOp->opflags & OPFLG_OUT2)!=0 ){
|
||||
assert( pOp->p2>0 );
|
||||
assert( pOp->p2<=p->nMem );
|
||||
memAboutToChange(p, &aMem[pOp->p2]);
|
||||
}
|
||||
if( (pOp->opflags & OPFLG_OUT3)!=0 ){
|
||||
assert( pOp->p3>0 );
|
||||
assert( pOp->p3<=p->nMem );
|
||||
memAboutToChange(p, &aMem[pOp->p3]);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -756,6 +759,7 @@ case OP_Goto: { /* jump */
|
||||
case OP_Gosub: { /* jump, in1 */
|
||||
pIn1 = &aMem[pOp->p1];
|
||||
assert( (pIn1->flags & MEM_Dyn)==0 );
|
||||
memAboutToChange(p, pIn1);
|
||||
pIn1->flags = MEM_Int;
|
||||
pIn1->u.i = pc;
|
||||
REGISTER_TRACE(pOp->p1, pIn1);
|
||||
@ -961,11 +965,7 @@ case OP_Null: { /* out2-prerelease */
|
||||
/* Opcode: Blob P1 P2 * P4
|
||||
**
|
||||
** P4 points to a blob of data P1 bytes long. Store this
|
||||
** blob in register P2. This instruction is not coded directly
|
||||
** by the compiler. Instead, the compiler layer specifies
|
||||
** an OP_HexBlob opcode, with the hex string representation of
|
||||
** the blob as P4. This opcode is transformed to an OP_Blob
|
||||
** the first time it is executed.
|
||||
** blob in register P2.
|
||||
*/
|
||||
case OP_Blob: { /* out2-prerelease */
|
||||
assert( pOp->p1 <= SQLITE_MAX_LENGTH );
|
||||
@ -1019,6 +1019,8 @@ case OP_Move: {
|
||||
while( n-- ){
|
||||
assert( pOut<=&aMem[p->nMem] );
|
||||
assert( pIn1<=&aMem[p->nMem] );
|
||||
assert( memIsValid(pIn1) );
|
||||
memAboutToChange(p, pOut);
|
||||
zMalloc = pOut->zMalloc;
|
||||
pOut->zMalloc = 0;
|
||||
sqlite3VdbeMemMove(pOut, pIn1);
|
||||
@ -1064,6 +1066,9 @@ case OP_SCopy: { /* in1, out2 */
|
||||
pOut = &aMem[pOp->p2];
|
||||
assert( pOut!=pIn1 );
|
||||
sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem);
|
||||
#ifdef SQLITE_DEBUG
|
||||
if( pOut->pScopyFrom==0 ) pOut->pScopyFrom = pIn1;
|
||||
#endif
|
||||
REGISTER_TRACE(pOp->p2, pOut);
|
||||
break;
|
||||
}
|
||||
@ -1122,6 +1127,10 @@ case OP_ResultRow: {
|
||||
*/
|
||||
pMem = p->pResultSet = &aMem[pOp->p1];
|
||||
for(i=0; i<pOp->p2; i++){
|
||||
assert( memIsValid(&pMem[i]) );
|
||||
Deephemeralize(&pMem[i]);
|
||||
assert( (pMem[i].flags & MEM_Ephem)==0
|
||||
|| (pMem[i].flags & (MEM_Str|MEM_Blob))==0 );
|
||||
sqlite3VdbeMemNulTerminate(&pMem[i]);
|
||||
sqlite3VdbeMemStoreType(&pMem[i]);
|
||||
REGISTER_TRACE(pOp->p1+i, &pMem[i]);
|
||||
@ -1347,12 +1356,17 @@ case OP_Function: {
|
||||
n = pOp->p5;
|
||||
apVal = p->apArg;
|
||||
assert( apVal || n==0 );
|
||||
assert( pOp->p3>0 && pOp->p3<=p->nMem );
|
||||
pOut = &aMem[pOp->p3];
|
||||
memAboutToChange(p, pOut);
|
||||
|
||||
assert( n==0 || (pOp->p2>0 && pOp->p2+n<=p->nMem+1) );
|
||||
assert( pOp->p3<pOp->p2 || pOp->p3>=pOp->p2+n );
|
||||
pArg = &aMem[pOp->p2];
|
||||
for(i=0; i<n; i++, pArg++){
|
||||
assert( memIsValid(pArg) );
|
||||
apVal[i] = pArg;
|
||||
Deephemeralize(pArg);
|
||||
sqlite3VdbeMemStoreType(pArg);
|
||||
REGISTER_TRACE(pOp->p2+i, pArg);
|
||||
}
|
||||
@ -1366,8 +1380,6 @@ case OP_Function: {
|
||||
ctx.pFunc = ctx.pVdbeFunc->pFunc;
|
||||
}
|
||||
|
||||
assert( pOp->p3>0 && pOp->p3<=p->nMem );
|
||||
pOut = &aMem[pOp->p3];
|
||||
ctx.s.flags = MEM_Null;
|
||||
ctx.s.db = db;
|
||||
ctx.s.xDel = 0;
|
||||
@ -1387,7 +1399,7 @@ case OP_Function: {
|
||||
assert( pOp[-1].opcode==OP_CollSeq );
|
||||
ctx.pColl = pOp[-1].p4.pColl;
|
||||
}
|
||||
(*ctx.pFunc->xFunc)(&ctx, n, apVal);
|
||||
(*ctx.pFunc->xFunc)(&ctx, n, apVal); /* IMP: R-24505-23230 */
|
||||
if( db->mallocFailed ){
|
||||
/* Even though a malloc() has failed, the implementation of the
|
||||
** user function may have called an sqlite3_result_XXX() function
|
||||
@ -1439,7 +1451,7 @@ case OP_Function: {
|
||||
/* Opcode: ShiftLeft P1 P2 P3 * *
|
||||
**
|
||||
** Shift the integer value in register P2 to the left by the
|
||||
** number of bits specified by the integer in regiser P1.
|
||||
** number of bits specified by the integer in register P1.
|
||||
** Store the result in register P3.
|
||||
** If either input is NULL, the result is NULL.
|
||||
*/
|
||||
@ -1487,6 +1499,7 @@ case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */
|
||||
*/
|
||||
case OP_AddImm: { /* in1 */
|
||||
pIn1 = &aMem[pOp->p1];
|
||||
memAboutToChange(p, pIn1);
|
||||
sqlite3VdbeMemIntegerify(pIn1);
|
||||
pIn1->u.i += pOp->p2;
|
||||
break;
|
||||
@ -1546,6 +1559,7 @@ case OP_RealAffinity: { /* in1 */
|
||||
*/
|
||||
case OP_ToText: { /* same as TK_TO_TEXT, in1 */
|
||||
pIn1 = &aMem[pOp->p1];
|
||||
memAboutToChange(p, pIn1);
|
||||
if( pIn1->flags & MEM_Null ) break;
|
||||
assert( MEM_Str==(MEM_Blob>>3) );
|
||||
pIn1->flags |= (pIn1->flags&MEM_Blob)>>3;
|
||||
@ -1592,16 +1606,14 @@ case OP_ToBlob: { /* same as TK_TO_BLOB, in1 */
|
||||
*/
|
||||
case OP_ToNumeric: { /* same as TK_TO_NUMERIC, in1 */
|
||||
pIn1 = &aMem[pOp->p1];
|
||||
if( (pIn1->flags & (MEM_Null|MEM_Int|MEM_Real))==0 ){
|
||||
sqlite3VdbeMemNumerify(pIn1);
|
||||
}
|
||||
sqlite3VdbeMemNumerify(pIn1);
|
||||
break;
|
||||
}
|
||||
#endif /* SQLITE_OMIT_CAST */
|
||||
|
||||
/* Opcode: ToInt P1 * * * *
|
||||
**
|
||||
** Force the value in register P1 be an integer. If
|
||||
** Force the value in register P1 to be an integer. If
|
||||
** The value is currently a real number, drop its fractional part.
|
||||
** If the value is text or blob, try to convert it to an integer using the
|
||||
** equivalent of atoi() and store 0 if no such conversion is possible.
|
||||
@ -1628,6 +1640,7 @@ case OP_ToInt: { /* same as TK_TO_INT, in1 */
|
||||
*/
|
||||
case OP_ToReal: { /* same as TK_TO_REAL, in1 */
|
||||
pIn1 = &aMem[pOp->p1];
|
||||
memAboutToChange(p, pIn1);
|
||||
if( (pIn1->flags & MEM_Null)==0 ){
|
||||
sqlite3VdbeMemRealify(pIn1);
|
||||
}
|
||||
@ -1642,7 +1655,7 @@ case OP_ToReal: { /* same as TK_TO_REAL, in1 */
|
||||
**
|
||||
** If the SQLITE_JUMPIFNULL bit of P5 is set and either reg(P1) or
|
||||
** reg(P3) is NULL then take the jump. If the SQLITE_JUMPIFNULL
|
||||
** bit is clear then fall thru if either operand is NULL.
|
||||
** bit is clear then fall through if either operand is NULL.
|
||||
**
|
||||
** The SQLITE_AFF_MASK portion of P5 must be an affinity character -
|
||||
** SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made
|
||||
@ -1770,6 +1783,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
|
||||
|
||||
if( pOp->p5 & SQLITE_STOREP2 ){
|
||||
pOut = &aMem[pOp->p2];
|
||||
memAboutToChange(p, pOut);
|
||||
MemSetTypeFlag(pOut, MEM_Int);
|
||||
pOut->u.i = res;
|
||||
REGISTER_TRACE(pOp->p2, pOut);
|
||||
@ -1801,8 +1815,8 @@ case OP_Permutation: {
|
||||
|
||||
/* Opcode: Compare P1 P2 P3 P4 *
|
||||
**
|
||||
** Compare to vectors of registers in reg(P1)..reg(P1+P3-1) (all this
|
||||
** one "A") and in reg(P2)..reg(P2+P3-1) ("B"). Save the result of
|
||||
** Compare two vectors of registers in reg(P1)..reg(P1+P3-1) (call this
|
||||
** vector "A") and in reg(P2)..reg(P2+P3-1) ("B"). Save the result of
|
||||
** the comparison for use by the next OP_Jump instruct.
|
||||
**
|
||||
** P4 is a KeyInfo structure that defines collating sequences and sort
|
||||
@ -1842,6 +1856,8 @@ case OP_Compare: {
|
||||
#endif /* SQLITE_DEBUG */
|
||||
for(i=0; i<n; i++){
|
||||
idx = aPermute ? aPermute[i] : i;
|
||||
assert( memIsValid(&aMem[p1+idx]) );
|
||||
assert( memIsValid(&aMem[p2+idx]) );
|
||||
REGISTER_TRACE(p1+idx, &aMem[p1+idx]);
|
||||
REGISTER_TRACE(p2+idx, &aMem[p2+idx]);
|
||||
assert( i<pKeyInfo->nField );
|
||||
@ -2067,6 +2083,7 @@ case OP_Column: {
|
||||
assert( p1<p->nCursor );
|
||||
assert( pOp->p3>0 && pOp->p3<=p->nMem );
|
||||
pDest = &aMem[pOp->p3];
|
||||
memAboutToChange(p, pDest);
|
||||
MemSetTypeFlag(pDest, MEM_Null);
|
||||
zRec = 0;
|
||||
|
||||
@ -2114,6 +2131,7 @@ case OP_Column: {
|
||||
}else if( pC->pseudoTableReg>0 ){
|
||||
pReg = &aMem[pC->pseudoTableReg];
|
||||
assert( pReg->flags & MEM_Blob );
|
||||
assert( memIsValid(pReg) );
|
||||
payloadSize = pReg->n;
|
||||
zRec = pReg->z;
|
||||
pC->cacheStatus = (pOp->p5&OPFLAG_CLEARCACHE) ? CACHE_STALE : p->cacheCtr;
|
||||
@ -2336,6 +2354,7 @@ case OP_Affinity: {
|
||||
pIn1 = &aMem[pOp->p1];
|
||||
while( (cAff = *(zAffinity++))!=0 ){
|
||||
assert( pIn1 <= &p->aMem[p->nMem] );
|
||||
assert( memIsValid(pIn1) );
|
||||
ExpandBlob(pIn1);
|
||||
applyAffinity(pIn1, cAff, encoding);
|
||||
pIn1++;
|
||||
@ -2345,12 +2364,9 @@ case OP_Affinity: {
|
||||
|
||||
/* Opcode: MakeRecord P1 P2 P3 P4 *
|
||||
**
|
||||
** Convert P2 registers beginning with P1 into a single entry
|
||||
** suitable for use as a data record in a database table or as a key
|
||||
** in an index. The details of the format are irrelevant as long as
|
||||
** the OP_Column opcode can decode the record later.
|
||||
** Refer to source code comments for the details of the record
|
||||
** format.
|
||||
** Convert P2 registers beginning with P1 into the [record format]
|
||||
** use as a data record in a database table or as a key
|
||||
** in an index. The OP_Column opcode can decode the record later.
|
||||
**
|
||||
** P4 may be a string that is P2 characters long. The nth character of the
|
||||
** string indicates the column affinity that should be used for the nth
|
||||
@ -2405,10 +2421,16 @@ case OP_MakeRecord: {
|
||||
pLast = &pData0[nField-1];
|
||||
file_format = p->minWriteFileFormat;
|
||||
|
||||
/* Identify the output register */
|
||||
assert( pOp->p3<pOp->p1 || pOp->p3>=pOp->p1+pOp->p2 );
|
||||
pOut = &aMem[pOp->p3];
|
||||
memAboutToChange(p, pOut);
|
||||
|
||||
/* Loop through the elements that will make up the record to figure
|
||||
** out how much space is required for the new record.
|
||||
*/
|
||||
for(pRec=pData0; pRec<=pLast; pRec++){
|
||||
assert( memIsValid(pRec) );
|
||||
if( zAffinity ){
|
||||
applyAffinity(pRec, zAffinity[pRec-pData0], encoding);
|
||||
}
|
||||
@ -2443,8 +2465,6 @@ case OP_MakeRecord: {
|
||||
** be one of the input registers (because the following call to
|
||||
** sqlite3VdbeMemGrow() could clobber the value before it is used).
|
||||
*/
|
||||
assert( pOp->p3<pOp->p1 || pOp->p3>=pOp->p1+pOp->p2 );
|
||||
pOut = &aMem[pOp->p3];
|
||||
if( sqlite3VdbeMemGrow(pOut, (int)nByte, 0) ){
|
||||
goto no_mem;
|
||||
}
|
||||
@ -2613,6 +2633,7 @@ case OP_Savepoint: {
|
||||
if( p1==SAVEPOINT_ROLLBACK && (db->flags&SQLITE_InternChanges)!=0 ){
|
||||
sqlite3ExpirePreparedStatements(db);
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
db->flags = (db->flags | SQLITE_InternChanges);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2991,6 +3012,8 @@ case OP_OpenWrite: {
|
||||
assert( p2>0 );
|
||||
assert( p2<=p->nMem );
|
||||
pIn2 = &aMem[p2];
|
||||
assert( memIsValid(pIn2) );
|
||||
assert( (pIn2->flags & MEM_Int)!=0 );
|
||||
sqlite3VdbeMemIntegerify(pIn2);
|
||||
p2 = (int)pIn2->u.i;
|
||||
/* The p2 value always comes from a prior OP_CreateTable opcode and
|
||||
@ -3013,6 +3036,7 @@ case OP_OpenWrite: {
|
||||
pCur = allocateCursor(p, pOp->p1, nField, iDb, 1);
|
||||
if( pCur==0 ) goto no_mem;
|
||||
pCur->nullRow = 1;
|
||||
pCur->isOrdered = 1;
|
||||
rc = sqlite3BtreeCursor(pX, p2, wrFlag, pKeyInfo, pCur->pCursor);
|
||||
pCur->pKeyInfo = pKeyInfo;
|
||||
|
||||
@ -3063,7 +3087,7 @@ case OP_OpenWrite: {
|
||||
case OP_OpenAutoindex:
|
||||
case OP_OpenEphemeral: {
|
||||
VdbeCursor *pCx;
|
||||
static const int openFlags =
|
||||
static const int vfsFlags =
|
||||
SQLITE_OPEN_READWRITE |
|
||||
SQLITE_OPEN_CREATE |
|
||||
SQLITE_OPEN_EXCLUSIVE |
|
||||
@ -3074,21 +3098,21 @@ case OP_OpenEphemeral: {
|
||||
pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, 1);
|
||||
if( pCx==0 ) goto no_mem;
|
||||
pCx->nullRow = 1;
|
||||
rc = sqlite3BtreeFactory(db, 0, 1, SQLITE_DEFAULT_TEMP_CACHE_SIZE, openFlags,
|
||||
&pCx->pBt);
|
||||
rc = sqlite3BtreeOpen(0, db, &pCx->pBt,
|
||||
BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5, vfsFlags);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3BtreeBeginTrans(pCx->pBt, 1);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
/* If a transient index is required, create it by calling
|
||||
** sqlite3BtreeCreateTable() with the BTREE_ZERODATA flag before
|
||||
** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before
|
||||
** opening it. If a transient table is required, just use the
|
||||
** automatically created table with root-page 1 (an INTKEY table).
|
||||
** automatically created table with root-page 1 (an BLOB_INTKEY table).
|
||||
*/
|
||||
if( pOp->p4.pKeyInfo ){
|
||||
int pgno;
|
||||
assert( pOp->p4type==P4_KEYINFO );
|
||||
rc = sqlite3BtreeCreateTable(pCx->pBt, &pgno, BTREE_ZERODATA);
|
||||
rc = sqlite3BtreeCreateTable(pCx->pBt, &pgno, BTREE_BLOBKEY);
|
||||
if( rc==SQLITE_OK ){
|
||||
assert( pgno==MASTER_ROOT+1 );
|
||||
rc = sqlite3BtreeCursor(pCx->pBt, pgno, 1,
|
||||
@ -3102,6 +3126,7 @@ case OP_OpenEphemeral: {
|
||||
pCx->isTable = 1;
|
||||
}
|
||||
}
|
||||
pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED);
|
||||
pCx->isIndex = !pCx->isTable;
|
||||
break;
|
||||
}
|
||||
@ -3217,6 +3242,7 @@ case OP_SeekGt: { /* jump, in3 */
|
||||
assert( OP_SeekLe == OP_SeekLt+1 );
|
||||
assert( OP_SeekGe == OP_SeekLt+2 );
|
||||
assert( OP_SeekGt == OP_SeekLt+3 );
|
||||
assert( pC->isOrdered );
|
||||
if( pC->pCursor!=0 ){
|
||||
oc = pOp->opcode;
|
||||
pC->nullRow = 0;
|
||||
@ -3299,6 +3325,9 @@ case OP_SeekGt: { /* jump, in3 */
|
||||
assert( oc!=OP_SeekLt || r.flags==0 );
|
||||
|
||||
r.aMem = &aMem[pOp->p3];
|
||||
#ifdef SQLITE_DEBUG
|
||||
{ int i; for(i=0; i<r.nField; i++) assert( memIsValid(&r.aMem[i]) ); }
|
||||
#endif
|
||||
ExpandBlob(r.aMem);
|
||||
rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, &r, 0, 0, &res);
|
||||
if( rc!=SQLITE_OK ){
|
||||
@ -3423,11 +3452,14 @@ case OP_Found: { /* jump, in3 */
|
||||
r.pKeyInfo = pC->pKeyInfo;
|
||||
r.nField = (u16)pOp->p4.i;
|
||||
r.aMem = pIn3;
|
||||
#ifdef SQLITE_DEBUG
|
||||
{ int i; for(i=0; i<r.nField; i++) assert( memIsValid(&r.aMem[i]) ); }
|
||||
#endif
|
||||
r.flags = UNPACKED_PREFIX_MATCH;
|
||||
pIdxKey = &r;
|
||||
}else{
|
||||
assert( pIn3->flags & MEM_Blob );
|
||||
ExpandBlob(pIn3);
|
||||
assert( (pIn3->flags & MEM_Zero)==0 ); /* zeroblobs already expanded */
|
||||
pIdxKey = sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z,
|
||||
aTempRec, sizeof(aTempRec));
|
||||
if( pIdxKey==0 ){
|
||||
@ -3520,6 +3552,9 @@ case OP_IsUnique: { /* jump, in3 */
|
||||
r.nField = nField + 1;
|
||||
r.flags = UNPACKED_PREFIX_SEARCH;
|
||||
r.aMem = aMx;
|
||||
#ifdef SQLITE_DEBUG
|
||||
{ int i; for(i=0; i<r.nField; i++) assert( memIsValid(&r.aMem[i]) ); }
|
||||
#endif
|
||||
|
||||
/* Extract the value of R from register P3. */
|
||||
sqlite3VdbeMemIntegerify(pIn3);
|
||||
@ -3542,7 +3577,7 @@ case OP_IsUnique: { /* jump, in3 */
|
||||
**
|
||||
** Use the content of register P3 as a integer key. If a record
|
||||
** with that key does not exist in table of P1, then jump to P2.
|
||||
** If the record does exist, then fall thru. The cursor is left
|
||||
** If the record does exist, then fall through. The cursor is left
|
||||
** pointing to the record if it exists.
|
||||
**
|
||||
** The difference between this operation and NotFound is that this
|
||||
@ -3696,7 +3731,9 @@ case OP_NewRowid: { /* out2-prerelease */
|
||||
/* Assert that P3 is a valid memory cell. */
|
||||
assert( pOp->p3<=p->nMem );
|
||||
pMem = &aMem[pOp->p3];
|
||||
memAboutToChange(p, pMem);
|
||||
}
|
||||
assert( memIsValid(pMem) );
|
||||
|
||||
REGISTER_TRACE(pOp->p3, pMem);
|
||||
sqlite3VdbeMemIntegerify(pMem);
|
||||
@ -3715,29 +3752,36 @@ case OP_NewRowid: { /* out2-prerelease */
|
||||
sqlite3BtreeSetCachedRowid(pC->pCursor, v<MAX_ROWID ? v+1 : 0);
|
||||
}
|
||||
if( pC->useRandomRowid ){
|
||||
/* IMPLEMENTATION-OF: R-48598-02938 If the largest ROWID is equal to the
|
||||
/* IMPLEMENTATION-OF: R-07677-41881 If the largest ROWID is equal to the
|
||||
** largest possible integer (9223372036854775807) then the database
|
||||
** engine starts picking candidate ROWIDs at random until it finds one
|
||||
** that is not previously used.
|
||||
*/
|
||||
** engine starts picking positive candidate ROWIDs at random until
|
||||
** it finds one that is not previously used. */
|
||||
assert( pOp->p3==0 ); /* We cannot be in random rowid mode if this is
|
||||
** an AUTOINCREMENT table. */
|
||||
/* on the first attempt, simply do one more than previous */
|
||||
v = db->lastRowid;
|
||||
v &= (MAX_ROWID>>1); /* ensure doesn't go negative */
|
||||
v++; /* ensure non-zero */
|
||||
cnt = 0;
|
||||
do{
|
||||
if( cnt==0 && (v&0xffffff)==v ){
|
||||
v++;
|
||||
while( ((rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, 0, (u64)v,
|
||||
0, &res))==SQLITE_OK)
|
||||
&& (res==0)
|
||||
&& (++cnt<100)){
|
||||
/* collision - try another random rowid */
|
||||
sqlite3_randomness(sizeof(v), &v);
|
||||
if( cnt<5 ){
|
||||
/* try "small" random rowids for the initial attempts */
|
||||
v &= 0xffffff;
|
||||
}else{
|
||||
sqlite3_randomness(sizeof(v), &v);
|
||||
if( cnt<5 ) v &= 0xffffff;
|
||||
v &= (MAX_ROWID>>1); /* ensure doesn't go negative */
|
||||
}
|
||||
rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, 0, (u64)v, 0, &res);
|
||||
cnt++;
|
||||
}while( cnt<100 && rc==SQLITE_OK && res==0 );
|
||||
v++; /* ensure non-zero */
|
||||
}
|
||||
if( rc==SQLITE_OK && res==0 ){
|
||||
rc = SQLITE_FULL; /* IMP: R-38219-53002 */
|
||||
goto abort_due_to_error;
|
||||
}
|
||||
assert( v>0 ); /* EV: R-40812-03570 */
|
||||
}
|
||||
pC->rowidIsValid = 0;
|
||||
pC->deferredMoveto = 0;
|
||||
@ -3805,6 +3849,7 @@ case OP_InsertInt: {
|
||||
|
||||
pData = &aMem[pOp->p2];
|
||||
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
|
||||
assert( memIsValid(pData) );
|
||||
pC = p->apCsr[pOp->p1];
|
||||
assert( pC!=0 );
|
||||
assert( pC->pCursor!=0 );
|
||||
@ -3815,6 +3860,7 @@ case OP_InsertInt: {
|
||||
if( pOp->opcode==OP_Insert ){
|
||||
pKey = &aMem[pOp->p3];
|
||||
assert( pKey->flags & MEM_Int );
|
||||
assert( memIsValid(pKey) );
|
||||
REGISTER_TRACE(pOp->p3, pKey);
|
||||
iKey = pKey->u.i;
|
||||
}else{
|
||||
@ -3962,6 +4008,7 @@ case OP_RowData: {
|
||||
i64 n64;
|
||||
|
||||
pOut = &aMem[pOp->p2];
|
||||
memAboutToChange(p, pOut);
|
||||
|
||||
/* Note that RowKey and RowData are really exactly the same instruction */
|
||||
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
|
||||
@ -4290,6 +4337,9 @@ case OP_IdxDelete: {
|
||||
r.nField = (u16)pOp->p3;
|
||||
r.flags = 0;
|
||||
r.aMem = &aMem[pOp->p2];
|
||||
#ifdef SQLITE_DEBUG
|
||||
{ int i; for(i=0; i<r.nField; i++) assert( memIsValid(&r.aMem[i]) ); }
|
||||
#endif
|
||||
rc = sqlite3BtreeMovetoUnpacked(pCrsr, &r, 0, 0, &res);
|
||||
if( rc==SQLITE_OK && res==0 ){
|
||||
rc = sqlite3BtreeDelete(pCrsr);
|
||||
@ -4370,6 +4420,7 @@ case OP_IdxGE: { /* jump */
|
||||
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
|
||||
pC = p->apCsr[pOp->p1];
|
||||
assert( pC!=0 );
|
||||
assert( pC->isOrdered );
|
||||
if( ALWAYS(pC->pCursor!=0) ){
|
||||
assert( pC->deferredMoveto==0 );
|
||||
assert( pOp->p5==0 || pOp->p5==1 );
|
||||
@ -4382,6 +4433,9 @@ case OP_IdxGE: { /* jump */
|
||||
r.flags = UNPACKED_IGNORE_ROWID;
|
||||
}
|
||||
r.aMem = &aMem[pOp->p3];
|
||||
#ifdef SQLITE_DEBUG
|
||||
{ int i; for(i=0; i<r.nField; i++) assert( memIsValid(&r.aMem[i]) ); }
|
||||
#endif
|
||||
rc = sqlite3VdbeIdxKeyCompare(pC, &r, &res);
|
||||
if( pOp->opcode==OP_IdxLT ){
|
||||
res = -res;
|
||||
@ -4481,6 +4535,8 @@ case OP_Clear: {
|
||||
if( pOp->p3 ){
|
||||
p->nChange += nChange;
|
||||
if( pOp->p3>0 ){
|
||||
assert( memIsValid(&aMem[pOp->p3]) );
|
||||
memAboutToChange(p, &aMem[pOp->p3]);
|
||||
aMem[pOp->p3].u.i += nChange;
|
||||
}
|
||||
}
|
||||
@ -4522,9 +4578,9 @@ case OP_CreateTable: { /* out2-prerelease */
|
||||
assert( pDb->pBt!=0 );
|
||||
if( pOp->opcode==OP_CreateTable ){
|
||||
/* flags = BTREE_INTKEY; */
|
||||
flags = BTREE_LEAFDATA|BTREE_INTKEY;
|
||||
flags = BTREE_INTKEY;
|
||||
}else{
|
||||
flags = BTREE_ZERODATA;
|
||||
flags = BTREE_BLOBKEY;
|
||||
}
|
||||
rc = sqlite3BtreeCreateTable(pDb->pBt, &pgno, flags);
|
||||
pOut->u.i = pgno;
|
||||
@ -4843,6 +4899,7 @@ case OP_Program: { /* jump */
|
||||
|
||||
pProgram = pOp->p4.pProgram;
|
||||
pRt = &aMem[pOp->p3];
|
||||
assert( memIsValid(pRt) );
|
||||
assert( pProgram->nOp>0 );
|
||||
|
||||
/* If the p5 flag is clear, then recursive invocation of triggers is
|
||||
@ -5012,6 +5069,7 @@ case OP_MemMax: { /* in2 */
|
||||
}else{
|
||||
pIn1 = &aMem[pOp->p1];
|
||||
}
|
||||
assert( memIsValid(pIn1) );
|
||||
sqlite3VdbeMemIntegerify(pIn1);
|
||||
pIn2 = &aMem[pOp->p2];
|
||||
sqlite3VdbeMemIntegerify(pIn2);
|
||||
@ -5096,7 +5154,9 @@ case OP_AggStep: {
|
||||
apVal = p->apArg;
|
||||
assert( apVal || n==0 );
|
||||
for(i=0; i<n; i++, pRec++){
|
||||
assert( memIsValid(pRec) );
|
||||
apVal[i] = pRec;
|
||||
memAboutToChange(p, pRec);
|
||||
sqlite3VdbeMemStoreType(pRec);
|
||||
}
|
||||
ctx.pFunc = pOp->p4.pFunc;
|
||||
@ -5116,7 +5176,7 @@ case OP_AggStep: {
|
||||
assert( pOp[-1].opcode==OP_CollSeq );
|
||||
ctx.pColl = pOp[-1].p4.pColl;
|
||||
}
|
||||
(ctx.pFunc->xStep)(&ctx, n, apVal);
|
||||
(ctx.pFunc->xStep)(&ctx, n, apVal); /* IMP: R-24505-23230 */
|
||||
if( ctx.isError ){
|
||||
sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3_value_text(&ctx.s));
|
||||
rc = ctx.isError;
|
||||
@ -5491,6 +5551,7 @@ case OP_VFilter: { /* jump */
|
||||
pQuery = &aMem[pOp->p3];
|
||||
pArgc = &pQuery[1];
|
||||
pCur = p->apCsr[pOp->p1];
|
||||
assert( memIsValid(pQuery) );
|
||||
REGISTER_TRACE(pOp->p3, pQuery);
|
||||
assert( pCur->pVtabCursor );
|
||||
pVtabCursor = pCur->pVtabCursor;
|
||||
@ -5546,6 +5607,7 @@ case OP_VColumn: {
|
||||
assert( pCur->pVtabCursor );
|
||||
assert( pOp->p3>0 && pOp->p3<=p->nMem );
|
||||
pDest = &aMem[pOp->p3];
|
||||
memAboutToChange(p, pDest);
|
||||
if( pCur->nullRow ){
|
||||
sqlite3VdbeMemSetNull(pDest);
|
||||
break;
|
||||
@ -5644,10 +5706,12 @@ case OP_VRename: {
|
||||
pVtab = pOp->p4.pVtab->pVtab;
|
||||
pName = &aMem[pOp->p1];
|
||||
assert( pVtab->pModule->xRename );
|
||||
assert( memIsValid(pName) );
|
||||
REGISTER_TRACE(pOp->p1, pName);
|
||||
assert( pName->flags & MEM_Str );
|
||||
rc = pVtab->pModule->xRename(pVtab, pName->z);
|
||||
importVtabErrMsg(p, pVtab);
|
||||
p->expired = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
@ -5694,6 +5758,8 @@ case OP_VUpdate: {
|
||||
apArg = p->apArg;
|
||||
pX = &aMem[pOp->p3];
|
||||
for(i=0; i<nArg; i++){
|
||||
assert( memIsValid(pX) );
|
||||
memAboutToChange(p, pX);
|
||||
sqlite3VdbeMemStoreType(pX);
|
||||
apArg[i] = pX;
|
||||
pX++;
|
||||
@ -5721,6 +5787,32 @@ case OP_Pagecount: { /* out2-prerelease */
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef SQLITE_OMIT_PAGER_PRAGMAS
|
||||
/* Opcode: MaxPgcnt P1 P2 P3 * *
|
||||
**
|
||||
** Try to set the maximum page count for database P1 to the value in P3.
|
||||
** Do not let the maximum page count fall below the current page count and
|
||||
** do not change the maximum page count value if P3==0.
|
||||
**
|
||||
** Store the maximum page count after the change in register P2.
|
||||
*/
|
||||
case OP_MaxPgcnt: { /* out2-prerelease */
|
||||
unsigned int newMax;
|
||||
Btree *pBt;
|
||||
|
||||
pBt = db->aDb[pOp->p1].pBt;
|
||||
newMax = 0;
|
||||
if( pOp->p3 ){
|
||||
newMax = sqlite3BtreeLastPage(pBt);
|
||||
if( newMax < (unsigned)pOp->p3 ) newMax = (unsigned)pOp->p3;
|
||||
}
|
||||
pOut->u.i = sqlite3BtreeMaxPageCount(pBt, newMax);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef SQLITE_OMIT_TRACE
|
||||
/* Opcode: Trace * * * P4 *
|
||||
**
|
||||
|
||||
135
src/vdbeInt.h
135
src/vdbeInt.h
@ -39,16 +39,14 @@ typedef unsigned char Bool;
|
||||
**
|
||||
** Every cursor that the virtual machine has open is represented by an
|
||||
** instance of the following structure.
|
||||
**
|
||||
** If the VdbeCursor.isTriggerRow flag is set it means that this cursor is
|
||||
** really a single row that represents the NEW or OLD pseudo-table of
|
||||
** a row trigger. The data for the row is stored in VdbeCursor.pData and
|
||||
** the rowid is in VdbeCursor.iKey.
|
||||
*/
|
||||
struct VdbeCursor {
|
||||
BtCursor *pCursor; /* The cursor structure of the backend */
|
||||
Btree *pBt; /* Separate file holding temporary table */
|
||||
KeyInfo *pKeyInfo; /* Info about index keys needed by index cursors */
|
||||
int iDb; /* Index of cursor database in db->aDb[] (or -1) */
|
||||
i64 lastRowid; /* Last rowid from a Next or NextIdx operation */
|
||||
int pseudoTableReg; /* Register holding pseudotable content. */
|
||||
int nField; /* Number of fields in the header */
|
||||
Bool zeroed; /* True if zeroed out and ready for reuse */
|
||||
Bool rowidIsValid; /* True if lastRowid is valid */
|
||||
Bool atFirst; /* True if pointing to first entry */
|
||||
@ -57,14 +55,12 @@ struct VdbeCursor {
|
||||
Bool deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */
|
||||
Bool isTable; /* True if a table requiring integer keys */
|
||||
Bool isIndex; /* True if an index containing keys only - no data */
|
||||
i64 movetoTarget; /* Argument to the deferred sqlite3BtreeMoveto() */
|
||||
Btree *pBt; /* Separate file holding temporary table */
|
||||
int pseudoTableReg; /* Register holding pseudotable content. */
|
||||
KeyInfo *pKeyInfo; /* Info about index keys needed by index cursors */
|
||||
int nField; /* Number of fields in the header */
|
||||
i64 seqCount; /* Sequence counter */
|
||||
Bool isOrdered; /* True if the underlying table is BTREE_UNORDERED */
|
||||
sqlite3_vtab_cursor *pVtabCursor; /* The cursor for a virtual table */
|
||||
const sqlite3_module *pModule; /* Module for cursor pVtabCursor */
|
||||
i64 seqCount; /* Sequence counter */
|
||||
i64 movetoTarget; /* Argument to the deferred sqlite3BtreeMoveto() */
|
||||
i64 lastRowid; /* Last rowid from a Next or NextIdx operation */
|
||||
|
||||
/* Result of last sqlite3BtreeMoveto() done by an OP_NotExists or
|
||||
** OP_IsUnique opcode on this cursor. */
|
||||
@ -96,26 +92,34 @@ typedef struct VdbeCursor VdbeCursor;
|
||||
** restoring the state of the VM to as it was before the sub-program
|
||||
** began executing.
|
||||
**
|
||||
** Frames are stored in a linked list headed at Vdbe.pParent. Vdbe.pParent
|
||||
** is the parent of the current frame, or zero if the current frame
|
||||
** is the main Vdbe program.
|
||||
** The memory for a VdbeFrame object is allocated and managed by a memory
|
||||
** cell in the parent (calling) frame. When the memory cell is deleted or
|
||||
** overwritten, the VdbeFrame object is not freed immediately. Instead, it
|
||||
** is linked into the Vdbe.pDelFrame list. The contents of the Vdbe.pDelFrame
|
||||
** list is deleted when the VM is reset in VdbeHalt(). The reason for doing
|
||||
** this instead of deleting the VdbeFrame immediately is to avoid recursive
|
||||
** calls to sqlite3VdbeMemRelease() when the memory cells belonging to the
|
||||
** child frame are released.
|
||||
**
|
||||
** The currently executing frame is stored in Vdbe.pFrame. Vdbe.pFrame is
|
||||
** set to NULL if the currently executing frame is the main program.
|
||||
*/
|
||||
typedef struct VdbeFrame VdbeFrame;
|
||||
struct VdbeFrame {
|
||||
Vdbe *v; /* VM this frame belongs to */
|
||||
int pc; /* Program Counter */
|
||||
Op *aOp; /* Program instructions */
|
||||
int pc; /* Program Counter in parent (calling) frame */
|
||||
Op *aOp; /* Program instructions for parent frame */
|
||||
int nOp; /* Size of aOp array */
|
||||
Mem *aMem; /* Array of memory cells */
|
||||
Mem *aMem; /* Array of memory cells for parent frame */
|
||||
int nMem; /* Number of entries in aMem */
|
||||
VdbeCursor **apCsr; /* Element of Vdbe cursors */
|
||||
VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */
|
||||
u16 nCursor; /* Number of entries in apCsr */
|
||||
void *token; /* Copy of SubProgram.token */
|
||||
int nChildMem; /* Number of memory cells for child frame */
|
||||
int nChildCsr; /* Number of cursors for child frame */
|
||||
i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */
|
||||
int nChange; /* Statement changes (Vdbe.nChanges) */
|
||||
VdbeFrame *pParent; /* Parent of this frame */
|
||||
VdbeFrame *pParent; /* Parent of this frame, or NULL if parent is main */
|
||||
};
|
||||
|
||||
#define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))])
|
||||
@ -128,29 +132,27 @@ struct VdbeFrame {
|
||||
/*
|
||||
** Internally, the vdbe manipulates nearly all SQL values as Mem
|
||||
** structures. Each Mem struct may cache multiple representations (string,
|
||||
** integer etc.) of the same value. A value (and therefore Mem structure)
|
||||
** has the following properties:
|
||||
**
|
||||
** Each value has a manifest type. The manifest type of the value stored
|
||||
** in a Mem struct is returned by the MemType(Mem*) macro. The type is
|
||||
** one of SQLITE_NULL, SQLITE_INTEGER, SQLITE_REAL, SQLITE_TEXT or
|
||||
** SQLITE_BLOB.
|
||||
** integer etc.) of the same value.
|
||||
*/
|
||||
struct Mem {
|
||||
sqlite3 *db; /* The associated database connection */
|
||||
char *z; /* String or BLOB value */
|
||||
double r; /* Real value */
|
||||
union {
|
||||
i64 i; /* Integer value. */
|
||||
i64 i; /* Integer value used when MEM_Int is set in flags */
|
||||
int nZero; /* Used when bit MEM_Zero is set in flags */
|
||||
FuncDef *pDef; /* Used only when flags==MEM_Agg */
|
||||
RowSet *pRowSet; /* Used only when flags==MEM_RowSet */
|
||||
VdbeFrame *pFrame; /* Used when flags==MEM_Frame */
|
||||
} u;
|
||||
double r; /* Real value */
|
||||
sqlite3 *db; /* The associated database connection */
|
||||
char *z; /* String or BLOB value */
|
||||
int n; /* Number of characters in string value, excluding '\0' */
|
||||
u16 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
|
||||
u8 type; /* One of SQLITE_NULL, SQLITE_TEXT, SQLITE_INTEGER, etc */
|
||||
u8 enc; /* SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */
|
||||
#ifdef SQLITE_DEBUG
|
||||
Mem *pScopyFrom; /* This Mem is a shallow copy of pScopyFrom */
|
||||
void *pFiller; /* So that sizeof(Mem) is a multiple of 8 */
|
||||
#endif
|
||||
void (*xDel)(void *); /* If not null, call this function to delete Mem.z */
|
||||
char *zMalloc; /* Dynamic buffer allocated by sqlite3_malloc() */
|
||||
};
|
||||
@ -166,9 +168,6 @@ struct Mem {
|
||||
** database (see below for exceptions). If the MEM_Term flag is also
|
||||
** set, then the string is nul terminated. The MEM_Int and MEM_Real
|
||||
** flags may coexist with the MEM_Str flag.
|
||||
**
|
||||
** Multiple of these values can appear in Mem.flags. But only one
|
||||
** at a time can appear in Mem.type.
|
||||
*/
|
||||
#define MEM_Null 0x0001 /* Value is NULL */
|
||||
#define MEM_Str 0x0002 /* Value is a string */
|
||||
@ -177,6 +176,7 @@ struct Mem {
|
||||
#define MEM_Blob 0x0010 /* Value is a BLOB */
|
||||
#define MEM_RowSet 0x0020 /* Value is a RowSet object */
|
||||
#define MEM_Frame 0x0040 /* Value is a VdbeFrame object */
|
||||
#define MEM_Invalid 0x0080 /* Value is undefined */
|
||||
#define MEM_TypeMask 0x00ff /* Mask of type bits */
|
||||
|
||||
/* Whenever Mem contains a valid string or blob representation, one of
|
||||
@ -190,19 +190,25 @@ struct Mem {
|
||||
#define MEM_Ephem 0x1000 /* Mem.z points to an ephemeral string */
|
||||
#define MEM_Agg 0x2000 /* Mem.z points to an agg function context */
|
||||
#define MEM_Zero 0x4000 /* Mem.i contains count of 0s appended to blob */
|
||||
|
||||
#ifdef SQLITE_OMIT_INCRBLOB
|
||||
#undef MEM_Zero
|
||||
#define MEM_Zero 0x0000
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** Clear any existing type flags from a Mem and replace them with f
|
||||
*/
|
||||
#define MemSetTypeFlag(p, f) \
|
||||
((p)->flags = ((p)->flags&~(MEM_TypeMask|MEM_Zero))|f)
|
||||
|
||||
/*
|
||||
** Return true if a memory cell is not marked as invalid. This macro
|
||||
** is for use inside assert() statements only.
|
||||
*/
|
||||
#ifdef SQLITE_DEBUG
|
||||
#define memIsValid(M) ((M)->flags & MEM_Invalid)==0
|
||||
#endif
|
||||
|
||||
|
||||
/* A VdbeFunc is just a FuncDef (defined in sqliteInt.h) that contains
|
||||
** additional information about auxiliary information bound to arguments
|
||||
@ -244,23 +250,11 @@ struct sqlite3_context {
|
||||
CollSeq *pColl; /* Collating sequence */
|
||||
};
|
||||
|
||||
/*
|
||||
** A Set structure is used for quick testing to see if a value
|
||||
** is part of a small set. Sets are used to implement code like
|
||||
** this:
|
||||
** x.y IN ('hi','hoo','hum')
|
||||
*/
|
||||
typedef struct Set Set;
|
||||
struct Set {
|
||||
Hash hash; /* A set is just a hash table */
|
||||
HashElem *prev; /* Previously accessed hash elemen */
|
||||
};
|
||||
|
||||
/*
|
||||
** An instance of the virtual machine. This structure contains the complete
|
||||
** state of the virtual machine.
|
||||
**
|
||||
** The "sqlite3_stmt" structure pointer that is returned by sqlite3_compile()
|
||||
** The "sqlite3_stmt" structure pointer that is returned by sqlite3_prepare()
|
||||
** is really a pointer to an instance of this structure.
|
||||
**
|
||||
** The Vdbe.inVtabMethod variable is set to non-zero for the duration of
|
||||
@ -273,31 +267,31 @@ struct Set {
|
||||
*/
|
||||
struct Vdbe {
|
||||
sqlite3 *db; /* The database connection that owns this statement */
|
||||
Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */
|
||||
int nOp; /* Number of instructions in the program */
|
||||
int nOpAlloc; /* Number of slots allocated for aOp[] */
|
||||
Op *aOp; /* Space to hold the virtual machine's program */
|
||||
int nLabel; /* Number of labels used */
|
||||
int nLabelAlloc; /* Number of slots allocated in aLabel[] */
|
||||
int *aLabel; /* Space to hold the labels */
|
||||
Mem *aMem; /* The memory locations */
|
||||
Mem **apArg; /* Arguments to currently executing user function */
|
||||
Mem *aColName; /* Column names to return */
|
||||
Mem *pResultSet; /* Pointer to an array of results */
|
||||
int nMem; /* Number of memory locations currently allocated */
|
||||
int nOp; /* Number of instructions in the program */
|
||||
int nOpAlloc; /* Number of slots allocated for aOp[] */
|
||||
int nLabel; /* Number of labels used */
|
||||
int nLabelAlloc; /* Number of slots allocated in aLabel[] */
|
||||
int *aLabel; /* Space to hold the labels */
|
||||
u16 nResColumn; /* Number of columns in one row of the result set */
|
||||
u16 nCursor; /* Number of slots in apCsr[] */
|
||||
u32 magic; /* Magic number for sanity checking */
|
||||
char *zErrMsg; /* Error message written here */
|
||||
Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */
|
||||
VdbeCursor **apCsr; /* One element of this array for each open cursor */
|
||||
u8 errorAction; /* Recovery action to do in case of an error */
|
||||
u8 okVar; /* True if azVar[] has been initialized */
|
||||
ynVar nVar; /* Number of entries in aVar[] */
|
||||
Mem *aVar; /* Values for the OP_Variable opcode. */
|
||||
char **azVar; /* Name of variables */
|
||||
u32 magic; /* Magic number for sanity checking */
|
||||
int nMem; /* Number of memory locations currently allocated */
|
||||
Mem *aMem; /* The memory locations */
|
||||
ynVar nVar; /* Number of entries in aVar[] */
|
||||
u32 cacheCtr; /* VdbeCursor row cache generation counter */
|
||||
int pc; /* The program counter */
|
||||
int rc; /* Value to return */
|
||||
char *zErrMsg; /* Error message written here */
|
||||
u8 errorAction; /* Recovery action to do in case of an error */
|
||||
u8 okVar; /* True if azVar[] has been initialized */
|
||||
u8 explain; /* True if EXPLAIN present on SQL command */
|
||||
u8 changeCntOn; /* True to update the change-counter */
|
||||
u8 expired; /* True if the VM needs to be recompiled */
|
||||
@ -309,18 +303,21 @@ struct Vdbe {
|
||||
u8 isPrepareV2; /* True if prepared with prepare_v2() */
|
||||
int nChange; /* Number of db changes made since last reset */
|
||||
int btreeMask; /* Bitmask of db->aDb[] entries referenced */
|
||||
i64 startTime; /* Time when query started - used for profiling */
|
||||
BtreeMutexArray aMutex; /* An array of Btree used here and needing locks */
|
||||
int iStatement; /* Statement number (or 0 if has not opened stmt) */
|
||||
int aCounter[3]; /* Counters used by sqlite3_stmt_status() */
|
||||
char *zSql; /* Text of the SQL statement that generated this */
|
||||
void *pFree; /* Free this when deleting the vdbe */
|
||||
BtreeMutexArray aMutex; /* An array of Btree used here and needing locks */
|
||||
#ifndef SQLITE_OMIT_TRACE
|
||||
i64 startTime; /* Time when query started - used for profiling */
|
||||
#endif
|
||||
i64 nFkConstraint; /* Number of imm. FK constraints this VM */
|
||||
i64 nStmtDefCons; /* Number of def. constraints when stmt started */
|
||||
int iStatement; /* Statement number (or 0 if has not opened stmt) */
|
||||
char *zSql; /* Text of the SQL statement that generated this */
|
||||
void *pFree; /* Free this when deleting the vdbe */
|
||||
#ifdef SQLITE_DEBUG
|
||||
FILE *trace; /* Write an execution trace here, if not NULL */
|
||||
#endif
|
||||
VdbeFrame *pFrame; /* Parent frame */
|
||||
VdbeFrame *pDelFrame; /* List of frame objects to free on VM reset */
|
||||
int nFrame; /* Number of frames in pFrame list */
|
||||
u32 expmask; /* Binding to these vars invalidates VM */
|
||||
SubProgram *pProgram; /* Linked list of all sub-programs used by VM */
|
||||
@ -391,6 +388,10 @@ void sqlite3VdbeFrameDelete(VdbeFrame*);
|
||||
int sqlite3VdbeFrameRestore(VdbeFrame *);
|
||||
void sqlite3VdbeMemStoreType(Mem *pMem);
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
void sqlite3VdbeMemPrepareToChange(Vdbe*,Mem*);
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_OMIT_FOREIGN_KEY
|
||||
int sqlite3VdbeCheckFk(Vdbe *, int);
|
||||
#else
|
||||
|
||||
@ -65,6 +65,8 @@ static int vdbeSafetyNotNull(Vdbe *p){
|
||||
int sqlite3_finalize(sqlite3_stmt *pStmt){
|
||||
int rc;
|
||||
if( pStmt==0 ){
|
||||
/* IMPLEMENTATION-OF: R-57228-12904 Invoking sqlite3_finalize() on a NULL
|
||||
** pointer is a harmless no-op. */
|
||||
rc = SQLITE_OK;
|
||||
}else{
|
||||
Vdbe *v = (Vdbe*)pStmt;
|
||||
@ -141,7 +143,7 @@ const void *sqlite3_value_blob(sqlite3_value *pVal){
|
||||
sqlite3VdbeMemExpandBlob(p);
|
||||
p->flags &= ~MEM_Str;
|
||||
p->flags |= MEM_Blob;
|
||||
return p->z;
|
||||
return p->n ? p->z : 0;
|
||||
}else{
|
||||
return sqlite3_value_text(pVal);
|
||||
}
|
||||
@ -343,11 +345,30 @@ static int sqlite3Step(Vdbe *p){
|
||||
assert(p);
|
||||
if( p->magic!=VDBE_MAGIC_RUN ){
|
||||
/* We used to require that sqlite3_reset() be called before retrying
|
||||
** sqlite3_step() after any error. But after 3.6.23, we changed this
|
||||
** so that sqlite3_reset() would be called automatically instead of
|
||||
** throwing the error.
|
||||
** sqlite3_step() after any error or after SQLITE_DONE. But beginning
|
||||
** with version 3.7.0, we changed this so that sqlite3_reset() would
|
||||
** be called automatically instead of throwing the SQLITE_MISUSE error.
|
||||
** This "automatic-reset" change is not technically an incompatibility,
|
||||
** since any application that receives an SQLITE_MISUSE is broken by
|
||||
** definition.
|
||||
**
|
||||
** Nevertheless, some published applications that were originally written
|
||||
** for version 3.6.23 or earlier do in fact depend on SQLITE_MISUSE
|
||||
** returns, and the so were broken by the automatic-reset change. As a
|
||||
** a work-around, the SQLITE_OMIT_AUTORESET compile-time restores the
|
||||
** legacy behavior of returning SQLITE_MISUSE for cases where the
|
||||
** previous sqlite3_step() returned something other than a SQLITE_LOCKED
|
||||
** or SQLITE_BUSY error.
|
||||
*/
|
||||
#ifdef SQLITE_OMIT_AUTORESET
|
||||
if( p->rc==SQLITE_BUSY || p->rc==SQLITE_LOCKED ){
|
||||
sqlite3_reset((sqlite3_stmt*)p);
|
||||
}else{
|
||||
return SQLITE_MISUSE_BKPT;
|
||||
}
|
||||
#else
|
||||
sqlite3_reset((sqlite3_stmt*)p);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Check that malloc() has not failed. If it has, return early. */
|
||||
@ -389,7 +410,9 @@ static int sqlite3Step(Vdbe *p){
|
||||
}else
|
||||
#endif /* SQLITE_OMIT_EXPLAIN */
|
||||
{
|
||||
db->vdbeExecCnt++;
|
||||
rc = sqlite3VdbeExec(p);
|
||||
db->vdbeExecCnt--;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_TRACE
|
||||
@ -495,6 +518,12 @@ void *sqlite3_user_data(sqlite3_context *p){
|
||||
/*
|
||||
** Extract the user data from a sqlite3_context structure and return a
|
||||
** pointer to it.
|
||||
**
|
||||
** IMPLEMENTATION-OF: R-46798-50301 The sqlite3_context_db_handle() interface
|
||||
** returns a copy of the pointer to the database connection (the 1st
|
||||
** parameter) of the sqlite3_create_function() and
|
||||
** sqlite3_create_function16() routines that originally registered the
|
||||
** application defined function.
|
||||
*/
|
||||
sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){
|
||||
assert( p && p->pFunc );
|
||||
@ -677,7 +706,7 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){
|
||||
#if defined(SQLITE_DEBUG) && defined(__GNUC__)
|
||||
__attribute__((aligned(8)))
|
||||
#endif
|
||||
= {{0}, (double)0, 0, "", 0, MEM_Null, SQLITE_NULL, 0, 0, 0 };
|
||||
= {0, "", (double)0, {0}, 0, MEM_Null, SQLITE_NULL, 0, 0, 0 };
|
||||
|
||||
if( pVm && ALWAYS(pVm->db) ){
|
||||
sqlite3_mutex_enter(pVm->db->mutex);
|
||||
@ -704,8 +733,7 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){
|
||||
** sqlite3_column_real()
|
||||
** sqlite3_column_bytes()
|
||||
** sqlite3_column_bytes16()
|
||||
**
|
||||
** But not for sqlite3_column_blob(), which never calls malloc().
|
||||
** sqiite3_column_blob()
|
||||
*/
|
||||
static void columnMallocFailure(sqlite3_stmt *pStmt)
|
||||
{
|
||||
@ -973,6 +1001,12 @@ static int vdbeUnbind(Vdbe *p, int i){
|
||||
|
||||
/* If the bit corresponding to this variable in Vdbe.expmask is set, then
|
||||
** binding a new value to this variable invalidates the current query plan.
|
||||
**
|
||||
** IMPLEMENTATION-OF: R-48440-37595 If the specific value bound to host
|
||||
** parameter in the WHERE clause might influence the choice of query plan
|
||||
** for a statement, then the statement will be automatically recompiled,
|
||||
** as if there had been a schema change, on the first sqlite3_step() call
|
||||
** following any change to the bindings of that parameter.
|
||||
*/
|
||||
if( p->isPrepareV2 &&
|
||||
((i<32 && p->expmask & ((u32)1 << i)) || p->expmask==0xffffffff)
|
||||
@ -1009,6 +1043,8 @@ static int bindText(
|
||||
rc = sqlite3ApiExit(p->db, rc);
|
||||
}
|
||||
sqlite3_mutex_leave(p->db->mutex);
|
||||
}else if( xDel!=SQLITE_STATIC && xDel!=SQLITE_TRANSIENT ){
|
||||
xDel((void*)zData);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@ -1251,6 +1287,14 @@ sqlite3 *sqlite3_db_handle(sqlite3_stmt *pStmt){
|
||||
return pStmt ? ((Vdbe*)pStmt)->db : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if the prepared statement is guaranteed to not modify the
|
||||
** database.
|
||||
*/
|
||||
int sqlite3_stmt_readonly(sqlite3_stmt *pStmt){
|
||||
return pStmt ? ((Vdbe*)pStmt)->readOnly : 1;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to the next prepared statement after pStmt associated
|
||||
** with database connection pDb. If pStmt is NULL, return the first
|
||||
|
||||
@ -408,7 +408,7 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
|
||||
pOp->opflags = sqlite3OpcodeProperty[opcode];
|
||||
if( opcode==OP_Function || opcode==OP_AggStep ){
|
||||
if( pOp->p5>nMaxArgs ) nMaxArgs = pOp->p5;
|
||||
}else if( opcode==OP_Transaction && pOp->p2!=0 ){
|
||||
}else if( (opcode==OP_Transaction && pOp->p2!=0) || opcode==OP_Vacuum ){
|
||||
p->readOnly = 0;
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
}else if( opcode==OP_VUpdate ){
|
||||
@ -1182,12 +1182,10 @@ int sqlite3VdbeList(
|
||||
pMem->type = SQLITE_INTEGER;
|
||||
pMem++;
|
||||
|
||||
if( p->explain==1 ){
|
||||
pMem->flags = MEM_Int;
|
||||
pMem->u.i = pOp->p3; /* P3 */
|
||||
pMem->type = SQLITE_INTEGER;
|
||||
pMem++;
|
||||
}
|
||||
pMem->flags = MEM_Int;
|
||||
pMem->u.i = pOp->p3; /* P3 */
|
||||
pMem->type = SQLITE_INTEGER;
|
||||
pMem++;
|
||||
|
||||
if( sqlite3VdbeMemGrow(pMem, 32, 0) ){ /* P4 */
|
||||
assert( p->db->mallocFailed );
|
||||
@ -1232,7 +1230,7 @@ int sqlite3VdbeList(
|
||||
}
|
||||
}
|
||||
|
||||
p->nResColumn = 8 - 5*(p->explain-1);
|
||||
p->nResColumn = 8 - 4*(p->explain-1);
|
||||
p->rc = SQLITE_OK;
|
||||
rc = SQLITE_ROW;
|
||||
}
|
||||
@ -1539,6 +1537,11 @@ static void closeAllCursors(Vdbe *p){
|
||||
if( p->aMem ){
|
||||
releaseMemArray(&p->aMem[1], p->nMem);
|
||||
}
|
||||
while( p->pDelFrame ){
|
||||
VdbeFrame *pDel = p->pDelFrame;
|
||||
p->pDelFrame = pDel->pParent;
|
||||
sqlite3VdbeFrameDelete(pDel);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1755,9 +1758,10 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
|
||||
Btree *pBt = db->aDb[i].pBt;
|
||||
if( sqlite3BtreeIsInTrans(pBt) ){
|
||||
char const *zFile = sqlite3BtreeGetJournalname(pBt);
|
||||
if( zFile==0 || zFile[0]==0 ){
|
||||
if( zFile==0 ){
|
||||
continue; /* Ignore TEMP and :memory: databases */
|
||||
}
|
||||
assert( zFile[0]!=0 );
|
||||
if( !needSync && !sqlite3BtreeSyncDisabled(pBt) ){
|
||||
needSync = 1;
|
||||
}
|
||||
|
||||
214
src/vdbeblob.c
214
src/vdbeblob.c
@ -26,11 +26,82 @@ struct Incrblob {
|
||||
int flags; /* Copy of "flags" passed to sqlite3_blob_open() */
|
||||
int nByte; /* Size of open blob, in bytes */
|
||||
int iOffset; /* Byte offset of blob in cursor data */
|
||||
int iCol; /* Table column this handle is open on */
|
||||
BtCursor *pCsr; /* Cursor pointing at blob row */
|
||||
sqlite3_stmt *pStmt; /* Statement holding cursor open */
|
||||
sqlite3 *db; /* The associated database */
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
** This function is used by both blob_open() and blob_reopen(). It seeks
|
||||
** the b-tree cursor associated with blob handle p to point to row iRow.
|
||||
** If successful, SQLITE_OK is returned and subsequent calls to
|
||||
** sqlite3_blob_read() or sqlite3_blob_write() access the specified row.
|
||||
**
|
||||
** If an error occurs, or if the specified row does not exist or does not
|
||||
** contain a value of type TEXT or BLOB in the column nominated when the
|
||||
** blob handle was opened, then an error code is returned and *pzErr may
|
||||
** be set to point to a buffer containing an error message. It is the
|
||||
** responsibility of the caller to free the error message buffer using
|
||||
** sqlite3DbFree().
|
||||
**
|
||||
** If an error does occur, then the b-tree cursor is closed. All subsequent
|
||||
** calls to sqlite3_blob_read(), blob_write() or blob_reopen() will
|
||||
** immediately return SQLITE_ABORT.
|
||||
*/
|
||||
static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){
|
||||
int rc; /* Error code */
|
||||
char *zErr = 0; /* Error message */
|
||||
Vdbe *v = (Vdbe *)p->pStmt;
|
||||
|
||||
/* Set the value of the SQL statements only variable to integer iRow.
|
||||
** This is done directly instead of using sqlite3_bind_int64() to avoid
|
||||
** triggering asserts related to mutexes.
|
||||
*/
|
||||
assert( v->aVar[0].flags&MEM_Int );
|
||||
v->aVar[0].u.i = iRow;
|
||||
|
||||
rc = sqlite3_step(p->pStmt);
|
||||
if( rc==SQLITE_ROW ){
|
||||
u32 type = v->apCsr[0]->aType[p->iCol];
|
||||
if( type<12 ){
|
||||
zErr = sqlite3MPrintf(p->db, "cannot open value of type %s",
|
||||
type==0?"null": type==7?"real": "integer"
|
||||
);
|
||||
rc = SQLITE_ERROR;
|
||||
sqlite3_finalize(p->pStmt);
|
||||
p->pStmt = 0;
|
||||
}else{
|
||||
p->iOffset = v->apCsr[0]->aOffset[p->iCol];
|
||||
p->nByte = sqlite3VdbeSerialTypeLen(type);
|
||||
p->pCsr = v->apCsr[0]->pCursor;
|
||||
sqlite3BtreeEnterCursor(p->pCsr);
|
||||
sqlite3BtreeCacheOverflow(p->pCsr);
|
||||
sqlite3BtreeLeaveCursor(p->pCsr);
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_ROW ){
|
||||
rc = SQLITE_OK;
|
||||
}else if( p->pStmt ){
|
||||
rc = sqlite3_finalize(p->pStmt);
|
||||
p->pStmt = 0;
|
||||
if( rc==SQLITE_OK ){
|
||||
zErr = sqlite3MPrintf(p->db, "no such rowid: %lld", iRow);
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
zErr = sqlite3MPrintf(p->db, "%s", sqlite3_errmsg(p->db));
|
||||
}
|
||||
}
|
||||
|
||||
assert( rc!=SQLITE_OK || zErr==0 );
|
||||
assert( rc!=SQLITE_ROW && rc!=SQLITE_DONE );
|
||||
|
||||
*pzErr = zErr;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a blob handle.
|
||||
*/
|
||||
@ -71,29 +142,35 @@ int sqlite3_blob_open(
|
||||
{OP_OpenWrite, 0, 0, 0}, /* 4: Open cursor 0 for read/write */
|
||||
|
||||
{OP_Variable, 1, 1, 1}, /* 5: Push the rowid to the stack */
|
||||
{OP_NotExists, 0, 9, 1}, /* 6: Seek the cursor */
|
||||
{OP_NotExists, 0, 10, 1}, /* 6: Seek the cursor */
|
||||
{OP_Column, 0, 0, 1}, /* 7 */
|
||||
{OP_ResultRow, 1, 0, 0}, /* 8 */
|
||||
{OP_Close, 0, 0, 0}, /* 9 */
|
||||
{OP_Halt, 0, 0, 0}, /* 10 */
|
||||
{OP_Goto, 0, 5, 0}, /* 9 */
|
||||
{OP_Close, 0, 0, 0}, /* 10 */
|
||||
{OP_Halt, 0, 0, 0}, /* 11 */
|
||||
};
|
||||
|
||||
Vdbe *v = 0;
|
||||
int rc = SQLITE_OK;
|
||||
char *zErr = 0;
|
||||
Table *pTab;
|
||||
Parse *pParse;
|
||||
Parse *pParse = 0;
|
||||
Incrblob *pBlob = 0;
|
||||
|
||||
flags = !!flags; /* flags = (flags ? 1 : 0); */
|
||||
*ppBlob = 0;
|
||||
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
|
||||
pBlob = (Incrblob *)sqlite3DbMallocZero(db, sizeof(Incrblob));
|
||||
if( !pBlob ) goto blob_open_out;
|
||||
pParse = sqlite3StackAllocRaw(db, sizeof(*pParse));
|
||||
if( pParse==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
goto blob_open_out;
|
||||
}
|
||||
if( !pParse ) goto blob_open_out;
|
||||
|
||||
do {
|
||||
memset(pParse, 0, sizeof(Parse));
|
||||
pParse->db = db;
|
||||
sqlite3DbFree(db, zErr);
|
||||
zErr = 0;
|
||||
|
||||
sqlite3BtreeEnterAll(db);
|
||||
pTab = sqlite3LocateTable(pParse, 0, zTable, zDb);
|
||||
@ -119,7 +196,7 @@ int sqlite3_blob_open(
|
||||
}
|
||||
|
||||
/* Now search pTab for the exact column. */
|
||||
for(iCol=0; iCol < pTab->nCol; iCol++) {
|
||||
for(iCol=0; iCol<pTab->nCol; iCol++) {
|
||||
if( sqlite3StrICmp(pTab->aCol[iCol].zName, zColumn)==0 ){
|
||||
break;
|
||||
}
|
||||
@ -173,11 +250,14 @@ int sqlite3_blob_open(
|
||||
}
|
||||
}
|
||||
|
||||
v = sqlite3VdbeCreate(db);
|
||||
if( v ){
|
||||
pBlob->pStmt = (sqlite3_stmt *)sqlite3VdbeCreate(db);
|
||||
assert( pBlob->pStmt || db->mallocFailed );
|
||||
if( pBlob->pStmt ){
|
||||
Vdbe *v = (Vdbe *)pBlob->pStmt;
|
||||
int iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
|
||||
|
||||
sqlite3VdbeAddOpList(v, sizeof(openBlob)/sizeof(VdbeOpList), openBlob);
|
||||
flags = !!flags; /* flags = (flags ? 1 : 0); */
|
||||
|
||||
|
||||
/* Configure the OP_Transaction */
|
||||
sqlite3VdbeChangeP1(v, 0, iDb);
|
||||
@ -220,65 +300,25 @@ int sqlite3_blob_open(
|
||||
}
|
||||
}
|
||||
|
||||
pBlob->flags = flags;
|
||||
pBlob->iCol = iCol;
|
||||
pBlob->db = db;
|
||||
sqlite3BtreeLeaveAll(db);
|
||||
if( db->mallocFailed ){
|
||||
goto blob_open_out;
|
||||
}
|
||||
|
||||
sqlite3_bind_int64((sqlite3_stmt *)v, 1, iRow);
|
||||
rc = sqlite3_step((sqlite3_stmt *)v);
|
||||
if( rc!=SQLITE_ROW ){
|
||||
nAttempt++;
|
||||
rc = sqlite3_finalize((sqlite3_stmt *)v);
|
||||
sqlite3DbFree(db, zErr);
|
||||
zErr = sqlite3MPrintf(db, sqlite3_errmsg(db));
|
||||
v = 0;
|
||||
}
|
||||
} while( nAttempt<5 && rc==SQLITE_SCHEMA );
|
||||
|
||||
if( rc==SQLITE_ROW ){
|
||||
/* The row-record has been opened successfully. Check that the
|
||||
** column in question contains text or a blob. If it contains
|
||||
** text, it is up to the caller to get the encoding right.
|
||||
*/
|
||||
Incrblob *pBlob;
|
||||
u32 type = v->apCsr[0]->aType[iCol];
|
||||
|
||||
if( type<12 ){
|
||||
sqlite3DbFree(db, zErr);
|
||||
zErr = sqlite3MPrintf(db, "cannot open value of type %s",
|
||||
type==0?"null": type==7?"real": "integer"
|
||||
);
|
||||
rc = SQLITE_ERROR;
|
||||
goto blob_open_out;
|
||||
}
|
||||
pBlob = (Incrblob *)sqlite3DbMallocZero(db, sizeof(Incrblob));
|
||||
if( db->mallocFailed ){
|
||||
sqlite3DbFree(db, pBlob);
|
||||
goto blob_open_out;
|
||||
}
|
||||
pBlob->flags = flags;
|
||||
pBlob->pCsr = v->apCsr[0]->pCursor;
|
||||
sqlite3BtreeEnterCursor(pBlob->pCsr);
|
||||
sqlite3BtreeCacheOverflow(pBlob->pCsr);
|
||||
sqlite3BtreeLeaveCursor(pBlob->pCsr);
|
||||
pBlob->pStmt = (sqlite3_stmt *)v;
|
||||
pBlob->iOffset = v->apCsr[0]->aOffset[iCol];
|
||||
pBlob->nByte = sqlite3VdbeSerialTypeLen(type);
|
||||
pBlob->db = db;
|
||||
*ppBlob = (sqlite3_blob *)pBlob;
|
||||
rc = SQLITE_OK;
|
||||
}else if( rc==SQLITE_OK ){
|
||||
sqlite3DbFree(db, zErr);
|
||||
zErr = sqlite3MPrintf(db, "no such rowid: %lld", iRow);
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
sqlite3_bind_int64(pBlob->pStmt, 1, iRow);
|
||||
rc = blobSeekToRow(pBlob, iRow, &zErr);
|
||||
} while( (++nAttempt)<5 && rc==SQLITE_SCHEMA );
|
||||
|
||||
blob_open_out:
|
||||
if( v && (rc!=SQLITE_OK || db->mallocFailed) ){
|
||||
sqlite3VdbeFinalize(v);
|
||||
if( rc==SQLITE_OK && db->mallocFailed==0 ){
|
||||
*ppBlob = (sqlite3_blob *)pBlob;
|
||||
}else{
|
||||
if( pBlob && pBlob->pStmt ) sqlite3VdbeFinalize((Vdbe *)pBlob->pStmt);
|
||||
sqlite3DbFree(db, pBlob);
|
||||
}
|
||||
sqlite3Error(db, rc, zErr);
|
||||
sqlite3Error(db, rc, (zErr ? "%s" : 0), zErr);
|
||||
sqlite3DbFree(db, zErr);
|
||||
sqlite3StackFree(db, pParse);
|
||||
rc = sqlite3ApiExit(db, rc);
|
||||
@ -331,7 +371,7 @@ static int blobReadWrite(
|
||||
/* Request is out of range. Return a transient error. */
|
||||
rc = SQLITE_ERROR;
|
||||
sqlite3Error(db, SQLITE_ERROR, 0);
|
||||
} else if( v==0 ){
|
||||
}else if( v==0 ){
|
||||
/* If there is no statement handle, then the blob-handle has
|
||||
** already been invalidated. Return SQLITE_ABORT in this case.
|
||||
*/
|
||||
@ -379,7 +419,47 @@ int sqlite3_blob_write(sqlite3_blob *pBlob, const void *z, int n, int iOffset){
|
||||
*/
|
||||
int sqlite3_blob_bytes(sqlite3_blob *pBlob){
|
||||
Incrblob *p = (Incrblob *)pBlob;
|
||||
return p ? p->nByte : 0;
|
||||
return (p && p->pStmt) ? p->nByte : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Move an existing blob handle to point to a different row of the same
|
||||
** database table.
|
||||
**
|
||||
** If an error occurs, or if the specified row does not exist or does not
|
||||
** contain a blob or text value, then an error code is returned and the
|
||||
** database handle error code and message set. If this happens, then all
|
||||
** subsequent calls to sqlite3_blob_xxx() functions (except blob_close())
|
||||
** immediately return SQLITE_ABORT.
|
||||
*/
|
||||
int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){
|
||||
int rc;
|
||||
Incrblob *p = (Incrblob *)pBlob;
|
||||
sqlite3 *db;
|
||||
|
||||
if( p==0 ) return SQLITE_MISUSE_BKPT;
|
||||
db = p->db;
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
|
||||
if( p->pStmt==0 ){
|
||||
/* If there is no statement handle, then the blob-handle has
|
||||
** already been invalidated. Return SQLITE_ABORT in this case.
|
||||
*/
|
||||
rc = SQLITE_ABORT;
|
||||
}else{
|
||||
char *zErr;
|
||||
rc = blobSeekToRow(p, iRow, &zErr);
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3Error(db, rc, (zErr ? "%s" : 0), zErr);
|
||||
sqlite3DbFree(db, zErr);
|
||||
}
|
||||
assert( rc!=SQLITE_SCHEMA );
|
||||
}
|
||||
|
||||
rc = sqlite3ApiExit(db, rc);
|
||||
assert( rc==SQLITE_OK || p->pStmt==0 );
|
||||
sqlite3_mutex_leave(db->mutex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* #ifndef SQLITE_OMIT_INCRBLOB */
|
||||
|
||||
@ -132,6 +132,9 @@ int sqlite3VdbeMemMakeWriteable(Mem *pMem){
|
||||
pMem->z[pMem->n] = 0;
|
||||
pMem->z[pMem->n+1] = 0;
|
||||
pMem->flags |= MEM_Term;
|
||||
#ifdef SQLITE_DEBUG
|
||||
pMem->pScopyFrom = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
@ -252,7 +255,7 @@ int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){
|
||||
ctx.s.db = pMem->db;
|
||||
ctx.pMem = pMem;
|
||||
ctx.pFunc = pFunc;
|
||||
pFunc->xFinalize(&ctx);
|
||||
pFunc->xFinalize(&ctx); /* IMP: R-24505-23230 */
|
||||
assert( 0==(pMem->flags&MEM_Dyn) && !pMem->xDel );
|
||||
sqlite3DbFree(pMem->db, pMem->zMalloc);
|
||||
memcpy(pMem, &ctx.s, sizeof(ctx.s));
|
||||
@ -365,13 +368,9 @@ i64 sqlite3VdbeIntValue(Mem *pMem){
|
||||
return doubleToInt64(pMem->r);
|
||||
}else if( flags & (MEM_Str|MEM_Blob) ){
|
||||
i64 value;
|
||||
pMem->flags |= MEM_Str;
|
||||
if( sqlite3VdbeChangeEncoding(pMem, SQLITE_UTF8)
|
||||
|| sqlite3VdbeMemNulTerminate(pMem) ){
|
||||
return 0;
|
||||
}
|
||||
assert( pMem->z );
|
||||
sqlite3Atoi64(pMem->z, &value);
|
||||
assert( pMem->z || pMem->n==0 );
|
||||
testcase( pMem->z==0 );
|
||||
sqlite3Atoi64(pMem->z, &value, pMem->n, pMem->enc);
|
||||
return value;
|
||||
}else{
|
||||
return 0;
|
||||
@ -394,14 +393,7 @@ double sqlite3VdbeRealValue(Mem *pMem){
|
||||
}else if( pMem->flags & (MEM_Str|MEM_Blob) ){
|
||||
/* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */
|
||||
double val = (double)0;
|
||||
pMem->flags |= MEM_Str;
|
||||
if( sqlite3VdbeChangeEncoding(pMem, SQLITE_UTF8)
|
||||
|| sqlite3VdbeMemNulTerminate(pMem) ){
|
||||
/* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */
|
||||
return (double)0;
|
||||
}
|
||||
assert( pMem->z );
|
||||
sqlite3AtoF(pMem->z, &val);
|
||||
sqlite3AtoF(pMem->z, &val, pMem->n, pMem->enc);
|
||||
return val;
|
||||
}else{
|
||||
/* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */
|
||||
@ -474,21 +466,19 @@ int sqlite3VdbeMemRealify(Mem *pMem){
|
||||
** as much of the string as we can and ignore the rest.
|
||||
*/
|
||||
int sqlite3VdbeMemNumerify(Mem *pMem){
|
||||
int rc;
|
||||
assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_Null))==0 );
|
||||
assert( (pMem->flags & (MEM_Blob|MEM_Str))!=0 );
|
||||
assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
|
||||
rc = sqlite3VdbeChangeEncoding(pMem, SQLITE_UTF8);
|
||||
if( rc ) return rc;
|
||||
rc = sqlite3VdbeMemNulTerminate(pMem);
|
||||
if( rc ) return rc;
|
||||
if( sqlite3Atoi64(pMem->z, &pMem->u.i) ){
|
||||
MemSetTypeFlag(pMem, MEM_Int);
|
||||
}else{
|
||||
pMem->r = sqlite3VdbeRealValue(pMem);
|
||||
MemSetTypeFlag(pMem, MEM_Real);
|
||||
sqlite3VdbeIntegerAffinity(pMem);
|
||||
if( (pMem->flags & (MEM_Int|MEM_Real|MEM_Null))==0 ){
|
||||
assert( (pMem->flags & (MEM_Blob|MEM_Str))!=0 );
|
||||
assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
|
||||
if( 0==sqlite3Atoi64(pMem->z, &pMem->u.i, pMem->n, pMem->enc) ){
|
||||
MemSetTypeFlag(pMem, MEM_Int);
|
||||
}else{
|
||||
pMem->r = sqlite3VdbeRealValue(pMem);
|
||||
MemSetTypeFlag(pMem, MEM_Real);
|
||||
sqlite3VdbeIntegerAffinity(pMem);
|
||||
}
|
||||
}
|
||||
assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_Null))!=0 );
|
||||
pMem->flags &= ~(MEM_Str|MEM_Blob);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
@ -497,7 +487,9 @@ int sqlite3VdbeMemNumerify(Mem *pMem){
|
||||
*/
|
||||
void sqlite3VdbeMemSetNull(Mem *pMem){
|
||||
if( pMem->flags & MEM_Frame ){
|
||||
sqlite3VdbeFrameDelete(pMem->u.pFrame);
|
||||
VdbeFrame *pFrame = pMem->u.pFrame;
|
||||
pFrame->pParent = pFrame->v->pDelFrame;
|
||||
pFrame->v->pDelFrame = pFrame;
|
||||
}
|
||||
if( pMem->flags & MEM_RowSet ){
|
||||
sqlite3RowSetClear(pMem->u.pRowSet);
|
||||
@ -593,6 +585,28 @@ int sqlite3VdbeMemTooBig(Mem *p){
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
/*
|
||||
** This routine prepares a memory cell for modication by breaking
|
||||
** its link to a shallow copy and by marking any current shallow
|
||||
** copies of this cell as invalid.
|
||||
**
|
||||
** This is used for testing and debugging only - to make sure shallow
|
||||
** copies are not misused.
|
||||
*/
|
||||
void sqlite3VdbeMemPrepareToChange(Vdbe *pVdbe, Mem *pMem){
|
||||
int i;
|
||||
Mem *pX;
|
||||
for(i=1, pX=&pVdbe->aMem[1]; i<=pVdbe->nMem; i++, pX++){
|
||||
if( pX->pScopyFrom==pMem ){
|
||||
pX->flags |= MEM_Invalid;
|
||||
pX->pScopyFrom = 0;
|
||||
}
|
||||
}
|
||||
pMem->pScopyFrom = 0;
|
||||
}
|
||||
#endif /* SQLITE_DEBUG */
|
||||
|
||||
/*
|
||||
** Size of struct Mem not including the Mem.zMalloc member.
|
||||
*/
|
||||
@ -961,7 +975,7 @@ const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
sqlite3VdbeMemNulTerminate(pVal);
|
||||
sqlite3VdbeMemNulTerminate(pVal); /* IMP: R-59893-45467 */
|
||||
}else{
|
||||
assert( (pVal->flags&MEM_Blob)==0 );
|
||||
sqlite3VdbeMemStringify(pVal, enc);
|
||||
@ -1009,6 +1023,8 @@ int sqlite3ValueFromExpr(
|
||||
int op;
|
||||
char *zVal = 0;
|
||||
sqlite3_value *pVal = 0;
|
||||
int negInt = 1;
|
||||
const char *zNeg = "";
|
||||
|
||||
if( !pExpr ){
|
||||
*ppVal = 0;
|
||||
@ -1026,13 +1042,24 @@ int sqlite3ValueFromExpr(
|
||||
if( NEVER(op==TK_REGISTER) ) op = pExpr->op2;
|
||||
#endif
|
||||
|
||||
/* Handle negative integers in a single step. This is needed in the
|
||||
** case when the value is -9223372036854775808.
|
||||
*/
|
||||
if( op==TK_UMINUS
|
||||
&& (pExpr->pLeft->op==TK_INTEGER || pExpr->pLeft->op==TK_FLOAT) ){
|
||||
pExpr = pExpr->pLeft;
|
||||
op = pExpr->op;
|
||||
negInt = -1;
|
||||
zNeg = "-";
|
||||
}
|
||||
|
||||
if( op==TK_STRING || op==TK_FLOAT || op==TK_INTEGER ){
|
||||
pVal = sqlite3ValueNew(db);
|
||||
if( pVal==0 ) goto no_mem;
|
||||
if( ExprHasProperty(pExpr, EP_IntValue) ){
|
||||
sqlite3VdbeMemSetInt64(pVal, (i64)pExpr->u.iValue);
|
||||
sqlite3VdbeMemSetInt64(pVal, (i64)pExpr->u.iValue*negInt);
|
||||
}else{
|
||||
zVal = sqlite3DbStrDup(db, pExpr->u.zToken);
|
||||
zVal = sqlite3MPrintf(db, "%s%s", zNeg, pExpr->u.zToken);
|
||||
if( zVal==0 ) goto no_mem;
|
||||
sqlite3ValueSetStr(pVal, -1, zVal, SQLITE_UTF8, SQLITE_DYNAMIC);
|
||||
if( op==TK_FLOAT ) pVal->type = SQLITE_FLOAT;
|
||||
@ -1042,14 +1069,18 @@ int sqlite3ValueFromExpr(
|
||||
}else{
|
||||
sqlite3ValueApplyAffinity(pVal, affinity, SQLITE_UTF8);
|
||||
}
|
||||
if( pVal->flags & (MEM_Int|MEM_Real) ) pVal->flags &= ~MEM_Str;
|
||||
if( enc!=SQLITE_UTF8 ){
|
||||
sqlite3VdbeChangeEncoding(pVal, enc);
|
||||
}
|
||||
}else if( op==TK_UMINUS ) {
|
||||
/* This branch happens for multiple negative signs. Ex: -(-5) */
|
||||
if( SQLITE_OK==sqlite3ValueFromExpr(db,pExpr->pLeft,enc,affinity,&pVal) ){
|
||||
sqlite3VdbeMemNumerify(pVal);
|
||||
pVal->u.i = -1 * pVal->u.i;
|
||||
/* (double)-1 In case of SQLITE_OMIT_FLOATING_POINT... */
|
||||
pVal->r = (double)-1 * pVal->r;
|
||||
sqlite3ValueApplyAffinity(pVal, affinity, enc);
|
||||
}
|
||||
}
|
||||
#ifndef SQLITE_OMIT_BLOB_LITERAL
|
||||
|
||||
124
src/vdbetrace.c
124
src/vdbetrace.c
@ -44,9 +44,12 @@ static int findNextHostParameter(const char *zSql, int *pnToken){
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to a string in memory obtained form sqlite3DbMalloc() which
|
||||
** holds a copy of zRawSql but with host parameters expanded to their
|
||||
** current bindings.
|
||||
** This function returns a pointer to a nul-terminated string in memory
|
||||
** obtained from sqlite3DbMalloc(). If sqlite3.vdbeExecCnt is 1, then the
|
||||
** string contains a copy of zRawSql but with host parameters expanded to
|
||||
** their current bindings. Or, if sqlite3.vdbeExecCnt is greater than 1,
|
||||
** then the returned string holds a copy of zRawSql with "-- " prepended
|
||||
** to each line of text.
|
||||
**
|
||||
** The calling function is responsible for making sure the memory returned
|
||||
** is eventually freed.
|
||||
@ -77,63 +80,72 @@ char *sqlite3VdbeExpandSql(
|
||||
sqlite3StrAccumInit(&out, zBase, sizeof(zBase),
|
||||
db->aLimit[SQLITE_LIMIT_LENGTH]);
|
||||
out.db = db;
|
||||
while( zRawSql[0] ){
|
||||
n = findNextHostParameter(zRawSql, &nToken);
|
||||
assert( n>0 );
|
||||
sqlite3StrAccumAppend(&out, zRawSql, n);
|
||||
zRawSql += n;
|
||||
assert( zRawSql[0] || nToken==0 );
|
||||
if( nToken==0 ) break;
|
||||
if( zRawSql[0]=='?' ){
|
||||
if( nToken>1 ){
|
||||
assert( sqlite3Isdigit(zRawSql[1]) );
|
||||
sqlite3GetInt32(&zRawSql[1], &idx);
|
||||
}else{
|
||||
idx = nextIndex;
|
||||
}
|
||||
}else{
|
||||
assert( zRawSql[0]==':' || zRawSql[0]=='$' || zRawSql[0]=='@' );
|
||||
testcase( zRawSql[0]==':' );
|
||||
testcase( zRawSql[0]=='$' );
|
||||
testcase( zRawSql[0]=='@' );
|
||||
idx = sqlite3VdbeParameterIndex(p, zRawSql, nToken);
|
||||
assert( idx>0 );
|
||||
if( db->vdbeExecCnt>1 ){
|
||||
while( *zRawSql ){
|
||||
const char *zStart = zRawSql;
|
||||
while( *(zRawSql++)!='\n' && *zRawSql );
|
||||
sqlite3StrAccumAppend(&out, "-- ", 3);
|
||||
sqlite3StrAccumAppend(&out, zStart, zRawSql-zStart);
|
||||
}
|
||||
zRawSql += nToken;
|
||||
nextIndex = idx + 1;
|
||||
assert( idx>0 && idx<=p->nVar );
|
||||
pVar = &p->aVar[idx-1];
|
||||
if( pVar->flags & MEM_Null ){
|
||||
sqlite3StrAccumAppend(&out, "NULL", 4);
|
||||
}else if( pVar->flags & MEM_Int ){
|
||||
sqlite3XPrintf(&out, "%lld", pVar->u.i);
|
||||
}else if( pVar->flags & MEM_Real ){
|
||||
sqlite3XPrintf(&out, "%!.15g", pVar->r);
|
||||
}else if( pVar->flags & MEM_Str ){
|
||||
}else{
|
||||
while( zRawSql[0] ){
|
||||
n = findNextHostParameter(zRawSql, &nToken);
|
||||
assert( n>0 );
|
||||
sqlite3StrAccumAppend(&out, zRawSql, n);
|
||||
zRawSql += n;
|
||||
assert( zRawSql[0] || nToken==0 );
|
||||
if( nToken==0 ) break;
|
||||
if( zRawSql[0]=='?' ){
|
||||
if( nToken>1 ){
|
||||
assert( sqlite3Isdigit(zRawSql[1]) );
|
||||
sqlite3GetInt32(&zRawSql[1], &idx);
|
||||
}else{
|
||||
idx = nextIndex;
|
||||
}
|
||||
}else{
|
||||
assert( zRawSql[0]==':' || zRawSql[0]=='$' || zRawSql[0]=='@' );
|
||||
testcase( zRawSql[0]==':' );
|
||||
testcase( zRawSql[0]=='$' );
|
||||
testcase( zRawSql[0]=='@' );
|
||||
idx = sqlite3VdbeParameterIndex(p, zRawSql, nToken);
|
||||
assert( idx>0 );
|
||||
}
|
||||
zRawSql += nToken;
|
||||
nextIndex = idx + 1;
|
||||
assert( idx>0 && idx<=p->nVar );
|
||||
pVar = &p->aVar[idx-1];
|
||||
if( pVar->flags & MEM_Null ){
|
||||
sqlite3StrAccumAppend(&out, "NULL", 4);
|
||||
}else if( pVar->flags & MEM_Int ){
|
||||
sqlite3XPrintf(&out, "%lld", pVar->u.i);
|
||||
}else if( pVar->flags & MEM_Real ){
|
||||
sqlite3XPrintf(&out, "%!.15g", pVar->r);
|
||||
}else if( pVar->flags & MEM_Str ){
|
||||
#ifndef SQLITE_OMIT_UTF16
|
||||
u8 enc = ENC(db);
|
||||
if( enc!=SQLITE_UTF8 ){
|
||||
Mem utf8;
|
||||
memset(&utf8, 0, sizeof(utf8));
|
||||
utf8.db = db;
|
||||
sqlite3VdbeMemSetStr(&utf8, pVar->z, pVar->n, enc, SQLITE_STATIC);
|
||||
sqlite3VdbeChangeEncoding(&utf8, SQLITE_UTF8);
|
||||
sqlite3XPrintf(&out, "'%.*q'", utf8.n, utf8.z);
|
||||
sqlite3VdbeMemRelease(&utf8);
|
||||
}else
|
||||
u8 enc = ENC(db);
|
||||
if( enc!=SQLITE_UTF8 ){
|
||||
Mem utf8;
|
||||
memset(&utf8, 0, sizeof(utf8));
|
||||
utf8.db = db;
|
||||
sqlite3VdbeMemSetStr(&utf8, pVar->z, pVar->n, enc, SQLITE_STATIC);
|
||||
sqlite3VdbeChangeEncoding(&utf8, SQLITE_UTF8);
|
||||
sqlite3XPrintf(&out, "'%.*q'", utf8.n, utf8.z);
|
||||
sqlite3VdbeMemRelease(&utf8);
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
sqlite3XPrintf(&out, "'%.*q'", pVar->n, pVar->z);
|
||||
{
|
||||
sqlite3XPrintf(&out, "'%.*q'", pVar->n, pVar->z);
|
||||
}
|
||||
}else if( pVar->flags & MEM_Zero ){
|
||||
sqlite3XPrintf(&out, "zeroblob(%d)", pVar->u.nZero);
|
||||
}else{
|
||||
assert( pVar->flags & MEM_Blob );
|
||||
sqlite3StrAccumAppend(&out, "x'", 2);
|
||||
for(i=0; i<pVar->n; i++){
|
||||
sqlite3XPrintf(&out, "%02x", pVar->z[i]&0xff);
|
||||
}
|
||||
sqlite3StrAccumAppend(&out, "'", 1);
|
||||
}
|
||||
}else if( pVar->flags & MEM_Zero ){
|
||||
sqlite3XPrintf(&out, "zeroblob(%d)", pVar->u.nZero);
|
||||
}else{
|
||||
assert( pVar->flags & MEM_Blob );
|
||||
sqlite3StrAccumAppend(&out, "x'", 2);
|
||||
for(i=0; i<pVar->n; i++){
|
||||
sqlite3XPrintf(&out, "%02x", pVar->z[i]&0xff);
|
||||
}
|
||||
sqlite3StrAccumAppend(&out, "'", 1);
|
||||
}
|
||||
}
|
||||
return sqlite3StrAccumFinish(&out);
|
||||
|
||||
@ -371,7 +371,7 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
|
||||
sqlite3ChangeCookie(pParse, iDb);
|
||||
|
||||
sqlite3VdbeAddOp2(v, OP_Expire, 0, 0);
|
||||
zWhere = sqlite3MPrintf(db, "name='%q'", pTab->zName);
|
||||
zWhere = sqlite3MPrintf(db, "name='%q' AND type='table'", pTab->zName);
|
||||
sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 1, 0, zWhere, P4_DYNAMIC);
|
||||
sqlite3VdbeAddOp4(v, OP_VCreate, iDb, 0, 0,
|
||||
pTab->zName, sqlite3Strlen30(pTab->zName) + 1);
|
||||
@ -672,7 +672,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
|
||||
}
|
||||
db->pVTab = 0;
|
||||
}else{
|
||||
sqlite3Error(db, SQLITE_ERROR, zErr);
|
||||
sqlite3Error(db, SQLITE_ERROR, (zErr ? "%s" : 0), zErr);
|
||||
sqlite3DbFree(db, zErr);
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
|
||||
117
src/wal.c
117
src/wal.c
@ -428,6 +428,13 @@ struct Wal {
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
** Candidate values for Wal.exclusiveMode.
|
||||
*/
|
||||
#define WAL_NORMAL_MODE 0
|
||||
#define WAL_EXCLUSIVE_MODE 1
|
||||
#define WAL_HEAPMEMORY_MODE 2
|
||||
|
||||
/*
|
||||
** Each page of the wal-index mapping contains a hash-table made up of
|
||||
** an array of HASHTABLE_NSLOT elements of the following type.
|
||||
@ -451,14 +458,14 @@ typedef u16 ht_slot;
|
||||
*/
|
||||
struct WalIterator {
|
||||
int iPrior; /* Last result returned from the iterator */
|
||||
int nSegment; /* Size of the aSegment[] array */
|
||||
int nSegment; /* Number of entries in aSegment[] */
|
||||
struct WalSegment {
|
||||
int iNext; /* Next slot in aIndex[] not yet returned */
|
||||
ht_slot *aIndex; /* i0, i1, i2... such that aPgno[iN] ascend */
|
||||
u32 *aPgno; /* Array of page numbers. */
|
||||
int nEntry; /* Max size of aPgno[] and aIndex[] arrays */
|
||||
int nEntry; /* Nr. of entries in aPgno[] and aIndex[] */
|
||||
int iZero; /* Frame number associated with aPgno[0] */
|
||||
} aSegment[1]; /* One for every 32KB page in the WAL */
|
||||
} aSegment[1]; /* One for every 32KB page in the wal-index */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -514,9 +521,14 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){
|
||||
|
||||
/* Request a pointer to the required page from the VFS */
|
||||
if( pWal->apWiData[iPage]==0 ){
|
||||
rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ,
|
||||
pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
|
||||
);
|
||||
if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){
|
||||
pWal->apWiData[iPage] = (u32 volatile *)sqlite3MallocZero(WALINDEX_PGSZ);
|
||||
if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ,
|
||||
pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
*ppPage = pWal->apWiData[iPage];
|
||||
@ -599,6 +611,12 @@ static void walChecksumBytes(
|
||||
aOut[1] = s2;
|
||||
}
|
||||
|
||||
static void walShmBarrier(Wal *pWal){
|
||||
if( pWal->exclusiveMode!=WAL_HEAPMEMORY_MODE ){
|
||||
sqlite3OsShmBarrier(pWal->pDbFd);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Write the header information in pWal->hdr into the wal-index.
|
||||
**
|
||||
@ -613,7 +631,7 @@ static void walIndexWriteHdr(Wal *pWal){
|
||||
pWal->hdr.iVersion = WALINDEX_MAX_VERSION;
|
||||
walChecksumBytes(1, (u8*)&pWal->hdr, nCksum, 0, pWal->hdr.aCksum);
|
||||
memcpy((void *)&aHdr[1], (void *)&pWal->hdr, sizeof(WalIndexHdr));
|
||||
sqlite3OsShmBarrier(pWal->pDbFd);
|
||||
walShmBarrier(pWal);
|
||||
memcpy((void *)&aHdr[0], (void *)&pWal->hdr, sizeof(WalIndexHdr));
|
||||
}
|
||||
|
||||
@ -1185,7 +1203,15 @@ recovery_error:
|
||||
** Close an open wal-index.
|
||||
*/
|
||||
static void walIndexClose(Wal *pWal, int isDelete){
|
||||
sqlite3OsShmUnmap(pWal->pDbFd, isDelete);
|
||||
if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){
|
||||
int i;
|
||||
for(i=0; i<pWal->nWiData; i++){
|
||||
sqlite3_free((void *)pWal->apWiData[i]);
|
||||
pWal->apWiData[i] = 0;
|
||||
}
|
||||
}else{
|
||||
sqlite3OsShmUnmap(pWal->pDbFd, isDelete);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1207,6 +1233,7 @@ int sqlite3WalOpen(
|
||||
sqlite3_vfs *pVfs, /* vfs module to open wal and wal-index */
|
||||
sqlite3_file *pDbFd, /* The open database file */
|
||||
const char *zWalName, /* Name of the WAL file */
|
||||
int bNoShm, /* True to run in heap-memory mode */
|
||||
Wal **ppWal /* OUT: Allocated Wal handle */
|
||||
){
|
||||
int rc; /* Return Code */
|
||||
@ -1240,6 +1267,7 @@ int sqlite3WalOpen(
|
||||
pRet->pDbFd = pDbFd;
|
||||
pRet->readLock = -1;
|
||||
pRet->zWalName = zWalName;
|
||||
pRet->exclusiveMode = (bNoShm ? WAL_HEAPMEMORY_MODE: WAL_NORMAL_MODE);
|
||||
|
||||
/* Open file handle on the write-ahead log file. */
|
||||
flags = (SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_WAL);
|
||||
@ -1301,9 +1329,29 @@ static int walIteratorNext(
|
||||
|
||||
/*
|
||||
** This function merges two sorted lists into a single sorted list.
|
||||
**
|
||||
** aLeft[] and aRight[] are arrays of indices. The sort key is
|
||||
** aContent[aLeft[]] and aContent[aRight[]]. Upon entry, the following
|
||||
** is guaranteed for all J<K:
|
||||
**
|
||||
** aContent[aLeft[J]] < aContent[aLeft[K]]
|
||||
** aContent[aRight[J]] < aContent[aRight[K]]
|
||||
**
|
||||
** This routine overwrites aRight[] with a new (probably longer) sequence
|
||||
** of indices such that the aRight[] contains every index that appears in
|
||||
** either aLeft[] or the old aRight[] and such that the second condition
|
||||
** above is still met.
|
||||
**
|
||||
** The aContent[aLeft[X]] values will be unique for all X. And the
|
||||
** aContent[aRight[X]] values will be unique too. But there might be
|
||||
** one or more combinations of X and Y such that
|
||||
**
|
||||
** aLeft[X]!=aRight[Y] && aContent[aLeft[X]] == aContent[aRight[Y]]
|
||||
**
|
||||
** When that happens, omit the aLeft[X] and use the aRight[Y] index.
|
||||
*/
|
||||
static void walMerge(
|
||||
u32 *aContent, /* Pages in wal */
|
||||
const u32 *aContent, /* Pages in wal - keys for the sort */
|
||||
ht_slot *aLeft, /* IN: Left hand input list */
|
||||
int nLeft, /* IN: Elements in array *paLeft */
|
||||
ht_slot **paRight, /* IN/OUT: Right hand input list */
|
||||
@ -1343,10 +1391,24 @@ static void walMerge(
|
||||
}
|
||||
|
||||
/*
|
||||
** Sort the elements in list aList, removing any duplicates.
|
||||
** Sort the elements in list aList using aContent[] as the sort key.
|
||||
** Remove elements with duplicate keys, preferring to keep the
|
||||
** larger aList[] values.
|
||||
**
|
||||
** The aList[] entries are indices into aContent[]. The values in
|
||||
** aList[] are to be sorted so that for all J<K:
|
||||
**
|
||||
** aContent[aList[J]] < aContent[aList[K]]
|
||||
**
|
||||
** For any X and Y such that
|
||||
**
|
||||
** aContent[aList[X]] == aContent[aList[Y]]
|
||||
**
|
||||
** Keep the larger of the two values aList[X] and aList[Y] and discard
|
||||
** the smaller.
|
||||
*/
|
||||
static void walMergesort(
|
||||
u32 *aContent, /* Pages in wal */
|
||||
const u32 *aContent, /* Pages in wal */
|
||||
ht_slot *aBuffer, /* Buffer of at least *pnList items to use */
|
||||
ht_slot *aList, /* IN/OUT: List to sort */
|
||||
int *pnList /* IN/OUT: Number of elements in aList[] */
|
||||
@ -1411,6 +1473,7 @@ static void walIteratorFree(WalIterator *p){
|
||||
/*
|
||||
** Construct a WalInterator object that can be used to loop over all
|
||||
** pages in the WAL in ascending order. The caller must hold the checkpoint
|
||||
** lock.
|
||||
**
|
||||
** On success, make *pp point to the newly allocated WalInterator object
|
||||
** return SQLITE_OK. Otherwise, return an error code. If this routine
|
||||
@ -1545,7 +1608,8 @@ static int walCheckpoint(
|
||||
szPage = (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16);
|
||||
testcase( szPage<=32768 );
|
||||
testcase( szPage>=65536 );
|
||||
if( pWal->hdr.mxFrame==0 ) return SQLITE_OK;
|
||||
pInfo = walCkptInfo(pWal);
|
||||
if( pInfo->nBackfill>=pWal->hdr.mxFrame ) return SQLITE_OK;
|
||||
|
||||
/* Allocate the iterator */
|
||||
rc = walIteratorInit(pWal, &pIter);
|
||||
@ -1567,7 +1631,6 @@ static int walCheckpoint(
|
||||
*/
|
||||
mxSafeFrame = pWal->hdr.mxFrame;
|
||||
mxPage = pWal->hdr.nPage;
|
||||
pInfo = walCkptInfo(pWal);
|
||||
for(i=1; i<WAL_NREADER; i++){
|
||||
u32 y = pInfo->aReadMark[i];
|
||||
if( mxSafeFrame>=y ){
|
||||
@ -1673,7 +1736,9 @@ int sqlite3WalClose(
|
||||
*/
|
||||
rc = sqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE);
|
||||
if( rc==SQLITE_OK ){
|
||||
pWal->exclusiveMode = 1;
|
||||
if( pWal->exclusiveMode==WAL_NORMAL_MODE ){
|
||||
pWal->exclusiveMode = WAL_EXCLUSIVE_MODE;
|
||||
}
|
||||
rc = sqlite3WalCheckpoint(pWal, sync_flags, nBuf, zBuf);
|
||||
if( rc==SQLITE_OK ){
|
||||
isDelete = 1;
|
||||
@ -1729,7 +1794,7 @@ static int walIndexTryHdr(Wal *pWal, int *pChanged){
|
||||
*/
|
||||
aHdr = walIndexHdr(pWal);
|
||||
memcpy(&h1, (void *)&aHdr[0], sizeof(h1));
|
||||
sqlite3OsShmBarrier(pWal->pDbFd);
|
||||
walShmBarrier(pWal);
|
||||
memcpy(&h2, (void *)&aHdr[1], sizeof(h2));
|
||||
|
||||
if( memcmp(&h1, &h2, sizeof(h1))!=0 ){
|
||||
@ -1930,7 +1995,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
|
||||
** and can be safely ignored.
|
||||
*/
|
||||
rc = walLockShared(pWal, WAL_READ_LOCK(0));
|
||||
sqlite3OsShmBarrier(pWal->pDbFd);
|
||||
walShmBarrier(pWal);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) ){
|
||||
/* It is not safe to allow the reader to continue here if frames
|
||||
@ -2024,7 +2089,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
|
||||
** log-wrap (either of which would require an exclusive lock on
|
||||
** WAL_READ_LOCK(mxI)) has not occurred since the snapshot was valid.
|
||||
*/
|
||||
sqlite3OsShmBarrier(pWal->pDbFd);
|
||||
walShmBarrier(pWal);
|
||||
if( pInfo->aReadMark[mxI]!=mxReadMark
|
||||
|| memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr))
|
||||
){
|
||||
@ -2366,7 +2431,7 @@ int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){
|
||||
**
|
||||
** SQLITE_OK is returned if no error is encountered (regardless of whether
|
||||
** or not pWal->hdr.mxFrame is modified). An SQLite error code is returned
|
||||
** if some error
|
||||
** if an error occurs.
|
||||
*/
|
||||
static int walRestartLog(Wal *pWal){
|
||||
int rc = SQLITE_OK;
|
||||
@ -2399,6 +2464,8 @@ static int walRestartLog(Wal *pWal){
|
||||
for(i=1; i<WAL_NREADER; i++) pInfo->aReadMark[i] = READMARK_NOT_USED;
|
||||
assert( pInfo->aReadMark[0]==0 );
|
||||
walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
|
||||
}else if( rc!=SQLITE_BUSY ){
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
walUnlockShared(pWal, WAL_READ_LOCK(0));
|
||||
@ -2478,7 +2545,7 @@ int sqlite3WalFrames(
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
assert( pWal->szPage==szPage );
|
||||
assert( (int)pWal->szPage==szPage );
|
||||
|
||||
/* Write the log file. */
|
||||
for(p=pList; p; p=p->pDirty){
|
||||
@ -2665,13 +2732,14 @@ int sqlite3WalCallback(Wal *pWal){
|
||||
** on the main database file before invoking this operation.
|
||||
**
|
||||
** If op is negative, then do a dry-run of the op==1 case but do
|
||||
** not actually change anything. The pager uses this to see if it
|
||||
** not actually change anything. The pager uses this to see if it
|
||||
** should acquire the database exclusive lock prior to invoking
|
||||
** the op==1 case.
|
||||
*/
|
||||
int sqlite3WalExclusiveMode(Wal *pWal, int op){
|
||||
int rc;
|
||||
assert( pWal->writeLock==0 );
|
||||
assert( pWal->exclusiveMode!=WAL_HEAPMEMORY_MODE || op==-1 );
|
||||
|
||||
/* pWal->readLock is usually set, but might be -1 if there was a
|
||||
** prior error while attempting to acquire are read-lock. This cannot
|
||||
@ -2705,4 +2773,13 @@ int sqlite3WalExclusiveMode(Wal *pWal, int op){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if the argument is non-NULL and the WAL module is using
|
||||
** heap-memory for the wal-index. Otherwise, if the argument is NULL or the
|
||||
** WAL module is using shared-memory, return false.
|
||||
*/
|
||||
int sqlite3WalHeapMemory(Wal *pWal){
|
||||
return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE );
|
||||
}
|
||||
|
||||
#endif /* #ifndef SQLITE_OMIT_WAL */
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
# define sqlite3WalCheckpoint(u,v,w,x) 0
|
||||
# define sqlite3WalCallback(z) 0
|
||||
# define sqlite3WalExclusiveMode(y,z) 0
|
||||
# define sqlite3WalHeapMemory(z) 0
|
||||
#else
|
||||
|
||||
#define WAL_SAVEPOINT_NDATA 4
|
||||
@ -45,7 +46,7 @@
|
||||
typedef struct Wal Wal;
|
||||
|
||||
/* Open and close a connection to a write-ahead log. */
|
||||
int sqlite3WalOpen(sqlite3_vfs*, sqlite3_file*, const char *zName, Wal**);
|
||||
int sqlite3WalOpen(sqlite3_vfs*, sqlite3_file*, const char *zName, int, Wal**);
|
||||
int sqlite3WalClose(Wal *pWal, int sync_flags, int, u8 *);
|
||||
|
||||
/* Used by readers to open (lock) and close (unlock) a snapshot. A
|
||||
@ -102,5 +103,11 @@ int sqlite3WalCallback(Wal *pWal);
|
||||
*/
|
||||
int sqlite3WalExclusiveMode(Wal *pWal, int op);
|
||||
|
||||
/* Return true if the argument is non-NULL and the WAL module is using
|
||||
** heap-memory for the wal-index. Otherwise, if the argument is NULL or the
|
||||
** WAL module is using shared-memory, return false.
|
||||
*/
|
||||
int sqlite3WalHeapMemory(Wal *pWal);
|
||||
|
||||
#endif /* ifndef SQLITE_OMIT_WAL */
|
||||
#endif /* _WAL_H_ */
|
||||
|
||||
334
src/where.c
334
src/where.c
@ -192,7 +192,6 @@ struct WhereMaskSet {
|
||||
struct WhereCost {
|
||||
WherePlan plan; /* The lookup strategy */
|
||||
double rCost; /* Overall cost of pursuing this search strategy */
|
||||
double nRow; /* Estimated number of output rows */
|
||||
Bitmask used; /* Bitmask of cursors used by this plan */
|
||||
};
|
||||
|
||||
@ -235,10 +234,11 @@ struct WhereCost {
|
||||
#define WHERE_COLUMN_IN 0x00040000 /* x IN (...) */
|
||||
#define WHERE_COLUMN_NULL 0x00080000 /* x IS NULL */
|
||||
#define WHERE_INDEXED 0x000f0000 /* Anything that uses an index */
|
||||
#define WHERE_NOT_FULLSCAN 0x000f3000 /* Does not do a full table scan */
|
||||
#define WHERE_NOT_FULLSCAN 0x100f3000 /* Does not do a full table scan */
|
||||
#define WHERE_IN_ABLE 0x000f1000 /* Able to support an IN operator */
|
||||
#define WHERE_TOP_LIMIT 0x00100000 /* x<EXPR or x<=EXPR constraint */
|
||||
#define WHERE_BTM_LIMIT 0x00200000 /* x>EXPR or x>=EXPR constraint */
|
||||
#define WHERE_BOTH_LIMIT 0x00300000 /* Both x>EXPR and x<EXPR */
|
||||
#define WHERE_IDX_ONLY 0x00800000 /* Use index only - omit table */
|
||||
#define WHERE_ORDERBY 0x01000000 /* Output will appear in correct order */
|
||||
#define WHERE_REVERSE 0x02000000 /* Scan in reverse order */
|
||||
@ -669,11 +669,12 @@ static int isLikeOrGlob(
|
||||
}
|
||||
if( op==TK_VARIABLE ){
|
||||
Vdbe *pReprepare = pParse->pReprepare;
|
||||
pVal = sqlite3VdbeGetValue(pReprepare, pRight->iColumn, SQLITE_AFF_NONE);
|
||||
int iCol = pRight->iColumn;
|
||||
pVal = sqlite3VdbeGetValue(pReprepare, iCol, SQLITE_AFF_NONE);
|
||||
if( pVal && sqlite3_value_type(pVal)==SQLITE_TEXT ){
|
||||
z = (char *)sqlite3_value_text(pVal);
|
||||
}
|
||||
sqlite3VdbeSetVarmask(pParse->pVdbe, pRight->iColumn);
|
||||
sqlite3VdbeSetVarmask(pParse->pVdbe, iCol); /* IMP: R-23257-02778 */
|
||||
assert( pRight->op==TK_VARIABLE || pRight->op==TK_REGISTER );
|
||||
}else if( op==TK_STRING ){
|
||||
z = pRight->u.zToken;
|
||||
@ -691,7 +692,7 @@ static int isLikeOrGlob(
|
||||
*ppPrefix = pPrefix;
|
||||
if( op==TK_VARIABLE ){
|
||||
Vdbe *v = pParse->pVdbe;
|
||||
sqlite3VdbeSetVarmask(v, pRight->iColumn);
|
||||
sqlite3VdbeSetVarmask(v, pRight->iColumn); /* IMP: R-23257-02778 */
|
||||
if( *pisComplete && pRight->u.zToken[1] ){
|
||||
/* If the rhs of the LIKE expression is a variable, and the current
|
||||
** value of the variable means there is no need to invoke the LIKE
|
||||
@ -1555,7 +1556,8 @@ static void TRACE_IDX_OUTPUTS(sqlite3_index_info *p){
|
||||
** Required because bestIndex() is called by bestOrClauseIndex()
|
||||
*/
|
||||
static void bestIndex(
|
||||
Parse*, WhereClause*, struct SrcList_item*, Bitmask, ExprList*, WhereCost*);
|
||||
Parse*, WhereClause*, struct SrcList_item*,
|
||||
Bitmask, Bitmask, ExprList*, WhereCost*);
|
||||
|
||||
/*
|
||||
** This routine attempts to find an scanning strategy that can be used
|
||||
@ -1568,7 +1570,8 @@ static void bestOrClauseIndex(
|
||||
Parse *pParse, /* The parsing context */
|
||||
WhereClause *pWC, /* The WHERE clause */
|
||||
struct SrcList_item *pSrc, /* The FROM clause term to search */
|
||||
Bitmask notReady, /* Mask of cursors that are not available */
|
||||
Bitmask notReady, /* Mask of cursors not available for indexing */
|
||||
Bitmask notValid, /* Cursors not available for any purpose */
|
||||
ExprList *pOrderBy, /* The ORDER BY clause */
|
||||
WhereCost *pCost /* Lowest cost query plan */
|
||||
){
|
||||
@ -1578,8 +1581,9 @@ static void bestOrClauseIndex(
|
||||
WhereTerm * const pWCEnd = &pWC->a[pWC->nTerm]; /* End of pWC->a[] */
|
||||
WhereTerm *pTerm; /* A single term of the WHERE clause */
|
||||
|
||||
/* No OR-clause optimization allowed if the NOT INDEXED clause is used */
|
||||
if( pSrc->notIndexed ){
|
||||
/* No OR-clause optimization allowed if the INDEXED BY or NOT INDEXED clauses
|
||||
** are used */
|
||||
if( pSrc->notIndexed || pSrc->pIndex!=0 ){
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1604,7 +1608,7 @@ static void bestOrClauseIndex(
|
||||
));
|
||||
if( pOrTerm->eOperator==WO_AND ){
|
||||
WhereClause *pAndWC = &pOrTerm->u.pAndInfo->wc;
|
||||
bestIndex(pParse, pAndWC, pSrc, notReady, 0, &sTermCost);
|
||||
bestIndex(pParse, pAndWC, pSrc, notReady, notValid, 0, &sTermCost);
|
||||
}else if( pOrTerm->leftCursor==iCur ){
|
||||
WhereClause tempWC;
|
||||
tempWC.pParse = pWC->pParse;
|
||||
@ -1612,12 +1616,12 @@ static void bestOrClauseIndex(
|
||||
tempWC.op = TK_AND;
|
||||
tempWC.a = pOrTerm;
|
||||
tempWC.nTerm = 1;
|
||||
bestIndex(pParse, &tempWC, pSrc, notReady, 0, &sTermCost);
|
||||
bestIndex(pParse, &tempWC, pSrc, notReady, notValid, 0, &sTermCost);
|
||||
}else{
|
||||
continue;
|
||||
}
|
||||
rTotal += sTermCost.rCost;
|
||||
nRow += sTermCost.nRow;
|
||||
nRow += sTermCost.plan.nRow;
|
||||
used |= sTermCost.used;
|
||||
if( rTotal>=pCost->rCost ) break;
|
||||
}
|
||||
@ -1636,8 +1640,8 @@ static void bestOrClauseIndex(
|
||||
WHERETRACE(("... multi-index OR cost=%.9g nrow=%.9g\n", rTotal, nRow));
|
||||
if( rTotal<pCost->rCost ){
|
||||
pCost->rCost = rTotal;
|
||||
pCost->nRow = nRow;
|
||||
pCost->used = used;
|
||||
pCost->plan.nRow = nRow;
|
||||
pCost->plan.wsFlags = flags;
|
||||
pCost->plan.u.pTerm = pTerm;
|
||||
}
|
||||
@ -1705,7 +1709,7 @@ static void bestAutomaticIndex(
|
||||
|
||||
assert( pParse->nQueryLoop >= (double)1 );
|
||||
pTable = pSrc->pTab;
|
||||
nTableRow = pTable->pIndex ? pTable->pIndex->aiRowEst[0] : 1000000;
|
||||
nTableRow = pTable->nRowEst;
|
||||
logN = estLog(nTableRow);
|
||||
costTempIdx = 2*logN*(nTableRow/pParse->nQueryLoop + 1);
|
||||
if( costTempIdx>=pCost->rCost ){
|
||||
@ -1721,7 +1725,7 @@ static void bestAutomaticIndex(
|
||||
WHERETRACE(("auto-index reduces cost from %.2f to %.2f\n",
|
||||
pCost->rCost, costTempIdx));
|
||||
pCost->rCost = costTempIdx;
|
||||
pCost->nRow = logN + 1;
|
||||
pCost->plan.nRow = logN + 1;
|
||||
pCost->plan.wsFlags = WHERE_TEMP_INDEX;
|
||||
pCost->used = pTerm->prereqRight;
|
||||
break;
|
||||
@ -2059,7 +2063,8 @@ static void bestVirtualIndex(
|
||||
Parse *pParse, /* The parsing context */
|
||||
WhereClause *pWC, /* The WHERE clause */
|
||||
struct SrcList_item *pSrc, /* The FROM clause term to search */
|
||||
Bitmask notReady, /* Mask of cursors that are not available */
|
||||
Bitmask notReady, /* Mask of cursors not available for index */
|
||||
Bitmask notValid, /* Cursors not valid for any purpose */
|
||||
ExprList *pOrderBy, /* The order by clause */
|
||||
WhereCost *pCost, /* Lowest cost query plan */
|
||||
sqlite3_index_info **ppIdxInfo /* Index information passed to xBestIndex */
|
||||
@ -2189,7 +2194,7 @@ static void bestVirtualIndex(
|
||||
/* Try to find a more efficient access pattern by using multiple indexes
|
||||
** to optimize an OR expression within the WHERE clause.
|
||||
*/
|
||||
bestOrClauseIndex(pParse, pWC, pSrc, notReady, pOrderBy, pCost);
|
||||
bestOrClauseIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost);
|
||||
}
|
||||
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
||||
|
||||
@ -2310,12 +2315,11 @@ static int valueFromExpr(
|
||||
u8 aff,
|
||||
sqlite3_value **pp
|
||||
){
|
||||
/* The evalConstExpr() function will have already converted any TK_VARIABLE
|
||||
** expression involved in an comparison into a TK_REGISTER. */
|
||||
assert( pExpr->op!=TK_VARIABLE );
|
||||
if( pExpr->op==TK_REGISTER && pExpr->op2==TK_VARIABLE ){
|
||||
if( pExpr->op==TK_VARIABLE
|
||||
|| (pExpr->op==TK_REGISTER && pExpr->op2==TK_VARIABLE)
|
||||
){
|
||||
int iVar = pExpr->iColumn;
|
||||
sqlite3VdbeSetVarmask(pParse->pVdbe, iVar);
|
||||
sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); /* IMP: R-23257-02778 */
|
||||
*pp = sqlite3VdbeGetValue(pParse->pReprepare, iVar, aff);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@ -2470,7 +2474,8 @@ static void bestBtreeIndex(
|
||||
Parse *pParse, /* The parsing context */
|
||||
WhereClause *pWC, /* The WHERE clause */
|
||||
struct SrcList_item *pSrc, /* The FROM clause term to search */
|
||||
Bitmask notReady, /* Mask of cursors that are not available */
|
||||
Bitmask notReady, /* Mask of cursors not available for indexing */
|
||||
Bitmask notValid, /* Cursors not available for any purpose */
|
||||
ExprList *pOrderBy, /* The ORDER BY clause */
|
||||
WhereCost *pCost /* Lowest cost query plan */
|
||||
){
|
||||
@ -2512,23 +2517,14 @@ static void bestBtreeIndex(
|
||||
sPk.nColumn = 1;
|
||||
sPk.aiColumn = &aiColumnPk;
|
||||
sPk.aiRowEst = aiRowEstPk;
|
||||
aiRowEstPk[1] = 1;
|
||||
sPk.onError = OE_Replace;
|
||||
sPk.pTable = pSrc->pTab;
|
||||
aiRowEstPk[0] = pSrc->pTab->nRowEst;
|
||||
aiRowEstPk[1] = 1;
|
||||
pFirst = pSrc->pTab->pIndex;
|
||||
if( pSrc->notIndexed==0 ){
|
||||
sPk.pNext = pFirst;
|
||||
}
|
||||
/* The aiRowEstPk[0] is an estimate of the total number of rows in the
|
||||
** table. Get this information from the ANALYZE information if it is
|
||||
** available. If not available, assume the table 1 million rows in size.
|
||||
*/
|
||||
if( pFirst ){
|
||||
assert( pFirst->aiRowEst!=0 ); /* Allocated together with pFirst */
|
||||
aiRowEstPk[0] = pFirst->aiRowEst[0];
|
||||
}else{
|
||||
aiRowEstPk[0] = 1000000;
|
||||
}
|
||||
pProbe = &sPk;
|
||||
wsFlagMask = ~(
|
||||
WHERE_COLUMN_IN|WHERE_COLUMN_EQ|WHERE_COLUMN_NULL|WHERE_COLUMN_RANGE
|
||||
@ -2741,16 +2737,16 @@ static void bestBtreeIndex(
|
||||
** with this step if we already know this index will not be chosen.
|
||||
** Also, never reduce the output row count below 2 using this step.
|
||||
**
|
||||
** Do not reduce the output row count if pSrc is the only table that
|
||||
** is notReady; if notReady is a power of two. This will be the case
|
||||
** when the main sqlite3WhereBegin() loop is scanning for a table with
|
||||
** and "optimal" index, and on such a scan the output row count
|
||||
** reduction is not valid because it does not update the "pCost->used"
|
||||
** bitmap. The notReady bitmap will also be a power of two when we
|
||||
** are scanning for the last table in a 64-way join. We are willing
|
||||
** to bypass this optimization in that corner case.
|
||||
** It is critical that the notValid mask be used here instead of
|
||||
** the notReady mask. When computing an "optimal" index, the notReady
|
||||
** mask will only have one bit set - the bit for the current table.
|
||||
** The notValid mask, on the other hand, always has all bits set for
|
||||
** tables that are not in outer loops. If notReady is used here instead
|
||||
** of notValid, then a optimal index that depends on inner joins loops
|
||||
** might be selected even when there exists an optimal index that has
|
||||
** no such dependency.
|
||||
*/
|
||||
if( nRow>2 && cost<=pCost->rCost && (notReady & (notReady-1))!=0 ){
|
||||
if( nRow>2 && cost<=pCost->rCost ){
|
||||
int k; /* Loop counter */
|
||||
int nSkipEq = nEq; /* Number of == constraints to skip */
|
||||
int nSkipRange = nBound; /* Number of < constraints to skip */
|
||||
@ -2759,7 +2755,7 @@ static void bestBtreeIndex(
|
||||
thisTab = getMask(pWC->pMaskSet, iCur);
|
||||
for(pTerm=pWC->a, k=pWC->nTerm; nRow>2 && k; k--, pTerm++){
|
||||
if( pTerm->wtFlags & TERM_VIRTUAL ) continue;
|
||||
if( (pTerm->prereqAll & notReady)!=thisTab ) continue;
|
||||
if( (pTerm->prereqAll & notValid)!=thisTab ) continue;
|
||||
if( pTerm->eOperator & (WO_EQ|WO_IN|WO_ISNULL) ){
|
||||
if( nSkipEq ){
|
||||
/* Ignore the first nEq equality matches since the index
|
||||
@ -2801,11 +2797,11 @@ static void bestBtreeIndex(
|
||||
** index and its cost in the pCost structure.
|
||||
*/
|
||||
if( (!pIdx || wsFlags)
|
||||
&& (cost<pCost->rCost || (cost<=pCost->rCost && nRow<pCost->nRow))
|
||||
&& (cost<pCost->rCost || (cost<=pCost->rCost && nRow<pCost->plan.nRow))
|
||||
){
|
||||
pCost->rCost = cost;
|
||||
pCost->nRow = nRow;
|
||||
pCost->used = used;
|
||||
pCost->plan.nRow = nRow;
|
||||
pCost->plan.wsFlags = (wsFlags&wsFlagMask);
|
||||
pCost->plan.nEq = nEq;
|
||||
pCost->plan.u.pIdx = pIdx;
|
||||
@ -2841,7 +2837,7 @@ static void bestBtreeIndex(
|
||||
pCost->plan.u.pIdx ? pCost->plan.u.pIdx->zName : "ipk")
|
||||
));
|
||||
|
||||
bestOrClauseIndex(pParse, pWC, pSrc, notReady, pOrderBy, pCost);
|
||||
bestOrClauseIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost);
|
||||
bestAutomaticIndex(pParse, pWC, pSrc, notReady, pCost);
|
||||
pCost->plan.wsFlags |= eqTermMask;
|
||||
}
|
||||
@ -2856,14 +2852,15 @@ static void bestIndex(
|
||||
Parse *pParse, /* The parsing context */
|
||||
WhereClause *pWC, /* The WHERE clause */
|
||||
struct SrcList_item *pSrc, /* The FROM clause term to search */
|
||||
Bitmask notReady, /* Mask of cursors that are not available */
|
||||
Bitmask notReady, /* Mask of cursors not available for indexing */
|
||||
Bitmask notValid, /* Cursors not available for any purpose */
|
||||
ExprList *pOrderBy, /* The ORDER BY clause */
|
||||
WhereCost *pCost /* Lowest cost query plan */
|
||||
){
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
if( IsVirtual(pSrc->pTab) ){
|
||||
sqlite3_index_info *p = 0;
|
||||
bestVirtualIndex(pParse, pWC, pSrc, notReady, pOrderBy, pCost, &p);
|
||||
bestVirtualIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost,&p);
|
||||
if( p->needToFreeIdxStr ){
|
||||
sqlite3_free(p->idxStr);
|
||||
}
|
||||
@ -2871,7 +2868,7 @@ static void bestIndex(
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
bestBtreeIndex(pParse, pWC, pSrc, notReady, pOrderBy, pCost);
|
||||
bestBtreeIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3133,6 +3130,161 @@ static int codeAllEqualityTerms(
|
||||
return regBase;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_EXPLAIN
|
||||
/*
|
||||
** This routine is a helper for explainIndexRange() below
|
||||
**
|
||||
** pStr holds the text of an expression that we are building up one term
|
||||
** at a time. This routine adds a new term to the end of the expression.
|
||||
** Terms are separated by AND so add the "AND" text for second and subsequent
|
||||
** terms only.
|
||||
*/
|
||||
static void explainAppendTerm(
|
||||
StrAccum *pStr, /* The text expression being built */
|
||||
int iTerm, /* Index of this term. First is zero */
|
||||
const char *zColumn, /* Name of the column */
|
||||
const char *zOp /* Name of the operator */
|
||||
){
|
||||
if( iTerm ) sqlite3StrAccumAppend(pStr, " AND ", 5);
|
||||
sqlite3StrAccumAppend(pStr, zColumn, -1);
|
||||
sqlite3StrAccumAppend(pStr, zOp, 1);
|
||||
sqlite3StrAccumAppend(pStr, "?", 1);
|
||||
}
|
||||
|
||||
/*
|
||||
** Argument pLevel describes a strategy for scanning table pTab. This
|
||||
** function returns a pointer to a string buffer containing a description
|
||||
** of the subset of table rows scanned by the strategy in the form of an
|
||||
** SQL expression. Or, if all rows are scanned, NULL is returned.
|
||||
**
|
||||
** For example, if the query:
|
||||
**
|
||||
** SELECT * FROM t1 WHERE a=1 AND b>2;
|
||||
**
|
||||
** is run and there is an index on (a, b), then this function returns a
|
||||
** string similar to:
|
||||
**
|
||||
** "a=? AND b>?"
|
||||
**
|
||||
** The returned pointer points to memory obtained from sqlite3DbMalloc().
|
||||
** It is the responsibility of the caller to free the buffer when it is
|
||||
** no longer required.
|
||||
*/
|
||||
static char *explainIndexRange(sqlite3 *db, WhereLevel *pLevel, Table *pTab){
|
||||
WherePlan *pPlan = &pLevel->plan;
|
||||
Index *pIndex = pPlan->u.pIdx;
|
||||
int nEq = pPlan->nEq;
|
||||
int i, j;
|
||||
Column *aCol = pTab->aCol;
|
||||
int *aiColumn = pIndex->aiColumn;
|
||||
StrAccum txt;
|
||||
|
||||
if( nEq==0 && (pPlan->wsFlags & (WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))==0 ){
|
||||
return 0;
|
||||
}
|
||||
sqlite3StrAccumInit(&txt, 0, 0, SQLITE_MAX_LENGTH);
|
||||
txt.db = db;
|
||||
sqlite3StrAccumAppend(&txt, " (", 2);
|
||||
for(i=0; i<nEq; i++){
|
||||
explainAppendTerm(&txt, i, aCol[aiColumn[i]].zName, "=");
|
||||
}
|
||||
|
||||
j = i;
|
||||
if( pPlan->wsFlags&WHERE_BTM_LIMIT ){
|
||||
explainAppendTerm(&txt, i++, aCol[aiColumn[j]].zName, ">");
|
||||
}
|
||||
if( pPlan->wsFlags&WHERE_TOP_LIMIT ){
|
||||
explainAppendTerm(&txt, i, aCol[aiColumn[j]].zName, "<");
|
||||
}
|
||||
sqlite3StrAccumAppend(&txt, ")", 1);
|
||||
return sqlite3StrAccumFinish(&txt);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN
|
||||
** command. If the query being compiled is an EXPLAIN QUERY PLAN, a single
|
||||
** record is added to the output to describe the table scan strategy in
|
||||
** pLevel.
|
||||
*/
|
||||
static void explainOneScan(
|
||||
Parse *pParse, /* Parse context */
|
||||
SrcList *pTabList, /* Table list this loop refers to */
|
||||
WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */
|
||||
int iLevel, /* Value for "level" column of output */
|
||||
int iFrom, /* Value for "from" column of output */
|
||||
u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */
|
||||
){
|
||||
if( pParse->explain==2 ){
|
||||
u32 flags = pLevel->plan.wsFlags;
|
||||
struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom];
|
||||
Vdbe *v = pParse->pVdbe; /* VM being constructed */
|
||||
sqlite3 *db = pParse->db; /* Database handle */
|
||||
char *zMsg; /* Text to add to EQP output */
|
||||
sqlite3_int64 nRow; /* Expected number of rows visited by scan */
|
||||
int iId = pParse->iSelectId; /* Select id (left-most output column) */
|
||||
int isSearch; /* True for a SEARCH. False for SCAN. */
|
||||
|
||||
if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_ONETABLE_ONLY) ) return;
|
||||
|
||||
isSearch = (pLevel->plan.nEq>0)
|
||||
|| (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0
|
||||
|| (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX));
|
||||
|
||||
zMsg = sqlite3MPrintf(db, "%s", isSearch?"SEARCH":"SCAN");
|
||||
if( pItem->pSelect ){
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s SUBQUERY %d", zMsg,pItem->iSelectId);
|
||||
}else{
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s TABLE %s", zMsg, pItem->zName);
|
||||
}
|
||||
|
||||
if( pItem->zAlias ){
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s AS %s", zMsg, pItem->zAlias);
|
||||
}
|
||||
if( (flags & WHERE_INDEXED)!=0 ){
|
||||
char *zWhere = explainIndexRange(db, pLevel, pItem->pTab);
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s USING %s%sINDEX%s%s%s", zMsg,
|
||||
((flags & WHERE_TEMP_INDEX)?"AUTOMATIC ":""),
|
||||
((flags & WHERE_IDX_ONLY)?"COVERING ":""),
|
||||
((flags & WHERE_TEMP_INDEX)?"":" "),
|
||||
((flags & WHERE_TEMP_INDEX)?"": pLevel->plan.u.pIdx->zName),
|
||||
zWhere
|
||||
);
|
||||
sqlite3DbFree(db, zWhere);
|
||||
}else if( flags & (WHERE_ROWID_EQ|WHERE_ROWID_RANGE) ){
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s USING INTEGER PRIMARY KEY", zMsg);
|
||||
|
||||
if( flags&WHERE_ROWID_EQ ){
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid=?)", zMsg);
|
||||
}else if( (flags&WHERE_BOTH_LIMIT)==WHERE_BOTH_LIMIT ){
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid>? AND rowid<?)", zMsg);
|
||||
}else if( flags&WHERE_BTM_LIMIT ){
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid>?)", zMsg);
|
||||
}else if( flags&WHERE_TOP_LIMIT ){
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid<?)", zMsg);
|
||||
}
|
||||
}
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
else if( (flags & WHERE_VIRTUALTABLE)!=0 ){
|
||||
sqlite3_index_info *pVtabIdx = pLevel->plan.u.pVtabIdx;
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s VIRTUAL TABLE INDEX %d:%s", zMsg,
|
||||
pVtabIdx->idxNum, pVtabIdx->idxStr);
|
||||
}
|
||||
#endif
|
||||
if( wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX) ){
|
||||
testcase( wctrlFlags & WHERE_ORDERBY_MIN );
|
||||
nRow = 1;
|
||||
}else{
|
||||
nRow = (sqlite3_int64)pLevel->plan.nRow;
|
||||
}
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s (~%lld rows)", zMsg, nRow);
|
||||
sqlite3VdbeAddOp4(v, OP_Explain, iId, iLevel, iFrom, zMsg, P4_DYNAMIC);
|
||||
}
|
||||
}
|
||||
#else
|
||||
# define explainOneScan(u,v,w,x,y,z)
|
||||
#endif /* SQLITE_OMIT_EXPLAIN */
|
||||
|
||||
|
||||
/*
|
||||
** Generate code for the start of the iLevel-th loop in the WHERE clause
|
||||
** implementation described by pWInfo.
|
||||
@ -3540,7 +3692,7 @@ static Bitmask codeOneLoopStart(
|
||||
r1 = sqlite3GetTempReg(pParse);
|
||||
testcase( pLevel->plan.wsFlags & WHERE_BTM_LIMIT );
|
||||
testcase( pLevel->plan.wsFlags & WHERE_TOP_LIMIT );
|
||||
if( pLevel->plan.wsFlags & (WHERE_BTM_LIMIT|WHERE_TOP_LIMIT) ){
|
||||
if( (pLevel->plan.wsFlags & (WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0 ){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, nEq, r1);
|
||||
sqlite3VdbeAddOp2(v, OP_IsNull, r1, addrCont);
|
||||
}
|
||||
@ -3674,6 +3826,9 @@ static Bitmask codeOneLoopStart(
|
||||
WHERE_OMIT_OPEN | WHERE_OMIT_CLOSE |
|
||||
WHERE_FORCE_TABLE | WHERE_ONETABLE_ONLY);
|
||||
if( pSubWInfo ){
|
||||
explainOneScan(
|
||||
pParse, pOrTab, &pSubWInfo->a[0], iLevel, pLevel->iFrom, 0
|
||||
);
|
||||
if( (wctrlFlags & WHERE_DUPLICATES_OK)==0 ){
|
||||
int iSet = ((ii==pOrWc->nTerm-1)?-1:ii);
|
||||
int r;
|
||||
@ -4069,6 +4224,7 @@ WhereInfo *sqlite3WhereBegin(
|
||||
|
||||
memset(&bestPlan, 0, sizeof(bestPlan));
|
||||
bestPlan.rCost = SQLITE_BIG_DBL;
|
||||
WHERETRACE(("*** Begin search for loop %d ***\n", i));
|
||||
|
||||
/* Loop through the remaining entries in the FROM clause to find the
|
||||
** next nested loop. The loop tests all FROM clause entries
|
||||
@ -4087,9 +4243,15 @@ WhereInfo *sqlite3WhereBegin(
|
||||
** other FROM clause terms that are notReady. If no notReady terms are
|
||||
** used then the "optimal" query plan works.
|
||||
**
|
||||
** Note that the WhereCost.nRow parameter for an optimal scan might
|
||||
** not be as small as it would be if the table really were the innermost
|
||||
** join. The nRow value can be reduced by WHERE clause constraints
|
||||
** that do not use indices. But this nRow reduction only happens if the
|
||||
** table really is the innermost join.
|
||||
**
|
||||
** The second loop iteration is only performed if no optimal scan
|
||||
** strategies were found by the first loop. This 2nd iteration is used to
|
||||
** search for the lowest cost scan overall.
|
||||
** strategies were found by the first iteration. This second iteration
|
||||
** is used to search for the lowest cost scan overall.
|
||||
**
|
||||
** Previous versions of SQLite performed only the second iteration -
|
||||
** the next outermost loop was always that with the lowest overall
|
||||
@ -4102,14 +4264,14 @@ WhereInfo *sqlite3WhereBegin(
|
||||
**
|
||||
** The best strategy is to iterate through table t1 first. However it
|
||||
** is not possible to determine this with a simple greedy algorithm.
|
||||
** However, since the cost of a linear scan through table t2 is the same
|
||||
** Since the cost of a linear scan through table t2 is the same
|
||||
** as the cost of a linear scan through table t1, a simple greedy
|
||||
** algorithm may choose to use t2 for the outer loop, which is a much
|
||||
** costlier approach.
|
||||
*/
|
||||
nUnconstrained = 0;
|
||||
notIndexed = 0;
|
||||
for(isOptimal=(iFrom<nTabList-1); isOptimal>=0; isOptimal--){
|
||||
for(isOptimal=(iFrom<nTabList-1); isOptimal>=0 && bestJ<0; isOptimal--){
|
||||
Bitmask mask; /* Mask of tables not yet ready */
|
||||
for(j=iFrom, pTabItem=&pTabList->a[j]; j<nTabList; j++, pTabItem++){
|
||||
int doNotReorder; /* True if this table should not be reordered */
|
||||
@ -4127,15 +4289,19 @@ WhereInfo *sqlite3WhereBegin(
|
||||
pOrderBy = ((i==0 && ppOrderBy )?*ppOrderBy:0);
|
||||
if( pTabItem->pIndex==0 ) nUnconstrained++;
|
||||
|
||||
WHERETRACE(("=== trying table %d with isOptimal=%d ===\n",
|
||||
j, isOptimal));
|
||||
assert( pTabItem->pTab );
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
if( IsVirtual(pTabItem->pTab) ){
|
||||
sqlite3_index_info **pp = &pWInfo->a[j].pIdxInfo;
|
||||
bestVirtualIndex(pParse, pWC, pTabItem, mask, pOrderBy, &sCost, pp);
|
||||
bestVirtualIndex(pParse, pWC, pTabItem, mask, notReady, pOrderBy,
|
||||
&sCost, pp);
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
bestBtreeIndex(pParse, pWC, pTabItem, mask, pOrderBy, &sCost);
|
||||
bestBtreeIndex(pParse, pWC, pTabItem, mask, notReady, pOrderBy,
|
||||
&sCost);
|
||||
}
|
||||
assert( isOptimal || (sCost.used¬Ready)==0 );
|
||||
|
||||
@ -4175,10 +4341,12 @@ WhereInfo *sqlite3WhereBegin(
|
||||
&& (nUnconstrained==0 || pTabItem->pIndex==0 /* (3) */
|
||||
|| NEVER((sCost.plan.wsFlags & WHERE_NOT_FULLSCAN)!=0))
|
||||
&& (bestJ<0 || sCost.rCost<bestPlan.rCost /* (4) */
|
||||
|| (sCost.rCost<=bestPlan.rCost && sCost.nRow<bestPlan.nRow))
|
||||
|| (sCost.rCost<=bestPlan.rCost
|
||||
&& sCost.plan.nRow<bestPlan.plan.nRow))
|
||||
){
|
||||
WHERETRACE(("... best so far with cost=%g and nRow=%g\n",
|
||||
sCost.rCost, sCost.nRow));
|
||||
WHERETRACE(("=== table %d is best so far"
|
||||
" with cost=%g and nRow=%g\n",
|
||||
j, sCost.rCost, sCost.plan.nRow));
|
||||
bestPlan = sCost;
|
||||
bestJ = j;
|
||||
}
|
||||
@ -4187,8 +4355,9 @@ WhereInfo *sqlite3WhereBegin(
|
||||
}
|
||||
assert( bestJ>=0 );
|
||||
assert( notReady & getMask(pMaskSet, pTabList->a[bestJ].iCursor) );
|
||||
WHERETRACE(("*** Optimizer selects table %d for loop %d\n", bestJ,
|
||||
pLevel-pWInfo->a));
|
||||
WHERETRACE(("*** Optimizer selects table %d for loop %d"
|
||||
" with cost=%g and nRow=%g\n",
|
||||
bestJ, pLevel-pWInfo->a, bestPlan.rCost, bestPlan.plan.nRow));
|
||||
if( (bestPlan.plan.wsFlags & WHERE_ORDERBY)!=0 ){
|
||||
*ppOrderBy = 0;
|
||||
}
|
||||
@ -4203,7 +4372,9 @@ WhereInfo *sqlite3WhereBegin(
|
||||
}
|
||||
notReady &= ~getMask(pMaskSet, pTabList->a[bestJ].iCursor);
|
||||
pLevel->iFrom = (u8)bestJ;
|
||||
if( bestPlan.nRow>=(double)1 ) pParse->nQueryLoop *= bestPlan.nRow;
|
||||
if( bestPlan.plan.nRow>=(double)1 ){
|
||||
pParse->nQueryLoop *= bestPlan.plan.nRow;
|
||||
}
|
||||
|
||||
/* Check that if the table scanned by this loop iteration had an
|
||||
** INDEXED BY clause attached to it, that the named index is being
|
||||
@ -4251,44 +4422,15 @@ WhereInfo *sqlite3WhereBegin(
|
||||
*/
|
||||
sqlite3CodeVerifySchema(pParse, -1); /* Insert the cookie verifier Goto */
|
||||
notReady = ~(Bitmask)0;
|
||||
pWInfo->nRowOut = (double)1;
|
||||
for(i=0, pLevel=pWInfo->a; i<nTabList; i++, pLevel++){
|
||||
Table *pTab; /* Table to open */
|
||||
int iDb; /* Index of database containing table/index */
|
||||
|
||||
#ifndef SQLITE_OMIT_EXPLAIN
|
||||
if( pParse->explain==2 ){
|
||||
char *zMsg;
|
||||
struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom];
|
||||
zMsg = sqlite3MPrintf(db, "TABLE %s", pItem->zName);
|
||||
if( pItem->zAlias ){
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s AS %s", zMsg, pItem->zAlias);
|
||||
}
|
||||
if( (pLevel->plan.wsFlags & WHERE_TEMP_INDEX)!=0 ){
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s WITH AUTOMATIC INDEX", zMsg);
|
||||
}else if( (pLevel->plan.wsFlags & WHERE_INDEXED)!=0 ){
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s WITH INDEX %s",
|
||||
zMsg, pLevel->plan.u.pIdx->zName);
|
||||
}else if( pLevel->plan.wsFlags & WHERE_MULTI_OR ){
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s VIA MULTI-INDEX UNION", zMsg);
|
||||
}else if( pLevel->plan.wsFlags & (WHERE_ROWID_EQ|WHERE_ROWID_RANGE) ){
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s USING PRIMARY KEY", zMsg);
|
||||
}
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
else if( (pLevel->plan.wsFlags & WHERE_VIRTUALTABLE)!=0 ){
|
||||
sqlite3_index_info *pVtabIdx = pLevel->plan.u.pVtabIdx;
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s VIRTUAL TABLE INDEX %d:%s", zMsg,
|
||||
pVtabIdx->idxNum, pVtabIdx->idxStr);
|
||||
}
|
||||
#endif
|
||||
if( pLevel->plan.wsFlags & WHERE_ORDERBY ){
|
||||
zMsg = sqlite3MAppendf(db, zMsg, "%s ORDER BY", zMsg);
|
||||
}
|
||||
sqlite3VdbeAddOp4(v, OP_Explain, i, pLevel->iFrom, 0, zMsg, P4_DYNAMIC);
|
||||
}
|
||||
#endif /* SQLITE_OMIT_EXPLAIN */
|
||||
pTabItem = &pTabList->a[pLevel->iFrom];
|
||||
pTab = pTabItem->pTab;
|
||||
pLevel->iTabCur = pTabItem->iCursor;
|
||||
pWInfo->nRowOut *= pLevel->plan.nRow;
|
||||
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
|
||||
if( (pTab->tabFlags & TF_Ephemeral)!=0 || pTab->pSelect ){
|
||||
/* Do nothing */
|
||||
@ -4344,8 +4486,10 @@ WhereInfo *sqlite3WhereBegin(
|
||||
*/
|
||||
notReady = ~(Bitmask)0;
|
||||
for(i=0; i<nTabList; i++){
|
||||
pLevel = &pWInfo->a[i];
|
||||
explainOneScan(pParse, pTabList, pLevel, i, pLevel->iFrom, wctrlFlags);
|
||||
notReady = codeOneLoopStart(pWInfo, i, wctrlFlags, notReady);
|
||||
pWInfo->iContinue = pWInfo->a[i].addrCont;
|
||||
pWInfo->iContinue = pLevel->addrCont;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_TEST /* For testing and debugging use only */
|
||||
|
||||
@ -16,6 +16,7 @@ source $testdir/permutations.test
|
||||
|
||||
run_test_suite full
|
||||
|
||||
run_test_suite no_optimization
|
||||
run_test_suite memsubsys1
|
||||
run_test_suite memsubsys2
|
||||
run_test_suite singlethread
|
||||
|
||||
@ -27,11 +27,6 @@ ifcapable {!pragma} return
|
||||
#
|
||||
do_not_use_codec
|
||||
|
||||
# These tests do not work if there is a codec.
|
||||
#
|
||||
#if {[catch {sqlite3 -has_codec} r] || $r} return
|
||||
#
|
||||
|
||||
# The file format change affects the way row-records stored in tables (but
|
||||
# not indices) are interpreted. Before version 3.1.3, a row-record for a
|
||||
# table with N columns was guaranteed to contain exactly N fields. As
|
||||
|
||||
@ -28,7 +28,7 @@ ifcapable !altertable {
|
||||
|
||||
# Determine if there is a codec available on this test.
|
||||
#
|
||||
if {[catch {sqlite3 -has_codec} r] || $r} {
|
||||
if {[catch {sqlite3 -has-codec} r] || $r} {
|
||||
set has_codec 1
|
||||
} else {
|
||||
set has_codec 0
|
||||
@ -55,6 +55,7 @@ proc get_file_format {{fname test.db}} {
|
||||
|
||||
do_test alter3-1.1 {
|
||||
execsql {
|
||||
PRAGMA legacy_file_format=ON;
|
||||
CREATE TABLE abc(a, b, c);
|
||||
SELECT sql FROM sqlite_master;
|
||||
}
|
||||
@ -198,6 +199,7 @@ do_test alter3-4.1 {
|
||||
file delete -force test.db
|
||||
set ::DB [sqlite3 db test.db]
|
||||
execsql {
|
||||
PRAGMA legacy_file_format=ON;
|
||||
CREATE TABLE t1(a, b);
|
||||
INSERT INTO t1 VALUES(1, 100);
|
||||
INSERT INTO t1 VALUES(2, 300);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user