Merge branch 'sqlite-release' into release-integration
Conflicts: 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,8 +173,9 @@ 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 \
|
||||
fts3.lo fts3_expr.lo fts3_hash.lo fts3_icu.lo fts3_porter.lo \
|
||||
callback.lo complete.lo ctime.lo date.lo delete.lo \
|
||||
expr.lo fault.lo fkey.lo \
|
||||
fts3.lo fts3_aux.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 \
|
||||
icu.lo insert.lo journal.lo legacy.lo loadext.lo \
|
||||
@@ -190,8 +187,8 @@ LIBOBJS0 = alter.lo analyze.lo attach.lo auth.lo \
|
||||
random.lo resolve.lo rowset.lo rtree.lo select.lo status.lo \
|
||||
table.lo tokenize.lo trigger.lo \
|
||||
update.lo util.lo vacuum.lo \
|
||||
vdbe.lo vdbeapi.lo vdbeaux.lo vdbeblob.lo vdbemem.lo vdbetrace.lo \
|
||||
wal.lo walker.lo where.lo utf.lo vtab.lo $(CRYPTOLIBOBJ)
|
||||
vdbe.lo vdbeapi.lo vdbeaux.lo vdbeblob.lo vdbemem.lo vdbesort.lo \
|
||||
vdbetrace.lo wal.lo walker.lo where.lo utf.lo vtab.lo $(CRYPTOLIBOBJ)
|
||||
|
||||
# Object files for the amalgamation.
|
||||
#
|
||||
@@ -288,6 +285,7 @@ SRC = \
|
||||
$(TOP)/src/vdbeaux.c \
|
||||
$(TOP)/src/vdbeblob.c \
|
||||
$(TOP)/src/vdbemem.c \
|
||||
$(TOP)/src/vdbesort.c \
|
||||
$(TOP)/src/vdbetrace.c \
|
||||
$(TOP)/src/vdbeInt.h \
|
||||
$(TOP)/src/vtab.c \
|
||||
@@ -320,6 +318,7 @@ SRC += \
|
||||
$(TOP)/ext/fts3/fts3.c \
|
||||
$(TOP)/ext/fts3/fts3.h \
|
||||
$(TOP)/ext/fts3/fts3Int.h \
|
||||
$(TOP)/ext/fts3/fts3_aux.c \
|
||||
$(TOP)/ext/fts3/fts3_expr.c \
|
||||
$(TOP)/ext/fts3/fts3_hash.c \
|
||||
$(TOP)/ext/fts3/fts3_hash.h \
|
||||
@@ -369,22 +368,31 @@ TESTSRC = \
|
||||
$(TOP)/src/test_demovfs.c \
|
||||
$(TOP)/src/test_devsym.c \
|
||||
$(TOP)/src/test_func.c \
|
||||
$(TOP)/src/test_fuzzer.c \
|
||||
$(TOP)/src/test_hexio.c \
|
||||
$(TOP)/src/test_init.c \
|
||||
$(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_syscall.c \
|
||||
$(TOP)/src/test_stat.c \
|
||||
$(TOP)/src/test_tclvar.c \
|
||||
$(TOP)/src/test_thread.c \
|
||||
$(TOP)/src/test_vfs.c \
|
||||
$(TOP)/src/test_wsd.c
|
||||
$(TOP)/src/test_wholenumber.c \
|
||||
$(TOP)/src/test_wsd.c \
|
||||
$(TOP)/ext/fts3/fts3_term.c \
|
||||
$(TOP)/ext/fts3/fts3_test.c
|
||||
|
||||
# Source code to the library files needed by the test fixture
|
||||
#
|
||||
@@ -424,7 +432,9 @@ TESTSRC2 = \
|
||||
$(TOP)/src/where.c \
|
||||
parse.c \
|
||||
$(TOP)/ext/fts3/fts3.c \
|
||||
$(TOP)/ext/fts3/fts3_aux.c \
|
||||
$(TOP)/ext/fts3/fts3_expr.c \
|
||||
$(TOP)/ext/fts3/fts3_term.c \
|
||||
$(TOP)/ext/fts3/fts3_tokenizer.c \
|
||||
$(TOP)/ext/fts3/fts3_write.c \
|
||||
$(TOP)/ext/async/sqlite3async.c
|
||||
@@ -471,6 +481,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.
|
||||
@@ -517,6 +529,9 @@ sqlite3$(TEXE): $(TOP)/src/shell.c libsqlite3.la sqlite3.h
|
||||
sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl
|
||||
$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl
|
||||
|
||||
sqlite3-all.c: sqlite3.c $(TOP)/tool/split-sqlite3c.tcl
|
||||
$(TCLSH_CMD) $(TOP)/tool/split-sqlite3c.tcl
|
||||
|
||||
# Rule to build the amalgamation
|
||||
#
|
||||
sqlite3.lo: sqlite3.c
|
||||
@@ -742,6 +757,9 @@ vdbeblob.lo: $(TOP)/src/vdbeblob.c $(HDR)
|
||||
vdbemem.lo: $(TOP)/src/vdbemem.c $(HDR)
|
||||
$(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/vdbemem.c
|
||||
|
||||
vdbesort.lo: $(TOP)/src/vdbesort.c $(HDR)
|
||||
$(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/vdbesort.c
|
||||
|
||||
vdbetrace.lo: $(TOP)/src/vdbetrace.c $(HDR)
|
||||
$(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/vdbetrace.c
|
||||
|
||||
@@ -773,7 +791,7 @@ tclsqlite3$(TEXE): tclsqlite-shell.lo libsqlite3.la
|
||||
# Rules to build opcodes.c and opcodes.h
|
||||
#
|
||||
opcodes.c: opcodes.h $(TOP)/mkopcodec.awk
|
||||
sort -n -b -k 3 opcodes.h | $(NAWK) -f $(TOP)/mkopcodec.awk >opcodes.c
|
||||
$(NAWK) -f $(TOP)/mkopcodec.awk opcodes.h >opcodes.c
|
||||
|
||||
opcodes.h: parse.h $(TOP)/src/vdbe.c $(TOP)/mkopcodeh.awk
|
||||
cat parse.h $(TOP)/src/vdbe.c | $(NAWK) -f $(TOP)/mkopcodeh.awk >opcodes.h
|
||||
@@ -790,7 +808,7 @@ parse.c: $(TOP)/src/parse.y lemon$(BEXE) $(TOP)/addopcodes.awk
|
||||
$(NAWK) -f $(TOP)/addopcodes.awk parse.h.temp >parse.h
|
||||
|
||||
sqlite3.h: $(TOP)/src/sqlite.h.in $(TOP)/manifest.uuid $(TOP)/VERSION
|
||||
tclsh $(TOP)/tool/mksqlite3h.tcl $(TOP) >sqlite3.h
|
||||
$(TCLSH_CMD) $(TOP)/tool/mksqlite3h.tcl $(TOP) >sqlite3.h
|
||||
|
||||
keywordhash.h: $(TOP)/tool/mkkeywordhash.c
|
||||
$(BCC) -o mkkeywordhash$(BEXE) $(OPT_FEATURE_FLAGS) $(OPTS) $(TOP)/tool/mkkeywordhash.c
|
||||
@@ -824,6 +842,9 @@ fts2_tokenizer1.lo: $(TOP)/ext/fts2/fts2_tokenizer1.c $(HDR) $(EXTHDR)
|
||||
fts3.lo: $(TOP)/ext/fts3/fts3.c $(HDR) $(EXTHDR)
|
||||
$(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3.c
|
||||
|
||||
fts3_aux.lo: $(TOP)/ext/fts3/fts3_aux.c $(HDR) $(EXTHDR)
|
||||
$(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_aux.c
|
||||
|
||||
fts3_expr.lo: $(TOP)/ext/fts3/fts3_expr.c $(HDR) $(EXTHDR)
|
||||
$(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_expr.c
|
||||
|
||||
@@ -833,12 +854,12 @@ fts3_hash.lo: $(TOP)/ext/fts3/fts3_hash.c $(HDR) $(EXTHDR)
|
||||
fts3_icu.lo: $(TOP)/ext/fts3/fts3_icu.c $(HDR) $(EXTHDR)
|
||||
$(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_icu.c
|
||||
|
||||
fts3_snippet.lo: $(TOP)/ext/fts3/fts3_snippet.c $(HDR) $(EXTHDR)
|
||||
$(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_snippet.c
|
||||
|
||||
fts3_porter.lo: $(TOP)/ext/fts3/fts3_porter.c $(HDR) $(EXTHDR)
|
||||
$(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_porter.c
|
||||
|
||||
fts3_snippet.lo: $(TOP)/ext/fts3/fts3_snippet.c $(HDR) $(EXTHDR)
|
||||
$(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_snippet.c
|
||||
|
||||
fts3_tokenizer.lo: $(TOP)/ext/fts3/fts3_tokenizer.c $(HDR) $(EXTHDR)
|
||||
$(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_tokenizer.c
|
||||
|
||||
@@ -861,6 +882,7 @@ rtree.lo: $(TOP)/ext/rtree/rtree.c $(HDR) $(EXTHDR)
|
||||
#
|
||||
TESTFIXTURE_FLAGS = -DTCLSH=1 -DSQLITE_TEST=1 -DSQLITE_CRASH_TEST=1
|
||||
TESTFIXTURE_FLAGS += -DSQLITE_SERVER=1 -DSQLITE_PRIVATE="" -DSQLITE_CORE
|
||||
TESTFIXTURE_FLAGS += -DBUILD_sqlite
|
||||
|
||||
TESTFIXTURE_SRC0 = $(TESTSRC2) libsqlite3.la
|
||||
TESTFIXTURE_SRC1 = sqlite3.c
|
||||
@@ -880,18 +902,16 @@ soaktest: testfixture$(TEXE) sqlite3$(TEXE)
|
||||
test: testfixture$(TEXE) sqlite3$(TEXE)
|
||||
./testfixture$(TEXE) $(TOP)/test/veryquick.test
|
||||
|
||||
sqlite3_analyzer$(TEXE): $(TESTFIXTURE_SRC) $(TOP)/tool/spaceanal.tcl
|
||||
sed \
|
||||
-e '/^#/d' \
|
||||
-e 's,\\,\\\\,g' \
|
||||
-e 's,",\\",g' \
|
||||
-e 's,^,",' \
|
||||
-e 's,$$,\\n",' \
|
||||
$(TOP)/tool/spaceanal.tcl >spaceanal_tcl.h
|
||||
$(LTLINK) -DTCLSH=2 -DSQLITE_TEST=1 -DSQLITE_CRASH_TEST=1 \
|
||||
-DSQLITE_SERVER=1 -DSQLITE_PRIVATE="" -DSQLITE_CORE \
|
||||
$(TEMP_STORE) -o $@ $(TESTFIXTURE_SRC) $(LIBTCL) $(TLIBS)
|
||||
sqlite3_analyzer.c: sqlite3.c $(TOP)/src/test_stat.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl
|
||||
echo "#define TCLSH 2" > $@
|
||||
cat sqlite3.c $(TOP)/src/test_stat.c $(TOP)/src/tclsqlite.c >> $@
|
||||
echo "static const char *tclsh_main_loop(void){" >> $@
|
||||
echo "static const char *zMainloop = " >> $@
|
||||
$(NAWK) -f $(TOP)/tool/tostr.awk $(TOP)/tool/spaceanal.tcl >> $@
|
||||
echo "; return zMainloop; }" >> $@
|
||||
|
||||
sqlite3_analyzer$(TEXE): sqlite3_analyzer.c
|
||||
$(LTLINK) sqlite3_analyzer.c -o $@ $(LIBTCL) $(TLIBS)
|
||||
|
||||
# Standard install and cleanup targets
|
||||
#
|
||||
@@ -925,9 +945,11 @@ clean:
|
||||
rm -f $(PUBLISH)
|
||||
rm -f *.da *.bb *.bbg gmon.out
|
||||
rm -rf tsrc .target_source
|
||||
rm -f tclsqlite3$(TEXE)
|
||||
rm -f testfixture$(TEXE) test.db
|
||||
rm -f sqlite3.dll sqlite3.lib sqlite3.def
|
||||
rm -f sqlite3.dll sqlite3.lib sqlite3.exp sqlite3.def
|
||||
rm -f sqlite3.c
|
||||
rm -f sqlite3_analyzer$(TEXE) sqlite3_analyzer.c
|
||||
|
||||
distclean: clean
|
||||
rm -f config.log config.status libtool Makefile sqlite3.pc
|
||||
|
||||
@@ -517,7 +517,7 @@ tclsqlite.o: $(TOP)/src/tclsqlite.c $(HDR)
|
||||
# Rules to build opcodes.c and opcodes.h
|
||||
#
|
||||
opcodes.c: opcodes.h $(TOP)/mkopcodec.awk
|
||||
sort -n -b -k 3 opcodes.h | $(NAWK) -f $(TOP)/mkopcodec.awk >opcodes.c
|
||||
$(NAWK) -f $(TOP)/mkopcodec.awk opcodes.h >opcodes.c
|
||||
|
||||
opcodes.h: parse.h $(TOP)/src/vdbe.c $(TOP)/mkopcodeh.awk
|
||||
cat parse.h $(TOP)/src/vdbe.c | \
|
||||
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
@@ -75,6 +75,9 @@
|
||||
/* Define to 1 if you have the `usleep' function. */
|
||||
#undef HAVE_USLEEP
|
||||
|
||||
/* Define to 1 if you have the utime() library function. */
|
||||
#undef HAVE_UTIME
|
||||
|
||||
/* Define to the sub-directory in which libtool stores uninstalled libraries.
|
||||
*/
|
||||
#undef LT_OBJDIR
|
||||
|
||||
@@ -127,7 +127,7 @@ AC_CHECK_HEADERS([sys/types.h stdlib.h stdint.h inttypes.h])
|
||||
#########
|
||||
# Figure out whether or not we have these functions
|
||||
#
|
||||
AC_CHECK_FUNCS([usleep fdatasync localtime_r gmtime_r localtime_s])
|
||||
AC_CHECK_FUNCS([usleep fdatasync localtime_r gmtime_r localtime_s utime])
|
||||
|
||||
#########
|
||||
# By default, we use the amalgamation (this may be changed below...)
|
||||
@@ -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
|
||||
#
|
||||
@@ -637,6 +619,7 @@ AC_ARG_ENABLE(load-extension, AC_HELP_STRING([--enable-load-extension],
|
||||
[use_loadextension=$enableval],[use_loadextension=no])
|
||||
if test "${use_loadextension}" = "yes" ; then
|
||||
OPT_FEATURE_FLAGS=""
|
||||
AC_SEARCH_LIBS(dlopen, dl)
|
||||
else
|
||||
OPT_FEATURE_FLAGS="-DSQLITE_OMIT_LOAD_EXTENSION=1"
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,679 @@
|
||||
# A Tk console widget for SQLite. Invoke sqlitecon::create with a window name,
|
||||
# a prompt string, a title to set a new top-level window, and the SQLite
|
||||
# database handle. For example:
|
||||
#
|
||||
# sqlitecon::create .sqlcon {sql:- } {SQL Console} db
|
||||
#
|
||||
# A toplevel window is created that allows you to type in SQL commands to
|
||||
# be processed on the spot.
|
||||
#
|
||||
# A limited set of dot-commands are supported:
|
||||
#
|
||||
# .table
|
||||
# .schema ?TABLE?
|
||||
# .mode list|column|multicolumn|line
|
||||
# .exit
|
||||
#
|
||||
# In addition, a new SQL function named "edit()" is created. This function
|
||||
# takes a single text argument and returns a text result. Whenever the
|
||||
# the function is called, it pops up a new toplevel window containing a
|
||||
# text editor screen initialized to the argument. When the "OK" button
|
||||
# is pressed, whatever revised text is in the text editor is returned as
|
||||
# the result of the edit() function. This allows text fields of SQL tables
|
||||
# to be edited quickly and easily as follows:
|
||||
#
|
||||
# UPDATE table1 SET dscr = edit(dscr) WHERE rowid=15;
|
||||
#
|
||||
|
||||
|
||||
# Create a namespace to work in
|
||||
#
|
||||
namespace eval ::sqlitecon {
|
||||
# do nothing
|
||||
}
|
||||
|
||||
# Create a console widget named $w. The prompt string is $prompt.
|
||||
# The title at the top of the window is $title. The database connection
|
||||
# object is $db
|
||||
#
|
||||
proc sqlitecon::create {w prompt title db} {
|
||||
upvar #0 $w.t v
|
||||
if {[winfo exists $w]} {destroy $w}
|
||||
if {[info exists v]} {unset v}
|
||||
toplevel $w
|
||||
wm title $w $title
|
||||
wm iconname $w $title
|
||||
frame $w.mb -bd 2 -relief raised
|
||||
pack $w.mb -side top -fill x
|
||||
menubutton $w.mb.file -text File -menu $w.mb.file.m
|
||||
menubutton $w.mb.edit -text Edit -menu $w.mb.edit.m
|
||||
pack $w.mb.file $w.mb.edit -side left -padx 8 -pady 1
|
||||
set m [menu $w.mb.file.m -tearoff 0]
|
||||
$m add command -label {Close} -command "destroy $w"
|
||||
sqlitecon::create_child $w $prompt $w.mb.edit.m
|
||||
set v(db) $db
|
||||
$db function edit ::sqlitecon::_edit
|
||||
}
|
||||
|
||||
# This routine creates a console as a child window within a larger
|
||||
# window. It also creates an edit menu named "$editmenu" if $editmenu!="".
|
||||
# The calling function is responsible for posting the edit menu.
|
||||
#
|
||||
proc sqlitecon::create_child {w prompt editmenu} {
|
||||
upvar #0 $w.t v
|
||||
if {$editmenu!=""} {
|
||||
set m [menu $editmenu -tearoff 0]
|
||||
$m add command -label Cut -command "sqlitecon::Cut $w.t"
|
||||
$m add command -label Copy -command "sqlitecon::Copy $w.t"
|
||||
$m add command -label Paste -command "sqlitecon::Paste $w.t"
|
||||
$m add command -label {Clear Screen} -command "sqlitecon::Clear $w.t"
|
||||
$m add separator
|
||||
$m add command -label {Save As...} -command "sqlitecon::SaveFile $w.t"
|
||||
catch {$editmenu config -postcommand "sqlitecon::EnableEditMenu $w"}
|
||||
}
|
||||
scrollbar $w.sb -orient vertical -command "$w.t yview"
|
||||
pack $w.sb -side right -fill y
|
||||
text $w.t -font fixed -yscrollcommand "$w.sb set"
|
||||
pack $w.t -side right -fill both -expand 1
|
||||
bindtags $w.t Sqlitecon
|
||||
set v(editmenu) $editmenu
|
||||
set v(history) 0
|
||||
set v(historycnt) 0
|
||||
set v(current) -1
|
||||
set v(prompt) $prompt
|
||||
set v(prior) {}
|
||||
set v(plength) [string length $v(prompt)]
|
||||
set v(x) 0
|
||||
set v(y) 0
|
||||
set v(mode) column
|
||||
set v(header) on
|
||||
$w.t mark set insert end
|
||||
$w.t tag config ok -foreground blue
|
||||
$w.t tag config err -foreground red
|
||||
$w.t insert end $v(prompt)
|
||||
$w.t mark set out 1.0
|
||||
after idle "focus $w.t"
|
||||
}
|
||||
|
||||
bind Sqlitecon <1> {sqlitecon::Button1 %W %x %y}
|
||||
bind Sqlitecon <B1-Motion> {sqlitecon::B1Motion %W %x %y}
|
||||
bind Sqlitecon <B1-Leave> {sqlitecon::B1Leave %W %x %y}
|
||||
bind Sqlitecon <B1-Enter> {sqlitecon::cancelMotor %W}
|
||||
bind Sqlitecon <ButtonRelease-1> {sqlitecon::cancelMotor %W}
|
||||
bind Sqlitecon <KeyPress> {sqlitecon::Insert %W %A}
|
||||
bind Sqlitecon <Left> {sqlitecon::Left %W}
|
||||
bind Sqlitecon <Control-b> {sqlitecon::Left %W}
|
||||
bind Sqlitecon <Right> {sqlitecon::Right %W}
|
||||
bind Sqlitecon <Control-f> {sqlitecon::Right %W}
|
||||
bind Sqlitecon <BackSpace> {sqlitecon::Backspace %W}
|
||||
bind Sqlitecon <Control-h> {sqlitecon::Backspace %W}
|
||||
bind Sqlitecon <Delete> {sqlitecon::Delete %W}
|
||||
bind Sqlitecon <Control-d> {sqlitecon::Delete %W}
|
||||
bind Sqlitecon <Home> {sqlitecon::Home %W}
|
||||
bind Sqlitecon <Control-a> {sqlitecon::Home %W}
|
||||
bind Sqlitecon <End> {sqlitecon::End %W}
|
||||
bind Sqlitecon <Control-e> {sqlitecon::End %W}
|
||||
bind Sqlitecon <Return> {sqlitecon::Enter %W}
|
||||
bind Sqlitecon <KP_Enter> {sqlitecon::Enter %W}
|
||||
bind Sqlitecon <Up> {sqlitecon::Prior %W}
|
||||
bind Sqlitecon <Control-p> {sqlitecon::Prior %W}
|
||||
bind Sqlitecon <Down> {sqlitecon::Next %W}
|
||||
bind Sqlitecon <Control-n> {sqlitecon::Next %W}
|
||||
bind Sqlitecon <Control-k> {sqlitecon::EraseEOL %W}
|
||||
bind Sqlitecon <<Cut>> {sqlitecon::Cut %W}
|
||||
bind Sqlitecon <<Copy>> {sqlitecon::Copy %W}
|
||||
bind Sqlitecon <<Paste>> {sqlitecon::Paste %W}
|
||||
bind Sqlitecon <<Clear>> {sqlitecon::Clear %W}
|
||||
|
||||
# Insert a single character at the insertion cursor
|
||||
#
|
||||
proc sqlitecon::Insert {w a} {
|
||||
$w insert insert $a
|
||||
$w yview insert
|
||||
}
|
||||
|
||||
# Move the cursor one character to the left
|
||||
#
|
||||
proc sqlitecon::Left {w} {
|
||||
upvar #0 $w v
|
||||
scan [$w index insert] %d.%d row col
|
||||
if {$col>$v(plength)} {
|
||||
$w mark set insert "insert -1c"
|
||||
}
|
||||
}
|
||||
|
||||
# Erase the character to the left of the cursor
|
||||
#
|
||||
proc sqlitecon::Backspace {w} {
|
||||
upvar #0 $w v
|
||||
scan [$w index insert] %d.%d row col
|
||||
if {$col>$v(plength)} {
|
||||
$w delete {insert -1c}
|
||||
}
|
||||
}
|
||||
|
||||
# Erase to the end of the line
|
||||
#
|
||||
proc sqlitecon::EraseEOL {w} {
|
||||
upvar #0 $w v
|
||||
scan [$w index insert] %d.%d row col
|
||||
if {$col>=$v(plength)} {
|
||||
$w delete insert {insert lineend}
|
||||
}
|
||||
}
|
||||
|
||||
# Move the cursor one character to the right
|
||||
#
|
||||
proc sqlitecon::Right {w} {
|
||||
$w mark set insert "insert +1c"
|
||||
}
|
||||
|
||||
# Erase the character to the right of the cursor
|
||||
#
|
||||
proc sqlitecon::Delete w {
|
||||
$w delete insert
|
||||
}
|
||||
|
||||
# Move the cursor to the beginning of the current line
|
||||
#
|
||||
proc sqlitecon::Home w {
|
||||
upvar #0 $w v
|
||||
scan [$w index insert] %d.%d row col
|
||||
$w mark set insert $row.$v(plength)
|
||||
}
|
||||
|
||||
# Move the cursor to the end of the current line
|
||||
#
|
||||
proc sqlitecon::End w {
|
||||
$w mark set insert {insert lineend}
|
||||
}
|
||||
|
||||
# Add a line to the history
|
||||
#
|
||||
proc sqlitecon::addHistory {w line} {
|
||||
upvar #0 $w v
|
||||
if {$v(historycnt)>0} {
|
||||
set last [lindex $v(history) [expr $v(historycnt)-1]]
|
||||
if {[string compare $last $line]} {
|
||||
lappend v(history) $line
|
||||
incr v(historycnt)
|
||||
}
|
||||
} else {
|
||||
set v(history) [list $line]
|
||||
set v(historycnt) 1
|
||||
}
|
||||
set v(current) $v(historycnt)
|
||||
}
|
||||
|
||||
# Called when "Enter" is pressed. Do something with the line
|
||||
# of text that was entered.
|
||||
#
|
||||
proc sqlitecon::Enter w {
|
||||
upvar #0 $w v
|
||||
scan [$w index insert] %d.%d row col
|
||||
set start $row.$v(plength)
|
||||
set line [$w get $start "$start lineend"]
|
||||
$w insert end \n
|
||||
$w mark set out end
|
||||
if {$v(prior)==""} {
|
||||
set cmd $line
|
||||
} else {
|
||||
set cmd $v(prior)\n$line
|
||||
}
|
||||
if {[string index $cmd 0]=="." || [$v(db) complete $cmd]} {
|
||||
regsub -all {\n} [string trim $cmd] { } cmd2
|
||||
addHistory $w $cmd2
|
||||
set rc [catch {DoCommand $w $cmd} res]
|
||||
if {![winfo exists $w]} return
|
||||
if {$rc} {
|
||||
$w insert end $res\n err
|
||||
} elseif {[string length $res]>0} {
|
||||
$w insert end $res\n ok
|
||||
}
|
||||
set v(prior) {}
|
||||
$w insert end $v(prompt)
|
||||
} else {
|
||||
set v(prior) $cmd
|
||||
regsub -all {[^ ]} $v(prompt) . x
|
||||
$w insert end $x
|
||||
}
|
||||
$w mark set insert end
|
||||
$w mark set out {insert linestart}
|
||||
$w yview insert
|
||||
}
|
||||
|
||||
# Execute a single SQL command. Pay special attention to control
|
||||
# directives that begin with "."
|
||||
#
|
||||
# The return value is the text output from the command, properly
|
||||
# formatted.
|
||||
#
|
||||
proc sqlitecon::DoCommand {w cmd} {
|
||||
upvar #0 $w v
|
||||
set mode $v(mode)
|
||||
set header $v(header)
|
||||
if {[regexp {^(\.[a-z]+)} $cmd all word]} {
|
||||
if {$word==".mode"} {
|
||||
regexp {^.[a-z]+ +([a-z]+)} $cmd all v(mode)
|
||||
return {}
|
||||
} elseif {$word==".exit"} {
|
||||
destroy [winfo toplevel $w]
|
||||
return {}
|
||||
} elseif {$word==".header"} {
|
||||
regexp {^.[a-z]+ +([a-z]+)} $cmd all v(header)
|
||||
return {}
|
||||
} elseif {$word==".tables"} {
|
||||
set mode multicolumn
|
||||
set cmd {SELECT name FROM sqlite_master WHERE type='table'
|
||||
UNION ALL
|
||||
SELECT name FROM sqlite_temp_master WHERE type='table'}
|
||||
$v(db) eval {PRAGMA database_list} {
|
||||
if {$name!="temp" && $name!="main"} {
|
||||
append cmd "UNION ALL SELECT name FROM $name.sqlite_master\
|
||||
WHERE type='table'"
|
||||
}
|
||||
}
|
||||
append cmd { ORDER BY 1}
|
||||
} elseif {$word==".fullschema"} {
|
||||
set pattern %
|
||||
regexp {^.[a-z]+ +([^ ]+)} $cmd all pattern
|
||||
set mode list
|
||||
set header 0
|
||||
set cmd "SELECT sql FROM sqlite_master WHERE tbl_name LIKE '$pattern'
|
||||
AND sql NOT NULL UNION ALL SELECT sql FROM sqlite_temp_master
|
||||
WHERE tbl_name LIKE '$pattern' AND sql NOT NULL"
|
||||
$v(db) eval {PRAGMA database_list} {
|
||||
if {$name!="temp" && $name!="main"} {
|
||||
append cmd " UNION ALL SELECT sql FROM $name.sqlite_master\
|
||||
WHERE tbl_name LIKE '$pattern' AND sql NOT NULL"
|
||||
}
|
||||
}
|
||||
} elseif {$word==".schema"} {
|
||||
set pattern %
|
||||
regexp {^.[a-z]+ +([^ ]+)} $cmd all pattern
|
||||
set mode list
|
||||
set header 0
|
||||
set cmd "SELECT sql FROM sqlite_master WHERE name LIKE '$pattern'
|
||||
AND sql NOT NULL UNION ALL SELECT sql FROM sqlite_temp_master
|
||||
WHERE name LIKE '$pattern' AND sql NOT NULL"
|
||||
$v(db) eval {PRAGMA database_list} {
|
||||
if {$name!="temp" && $name!="main"} {
|
||||
append cmd " UNION ALL SELECT sql FROM $name.sqlite_master\
|
||||
WHERE name LIKE '$pattern' AND sql NOT NULL"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return \
|
||||
".exit\n.mode line|list|column\n.schema ?TABLENAME?\n.tables"
|
||||
}
|
||||
}
|
||||
set res {}
|
||||
if {$mode=="list"} {
|
||||
$v(db) eval $cmd x {
|
||||
set sep {}
|
||||
foreach col $x(*) {
|
||||
append res $sep$x($col)
|
||||
set sep |
|
||||
}
|
||||
append res \n
|
||||
}
|
||||
if {[info exists x(*)] && $header} {
|
||||
set sep {}
|
||||
set hdr {}
|
||||
foreach col $x(*) {
|
||||
append hdr $sep$col
|
||||
set sep |
|
||||
}
|
||||
set res $hdr\n$res
|
||||
}
|
||||
} elseif {[string range $mode 0 2]=="col"} {
|
||||
set y {}
|
||||
$v(db) eval $cmd x {
|
||||
foreach col $x(*) {
|
||||
if {![info exists cw($col)] || $cw($col)<[string length $x($col)]} {
|
||||
set cw($col) [string length $x($col)]
|
||||
}
|
||||
lappend y $x($col)
|
||||
}
|
||||
}
|
||||
if {[info exists x(*)] && $header} {
|
||||
set hdr {}
|
||||
set ln {}
|
||||
set dash ---------------------------------------------------------------
|
||||
append dash ------------------------------------------------------------
|
||||
foreach col $x(*) {
|
||||
if {![info exists cw($col)] || $cw($col)<[string length $col]} {
|
||||
set cw($col) [string length $col]
|
||||
}
|
||||
lappend hdr $col
|
||||
lappend ln [string range $dash 1 $cw($col)]
|
||||
}
|
||||
set y [concat $hdr $ln $y]
|
||||
}
|
||||
if {[info exists x(*)]} {
|
||||
set format {}
|
||||
set arglist {}
|
||||
set arglist2 {}
|
||||
set i 0
|
||||
foreach col $x(*) {
|
||||
lappend arglist x$i
|
||||
append arglist2 " \$x$i"
|
||||
incr i
|
||||
append format " %-$cw($col)s"
|
||||
}
|
||||
set format [string trimleft $format]\n
|
||||
if {[llength $arglist]>0} {
|
||||
foreach $arglist $y "append res \[format [list $format] $arglist2\]"
|
||||
}
|
||||
}
|
||||
} elseif {$mode=="multicolumn"} {
|
||||
set y [$v(db) eval $cmd]
|
||||
set max 0
|
||||
foreach e $y {
|
||||
if {$max<[string length $e]} {set max [string length $e]}
|
||||
}
|
||||
set ncol [expr {int(80/($max+2))}]
|
||||
if {$ncol<1} {set ncol 1}
|
||||
set nelem [llength $y]
|
||||
set nrow [expr {($nelem+$ncol-1)/$ncol}]
|
||||
set format "%-${max}s"
|
||||
for {set i 0} {$i<$nrow} {incr i} {
|
||||
set j $i
|
||||
while 1 {
|
||||
append res [format $format [lindex $y $j]]
|
||||
incr j $nrow
|
||||
if {$j>=$nelem} break
|
||||
append res { }
|
||||
}
|
||||
append res \n
|
||||
}
|
||||
} else {
|
||||
$v(db) eval $cmd x {
|
||||
foreach col $x(*) {append res "$col = $x($col)\n"}
|
||||
append res \n
|
||||
}
|
||||
}
|
||||
return [string trimright $res]
|
||||
}
|
||||
|
||||
# Change the line to the previous line
|
||||
#
|
||||
proc sqlitecon::Prior w {
|
||||
upvar #0 $w v
|
||||
if {$v(current)<=0} return
|
||||
incr v(current) -1
|
||||
set line [lindex $v(history) $v(current)]
|
||||
sqlitecon::SetLine $w $line
|
||||
}
|
||||
|
||||
# Change the line to the next line
|
||||
#
|
||||
proc sqlitecon::Next w {
|
||||
upvar #0 $w v
|
||||
if {$v(current)>=$v(historycnt)} return
|
||||
incr v(current) 1
|
||||
set line [lindex $v(history) $v(current)]
|
||||
sqlitecon::SetLine $w $line
|
||||
}
|
||||
|
||||
# Change the contents of the entry line
|
||||
#
|
||||
proc sqlitecon::SetLine {w line} {
|
||||
upvar #0 $w v
|
||||
scan [$w index insert] %d.%d row col
|
||||
set start $row.$v(plength)
|
||||
$w delete $start end
|
||||
$w insert end $line
|
||||
$w mark set insert end
|
||||
$w yview insert
|
||||
}
|
||||
|
||||
# Called when the mouse button is pressed at position $x,$y on
|
||||
# the console widget.
|
||||
#
|
||||
proc sqlitecon::Button1 {w x y} {
|
||||
global tkPriv
|
||||
upvar #0 $w v
|
||||
set v(mouseMoved) 0
|
||||
set v(pressX) $x
|
||||
set p [sqlitecon::nearestBoundry $w $x $y]
|
||||
scan [$w index insert] %d.%d ix iy
|
||||
scan $p %d.%d px py
|
||||
if {$px==$ix} {
|
||||
$w mark set insert $p
|
||||
}
|
||||
$w mark set anchor $p
|
||||
focus $w
|
||||
}
|
||||
|
||||
# Find the boundry between characters that is nearest
|
||||
# to $x,$y
|
||||
#
|
||||
proc sqlitecon::nearestBoundry {w x y} {
|
||||
set p [$w index @$x,$y]
|
||||
set bb [$w bbox $p]
|
||||
if {![string compare $bb ""]} {return $p}
|
||||
if {($x-[lindex $bb 0])<([lindex $bb 2]/2)} {return $p}
|
||||
$w index "$p + 1 char"
|
||||
}
|
||||
|
||||
# This routine extends the selection to the point specified by $x,$y
|
||||
#
|
||||
proc sqlitecon::SelectTo {w x y} {
|
||||
upvar #0 $w v
|
||||
set cur [sqlitecon::nearestBoundry $w $x $y]
|
||||
if {[catch {$w index anchor}]} {
|
||||
$w mark set anchor $cur
|
||||
}
|
||||
set anchor [$w index anchor]
|
||||
if {[$w compare $cur != $anchor] || (abs($v(pressX) - $x) >= 3)} {
|
||||
if {$v(mouseMoved)==0} {
|
||||
$w tag remove sel 0.0 end
|
||||
}
|
||||
set v(mouseMoved) 1
|
||||
}
|
||||
if {[$w compare $cur < anchor]} {
|
||||
set first $cur
|
||||
set last anchor
|
||||
} else {
|
||||
set first anchor
|
||||
set last $cur
|
||||
}
|
||||
if {$v(mouseMoved)} {
|
||||
$w tag remove sel 0.0 $first
|
||||
$w tag add sel $first $last
|
||||
$w tag remove sel $last end
|
||||
update idletasks
|
||||
}
|
||||
}
|
||||
|
||||
# Called whenever the mouse moves while button-1 is held down.
|
||||
#
|
||||
proc sqlitecon::B1Motion {w x y} {
|
||||
upvar #0 $w v
|
||||
set v(y) $y
|
||||
set v(x) $x
|
||||
sqlitecon::SelectTo $w $x $y
|
||||
}
|
||||
|
||||
# Called whenever the mouse leaves the boundries of the widget
|
||||
# while button 1 is held down.
|
||||
#
|
||||
proc sqlitecon::B1Leave {w x y} {
|
||||
upvar #0 $w v
|
||||
set v(y) $y
|
||||
set v(x) $x
|
||||
sqlitecon::motor $w
|
||||
}
|
||||
|
||||
# This routine is called to automatically scroll the window when
|
||||
# the mouse drags offscreen.
|
||||
#
|
||||
proc sqlitecon::motor w {
|
||||
upvar #0 $w v
|
||||
if {![winfo exists $w]} return
|
||||
if {$v(y)>=[winfo height $w]} {
|
||||
$w yview scroll 1 units
|
||||
} elseif {$v(y)<0} {
|
||||
$w yview scroll -1 units
|
||||
} else {
|
||||
return
|
||||
}
|
||||
sqlitecon::SelectTo $w $v(x) $v(y)
|
||||
set v(timer) [after 50 sqlitecon::motor $w]
|
||||
}
|
||||
|
||||
# This routine cancels the scrolling motor if it is active
|
||||
#
|
||||
proc sqlitecon::cancelMotor w {
|
||||
upvar #0 $w v
|
||||
catch {after cancel $v(timer)}
|
||||
catch {unset v(timer)}
|
||||
}
|
||||
|
||||
# Do a Copy operation on the stuff currently selected.
|
||||
#
|
||||
proc sqlitecon::Copy w {
|
||||
if {![catch {set text [$w get sel.first sel.last]}]} {
|
||||
clipboard clear -displayof $w
|
||||
clipboard append -displayof $w $text
|
||||
}
|
||||
}
|
||||
|
||||
# Return 1 if the selection exists and is contained
|
||||
# entirely on the input line. Return 2 if the selection
|
||||
# exists but is not entirely on the input line. Return 0
|
||||
# if the selection does not exist.
|
||||
#
|
||||
proc sqlitecon::canCut w {
|
||||
set r [catch {
|
||||
scan [$w index sel.first] %d.%d s1x s1y
|
||||
scan [$w index sel.last] %d.%d s2x s2y
|
||||
scan [$w index insert] %d.%d ix iy
|
||||
}]
|
||||
if {$r==1} {return 0}
|
||||
if {$s1x==$ix && $s2x==$ix} {return 1}
|
||||
return 2
|
||||
}
|
||||
|
||||
# Do a Cut operation if possible. Cuts are only allowed
|
||||
# if the current selection is entirely contained on the
|
||||
# current input line.
|
||||
#
|
||||
proc sqlitecon::Cut w {
|
||||
if {[sqlitecon::canCut $w]==1} {
|
||||
sqlitecon::Copy $w
|
||||
$w delete sel.first sel.last
|
||||
}
|
||||
}
|
||||
|
||||
# Do a paste opeation.
|
||||
#
|
||||
proc sqlitecon::Paste w {
|
||||
if {[sqlitecon::canCut $w]==1} {
|
||||
$w delete sel.first sel.last
|
||||
}
|
||||
if {[catch {selection get -displayof $w -selection CLIPBOARD} topaste]
|
||||
&& [catch {selection get -displayof $w -selection PRIMARY} topaste]} {
|
||||
return
|
||||
}
|
||||
if {[info exists ::$w]} {
|
||||
set prior 0
|
||||
foreach line [split $topaste \n] {
|
||||
if {$prior} {
|
||||
sqlitecon::Enter $w
|
||||
update
|
||||
}
|
||||
set prior 1
|
||||
$w insert insert $line
|
||||
}
|
||||
} else {
|
||||
$w insert insert $topaste
|
||||
}
|
||||
}
|
||||
|
||||
# Enable or disable entries in the Edit menu
|
||||
#
|
||||
proc sqlitecon::EnableEditMenu w {
|
||||
upvar #0 $w.t v
|
||||
set m $v(editmenu)
|
||||
if {$m=="" || ![winfo exists $m]} return
|
||||
switch [sqlitecon::canCut $w.t] {
|
||||
0 {
|
||||
$m entryconf Copy -state disabled
|
||||
$m entryconf Cut -state disabled
|
||||
}
|
||||
1 {
|
||||
$m entryconf Copy -state normal
|
||||
$m entryconf Cut -state normal
|
||||
}
|
||||
2 {
|
||||
$m entryconf Copy -state normal
|
||||
$m entryconf Cut -state disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Prompt the user for the name of a writable file. Then write the
|
||||
# entire contents of the console screen to that file.
|
||||
#
|
||||
proc sqlitecon::SaveFile w {
|
||||
set types {
|
||||
{{Text Files} {.txt}}
|
||||
{{All Files} *}
|
||||
}
|
||||
set f [tk_getSaveFile -filetypes $types -title "Write Screen To..."]
|
||||
if {$f!=""} {
|
||||
if {[catch {open $f w} fd]} {
|
||||
tk_messageBox -type ok -icon error -message $fd
|
||||
} else {
|
||||
puts $fd [string trimright [$w get 1.0 end] \n]
|
||||
close $fd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Erase everything from the console above the insertion line.
|
||||
#
|
||||
proc sqlitecon::Clear w {
|
||||
$w delete 1.0 {insert linestart}
|
||||
}
|
||||
|
||||
# An in-line editor for SQL
|
||||
#
|
||||
proc sqlitecon::_edit {origtxt {title {}}} {
|
||||
for {set i 0} {[winfo exists .ed$i]} {incr i} continue
|
||||
set w .ed$i
|
||||
toplevel $w
|
||||
wm protocol $w WM_DELETE_WINDOW "$w.b.can invoke"
|
||||
wm title $w {Inline SQL Editor}
|
||||
frame $w.b
|
||||
pack $w.b -side bottom -fill x
|
||||
button $w.b.can -text Cancel -width 6 -command [list set ::$w 0]
|
||||
button $w.b.ok -text OK -width 6 -command [list set ::$w 1]
|
||||
button $w.b.cut -text Cut -width 6 -command [list ::sqlitecon::Cut $w.t]
|
||||
button $w.b.copy -text Copy -width 6 -command [list ::sqlitecon::Copy $w.t]
|
||||
button $w.b.paste -text Paste -width 6 -command [list ::sqlitecon::Paste $w.t]
|
||||
set ::$w {}
|
||||
pack $w.b.cut $w.b.copy $w.b.paste $w.b.can $w.b.ok\
|
||||
-side left -padx 5 -pady 5 -expand 1
|
||||
if {$title!=""} {
|
||||
label $w.title -text $title
|
||||
pack $w.title -side top -padx 5 -pady 5
|
||||
}
|
||||
text $w.t -bg white -fg black -yscrollcommand [list $w.sb set]
|
||||
pack $w.t -side left -fill both -expand 1
|
||||
scrollbar $w.sb -orient vertical -command [list $w.t yview]
|
||||
pack $w.sb -side left -fill y
|
||||
$w.t insert end $origtxt
|
||||
|
||||
vwait ::$w
|
||||
|
||||
if {[set ::$w]} {
|
||||
set txt [string trimright [$w.t get 1.0 end]]
|
||||
} else {
|
||||
set txt $origtxt
|
||||
}
|
||||
destroy $w
|
||||
return $txt
|
||||
}
|
||||
@@ -0,0 +1,892 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>The Lemon Parser Generator</title>
|
||||
</head>
|
||||
<body bgcolor=white>
|
||||
<h1 align=center>The Lemon Parser Generator</h1>
|
||||
|
||||
<p>Lemon is an LALR(1) parser generator for C or C++.
|
||||
It does the same job as ``bison'' and ``yacc''.
|
||||
But lemon is not another bison or yacc clone. It
|
||||
uses a different grammar syntax which is designed to
|
||||
reduce the number of coding errors. Lemon also uses a more
|
||||
sophisticated parsing engine that is faster than yacc and
|
||||
bison and which is both reentrant and thread-safe.
|
||||
Furthermore, Lemon implements features that can be used
|
||||
to eliminate resource leaks, making is suitable for use
|
||||
in long-running programs such as graphical user interfaces
|
||||
or embedded controllers.</p>
|
||||
|
||||
<p>This document is an introduction to the Lemon
|
||||
parser generator.</p>
|
||||
|
||||
<h2>Theory of Operation</h2>
|
||||
|
||||
<p>The main goal of Lemon is to translate a context free grammar (CFG)
|
||||
for a particular language into C code that implements a parser for
|
||||
that language.
|
||||
The program has two inputs:
|
||||
<ul>
|
||||
<li>The grammar specification.
|
||||
<li>A parser template file.
|
||||
</ul>
|
||||
Typically, only the grammar specification is supplied by the programmer.
|
||||
Lemon comes with a default parser template which works fine for most
|
||||
applications. But the user is free to substitute a different parser
|
||||
template if desired.</p>
|
||||
|
||||
<p>Depending on command-line options, Lemon will generate between
|
||||
one and three files of outputs.
|
||||
<ul>
|
||||
<li>C code to implement the parser.
|
||||
<li>A header file defining an integer ID for each terminal symbol.
|
||||
<li>An information file that describes the states of the generated parser
|
||||
automaton.
|
||||
</ul>
|
||||
By default, all three of these output files are generated.
|
||||
The header file is suppressed if the ``-m'' command-line option is
|
||||
used and the report file is omitted when ``-q'' is selected.</p>
|
||||
|
||||
<p>The grammar specification file uses a ``.y'' suffix, by convention.
|
||||
In the examples used in this document, we'll assume the name of the
|
||||
grammar file is ``gram.y''. A typical use of Lemon would be the
|
||||
following command:
|
||||
<pre>
|
||||
lemon gram.y
|
||||
</pre>
|
||||
This command will generate three output files named ``gram.c'',
|
||||
``gram.h'' and ``gram.out''.
|
||||
The first is C code to implement the parser. The second
|
||||
is the header file that defines numerical values for all
|
||||
terminal symbols, and the last is the report that explains
|
||||
the states used by the parser automaton.</p>
|
||||
|
||||
<h3>Command Line Options</h3>
|
||||
|
||||
<p>The behavior of Lemon can be modified using command-line options.
|
||||
You can obtain a list of the available command-line options together
|
||||
with a brief explanation of what each does by typing
|
||||
<pre>
|
||||
lemon -?
|
||||
</pre>
|
||||
As of this writing, the following command-line options are supported:
|
||||
<ul>
|
||||
<li><tt>-b</tt>
|
||||
<li><tt>-c</tt>
|
||||
<li><tt>-g</tt>
|
||||
<li><tt>-m</tt>
|
||||
<li><tt>-q</tt>
|
||||
<li><tt>-s</tt>
|
||||
<li><tt>-x</tt>
|
||||
</ul>
|
||||
The ``-b'' option reduces the amount of text in the report file by
|
||||
printing only the basis of each parser state, rather than the full
|
||||
configuration.
|
||||
The ``-c'' option suppresses action table compression. Using -c
|
||||
will make the parser a little larger and slower but it will detect
|
||||
syntax errors sooner.
|
||||
The ``-g'' option causes no output files to be generated at all.
|
||||
Instead, the input grammar file is printed on standard output but
|
||||
with all comments, actions and other extraneous text deleted. This
|
||||
is a useful way to get a quick summary of a grammar.
|
||||
The ``-m'' option causes the output C source file to be compatible
|
||||
with the ``makeheaders'' program.
|
||||
Makeheaders is a program that automatically generates header files
|
||||
from C source code. When the ``-m'' option is used, the header
|
||||
file is not output since the makeheaders program will take care
|
||||
of generated all header files automatically.
|
||||
The ``-q'' option suppresses the report file.
|
||||
Using ``-s'' causes a brief summary of parser statistics to be
|
||||
printed. Like this:
|
||||
<pre>
|
||||
Parser statistics: 74 terminals, 70 nonterminals, 179 rules
|
||||
340 states, 2026 parser table entries, 0 conflicts
|
||||
</pre>
|
||||
Finally, the ``-x'' option causes Lemon to print its version number
|
||||
and then stops without attempting to read the grammar or generate a parser.</p>
|
||||
|
||||
<h3>The Parser Interface</h3>
|
||||
|
||||
<p>Lemon doesn't generate a complete, working program. It only generates
|
||||
a few subroutines that implement a parser. This section describes
|
||||
the interface to those subroutines. It is up to the programmer to
|
||||
call these subroutines in an appropriate way in order to produce a
|
||||
complete system.</p>
|
||||
|
||||
<p>Before a program begins using a Lemon-generated parser, the program
|
||||
must first create the parser.
|
||||
A new parser is created as follows:
|
||||
<pre>
|
||||
void *pParser = ParseAlloc( malloc );
|
||||
</pre>
|
||||
The ParseAlloc() routine allocates and initializes a new parser and
|
||||
returns a pointer to it.
|
||||
The actual data structure used to represent a parser is opaque --
|
||||
its internal structure is not visible or usable by the calling routine.
|
||||
For this reason, the ParseAlloc() routine returns a pointer to void
|
||||
rather than a pointer to some particular structure.
|
||||
The sole argument to the ParseAlloc() routine is a pointer to the
|
||||
subroutine used to allocate memory. Typically this means ``malloc()''.</p>
|
||||
|
||||
<p>After a program is finished using a parser, it can reclaim all
|
||||
memory allocated by that parser by calling
|
||||
<pre>
|
||||
ParseFree(pParser, free);
|
||||
</pre>
|
||||
The first argument is the same pointer returned by ParseAlloc(). The
|
||||
second argument is a pointer to the function used to release bulk
|
||||
memory back to the system.</p>
|
||||
|
||||
<p>After a parser has been allocated using ParseAlloc(), the programmer
|
||||
must supply the parser with a sequence of tokens (terminal symbols) to
|
||||
be parsed. This is accomplished by calling the following function
|
||||
once for each token:
|
||||
<pre>
|
||||
Parse(pParser, hTokenID, sTokenData, pArg);
|
||||
</pre>
|
||||
The first argument to the Parse() routine is the pointer returned by
|
||||
ParseAlloc().
|
||||
The second argument is a small positive integer that tells the parse the
|
||||
type of the next token in the data stream.
|
||||
There is one token type for each terminal symbol in the grammar.
|
||||
The gram.h file generated by Lemon contains #define statements that
|
||||
map symbolic terminal symbol names into appropriate integer values.
|
||||
(A value of 0 for the second argument is a special flag to the
|
||||
parser to indicate that the end of input has been reached.)
|
||||
The third argument is the value of the given token. By default,
|
||||
the type of the third argument is integer, but the grammar will
|
||||
usually redefine this type to be some kind of structure.
|
||||
Typically the second argument will be a broad category of tokens
|
||||
such as ``identifier'' or ``number'' and the third argument will
|
||||
be the name of the identifier or the value of the number.</p>
|
||||
|
||||
<p>The Parse() function may have either three or four arguments,
|
||||
depending on the grammar. If the grammar specification file request
|
||||
it, the Parse() function will have a fourth parameter that can be
|
||||
of any type chosen by the programmer. The parser doesn't do anything
|
||||
with this argument except to pass it through to action routines.
|
||||
This is a convenient mechanism for passing state information down
|
||||
to the action routines without having to use global variables.</p>
|
||||
|
||||
<p>A typical use of a Lemon parser might look something like the
|
||||
following:
|
||||
<pre>
|
||||
01 ParseTree *ParseFile(const char *zFilename){
|
||||
02 Tokenizer *pTokenizer;
|
||||
03 void *pParser;
|
||||
04 Token sToken;
|
||||
05 int hTokenId;
|
||||
06 ParserState sState;
|
||||
07
|
||||
08 pTokenizer = TokenizerCreate(zFilename);
|
||||
09 pParser = ParseAlloc( malloc );
|
||||
10 InitParserState(&sState);
|
||||
11 while( GetNextToken(pTokenizer, &hTokenId, &sToken) ){
|
||||
12 Parse(pParser, hTokenId, sToken, &sState);
|
||||
13 }
|
||||
14 Parse(pParser, 0, sToken, &sState);
|
||||
15 ParseFree(pParser, free );
|
||||
16 TokenizerFree(pTokenizer);
|
||||
17 return sState.treeRoot;
|
||||
18 }
|
||||
</pre>
|
||||
This example shows a user-written routine that parses a file of
|
||||
text and returns a pointer to the parse tree.
|
||||
(We've omitted all error-handling from this example to keep it
|
||||
simple.)
|
||||
We assume the existence of some kind of tokenizer which is created
|
||||
using TokenizerCreate() on line 8 and deleted by TokenizerFree()
|
||||
on line 16. The GetNextToken() function on line 11 retrieves the
|
||||
next token from the input file and puts its type in the
|
||||
integer variable hTokenId. The sToken variable is assumed to be
|
||||
some kind of structure that contains details about each token,
|
||||
such as its complete text, what line it occurs on, etc. </p>
|
||||
|
||||
<p>This example also assumes the existence of structure of type
|
||||
ParserState that holds state information about a particular parse.
|
||||
An instance of such a structure is created on line 6 and initialized
|
||||
on line 10. A pointer to this structure is passed into the Parse()
|
||||
routine as the optional 4th argument.
|
||||
The action routine specified by the grammar for the parser can use
|
||||
the ParserState structure to hold whatever information is useful and
|
||||
appropriate. In the example, we note that the treeRoot field of
|
||||
the ParserState structure is left pointing to the root of the parse
|
||||
tree.</p>
|
||||
|
||||
<p>The core of this example as it relates to Lemon is as follows:
|
||||
<pre>
|
||||
ParseFile(){
|
||||
pParser = ParseAlloc( malloc );
|
||||
while( GetNextToken(pTokenizer,&hTokenId, &sToken) ){
|
||||
Parse(pParser, hTokenId, sToken);
|
||||
}
|
||||
Parse(pParser, 0, sToken);
|
||||
ParseFree(pParser, free );
|
||||
}
|
||||
</pre>
|
||||
Basically, what a program has to do to use a Lemon-generated parser
|
||||
is first create the parser, then send it lots of tokens obtained by
|
||||
tokenizing an input source. When the end of input is reached, the
|
||||
Parse() routine should be called one last time with a token type
|
||||
of 0. This step is necessary to inform the parser that the end of
|
||||
input has been reached. Finally, we reclaim memory used by the
|
||||
parser by calling ParseFree().</p>
|
||||
|
||||
<p>There is one other interface routine that should be mentioned
|
||||
before we move on.
|
||||
The ParseTrace() function can be used to generate debugging output
|
||||
from the parser. A prototype for this routine is as follows:
|
||||
<pre>
|
||||
ParseTrace(FILE *stream, char *zPrefix);
|
||||
</pre>
|
||||
After this routine is called, a short (one-line) message is written
|
||||
to the designated output stream every time the parser changes states
|
||||
or calls an action routine. Each such message is prefaced using
|
||||
the text given by zPrefix. This debugging output can be turned off
|
||||
by calling ParseTrace() again with a first argument of NULL (0).</p>
|
||||
|
||||
<h3>Differences With YACC and BISON</h3>
|
||||
|
||||
<p>Programmers who have previously used the yacc or bison parser
|
||||
generator will notice several important differences between yacc and/or
|
||||
bison and Lemon.
|
||||
<ul>
|
||||
<li>In yacc and bison, the parser calls the tokenizer. In Lemon,
|
||||
the tokenizer calls the parser.
|
||||
<li>Lemon uses no global variables. Yacc and bison use global variables
|
||||
to pass information between the tokenizer and parser.
|
||||
<li>Lemon allows multiple parsers to be running simultaneously. Yacc
|
||||
and bison do not.
|
||||
</ul>
|
||||
These differences may cause some initial confusion for programmers
|
||||
with prior yacc and bison experience.
|
||||
But after years of experience using Lemon, I firmly
|
||||
believe that the Lemon way of doing things is better.</p>
|
||||
|
||||
<h2>Input File Syntax</h2>
|
||||
|
||||
<p>The main purpose of the grammar specification file for Lemon is
|
||||
to define the grammar for the parser. But the input file also
|
||||
specifies additional information Lemon requires to do its job.
|
||||
Most of the work in using Lemon is in writing an appropriate
|
||||
grammar file.</p>
|
||||
|
||||
<p>The grammar file for lemon is, for the most part, free format.
|
||||
It does not have sections or divisions like yacc or bison. Any
|
||||
declaration can occur at any point in the file.
|
||||
Lemon ignores whitespace (except where it is needed to separate
|
||||
tokens) and it honors the same commenting conventions as C and C++.</p>
|
||||
|
||||
<h3>Terminals and Nonterminals</h3>
|
||||
|
||||
<p>A terminal symbol (token) is any string of alphanumeric
|
||||
and underscore characters
|
||||
that begins with an upper case letter.
|
||||
A terminal can contain lowercase letters after the first character,
|
||||
but the usual convention is to make terminals all upper case.
|
||||
A nonterminal, on the other hand, is any string of alphanumeric
|
||||
and underscore characters than begins with a lower case letter.
|
||||
Again, the usual convention is to make nonterminals use all lower
|
||||
case letters.</p>
|
||||
|
||||
<p>In Lemon, terminal and nonterminal symbols do not need to
|
||||
be declared or identified in a separate section of the grammar file.
|
||||
Lemon is able to generate a list of all terminals and nonterminals
|
||||
by examining the grammar rules, and it can always distinguish a
|
||||
terminal from a nonterminal by checking the case of the first
|
||||
character of the name.</p>
|
||||
|
||||
<p>Yacc and bison allow terminal symbols to have either alphanumeric
|
||||
names or to be individual characters included in single quotes, like
|
||||
this: ')' or '$'. Lemon does not allow this alternative form for
|
||||
terminal symbols. With Lemon, all symbols, terminals and nonterminals,
|
||||
must have alphanumeric names.</p>
|
||||
|
||||
<h3>Grammar Rules</h3>
|
||||
|
||||
<p>The main component of a Lemon grammar file is a sequence of grammar
|
||||
rules.
|
||||
Each grammar rule consists of a nonterminal symbol followed by
|
||||
the special symbol ``::='' and then a list of terminals and/or nonterminals.
|
||||
The rule is terminated by a period.
|
||||
The list of terminals and nonterminals on the right-hand side of the
|
||||
rule can be empty.
|
||||
Rules can occur in any order, except that the left-hand side of the
|
||||
first rule is assumed to be the start symbol for the grammar (unless
|
||||
specified otherwise using the <tt>%start</tt> directive described below.)
|
||||
A typical sequence of grammar rules might look something like this:
|
||||
<pre>
|
||||
expr ::= expr PLUS expr.
|
||||
expr ::= expr TIMES expr.
|
||||
expr ::= LPAREN expr RPAREN.
|
||||
expr ::= VALUE.
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p>There is one non-terminal in this example, ``expr'', and five
|
||||
terminal symbols or tokens: ``PLUS'', ``TIMES'', ``LPAREN'',
|
||||
``RPAREN'' and ``VALUE''.</p>
|
||||
|
||||
<p>Like yacc and bison, Lemon allows the grammar to specify a block
|
||||
of C code that will be executed whenever a grammar rule is reduced
|
||||
by the parser.
|
||||
In Lemon, this action is specified by putting the C code (contained
|
||||
within curly braces <tt>{...}</tt>) immediately after the
|
||||
period that closes the rule.
|
||||
For example:
|
||||
<pre>
|
||||
expr ::= expr PLUS expr. { printf("Doing an addition...\n"); }
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p>In order to be useful, grammar actions must normally be linked to
|
||||
their associated grammar rules.
|
||||
In yacc and bison, this is accomplished by embedding a ``$$'' in the
|
||||
action to stand for the value of the left-hand side of the rule and
|
||||
symbols ``$1'', ``$2'', and so forth to stand for the value of
|
||||
the terminal or nonterminal at position 1, 2 and so forth on the
|
||||
right-hand side of the rule.
|
||||
This idea is very powerful, but it is also very error-prone. The
|
||||
single most common source of errors in a yacc or bison grammar is
|
||||
to miscount the number of symbols on the right-hand side of a grammar
|
||||
rule and say ``$7'' when you really mean ``$8''.</p>
|
||||
|
||||
<p>Lemon avoids the need to count grammar symbols by assigning symbolic
|
||||
names to each symbol in a grammar rule and then using those symbolic
|
||||
names in the action.
|
||||
In yacc or bison, one would write this:
|
||||
<pre>
|
||||
expr -> expr PLUS expr { $$ = $1 + $3; };
|
||||
</pre>
|
||||
But in Lemon, the same rule becomes the following:
|
||||
<pre>
|
||||
expr(A) ::= expr(B) PLUS expr(C). { A = B+C; }
|
||||
</pre>
|
||||
In the Lemon rule, any symbol in parentheses after a grammar rule
|
||||
symbol becomes a place holder for that symbol in the grammar rule.
|
||||
This place holder can then be used in the associated C action to
|
||||
stand for the value of that symbol.<p>
|
||||
|
||||
<p>The Lemon notation for linking a grammar rule with its reduce
|
||||
action is superior to yacc/bison on several counts.
|
||||
First, as mentioned above, the Lemon method avoids the need to
|
||||
count grammar symbols.
|
||||
Secondly, if a terminal or nonterminal in a Lemon grammar rule
|
||||
includes a linking symbol in parentheses but that linking symbol
|
||||
is not actually used in the reduce action, then an error message
|
||||
is generated.
|
||||
For example, the rule
|
||||
<pre>
|
||||
expr(A) ::= expr(B) PLUS expr(C). { A = B; }
|
||||
</pre>
|
||||
will generate an error because the linking symbol ``C'' is used
|
||||
in the grammar rule but not in the reduce action.</p>
|
||||
|
||||
<p>The Lemon notation for linking grammar rules to reduce actions
|
||||
also facilitates the use of destructors for reclaiming memory
|
||||
allocated by the values of terminals and nonterminals on the
|
||||
right-hand side of a rule.</p>
|
||||
|
||||
<h3>Precedence Rules</h3>
|
||||
|
||||
<p>Lemon resolves parsing ambiguities in exactly the same way as
|
||||
yacc and bison. A shift-reduce conflict is resolved in favor
|
||||
of the shift, and a reduce-reduce conflict is resolved by reducing
|
||||
whichever rule comes first in the grammar file.</p>
|
||||
|
||||
<p>Just like in
|
||||
yacc and bison, Lemon allows a measure of control
|
||||
over the resolution of paring conflicts using precedence rules.
|
||||
A precedence value can be assigned to any terminal symbol
|
||||
using the %left, %right or %nonassoc directives. Terminal symbols
|
||||
mentioned in earlier directives have a lower precedence that
|
||||
terminal symbols mentioned in later directives. For example:</p>
|
||||
|
||||
<p><pre>
|
||||
%left AND.
|
||||
%left OR.
|
||||
%nonassoc EQ NE GT GE LT LE.
|
||||
%left PLUS MINUS.
|
||||
%left TIMES DIVIDE MOD.
|
||||
%right EXP NOT.
|
||||
</pre></p>
|
||||
|
||||
<p>In the preceding sequence of directives, the AND operator is
|
||||
defined to have the lowest precedence. The OR operator is one
|
||||
precedence level higher. And so forth. Hence, the grammar would
|
||||
attempt to group the ambiguous expression
|
||||
<pre>
|
||||
a AND b OR c
|
||||
</pre>
|
||||
like this
|
||||
<pre>
|
||||
a AND (b OR c).
|
||||
</pre>
|
||||
The associativity (left, right or nonassoc) is used to determine
|
||||
the grouping when the precedence is the same. AND is left-associative
|
||||
in our example, so
|
||||
<pre>
|
||||
a AND b AND c
|
||||
</pre>
|
||||
is parsed like this
|
||||
<pre>
|
||||
(a AND b) AND c.
|
||||
</pre>
|
||||
The EXP operator is right-associative, though, so
|
||||
<pre>
|
||||
a EXP b EXP c
|
||||
</pre>
|
||||
is parsed like this
|
||||
<pre>
|
||||
a EXP (b EXP c).
|
||||
</pre>
|
||||
The nonassoc precedence is used for non-associative operators.
|
||||
So
|
||||
<pre>
|
||||
a EQ b EQ c
|
||||
</pre>
|
||||
is an error.</p>
|
||||
|
||||
<p>The precedence of non-terminals is transferred to rules as follows:
|
||||
The precedence of a grammar rule is equal to the precedence of the
|
||||
left-most terminal symbol in the rule for which a precedence is
|
||||
defined. This is normally what you want, but in those cases where
|
||||
you want to precedence of a grammar rule to be something different,
|
||||
you can specify an alternative precedence symbol by putting the
|
||||
symbol in square braces after the period at the end of the rule and
|
||||
before any C-code. For example:</p>
|
||||
|
||||
<p><pre>
|
||||
expr = MINUS expr. [NOT]
|
||||
</pre></p>
|
||||
|
||||
<p>This rule has a precedence equal to that of the NOT symbol, not the
|
||||
MINUS symbol as would have been the case by default.</p>
|
||||
|
||||
<p>With the knowledge of how precedence is assigned to terminal
|
||||
symbols and individual
|
||||
grammar rules, we can now explain precisely how parsing conflicts
|
||||
are resolved in Lemon. Shift-reduce conflicts are resolved
|
||||
as follows:
|
||||
<ul>
|
||||
<li> If either the token to be shifted or the rule to be reduced
|
||||
lacks precedence information, then resolve in favor of the
|
||||
shift, but report a parsing conflict.
|
||||
<li> If the precedence of the token to be shifted is greater than
|
||||
the precedence of the rule to reduce, then resolve in favor
|
||||
of the shift. No parsing conflict is reported.
|
||||
<li> If the precedence of the token it be shifted is less than the
|
||||
precedence of the rule to reduce, then resolve in favor of the
|
||||
reduce action. No parsing conflict is reported.
|
||||
<li> If the precedences are the same and the shift token is
|
||||
right-associative, then resolve in favor of the shift.
|
||||
No parsing conflict is reported.
|
||||
<li> If the precedences are the same the the shift token is
|
||||
left-associative, then resolve in favor of the reduce.
|
||||
No parsing conflict is reported.
|
||||
<li> Otherwise, resolve the conflict by doing the shift and
|
||||
report the parsing conflict.
|
||||
</ul>
|
||||
Reduce-reduce conflicts are resolved this way:
|
||||
<ul>
|
||||
<li> If either reduce rule
|
||||
lacks precedence information, then resolve in favor of the
|
||||
rule that appears first in the grammar and report a parsing
|
||||
conflict.
|
||||
<li> If both rules have precedence and the precedence is different
|
||||
then resolve the dispute in favor of the rule with the highest
|
||||
precedence and do not report a conflict.
|
||||
<li> Otherwise, resolve the conflict by reducing by the rule that
|
||||
appears first in the grammar and report a parsing conflict.
|
||||
</ul>
|
||||
|
||||
<h3>Special Directives</h3>
|
||||
|
||||
<p>The input grammar to Lemon consists of grammar rules and special
|
||||
directives. We've described all the grammar rules, so now we'll
|
||||
talk about the special directives.</p>
|
||||
|
||||
<p>Directives in lemon can occur in any order. You can put them before
|
||||
the grammar rules, or after the grammar rules, or in the mist of the
|
||||
grammar rules. It doesn't matter. The relative order of
|
||||
directives used to assign precedence to terminals is important, but
|
||||
other than that, the order of directives in Lemon is arbitrary.</p>
|
||||
|
||||
<p>Lemon supports the following special directives:
|
||||
<ul>
|
||||
<li><tt>%code</tt>
|
||||
<li><tt>%default_destructor</tt>
|
||||
<li><tt>%default_type</tt>
|
||||
<li><tt>%destructor</tt>
|
||||
<li><tt>%extra_argument</tt>
|
||||
<li><tt>%include</tt>
|
||||
<li><tt>%left</tt>
|
||||
<li><tt>%name</tt>
|
||||
<li><tt>%nonassoc</tt>
|
||||
<li><tt>%parse_accept</tt>
|
||||
<li><tt>%parse_failure </tt>
|
||||
<li><tt>%right</tt>
|
||||
<li><tt>%stack_overflow</tt>
|
||||
<li><tt>%stack_size</tt>
|
||||
<li><tt>%start_symbol</tt>
|
||||
<li><tt>%syntax_error</tt>
|
||||
<li><tt>%token_destructor</tt>
|
||||
<li><tt>%token_prefix</tt>
|
||||
<li><tt>%token_type</tt>
|
||||
<li><tt>%type</tt>
|
||||
</ul>
|
||||
Each of these directives will be described separately in the
|
||||
following sections:</p>
|
||||
|
||||
<h4>The <tt>%code</tt> directive</h4>
|
||||
|
||||
<p>The %code directive is used to specify addition C/C++ code that
|
||||
is added to the end of the main output file. This is similar to
|
||||
the %include directive except that %include is inserted at the
|
||||
beginning of the main output file.</p>
|
||||
|
||||
<p>%code is typically used to include some action routines or perhaps
|
||||
a tokenizer as part of the output file.</p>
|
||||
|
||||
<h4>The <tt>%default_destructor</tt> directive</h4>
|
||||
|
||||
<p>The %default_destructor directive specifies a destructor to
|
||||
use for non-terminals that do not have their own destructor
|
||||
specified by a separate %destructor directive. See the documentation
|
||||
on the %destructor directive below for additional information.</p>
|
||||
|
||||
<p>In some grammers, many different non-terminal symbols have the
|
||||
same datatype and hence the same destructor. This directive is
|
||||
a convenience way to specify the same destructor for all those
|
||||
non-terminals using a single statement.</p>
|
||||
|
||||
<h4>The <tt>%default_type</tt> directive</h4>
|
||||
|
||||
<p>The %default_type directive specifies the datatype of non-terminal
|
||||
symbols that do no have their own datatype defined using a separate
|
||||
%type directive. See the documentation on %type below for addition
|
||||
information.</p>
|
||||
|
||||
<h4>The <tt>%destructor</tt> directive</h4>
|
||||
|
||||
<p>The %destructor directive is used to specify a destructor for
|
||||
a non-terminal symbol.
|
||||
(See also the %token_destructor directive which is used to
|
||||
specify a destructor for terminal symbols.)</p>
|
||||
|
||||
<p>A non-terminal's destructor is called to dispose of the
|
||||
non-terminal's value whenever the non-terminal is popped from
|
||||
the stack. This includes all of the following circumstances:
|
||||
<ul>
|
||||
<li> When a rule reduces and the value of a non-terminal on
|
||||
the right-hand side is not linked to C code.
|
||||
<li> When the stack is popped during error processing.
|
||||
<li> When the ParseFree() function runs.
|
||||
</ul>
|
||||
The destructor can do whatever it wants with the value of
|
||||
the non-terminal, but its design is to deallocate memory
|
||||
or other resources held by that non-terminal.</p>
|
||||
|
||||
<p>Consider an example:
|
||||
<pre>
|
||||
%type nt {void*}
|
||||
%destructor nt { free($$); }
|
||||
nt(A) ::= ID NUM. { A = malloc( 100 ); }
|
||||
</pre>
|
||||
This example is a bit contrived but it serves to illustrate how
|
||||
destructors work. The example shows a non-terminal named
|
||||
``nt'' that holds values of type ``void*''. When the rule for
|
||||
an ``nt'' reduces, it sets the value of the non-terminal to
|
||||
space obtained from malloc(). Later, when the nt non-terminal
|
||||
is popped from the stack, the destructor will fire and call
|
||||
free() on this malloced space, thus avoiding a memory leak.
|
||||
(Note that the symbol ``$$'' in the destructor code is replaced
|
||||
by the value of the non-terminal.)</p>
|
||||
|
||||
<p>It is important to note that the value of a non-terminal is passed
|
||||
to the destructor whenever the non-terminal is removed from the
|
||||
stack, unless the non-terminal is used in a C-code action. If
|
||||
the non-terminal is used by C-code, then it is assumed that the
|
||||
C-code will take care of destroying it if it should really
|
||||
be destroyed. More commonly, the value is used to build some
|
||||
larger structure and we don't want to destroy it, which is why
|
||||
the destructor is not called in this circumstance.</p>
|
||||
|
||||
<p>By appropriate use of destructors, it is possible to
|
||||
build a parser using Lemon that can be used within a long-running
|
||||
program, such as a GUI, that will not leak memory or other resources.
|
||||
To do the same using yacc or bison is much more difficult.</p>
|
||||
|
||||
<h4>The <tt>%extra_argument</tt> directive</h4>
|
||||
|
||||
The %extra_argument directive instructs Lemon to add a 4th parameter
|
||||
to the parameter list of the Parse() function it generates. Lemon
|
||||
doesn't do anything itself with this extra argument, but it does
|
||||
make the argument available to C-code action routines, destructors,
|
||||
and so forth. For example, if the grammar file contains:</p>
|
||||
|
||||
<p><pre>
|
||||
%extra_argument { MyStruct *pAbc }
|
||||
</pre></p>
|
||||
|
||||
<p>Then the Parse() function generated will have an 4th parameter
|
||||
of type ``MyStruct*'' and all action routines will have access to
|
||||
a variable named ``pAbc'' that is the value of the 4th parameter
|
||||
in the most recent call to Parse().</p>
|
||||
|
||||
<h4>The <tt>%include</tt> directive</h4>
|
||||
|
||||
<p>The %include directive specifies C code that is included at the
|
||||
top of the generated parser. You can include any text you want --
|
||||
the Lemon parser generator copies it blindly. If you have multiple
|
||||
%include directives in your grammar file the value of the last
|
||||
%include directive overwrites all the others.</p.
|
||||
|
||||
<p>The %include directive is very handy for getting some extra #include
|
||||
preprocessor statements at the beginning of the generated parser.
|
||||
For example:</p>
|
||||
|
||||
<p><pre>
|
||||
%include {#include <unistd.h>}
|
||||
</pre></p>
|
||||
|
||||
<p>This might be needed, for example, if some of the C actions in the
|
||||
grammar call functions that are prototyed in unistd.h.</p>
|
||||
|
||||
<h4>The <tt>%left</tt> directive</h4>
|
||||
|
||||
The %left directive is used (along with the %right and
|
||||
%nonassoc directives) to declare precedences of terminal
|
||||
symbols. Every terminal symbol whose name appears after
|
||||
a %left directive but before the next period (``.'') is
|
||||
given the same left-associative precedence value. Subsequent
|
||||
%left directives have higher precedence. For example:</p>
|
||||
|
||||
<p><pre>
|
||||
%left AND.
|
||||
%left OR.
|
||||
%nonassoc EQ NE GT GE LT LE.
|
||||
%left PLUS MINUS.
|
||||
%left TIMES DIVIDE MOD.
|
||||
%right EXP NOT.
|
||||
</pre></p>
|
||||
|
||||
<p>Note the period that terminates each %left, %right or %nonassoc
|
||||
directive.</p>
|
||||
|
||||
<p>LALR(1) grammars can get into a situation where they require
|
||||
a large amount of stack space if you make heavy use or right-associative
|
||||
operators. For this reason, it is recommended that you use %left
|
||||
rather than %right whenever possible.</p>
|
||||
|
||||
<h4>The <tt>%name</tt> directive</h4>
|
||||
|
||||
<p>By default, the functions generated by Lemon all begin with the
|
||||
five-character string ``Parse''. You can change this string to something
|
||||
different using the %name directive. For instance:</p>
|
||||
|
||||
<p><pre>
|
||||
%name Abcde
|
||||
</pre></p>
|
||||
|
||||
<p>Putting this directive in the grammar file will cause Lemon to generate
|
||||
functions named
|
||||
<ul>
|
||||
<li> AbcdeAlloc(),
|
||||
<li> AbcdeFree(),
|
||||
<li> AbcdeTrace(), and
|
||||
<li> Abcde().
|
||||
</ul>
|
||||
The %name directive allows you to generator two or more different
|
||||
parsers and link them all into the same executable.
|
||||
</p>
|
||||
|
||||
<h4>The <tt>%nonassoc</tt> directive</h4>
|
||||
|
||||
<p>This directive is used to assign non-associative precedence to
|
||||
one or more terminal symbols. See the section on precedence rules
|
||||
or on the %left directive for additional information.</p>
|
||||
|
||||
<h4>The <tt>%parse_accept</tt> directive</h4>
|
||||
|
||||
<p>The %parse_accept directive specifies a block of C code that is
|
||||
executed whenever the parser accepts its input string. To ``accept''
|
||||
an input string means that the parser was able to process all tokens
|
||||
without error.</p>
|
||||
|
||||
<p>For example:</p>
|
||||
|
||||
<p><pre>
|
||||
%parse_accept {
|
||||
printf("parsing complete!\n");
|
||||
}
|
||||
</pre></p>
|
||||
|
||||
|
||||
<h4>The <tt>%parse_failure</tt> directive</h4>
|
||||
|
||||
<p>The %parse_failure directive specifies a block of C code that
|
||||
is executed whenever the parser fails complete. This code is not
|
||||
executed until the parser has tried and failed to resolve an input
|
||||
error using is usual error recovery strategy. The routine is
|
||||
only invoked when parsing is unable to continue.</p>
|
||||
|
||||
<p><pre>
|
||||
%parse_failure {
|
||||
fprintf(stderr,"Giving up. Parser is hopelessly lost...\n");
|
||||
}
|
||||
</pre></p>
|
||||
|
||||
<h4>The <tt>%right</tt> directive</h4>
|
||||
|
||||
<p>This directive is used to assign right-associative precedence to
|
||||
one or more terminal symbols. See the section on precedence rules
|
||||
or on the %left directive for additional information.</p>
|
||||
|
||||
<h4>The <tt>%stack_overflow</tt> directive</h4>
|
||||
|
||||
<p>The %stack_overflow directive specifies a block of C code that
|
||||
is executed if the parser's internal stack ever overflows. Typically
|
||||
this just prints an error message. After a stack overflow, the parser
|
||||
will be unable to continue and must be reset.</p>
|
||||
|
||||
<p><pre>
|
||||
%stack_overflow {
|
||||
fprintf(stderr,"Giving up. Parser stack overflow\n");
|
||||
}
|
||||
</pre></p>
|
||||
|
||||
<p>You can help prevent parser stack overflows by avoiding the use
|
||||
of right recursion and right-precedence operators in your grammar.
|
||||
Use left recursion and and left-precedence operators instead, to
|
||||
encourage rules to reduce sooner and keep the stack size down.
|
||||
For example, do rules like this:
|
||||
<pre>
|
||||
list ::= list element. // left-recursion. Good!
|
||||
list ::= .
|
||||
</pre>
|
||||
Not like this:
|
||||
<pre>
|
||||
list ::= element list. // right-recursion. Bad!
|
||||
list ::= .
|
||||
</pre>
|
||||
|
||||
<h4>The <tt>%stack_size</tt> directive</h4>
|
||||
|
||||
<p>If stack overflow is a problem and you can't resolve the trouble
|
||||
by using left-recursion, then you might want to increase the size
|
||||
of the parser's stack using this directive. Put an positive integer
|
||||
after the %stack_size directive and Lemon will generate a parse
|
||||
with a stack of the requested size. The default value is 100.</p>
|
||||
|
||||
<p><pre>
|
||||
%stack_size 2000
|
||||
</pre></p>
|
||||
|
||||
<h4>The <tt>%start_symbol</tt> directive</h4>
|
||||
|
||||
<p>By default, the start-symbol for the grammar that Lemon generates
|
||||
is the first non-terminal that appears in the grammar file. But you
|
||||
can choose a different start-symbol using the %start_symbol directive.</p>
|
||||
|
||||
<p><pre>
|
||||
%start_symbol prog
|
||||
</pre></p>
|
||||
|
||||
<h4>The <tt>%token_destructor</tt> directive</h4>
|
||||
|
||||
<p>The %destructor directive assigns a destructor to a non-terminal
|
||||
symbol. (See the description of the %destructor directive above.)
|
||||
This directive does the same thing for all terminal symbols.</p>
|
||||
|
||||
<p>Unlike non-terminal symbols which may each have a different data type
|
||||
for their values, terminals all use the same data type (defined by
|
||||
the %token_type directive) and so they use a common destructor. Other
|
||||
than that, the token destructor works just like the non-terminal
|
||||
destructors.</p>
|
||||
|
||||
<h4>The <tt>%token_prefix</tt> directive</h4>
|
||||
|
||||
<p>Lemon generates #defines that assign small integer constants
|
||||
to each terminal symbol in the grammar. If desired, Lemon will
|
||||
add a prefix specified by this directive
|
||||
to each of the #defines it generates.
|
||||
So if the default output of Lemon looked like this:
|
||||
<pre>
|
||||
#define AND 1
|
||||
#define MINUS 2
|
||||
#define OR 3
|
||||
#define PLUS 4
|
||||
</pre>
|
||||
You can insert a statement into the grammar like this:
|
||||
<pre>
|
||||
%token_prefix TOKEN_
|
||||
</pre>
|
||||
to cause Lemon to produce these symbols instead:
|
||||
<pre>
|
||||
#define TOKEN_AND 1
|
||||
#define TOKEN_MINUS 2
|
||||
#define TOKEN_OR 3
|
||||
#define TOKEN_PLUS 4
|
||||
</pre>
|
||||
|
||||
<h4>The <tt>%token_type</tt> and <tt>%type</tt> directives</h4>
|
||||
|
||||
<p>These directives are used to specify the data types for values
|
||||
on the parser's stack associated with terminal and non-terminal
|
||||
symbols. The values of all terminal symbols must be of the same
|
||||
type. This turns out to be the same data type as the 3rd parameter
|
||||
to the Parse() function generated by Lemon. Typically, you will
|
||||
make the value of a terminal symbol by a pointer to some kind of
|
||||
token structure. Like this:</p>
|
||||
|
||||
<p><pre>
|
||||
%token_type {Token*}
|
||||
</pre></p>
|
||||
|
||||
<p>If the data type of terminals is not specified, the default value
|
||||
is ``int''.</p>
|
||||
|
||||
<p>Non-terminal symbols can each have their own data types. Typically
|
||||
the data type of a non-terminal is a pointer to the root of a parse-tree
|
||||
structure that contains all information about that non-terminal.
|
||||
For example:</p>
|
||||
|
||||
<p><pre>
|
||||
%type expr {Expr*}
|
||||
</pre></p>
|
||||
|
||||
<p>Each entry on the parser's stack is actually a union containing
|
||||
instances of all data types for every non-terminal and terminal symbol.
|
||||
Lemon will automatically use the correct element of this union depending
|
||||
on what the corresponding non-terminal or terminal symbol is. But
|
||||
the grammar designer should keep in mind that the size of the union
|
||||
will be the size of its largest element. So if you have a single
|
||||
non-terminal whose data type requires 1K of storage, then your 100
|
||||
entry parser stack will require 100K of heap space. If you are willing
|
||||
and able to pay that price, fine. You just need to know.</p>
|
||||
|
||||
<h3>Error Processing</h3>
|
||||
|
||||
<p>After extensive experimentation over several years, it has been
|
||||
discovered that the error recovery strategy used by yacc is about
|
||||
as good as it gets. And so that is what Lemon uses.</p>
|
||||
|
||||
<p>When a Lemon-generated parser encounters a syntax error, it
|
||||
first invokes the code specified by the %syntax_error directive, if
|
||||
any. It then enters its error recovery strategy. The error recovery
|
||||
strategy is to begin popping the parsers stack until it enters a
|
||||
state where it is permitted to shift a special non-terminal symbol
|
||||
named ``error''. It then shifts this non-terminal and continues
|
||||
parsing. But the %syntax_error routine will not be called again
|
||||
until at least three new tokens have been successfully shifted.</p>
|
||||
|
||||
<p>If the parser pops its stack until the stack is empty, and it still
|
||||
is unable to shift the error symbol, then the %parse_failed routine
|
||||
is invoked and the parser resets itself to its start state, ready
|
||||
to begin parsing a new file. This is what will happen at the very
|
||||
first syntax error, of course, if there are no instances of the
|
||||
``error'' non-terminal in your grammar.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,76 @@
|
||||
*** Throughout this document, a page is deemed to have been synced
|
||||
automatically as soon as it is written when PRAGMA synchronous=OFF.
|
||||
Otherwise, the page is not synced until the xSync method of the VFS
|
||||
is called successfully on the file containing the page.
|
||||
|
||||
*** Definition: A page of the database file is said to be "overwriteable" if
|
||||
one or more of the following are true about the page:
|
||||
|
||||
(a) The original content of the page as it was at the beginning of
|
||||
the transaction has been written into the rollback journal and
|
||||
synced.
|
||||
|
||||
(b) The page was a freelist leaf page at the start of the transaction.
|
||||
|
||||
(c) The page number is greater than the largest page that existed in
|
||||
the database file at the start of the transaction.
|
||||
|
||||
(1) A page of the database file is never overwritten unless one of the
|
||||
following are true:
|
||||
|
||||
(a) The page and all other pages on the same sector are overwriteable.
|
||||
|
||||
(b) The atomic page write optimization is enabled, and the entire
|
||||
transaction other than the update of the transaction sequence
|
||||
number consists of a single page change.
|
||||
|
||||
(2) The content of a page written into the rollback journal exactly matches
|
||||
both the content in the database when the rollback journal was written
|
||||
and the content in the database at the beginning of the current
|
||||
transaction.
|
||||
|
||||
(3) Writes to the database file are an integer multiple of the page size
|
||||
in length and are aligned to a page boundary.
|
||||
|
||||
(4) Reads from the database file are either aligned on a page boundary and
|
||||
an integer multiple of the page size in length or are taken from the
|
||||
first 100 bytes of the database file.
|
||||
|
||||
(5) All writes to the database file are synced prior to the rollback journal
|
||||
being deleted, truncated, or zeroed.
|
||||
|
||||
(6) If a master journal file is used, then all writes to the database file
|
||||
are synced prior to the master journal being deleted.
|
||||
|
||||
*** Definition: Two databases (or the same database at two points it time)
|
||||
are said to be "logically equivalent" if they give the same answer to
|
||||
all queries. Note in particular the the content of freelist leaf
|
||||
pages can be changed arbitarily without effecting the logical equivalence
|
||||
of the database.
|
||||
|
||||
(7) At any time, if any subset, including the empty set and the total set,
|
||||
of the unsynced changes to a rollback journal are removed and the
|
||||
journal is rolled back, the resulting database file will be logical
|
||||
equivalent to the database file at the beginning of the transaction.
|
||||
|
||||
(8) When a transaction is rolled back, the xTruncate method of the VFS
|
||||
is called to restore the database file to the same size it was at
|
||||
the beginning of the transaction. (In some VFSes, the xTruncate
|
||||
method is a no-op, but that does not change the fact the SQLite will
|
||||
invoke it.)
|
||||
|
||||
(9) Whenever the database file is modified, at least one bit in the range
|
||||
of bytes from 24 through 39 inclusive will be changed prior to releasing
|
||||
the EXCLUSIVE lock.
|
||||
|
||||
(10) The pattern of bits in bytes 24 through 39 shall not repeat in less
|
||||
than one billion transactions.
|
||||
|
||||
(11) A database file is well-formed at the beginning and at the conclusion
|
||||
of every transaction.
|
||||
|
||||
(12) An EXCLUSIVE lock must be held on the database file before making
|
||||
any changes to the database file.
|
||||
|
||||
(13) A SHARED lock must be held on the database file before reading any
|
||||
content out of the database file.
|
||||
@@ -0,0 +1,130 @@
|
||||
The 5 states of an historical rollback lock as implemented by the
|
||||
xLock, xUnlock, and xCheckReservedLock methods of the sqlite3_io_methods
|
||||
objec are:
|
||||
|
||||
UNLOCKED
|
||||
SHARED
|
||||
RESERVED
|
||||
PENDING
|
||||
EXCLUSIVE
|
||||
|
||||
The wal-index file has a similar locking hierarchy implemented using
|
||||
the xShmLock method of the sqlite3_vfs object, but with 7
|
||||
states. Each connection to a wal-index file must be in one of
|
||||
the following 7 states:
|
||||
|
||||
UNLOCKED
|
||||
READ
|
||||
READ_FULL
|
||||
WRITE
|
||||
PENDING
|
||||
CHECKPOINT
|
||||
RECOVER
|
||||
|
||||
These roughly correspond to the 5 states of a rollback lock except
|
||||
that SHARED is split out into 2 states: READ and READ_FULL and
|
||||
there is an extra RECOVER state used for wal-index reconstruction.
|
||||
|
||||
The meanings of the various wal-index locking states is as follows:
|
||||
|
||||
UNLOCKED - The wal-index is not in use.
|
||||
|
||||
READ - Some prefix of the wal-index is being read. Additional
|
||||
wal-index information can be appended at any time. The
|
||||
newly appended content will be ignored by the holder of
|
||||
the READ lock.
|
||||
|
||||
READ_FULL - The entire wal-index is being read. No new information
|
||||
can be added to the wal-index. The holder of a READ_FULL
|
||||
lock promises never to read pages from the database file
|
||||
that are available anywhere in the wal-index.
|
||||
|
||||
WRITE - It is OK to append to the wal-index file and to adjust
|
||||
the header to indicate the new "last valid frame".
|
||||
|
||||
PENDING - Waiting on all READ locks to clear so that a
|
||||
CHECKPOINT lock can be acquired.
|
||||
|
||||
CHECKPOINT - It is OK to write any WAL data into the database file
|
||||
and zero the last valid frame field of the wal-index
|
||||
header. The wal-index file itself may not be changed
|
||||
other than to zero the last valid frame field in the
|
||||
header.
|
||||
|
||||
RECOVER - Held during wal-index recovery. Used to prevent a
|
||||
race if multiple clients try to recover a wal-index at
|
||||
the same time.
|
||||
|
||||
|
||||
A particular lock manager implementation may coalesce one or more of
|
||||
the wal-index locking states, though with a reduction in concurrency.
|
||||
For example, an implemention might implement only exclusive locking,
|
||||
in which case all states would be equivalent to CHECKPOINT, meaning that
|
||||
only one reader or one writer or one checkpointer could be active at a
|
||||
time. Or, an implementation might combine READ and READ_FULL into
|
||||
a single state equivalent to READ, meaning that a writer could
|
||||
coexist with a reader, but no reader or writers could coexist with a
|
||||
checkpointer.
|
||||
|
||||
The lock manager must obey the following rules:
|
||||
|
||||
(1) A READ cannot coexist with CHECKPOINT.
|
||||
(2) A READ_FULL cannot coexist with WRITE.
|
||||
(3) None of WRITE, PENDING, CHECKPOINT, or RECOVER can coexist.
|
||||
|
||||
The SQLite core will obey the next set of rules. These rules are
|
||||
assertions on the behavior of the SQLite core which might be verified
|
||||
during testing using an instrumented lock manager.
|
||||
|
||||
(5) No part of the wal-index will be read without holding either some
|
||||
kind of SHM lock or an EXCLUSIVE lock on the original database.
|
||||
The original database is the file named in the 2nd parameter to
|
||||
the xShmOpen method.
|
||||
|
||||
(6) A holder of a READ_FULL will never read any page of the database
|
||||
file that is contained anywhere in the wal-index.
|
||||
|
||||
(7) No part of the wal-index other than the header will be written nor
|
||||
will the size of the wal-index grow without holding a WRITE or
|
||||
an EXCLUSIVE on the original database file.
|
||||
|
||||
(8) The wal-index header will not be written without holding one of
|
||||
WRITE, CHECKPOINT, or RECOVER on the wal-index or an EXCLUSIVE on
|
||||
the original database files.
|
||||
|
||||
(9) A CHECKPOINT or RECOVER must be held on the wal-index, or an
|
||||
EXCLUSIVE on the original database file, in order to reset the
|
||||
last valid frame counter in the header of the wal-index back to zero.
|
||||
|
||||
(10) A WRITE can only increase the last valid frame pointer in the header.
|
||||
|
||||
The SQLite core will only ever send requests for UNLOCK, READ, WRITE,
|
||||
CHECKPOINT, or RECOVER to the lock manager. The SQLite core will never
|
||||
request a READ_FULL or PENDING lock though the lock manager may deliver
|
||||
those locking states in response to READ and CHECKPOINT requests,
|
||||
respectively, if and only if the requested READ or CHECKPOINT cannot
|
||||
be delivered.
|
||||
|
||||
The following are the allowed lock transitions:
|
||||
|
||||
Original-State Request New-State
|
||||
-------------- ---------- ----------
|
||||
(11a) UNLOCK READ READ
|
||||
(11b) UNLOCK READ READ_FULL
|
||||
(11c) UNLOCK CHECKPOINT PENDING
|
||||
(11d) UNLOCK CHECKPOINT CHECKPOINT
|
||||
(11e) READ UNLOCK UNLOCK
|
||||
(11f) READ WRITE WRITE
|
||||
(11g) READ RECOVER RECOVER
|
||||
(11h) READ_FULL UNLOCK UNLOCK
|
||||
(11i) READ_FULL WRITE WRITE
|
||||
(11j) READ_FULL RECOVER RECOVER
|
||||
(11k) WRITE READ READ
|
||||
(11l) PENDING UNLOCK UNLOCK
|
||||
(11m) PENDING CHECKPOINT CHECKPOINT
|
||||
(11n) CHECKPOINT UNLOCK UNLOCK
|
||||
(11o) RECOVER READ READ
|
||||
|
||||
These 15 transitions are all that needs to be supported. The lock
|
||||
manager implementation can assert that fact. The other 27 possible
|
||||
transitions among the 7 locking states will never occur.
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
|
||||
FTS4 CONTENT OPTION
|
||||
|
||||
Normally, in order to create a full-text index on a dataset, the FTS4
|
||||
module stores a copy of all indexed documents in a specially created
|
||||
database table.
|
||||
|
||||
As of SQLite version 3.7.9, FTS4 supports a new option - "content" -
|
||||
designed to extend FTS4 to support the creation of full-text indexes where:
|
||||
|
||||
* The indexed documents are not stored within the SQLite database
|
||||
at all (a "contentless" FTS4 table), or
|
||||
|
||||
* The indexed documents are stored in a database table created and
|
||||
managed by the user (an "external content" FTS4 table).
|
||||
|
||||
Because the indexed documents themselves are usually much larger than
|
||||
the full-text index, the content option can sometimes be used to achieve
|
||||
significant space savings.
|
||||
|
||||
CONTENTLESS FTS4 TABLES
|
||||
|
||||
In order to create an FTS4 table that does not store a copy of the indexed
|
||||
documents at all, the content option should be set to an empty string.
|
||||
For example, the following SQL creates such an FTS4 table with three
|
||||
columns - "a", "b", and "c":
|
||||
|
||||
CREATE VIRTUAL TABLE t1 USING fts4(content="", a, b, c);
|
||||
|
||||
Data can be inserted into such an FTS4 table using an INSERT statements.
|
||||
However, unlike ordinary FTS4 tables, the user must supply an explicit
|
||||
integer docid value. For example:
|
||||
|
||||
-- This statement is Ok:
|
||||
INSERT INTO t1(docid, a, b, c) VALUES(1, 'a b c', 'd e f', 'g h i');
|
||||
|
||||
-- This statement causes an error, as no docid value has been provided:
|
||||
INSERT INTO t1(a, b, c) VALUES('j k l', 'm n o', 'p q r');
|
||||
|
||||
It is not possible to UPDATE or DELETE a row stored in a contentless FTS4
|
||||
table. Attempting to do so is an error.
|
||||
|
||||
Contentless FTS4 tables also support SELECT statements. However, it is
|
||||
an error to attempt to retrieve the value of any table column other than
|
||||
the docid column. The auxiliary function matchinfo() may be used, but
|
||||
snippet() and offsets() may not. For example:
|
||||
|
||||
-- The following statements are Ok:
|
||||
SELECT docid FROM t1 WHERE t1 MATCH 'xxx';
|
||||
SELECT docid FROM t1 WHERE a MATCH 'xxx';
|
||||
SELECT matchinfo(t1) FROM t1 WHERE t1 MATCH 'xxx';
|
||||
|
||||
-- The following statements all cause errors, as the value of columns
|
||||
-- other than docid are required to evaluate them.
|
||||
SELECT * FROM t1;
|
||||
SELECT a, b FROM t1 WHERE t1 MATCH 'xxx';
|
||||
SELECT docid FROM t1 WHERE a LIKE 'xxx%';
|
||||
SELECT snippet(t1) FROM t1 WHERE t1 MATCH 'xxx';
|
||||
|
||||
Errors related to attempting to retrieve column values other than docid
|
||||
are runtime errors that occur within sqlite3_step(). In some cases, for
|
||||
example if the MATCH expression in a SELECT query matches zero rows, there
|
||||
may be no error at all even if a statement does refer to column values
|
||||
other than docid.
|
||||
|
||||
EXTERNAL CONTENT FTS4 TABLES
|
||||
|
||||
An "external content" FTS4 table is similar to a contentless table, except
|
||||
that if evaluation of a query requires the value of a column other than
|
||||
docid, FTS4 attempts to retrieve that value from a table (or view, or
|
||||
virtual table) nominated by the user (hereafter referred to as the "content
|
||||
table"). The FTS4 module never writes to the content table, and writing
|
||||
to the content table does not affect the full-text index. It is the
|
||||
responsibility of the user to ensure that the content table and the
|
||||
full-text index are consistent.
|
||||
|
||||
An external content FTS4 table is created by setting the content option
|
||||
to the name of a table (or view, or virtual table) that may be queried by
|
||||
FTS4 to retrieve column values when required. If the nominated table does
|
||||
not exist, then an external content table behaves in the same way as
|
||||
a contentless table. For example:
|
||||
|
||||
CREATE TABLE t2(id INTEGER PRIMARY KEY, a, b, c);
|
||||
CREATE VIRTUAL TABLE t3 USING fts4(content="t2", a, c);
|
||||
|
||||
Assuming the nominated table does exist, then its columns must be the same
|
||||
as or a superset of those defined for the FTS table.
|
||||
|
||||
When a users query on the FTS table requires a column value other than
|
||||
docid, FTS attempts to read this value from the corresponding column of
|
||||
the row in the content table with a rowid value equal to the current FTS
|
||||
docid. Or, if such a row cannot be found in the content table, a NULL
|
||||
value is used instead. For example:
|
||||
|
||||
CREATE TABLE t2(id INTEGER PRIMARY KEY, a, b, c, d);
|
||||
CREATE VIRTUAL TABLE t3 USING fts4(content="t2", b, c);
|
||||
|
||||
INSERT INTO t2 VALUES(2, 'a b', 'c d', 'e f');
|
||||
INSERT INTO t2 VALUES(3, 'g h', 'i j', 'k l');
|
||||
INSERT INTO t3(docid, b, c) SELECT id, b, c FROM t2;
|
||||
|
||||
-- The following query returns a single row with two columns containing
|
||||
-- the text values "i j" and "k l".
|
||||
--
|
||||
-- The query uses the full-text index to discover that the MATCH
|
||||
-- term matches the row with docid=3. It then retrieves the values
|
||||
-- of columns b and c from the row with rowid=3 in the content table
|
||||
-- to return.
|
||||
--
|
||||
SELECT * FROM t3 WHERE t3 MATCH 'k';
|
||||
|
||||
-- Following the UPDATE, the query still returns a single row, this
|
||||
-- time containing the text values "xxx" and "yyy". This is because the
|
||||
-- full-text index still indicates that the row with docid=3 matches
|
||||
-- the FTS4 query 'k', even though the documents stored in the content
|
||||
-- table have been modified.
|
||||
--
|
||||
UPDATE t2 SET b = 'xxx', c = 'yyy' WHERE rowid = 3;
|
||||
SELECT * FROM t3 WHERE t3 MATCH 'k';
|
||||
|
||||
-- Following the DELETE below, the query returns one row containing two
|
||||
-- NULL values. NULL values are returned because FTS is unable to find
|
||||
-- a row with rowid=3 within the content table.
|
||||
--
|
||||
DELETE FROM t2;
|
||||
SELECT * FROM t3 WHERE t3 MATCH 'k';
|
||||
|
||||
When a row is deleted from an external content FTS4 table, FTS4 needs to
|
||||
retrieve the column values of the row being deleted from the content table.
|
||||
This is so that FTS4 can update the full-text index entries for each token
|
||||
that occurs within the deleted row to indicate that that row has been
|
||||
deleted. If the content table row cannot be found, or if it contains values
|
||||
inconsistent with the contents of the FTS index, the results can be difficult
|
||||
to predict. The FTS index may be left containing entries corresponding to the
|
||||
deleted row, which can lead to seemingly nonsensical results being returned
|
||||
by subsequent SELECT queries. The same applies when a row is updated, as
|
||||
internally an UPDATE is the same as a DELETE followed by an INSERT.
|
||||
|
||||
Instead of writing separately to the full-text index and the content table,
|
||||
some users may wish to use database triggers to keep the full-text index
|
||||
up to date with respect to the set of documents stored in the content table.
|
||||
For example, using the tables from earlier examples:
|
||||
|
||||
CREATE TRIGGER t2_bu BEFORE UPDATE ON t2 BEGIN
|
||||
DELETE FROM t3 WHERE docid=old.rowid;
|
||||
END;
|
||||
CREATE TRIGGER t2_bd BEFORE DELETE ON t2 BEGIN
|
||||
DELETE FROM t3 WHERE docid=old.rowid;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER t2_bu AFTER UPDATE ON t2 BEGIN
|
||||
INSERT INTO t3(docid, b, c) VALUES(new.rowid, new.b, new.c);
|
||||
END;
|
||||
CREATE TRIGGER t2_bd AFTER INSERT ON t2 BEGIN
|
||||
INSERT INTO t3(docid, b, c) VALUES(new.rowid, new.b, new.c);
|
||||
END;
|
||||
|
||||
The DELETE trigger must be fired before the actual delete takes place
|
||||
on the content table. This is so that FTS4 can still retrieve the original
|
||||
values in order to update the full-text index. And the INSERT trigger must
|
||||
be fired after the new row is inserted, so as to handle the case where the
|
||||
rowid is assigned automatically within the system. The UPDATE trigger must
|
||||
be split into two parts, one fired before and one after the update of the
|
||||
content table, for the same reasons.
|
||||
|
||||
FTS4 features a special command similar to the 'optimize' command that
|
||||
deletes the entire full-text index and rebuilds it based on the current
|
||||
set of documents in the content table. Assuming again that "t3" is the
|
||||
name of the external content FTS4 table, the command is:
|
||||
|
||||
INSERT INTO t3(t3) VALUES('rebuild');
|
||||
|
||||
This command may also be used with ordinary FTS4 tables, although it may
|
||||
only be useful if the full-text index has somehow become corrupt. It is an
|
||||
error to attempt to rebuild the full-text index maintained by a contentless
|
||||
FTS4 table.
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
******************************************************************************
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef _FTSINT_H
|
||||
#define _FTSINT_H
|
||||
|
||||
@@ -19,6 +18,23 @@
|
||||
# define NDEBUG 1
|
||||
#endif
|
||||
|
||||
/*
|
||||
** FTS4 is really an extension for FTS3. It is enabled using the
|
||||
** SQLITE_ENABLE_FTS3 macro. But to avoid confusion we also all
|
||||
** the SQLITE_ENABLE_FTS4 macro to serve as an alisse for SQLITE_ENABLE_FTS3.
|
||||
*/
|
||||
#if defined(SQLITE_ENABLE_FTS4) && !defined(SQLITE_ENABLE_FTS3)
|
||||
# define SQLITE_ENABLE_FTS3
|
||||
#endif
|
||||
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
|
||||
/* If not building as part of the core, include sqlite3ext.h. */
|
||||
#ifndef SQLITE_CORE
|
||||
# include "sqlite3ext.h"
|
||||
extern const sqlite3_api_routines *sqlite3_api;
|
||||
#endif
|
||||
|
||||
#include "sqlite3.h"
|
||||
#include "fts3_tokenizer.h"
|
||||
#include "fts3_hash.h"
|
||||
@@ -47,12 +63,35 @@
|
||||
*/
|
||||
#define SizeofArray(X) ((int)(sizeof(X)/sizeof(X[0])))
|
||||
|
||||
|
||||
#ifndef MIN
|
||||
# define MIN(x,y) ((x)<(y)?(x):(y))
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Maximum length of a varint encoded integer. The varint format is different
|
||||
** from that used by SQLite, so the maximum length is 10, not 9.
|
||||
*/
|
||||
#define FTS3_VARINT_MAX 10
|
||||
|
||||
/*
|
||||
** FTS4 virtual tables may maintain multiple indexes - one index of all terms
|
||||
** in the document set and zero or more prefix indexes. All indexes are stored
|
||||
** as one or more b+-trees in the %_segments and %_segdir tables.
|
||||
**
|
||||
** It is possible to determine which index a b+-tree belongs to based on the
|
||||
** value stored in the "%_segdir.level" column. Given this value L, the index
|
||||
** that the b+-tree belongs to is (L<<10). In other words, all b+-trees with
|
||||
** level values between 0 and 1023 (inclusive) belong to index 0, all levels
|
||||
** between 1024 and 2047 to index 1, and so on.
|
||||
**
|
||||
** It is considered impossible for an index to use more than 1024 levels. In
|
||||
** theory though this may happen, but only after at least
|
||||
** (FTS3_MERGE_COUNT^1024) separate flushes of the pending-terms tables.
|
||||
*/
|
||||
#define FTS3_SEGDIR_MAXLEVEL 1024
|
||||
#define FTS3_SEGDIR_MAXLEVEL_STR "1024"
|
||||
|
||||
/*
|
||||
** The testcase() macro is only used by the amalgamation. If undefined,
|
||||
** make it a no-op.
|
||||
@@ -77,8 +116,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.
|
||||
*/
|
||||
@@ -86,18 +131,50 @@ typedef unsigned char u8; /* 1-byte (or larger) unsigned integer */
|
||||
typedef short int i16; /* 2-byte (or larger) signed integer */
|
||||
typedef unsigned int u32; /* 4-byte unsigned integer */
|
||||
typedef sqlite3_uint64 u64; /* 8-byte unsigned integer */
|
||||
|
||||
/*
|
||||
** Macro used to suppress compiler warnings for unused parameters.
|
||||
*/
|
||||
#define UNUSED_PARAMETER(x) (void)(x)
|
||||
|
||||
/*
|
||||
** Activate assert() only if SQLITE_TEST is enabled.
|
||||
*/
|
||||
#if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
|
||||
# define NDEBUG 1
|
||||
#endif
|
||||
|
||||
/*
|
||||
** The TESTONLY macro is used to enclose variable declarations or
|
||||
** other bits of code that are needed to support the arguments
|
||||
** within testcase() and assert() macros.
|
||||
*/
|
||||
#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
|
||||
# define TESTONLY(X) X
|
||||
#else
|
||||
# define TESTONLY(X)
|
||||
#endif
|
||||
|
||||
#endif /* SQLITE_AMALGAMATION */
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
int sqlite3Fts3Corrupt(void);
|
||||
# define FTS_CORRUPT_VTAB sqlite3Fts3Corrupt()
|
||||
#else
|
||||
# define FTS_CORRUPT_VTAB SQLITE_CORRUPT_VTAB
|
||||
#endif
|
||||
|
||||
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 Fts3Doclist Fts3Doclist;
|
||||
typedef struct Fts3SegFilter Fts3SegFilter;
|
||||
typedef struct Fts3DeferredToken Fts3DeferredToken;
|
||||
typedef struct Fts3SegReader Fts3SegReader;
|
||||
typedef struct Fts3MultiSegReader Fts3MultiSegReader;
|
||||
|
||||
/*
|
||||
** A connection to a fulltext index is an instance of the following
|
||||
@@ -114,38 +191,56 @@ struct Fts3Table {
|
||||
int nColumn; /* number of named columns in virtual table */
|
||||
char **azColumn; /* column names. malloced */
|
||||
sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */
|
||||
char *zContentTbl; /* content=xxx option, or NULL */
|
||||
|
||||
/* 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];
|
||||
sqlite3_stmt *aStmt[27];
|
||||
|
||||
/* 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 */
|
||||
char *zReadExprlist;
|
||||
char *zWriteExprlist;
|
||||
|
||||
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 */
|
||||
u8 bDescIdx; /* True if doclists are in reverse order */
|
||||
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
|
||||
/* TODO: Fix the first paragraph of this comment.
|
||||
**
|
||||
** The following hash table is used to buffer pending index updates during
|
||||
** transactions. Variable nPendingData estimates the memory size of the
|
||||
** pending data, including hash table overhead, but not malloc overhead.
|
||||
** When nPendingData exceeds nMaxPendingData, the buffer is flushed
|
||||
** automatically. Variable iPrevDocid is the docid of the most recently
|
||||
** inserted record.
|
||||
**
|
||||
** A single FTS4 table may have multiple full-text indexes. For each index
|
||||
** there is an entry in the aIndex[] array. Index 0 is an index of all the
|
||||
** terms that appear in the document set. Each subsequent index in aIndex[]
|
||||
** is an index of prefixes of a specific length.
|
||||
*/
|
||||
int nMaxPendingData;
|
||||
int nPendingData;
|
||||
sqlite_int64 iPrevDocid;
|
||||
Fts3Hash pendingTerms;
|
||||
int nIndex; /* Size of aIndex[] */
|
||||
struct Fts3Index {
|
||||
int nPrefix; /* Prefix length (0 for main terms index) */
|
||||
Fts3Hash hPending; /* Pending terms table for this index */
|
||||
} *aIndex;
|
||||
int nMaxPendingData; /* Max pending data before flush to disk */
|
||||
int nPendingData; /* Current bytes of pending data */
|
||||
sqlite_int64 iPrevDocid; /* Docid of most recently inserted document */
|
||||
|
||||
#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
|
||||
/* State variables used for validating that the transaction control
|
||||
** methods of the virtual table are called at appropriate times. These
|
||||
** values do not contribution to the FTS computation; they are used for
|
||||
** verifying the SQLite core.
|
||||
*/
|
||||
int inTransaction; /* True after xBegin but before xCommit/xRollback */
|
||||
int mxSavepoint; /* Largest valid xSavepoint integer */
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -160,14 +255,27 @@ 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 */
|
||||
u8 bDesc; /* True to sort in descending order */
|
||||
int eEvalmode; /* An FTS3_EVAL_XX constant */
|
||||
int nRowAvg; /* Average size of database rows, in pages */
|
||||
sqlite3_int64 nDoc; /* Documents in table */
|
||||
|
||||
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
|
||||
@@ -187,35 +295,71 @@ struct Fts3Cursor {
|
||||
#define FTS3_DOCID_SEARCH 1 /* Lookup by rowid on %_content table */
|
||||
#define FTS3_FULLTEXT_SEARCH 2 /* Full-text index search */
|
||||
|
||||
|
||||
struct Fts3Doclist {
|
||||
char *aAll; /* Array containing doclist (or NULL) */
|
||||
int nAll; /* Size of a[] in bytes */
|
||||
char *pNextDocid; /* Pointer to next docid */
|
||||
|
||||
sqlite3_int64 iDocid; /* Current docid (if pList!=0) */
|
||||
int bFreeList; /* True if pList should be sqlite3_free()d */
|
||||
char *pList; /* Pointer to position list following iDocid */
|
||||
int nList; /* Length of position list */
|
||||
};
|
||||
|
||||
/*
|
||||
** 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.
|
||||
*/
|
||||
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 bFirst; /* True if token must appear at position 0 */
|
||||
|
||||
/* Variables above this point are populated when the expression is
|
||||
** parsed (by code in fts3_expr.c). Below this point the variables are
|
||||
** used when evaluating the expression. */
|
||||
Fts3DeferredToken *pDeferred; /* Deferred token object for this token */
|
||||
Fts3MultiSegReader *pSegcsr; /* Segment-reader for this token */
|
||||
};
|
||||
|
||||
struct Fts3Phrase {
|
||||
/* Cache of doclist for this phrase. */
|
||||
Fts3Doclist doclist;
|
||||
int bIncr; /* True if doclist is loaded incrementally */
|
||||
int iDoclistToken;
|
||||
|
||||
/* Variables below this point are populated by fts3_expr.c when parsing
|
||||
** a MATCH expression. Everything above is part of the evaluation phase.
|
||||
*/
|
||||
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 */
|
||||
};
|
||||
|
||||
/*
|
||||
** A tree of these objects forms the RHS of a MATCH operator.
|
||||
**
|
||||
** If Fts3Expr.eType is either FTSQUERY_NEAR or FTSQUERY_PHRASE and isLoaded
|
||||
** is true, then aDoclist points to a malloced buffer, size nDoclist bytes,
|
||||
** containing the results of the NEAR or phrase query in FTS3 doclist
|
||||
** format. As usual, the initial "Length" field found in doclists stored
|
||||
** on disk is omitted from this buffer.
|
||||
** If Fts3Expr.eType is FTSQUERY_PHRASE and isLoaded is true, then aDoclist
|
||||
** points to a malloced buffer, size nDoclist bytes, containing the results
|
||||
** of this phrase query in FTS3 doclist format. As usual, the initial
|
||||
** "Length" field found in doclists stored on disk is omitted from this
|
||||
** buffer.
|
||||
**
|
||||
** Variable pCurrent always points to the start of a docid field within
|
||||
** aDoclist. Since the doclist is usually scanned in docid order, this can
|
||||
** be used to accelerate seeking to the required docid within the doclist.
|
||||
** Variable aMI is used only for FTSQUERY_NEAR nodes to store the global
|
||||
** matchinfo data. If it is not NULL, it points to an array of size nCol*3,
|
||||
** where nCol is the number of columns in the queried FTS table. The array
|
||||
** is populated as follows:
|
||||
**
|
||||
** aMI[iCol*3 + 0] = Undefined
|
||||
** aMI[iCol*3 + 1] = Number of occurrences
|
||||
** aMI[iCol*3 + 2] = Number of rows containing at least one instance
|
||||
**
|
||||
** The aMI array is allocated using sqlite3_malloc(). It should be freed
|
||||
** when the expression node is.
|
||||
*/
|
||||
struct Fts3Expr {
|
||||
int eType; /* One of the FTSQUERY_XXX values defined below */
|
||||
@@ -225,12 +369,13 @@ struct Fts3Expr {
|
||||
Fts3Expr *pRight; /* Right operand */
|
||||
Fts3Phrase *pPhrase; /* Valid if eType==FTSQUERY_PHRASE */
|
||||
|
||||
int isLoaded; /* True if aDoclist/nDoclist are initialized. */
|
||||
char *aDoclist; /* Buffer containing doclist */
|
||||
int nDoclist; /* Size of aDoclist in bytes */
|
||||
/* The following are used by the fts3_eval.c module. */
|
||||
sqlite3_int64 iDocid; /* Current docid */
|
||||
u8 bEof; /* True this expression is at EOF already */
|
||||
u8 bStart; /* True if iDocid is valid */
|
||||
u8 bDeferred; /* True if this expression is entirely deferred */
|
||||
|
||||
sqlite3_int64 iCurrent;
|
||||
char *pCurrent;
|
||||
u32 *aMI;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -251,34 +396,47 @@ 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 *);
|
||||
int sqlite3Fts3SegReaderIterate(
|
||||
Fts3Table *, Fts3SegReader **, int, Fts3SegFilter *,
|
||||
int (*)(Fts3Table *, void *, char *, int, char *, int), void *
|
||||
);
|
||||
int sqlite3Fts3ReadBlock(Fts3Table*, sqlite3_int64, char const**, int*);
|
||||
int sqlite3Fts3AllSegdirs(Fts3Table*, sqlite3_stmt **);
|
||||
int sqlite3Fts3MatchinfoDocsizeLocal(Fts3Cursor*, u32*);
|
||||
int sqlite3Fts3MatchinfoDocsizeGlobal(Fts3Cursor*, u32*);
|
||||
int sqlite3Fts3SegReaderPending(
|
||||
Fts3Table*,int,const char*,int,int,Fts3SegReader**);
|
||||
void sqlite3Fts3SegReaderFree(Fts3SegReader *);
|
||||
int sqlite3Fts3AllSegdirs(Fts3Table*, int, int, sqlite3_stmt **);
|
||||
int sqlite3Fts3ReadLock(Fts3Table *);
|
||||
int sqlite3Fts3ReadBlock(Fts3Table*, sqlite3_int64, char **, int*, 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 *);
|
||||
void sqlite3Fts3SegmentsClose(Fts3Table *);
|
||||
|
||||
/* Special values interpreted by sqlite3SegReaderCursor() */
|
||||
#define FTS3_SEGCURSOR_PENDING -1
|
||||
#define FTS3_SEGCURSOR_ALL -2
|
||||
|
||||
int sqlite3Fts3SegReaderStart(Fts3Table*, Fts3MultiSegReader*, Fts3SegFilter*);
|
||||
int sqlite3Fts3SegReaderStep(Fts3Table *, Fts3MultiSegReader *);
|
||||
void sqlite3Fts3SegReaderFinish(Fts3MultiSegReader *);
|
||||
|
||||
int sqlite3Fts3SegReaderCursor(
|
||||
Fts3Table *, int, int, const char *, int, int, int, Fts3MultiSegReader *);
|
||||
|
||||
/* Flags allowed as part of the 4th argument to SegmentReaderIterate() */
|
||||
#define FTS3_SEGMENT_REQUIRE_POS 0x00000001
|
||||
#define FTS3_SEGMENT_IGNORE_EMPTY 0x00000002
|
||||
#define FTS3_SEGMENT_COLUMN_FILTER 0x00000004
|
||||
#define FTS3_SEGMENT_PREFIX 0x00000008
|
||||
#define FTS3_SEGMENT_SCAN 0x00000010
|
||||
#define FTS3_SEGMENT_FIRST 0x00000020
|
||||
|
||||
/* Type passed as 4th argument to SegmentReaderIterate() */
|
||||
struct Fts3SegFilter {
|
||||
@@ -288,38 +446,78 @@ struct Fts3SegFilter {
|
||||
int flags;
|
||||
};
|
||||
|
||||
struct Fts3MultiSegReader {
|
||||
/* Used internally by sqlite3Fts3SegReaderXXX() calls */
|
||||
Fts3SegReader **apSegment; /* Array of Fts3SegReader objects */
|
||||
int nSegment; /* Size of apSegment array */
|
||||
int nAdvance; /* How many seg-readers to advance */
|
||||
Fts3SegFilter *pFilter; /* Pointer to filter object */
|
||||
char *aBuffer; /* Buffer to merge doclists in */
|
||||
int nBuffer; /* Allocated size of aBuffer[] in bytes */
|
||||
|
||||
int iColFilter; /* If >=0, filter for this column */
|
||||
int bRestart;
|
||||
|
||||
/* Used by fts3.c only. */
|
||||
int nCost; /* Cost of running iterator */
|
||||
int bLookup; /* True if a lookup of a single entry. */
|
||||
|
||||
/* Output values. Valid only after Fts3SegReaderStep() returns SQLITE_ROW. */
|
||||
char *zTerm; /* Pointer to term buffer */
|
||||
int nTerm; /* Size of zTerm in bytes */
|
||||
char *aDoclist; /* Pointer to doclist buffer */
|
||||
int nDoclist; /* Size of aDoclist[] in bytes */
|
||||
};
|
||||
|
||||
/* fts3.c */
|
||||
int sqlite3Fts3PutVarint(char *, sqlite3_int64);
|
||||
int sqlite3Fts3GetVarint(const char *, sqlite_int64 *);
|
||||
int sqlite3Fts3GetVarint32(const char *, int *);
|
||||
int sqlite3Fts3VarintLen(sqlite3_uint64);
|
||||
void sqlite3Fts3Dequote(char *);
|
||||
|
||||
char *sqlite3Fts3FindPositions(Fts3Expr *, sqlite3_int64, int);
|
||||
int sqlite3Fts3ExprLoadDoclist(Fts3Table *, Fts3Expr *);
|
||||
int sqlite3Fts3ExprNearTrim(Fts3Expr *, Fts3Expr *, int);
|
||||
void sqlite3Fts3DoclistPrev(int,char*,int,char**,sqlite3_int64*,int*,u8*);
|
||||
int sqlite3Fts3EvalPhraseStats(Fts3Cursor *, Fts3Expr *, u32 *);
|
||||
int sqlite3Fts3FirstFilter(sqlite3_int64, char *, int, char *);
|
||||
|
||||
/* 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 *,
|
||||
char **, int, int, const char *, int, Fts3Expr **
|
||||
char **, int, int, int, const char *, int, Fts3Expr **
|
||||
);
|
||||
void sqlite3Fts3ExprFree(Fts3Expr *);
|
||||
#ifdef SQLITE_TEST
|
||||
int sqlite3Fts3ExprInitTestInterface(sqlite3 *db);
|
||||
int sqlite3Fts3InitTerm(sqlite3 *db);
|
||||
#endif
|
||||
|
||||
/* fts3_aux.c */
|
||||
int sqlite3Fts3InitAux(sqlite3 *db);
|
||||
|
||||
void sqlite3Fts3EvalPhraseCleanup(Fts3Phrase *);
|
||||
|
||||
int sqlite3Fts3MsrIncrStart(
|
||||
Fts3Table*, Fts3MultiSegReader*, int, const char*, int);
|
||||
int sqlite3Fts3MsrIncrNext(
|
||||
Fts3Table *, Fts3MultiSegReader *, sqlite3_int64 *, char **, int *);
|
||||
char *sqlite3Fts3EvalPhrasePoslist(Fts3Cursor *, Fts3Expr *, int iCol);
|
||||
int sqlite3Fts3MsrOvfl(Fts3Cursor *, Fts3MultiSegReader *, int *);
|
||||
int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr);
|
||||
|
||||
int sqlite3Fts3DeferredTokenList(Fts3DeferredToken *, char **, int *);
|
||||
|
||||
#endif /* !SQLITE_CORE || SQLITE_ENABLE_FTS3 */
|
||||
#endif /* _FTSINT_H */
|
||||
|
||||
@@ -0,0 +1,474 @@
|
||||
/*
|
||||
** 2011 Jan 27
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
******************************************************************************
|
||||
**
|
||||
*/
|
||||
#include "fts3Int.h"
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
typedef struct Fts3auxTable Fts3auxTable;
|
||||
typedef struct Fts3auxCursor Fts3auxCursor;
|
||||
|
||||
struct Fts3auxTable {
|
||||
sqlite3_vtab base; /* Base class used by SQLite core */
|
||||
Fts3Table *pFts3Tab;
|
||||
};
|
||||
|
||||
struct Fts3auxCursor {
|
||||
sqlite3_vtab_cursor base; /* Base class used by SQLite core */
|
||||
Fts3MultiSegReader csr; /* Must be right after "base" */
|
||||
Fts3SegFilter filter;
|
||||
char *zStop;
|
||||
int nStop; /* Byte-length of string zStop */
|
||||
int isEof; /* True if cursor is at EOF */
|
||||
sqlite3_int64 iRowid; /* Current rowid */
|
||||
|
||||
int iCol; /* Current value of 'col' column */
|
||||
int nStat; /* Size of aStat[] array */
|
||||
struct Fts3auxColstats {
|
||||
sqlite3_int64 nDoc; /* 'documents' values for current csr row */
|
||||
sqlite3_int64 nOcc; /* 'occurrences' values for current csr row */
|
||||
} *aStat;
|
||||
};
|
||||
|
||||
/*
|
||||
** Schema of the terms table.
|
||||
*/
|
||||
#define FTS3_TERMS_SCHEMA "CREATE TABLE x(term, col, documents, occurrences)"
|
||||
|
||||
/*
|
||||
** This function does all the work for both the xConnect and xCreate methods.
|
||||
** These tables have no persistent representation of their own, so xConnect
|
||||
** and xCreate are identical operations.
|
||||
*/
|
||||
static int fts3auxConnectMethod(
|
||||
sqlite3 *db, /* Database connection */
|
||||
void *pUnused, /* Unused */
|
||||
int argc, /* Number of elements in argv array */
|
||||
const char * const *argv, /* xCreate/xConnect argument array */
|
||||
sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
|
||||
char **pzErr /* OUT: sqlite3_malloc'd error message */
|
||||
){
|
||||
char const *zDb; /* Name of database (e.g. "main") */
|
||||
char const *zFts3; /* Name of fts3 table */
|
||||
int nDb; /* Result of strlen(zDb) */
|
||||
int nFts3; /* Result of strlen(zFts3) */
|
||||
int nByte; /* Bytes of space to allocate here */
|
||||
int rc; /* value returned by declare_vtab() */
|
||||
Fts3auxTable *p; /* Virtual table object to return */
|
||||
|
||||
UNUSED_PARAMETER(pUnused);
|
||||
|
||||
/* The user should specify a single argument - the name of an fts3 table. */
|
||||
if( argc!=4 ){
|
||||
*pzErr = sqlite3_mprintf(
|
||||
"wrong number of arguments to fts4aux constructor"
|
||||
);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
zDb = argv[1];
|
||||
nDb = strlen(zDb);
|
||||
zFts3 = argv[3];
|
||||
nFts3 = strlen(zFts3);
|
||||
|
||||
rc = sqlite3_declare_vtab(db, FTS3_TERMS_SCHEMA);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
nByte = sizeof(Fts3auxTable) + sizeof(Fts3Table) + nDb + nFts3 + 2;
|
||||
p = (Fts3auxTable *)sqlite3_malloc(nByte);
|
||||
if( !p ) return SQLITE_NOMEM;
|
||||
memset(p, 0, nByte);
|
||||
|
||||
p->pFts3Tab = (Fts3Table *)&p[1];
|
||||
p->pFts3Tab->zDb = (char *)&p->pFts3Tab[1];
|
||||
p->pFts3Tab->zName = &p->pFts3Tab->zDb[nDb+1];
|
||||
p->pFts3Tab->db = db;
|
||||
p->pFts3Tab->nIndex = 1;
|
||||
|
||||
memcpy((char *)p->pFts3Tab->zDb, zDb, nDb);
|
||||
memcpy((char *)p->pFts3Tab->zName, zFts3, nFts3);
|
||||
sqlite3Fts3Dequote((char *)p->pFts3Tab->zName);
|
||||
|
||||
*ppVtab = (sqlite3_vtab *)p;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function does the work for both the xDisconnect and xDestroy methods.
|
||||
** These tables have no persistent representation of their own, so xDisconnect
|
||||
** and xDestroy are identical operations.
|
||||
*/
|
||||
static int fts3auxDisconnectMethod(sqlite3_vtab *pVtab){
|
||||
Fts3auxTable *p = (Fts3auxTable *)pVtab;
|
||||
Fts3Table *pFts3 = p->pFts3Tab;
|
||||
int i;
|
||||
|
||||
/* Free any prepared statements held */
|
||||
for(i=0; i<SizeofArray(pFts3->aStmt); i++){
|
||||
sqlite3_finalize(pFts3->aStmt[i]);
|
||||
}
|
||||
sqlite3_free(pFts3->zSegmentsTbl);
|
||||
sqlite3_free(p);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
#define FTS4AUX_EQ_CONSTRAINT 1
|
||||
#define FTS4AUX_GE_CONSTRAINT 2
|
||||
#define FTS4AUX_LE_CONSTRAINT 4
|
||||
|
||||
/*
|
||||
** xBestIndex - Analyze a WHERE and ORDER BY clause.
|
||||
*/
|
||||
static int fts3auxBestIndexMethod(
|
||||
sqlite3_vtab *pVTab,
|
||||
sqlite3_index_info *pInfo
|
||||
){
|
||||
int i;
|
||||
int iEq = -1;
|
||||
int iGe = -1;
|
||||
int iLe = -1;
|
||||
|
||||
UNUSED_PARAMETER(pVTab);
|
||||
|
||||
/* This vtab delivers always results in "ORDER BY term ASC" order. */
|
||||
if( pInfo->nOrderBy==1
|
||||
&& pInfo->aOrderBy[0].iColumn==0
|
||||
&& pInfo->aOrderBy[0].desc==0
|
||||
){
|
||||
pInfo->orderByConsumed = 1;
|
||||
}
|
||||
|
||||
/* Search for equality and range constraints on the "term" column. */
|
||||
for(i=0; i<pInfo->nConstraint; i++){
|
||||
if( pInfo->aConstraint[i].usable && pInfo->aConstraint[i].iColumn==0 ){
|
||||
int op = pInfo->aConstraint[i].op;
|
||||
if( op==SQLITE_INDEX_CONSTRAINT_EQ ) iEq = i;
|
||||
if( op==SQLITE_INDEX_CONSTRAINT_LT ) iLe = i;
|
||||
if( op==SQLITE_INDEX_CONSTRAINT_LE ) iLe = i;
|
||||
if( op==SQLITE_INDEX_CONSTRAINT_GT ) iGe = i;
|
||||
if( op==SQLITE_INDEX_CONSTRAINT_GE ) iGe = i;
|
||||
}
|
||||
}
|
||||
|
||||
if( iEq>=0 ){
|
||||
pInfo->idxNum = FTS4AUX_EQ_CONSTRAINT;
|
||||
pInfo->aConstraintUsage[iEq].argvIndex = 1;
|
||||
pInfo->estimatedCost = 5;
|
||||
}else{
|
||||
pInfo->idxNum = 0;
|
||||
pInfo->estimatedCost = 20000;
|
||||
if( iGe>=0 ){
|
||||
pInfo->idxNum += FTS4AUX_GE_CONSTRAINT;
|
||||
pInfo->aConstraintUsage[iGe].argvIndex = 1;
|
||||
pInfo->estimatedCost /= 2;
|
||||
}
|
||||
if( iLe>=0 ){
|
||||
pInfo->idxNum += FTS4AUX_LE_CONSTRAINT;
|
||||
pInfo->aConstraintUsage[iLe].argvIndex = 1 + (iGe>=0);
|
||||
pInfo->estimatedCost /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xOpen - Open a cursor.
|
||||
*/
|
||||
static int fts3auxOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
|
||||
Fts3auxCursor *pCsr; /* Pointer to cursor object to return */
|
||||
|
||||
UNUSED_PARAMETER(pVTab);
|
||||
|
||||
pCsr = (Fts3auxCursor *)sqlite3_malloc(sizeof(Fts3auxCursor));
|
||||
if( !pCsr ) return SQLITE_NOMEM;
|
||||
memset(pCsr, 0, sizeof(Fts3auxCursor));
|
||||
|
||||
*ppCsr = (sqlite3_vtab_cursor *)pCsr;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xClose - Close a cursor.
|
||||
*/
|
||||
static int fts3auxCloseMethod(sqlite3_vtab_cursor *pCursor){
|
||||
Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab;
|
||||
Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
|
||||
|
||||
sqlite3Fts3SegmentsClose(pFts3);
|
||||
sqlite3Fts3SegReaderFinish(&pCsr->csr);
|
||||
sqlite3_free((void *)pCsr->filter.zTerm);
|
||||
sqlite3_free(pCsr->zStop);
|
||||
sqlite3_free(pCsr->aStat);
|
||||
sqlite3_free(pCsr);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static int fts3auxGrowStatArray(Fts3auxCursor *pCsr, int nSize){
|
||||
if( nSize>pCsr->nStat ){
|
||||
struct Fts3auxColstats *aNew;
|
||||
aNew = (struct Fts3auxColstats *)sqlite3_realloc(pCsr->aStat,
|
||||
sizeof(struct Fts3auxColstats) * nSize
|
||||
);
|
||||
if( aNew==0 ) return SQLITE_NOMEM;
|
||||
memset(&aNew[pCsr->nStat], 0,
|
||||
sizeof(struct Fts3auxColstats) * (nSize - pCsr->nStat)
|
||||
);
|
||||
pCsr->aStat = aNew;
|
||||
pCsr->nStat = nSize;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xNext - Advance the cursor to the next row, if any.
|
||||
*/
|
||||
static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){
|
||||
Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
|
||||
Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab;
|
||||
int rc;
|
||||
|
||||
/* Increment our pretend rowid value. */
|
||||
pCsr->iRowid++;
|
||||
|
||||
for(pCsr->iCol++; pCsr->iCol<pCsr->nStat; pCsr->iCol++){
|
||||
if( pCsr->aStat[pCsr->iCol].nDoc>0 ) return SQLITE_OK;
|
||||
}
|
||||
|
||||
rc = sqlite3Fts3SegReaderStep(pFts3, &pCsr->csr);
|
||||
if( rc==SQLITE_ROW ){
|
||||
int i = 0;
|
||||
int nDoclist = pCsr->csr.nDoclist;
|
||||
char *aDoclist = pCsr->csr.aDoclist;
|
||||
int iCol;
|
||||
|
||||
int eState = 0;
|
||||
|
||||
if( pCsr->zStop ){
|
||||
int n = (pCsr->nStop<pCsr->csr.nTerm) ? pCsr->nStop : pCsr->csr.nTerm;
|
||||
int mc = memcmp(pCsr->zStop, pCsr->csr.zTerm, n);
|
||||
if( mc<0 || (mc==0 && pCsr->csr.nTerm>pCsr->nStop) ){
|
||||
pCsr->isEof = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if( fts3auxGrowStatArray(pCsr, 2) ) return SQLITE_NOMEM;
|
||||
memset(pCsr->aStat, 0, sizeof(struct Fts3auxColstats) * pCsr->nStat);
|
||||
iCol = 0;
|
||||
|
||||
while( i<nDoclist ){
|
||||
sqlite3_int64 v = 0;
|
||||
|
||||
i += sqlite3Fts3GetVarint(&aDoclist[i], &v);
|
||||
switch( eState ){
|
||||
/* State 0. In this state the integer just read was a docid. */
|
||||
case 0:
|
||||
pCsr->aStat[0].nDoc++;
|
||||
eState = 1;
|
||||
iCol = 0;
|
||||
break;
|
||||
|
||||
/* State 1. In this state we are expecting either a 1, indicating
|
||||
** that the following integer will be a column number, or the
|
||||
** start of a position list for column 0.
|
||||
**
|
||||
** The only difference between state 1 and state 2 is that if the
|
||||
** integer encountered in state 1 is not 0 or 1, then we need to
|
||||
** increment the column 0 "nDoc" count for this term.
|
||||
*/
|
||||
case 1:
|
||||
assert( iCol==0 );
|
||||
if( v>1 ){
|
||||
pCsr->aStat[1].nDoc++;
|
||||
}
|
||||
eState = 2;
|
||||
/* fall through */
|
||||
|
||||
case 2:
|
||||
if( v==0 ){ /* 0x00. Next integer will be a docid. */
|
||||
eState = 0;
|
||||
}else if( v==1 ){ /* 0x01. Next integer will be a column number. */
|
||||
eState = 3;
|
||||
}else{ /* 2 or greater. A position. */
|
||||
pCsr->aStat[iCol+1].nOcc++;
|
||||
pCsr->aStat[0].nOcc++;
|
||||
}
|
||||
break;
|
||||
|
||||
/* State 3. The integer just read is a column number. */
|
||||
default: assert( eState==3 );
|
||||
iCol = (int)v;
|
||||
if( fts3auxGrowStatArray(pCsr, iCol+2) ) return SQLITE_NOMEM;
|
||||
pCsr->aStat[iCol+1].nDoc++;
|
||||
eState = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pCsr->iCol = 0;
|
||||
rc = SQLITE_OK;
|
||||
}else{
|
||||
pCsr->isEof = 1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** xFilter - Initialize a cursor to point at the start of its data.
|
||||
*/
|
||||
static int fts3auxFilterMethod(
|
||||
sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
|
||||
int idxNum, /* Strategy index */
|
||||
const char *idxStr, /* Unused */
|
||||
int nVal, /* Number of elements in apVal */
|
||||
sqlite3_value **apVal /* Arguments for the indexing scheme */
|
||||
){
|
||||
Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
|
||||
Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab;
|
||||
int rc;
|
||||
int isScan;
|
||||
|
||||
UNUSED_PARAMETER(nVal);
|
||||
UNUSED_PARAMETER(idxStr);
|
||||
|
||||
assert( idxStr==0 );
|
||||
assert( idxNum==FTS4AUX_EQ_CONSTRAINT || idxNum==0
|
||||
|| idxNum==FTS4AUX_LE_CONSTRAINT || idxNum==FTS4AUX_GE_CONSTRAINT
|
||||
|| idxNum==(FTS4AUX_LE_CONSTRAINT|FTS4AUX_GE_CONSTRAINT)
|
||||
);
|
||||
isScan = (idxNum!=FTS4AUX_EQ_CONSTRAINT);
|
||||
|
||||
/* In case this cursor is being reused, close and zero it. */
|
||||
testcase(pCsr->filter.zTerm);
|
||||
sqlite3Fts3SegReaderFinish(&pCsr->csr);
|
||||
sqlite3_free((void *)pCsr->filter.zTerm);
|
||||
sqlite3_free(pCsr->aStat);
|
||||
memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr);
|
||||
|
||||
pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY;
|
||||
if( isScan ) pCsr->filter.flags |= FTS3_SEGMENT_SCAN;
|
||||
|
||||
if( idxNum&(FTS4AUX_EQ_CONSTRAINT|FTS4AUX_GE_CONSTRAINT) ){
|
||||
const unsigned char *zStr = sqlite3_value_text(apVal[0]);
|
||||
if( zStr ){
|
||||
pCsr->filter.zTerm = sqlite3_mprintf("%s", zStr);
|
||||
pCsr->filter.nTerm = sqlite3_value_bytes(apVal[0]);
|
||||
if( pCsr->filter.zTerm==0 ) return SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
if( idxNum&FTS4AUX_LE_CONSTRAINT ){
|
||||
int iIdx = (idxNum&FTS4AUX_GE_CONSTRAINT) ? 1 : 0;
|
||||
pCsr->zStop = sqlite3_mprintf("%s", sqlite3_value_text(apVal[iIdx]));
|
||||
pCsr->nStop = sqlite3_value_bytes(apVal[iIdx]);
|
||||
if( pCsr->zStop==0 ) return SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
rc = sqlite3Fts3SegReaderCursor(pFts3, 0, FTS3_SEGCURSOR_ALL,
|
||||
pCsr->filter.zTerm, pCsr->filter.nTerm, 0, isScan, &pCsr->csr
|
||||
);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3Fts3SegReaderStart(pFts3, &pCsr->csr, &pCsr->filter);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ) rc = fts3auxNextMethod(pCursor);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** xEof - Return true if the cursor is at EOF, or false otherwise.
|
||||
*/
|
||||
static int fts3auxEofMethod(sqlite3_vtab_cursor *pCursor){
|
||||
Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
|
||||
return pCsr->isEof;
|
||||
}
|
||||
|
||||
/*
|
||||
** xColumn - Return a column value.
|
||||
*/
|
||||
static int fts3auxColumnMethod(
|
||||
sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
|
||||
sqlite3_context *pContext, /* Context for sqlite3_result_xxx() calls */
|
||||
int iCol /* Index of column to read value from */
|
||||
){
|
||||
Fts3auxCursor *p = (Fts3auxCursor *)pCursor;
|
||||
|
||||
assert( p->isEof==0 );
|
||||
if( iCol==0 ){ /* Column "term" */
|
||||
sqlite3_result_text(pContext, p->csr.zTerm, p->csr.nTerm, SQLITE_TRANSIENT);
|
||||
}else if( iCol==1 ){ /* Column "col" */
|
||||
if( p->iCol ){
|
||||
sqlite3_result_int(pContext, p->iCol-1);
|
||||
}else{
|
||||
sqlite3_result_text(pContext, "*", -1, SQLITE_STATIC);
|
||||
}
|
||||
}else if( iCol==2 ){ /* Column "documents" */
|
||||
sqlite3_result_int64(pContext, p->aStat[p->iCol].nDoc);
|
||||
}else{ /* Column "occurrences" */
|
||||
sqlite3_result_int64(pContext, p->aStat[p->iCol].nOcc);
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xRowid - Return the current rowid for the cursor.
|
||||
*/
|
||||
static int fts3auxRowidMethod(
|
||||
sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
|
||||
sqlite_int64 *pRowid /* OUT: Rowid value */
|
||||
){
|
||||
Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
|
||||
*pRowid = pCsr->iRowid;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Register the fts3aux module with database connection db. Return SQLITE_OK
|
||||
** if successful or an error code if sqlite3_create_module() fails.
|
||||
*/
|
||||
int sqlite3Fts3InitAux(sqlite3 *db){
|
||||
static const sqlite3_module fts3aux_module = {
|
||||
0, /* iVersion */
|
||||
fts3auxConnectMethod, /* xCreate */
|
||||
fts3auxConnectMethod, /* xConnect */
|
||||
fts3auxBestIndexMethod, /* xBestIndex */
|
||||
fts3auxDisconnectMethod, /* xDisconnect */
|
||||
fts3auxDisconnectMethod, /* xDestroy */
|
||||
fts3auxOpenMethod, /* xOpen */
|
||||
fts3auxCloseMethod, /* xClose */
|
||||
fts3auxFilterMethod, /* xFilter */
|
||||
fts3auxNextMethod, /* xNext */
|
||||
fts3auxEofMethod, /* xEof */
|
||||
fts3auxColumnMethod, /* xColumn */
|
||||
fts3auxRowidMethod, /* xRowid */
|
||||
0, /* xUpdate */
|
||||
0, /* xBegin */
|
||||
0, /* xSync */
|
||||
0, /* xCommit */
|
||||
0, /* xRollback */
|
||||
0, /* xFindFunction */
|
||||
0, /* xRename */
|
||||
0, /* xSavepoint */
|
||||
0, /* xRelease */
|
||||
0 /* xRollbackTo */
|
||||
};
|
||||
int rc; /* Return code */
|
||||
|
||||
rc = sqlite3_create_module(db, "fts4aux", &fts3aux_module, 0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */
|
||||
@@ -15,6 +15,7 @@
|
||||
** syntax is relatively simple, the whole tokenizer/parser system is
|
||||
** hand-coded.
|
||||
*/
|
||||
#include "fts3Int.h"
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
|
||||
/*
|
||||
@@ -77,16 +78,25 @@ int sqlite3_fts3_enable_parentheses = 0;
|
||||
*/
|
||||
#define SQLITE_FTS3_DEFAULT_NEAR_PARAM 10
|
||||
|
||||
#include "fts3Int.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
/*
|
||||
** isNot:
|
||||
** This variable is used by function getNextNode(). When getNextNode() is
|
||||
** called, it sets ParseContext.isNot to true if the 'next node' is a
|
||||
** FTSQUERY_PHRASE with a unary "-" attached to it. i.e. "mysql" in the
|
||||
** FTS3 query "sqlite -mysql". Otherwise, ParseContext.isNot is set to
|
||||
** zero.
|
||||
*/
|
||||
typedef struct ParseContext ParseContext;
|
||||
struct ParseContext {
|
||||
sqlite3_tokenizer *pTokenizer; /* Tokenizer module */
|
||||
const char **azCol; /* Array of column names for fts3 table */
|
||||
int bFts4; /* True to allow FTS4-only syntax */
|
||||
int nCol; /* Number of entries in azCol[] */
|
||||
int iDefaultCol; /* Default column to query */
|
||||
int isNot; /* True if getNextNode() sees a unary - */
|
||||
sqlite3_context *pCtx; /* Write error message here */
|
||||
int nNest; /* Number of nested brackets */
|
||||
};
|
||||
@@ -106,6 +116,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 +165,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;
|
||||
@@ -160,9 +181,21 @@ static int getNextToken(
|
||||
pRet->pPhrase->aToken[0].isPrefix = 1;
|
||||
iEnd++;
|
||||
}
|
||||
if( !sqlite3_fts3_enable_parentheses && iStart>0 && z[iStart-1]=='-' ){
|
||||
pRet->pPhrase->isNot = 1;
|
||||
|
||||
while( 1 ){
|
||||
if( !sqlite3_fts3_enable_parentheses
|
||||
&& iStart>0 && z[iStart-1]=='-'
|
||||
){
|
||||
pParse->isNot = 1;
|
||||
iStart--;
|
||||
}else if( pParse->bFts4 && iStart>0 && z[iStart-1]=='^' ){
|
||||
pRet->pPhrase->aToken[0].bFirst = 1;
|
||||
iStart--;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
nConsumed = iEnd;
|
||||
}
|
||||
@@ -213,35 +246,56 @@ static int getNextString(
|
||||
char *zTemp = 0;
|
||||
int nTemp = 0;
|
||||
|
||||
const int nSpace = sizeof(Fts3Expr) + sizeof(Fts3Phrase);
|
||||
int nToken = 0;
|
||||
|
||||
/* The final Fts3Expr data structure, including the Fts3Phrase,
|
||||
** Fts3PhraseToken structures token buffers are all stored as a single
|
||||
** allocation so that the expression can be freed with a single call to
|
||||
** sqlite3_free(). Setting this up requires a two pass approach.
|
||||
**
|
||||
** The first pass, in the block below, uses a tokenizer cursor to iterate
|
||||
** through the tokens in the expression. This pass uses fts3ReallocOrFree()
|
||||
** to assemble data in two dynamic buffers:
|
||||
**
|
||||
** Buffer p: Points to the Fts3Expr structure, followed by the Fts3Phrase
|
||||
** structure, followed by the array of Fts3PhraseToken
|
||||
** structures. This pass only populates the Fts3PhraseToken array.
|
||||
**
|
||||
** Buffer zTemp: Contains copies of all tokens.
|
||||
**
|
||||
** The second pass, in the block that begins "if( rc==SQLITE_DONE )" below,
|
||||
** appends buffer zTemp to buffer p, and fills in the Fts3Expr and Fts3Phrase
|
||||
** structures.
|
||||
*/
|
||||
rc = pModule->xOpen(pTokenizer, zInput, nInput, &pCursor);
|
||||
if( rc==SQLITE_OK ){
|
||||
int ii;
|
||||
pCursor->pTokenizer = pTokenizer;
|
||||
for(ii=0; rc==SQLITE_OK; ii++){
|
||||
const char *zToken;
|
||||
int nToken, iBegin, iEnd, iPos;
|
||||
rc = pModule->xNext(pCursor, &zToken, &nToken, &iBegin, &iEnd, &iPos);
|
||||
const char *zByte;
|
||||
int nByte, iBegin, iEnd, iPos;
|
||||
rc = pModule->xNext(pCursor, &zByte, &nByte, &iBegin, &iEnd, &iPos);
|
||||
if( rc==SQLITE_OK ){
|
||||
int nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase);
|
||||
p = fts3ReallocOrFree(p, nByte+ii*sizeof(struct PhraseToken));
|
||||
zTemp = fts3ReallocOrFree(zTemp, nTemp + nToken);
|
||||
if( !p || !zTemp ){
|
||||
goto no_mem;
|
||||
}
|
||||
if( ii==0 ){
|
||||
memset(p, 0, nByte);
|
||||
p->pPhrase = (Fts3Phrase *)&p[1];
|
||||
}
|
||||
p->pPhrase = (Fts3Phrase *)&p[1];
|
||||
p->pPhrase->nToken = ii+1;
|
||||
p->pPhrase->aToken[ii].n = nToken;
|
||||
memcpy(&zTemp[nTemp], zToken, nToken);
|
||||
nTemp += nToken;
|
||||
if( iEnd<nInput && zInput[iEnd]=='*' ){
|
||||
p->pPhrase->aToken[ii].isPrefix = 1;
|
||||
}else{
|
||||
p->pPhrase->aToken[ii].isPrefix = 0;
|
||||
}
|
||||
Fts3PhraseToken *pToken;
|
||||
|
||||
p = fts3ReallocOrFree(p, nSpace + ii*sizeof(Fts3PhraseToken));
|
||||
if( !p ) goto no_mem;
|
||||
|
||||
zTemp = fts3ReallocOrFree(zTemp, nTemp + nByte);
|
||||
if( !zTemp ) goto no_mem;
|
||||
|
||||
assert( nToken==ii );
|
||||
pToken = &((Fts3Phrase *)(&p[1]))->aToken[ii];
|
||||
memset(pToken, 0, sizeof(Fts3PhraseToken));
|
||||
|
||||
memcpy(&zTemp[nTemp], zByte, nByte);
|
||||
nTemp += nByte;
|
||||
|
||||
pToken->n = nByte;
|
||||
pToken->isPrefix = (iEnd<nInput && zInput[iEnd]=='*');
|
||||
pToken->bFirst = (iBegin>0 && zInput[iBegin-1]=='^');
|
||||
nToken = ii+1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,28 +305,28 @@ static int getNextString(
|
||||
|
||||
if( rc==SQLITE_DONE ){
|
||||
int jj;
|
||||
char *zNew = NULL;
|
||||
int nNew = 0;
|
||||
int nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase);
|
||||
nByte += (p?(p->pPhrase->nToken-1):0) * sizeof(struct PhraseToken);
|
||||
p = fts3ReallocOrFree(p, nByte + nTemp);
|
||||
if( !p ){
|
||||
goto no_mem;
|
||||
}
|
||||
if( zTemp ){
|
||||
zNew = &(((char *)p)[nByte]);
|
||||
memcpy(zNew, zTemp, nTemp);
|
||||
}else{
|
||||
memset(p, 0, nByte+nTemp);
|
||||
}
|
||||
p->pPhrase = (Fts3Phrase *)&p[1];
|
||||
for(jj=0; jj<p->pPhrase->nToken; jj++){
|
||||
p->pPhrase->aToken[jj].z = &zNew[nNew];
|
||||
nNew += p->pPhrase->aToken[jj].n;
|
||||
}
|
||||
sqlite3_free(zTemp);
|
||||
char *zBuf = 0;
|
||||
|
||||
p = fts3ReallocOrFree(p, nSpace + nToken*sizeof(Fts3PhraseToken) + nTemp);
|
||||
if( !p ) goto no_mem;
|
||||
memset(p, 0, (char *)&(((Fts3Phrase *)&p[1])->aToken[0])-(char *)p);
|
||||
p->eType = FTSQUERY_PHRASE;
|
||||
p->pPhrase = (Fts3Phrase *)&p[1];
|
||||
p->pPhrase->iColumn = pParse->iDefaultCol;
|
||||
p->pPhrase->nToken = nToken;
|
||||
|
||||
zBuf = (char *)&p->pPhrase->aToken[nToken];
|
||||
if( zTemp ){
|
||||
memcpy(zBuf, zTemp, nTemp);
|
||||
sqlite3_free(zTemp);
|
||||
}else{
|
||||
assert( nTemp==0 );
|
||||
}
|
||||
|
||||
for(jj=0; jj<p->pPhrase->nToken; jj++){
|
||||
p->pPhrase->aToken[jj].z = zBuf;
|
||||
zBuf += p->pPhrase->aToken[jj].n;
|
||||
}
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
|
||||
@@ -329,6 +383,8 @@ static int getNextNode(
|
||||
const char *zInput = z;
|
||||
int nInput = n;
|
||||
|
||||
pParse->isNot = 0;
|
||||
|
||||
/* Skip over any whitespace before checking for a keyword, an open or
|
||||
** close bracket, or a quoted string.
|
||||
*/
|
||||
@@ -372,11 +428,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 +449,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 ){
|
||||
@@ -549,16 +603,15 @@ static int fts3ExprParse(
|
||||
int isPhrase;
|
||||
|
||||
if( !sqlite3_fts3_enable_parentheses
|
||||
&& p->eType==FTSQUERY_PHRASE && p->pPhrase->isNot
|
||||
&& p->eType==FTSQUERY_PHRASE && pParse->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 ){
|
||||
@@ -568,7 +621,6 @@ static int fts3ExprParse(
|
||||
p = pPrev;
|
||||
}else{
|
||||
int eType = p->eType;
|
||||
assert( eType!=FTSQUERY_PHRASE || !p->pPhrase->isNot );
|
||||
isPhrase = (eType==FTSQUERY_PHRASE || p->pLeft);
|
||||
|
||||
/* The isRequirePhrase variable is set to true if a phrase or
|
||||
@@ -586,13 +638,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;
|
||||
@@ -695,6 +746,7 @@ exprparse_out:
|
||||
int sqlite3Fts3ExprParse(
|
||||
sqlite3_tokenizer *pTokenizer, /* Tokenizer module */
|
||||
char **azCol, /* Array of column names for fts3 table */
|
||||
int bFts4, /* True to allow FTS4-only syntax */
|
||||
int nCol, /* Number of entries in azCol[] */
|
||||
int iDefaultCol, /* Default column to query */
|
||||
const char *z, int n, /* Text of MATCH query */
|
||||
@@ -708,6 +760,7 @@ int sqlite3Fts3ExprParse(
|
||||
sParse.nCol = nCol;
|
||||
sParse.iDefaultCol = iDefaultCol;
|
||||
sParse.nNest = 0;
|
||||
sParse.bFts4 = bFts4;
|
||||
if( z==0 ){
|
||||
*ppExpr = 0;
|
||||
return SQLITE_OK;
|
||||
@@ -732,9 +785,11 @@ int sqlite3Fts3ExprParse(
|
||||
*/
|
||||
void sqlite3Fts3ExprFree(Fts3Expr *p){
|
||||
if( p ){
|
||||
assert( p->eType==FTSQUERY_PHRASE || p->pPhrase==0 );
|
||||
sqlite3Fts3ExprFree(p->pLeft);
|
||||
sqlite3Fts3ExprFree(p->pRight);
|
||||
sqlite3_free(p->aDoclist);
|
||||
sqlite3Fts3EvalPhraseCleanup(p->pPhrase);
|
||||
sqlite3_free(p->aMI);
|
||||
sqlite3_free(p);
|
||||
}
|
||||
}
|
||||
@@ -777,47 +832,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 0", zBuf, pPhrase->iColumn);
|
||||
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 +909,7 @@ static void fts3ExprTest(
|
||||
int nCol;
|
||||
int ii;
|
||||
Fts3Expr *pExpr;
|
||||
char *zBuf = 0;
|
||||
sqlite3 *db = sqlite3_context_db_handle(context);
|
||||
|
||||
if( argc<3 ){
|
||||
@@ -888,20 +950,19 @@ static void fts3ExprTest(
|
||||
}
|
||||
|
||||
rc = sqlite3Fts3ExprParse(
|
||||
pTokenizer, azCol, nCol, nCol, zExpr, nExpr, &pExpr
|
||||
pTokenizer, azCol, 0, 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);
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
** * The FTS3 module is being built into the core of
|
||||
** SQLite (in which case SQLITE_ENABLE_FTS3 is defined).
|
||||
*/
|
||||
#include "fts3Int.h"
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "sqlite3.h"
|
||||
#include "fts3_hash.h"
|
||||
|
||||
/*
|
||||
|
||||
@@ -10,10 +10,8 @@
|
||||
**
|
||||
*************************************************************************
|
||||
** This file implements a tokenizer for fts3 based on the ICU library.
|
||||
**
|
||||
** $Id: fts3_icu.c,v 1.3 2008/09/01 18:34:20 danielk1977 Exp $
|
||||
*/
|
||||
|
||||
#include "fts3Int.h"
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
#ifdef SQLITE_ENABLE_ICU
|
||||
|
||||
|
||||
@@ -22,9 +22,8 @@
|
||||
** * The FTS3 module is being built into the core of
|
||||
** SQLite (in which case SQLITE_ENABLE_FTS3 is defined).
|
||||
*/
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
|
||||
#include "fts3Int.h"
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
@@ -343,7 +342,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);
|
||||
|
||||
@@ -11,12 +11,28 @@
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "fts3Int.h"
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
|
||||
#include "fts3Int.h"
|
||||
#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 */
|
||||
};
|
||||
|
||||
@@ -158,92 +176,24 @@ static int fts3ExprIterate(
|
||||
return fts3ExprIterate2(pExpr, &iPhrase, x, pCtx);
|
||||
}
|
||||
|
||||
/*
|
||||
** The argument to this function is always a phrase node. Its doclist
|
||||
** (Fts3Expr.aDoclist[]) and the doclists associated with all phrase nodes
|
||||
** to the left of this one in the query tree have already been loaded.
|
||||
**
|
||||
** If this phrase node is part of a series of phrase nodes joined by
|
||||
** NEAR operators (and is not the left-most of said series), then elements are
|
||||
** removed from the phrases doclist consistent with the NEAR restriction. If
|
||||
** required, elements may be removed from the doclists of phrases to the
|
||||
** left of this one that are part of the same series of NEAR operator
|
||||
** connected phrases.
|
||||
**
|
||||
** If an OOM error occurs, SQLITE_NOMEM is returned. Otherwise, SQLITE_OK.
|
||||
*/
|
||||
static int fts3ExprNearTrim(Fts3Expr *pExpr){
|
||||
int rc = SQLITE_OK;
|
||||
Fts3Expr *pParent = pExpr->pParent;
|
||||
|
||||
assert( pExpr->eType==FTSQUERY_PHRASE );
|
||||
while( rc==SQLITE_OK
|
||||
&& pParent
|
||||
&& pParent->eType==FTSQUERY_NEAR
|
||||
&& pParent->pRight==pExpr
|
||||
){
|
||||
/* This expression (pExpr) is the right-hand-side of a NEAR operator.
|
||||
** Find the expression to the left of the same operator.
|
||||
*/
|
||||
int nNear = pParent->nNear;
|
||||
Fts3Expr *pLeft = pParent->pLeft;
|
||||
|
||||
if( pLeft->eType!=FTSQUERY_PHRASE ){
|
||||
assert( pLeft->eType==FTSQUERY_NEAR );
|
||||
assert( pLeft->pRight->eType==FTSQUERY_PHRASE );
|
||||
pLeft = pLeft->pRight;
|
||||
}
|
||||
|
||||
rc = sqlite3Fts3ExprNearTrim(pLeft, pExpr, nNear);
|
||||
|
||||
pExpr = pLeft;
|
||||
pParent = pExpr->pParent;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This is an fts3ExprIterate() callback used while loading the doclists
|
||||
** 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;
|
||||
Fts3Phrase *pPhrase = pExpr->pPhrase;
|
||||
LoadDoclistCtx *p = (LoadDoclistCtx *)ctx;
|
||||
|
||||
UNUSED_PARAMETER(iPhrase);
|
||||
|
||||
p->nPhrase++;
|
||||
p->nToken += pExpr->pPhrase->nToken;
|
||||
|
||||
if( pExpr->isLoaded==0 ){
|
||||
rc = sqlite3Fts3ExprLoadDoclist(p->pTab, pExpr);
|
||||
pExpr->isLoaded = 1;
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts3ExprNearTrim(pExpr);
|
||||
}
|
||||
}
|
||||
p->nToken += pPhrase->nToken;
|
||||
|
||||
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 +211,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
|
||||
@@ -404,11 +363,12 @@ static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){
|
||||
|
||||
pPhrase->nToken = pExpr->pPhrase->nToken;
|
||||
|
||||
pCsr = sqlite3Fts3FindPositions(pExpr, p->pCsr->iPrevId, p->iCol);
|
||||
pCsr = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol);
|
||||
if( pCsr ){
|
||||
int iFirst = 0;
|
||||
pPhrase->pList = pCsr;
|
||||
fts3GetDeltaPosition(&pCsr, &iFirst);
|
||||
assert( iFirst>=0 );
|
||||
pPhrase->pHead = pCsr;
|
||||
pPhrase->pTail = pCsr;
|
||||
pPhrase->iHead = iFirst;
|
||||
@@ -761,143 +721,452 @@ static int fts3ColumnlistCount(char **ppCollist){
|
||||
return nEntry;
|
||||
}
|
||||
|
||||
static void fts3LoadColumnlistCounts(char **pp, u32 *aOut, int isGlobal){
|
||||
char *pCsr = *pp;
|
||||
while( *pCsr ){
|
||||
int nHit;
|
||||
sqlite3_int64 iCol = 0;
|
||||
if( *pCsr==0x01 ){
|
||||
pCsr++;
|
||||
pCsr += sqlite3Fts3GetVarint(pCsr, &iCol);
|
||||
}
|
||||
nHit = fts3ColumnlistCount(&pCsr);
|
||||
assert( nHit>0 );
|
||||
if( isGlobal ){
|
||||
aOut[iCol*3+1]++;
|
||||
}
|
||||
aOut[iCol*3] += nHit;
|
||||
}
|
||||
pCsr++;
|
||||
*pp = pCsr;
|
||||
}
|
||||
|
||||
/*
|
||||
** 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;
|
||||
char *pEnd;
|
||||
const int iStart = 2 + (iPhrase * p->nCol * 3) + 1;
|
||||
|
||||
assert( pExpr->isLoaded );
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
return sqlite3Fts3EvalPhraseStats(
|
||||
p->pCursor, pExpr, &p->aMatchinfo[3*iPhrase*p->nCol]
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
** 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 */
|
||||
){
|
||||
MatchInfo *p = (MatchInfo *)pCtx;
|
||||
int iStart = iPhrase * p->nCol * 3;
|
||||
int i;
|
||||
|
||||
if( pExpr->aDoclist ){
|
||||
for(i=0; i<p->nCol; i++){
|
||||
char *pCsr;
|
||||
int iStart = 2 + (iPhrase * p->nCol * 3);
|
||||
int i;
|
||||
|
||||
for(i=0; i<p->nCol; i++) p->aMatchinfo[iStart+i*3] = 0;
|
||||
|
||||
pCsr = sqlite3Fts3FindPositions(pExpr, p->pCursor->iPrevId, -1);
|
||||
pCsr = sqlite3Fts3EvalPhrasePoslist(p->pCursor, pExpr, i);
|
||||
if( pCsr ){
|
||||
fts3LoadColumnlistCounts(&pCsr, &p->aMatchinfo[iStart], 0);
|
||||
p->aMatchinfo[iStart+i*3] = fts3ColumnlistCount(&pCsr);
|
||||
}else{
|
||||
p->aMatchinfo[iStart+i*3] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if( nDoc==0 ) return FTS_CORRUPT_VTAB;
|
||||
*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 */
|
||||
int iPosOffset; /* Tokens count up to end of this phrase */
|
||||
char *pRead; /* Cursor used to iterate through aDoclist */
|
||||
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 || iRead==1 ){
|
||||
pRead = 0;
|
||||
rc = 1;
|
||||
}else{
|
||||
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;
|
||||
}
|
||||
|
||||
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 */
|
||||
|
||||
for(i=0; i<pInfo->nPhrase; i++){
|
||||
LcsIterator *pIt = &aIter[i];
|
||||
pIt->pRead = sqlite3Fts3EvalPhrasePoslist(pCsr, pIt->pExpr, iCol);
|
||||
if( pIt->pRead ){
|
||||
pIt->iPos = pIt->iPosOffset;
|
||||
fts3LcsIteratorAdvance(&aIter[i]);
|
||||
nLive++;
|
||||
}
|
||||
}
|
||||
|
||||
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( pIter->pRead==0 ){
|
||||
/* 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 = 0;
|
||||
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++){
|
||||
u32 iVal;
|
||||
sqlite3_int64 nToken;
|
||||
a += sqlite3Fts3GetVarint(a, &nToken);
|
||||
iVal = (u32)(((u32)(nToken&0xffffffff)+nDoc/2)/nDoc);
|
||||
pInfo->aMatchinfo[iCol] = iVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 +1227,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 +1261,7 @@ void sqlite3Fts3Snippet(
|
||||
}
|
||||
|
||||
snippet_out:
|
||||
sqlite3Fts3SegmentsClose(pTab);
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3_result_error_code(pCtx, rc);
|
||||
sqlite3_free(res.z);
|
||||
@@ -1011,6 +1281,7 @@ struct TermOffset {
|
||||
};
|
||||
|
||||
struct TermOffsetCtx {
|
||||
Fts3Cursor *pCsr;
|
||||
int iCol; /* Column of table to populate aTerm for */
|
||||
int iTerm;
|
||||
sqlite3_int64 iDocid;
|
||||
@@ -1028,7 +1299,7 @@ static int fts3ExprTermOffsetInit(Fts3Expr *pExpr, int iPhrase, void *ctx){
|
||||
int iPos = 0; /* First position in position-list */
|
||||
|
||||
UNUSED_PARAMETER(iPhrase);
|
||||
pList = sqlite3Fts3FindPositions(pExpr, p->iDocid, p->iCol);
|
||||
pList = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol);
|
||||
nTerm = pExpr->pPhrase->nToken;
|
||||
if( pList ){
|
||||
fts3GetDeltaPosition(&pList, &iPos);
|
||||
@@ -1081,6 +1352,7 @@ void sqlite3Fts3Offsets(
|
||||
goto offsets_out;
|
||||
}
|
||||
sCtx.iDocid = pCsr->iPrevId;
|
||||
sCtx.pCsr = pCsr;
|
||||
|
||||
/* Loop through the table columns, appending offset information to
|
||||
** string-buffer res for each column.
|
||||
@@ -1138,7 +1410,7 @@ void sqlite3Fts3Offsets(
|
||||
|
||||
if( !pTerm ){
|
||||
/* All offsets for this column have been gathered. */
|
||||
break;
|
||||
rc = SQLITE_DONE;
|
||||
}else{
|
||||
assert( iCurrent<=iMinPos );
|
||||
if( 0==(0xFE&*pTerm->pList) ){
|
||||
@@ -1155,8 +1427,8 @@ void sqlite3Fts3Offsets(
|
||||
"%d %d %d %d ", iCol, pTerm-sCtx.aTerm, iStart, iEnd-iStart
|
||||
);
|
||||
rc = fts3StringAppend(&res, aBuffer, -1);
|
||||
}else if( rc==SQLITE_DONE ){
|
||||
rc = SQLITE_CORRUPT;
|
||||
}else if( rc==SQLITE_DONE && pTab->zContentTbl==0 ){
|
||||
rc = FTS_CORRUPT_VTAB;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1171,6 +1443,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 +1456,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
** 2011 Jan 27
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
******************************************************************************
|
||||
**
|
||||
** This file is not part of the production FTS code. It is only used for
|
||||
** testing. It contains a virtual table implementation that provides direct
|
||||
** access to the full-text index of an FTS table.
|
||||
*/
|
||||
|
||||
#include "fts3Int.h"
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
#ifdef SQLITE_TEST
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct Fts3termTable Fts3termTable;
|
||||
typedef struct Fts3termCursor Fts3termCursor;
|
||||
|
||||
struct Fts3termTable {
|
||||
sqlite3_vtab base; /* Base class used by SQLite core */
|
||||
int iIndex; /* Index for Fts3Table.aIndex[] */
|
||||
Fts3Table *pFts3Tab;
|
||||
};
|
||||
|
||||
struct Fts3termCursor {
|
||||
sqlite3_vtab_cursor base; /* Base class used by SQLite core */
|
||||
Fts3MultiSegReader csr; /* Must be right after "base" */
|
||||
Fts3SegFilter filter;
|
||||
|
||||
int isEof; /* True if cursor is at EOF */
|
||||
char *pNext;
|
||||
|
||||
sqlite3_int64 iRowid; /* Current 'rowid' value */
|
||||
sqlite3_int64 iDocid; /* Current 'docid' value */
|
||||
int iCol; /* Current 'col' value */
|
||||
int iPos; /* Current 'pos' value */
|
||||
};
|
||||
|
||||
/*
|
||||
** Schema of the terms table.
|
||||
*/
|
||||
#define FTS3_TERMS_SCHEMA "CREATE TABLE x(term, docid, col, pos)"
|
||||
|
||||
/*
|
||||
** This function does all the work for both the xConnect and xCreate methods.
|
||||
** These tables have no persistent representation of their own, so xConnect
|
||||
** and xCreate are identical operations.
|
||||
*/
|
||||
static int fts3termConnectMethod(
|
||||
sqlite3 *db, /* Database connection */
|
||||
void *pCtx, /* Non-zero for an fts4prefix table */
|
||||
int argc, /* Number of elements in argv array */
|
||||
const char * const *argv, /* xCreate/xConnect argument array */
|
||||
sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
|
||||
char **pzErr /* OUT: sqlite3_malloc'd error message */
|
||||
){
|
||||
char const *zDb; /* Name of database (e.g. "main") */
|
||||
char const *zFts3; /* Name of fts3 table */
|
||||
int nDb; /* Result of strlen(zDb) */
|
||||
int nFts3; /* Result of strlen(zFts3) */
|
||||
int nByte; /* Bytes of space to allocate here */
|
||||
int rc; /* value returned by declare_vtab() */
|
||||
Fts3termTable *p; /* Virtual table object to return */
|
||||
int iIndex = 0;
|
||||
|
||||
if( argc==5 ){
|
||||
iIndex = atoi(argv[4]);
|
||||
argc--;
|
||||
}
|
||||
|
||||
/* The user should specify a single argument - the name of an fts3 table. */
|
||||
if( argc!=4 ){
|
||||
*pzErr = sqlite3_mprintf(
|
||||
"wrong number of arguments to fts4term constructor"
|
||||
);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
zDb = argv[1];
|
||||
nDb = strlen(zDb);
|
||||
zFts3 = argv[3];
|
||||
nFts3 = strlen(zFts3);
|
||||
|
||||
rc = sqlite3_declare_vtab(db, FTS3_TERMS_SCHEMA);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
nByte = sizeof(Fts3termTable) + sizeof(Fts3Table) + nDb + nFts3 + 2;
|
||||
p = (Fts3termTable *)sqlite3_malloc(nByte);
|
||||
if( !p ) return SQLITE_NOMEM;
|
||||
memset(p, 0, nByte);
|
||||
|
||||
p->pFts3Tab = (Fts3Table *)&p[1];
|
||||
p->pFts3Tab->zDb = (char *)&p->pFts3Tab[1];
|
||||
p->pFts3Tab->zName = &p->pFts3Tab->zDb[nDb+1];
|
||||
p->pFts3Tab->db = db;
|
||||
p->pFts3Tab->nIndex = iIndex+1;
|
||||
p->iIndex = iIndex;
|
||||
|
||||
memcpy((char *)p->pFts3Tab->zDb, zDb, nDb);
|
||||
memcpy((char *)p->pFts3Tab->zName, zFts3, nFts3);
|
||||
sqlite3Fts3Dequote((char *)p->pFts3Tab->zName);
|
||||
|
||||
*ppVtab = (sqlite3_vtab *)p;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function does the work for both the xDisconnect and xDestroy methods.
|
||||
** These tables have no persistent representation of their own, so xDisconnect
|
||||
** and xDestroy are identical operations.
|
||||
*/
|
||||
static int fts3termDisconnectMethod(sqlite3_vtab *pVtab){
|
||||
Fts3termTable *p = (Fts3termTable *)pVtab;
|
||||
Fts3Table *pFts3 = p->pFts3Tab;
|
||||
int i;
|
||||
|
||||
/* Free any prepared statements held */
|
||||
for(i=0; i<SizeofArray(pFts3->aStmt); i++){
|
||||
sqlite3_finalize(pFts3->aStmt[i]);
|
||||
}
|
||||
sqlite3_free(pFts3->zSegmentsTbl);
|
||||
sqlite3_free(p);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
#define FTS4AUX_EQ_CONSTRAINT 1
|
||||
#define FTS4AUX_GE_CONSTRAINT 2
|
||||
#define FTS4AUX_LE_CONSTRAINT 4
|
||||
|
||||
/*
|
||||
** xBestIndex - Analyze a WHERE and ORDER BY clause.
|
||||
*/
|
||||
static int fts3termBestIndexMethod(
|
||||
sqlite3_vtab *pVTab,
|
||||
sqlite3_index_info *pInfo
|
||||
){
|
||||
UNUSED_PARAMETER(pVTab);
|
||||
|
||||
/* This vtab naturally does "ORDER BY term, docid, col, pos". */
|
||||
if( pInfo->nOrderBy ){
|
||||
int i;
|
||||
for(i=0; i<pInfo->nOrderBy; i++){
|
||||
if( pInfo->aOrderBy[i].iColumn!=i || pInfo->aOrderBy[i].desc ) break;
|
||||
}
|
||||
if( i==pInfo->nOrderBy ){
|
||||
pInfo->orderByConsumed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xOpen - Open a cursor.
|
||||
*/
|
||||
static int fts3termOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
|
||||
Fts3termCursor *pCsr; /* Pointer to cursor object to return */
|
||||
|
||||
UNUSED_PARAMETER(pVTab);
|
||||
|
||||
pCsr = (Fts3termCursor *)sqlite3_malloc(sizeof(Fts3termCursor));
|
||||
if( !pCsr ) return SQLITE_NOMEM;
|
||||
memset(pCsr, 0, sizeof(Fts3termCursor));
|
||||
|
||||
*ppCsr = (sqlite3_vtab_cursor *)pCsr;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xClose - Close a cursor.
|
||||
*/
|
||||
static int fts3termCloseMethod(sqlite3_vtab_cursor *pCursor){
|
||||
Fts3Table *pFts3 = ((Fts3termTable *)pCursor->pVtab)->pFts3Tab;
|
||||
Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
|
||||
|
||||
sqlite3Fts3SegmentsClose(pFts3);
|
||||
sqlite3Fts3SegReaderFinish(&pCsr->csr);
|
||||
sqlite3_free(pCsr);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xNext - Advance the cursor to the next row, if any.
|
||||
*/
|
||||
static int fts3termNextMethod(sqlite3_vtab_cursor *pCursor){
|
||||
Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
|
||||
Fts3Table *pFts3 = ((Fts3termTable *)pCursor->pVtab)->pFts3Tab;
|
||||
int rc;
|
||||
sqlite3_int64 v;
|
||||
|
||||
/* Increment our pretend rowid value. */
|
||||
pCsr->iRowid++;
|
||||
|
||||
/* Advance to the next term in the full-text index. */
|
||||
if( pCsr->csr.aDoclist==0
|
||||
|| pCsr->pNext>=&pCsr->csr.aDoclist[pCsr->csr.nDoclist-1]
|
||||
){
|
||||
rc = sqlite3Fts3SegReaderStep(pFts3, &pCsr->csr);
|
||||
if( rc!=SQLITE_ROW ){
|
||||
pCsr->isEof = 1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
pCsr->iCol = 0;
|
||||
pCsr->iPos = 0;
|
||||
pCsr->iDocid = 0;
|
||||
pCsr->pNext = pCsr->csr.aDoclist;
|
||||
|
||||
/* Read docid */
|
||||
pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &pCsr->iDocid);
|
||||
}
|
||||
|
||||
pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
|
||||
if( v==0 ){
|
||||
pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
|
||||
pCsr->iDocid += v;
|
||||
pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
|
||||
pCsr->iCol = 0;
|
||||
pCsr->iPos = 0;
|
||||
}
|
||||
|
||||
if( v==1 ){
|
||||
pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
|
||||
pCsr->iCol += v;
|
||||
pCsr->iPos = 0;
|
||||
pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
|
||||
}
|
||||
|
||||
pCsr->iPos += (v - 2);
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xFilter - Initialize a cursor to point at the start of its data.
|
||||
*/
|
||||
static int fts3termFilterMethod(
|
||||
sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
|
||||
int idxNum, /* Strategy index */
|
||||
const char *idxStr, /* Unused */
|
||||
int nVal, /* Number of elements in apVal */
|
||||
sqlite3_value **apVal /* Arguments for the indexing scheme */
|
||||
){
|
||||
Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
|
||||
Fts3termTable *p = (Fts3termTable *)pCursor->pVtab;
|
||||
Fts3Table *pFts3 = p->pFts3Tab;
|
||||
int rc;
|
||||
|
||||
UNUSED_PARAMETER(nVal);
|
||||
UNUSED_PARAMETER(idxNum);
|
||||
UNUSED_PARAMETER(idxStr);
|
||||
UNUSED_PARAMETER(apVal);
|
||||
|
||||
assert( idxStr==0 && idxNum==0 );
|
||||
|
||||
/* In case this cursor is being reused, close and zero it. */
|
||||
testcase(pCsr->filter.zTerm);
|
||||
sqlite3Fts3SegReaderFinish(&pCsr->csr);
|
||||
memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr);
|
||||
|
||||
pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY;
|
||||
pCsr->filter.flags |= FTS3_SEGMENT_SCAN;
|
||||
|
||||
rc = sqlite3Fts3SegReaderCursor(pFts3, p->iIndex, FTS3_SEGCURSOR_ALL,
|
||||
pCsr->filter.zTerm, pCsr->filter.nTerm, 0, 1, &pCsr->csr
|
||||
);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3Fts3SegReaderStart(pFts3, &pCsr->csr, &pCsr->filter);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts3termNextMethod(pCursor);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** xEof - Return true if the cursor is at EOF, or false otherwise.
|
||||
*/
|
||||
static int fts3termEofMethod(sqlite3_vtab_cursor *pCursor){
|
||||
Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
|
||||
return pCsr->isEof;
|
||||
}
|
||||
|
||||
/*
|
||||
** xColumn - Return a column value.
|
||||
*/
|
||||
static int fts3termColumnMethod(
|
||||
sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
|
||||
sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */
|
||||
int iCol /* Index of column to read value from */
|
||||
){
|
||||
Fts3termCursor *p = (Fts3termCursor *)pCursor;
|
||||
|
||||
assert( iCol>=0 && iCol<=3 );
|
||||
switch( iCol ){
|
||||
case 0:
|
||||
sqlite3_result_text(pCtx, p->csr.zTerm, p->csr.nTerm, SQLITE_TRANSIENT);
|
||||
break;
|
||||
case 1:
|
||||
sqlite3_result_int64(pCtx, p->iDocid);
|
||||
break;
|
||||
case 2:
|
||||
sqlite3_result_int64(pCtx, p->iCol);
|
||||
break;
|
||||
default:
|
||||
sqlite3_result_int64(pCtx, p->iPos);
|
||||
break;
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xRowid - Return the current rowid for the cursor.
|
||||
*/
|
||||
static int fts3termRowidMethod(
|
||||
sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
|
||||
sqlite_int64 *pRowid /* OUT: Rowid value */
|
||||
){
|
||||
Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
|
||||
*pRowid = pCsr->iRowid;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Register the fts3term module with database connection db. Return SQLITE_OK
|
||||
** if successful or an error code if sqlite3_create_module() fails.
|
||||
*/
|
||||
int sqlite3Fts3InitTerm(sqlite3 *db){
|
||||
static const sqlite3_module fts3term_module = {
|
||||
0, /* iVersion */
|
||||
fts3termConnectMethod, /* xCreate */
|
||||
fts3termConnectMethod, /* xConnect */
|
||||
fts3termBestIndexMethod, /* xBestIndex */
|
||||
fts3termDisconnectMethod, /* xDisconnect */
|
||||
fts3termDisconnectMethod, /* xDestroy */
|
||||
fts3termOpenMethod, /* xOpen */
|
||||
fts3termCloseMethod, /* xClose */
|
||||
fts3termFilterMethod, /* xFilter */
|
||||
fts3termNextMethod, /* xNext */
|
||||
fts3termEofMethod, /* xEof */
|
||||
fts3termColumnMethod, /* xColumn */
|
||||
fts3termRowidMethod, /* xRowid */
|
||||
0, /* xUpdate */
|
||||
0, /* xBegin */
|
||||
0, /* xSync */
|
||||
0, /* xCommit */
|
||||
0, /* xRollback */
|
||||
0, /* xFindFunction */
|
||||
0 /* xRename */
|
||||
};
|
||||
int rc; /* Return code */
|
||||
|
||||
rc = sqlite3_create_module(db, "fts4term", &fts3term_module, 0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */
|
||||
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
** 2011 Jun 13
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
******************************************************************************
|
||||
**
|
||||
** This file is not part of the production FTS code. It is only used for
|
||||
** testing. It contains a Tcl command that can be used to test if a document
|
||||
** matches an FTS NEAR expression.
|
||||
*/
|
||||
|
||||
#include <tcl.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
|
||||
/* Required so that the "ifdef SQLITE_ENABLE_FTS3" below works */
|
||||
#include "fts3Int.h"
|
||||
|
||||
#define NM_MAX_TOKEN 12
|
||||
|
||||
typedef struct NearPhrase NearPhrase;
|
||||
typedef struct NearDocument NearDocument;
|
||||
typedef struct NearToken NearToken;
|
||||
|
||||
struct NearDocument {
|
||||
int nToken; /* Length of token in bytes */
|
||||
NearToken *aToken; /* Token array */
|
||||
};
|
||||
|
||||
struct NearToken {
|
||||
int n; /* Length of token in bytes */
|
||||
const char *z; /* Pointer to token string */
|
||||
};
|
||||
|
||||
struct NearPhrase {
|
||||
int nNear; /* Preceding NEAR value */
|
||||
int nToken; /* Number of tokens in this phrase */
|
||||
NearToken aToken[NM_MAX_TOKEN]; /* Array of tokens in this phrase */
|
||||
};
|
||||
|
||||
static int nm_phrase_match(
|
||||
NearPhrase *p,
|
||||
NearToken *aToken
|
||||
){
|
||||
int ii;
|
||||
|
||||
for(ii=0; ii<p->nToken; ii++){
|
||||
NearToken *pToken = &p->aToken[ii];
|
||||
if( pToken->n>0 && pToken->z[pToken->n-1]=='*' ){
|
||||
if( aToken[ii].n<(pToken->n-1) ) return 0;
|
||||
if( memcmp(aToken[ii].z, pToken->z, pToken->n-1) ) return 0;
|
||||
}else{
|
||||
if( aToken[ii].n!=pToken->n ) return 0;
|
||||
if( memcmp(aToken[ii].z, pToken->z, pToken->n) ) return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nm_near_chain(
|
||||
int iDir, /* Direction to iterate through aPhrase[] */
|
||||
NearDocument *pDoc, /* Document to match against */
|
||||
int iPos, /* Position at which iPhrase was found */
|
||||
int nPhrase, /* Size of phrase array */
|
||||
NearPhrase *aPhrase, /* Phrase array */
|
||||
int iPhrase /* Index of phrase found */
|
||||
){
|
||||
int iStart;
|
||||
int iStop;
|
||||
int ii;
|
||||
int nNear;
|
||||
int iPhrase2;
|
||||
NearPhrase *p;
|
||||
NearPhrase *pPrev;
|
||||
|
||||
assert( iDir==1 || iDir==-1 );
|
||||
|
||||
if( iDir==1 ){
|
||||
if( (iPhrase+1)==nPhrase ) return 1;
|
||||
nNear = aPhrase[iPhrase+1].nNear;
|
||||
}else{
|
||||
if( iPhrase==0 ) return 1;
|
||||
nNear = aPhrase[iPhrase].nNear;
|
||||
}
|
||||
pPrev = &aPhrase[iPhrase];
|
||||
iPhrase2 = iPhrase+iDir;
|
||||
p = &aPhrase[iPhrase2];
|
||||
|
||||
iStart = iPos - nNear - p->nToken;
|
||||
iStop = iPos + nNear + pPrev->nToken;
|
||||
|
||||
if( iStart<0 ) iStart = 0;
|
||||
if( iStop > pDoc->nToken - p->nToken ) iStop = pDoc->nToken - p->nToken;
|
||||
|
||||
for(ii=iStart; ii<=iStop; ii++){
|
||||
if( nm_phrase_match(p, &pDoc->aToken[ii]) ){
|
||||
if( nm_near_chain(iDir, pDoc, ii, nPhrase, aPhrase, iPhrase2) ) return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nm_match_count(
|
||||
NearDocument *pDoc, /* Document to match against */
|
||||
int nPhrase, /* Size of phrase array */
|
||||
NearPhrase *aPhrase, /* Phrase array */
|
||||
int iPhrase /* Index of phrase to count matches for */
|
||||
){
|
||||
int nOcc = 0;
|
||||
int ii;
|
||||
NearPhrase *p = &aPhrase[iPhrase];
|
||||
|
||||
for(ii=0; ii<(pDoc->nToken + 1 - p->nToken); ii++){
|
||||
if( nm_phrase_match(p, &pDoc->aToken[ii]) ){
|
||||
/* Test forward NEAR chain (i>iPhrase) */
|
||||
if( 0==nm_near_chain(1, pDoc, ii, nPhrase, aPhrase, iPhrase) ) continue;
|
||||
|
||||
/* Test reverse NEAR chain (i<iPhrase) */
|
||||
if( 0==nm_near_chain(-1, pDoc, ii, nPhrase, aPhrase, iPhrase) ) continue;
|
||||
|
||||
/* This is a real match. Increment the counter. */
|
||||
nOcc++;
|
||||
}
|
||||
}
|
||||
|
||||
return nOcc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Tclcmd: fts3_near_match DOCUMENT EXPR ?OPTIONS?
|
||||
*/
|
||||
static int fts3_near_match_cmd(
|
||||
ClientData clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int nTotal = 0;
|
||||
int rc;
|
||||
int ii;
|
||||
int nPhrase;
|
||||
NearPhrase *aPhrase = 0;
|
||||
NearDocument doc = {0, 0};
|
||||
Tcl_Obj **apDocToken;
|
||||
Tcl_Obj *pRet;
|
||||
Tcl_Obj *pPhrasecount = 0;
|
||||
|
||||
Tcl_Obj **apExprToken;
|
||||
int nExprToken;
|
||||
|
||||
/* Must have 3 or more arguments. */
|
||||
if( objc<3 || (objc%2)==0 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DOCUMENT EXPR ?OPTION VALUE?...");
|
||||
rc = TCL_ERROR;
|
||||
goto near_match_out;
|
||||
}
|
||||
|
||||
for(ii=3; ii<objc; ii+=2){
|
||||
enum NM_enum { NM_PHRASECOUNTS };
|
||||
struct TestnmSubcmd {
|
||||
char *zName;
|
||||
enum NM_enum eOpt;
|
||||
} aOpt[] = {
|
||||
{ "-phrasecountvar", NM_PHRASECOUNTS },
|
||||
{ 0, 0 }
|
||||
};
|
||||
int iOpt;
|
||||
if( Tcl_GetIndexFromObjStruct(
|
||||
interp, objv[ii], aOpt, sizeof(aOpt[0]), "option", 0, &iOpt)
|
||||
){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
switch( aOpt[iOpt].eOpt ){
|
||||
case NM_PHRASECOUNTS:
|
||||
pPhrasecount = objv[ii+1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc = Tcl_ListObjGetElements(interp, objv[1], &doc.nToken, &apDocToken);
|
||||
if( rc!=TCL_OK ) goto near_match_out;
|
||||
doc.aToken = (NearToken *)ckalloc(doc.nToken*sizeof(NearToken));
|
||||
for(ii=0; ii<doc.nToken; ii++){
|
||||
doc.aToken[ii].z = Tcl_GetStringFromObj(apDocToken[ii], &doc.aToken[ii].n);
|
||||
}
|
||||
|
||||
rc = Tcl_ListObjGetElements(interp, objv[2], &nExprToken, &apExprToken);
|
||||
if( rc!=TCL_OK ) goto near_match_out;
|
||||
|
||||
nPhrase = (nExprToken + 1) / 2;
|
||||
aPhrase = (NearPhrase *)ckalloc(nPhrase * sizeof(NearPhrase));
|
||||
memset(aPhrase, 0, nPhrase * sizeof(NearPhrase));
|
||||
for(ii=0; ii<nPhrase; ii++){
|
||||
Tcl_Obj *pPhrase = apExprToken[ii*2];
|
||||
Tcl_Obj **apToken;
|
||||
int nToken;
|
||||
int jj;
|
||||
|
||||
rc = Tcl_ListObjGetElements(interp, pPhrase, &nToken, &apToken);
|
||||
if( rc!=TCL_OK ) goto near_match_out;
|
||||
if( nToken>NM_MAX_TOKEN ){
|
||||
Tcl_AppendResult(interp, "Too many tokens in phrase", 0);
|
||||
rc = TCL_ERROR;
|
||||
goto near_match_out;
|
||||
}
|
||||
for(jj=0; jj<nToken; jj++){
|
||||
NearToken *pT = &aPhrase[ii].aToken[jj];
|
||||
pT->z = Tcl_GetStringFromObj(apToken[jj], &pT->n);
|
||||
}
|
||||
aPhrase[ii].nToken = nToken;
|
||||
}
|
||||
for(ii=1; ii<nPhrase; ii++){
|
||||
Tcl_Obj *pNear = apExprToken[2*ii-1];
|
||||
int nNear;
|
||||
rc = Tcl_GetIntFromObj(interp, pNear, &nNear);
|
||||
if( rc!=TCL_OK ) goto near_match_out;
|
||||
aPhrase[ii].nNear = nNear;
|
||||
}
|
||||
|
||||
pRet = Tcl_NewObj();
|
||||
Tcl_IncrRefCount(pRet);
|
||||
for(ii=0; ii<nPhrase; ii++){
|
||||
int nOcc = nm_match_count(&doc, nPhrase, aPhrase, ii);
|
||||
Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(nOcc));
|
||||
nTotal += nOcc;
|
||||
}
|
||||
if( pPhrasecount ){
|
||||
Tcl_ObjSetVar2(interp, pPhrasecount, 0, pRet, 0);
|
||||
}
|
||||
Tcl_DecrRefCount(pRet);
|
||||
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(nTotal>0));
|
||||
|
||||
near_match_out:
|
||||
ckfree((char *)aPhrase);
|
||||
ckfree((char *)doc.aToken);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Tclcmd: fts3_configure_incr_load ?CHUNKSIZE THRESHOLD?
|
||||
**
|
||||
** Normally, FTS uses hard-coded values to determine the minimum doclist
|
||||
** size eligible for incremental loading, and the size of the chunks loaded
|
||||
** when a doclist is incrementally loaded. This command allows the built-in
|
||||
** values to be overridden for testing purposes.
|
||||
**
|
||||
** If present, the first argument is the chunksize in bytes to load doclists
|
||||
** in. The second argument is the minimum doclist size in bytes to use
|
||||
** incremental loading with.
|
||||
**
|
||||
** Whether or not the arguments are present, this command returns a list of
|
||||
** two integers - the initial chunksize and threshold when the command is
|
||||
** invoked. This can be used to restore the default behaviour after running
|
||||
** tests. For example:
|
||||
**
|
||||
** # Override incr-load settings for testing:
|
||||
** set cfg [fts3_configure_incr_load $new_chunksize $new_threshold]
|
||||
**
|
||||
** .... run tests ....
|
||||
**
|
||||
** # Restore initial incr-load settings:
|
||||
** eval fts3_configure_incr_load $cfg
|
||||
*/
|
||||
static int fts3_configure_incr_load_cmd(
|
||||
ClientData clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
#ifdef SQLITE_ENABLE_FTS3
|
||||
extern int test_fts3_node_chunksize;
|
||||
extern int test_fts3_node_chunk_threshold;
|
||||
Tcl_Obj *pRet;
|
||||
|
||||
if( objc!=1 && objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "?CHUNKSIZE THRESHOLD?");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
pRet = Tcl_NewObj();
|
||||
Tcl_IncrRefCount(pRet);
|
||||
Tcl_ListObjAppendElement(
|
||||
interp, pRet, Tcl_NewIntObj(test_fts3_node_chunksize));
|
||||
Tcl_ListObjAppendElement(
|
||||
interp, pRet, Tcl_NewIntObj(test_fts3_node_chunk_threshold));
|
||||
|
||||
if( objc==3 ){
|
||||
int iArg1;
|
||||
int iArg2;
|
||||
if( Tcl_GetIntFromObj(interp, objv[1], &iArg1)
|
||||
|| Tcl_GetIntFromObj(interp, objv[2], &iArg2)
|
||||
){
|
||||
Tcl_DecrRefCount(pRet);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
test_fts3_node_chunksize = iArg1;
|
||||
test_fts3_node_chunk_threshold = iArg2;
|
||||
}
|
||||
|
||||
Tcl_SetObjResult(interp, pRet);
|
||||
Tcl_DecrRefCount(pRet);
|
||||
#endif
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
int Sqlitetestfts3_Init(Tcl_Interp *interp){
|
||||
Tcl_CreateObjCommand(interp, "fts3_near_match", fts3_near_match_cmd, 0, 0);
|
||||
Tcl_CreateObjCommand(interp,
|
||||
"fts3_configure_incr_load", fts3_configure_incr_load_cmd, 0, 0
|
||||
);
|
||||
return TCL_OK;
|
||||
}
|
||||
#endif /* ifdef SQLITE_TEST */
|
||||
@@ -23,14 +23,9 @@
|
||||
** * The FTS3 module is being built into the core of
|
||||
** SQLite (in which case SQLITE_ENABLE_FTS3 is defined).
|
||||
*/
|
||||
#include "fts3Int.h"
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
|
||||
#include "sqlite3ext.h"
|
||||
#ifndef SQLITE_CORE
|
||||
SQLITE_EXTENSION_INIT1
|
||||
#endif
|
||||
|
||||
#include "fts3Int.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -97,7 +92,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 +130,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,38 +145,26 @@ 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;
|
||||
char *z = (char *)zArg;
|
||||
int n;
|
||||
int n = 0;
|
||||
char *zCopy;
|
||||
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 +460,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);
|
||||
|
||||
@@ -22,9 +22,8 @@
|
||||
** * The FTS3 module is being built into the core of
|
||||
** SQLite (in which case SQLITE_ENABLE_FTS3 is defined).
|
||||
*/
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
|
||||
#include "fts3Int.h"
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -247,6 +247,8 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){
|
||||
UBool res;
|
||||
const UChar *zString = sqlite3_value_text16(apArg[1]);
|
||||
|
||||
(void)nArg; /* Unused parameter */
|
||||
|
||||
/* If the left hand side of the regexp operator is NULL,
|
||||
** then the result is also NULL.
|
||||
*/
|
||||
@@ -475,7 +477,7 @@ int sqlite3IcuInit(sqlite3 *db){
|
||||
int rc = SQLITE_OK;
|
||||
int i;
|
||||
|
||||
for(i=0; rc==SQLITE_OK && i<(sizeof(scalars)/sizeof(struct IcuScalar)); i++){
|
||||
for(i=0; rc==SQLITE_OK && i<(int)(sizeof(scalars)/sizeof(scalars[0])); i++){
|
||||
struct IcuScalar *p = &scalars[i];
|
||||
rc = sqlite3_create_function(
|
||||
db, p->zName, p->nArg, p->enc, p->pContext, p->xFunc, 0, 0
|
||||
|
||||
@@ -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
|
||||
@@ -31,6 +31,8 @@ source $testdir/tester.tcl
|
||||
# rtree-7.*: Test renaming an r-tree table.
|
||||
# rtree-8.*: Test constrained scans of r-tree data.
|
||||
#
|
||||
# rtree-12.*: Test that on-conflict clauses are supported.
|
||||
#
|
||||
|
||||
ifcapable !rtree {
|
||||
finish_test
|
||||
@@ -416,4 +418,83 @@ do_test rtree-11.2 {
|
||||
}
|
||||
} {2}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test on-conflict clause handling.
|
||||
#
|
||||
db_delete_and_reopen
|
||||
do_execsql_test 12.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING rtree_i32(idx, x1, x2, y1, y2);
|
||||
INSERT INTO t1 VALUES(1, 1, 2, 3, 4);
|
||||
INSERT INTO t1 VALUES(2, 2, 3, 4, 5);
|
||||
INSERT INTO t1 VALUES(3, 3, 4, 5, 6);
|
||||
|
||||
CREATE TABLE source(idx, x1, x2, y1, y2);
|
||||
INSERT INTO source VALUES(5, 8, 8, 8, 8);
|
||||
INSERT INTO source VALUES(2, 7, 7, 7, 7);
|
||||
|
||||
}
|
||||
db_save_and_close
|
||||
foreach {tn sql_template testdata} {
|
||||
1 "INSERT %CONF% INTO t1 VALUES(2, 7, 7, 7, 7)" {
|
||||
ROLLBACK 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
|
||||
ABORT 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
IGNORE 0 0 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
FAIL 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
REPLACE 0 0 {1 1 2 3 4 2 7 7 7 7 3 3 4 5 6 4 4 5 6 7}
|
||||
}
|
||||
|
||||
2 "INSERT %CONF% INTO t1 SELECT * FROM source" {
|
||||
ROLLBACK 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
|
||||
ABORT 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
IGNORE 1 0 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7 5 8 8 8 8}
|
||||
FAIL 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7 5 8 8 8 8}
|
||||
REPLACE 1 0 {1 1 2 3 4 2 7 7 7 7 3 3 4 5 6 4 4 5 6 7 5 8 8 8 8}
|
||||
}
|
||||
|
||||
3 "UPDATE %CONF% t1 SET idx = 2 WHERE idx = 4" {
|
||||
ROLLBACK 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
|
||||
ABORT 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
IGNORE 1 0 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
FAIL 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
REPLACE 1 0 {1 1 2 3 4 2 4 5 6 7 3 3 4 5 6}
|
||||
}
|
||||
|
||||
3 "UPDATE %CONF% t1 SET idx = ((idx+1)%5)+1 WHERE idx > 2" {
|
||||
ROLLBACK 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
|
||||
ABORT 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
IGNORE 1 0 {1 1 2 3 4 2 2 3 4 5 4 4 5 6 7 5 3 4 5 6}
|
||||
FAIL 1 1 {1 1 2 3 4 2 2 3 4 5 4 4 5 6 7 5 3 4 5 6}
|
||||
REPLACE 1 0 {1 4 5 6 7 2 2 3 4 5 5 3 4 5 6}
|
||||
}
|
||||
|
||||
4 "INSERT %CONF% INTO t1 VALUES(2, 7, 6, 7, 7)" {
|
||||
ROLLBACK 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
|
||||
ABORT 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
IGNORE 0 0 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
FAIL 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
REPLACE 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
}
|
||||
|
||||
} {
|
||||
foreach {mode uses error data} $testdata {
|
||||
db_restore_and_reopen
|
||||
|
||||
set sql [string map [list %CONF% "OR $mode"] $sql_template]
|
||||
set testname "12.$tn.[string tolower $mode]"
|
||||
|
||||
execsql {
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(4, 4, 5, 6, 7);
|
||||
}
|
||||
|
||||
set res(0) {0 {}}
|
||||
set res(1) {1 {constraint failed}}
|
||||
do_catchsql_test $testname.1 $sql $res($error)
|
||||
do_test $testname.2 [list sql_uses_stmt db $sql] $uses
|
||||
do_execsql_test $testname.3 { SELECT * FROM t1 ORDER BY idx } $data
|
||||
|
||||
do_test $testname.4 { rtree_check db t1 } 0
|
||||
db close
|
||||
}
|
||||
}
|
||||
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 [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,86 @@ 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_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)}
|
||||
}
|
||||
|
||||
do_execsql_test rtree6-3.1 {
|
||||
CREATE VIRTUAL TABLE t3 USING rtree(id, x1, x2, y1, y2);
|
||||
INSERT INTO t3 VALUES(NULL, 1, 1, 2, 2);
|
||||
SELECT * FROM t3 WHERE
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5;
|
||||
} {1 1.0 1.0 2.0 2.0}
|
||||
|
||||
do_test rtree6.3.2 {
|
||||
rtree_strategy {
|
||||
SELECT * FROM t3 WHERE
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5
|
||||
}
|
||||
} {EaEaEaEaEaEaEaEaEaEaEaEaEaEaEaEaEaEaEaEa}
|
||||
do_test rtree6.3.3 {
|
||||
rtree_strategy {
|
||||
SELECT * FROM t3 WHERE
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5
|
||||
}
|
||||
} {EaEaEaEaEaEaEaEaEaEaEaEaEaEaEaEaEaEaEaEa}
|
||||
|
||||
do_execsql_test rtree6-3.4 {
|
||||
SELECT * FROM t3 WHERE x1>0.5 AND x1>0.8 AND x1>1.1
|
||||
} {}
|
||||
do_execsql_test rtree6-3.5 {
|
||||
SELECT * FROM t3 WHERE
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
|
||||
x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>1.1
|
||||
} {}
|
||||
|
||||
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} \
|
||||
]
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,34 @@
|
||||
# 2011 March 2
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
# Make sure the rtreenode() testing function can handle entries with
|
||||
# 64-bit rowids.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !rtree { finish_test ; return }
|
||||
|
||||
do_test rtreeB-1.1 {
|
||||
db eval {
|
||||
CREATE VIRTUAL TABLE t1 USING rtree(ii, x0, y0, x1, y1);
|
||||
INSERT INTO t1 VALUES(1073741824, 0.0, 0.0, 100.0, 100.0);
|
||||
INSERT INTO t1 VALUES(2147483646, 0.0, 0.0, 200.0, 200.0);
|
||||
INSERT INTO t1 VALUES(4294967296, 0.0, 0.0, 300.0, 300.0);
|
||||
INSERT INTO t1 VALUES(8589934592, 20.0, 20.0, 150.0, 150.0);
|
||||
INSERT INTO t1 VALUES(9223372036854775807, 150, 150, 400, 400);
|
||||
SELECT rtreenode(2, data) FROM t1_node;
|
||||
}
|
||||
} {{{1073741824 0.000000 0.000000 100.000000 100.000000} {2147483646 0.000000 0.000000 200.000000 200.000000} {4294967296 0.000000 0.000000 300.000000 300.000000} {8589934592 20.000000 20.000000 150.000000 150.000000} {9223372036854775807 150.000000 150.000000 400.000000 400.000000}}}
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -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
|
||||
|
||||
@@ -53,9 +53,9 @@ TCCX += -I$(TOP)/ext/async
|
||||
LIBOBJ+= alter.o analyze.o attach.o auth.o \
|
||||
backup.o bitvec.o btmutex.o btree.o build.o \
|
||||
callback.o complete.o ctime.o date.o delete.o expr.o fault.o fkey.o \
|
||||
fts3.o fts3_expr.o fts3_hash.o fts3_icu.o fts3_porter.o \
|
||||
fts3_snippet.o fts3_tokenizer.o fts3_tokenizer1.o fts3_write.o \
|
||||
func.o global.o hash.o \
|
||||
fts3.o fts3_aux.o fts3_expr.o fts3_hash.o fts3_icu.o fts3_porter.o \
|
||||
fts3_snippet.o fts3_tokenizer.o fts3_tokenizer1.o \
|
||||
fts3_write.o func.o global.o hash.o \
|
||||
icu.o insert.o journal.o legacy.o loadext.o \
|
||||
main.o malloc.o mem0.o mem1.o mem2.o mem3.o mem5.o \
|
||||
memjournal.o \
|
||||
@@ -65,8 +65,8 @@ LIBOBJ+= alter.o analyze.o attach.o auth.o \
|
||||
random.o resolve.o rowset.o rtree.o select.o status.o \
|
||||
table.o tokenize.o trigger.o \
|
||||
update.o util.o vacuum.o \
|
||||
vdbe.o vdbeapi.o vdbeaux.o vdbeblob.o vdbemem.o vdbetrace.o \
|
||||
wal.o walker.o where.o utf.o vtab.o
|
||||
vdbe.o vdbeapi.o vdbeaux.o vdbeblob.o vdbemem.o vdbesort.o \
|
||||
vdbetrace.o wal.o walker.o where.o utf.o vtab.o
|
||||
|
||||
|
||||
|
||||
@@ -155,6 +155,7 @@ SRC = \
|
||||
$(TOP)/src/vdbeaux.c \
|
||||
$(TOP)/src/vdbeblob.c \
|
||||
$(TOP)/src/vdbemem.c \
|
||||
$(TOP)/src/vdbesort.c \
|
||||
$(TOP)/src/vdbetrace.c \
|
||||
$(TOP)/src/vdbeInt.h \
|
||||
$(TOP)/src/vtab.c \
|
||||
@@ -187,6 +188,7 @@ SRC += \
|
||||
$(TOP)/ext/fts3/fts3.c \
|
||||
$(TOP)/ext/fts3/fts3.h \
|
||||
$(TOP)/ext/fts3/fts3Int.h \
|
||||
$(TOP)/ext/fts3/fts3_aux.c \
|
||||
$(TOP)/ext/fts3/fts3_expr.c \
|
||||
$(TOP)/ext/fts3/fts3_hash.c \
|
||||
$(TOP)/ext/fts3/fts3_hash.h \
|
||||
@@ -219,6 +221,8 @@ SRC += \
|
||||
# Source code to the test files.
|
||||
#
|
||||
TESTSRC = \
|
||||
$(TOP)/ext/fts3/fts3_term.c \
|
||||
$(TOP)/ext/fts3/fts3_test.c \
|
||||
$(TOP)/src/test1.c \
|
||||
$(TOP)/src/test2.c \
|
||||
$(TOP)/src/test3.c \
|
||||
@@ -236,21 +240,28 @@ TESTSRC = \
|
||||
$(TOP)/src/test_demovfs.c \
|
||||
$(TOP)/src/test_devsym.c \
|
||||
$(TOP)/src/test_func.c \
|
||||
$(TOP)/src/test_fuzzer.c \
|
||||
$(TOP)/src/test_hexio.c \
|
||||
$(TOP)/src/test_init.c \
|
||||
$(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_syscall.c \
|
||||
$(TOP)/src/test_tclvar.c \
|
||||
$(TOP)/src/test_thread.c \
|
||||
$(TOP)/src/test_vfs.c \
|
||||
$(TOP)/src/test_wholenumber.c \
|
||||
$(TOP)/src/test_wsd.c
|
||||
|
||||
#TESTSRC += $(TOP)/ext/fts2/fts2_tokenizer.c
|
||||
@@ -289,6 +300,7 @@ TESTSRC2 = \
|
||||
$(TOP)/src/where.c \
|
||||
parse.c \
|
||||
$(TOP)/ext/fts3/fts3.c \
|
||||
$(TOP)/ext/fts3/fts3_aux.c \
|
||||
$(TOP)/ext/fts3/fts3_expr.c \
|
||||
$(TOP)/ext/fts3/fts3_tokenizer.c \
|
||||
$(TOP)/ext/fts3/fts3_write.c \
|
||||
@@ -350,6 +362,9 @@ sqlite3$(EXE): $(TOP)/src/shell.c libsqlite3.a sqlite3.h
|
||||
$(TOP)/src/shell.c \
|
||||
libsqlite3.a $(LIBREADLINE) $(TLIBS) $(THREADLIB)
|
||||
|
||||
sqlite3.o: sqlite3.c
|
||||
$(TCCX) -c sqlite3.c
|
||||
|
||||
# This target creates a directory named "tsrc" and fills it with
|
||||
# copies of all of the C source code and header files needed to
|
||||
# build on the target system. Some of the C source code and header
|
||||
@@ -367,9 +382,22 @@ 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
|
||||
|
||||
sqlite3.c-debug: target_source $(TOP)/tool/mksqlite3c.tcl
|
||||
tclsh $(TOP)/tool/mksqlite3c.tcl --linemacros
|
||||
echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c
|
||||
cat sqlite3.c >>tclsqlite3.c
|
||||
echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c
|
||||
echo '#line 1 "tclsqlite.c"' >>tclsqlite3.c
|
||||
cat $(TOP)/src/tclsqlite.c >>tclsqlite3.c
|
||||
|
||||
sqlite3-all.c: sqlite3.c $(TOP)/tool/split-sqlite3c.tcl
|
||||
tclsh $(TOP)/tool/split-sqlite3c.tcl
|
||||
|
||||
fts2amal.c: target_source $(TOP)/ext/fts2/mkfts2amal.tcl
|
||||
tclsh $(TOP)/ext/fts2/mkfts2amal.tcl
|
||||
|
||||
@@ -404,7 +432,7 @@ tclsqlite.o: $(TOP)/src/tclsqlite.c $(HDR)
|
||||
# Rules to build opcodes.c and opcodes.h
|
||||
#
|
||||
opcodes.c: opcodes.h $(TOP)/mkopcodec.awk
|
||||
sort -n -b -k 3 opcodes.h | $(NAWK) -f $(TOP)/mkopcodec.awk >opcodes.c
|
||||
$(NAWK) -f $(TOP)/mkopcodec.awk opcodes.h >opcodes.c
|
||||
|
||||
opcodes.h: parse.h $(TOP)/src/vdbe.c $(TOP)/mkopcodeh.awk
|
||||
cat parse.h $(TOP)/src/vdbe.c | \
|
||||
@@ -456,6 +484,9 @@ fts2_tokenizer1.o: $(TOP)/ext/fts2/fts2_tokenizer1.c $(HDR) $(EXTHDR)
|
||||
fts3.o: $(TOP)/ext/fts3/fts3.c $(HDR) $(EXTHDR)
|
||||
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3.c
|
||||
|
||||
fts3_aux.o: $(TOP)/ext/fts3/fts3_aux.c $(HDR) $(EXTHDR)
|
||||
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_aux.c
|
||||
|
||||
fts3_expr.o: $(TOP)/ext/fts3/fts3_expr.c $(HDR) $(EXTHDR)
|
||||
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_expr.c
|
||||
|
||||
@@ -490,6 +521,16 @@ tclsqlite3: $(TOP)/src/tclsqlite.c libsqlite3.a
|
||||
$(TCCX) $(TCL_FLAGS) -DTCLSH=1 -o tclsqlite3 \
|
||||
$(TOP)/src/tclsqlite.c libsqlite3.a $(LIBTCL) $(THREADLIB)
|
||||
|
||||
sqlite3_analyzer.c: sqlite3.c $(TOP)/src/test_stat.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl
|
||||
echo "#define TCLSH 2" > $@
|
||||
cat sqlite3.c $(TOP)/src/test_stat.c $(TOP)/src/tclsqlite.c >> $@
|
||||
echo "static const char *tclsh_main_loop(void){" >> $@
|
||||
echo "static const char *zMainloop = " >> $@
|
||||
$(NAWK) -f $(TOP)/tool/tostr.awk $(TOP)/tool/spaceanal.tcl >> $@
|
||||
echo "; return zMainloop; }" >> $@
|
||||
|
||||
sqlite3_analyzer$(EXE): sqlite3_analyzer.c
|
||||
$(TCCX) $(TCL_FLAGS) sqlite3_analyzer.c -o $@ $(LIBTCL) $(THREADLIB)
|
||||
|
||||
# Rules to build the 'testfixture' application.
|
||||
#
|
||||
@@ -521,20 +562,16 @@ soaktest: testfixture$(EXE) sqlite3$(EXE)
|
||||
test: testfixture$(EXE) sqlite3$(EXE)
|
||||
./testfixture$(EXE) $(TOP)/test/veryquick.test
|
||||
|
||||
sqlite3_analyzer$(EXE): $(TOP)/src/tclsqlite.c sqlite3.c $(TESTSRC) \
|
||||
$(TOP)/tool/spaceanal.tcl
|
||||
sed \
|
||||
-e '/^#/d' \
|
||||
-e 's,\\,\\\\,g' \
|
||||
-e 's,",\\",g' \
|
||||
-e 's,^,",' \
|
||||
-e 's,$$,\\n",' \
|
||||
$(TOP)/tool/spaceanal.tcl >spaceanal_tcl.h
|
||||
$(TCCX) $(TCL_FLAGS) -DTCLSH=2 $(TESTFIXTURE_FLAGS) \
|
||||
-DSQLITE_TEST=1 -DSQLITE_PRIVATE="" \
|
||||
$(TESTSRC) $(TOP)/src/tclsqlite.c sqlite3.c \
|
||||
-o sqlite3_analyzer$(EXE) \
|
||||
$(LIBTCL) $(THREADLIB)
|
||||
# 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.o $(TOP)/test/threadtest3.c $(TOP)/test/tt3_checkpoint.c
|
||||
$(TCCX) -O2 sqlite3.o $(TOP)/test/threadtest3.c \
|
||||
-o threadtest3$(EXE) $(THREADLIB)
|
||||
|
||||
threadtest: threadtest3$(EXE)
|
||||
./threadtest3$(EXE)
|
||||
|
||||
TEST_EXTENSION = $(SHPREFIX)testloadext.$(SO)
|
||||
$(TEST_EXTENSION): $(TOP)/src/test_loadext.c
|
||||
@@ -543,6 +580,13 @@ $(TEST_EXTENSION): $(TOP)/src/test_loadext.c
|
||||
extensiontest: testfixture$(EXE) $(TEST_EXTENSION)
|
||||
./testfixture$(EXE) $(TOP)/test/loadext.test
|
||||
|
||||
# This target will fail if the SQLite amalgamation contains any exported
|
||||
# symbols that do not begin with "sqlite3_". It is run as part of the
|
||||
# releasetest.tcl script.
|
||||
#
|
||||
checksymbols: sqlite3.o
|
||||
nm -g --defined-only sqlite3.o | grep -v " sqlite3_" ; test $$? -ne 0
|
||||
|
||||
|
||||
# Standard install and cleanup targets
|
||||
#
|
||||
@@ -552,10 +596,16 @@ install: sqlite3 libsqlite3.a sqlite3.h
|
||||
mv sqlite3.h /usr/include
|
||||
|
||||
clean:
|
||||
rm -f *.o sqlite3 libsqlite3.a sqlite3.h opcodes.*
|
||||
rm -f lemon lempar.c parse.* sqlite*.tar.gz mkkeywordhash keywordhash.h
|
||||
rm -f *.o sqlite3 sqlite3.exe libsqlite3.a sqlite3.h opcodes.*
|
||||
rm -f lemon lemon.exe lempar.c parse.* sqlite*.tar.gz
|
||||
rm -f mkkeywordhash mkkeywordhash.exe keywordhash.h
|
||||
rm -f $(PUBLISH)
|
||||
rm -f *.da *.bb *.bbg gmon.out
|
||||
rm -rf tsrc target_source
|
||||
rm -f testloadext.dll libtestloadext.so
|
||||
rm -f amalgamation-testfixture amalgamation-testfixture.exe
|
||||
rm -f fts3-testfixture fts3-testfixture.exe
|
||||
rm -f testfixture testfixture.exe
|
||||
rm -f threadtest3 threadtest3.exe
|
||||
rm -f sqlite3.c fts?amal.c tclsqlite3.c
|
||||
rm -f sqlite3_analyzer sqlite3_analyzer.exe sqlite3_analyzer.c
|
||||
|
||||
@@ -1 +1 @@
|
||||
42537b60566f288167f1b5864a5435986838e3a3
|
||||
c7c6050ef060877ebe77b41d959e9df13f8c9b5e
|
||||
|
||||
@@ -17,13 +17,18 @@ BEGIN {
|
||||
print " || defined(SQLITE_DEBUG)"
|
||||
print "const char *sqlite3OpcodeName(int i){"
|
||||
print " static const char *const azName[] = { \"?\","
|
||||
mx = 0
|
||||
}
|
||||
/define OP_/ {
|
||||
sub("OP_","",$2)
|
||||
i++
|
||||
printf " /* %3d */ \"%s\",\n", $3, $2
|
||||
i = $3+0
|
||||
label[i] = $2
|
||||
if( mx<i ) mx = i
|
||||
}
|
||||
END {
|
||||
for(i=1; i<=mx; i++){
|
||||
printf " /* %3d */ \"%s\",\n", i, label[i]
|
||||
}
|
||||
print " };"
|
||||
print " return azName[i];"
|
||||
print "}"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -353,18 +358,34 @@ static void reloadTableSchema(Parse *pParse, Table *pTab, const char *zName){
|
||||
/* Reload the table, index and permanent trigger schemas. */
|
||||
zWhere = sqlite3MPrintf(pParse->db, "tbl_name=%Q", zName);
|
||||
if( !zWhere ) return;
|
||||
sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 0, 0, zWhere, P4_DYNAMIC);
|
||||
sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere);
|
||||
|
||||
#ifndef SQLITE_OMIT_TRIGGER
|
||||
/* Now, if the table is not stored in the temp database, reload any temp
|
||||
** triggers. Don't use IN(...) in case SQLITE_OMIT_SUBQUERY is defined.
|
||||
*/
|
||||
if( (zWhere=whereTempTriggers(pParse, pTab))!=0 ){
|
||||
sqlite3VdbeAddOp4(v, OP_ParseSchema, 1, 0, 0, zWhere, P4_DYNAMIC);
|
||||
sqlite3VdbeAddParseSchemaOp(v, 1, zWhere);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
** Parameter zName is the name of a table that is about to be altered
|
||||
** (either with ALTER TABLE ... RENAME TO or ALTER TABLE ... ADD COLUMN).
|
||||
** If the table is a system table, this function leaves an error message
|
||||
** in pParse->zErr (system tables may not be altered) and returns non-zero.
|
||||
**
|
||||
** Or, if zName is not a system table, zero is returned.
|
||||
*/
|
||||
static int isSystemTable(Parse *pParse, const char *zName){
|
||||
if( sqlite3Strlen30(zName)>6 && 0==sqlite3StrNICmp(zName, "sqlite_", 7) ){
|
||||
sqlite3ErrorMsg(pParse, "table %s may not be altered", zName);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate code to implement the "ALTER TABLE xxx RENAME TO yyy"
|
||||
** command.
|
||||
@@ -415,14 +436,11 @@ void sqlite3AlterRenameTable(
|
||||
/* Make sure it is not a system table being altered, or a reserved name
|
||||
** that the table is being renamed to.
|
||||
*/
|
||||
if( sqlite3Strlen30(pTab->zName)>6
|
||||
&& 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7)
|
||||
){
|
||||
sqlite3ErrorMsg(pParse, "table %s may not be altered", pTab->zName);
|
||||
if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ){
|
||||
goto exit_rename_table;
|
||||
}
|
||||
if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){
|
||||
goto exit_rename_table;
|
||||
if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ goto
|
||||
exit_rename_table;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_VIEW
|
||||
@@ -754,6 +772,9 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){
|
||||
sqlite3ErrorMsg(pParse, "Cannot add a column to a view");
|
||||
goto exit_begin_add_column;
|
||||
}
|
||||
if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ){
|
||||
goto exit_begin_add_column;
|
||||
}
|
||||
|
||||
assert( pTab->addColOffset>0 );
|
||||
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
|
||||
|
||||
@@ -70,8 +70,12 @@ static void attachFunc(
|
||||
sqlite3 *db = sqlite3_context_db_handle(context);
|
||||
const char *zName;
|
||||
const char *zFile;
|
||||
char *zPath = 0;
|
||||
char *zErr = 0;
|
||||
unsigned int flags;
|
||||
Db *aNew;
|
||||
char *zErrDyn = 0;
|
||||
sqlite3_vfs *pVfs;
|
||||
|
||||
UNUSED_PARAMETER(NotUsed);
|
||||
|
||||
@@ -124,9 +128,18 @@ 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);
|
||||
flags = db->openFlags;
|
||||
rc = sqlite3ParseUri(db->pVfs->zName, zFile, &flags, &pVfs, &zPath, &zErr);
|
||||
if( rc!=SQLITE_OK ){
|
||||
if( rc==SQLITE_NOMEM ) db->mallocFailed = 1;
|
||||
sqlite3_result_error(context, zErr, -1);
|
||||
sqlite3_free(zErr);
|
||||
return;
|
||||
}
|
||||
assert( pVfs );
|
||||
flags |= SQLITE_OPEN_MAIN_DB;
|
||||
rc = sqlite3BtreeOpen(pVfs, zPath, db, &aNew->pBt, 0, flags);
|
||||
sqlite3_free( zPath );
|
||||
db->nDb++;
|
||||
if( rc==SQLITE_CONSTRAINT ){
|
||||
rc = SQLITE_ERROR;
|
||||
@@ -177,7 +190,9 @@ static void attachFunc(
|
||||
case SQLITE_NULL:
|
||||
/* No key specified. Use the key from the main database */
|
||||
sqlite3CodecGetKey(db, 0, (void**)&zKey, &nKey);
|
||||
rc = sqlite3CodecAttach(db, db->nDb-1, zKey, nKey);
|
||||
if( nKey>0 || sqlite3BtreeGetReserve(db->aDb[0].pBt)>0 ){
|
||||
rc = sqlite3CodecAttach(db, db->nDb-1, zKey, nKey);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -201,7 +216,7 @@ static void attachFunc(
|
||||
db->aDb[iDb].pBt = 0;
|
||||
db->aDb[iDb].pSchema = 0;
|
||||
}
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
db->nDb = iDb;
|
||||
if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
|
||||
db->mallocFailed = 1;
|
||||
@@ -273,7 +288,7 @@ static void detachFunc(
|
||||
sqlite3BtreeClose(pDb->pBt);
|
||||
pDb->pBt = 0;
|
||||
pDb->pSchema = 0;
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
return;
|
||||
|
||||
detach_error:
|
||||
@@ -313,9 +328,11 @@ static void codeAttach(
|
||||
|
||||
#ifndef SQLITE_OMIT_AUTHORIZATION
|
||||
if( pAuthArg ){
|
||||
char *zAuthArg = pAuthArg->u.zToken;
|
||||
if( NEVER(zAuthArg==0) ){
|
||||
goto attach_end;
|
||||
char *zAuthArg;
|
||||
if( pAuthArg->op==TK_STRING ){
|
||||
zAuthArg = pAuthArg->u.zToken;
|
||||
}else{
|
||||
zAuthArg = 0;
|
||||
}
|
||||
rc = sqlite3AuthCheck(pParse, type, zAuthArg, 0, 0);
|
||||
if(rc!=SQLITE_OK ){
|
||||
@@ -367,7 +384,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 +406,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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -205,6 +219,10 @@ static int backupOnePage(sqlite3_backup *p, Pgno iSrcPg, const u8 *zSrcData){
|
||||
int nDestPgsz = sqlite3BtreeGetPageSize(p->pDest);
|
||||
const int nCopy = MIN(nSrcPgsz, nDestPgsz);
|
||||
const i64 iEnd = (i64)iSrcPg*(i64)nSrcPgsz;
|
||||
#ifdef SQLITE_HAS_CODEC
|
||||
int nSrcReserve = sqlite3BtreeGetReserve(p->pSrc);
|
||||
int nDestReserve = sqlite3BtreeGetReserve(p->pDest);
|
||||
#endif
|
||||
|
||||
int rc = SQLITE_OK;
|
||||
i64 iOff;
|
||||
@@ -223,11 +241,22 @@ static int backupOnePage(sqlite3_backup *p, Pgno iSrcPg, const u8 *zSrcData){
|
||||
|
||||
#ifdef SQLITE_HAS_CODEC
|
||||
/* Backup is not possible if the page size of the destination is changing
|
||||
** a a codec is in use.
|
||||
** and a codec is in use.
|
||||
*/
|
||||
if( nSrcPgsz!=nDestPgsz && sqlite3PagerGetCodec(pDestPager)!=0 ){
|
||||
rc = SQLITE_READONLY;
|
||||
}
|
||||
|
||||
/* Backup is not possible if the number of bytes of reserve space differ
|
||||
** between source and destination. If there is a difference, try to
|
||||
** fix the destination to agree with the source. If that is not possible,
|
||||
** then the backup cannot proceed.
|
||||
*/
|
||||
if( nSrcReserve!=nDestReserve ){
|
||||
u32 newPgsz = nSrcPgsz;
|
||||
rc = sqlite3PagerSetPagesize(pDestPager, &newPgsz, nSrcReserve);
|
||||
if( rc==SQLITE_OK && newPgsz!=nSrcPgsz ) rc = SQLITE_READONLY;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* This loop runs once for each destination page spanned by the source
|
||||
@@ -381,64 +410,74 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
|
||||
** the case where the source and destination databases have the
|
||||
** same schema version.
|
||||
*/
|
||||
if( rc==SQLITE_DONE
|
||||
&& (rc = sqlite3BtreeUpdateMeta(p->pDest,1,p->iDestSchema+1))==SQLITE_OK
|
||||
){
|
||||
int nDestTruncate;
|
||||
|
||||
if( p->pDestDb ){
|
||||
sqlite3ResetInternalSchema(p->pDestDb, 0);
|
||||
}
|
||||
|
||||
/* Set nDestTruncate to the final number of pages in the destination
|
||||
** database. The complication here is that the destination page
|
||||
** size may be different to the source page size.
|
||||
**
|
||||
** If the source page size is smaller than the destination page size,
|
||||
** round up. In this case the call to sqlite3OsTruncate() below will
|
||||
** fix the size of the file. However it is important to call
|
||||
** sqlite3PagerTruncateImage() here so that any pages in the
|
||||
** destination file that lie beyond the nDestTruncate page mark are
|
||||
** journalled by PagerCommitPhaseOne() before they are destroyed
|
||||
** by the file truncation.
|
||||
*/
|
||||
assert( pgszSrc==sqlite3BtreeGetPageSize(p->pSrc) );
|
||||
assert( pgszDest==sqlite3BtreeGetPageSize(p->pDest) );
|
||||
if( pgszSrc<pgszDest ){
|
||||
int ratio = pgszDest/pgszSrc;
|
||||
nDestTruncate = (nSrcPage+ratio-1)/ratio;
|
||||
if( nDestTruncate==(int)PENDING_BYTE_PAGE(p->pDest->pBt) ){
|
||||
nDestTruncate--;
|
||||
if( rc==SQLITE_DONE ){
|
||||
rc = sqlite3BtreeUpdateMeta(p->pDest,1,p->iDestSchema+1);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( p->pDestDb ){
|
||||
sqlite3ResetInternalSchema(p->pDestDb, -1);
|
||||
}
|
||||
if( destMode==PAGER_JOURNALMODE_WAL ){
|
||||
rc = sqlite3BtreeSetVersion(p->pDest, 2);
|
||||
}
|
||||
}else{
|
||||
nDestTruncate = nSrcPage * (pgszSrc/pgszDest);
|
||||
}
|
||||
sqlite3PagerTruncateImage(pDestPager, nDestTruncate);
|
||||
|
||||
if( pgszSrc<pgszDest ){
|
||||
/* If the source page-size is smaller than the destination page-size,
|
||||
** two extra things may need to happen:
|
||||
if( rc==SQLITE_OK ){
|
||||
int nDestTruncate;
|
||||
/* Set nDestTruncate to the final number of pages in the destination
|
||||
** database. The complication here is that the destination page
|
||||
** size may be different to the source page size.
|
||||
**
|
||||
** * The destination may need to be truncated, and
|
||||
**
|
||||
** * Data stored on the pages immediately following the
|
||||
** pending-byte page in the source database may need to be
|
||||
** copied into the destination database.
|
||||
** If the source page size is smaller than the destination page size,
|
||||
** round up. In this case the call to sqlite3OsTruncate() below will
|
||||
** fix the size of the file. However it is important to call
|
||||
** sqlite3PagerTruncateImage() here so that any pages in the
|
||||
** destination file that lie beyond the nDestTruncate page mark are
|
||||
** journalled by PagerCommitPhaseOne() before they are destroyed
|
||||
** by the file truncation.
|
||||
*/
|
||||
const i64 iSize = (i64)pgszSrc * (i64)nSrcPage;
|
||||
sqlite3_file * const pFile = sqlite3PagerFile(pDestPager);
|
||||
assert( pgszSrc==sqlite3BtreeGetPageSize(p->pSrc) );
|
||||
assert( pgszDest==sqlite3BtreeGetPageSize(p->pDest) );
|
||||
if( pgszSrc<pgszDest ){
|
||||
int ratio = pgszDest/pgszSrc;
|
||||
nDestTruncate = (nSrcPage+ratio-1)/ratio;
|
||||
if( nDestTruncate==(int)PENDING_BYTE_PAGE(p->pDest->pBt) ){
|
||||
nDestTruncate--;
|
||||
}
|
||||
}else{
|
||||
nDestTruncate = nSrcPage * (pgszSrc/pgszDest);
|
||||
}
|
||||
sqlite3PagerTruncateImage(pDestPager, nDestTruncate);
|
||||
|
||||
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))
|
||||
){
|
||||
if( pgszSrc<pgszDest ){
|
||||
/* If the source page-size is smaller than the destination page-size,
|
||||
** two extra things may need to happen:
|
||||
**
|
||||
** * The destination may need to be truncated, and
|
||||
**
|
||||
** * Data stored on the pages immediately following the
|
||||
** pending-byte page in the source database may need to be
|
||||
** copied into the destination database.
|
||||
*/
|
||||
const i64 iSize = (i64)pgszSrc * (i64)nSrcPage;
|
||||
sqlite3_file * const pFile = sqlite3PagerFile(pDestPager);
|
||||
i64 iOff;
|
||||
i64 iEnd = MIN(PENDING_BYTE + pgszDest, iSize);
|
||||
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
|
||||
));
|
||||
|
||||
/* 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;
|
||||
@@ -453,16 +492,24 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/* Finish committing the transaction to the destination database. */
|
||||
if( SQLITE_OK==rc
|
||||
&& SQLITE_OK==(rc = sqlite3BtreeCommitPhaseTwo(p->pDest, 0))
|
||||
){
|
||||
rc = SQLITE_DONE;
|
||||
}
|
||||
}else{
|
||||
rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 0);
|
||||
}
|
||||
|
||||
/* Finish committing the transaction to the destination database. */
|
||||
if( SQLITE_OK==rc
|
||||
&& SQLITE_OK==(rc = sqlite3BtreeCommitPhaseTwo(p->pDest))
|
||||
){
|
||||
rc = SQLITE_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,7 +521,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
|
||||
if( bCloseTrans ){
|
||||
TESTONLY( int rc2 );
|
||||
TESTONLY( rc2 = ) sqlite3BtreeCommitPhaseOne(p->pSrc, 0);
|
||||
TESTONLY( rc2 |= ) sqlite3BtreeCommitPhaseTwo(p->pSrc);
|
||||
TESTONLY( rc2 |= ) sqlite3BtreeCommitPhaseTwo(p->pSrc, 0);
|
||||
assert( rc2==SQLITE_OK );
|
||||
}
|
||||
|
||||
@@ -496,14 +543,14 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
|
||||
*/
|
||||
int sqlite3_backup_finish(sqlite3_backup *p){
|
||||
sqlite3_backup **pp; /* Ptr to head of pagers backup list */
|
||||
sqlite3_mutex *mutex; /* Mutex to protect source database */
|
||||
MUTEX_LOGIC( sqlite3_mutex *mutex; ) /* Mutex to protect source database */
|
||||
int rc; /* Value to return */
|
||||
|
||||
/* Enter the mutexes */
|
||||
if( p==0 ) return SQLITE_OK;
|
||||
sqlite3_mutex_enter(p->pSrcDb->mutex);
|
||||
sqlite3BtreeEnter(p->pSrc);
|
||||
mutex = p->pSrcDb->mutex;
|
||||
MUTEX_LOGIC( mutex = p->pSrcDb->mutex; )
|
||||
if( p->pDestDb ){
|
||||
sqlite3_mutex_enter(p->pDestDb->mutex);
|
||||
}
|
||||
@@ -533,6 +580,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);
|
||||
@@ -576,7 +626,11 @@ void sqlite3BackupUpdate(sqlite3_backup *pBackup, Pgno iPage, const u8 *aData){
|
||||
** has been modified by a transaction on the source pager. Copy
|
||||
** the new data into the backup.
|
||||
*/
|
||||
int rc = backupOnePage(p, iPage, aData);
|
||||
int rc;
|
||||
assert( p->pDestDb );
|
||||
sqlite3_mutex_enter(p->pDestDb->mutex);
|
||||
rc = backupOnePage(p, iPage, aData);
|
||||
sqlite3_mutex_leave(p->pDestDb->mutex);
|
||||
assert( rc!=SQLITE_BUSY && rc!=SQLITE_LOCKED );
|
||||
if( rc!=SQLITE_OK ){
|
||||
p->rc = rc;
|
||||
@@ -615,10 +669,18 @@ void sqlite3BackupRestart(sqlite3_backup *pBackup){
|
||||
*/
|
||||
int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
|
||||
int rc;
|
||||
sqlite3_file *pFd; /* File descriptor for database pTo */
|
||||
sqlite3_backup b;
|
||||
sqlite3BtreeEnter(pTo);
|
||||
sqlite3BtreeEnter(pFrom);
|
||||
|
||||
assert( sqlite3BtreeIsInTrans(pTo) );
|
||||
pFd = sqlite3PagerFile(sqlite3BtreePager(pTo));
|
||||
if( pFd->pMethods ){
|
||||
i64 nByte = sqlite3BtreeGetPageSize(pFrom)*(i64)sqlite3BtreeLastPage(pFrom);
|
||||
sqlite3OsFileControl(pFd, SQLITE_FCNTL_OVERWRITE, &nByte);
|
||||
}
|
||||
|
||||
/* Set up an sqlite3_backup object. sqlite3_backup.pDestDb must be set
|
||||
** to 0. This is used by the implementations of sqlite3_backup_step()
|
||||
** and sqlite3_backup_finish() to detect that they are being called
|
||||
@@ -642,8 +704,11 @@ int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
|
||||
rc = sqlite3_backup_finish(&b);
|
||||
if( rc==SQLITE_OK ){
|
||||
pTo->pBt->pageSizeFixed = 0;
|
||||
}else{
|
||||
sqlite3PagerClearCache(sqlite3BtreePager(b.pDest));
|
||||
}
|
||||
|
||||
assert( sqlite3BtreeIsInTrans(pTo)==0 );
|
||||
sqlite3BtreeLeave(pFrom);
|
||||
sqlite3BtreeLeave(pTo);
|
||||
return rc;
|
||||
|
||||
@@ -39,12 +39,13 @@ static void lockBtreeMutex(Btree *p){
|
||||
** clear the p->locked boolean.
|
||||
*/
|
||||
static void unlockBtreeMutex(Btree *p){
|
||||
BtShared *pBt = p->pBt;
|
||||
assert( p->locked==1 );
|
||||
assert( sqlite3_mutex_held(p->pBt->mutex) );
|
||||
assert( sqlite3_mutex_held(pBt->mutex) );
|
||||
assert( sqlite3_mutex_held(p->db->mutex) );
|
||||
assert( p->db==p->pBt->db );
|
||||
assert( p->db==pBt->db );
|
||||
|
||||
sqlite3_mutex_leave(p->pBt->mutex);
|
||||
sqlite3_mutex_leave(pBt->mutex);
|
||||
p->locked = 0;
|
||||
}
|
||||
|
||||
@@ -185,30 +186,11 @@ void sqlite3BtreeLeaveCursor(BtCursor *pCur){
|
||||
*/
|
||||
void sqlite3BtreeEnterAll(sqlite3 *db){
|
||||
int i;
|
||||
Btree *p, *pLater;
|
||||
Btree *p;
|
||||
assert( sqlite3_mutex_held(db->mutex) );
|
||||
for(i=0; i<db->nDb; i++){
|
||||
p = db->aDb[i].pBt;
|
||||
assert( !p || (p->locked==0 && p->sharable) || p->pBt->db==p->db );
|
||||
if( p && p->sharable ){
|
||||
p->wantToLock++;
|
||||
if( !p->locked ){
|
||||
assert( p->wantToLock==1 );
|
||||
while( p->pPrev ) p = p->pPrev;
|
||||
/* Reason for ALWAYS: There must be at least on unlocked Btree in
|
||||
** the chain. Otherwise the !p->locked test above would have failed */
|
||||
while( p->locked && ALWAYS(p->pNext) ) p = p->pNext;
|
||||
for(pLater = p->pNext; pLater; pLater=pLater->pNext){
|
||||
if( pLater->locked ){
|
||||
unlockBtreeMutex(pLater);
|
||||
}
|
||||
}
|
||||
while( p ){
|
||||
lockBtreeMutex(p);
|
||||
p = p->pNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( p ) sqlite3BtreeEnter(p);
|
||||
}
|
||||
}
|
||||
void sqlite3BtreeLeaveAll(sqlite3 *db){
|
||||
@@ -217,16 +199,18 @@ void sqlite3BtreeLeaveAll(sqlite3 *db){
|
||||
assert( sqlite3_mutex_held(db->mutex) );
|
||||
for(i=0; i<db->nDb; i++){
|
||||
p = db->aDb[i].pBt;
|
||||
if( p && p->sharable ){
|
||||
assert( p->wantToLock>0 );
|
||||
p->wantToLock--;
|
||||
if( p->wantToLock==0 ){
|
||||
unlockBtreeMutex(p);
|
||||
}
|
||||
}
|
||||
if( p ) sqlite3BtreeLeave(p);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if a particular Btree requires a lock. Return FALSE if
|
||||
** no lock is ever required since it is not sharable.
|
||||
*/
|
||||
int sqlite3BtreeSharable(Btree *p){
|
||||
return p->sharable;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
/*
|
||||
** Return true if the current thread holds the database connection
|
||||
@@ -251,97 +235,42 @@ int sqlite3BtreeHoldsAllMutexes(sqlite3 *db){
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
/*
|
||||
** Add a new Btree pointer to a BtreeMutexArray.
|
||||
** if the pointer can possibly be shared with
|
||||
** another database connection.
|
||||
**
|
||||
** The pointers are kept in sorted order by pBtree->pBt. That
|
||||
** way when we go to enter all the mutexes, we can enter them
|
||||
** in order without every having to backup and retry and without
|
||||
** worrying about deadlock.
|
||||
**
|
||||
** The number of shared btrees will always be small (usually 0 or 1)
|
||||
** so an insertion sort is an adequate algorithm here.
|
||||
*/
|
||||
void sqlite3BtreeMutexArrayInsert(BtreeMutexArray *pArray, Btree *pBtree){
|
||||
int i, j;
|
||||
BtShared *pBt;
|
||||
if( pBtree==0 || pBtree->sharable==0 ) return;
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
for(i=0; i<pArray->nMutex; i++){
|
||||
assert( pArray->aBtree[i]!=pBtree );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
assert( pArray->nMutex>=0 );
|
||||
assert( pArray->nMutex<ArraySize(pArray->aBtree)-1 );
|
||||
pBt = pBtree->pBt;
|
||||
for(i=0; i<pArray->nMutex; i++){
|
||||
assert( pArray->aBtree[i]!=pBtree );
|
||||
if( pArray->aBtree[i]->pBt>pBt ){
|
||||
for(j=pArray->nMutex; j>i; j--){
|
||||
pArray->aBtree[j] = pArray->aBtree[j-1];
|
||||
}
|
||||
pArray->aBtree[i] = pBtree;
|
||||
pArray->nMutex++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
pArray->aBtree[pArray->nMutex++] = pBtree;
|
||||
}
|
||||
|
||||
/*
|
||||
** Enter the mutex of every btree in the array. This routine is
|
||||
** called at the beginning of sqlite3VdbeExec(). The mutexes are
|
||||
** exited at the end of the same function.
|
||||
** Return true if the correct mutexes are held for accessing the
|
||||
** db->aDb[iDb].pSchema structure. The mutexes required for schema
|
||||
** access are:
|
||||
**
|
||||
** (1) The mutex on db
|
||||
** (2) if iDb!=1, then the mutex on db->aDb[iDb].pBt.
|
||||
**
|
||||
** If pSchema is not NULL, then iDb is computed from pSchema and
|
||||
** db using sqlite3SchemaToIndex().
|
||||
*/
|
||||
void sqlite3BtreeMutexArrayEnter(BtreeMutexArray *pArray){
|
||||
int i;
|
||||
for(i=0; i<pArray->nMutex; i++){
|
||||
Btree *p = pArray->aBtree[i];
|
||||
/* Some basic sanity checking */
|
||||
assert( i==0 || pArray->aBtree[i-1]->pBt<p->pBt );
|
||||
assert( !p->locked || p->wantToLock>0 );
|
||||
|
||||
/* We should already hold a lock on the database connection */
|
||||
assert( sqlite3_mutex_held(p->db->mutex) );
|
||||
|
||||
/* The Btree is sharable because only sharable Btrees are entered
|
||||
** into the array in the first place. */
|
||||
assert( p->sharable );
|
||||
|
||||
p->wantToLock++;
|
||||
if( !p->locked ){
|
||||
lockBtreeMutex(p);
|
||||
}
|
||||
}
|
||||
int sqlite3SchemaMutexHeld(sqlite3 *db, int iDb, Schema *pSchema){
|
||||
Btree *p;
|
||||
assert( db!=0 );
|
||||
if( pSchema ) iDb = sqlite3SchemaToIndex(db, pSchema);
|
||||
assert( iDb>=0 && iDb<db->nDb );
|
||||
if( !sqlite3_mutex_held(db->mutex) ) return 0;
|
||||
if( iDb==1 ) return 1;
|
||||
p = db->aDb[iDb].pBt;
|
||||
assert( p!=0 );
|
||||
return p->sharable==0 || p->locked==1;
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
#else /* SQLITE_THREADSAFE>0 above. SQLITE_THREADSAFE==0 below */
|
||||
/*
|
||||
** Leave the mutex of every btree in the group.
|
||||
** The following are special cases for mutex enter routines for use
|
||||
** in single threaded applications that use shared cache. Except for
|
||||
** these two routines, all mutex operations are no-ops in that case and
|
||||
** are null #defines in btree.h.
|
||||
**
|
||||
** If shared cache is disabled, then all btree mutex routines, including
|
||||
** the ones below, are no-ops and are null #defines in btree.h.
|
||||
*/
|
||||
void sqlite3BtreeMutexArrayLeave(BtreeMutexArray *pArray){
|
||||
int i;
|
||||
for(i=0; i<pArray->nMutex; i++){
|
||||
Btree *p = pArray->aBtree[i];
|
||||
/* Some basic sanity checking */
|
||||
assert( i==0 || pArray->aBtree[i-1]->pBt<p->pBt );
|
||||
assert( p->locked );
|
||||
assert( p->wantToLock>0 );
|
||||
|
||||
/* We should already hold a lock on the database connection */
|
||||
assert( sqlite3_mutex_held(p->db->mutex) );
|
||||
|
||||
p->wantToLock--;
|
||||
if( p->wantToLock==0 ){
|
||||
unlockBtreeMutex(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
void sqlite3BtreeEnter(Btree *p){
|
||||
p->pBt->db = p->db;
|
||||
}
|
||||
|
||||
@@ -656,18 +656,21 @@ static int btreeMoveto(
|
||||
int rc; /* Status code */
|
||||
UnpackedRecord *pIdxKey; /* Unpacked index key */
|
||||
char aSpace[150]; /* Temp space for pIdxKey - to avoid a malloc */
|
||||
char *pFree = 0;
|
||||
|
||||
if( pKey ){
|
||||
assert( nKey==(i64)(int)nKey );
|
||||
pIdxKey = sqlite3VdbeRecordUnpack(pCur->pKeyInfo, (int)nKey, pKey,
|
||||
aSpace, sizeof(aSpace));
|
||||
pIdxKey = sqlite3VdbeAllocUnpackedRecord(
|
||||
pCur->pKeyInfo, aSpace, sizeof(aSpace), &pFree
|
||||
);
|
||||
if( pIdxKey==0 ) return SQLITE_NOMEM;
|
||||
sqlite3VdbeRecordUnpack(pCur->pKeyInfo, (int)nKey, pKey, pIdxKey);
|
||||
}else{
|
||||
pIdxKey = 0;
|
||||
}
|
||||
rc = sqlite3BtreeMovetoUnpacked(pCur, pIdxKey, nKey, bias, pRes);
|
||||
if( pKey ){
|
||||
sqlite3VdbeDeleteUnpackedRecord(pIdxKey);
|
||||
if( pFree ){
|
||||
sqlite3DbFree(pCur->pKeyInfo->db, pFree);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -788,6 +791,7 @@ static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){
|
||||
*pRC = SQLITE_CORRUPT_BKPT;
|
||||
goto ptrmap_exit;
|
||||
}
|
||||
assert( offset <= (int)pBt->usableSize-5 );
|
||||
pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage);
|
||||
|
||||
if( eType!=pPtrmap[offset] || get4byte(&pPtrmap[offset+1])!=parent ){
|
||||
@@ -827,6 +831,11 @@ static int ptrmapGet(BtShared *pBt, Pgno key, u8 *pEType, Pgno *pPgno){
|
||||
pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage);
|
||||
|
||||
offset = PTRMAP_PTROFFSET(iPtrmap, key);
|
||||
if( offset<0 ){
|
||||
sqlite3PagerUnref(pDbPage);
|
||||
return SQLITE_CORRUPT_BKPT;
|
||||
}
|
||||
assert( offset <= (int)pBt->usableSize-5 );
|
||||
assert( pEType!=0 );
|
||||
*pEType = pPtrmap[offset];
|
||||
if( pPgno ) *pPgno = get4byte(&pPtrmap[offset+1]);
|
||||
@@ -851,6 +860,8 @@ static int ptrmapGet(BtShared *pBt, Pgno key, u8 *pEType, Pgno *pPgno){
|
||||
*/
|
||||
#define findCell(P,I) \
|
||||
((P)->aData + ((P)->maskPage & get2byte(&(P)->aData[(P)->cellOffset+2*(I)])))
|
||||
#define findCellv2(D,M,O,I) (D+(M&get2byte(D+(O+2*(I)))))
|
||||
|
||||
|
||||
/*
|
||||
** This a more complex version of findCell() that works for
|
||||
@@ -918,14 +929,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
|
||||
@@ -1233,7 +1239,7 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){
|
||||
*/
|
||||
top -= nByte;
|
||||
put2byte(&data[hdr+5], top);
|
||||
assert( top+nByte <= pPage->pBt->usableSize );
|
||||
assert( top+nByte <= (int)pPage->pBt->usableSize );
|
||||
*pIdx = top;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@@ -1254,7 +1260,7 @@ static int freeSpace(MemPage *pPage, int start, int size){
|
||||
assert( pPage->pBt!=0 );
|
||||
assert( sqlite3PagerIswriteable(pPage->pDbPage) );
|
||||
assert( start>=pPage->hdrOffset+6+pPage->childPtrSize );
|
||||
assert( (start + size)<=pPage->pBt->usableSize );
|
||||
assert( (start + size) <= (int)pPage->pBt->usableSize );
|
||||
assert( sqlite3_mutex_held(pPage->pBt->mutex) );
|
||||
assert( size>=0 ); /* Minimum cell size is 4 */
|
||||
|
||||
@@ -1297,7 +1303,7 @@ static int freeSpace(MemPage *pPage, int start, int size){
|
||||
while( (pbegin = get2byte(&data[addr]))>0 ){
|
||||
int pnext, psize, x;
|
||||
assert( pbegin>addr );
|
||||
assert( pbegin<=pPage->pBt->usableSize-4 );
|
||||
assert( pbegin <= (int)pPage->pBt->usableSize-4 );
|
||||
pnext = get2byte(&data[pbegin]);
|
||||
psize = get2byte(&data[pbegin+2]);
|
||||
if( pbegin + psize + 3 >= pnext && pnext>0 ){
|
||||
@@ -1672,11 +1678,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
|
||||
@@ -1684,13 +1699,13 @@ static int btreeInvokeBusyHandler(void *pArg){
|
||||
** to problems with locking.
|
||||
*/
|
||||
int sqlite3BtreeOpen(
|
||||
sqlite3_vfs *pVfs, /* VFS to use for this b-tree */
|
||||
const char *zFilename, /* Name of the file containing the BTree database */
|
||||
sqlite3 *db, /* Associated database handle */
|
||||
Btree **ppBtree, /* Pointer to new Btree object written here */
|
||||
int flags, /* Options */
|
||||
int vfsFlags /* Flags passed through to sqlite3_vfs.xOpen() */
|
||||
){
|
||||
sqlite3_vfs *pVfs; /* The VFS to use for this btree */
|
||||
BtShared *pBt = 0; /* Shared part of btree structure */
|
||||
Btree *p; /* Handle to return */
|
||||
sqlite3_mutex *mutexOpen = 0; /* Prevents a race condition. Ticket #3537 */
|
||||
@@ -1698,23 +1713,39 @@ 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( pVfs!=0 );
|
||||
assert( sqlite3_mutex_held(db->mutex) );
|
||||
assert( (flags&0xff)==flags ); /* flags fit in 8 bits */
|
||||
|
||||
pVfs = db->pVfs;
|
||||
/* 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;
|
||||
}
|
||||
p = sqlite3MallocZero(sizeof(Btree));
|
||||
if( !p ){
|
||||
return SQLITE_NOMEM;
|
||||
@@ -1731,21 +1762,23 @@ 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);
|
||||
sqlite3_mutex *mutexShared;
|
||||
MUTEX_LOGIC( sqlite3_mutex *mutexShared; )
|
||||
p->sharable = 1;
|
||||
if( !zFullPathname ){
|
||||
sqlite3_free(p);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
sqlite3OsFullPathname(pVfs, zFilename, nFullPathname, zFullPathname);
|
||||
#if SQLITE_THREADSAFE
|
||||
mutexOpen = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_OPEN);
|
||||
sqlite3_mutex_enter(mutexOpen);
|
||||
mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
|
||||
sqlite3_mutex_enter(mutexShared);
|
||||
#endif
|
||||
for(pBt=GLOBAL(BtShared*,sqlite3SharedCacheList); pBt; pBt=pBt->pNext){
|
||||
assert( pBt->nRef>0 );
|
||||
if( 0==strcmp(zFullPathname, sqlite3PagerFilename(pBt->pPager))
|
||||
@@ -1806,6 +1839,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;
|
||||
@@ -1850,9 +1884,9 @@ int sqlite3BtreeOpen(
|
||||
/* Add the new BtShared object to the linked list sharable BtShareds.
|
||||
*/
|
||||
if( p->sharable ){
|
||||
sqlite3_mutex *mutexShared;
|
||||
MUTEX_LOGIC( sqlite3_mutex *mutexShared; )
|
||||
pBt->nRef = 1;
|
||||
mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
|
||||
MUTEX_LOGIC( mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);)
|
||||
if( SQLITE_THREADSAFE && sqlite3GlobalConfig.bCoreMutex ){
|
||||
pBt->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_FAST);
|
||||
if( pBt->mutex==0 ){
|
||||
@@ -1910,6 +1944,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) );
|
||||
@@ -1926,12 +1968,12 @@ btree_open_out:
|
||||
*/
|
||||
static int removeFromSharingList(BtShared *pBt){
|
||||
#ifndef SQLITE_OMIT_SHARED_CACHE
|
||||
sqlite3_mutex *pMaster;
|
||||
MUTEX_LOGIC( sqlite3_mutex *pMaster; )
|
||||
BtShared *pList;
|
||||
int removed = 0;
|
||||
|
||||
assert( sqlite3_mutex_notheld(pBt->mutex) );
|
||||
pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
|
||||
MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
|
||||
sqlite3_mutex_enter(pMaster);
|
||||
pBt->nRef--;
|
||||
if( pBt->nRef<=0 ){
|
||||
@@ -2067,11 +2109,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;
|
||||
}
|
||||
@@ -2092,7 +2140,6 @@ int sqlite3BtreeSyncDisabled(Btree *p){
|
||||
return rc;
|
||||
}
|
||||
|
||||
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) || !defined(SQLITE_OMIT_VACUUM)
|
||||
/*
|
||||
** Change the default pages size and the number of reserved bytes per page.
|
||||
** Or, if the page size has already been fixed, return SQLITE_READONLY
|
||||
@@ -2147,6 +2194,7 @@ int sqlite3BtreeGetPageSize(Btree *p){
|
||||
return p->pBt->pageSize;
|
||||
}
|
||||
|
||||
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) || !defined(SQLITE_OMIT_VACUUM)
|
||||
/*
|
||||
** Return the number of bytes of space at the end of every page that
|
||||
** are intentually left unused. This is the "reserved" space that is
|
||||
@@ -2346,7 +2394,7 @@ static int lockBtree(BtShared *pBt){
|
||||
pageSize-usableSize);
|
||||
return rc;
|
||||
}
|
||||
if( nPageHeader>nPageFile ){
|
||||
if( (pBt->db->flags & SQLITE_RecoveryMode)==0 && nPage>nPageFile ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
goto page1_init_failed;
|
||||
}
|
||||
@@ -2700,11 +2748,12 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){
|
||||
if( eType==PTRMAP_OVERFLOW1 ){
|
||||
CellInfo info;
|
||||
btreeParseCellPtr(pPage, pCell, &info);
|
||||
if( info.iOverflow ){
|
||||
if( iFrom==get4byte(&pCell[info.iOverflow]) ){
|
||||
put4byte(&pCell[info.iOverflow], iTo);
|
||||
break;
|
||||
}
|
||||
if( info.iOverflow
|
||||
&& pCell+info.iOverflow+3<=pPage->aData+pPage->maskPage
|
||||
&& iFrom==get4byte(&pCell[info.iOverflow])
|
||||
){
|
||||
put4byte(&pCell[info.iOverflow], iTo);
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
if( get4byte(pCell)==iFrom ){
|
||||
@@ -3125,12 +3174,23 @@ static void btreeEndTransaction(Btree *p){
|
||||
** the rollback journal (which causes the transaction to commit) and
|
||||
** drop locks.
|
||||
**
|
||||
** Normally, if an error occurs while the pager layer is attempting to
|
||||
** finalize the underlying journal file, this function returns an error and
|
||||
** the upper layer will attempt a rollback. However, if the second argument
|
||||
** is non-zero then this b-tree transaction is part of a multi-file
|
||||
** transaction. In this case, the transaction has already been committed
|
||||
** (by deleting a master journal file) and the caller will ignore this
|
||||
** functions return code. So, even if an error occurs in the pager layer,
|
||||
** reset the b-tree objects internal state to indicate that the write
|
||||
** transaction has been closed. This is quite safe, as the pager will have
|
||||
** transitioned to the error state.
|
||||
**
|
||||
** This will release the write lock on the database file. If there
|
||||
** are no active cursors, it also releases the read lock.
|
||||
*/
|
||||
int sqlite3BtreeCommitPhaseTwo(Btree *p){
|
||||
BtShared *pBt = p->pBt;
|
||||
int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){
|
||||
|
||||
if( p->inTrans==TRANS_NONE ) return SQLITE_OK;
|
||||
sqlite3BtreeEnter(p);
|
||||
btreeIntegrity(p);
|
||||
|
||||
@@ -3139,10 +3199,11 @@ 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);
|
||||
if( rc!=SQLITE_OK ){
|
||||
if( rc!=SQLITE_OK && bCleanup==0 ){
|
||||
sqlite3BtreeLeave(p);
|
||||
return rc;
|
||||
}
|
||||
@@ -3162,7 +3223,7 @@ int sqlite3BtreeCommit(Btree *p){
|
||||
sqlite3BtreeEnter(p);
|
||||
rc = sqlite3BtreeCommitPhaseOne(p, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3BtreeCommitPhaseTwo(p);
|
||||
rc = sqlite3BtreeCommitPhaseTwo(p, 0);
|
||||
}
|
||||
sqlite3BtreeLeave(p);
|
||||
return rc;
|
||||
@@ -3413,7 +3474,8 @@ static int btreeCursor(
|
||||
return SQLITE_READONLY;
|
||||
}
|
||||
if( iTable==1 && btreePagecount(pBt)==0 ){
|
||||
return SQLITE_EMPTY;
|
||||
assert( wrFlag==0 );
|
||||
iTable = 0;
|
||||
}
|
||||
|
||||
/* Now that no other errors can occur, finish filling in the BtCursor
|
||||
@@ -3878,21 +3940,55 @@ static int accessPayload(
|
||||
/* Need to read this page properly. It contains some of the
|
||||
** range of data that is being read (eOp==0) or written (eOp!=0).
|
||||
*/
|
||||
DbPage *pDbPage;
|
||||
#ifdef SQLITE_DIRECT_OVERFLOW_READ
|
||||
sqlite3_file *fd;
|
||||
#endif
|
||||
int a = amt;
|
||||
rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage);
|
||||
if( rc==SQLITE_OK ){
|
||||
aPayload = sqlite3PagerGetData(pDbPage);
|
||||
nextPage = get4byte(aPayload);
|
||||
if( a + offset > ovflSize ){
|
||||
a = ovflSize - offset;
|
||||
}
|
||||
rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage);
|
||||
sqlite3PagerUnref(pDbPage);
|
||||
offset = 0;
|
||||
amt -= a;
|
||||
pBuf += a;
|
||||
if( a + offset > ovflSize ){
|
||||
a = ovflSize - offset;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_DIRECT_OVERFLOW_READ
|
||||
/* If all the following are true:
|
||||
**
|
||||
** 1) this is a read operation, and
|
||||
** 2) data is required from the start of this overflow page, and
|
||||
** 3) the database is file-backed, and
|
||||
** 4) there is no open write-transaction, and
|
||||
** 5) the database is not a WAL database,
|
||||
**
|
||||
** then data can be read directly from the database file into the
|
||||
** output buffer, bypassing the page-cache altogether. This speeds
|
||||
** up loading large records that span many overflow pages.
|
||||
*/
|
||||
if( eOp==0 /* (1) */
|
||||
&& offset==0 /* (2) */
|
||||
&& pBt->inTransaction==TRANS_READ /* (4) */
|
||||
&& (fd = sqlite3PagerFile(pBt->pPager))->pMethods /* (3) */
|
||||
&& pBt->pPage1->aData[19]==0x01 /* (5) */
|
||||
){
|
||||
u8 aSave[4];
|
||||
u8 *aWrite = &pBuf[-4];
|
||||
memcpy(aSave, aWrite, 4);
|
||||
rc = sqlite3OsRead(fd, aWrite, a+4, pBt->pageSize * (nextPage-1));
|
||||
nextPage = get4byte(aWrite);
|
||||
memcpy(aWrite, aSave, 4);
|
||||
}else
|
||||
#endif
|
||||
|
||||
{
|
||||
DbPage *pDbPage;
|
||||
rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage);
|
||||
if( rc==SQLITE_OK ){
|
||||
aPayload = sqlite3PagerGetData(pDbPage);
|
||||
nextPage = get4byte(aPayload);
|
||||
rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage);
|
||||
sqlite3PagerUnref(pDbPage);
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
amt -= a;
|
||||
pBuf += a;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4167,6 +4263,9 @@ static int moveToRoot(BtCursor *pCur){
|
||||
releasePage(pCur->apPage[i]);
|
||||
}
|
||||
pCur->iPage = 0;
|
||||
}else if( pCur->pgnoRoot==0 ){
|
||||
pCur->eState = CURSOR_INVALID;
|
||||
return SQLITE_OK;
|
||||
}else{
|
||||
rc = getAndInitPage(pBt, pCur->pgnoRoot, &pCur->apPage[0]);
|
||||
if( rc!=SQLITE_OK ){
|
||||
@@ -4276,7 +4375,7 @@ int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){
|
||||
rc = moveToRoot(pCur);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( pCur->eState==CURSOR_INVALID ){
|
||||
assert( pCur->apPage[pCur->iPage]->nCell==0 );
|
||||
assert( pCur->pgnoRoot==0 || pCur->apPage[pCur->iPage]->nCell==0 );
|
||||
*pRes = 1;
|
||||
}else{
|
||||
assert( pCur->apPage[pCur->iPage]->nCell>0 );
|
||||
@@ -4315,7 +4414,7 @@ int sqlite3BtreeLast(BtCursor *pCur, int *pRes){
|
||||
rc = moveToRoot(pCur);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( CURSOR_INVALID==pCur->eState ){
|
||||
assert( pCur->apPage[pCur->iPage]->nCell==0 );
|
||||
assert( pCur->pgnoRoot==0 || pCur->apPage[pCur->iPage]->nCell==0 );
|
||||
*pRes = 1;
|
||||
}else{
|
||||
assert( pCur->eState==CURSOR_VALID );
|
||||
@@ -4388,17 +4487,17 @@ int sqlite3BtreeMovetoUnpacked(
|
||||
if( rc ){
|
||||
return rc;
|
||||
}
|
||||
assert( pCur->apPage[pCur->iPage] );
|
||||
assert( pCur->apPage[pCur->iPage]->isInit );
|
||||
assert( pCur->apPage[pCur->iPage]->nCell>0 || pCur->eState==CURSOR_INVALID );
|
||||
assert( pCur->pgnoRoot==0 || pCur->apPage[pCur->iPage] );
|
||||
assert( pCur->pgnoRoot==0 || pCur->apPage[pCur->iPage]->isInit );
|
||||
assert( pCur->eState==CURSOR_INVALID || pCur->apPage[pCur->iPage]->nCell>0 );
|
||||
if( pCur->eState==CURSOR_INVALID ){
|
||||
*pRes = -1;
|
||||
assert( pCur->apPage[pCur->iPage]->nCell==0 );
|
||||
assert( pCur->pgnoRoot==0 || pCur->apPage[pCur->iPage]->nCell==0 );
|
||||
return SQLITE_OK;
|
||||
}
|
||||
assert( pCur->apPage[0]->intKey || pIdxKey );
|
||||
for(;;){
|
||||
int lwr, upr;
|
||||
int lwr, upr, idx;
|
||||
Pgno chldPg;
|
||||
MemPage *pPage = pCur->apPage[pCur->iPage];
|
||||
int c;
|
||||
@@ -4414,14 +4513,14 @@ int sqlite3BtreeMovetoUnpacked(
|
||||
lwr = 0;
|
||||
upr = pPage->nCell-1;
|
||||
if( biasRight ){
|
||||
pCur->aiIdx[pCur->iPage] = (u16)upr;
|
||||
pCur->aiIdx[pCur->iPage] = (u16)(idx = upr);
|
||||
}else{
|
||||
pCur->aiIdx[pCur->iPage] = (u16)((upr+lwr)/2);
|
||||
pCur->aiIdx[pCur->iPage] = (u16)(idx = (upr+lwr)/2);
|
||||
}
|
||||
for(;;){
|
||||
int idx = pCur->aiIdx[pCur->iPage]; /* Index of current cell in pPage */
|
||||
u8 *pCell; /* Pointer to current cell in pPage */
|
||||
|
||||
assert( idx==pCur->aiIdx[pCur->iPage] );
|
||||
pCur->info.nSize = 0;
|
||||
pCell = findCell(pPage, idx) + pPage->childPtrSize;
|
||||
if( pPage->intKey ){
|
||||
@@ -4488,7 +4587,6 @@ int sqlite3BtreeMovetoUnpacked(
|
||||
if( c==0 ){
|
||||
if( pPage->intKey && !pPage->leaf ){
|
||||
lwr = idx;
|
||||
upr = lwr - 1;
|
||||
break;
|
||||
}else{
|
||||
*pRes = 0;
|
||||
@@ -4504,9 +4602,9 @@ int sqlite3BtreeMovetoUnpacked(
|
||||
if( lwr>upr ){
|
||||
break;
|
||||
}
|
||||
pCur->aiIdx[pCur->iPage] = (u16)((lwr+upr)/2);
|
||||
pCur->aiIdx[pCur->iPage] = (u16)(idx = (lwr+upr)/2);
|
||||
}
|
||||
assert( lwr==upr+1 );
|
||||
assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) );
|
||||
assert( pPage->isInit );
|
||||
if( pPage->leaf ){
|
||||
chldPg = 0;
|
||||
@@ -4771,8 +4869,10 @@ static int allocateBtreePage(
|
||||
pTrunk = 0;
|
||||
goto end_allocate_page;
|
||||
}
|
||||
assert( pTrunk!=0 );
|
||||
assert( pTrunk->aData!=0 );
|
||||
|
||||
k = get4byte(&pTrunk->aData[4]);
|
||||
k = get4byte(&pTrunk->aData[4]); /* # of leaves on this trunk page */
|
||||
if( k==0 && !searchList ){
|
||||
/* The trunk has no leaves and the list is not being searched.
|
||||
** So extract the trunk page itself and use it as the newly
|
||||
@@ -4857,19 +4957,13 @@ static int allocateBtreePage(
|
||||
u32 closest;
|
||||
Pgno iPage;
|
||||
unsigned char *aData = pTrunk->aData;
|
||||
rc = sqlite3PagerWrite(pTrunk->pDbPage);
|
||||
if( rc ){
|
||||
goto end_allocate_page;
|
||||
}
|
||||
if( nearby>0 ){
|
||||
u32 i;
|
||||
int dist;
|
||||
closest = 0;
|
||||
dist = get4byte(&aData[8]) - nearby;
|
||||
if( dist<0 ) dist = -dist;
|
||||
dist = sqlite3AbsInt32(get4byte(&aData[8]) - nearby);
|
||||
for(i=1; i<k; i++){
|
||||
int d2 = get4byte(&aData[8+i*4]) - nearby;
|
||||
if( d2<0 ) d2 = -d2;
|
||||
int d2 = sqlite3AbsInt32(get4byte(&aData[8+i*4]) - nearby);
|
||||
if( d2<dist ){
|
||||
closest = i;
|
||||
dist = d2;
|
||||
@@ -4892,11 +4986,12 @@ static int allocateBtreePage(
|
||||
TRACE(("ALLOCATE: %d was leaf %d of %d on trunk %d"
|
||||
": %d more free pages\n",
|
||||
*pPgno, closest+1, k, pTrunk->pgno, n-1));
|
||||
rc = sqlite3PagerWrite(pTrunk->pDbPage);
|
||||
if( rc ) goto end_allocate_page;
|
||||
if( closest<k-1 ){
|
||||
memcpy(&aData[8+closest*4], &aData[4+k*4], 4);
|
||||
}
|
||||
put4byte(&aData[4], k-1);
|
||||
assert( sqlite3PagerIswriteable(pTrunk->pDbPage) );
|
||||
noContent = !btreeGetHasContent(pBt, *pPgno);
|
||||
rc = btreeGetPage(pBt, *pPgno, ppPage, noContent);
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -4965,6 +5060,7 @@ end_allocate_page:
|
||||
}else{
|
||||
*ppPage = 0;
|
||||
}
|
||||
assert( rc!=SQLITE_OK || sqlite3PagerIswriteable((*ppPage)->pDbPage) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -5124,6 +5220,9 @@ static int clearCell(MemPage *pPage, unsigned char *pCell){
|
||||
if( info.iOverflow==0 ){
|
||||
return SQLITE_OK; /* No overflow pages. Return without doing anything */
|
||||
}
|
||||
if( pCell+info.iOverflow+3 > pPage->aData+pPage->maskPage ){
|
||||
return SQLITE_CORRUPT; /* Cell extends past end of page */
|
||||
}
|
||||
ovflPgno = get4byte(&pCell[info.iOverflow]);
|
||||
assert( pBt->usableSize > 4 );
|
||||
ovflPageSize = pBt->usableSize - 4;
|
||||
@@ -5341,10 +5440,10 @@ static int fillInCell(
|
||||
** "sz" must be the number of bytes in the cell.
|
||||
*/
|
||||
static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){
|
||||
int i; /* Loop counter */
|
||||
u32 pc; /* Offset to cell content of cell being deleted */
|
||||
u8 *data; /* pPage->aData */
|
||||
u8 *ptr; /* Used to move bytes around within data[] */
|
||||
u8 *endPtr; /* End of loop */
|
||||
int rc; /* The return code */
|
||||
int hdr; /* Beginning of the header. 0 most pages. 100 page 1 */
|
||||
|
||||
@@ -5369,9 +5468,11 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){
|
||||
*pRC = rc;
|
||||
return;
|
||||
}
|
||||
for(i=idx+1; i<pPage->nCell; i++, ptr+=2){
|
||||
ptr[0] = ptr[2];
|
||||
ptr[1] = ptr[3];
|
||||
endPtr = &data[pPage->cellOffset + 2*pPage->nCell - 2];
|
||||
assert( (SQLITE_PTR_TO_INT(ptr)&1)==0 ); /* ptr is always 2-byte aligned */
|
||||
while( ptr<endPtr ){
|
||||
*(u16*)ptr = *(u16*)&ptr[2];
|
||||
ptr += 2;
|
||||
}
|
||||
pPage->nCell--;
|
||||
put2byte(&data[hdr+3], pPage->nCell);
|
||||
@@ -5411,6 +5512,7 @@ static void insertCell(
|
||||
int cellOffset; /* Address of first cell pointer in data[] */
|
||||
u8 *data; /* The content of the whole page */
|
||||
u8 *ptr; /* Used for moving information around in data[] */
|
||||
u8 *endPtr; /* End of the loop */
|
||||
|
||||
int nSkip = (iChild ? 4 : 0);
|
||||
|
||||
@@ -5454,16 +5556,19 @@ static void insertCell(
|
||||
/* The allocateSpace() routine guarantees the following two properties
|
||||
** if it returns success */
|
||||
assert( idx >= end+2 );
|
||||
assert( idx+sz <= pPage->pBt->usableSize );
|
||||
assert( idx+sz <= (int)pPage->pBt->usableSize );
|
||||
pPage->nCell++;
|
||||
pPage->nFree -= (u16)(2 + sz);
|
||||
memcpy(&data[idx+nSkip], pCell+nSkip, sz-nSkip);
|
||||
if( iChild ){
|
||||
put4byte(&data[idx], iChild);
|
||||
}
|
||||
for(j=end, ptr=&data[j]; j>ins; j-=2, ptr-=2){
|
||||
ptr[0] = ptr[-2];
|
||||
ptr[1] = ptr[-1];
|
||||
ptr = &data[end];
|
||||
endPtr = &data[ins];
|
||||
assert( (SQLITE_PTR_TO_INT(ptr)&1)==0 ); /* ptr is always 2-byte aligned */
|
||||
while( ptr>endPtr ){
|
||||
*(u16*)ptr = *(u16*)&ptr[-2];
|
||||
ptr -= 2;
|
||||
}
|
||||
put2byte(&data[ins], idx);
|
||||
put2byte(&data[pPage->hdrOffset+3], pPage->nCell);
|
||||
@@ -5497,7 +5602,8 @@ static void assemblePage(
|
||||
|
||||
assert( pPage->nOverflow==0 );
|
||||
assert( sqlite3_mutex_held(pPage->pBt->mutex) );
|
||||
assert( nCell>=0 && nCell<=MX_CELL(pPage->pBt) && MX_CELL(pPage->pBt)<=10921);
|
||||
assert( nCell>=0 && nCell<=(int)MX_CELL(pPage->pBt)
|
||||
&& (int)MX_CELL(pPage->pBt)<=10921);
|
||||
assert( sqlite3PagerIswriteable(pPage->pDbPage) );
|
||||
|
||||
/* Check that the page has just been zeroed by zeroPage() */
|
||||
@@ -5507,10 +5613,11 @@ static void assemblePage(
|
||||
pCellptr = &data[pPage->cellOffset + nCell*2];
|
||||
cellbody = nUsable;
|
||||
for(i=nCell-1; i>=0; i--){
|
||||
u16 sz = aSize[i];
|
||||
pCellptr -= 2;
|
||||
cellbody -= aSize[i];
|
||||
cellbody -= sz;
|
||||
put2byte(pCellptr, cellbody);
|
||||
memcpy(&data[cellbody], apCell[i], aSize[i]);
|
||||
memcpy(&data[cellbody], apCell[i], sz);
|
||||
}
|
||||
put2byte(&data[hdr+3], nCell);
|
||||
put2byte(&data[hdr+5], cellbody);
|
||||
@@ -5711,7 +5818,7 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){
|
||||
|
||||
assert( pFrom->isInit );
|
||||
assert( pFrom->nFree>=iToHdr );
|
||||
assert( get2byte(&aFrom[iFromHdr+5])<=pBt->usableSize );
|
||||
assert( get2byte(&aFrom[iFromHdr+5]) <= (int)pBt->usableSize );
|
||||
|
||||
/* Copy the b-tree node content from page pFrom to page pTo. */
|
||||
iData = get2byte(&aFrom[iFromHdr+5]);
|
||||
@@ -5891,13 +5998,15 @@ static int balance_nonroot(
|
||||
** four bytes of the divider cell. So the pointer is safe to use
|
||||
** later on.
|
||||
**
|
||||
** Unless SQLite is compiled in secure-delete mode. In this case,
|
||||
** But not if we are in secure-delete mode. In secure-delete mode,
|
||||
** the dropCell() routine will overwrite the entire cell with zeroes.
|
||||
** In this case, temporarily copy the cell into the aOvflSpace[]
|
||||
** buffer. It will be copied out again as soon as the aSpace[] buffer
|
||||
** is allocated. */
|
||||
if( pBt->secureDelete ){
|
||||
int iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData);
|
||||
int iOff;
|
||||
|
||||
iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData);
|
||||
if( (iOff+szNew[i])>(int)pBt->usableSize ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
memset(apOld, 0, (i+1)*sizeof(MemPage*));
|
||||
@@ -5964,12 +6073,24 @@ static int balance_nonroot(
|
||||
memcpy(pOld->aData, apOld[i]->aData, pBt->pageSize);
|
||||
|
||||
limit = pOld->nCell+pOld->nOverflow;
|
||||
for(j=0; j<limit; j++){
|
||||
assert( nCell<nMaxCells );
|
||||
apCell[nCell] = findOverflowCell(pOld, j);
|
||||
szCell[nCell] = cellSizePtr(pOld, apCell[nCell]);
|
||||
nCell++;
|
||||
}
|
||||
if( pOld->nOverflow>0 ){
|
||||
for(j=0; j<limit; j++){
|
||||
assert( nCell<nMaxCells );
|
||||
apCell[nCell] = findOverflowCell(pOld, j);
|
||||
szCell[nCell] = cellSizePtr(pOld, apCell[nCell]);
|
||||
nCell++;
|
||||
}
|
||||
}else{
|
||||
u8 *aData = pOld->aData;
|
||||
u16 maskPage = pOld->maskPage;
|
||||
u16 cellOffset = pOld->cellOffset;
|
||||
for(j=0; j<limit; j++){
|
||||
assert( nCell<nMaxCells );
|
||||
apCell[nCell] = findCellv2(aData, maskPage, cellOffset, j);
|
||||
szCell[nCell] = cellSizePtr(pOld, apCell[nCell]);
|
||||
nCell++;
|
||||
}
|
||||
}
|
||||
if( i<nOld-1 && !leafData){
|
||||
u16 sz = (u16)szNew[i];
|
||||
u8 *pTemp;
|
||||
@@ -5978,7 +6099,7 @@ static int balance_nonroot(
|
||||
pTemp = &aSpace1[iSpace1];
|
||||
iSpace1 += sz;
|
||||
assert( sz<=pBt->maxLocal+23 );
|
||||
assert( iSpace1<=pBt->pageSize );
|
||||
assert( iSpace1 <= (int)pBt->pageSize );
|
||||
memcpy(pTemp, apDiv[i], sz);
|
||||
apCell[nCell] = pTemp+leafCorrection;
|
||||
assert( leafCorrection==0 || leafCorrection==4 );
|
||||
@@ -6143,9 +6264,7 @@ static int balance_nonroot(
|
||||
}
|
||||
}
|
||||
if( minI>i ){
|
||||
int t;
|
||||
MemPage *pT;
|
||||
t = apNew[i]->pgno;
|
||||
pT = apNew[i];
|
||||
apNew[i] = apNew[minI];
|
||||
apNew[minI] = pT;
|
||||
@@ -6224,7 +6343,7 @@ static int balance_nonroot(
|
||||
}
|
||||
iOvflSpace += sz;
|
||||
assert( sz<=pBt->maxLocal+23 );
|
||||
assert( iOvflSpace<=pBt->pageSize );
|
||||
assert( iOvflSpace <= (int)pBt->pageSize );
|
||||
insertCell(pParent, nxDiv, pCell, sz, pTemp, pNew->pgno, &rc);
|
||||
if( rc!=SQLITE_OK ) goto balance_cleanup;
|
||||
assert( sqlite3PagerIswriteable(pParent->pDbPage) );
|
||||
@@ -6307,6 +6426,7 @@ static int balance_nonroot(
|
||||
/* Cell i is the cell immediately following the last cell on old
|
||||
** sibling page j. If the siblings are not leaf pages of an
|
||||
** intkey b-tree, then cell i was a divider cell. */
|
||||
assert( j+1 < ArraySize(apCopy) );
|
||||
pOld = apCopy[++j];
|
||||
iNextOld = i + !leafData + pOld->nCell + pOld->nOverflow;
|
||||
if( pOld->nOverflow ){
|
||||
@@ -6669,7 +6789,7 @@ int sqlite3BtreeInsert(
|
||||
rc = fillInCell(pPage, newCell, pKey, nKey, pData, nData, nZero, &szNew);
|
||||
if( rc ) goto end_insert;
|
||||
assert( szNew==cellSizePtr(pPage, newCell) );
|
||||
assert( szNew<=MX_CELL_SIZE(pBt) );
|
||||
assert( szNew <= MX_CELL_SIZE(pBt) );
|
||||
idx = pCur->aiIdx[pCur->iPage];
|
||||
if( loc==0 ){
|
||||
u16 szOld;
|
||||
@@ -6809,7 +6929,7 @@ int sqlite3BtreeDelete(BtCursor *pCur){
|
||||
|
||||
pCell = findCell(pLeaf, pLeaf->nCell-1);
|
||||
nCell = cellSizePtr(pLeaf, pCell);
|
||||
assert( MX_CELL_SIZE(pBt)>=nCell );
|
||||
assert( MX_CELL_SIZE(pBt) >= nCell );
|
||||
|
||||
allocateTempSpace(pBt);
|
||||
pTmp = pBt->pTmpSpace;
|
||||
@@ -6860,11 +6980,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 +7104,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;
|
||||
}
|
||||
@@ -7282,6 +7409,11 @@ int sqlite3BtreeUpdateMeta(Btree *p, int idx, u32 iMeta){
|
||||
int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){
|
||||
i64 nEntry = 0; /* Value to return in *pnEntry */
|
||||
int rc; /* Return code */
|
||||
|
||||
if( pCur->pgnoRoot==0 ){
|
||||
*pnEntry = 0;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
rc = moveToRoot(pCur);
|
||||
|
||||
/* Unless an error occurs, the following loop runs one iteration for each
|
||||
@@ -7889,8 +8021,10 @@ int sqlite3BtreeIsInTrans(Btree *p){
|
||||
**
|
||||
** Return SQLITE_LOCKED if this or any other connection has an open
|
||||
** transaction on the shared-cache the argument Btree is connected to.
|
||||
**
|
||||
** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL or RESTART.
|
||||
*/
|
||||
int sqlite3BtreeCheckpoint(Btree *p){
|
||||
int sqlite3BtreeCheckpoint(Btree *p, int eMode, int *pnLog, int *pnCkpt){
|
||||
int rc = SQLITE_OK;
|
||||
if( p ){
|
||||
BtShared *pBt = p->pBt;
|
||||
@@ -7898,7 +8032,7 @@ int sqlite3BtreeCheckpoint(Btree *p){
|
||||
if( pBt->inTransaction!=TRANS_NONE ){
|
||||
rc = SQLITE_LOCKED;
|
||||
}else{
|
||||
rc = sqlite3PagerCheckpoint(pBt->pPager);
|
||||
rc = sqlite3PagerCheckpoint(pBt->pPager, eMode, pnLog, pnCkpt);
|
||||
}
|
||||
sqlite3BtreeLeave(p);
|
||||
}
|
||||
@@ -7938,7 +8072,7 @@ int sqlite3BtreeIsInBackup(Btree *p){
|
||||
**
|
||||
** Just before the shared-btree is closed, the function passed as the
|
||||
** xFree argument when the memory allocation was made is invoked on the
|
||||
** blob of allocated memory. This function should not call sqlite3_free()
|
||||
** blob of allocated memory. The xFree function should not call sqlite3_free()
|
||||
** on the memory, the btree layer does that.
|
||||
*/
|
||||
void *sqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void *)){
|
||||
@@ -8050,8 +8184,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
|
||||
@@ -8065,7 +8198,6 @@ int sqlite3BtreeSetVersion(Btree *pBtree, int iVersion){
|
||||
BtShared *pBt = pBtree->pBt;
|
||||
int rc; /* Return code */
|
||||
|
||||
assert( pBtree->inTrans==TRANS_NONE );
|
||||
assert( iVersion==1 || iVersion==2 );
|
||||
|
||||
/* If setting the version fields to 1, do not automatically open the
|
||||
|
||||
@@ -39,21 +39,10 @@
|
||||
typedef struct Btree Btree;
|
||||
typedef struct BtCursor BtCursor;
|
||||
typedef struct BtShared BtShared;
|
||||
typedef struct BtreeMutexArray BtreeMutexArray;
|
||||
|
||||
/*
|
||||
** This structure records all of the Btrees that need to hold
|
||||
** a mutex before we enter sqlite3VdbeExec(). The Btrees are
|
||||
** are placed in aBtree[] in order of aBtree[]->pBt. That way,
|
||||
** we can always lock and unlock them all quickly.
|
||||
*/
|
||||
struct BtreeMutexArray {
|
||||
int nMutex;
|
||||
Btree *aBtree[SQLITE_MAX_ATTACHED+1];
|
||||
};
|
||||
|
||||
|
||||
int sqlite3BtreeOpen(
|
||||
sqlite3_vfs *pVfs, /* VFS to use with this b-tree */
|
||||
const char *zFilename, /* Name of database file to open */
|
||||
sqlite3 *db, /* Associated database connection */
|
||||
Btree **ppBtree, /* Return open Btree* here */
|
||||
@@ -67,16 +56,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*);
|
||||
@@ -88,7 +76,7 @@ int sqlite3BtreeSetAutoVacuum(Btree *, int);
|
||||
int sqlite3BtreeGetAutoVacuum(Btree *);
|
||||
int sqlite3BtreeBeginTrans(Btree*,int);
|
||||
int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster);
|
||||
int sqlite3BtreeCommitPhaseTwo(Btree*);
|
||||
int sqlite3BtreeCommitPhaseTwo(Btree*, int);
|
||||
int sqlite3BtreeCommit(Btree*);
|
||||
int sqlite3BtreeRollback(Btree*);
|
||||
int sqlite3BtreeBeginStmt(Btree*,int);
|
||||
@@ -108,11 +96,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*);
|
||||
@@ -202,7 +196,7 @@ void sqlite3BtreeCursorList(Btree*);
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
int sqlite3BtreeCheckpoint(Btree*);
|
||||
int sqlite3BtreeCheckpoint(Btree*, int, int *, int *);
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -219,30 +213,28 @@ void sqlite3BtreeCursorList(Btree*);
|
||||
#endif
|
||||
|
||||
#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE
|
||||
int sqlite3BtreeSharable(Btree*);
|
||||
void sqlite3BtreeLeave(Btree*);
|
||||
void sqlite3BtreeEnterCursor(BtCursor*);
|
||||
void sqlite3BtreeLeaveCursor(BtCursor*);
|
||||
void sqlite3BtreeLeaveAll(sqlite3*);
|
||||
void sqlite3BtreeMutexArrayEnter(BtreeMutexArray*);
|
||||
void sqlite3BtreeMutexArrayLeave(BtreeMutexArray*);
|
||||
void sqlite3BtreeMutexArrayInsert(BtreeMutexArray*, Btree*);
|
||||
#ifndef NDEBUG
|
||||
/* These routines are used inside assert() statements only. */
|
||||
int sqlite3BtreeHoldsMutex(Btree*);
|
||||
int sqlite3BtreeHoldsAllMutexes(sqlite3*);
|
||||
int sqlite3SchemaMutexHeld(sqlite3*,int,Schema*);
|
||||
#endif
|
||||
#else
|
||||
|
||||
# define sqlite3BtreeSharable(X) 0
|
||||
# define sqlite3BtreeLeave(X)
|
||||
# define sqlite3BtreeEnterCursor(X)
|
||||
# define sqlite3BtreeLeaveCursor(X)
|
||||
# define sqlite3BtreeLeaveAll(X)
|
||||
# define sqlite3BtreeMutexArrayEnter(X)
|
||||
# define sqlite3BtreeMutexArrayLeave(X)
|
||||
# define sqlite3BtreeMutexArrayInsert(X,Y)
|
||||
|
||||
# define sqlite3BtreeHoldsMutex(X) 1
|
||||
# define sqlite3BtreeHoldsAllMutexes(X) 1
|
||||
# define sqlite3SchemaMutexHeld(X,Y,Z) 1
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
/* The following value is the maximum cell size assuming a maximum page
|
||||
** size give above.
|
||||
*/
|
||||
#define MX_CELL_SIZE(pBt) (pBt->pageSize-8)
|
||||
#define MX_CELL_SIZE(pBt) ((int)(pBt->pageSize-8))
|
||||
|
||||
/* The maximum number of cells on a single page of the database. This
|
||||
** assumes a minimum cell size of 6 bytes (4 bytes for the cell itself
|
||||
@@ -336,7 +336,7 @@ struct BtLock {
|
||||
** All fields in this structure are accessed under sqlite3.mutex.
|
||||
** The pBt pointer itself may not be changed while there exists cursors
|
||||
** in the referenced BtShared that point back to this Btree since those
|
||||
** cursors have to do go through this Btree to find their BtShared and
|
||||
** cursors have to go through this Btree to find their BtShared and
|
||||
** they often do so without holding sqlite3.mutex.
|
||||
*/
|
||||
struct Btree {
|
||||
@@ -409,23 +409,24 @@ 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) */
|
||||
u32 nPage; /* Number of pages in the database */
|
||||
void *pSchema; /* Pointer to space allocated by sqlite3BtreeSchema() */
|
||||
void (*xFreeSchema)(void*); /* Destructor for BtShared.pSchema */
|
||||
sqlite3_mutex *mutex; /* Non-recursive mutex required to access this struct */
|
||||
sqlite3_mutex *mutex; /* Non-recursive mutex required to access this object */
|
||||
Bitvec *pHasContent; /* Set of pages moved to free-list this transaction */
|
||||
#ifndef SQLITE_OMIT_SHARED_CACHE
|
||||
int nRef; /* Number of references to this structure */
|
||||
@@ -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 */
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -148,7 +148,7 @@ void sqlite3FinishCoding(Parse *pParse){
|
||||
** on each used database.
|
||||
*/
|
||||
if( pParse->cookieGoto>0 ){
|
||||
u32 mask;
|
||||
yDbMask mask;
|
||||
int iDb;
|
||||
sqlite3VdbeJumpHere(v, pParse->cookieGoto-1);
|
||||
for(iDb=0, mask=1; iDb<db->nDb; mask<<=1, iDb++){
|
||||
@@ -156,7 +156,10 @@ void sqlite3FinishCoding(Parse *pParse){
|
||||
sqlite3VdbeUsesBtree(v, iDb);
|
||||
sqlite3VdbeAddOp2(v,OP_Transaction, iDb, (mask & pParse->writeMask)!=0);
|
||||
if( db->init.busy==0 ){
|
||||
sqlite3VdbeAddOp2(v,OP_VerifyCookie, iDb, pParse->cookieValue[iDb]);
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
sqlite3VdbeAddOp3(v, OP_VerifyCookie,
|
||||
iDb, pParse->cookieValue[iDb],
|
||||
db->aDb[iDb].pSchema->iGeneration);
|
||||
}
|
||||
}
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
@@ -197,9 +200,7 @@ void sqlite3FinishCoding(Parse *pParse){
|
||||
/* A minimum of one cursor is required if autoincrement is used
|
||||
* See ticket [a696379c1f08866] */
|
||||
if( pParse->pAinc!=0 && pParse->nTab==0 ) pParse->nTab = 1;
|
||||
sqlite3VdbeMakeReady(v, pParse->nVar, pParse->nMem,
|
||||
pParse->nTab, pParse->nMaxArg, pParse->explain,
|
||||
pParse->isMultiWrite && pParse->mayAbort);
|
||||
sqlite3VdbeMakeReady(v, pParse);
|
||||
pParse->rc = SQLITE_DONE;
|
||||
pParse->colNamesSet = 0;
|
||||
}else{
|
||||
@@ -269,9 +270,12 @@ Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){
|
||||
int nName;
|
||||
assert( zName!=0 );
|
||||
nName = sqlite3Strlen30(zName);
|
||||
/* All mutexes are required for schema access. Make sure we hold them. */
|
||||
assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) );
|
||||
for(i=OMIT_TEMPDB; i<db->nDb; i++){
|
||||
int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
|
||||
if( zDatabase!=0 && sqlite3StrICmp(zDatabase, db->aDb[j].zName) ) continue;
|
||||
assert( sqlite3SchemaMutexHeld(db, j, 0) );
|
||||
p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName, nName);
|
||||
if( p ) break;
|
||||
}
|
||||
@@ -331,11 +335,14 @@ Index *sqlite3FindIndex(sqlite3 *db, const char *zName, const char *zDb){
|
||||
Index *p = 0;
|
||||
int i;
|
||||
int nName = sqlite3Strlen30(zName);
|
||||
/* All mutexes are required for schema access. Make sure we hold them. */
|
||||
assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) );
|
||||
for(i=OMIT_TEMPDB; i<db->nDb; i++){
|
||||
int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
|
||||
Schema *pSchema = db->aDb[j].pSchema;
|
||||
assert( pSchema );
|
||||
if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zName) ) continue;
|
||||
assert( sqlite3SchemaMutexHeld(db, j, 0) );
|
||||
p = sqlite3HashFind(&pSchema->idxHash, zName, nName);
|
||||
if( p ) break;
|
||||
}
|
||||
@@ -362,11 +369,13 @@ static void freeIndex(sqlite3 *db, Index *p){
|
||||
void sqlite3UnlinkAndDeleteIndex(sqlite3 *db, int iDb, const char *zIdxName){
|
||||
Index *pIndex;
|
||||
int len;
|
||||
Hash *pHash = &db->aDb[iDb].pSchema->idxHash;
|
||||
Hash *pHash;
|
||||
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
pHash = &db->aDb[iDb].pSchema->idxHash;
|
||||
len = sqlite3Strlen30(zIdxName);
|
||||
pIndex = sqlite3HashInsert(pHash, zIdxName, len, 0);
|
||||
if( pIndex ){
|
||||
if( ALWAYS(pIndex) ){
|
||||
if( pIndex->pTable->pIndex==pIndex ){
|
||||
pIndex->pTable->pIndex = pIndex->pNext;
|
||||
}else{
|
||||
@@ -391,26 +400,42 @@ void sqlite3UnlinkAndDeleteIndex(sqlite3 *db, int iDb, const char *zIdxName){
|
||||
** if there were schema changes during the transaction or if a
|
||||
** schema-cookie mismatch occurs.
|
||||
**
|
||||
** If iDb==0 then reset the internal schema tables for all database
|
||||
** files. If iDb>=1 then reset the internal schema for only the
|
||||
** If iDb<0 then reset the internal schema tables for all database
|
||||
** files. If iDb>=0 then reset the internal schema for only the
|
||||
** single file indicated.
|
||||
*/
|
||||
void sqlite3ResetInternalSchema(sqlite3 *db, int iDb){
|
||||
int i, j;
|
||||
assert( iDb>=0 && iDb<db->nDb );
|
||||
assert( iDb<db->nDb );
|
||||
|
||||
if( iDb==0 ){
|
||||
sqlite3BtreeEnterAll(db);
|
||||
if( iDb>=0 ){
|
||||
/* Case 1: Reset the single schema identified by iDb */
|
||||
Db *pDb = &db->aDb[iDb];
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
assert( pDb->pSchema!=0 );
|
||||
sqlite3SchemaClear(pDb->pSchema);
|
||||
|
||||
/* If any database other than TEMP is reset, then also reset TEMP
|
||||
** since TEMP might be holding triggers that reference tables in the
|
||||
** other database.
|
||||
*/
|
||||
if( iDb!=1 ){
|
||||
pDb = &db->aDb[1];
|
||||
assert( pDb->pSchema!=0 );
|
||||
sqlite3SchemaClear(pDb->pSchema);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for(i=iDb; i<db->nDb; i++){
|
||||
/* Case 2 (from here to the end): Reset all schemas for all attached
|
||||
** databases. */
|
||||
assert( iDb<0 );
|
||||
sqlite3BtreeEnterAll(db);
|
||||
for(i=0; i<db->nDb; i++){
|
||||
Db *pDb = &db->aDb[i];
|
||||
if( pDb->pSchema ){
|
||||
assert(i==1 || (pDb->pBt && sqlite3BtreeHoldsMutex(pDb->pBt)));
|
||||
sqlite3SchemaFree(pDb->pSchema);
|
||||
sqlite3SchemaClear(pDb->pSchema);
|
||||
}
|
||||
if( iDb>0 ) return;
|
||||
}
|
||||
assert( iDb==0 );
|
||||
db->flags &= ~SQLITE_InternChanges;
|
||||
sqlite3VtabUnlockList(db);
|
||||
sqlite3BtreeLeaveAll(db);
|
||||
@@ -496,6 +521,7 @@ void sqlite3DeleteTable(sqlite3 *db, Table *pTable){
|
||||
TESTONLY ( Index *pOld = ) sqlite3HashInsert(
|
||||
&pIndex->pSchema->idxHash, zName, sqlite3Strlen30(zName), 0
|
||||
);
|
||||
assert( db==0 || sqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) );
|
||||
assert( pOld==pIndex || pOld==0 );
|
||||
}
|
||||
freeIndex(db, pIndex);
|
||||
@@ -530,6 +556,7 @@ void sqlite3UnlinkAndDeleteTable(sqlite3 *db, int iDb, const char *zTabName){
|
||||
assert( db!=0 );
|
||||
assert( iDb>=0 && iDb<db->nDb );
|
||||
assert( zTabName );
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
testcase( zTabName[0]==0 ); /* Zero-length table names are allowed */
|
||||
pDb = &db->aDb[iDb];
|
||||
p = sqlite3HashInsert(&pDb->pSchema->tblHash, zTabName,
|
||||
@@ -726,8 +753,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 +803,21 @@ 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);
|
||||
}else{
|
||||
assert( !db->init.busy );
|
||||
sqlite3CodeVerifySchema(pParse, iDb);
|
||||
}
|
||||
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 +834,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;
|
||||
|
||||
@@ -811,6 +844,7 @@ void sqlite3StartTable(
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_AUTOINCREMENT
|
||||
if( !pParse->nested && strcmp(zName, "sqlite_sequence")==0 ){
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
pTable->pSchema->pSeqTab = pTable;
|
||||
}
|
||||
#endif
|
||||
@@ -1271,6 +1305,7 @@ void sqlite3ChangeCookie(Parse *pParse, int iDb){
|
||||
int r1 = sqlite3GetTempReg(pParse);
|
||||
sqlite3 *db = pParse->db;
|
||||
Vdbe *v = pParse->pVdbe;
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, db->aDb[iDb].pSchema->schema_cookie+1, r1);
|
||||
sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_SCHEMA_VERSION, r1);
|
||||
sqlite3ReleaseTempReg(pParse, r1);
|
||||
@@ -1378,7 +1413,7 @@ static char *createTableStmt(sqlite3 *db, Table *p){
|
||||
zSep = zSep2;
|
||||
identPut(zStmt, &k, pCol->zName);
|
||||
assert( pCol->affinity-SQLITE_AFF_TEXT >= 0 );
|
||||
assert( pCol->affinity-SQLITE_AFF_TEXT < sizeof(azType)/sizeof(azType[0]) );
|
||||
assert( pCol->affinity-SQLITE_AFF_TEXT < ArraySize(azType) );
|
||||
testcase( pCol->affinity==SQLITE_AFF_TEXT );
|
||||
testcase( pCol->affinity==SQLITE_AFF_NONE );
|
||||
testcase( pCol->affinity==SQLITE_AFF_NUMERIC );
|
||||
@@ -1573,6 +1608,7 @@ void sqlite3EndTable(
|
||||
*/
|
||||
if( p->tabFlags & TF_Autoincrement ){
|
||||
Db *pDb = &db->aDb[iDb];
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
if( pDb->pSchema->pSeqTab==0 ){
|
||||
sqlite3NestedParse(pParse,
|
||||
"CREATE TABLE %Q.sqlite_sequence(name,seq)",
|
||||
@@ -1583,8 +1619,8 @@ void sqlite3EndTable(
|
||||
#endif
|
||||
|
||||
/* Reparse everything to update our internal data structures */
|
||||
sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 0, 0,
|
||||
sqlite3MPrintf(db, "tbl_name='%q'",p->zName), P4_DYNAMIC);
|
||||
sqlite3VdbeAddParseSchemaOp(v, iDb,
|
||||
sqlite3MPrintf(db, "tbl_name='%q'", p->zName));
|
||||
}
|
||||
|
||||
|
||||
@@ -1593,6 +1629,7 @@ void sqlite3EndTable(
|
||||
if( db->init.busy ){
|
||||
Table *pOld;
|
||||
Schema *pSchema = p->pSchema;
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
pOld = sqlite3HashInsert(&pSchema->tblHash, p->zName,
|
||||
sqlite3Strlen30(p->zName),p);
|
||||
if( pOld ){
|
||||
@@ -1637,7 +1674,7 @@ void sqlite3CreateView(
|
||||
const char *z;
|
||||
Token sEnd;
|
||||
DbFixer sFix;
|
||||
Token *pName;
|
||||
Token *pName = 0;
|
||||
int iDb;
|
||||
sqlite3 *db = pParse->db;
|
||||
|
||||
@@ -1648,12 +1685,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)
|
||||
@@ -1779,6 +1814,7 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
|
||||
pSelTab->nCol = 0;
|
||||
pSelTab->aCol = 0;
|
||||
sqlite3DeleteTable(db, pSelTab);
|
||||
assert( sqlite3SchemaMutexHeld(db, 0, pTable->pSchema) );
|
||||
pTable->pSchema->flags |= DB_UnresetViews;
|
||||
}else{
|
||||
pTable->nCol = 0;
|
||||
@@ -1799,6 +1835,7 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
|
||||
*/
|
||||
static void sqliteViewResetAll(sqlite3 *db, int idx){
|
||||
HashElem *i;
|
||||
assert( sqlite3SchemaMutexHeld(db, idx, 0) );
|
||||
if( !DbHasProperty(db, idx, DB_UnresetViews) ) return;
|
||||
for(i=sqliteHashFirst(&db->aDb[idx].pSchema->tblHash); i;i=sqliteHashNext(i)){
|
||||
Table *pTab = sqliteHashData(i);
|
||||
@@ -1832,10 +1869,13 @@ static void sqliteViewResetAll(sqlite3 *db, int idx){
|
||||
** in order to be certain that we got the right one.
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
void sqlite3RootPageMoved(Db *pDb, int iFrom, int iTo){
|
||||
void sqlite3RootPageMoved(sqlite3 *db, int iDb, int iFrom, int iTo){
|
||||
HashElem *pElem;
|
||||
Hash *pHash;
|
||||
Db *pDb;
|
||||
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
pDb = &db->aDb[iDb];
|
||||
pHash = &pDb->pSchema->tblHash;
|
||||
for(pElem=sqliteHashFirst(pHash); pElem; pElem=sqliteHashNext(pElem)){
|
||||
Table *pTab = sqliteHashData(pElem);
|
||||
@@ -1940,6 +1980,100 @@ static void destroyTable(Parse *pParse, Table *pTab){
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove entries from the sqlite_statN tables (for N in (1,2,3))
|
||||
** after a DROP INDEX or DROP TABLE command.
|
||||
*/
|
||||
static void sqlite3ClearStatTables(
|
||||
Parse *pParse, /* The parsing context */
|
||||
int iDb, /* The database number */
|
||||
const char *zType, /* "idx" or "tbl" */
|
||||
const char *zName /* Name of index or table */
|
||||
){
|
||||
int i;
|
||||
const char *zDbName = pParse->db->aDb[iDb].zName;
|
||||
for(i=1; i<=3; i++){
|
||||
char zTab[24];
|
||||
sqlite3_snprintf(sizeof(zTab),zTab,"sqlite_stat%d",i);
|
||||
if( sqlite3FindTable(pParse->db, zTab, zDbName) ){
|
||||
sqlite3NestedParse(pParse,
|
||||
"DELETE FROM %Q.%s WHERE %s=%Q",
|
||||
zDbName, zTab, zType, zName
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate code to drop a table.
|
||||
*/
|
||||
void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){
|
||||
Vdbe *v;
|
||||
sqlite3 *db = pParse->db;
|
||||
Trigger *pTrigger;
|
||||
Db *pDb = &db->aDb[iDb];
|
||||
|
||||
v = sqlite3GetVdbe(pParse);
|
||||
assert( v!=0 );
|
||||
sqlite3BeginWriteOperation(pParse, 1, iDb);
|
||||
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
if( IsVirtual(pTab) ){
|
||||
sqlite3VdbeAddOp0(v, OP_VBegin);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Drop all triggers associated with the table being dropped. Code
|
||||
** is generated to remove entries from sqlite_master and/or
|
||||
** sqlite_temp_master if required.
|
||||
*/
|
||||
pTrigger = sqlite3TriggerList(pParse, pTab);
|
||||
while( pTrigger ){
|
||||
assert( pTrigger->pSchema==pTab->pSchema ||
|
||||
pTrigger->pSchema==db->aDb[1].pSchema );
|
||||
sqlite3DropTriggerPtr(pParse, pTrigger);
|
||||
pTrigger = pTrigger->pNext;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_AUTOINCREMENT
|
||||
/* Remove any entries of the sqlite_sequence table associated with
|
||||
** the table being dropped. This is done before the table is dropped
|
||||
** at the btree level, in case the sqlite_sequence table needs to
|
||||
** move as a result of the drop (can happen in auto-vacuum mode).
|
||||
*/
|
||||
if( pTab->tabFlags & TF_Autoincrement ){
|
||||
sqlite3NestedParse(pParse,
|
||||
"DELETE FROM %Q.sqlite_sequence WHERE name=%Q",
|
||||
pDb->zName, pTab->zName
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Drop all SQLITE_MASTER table and index entries that refer to the
|
||||
** table. The program name loops through the master table and deletes
|
||||
** every row that refers to a table of the same name as the one being
|
||||
** dropped. Triggers are handled seperately because a trigger can be
|
||||
** created in the temp database that refers to a table in another
|
||||
** database.
|
||||
*/
|
||||
sqlite3NestedParse(pParse,
|
||||
"DELETE FROM %Q.%s WHERE tbl_name=%Q and type!='trigger'",
|
||||
pDb->zName, SCHEMA_TABLE(iDb), pTab->zName);
|
||||
if( !isView && !IsVirtual(pTab) ){
|
||||
destroyTable(pParse, pTab);
|
||||
}
|
||||
|
||||
/* Remove the table entry from SQLite's internal schema and modify
|
||||
** the schema cookie.
|
||||
*/
|
||||
if( IsVirtual(pTab) ){
|
||||
sqlite3VdbeAddOp4(v, OP_VDestroy, iDb, 0, 0, pTab->zName, 0);
|
||||
}
|
||||
sqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0);
|
||||
sqlite3ChangeCookie(pParse, iDb);
|
||||
sqliteViewResetAll(db, iDb);
|
||||
}
|
||||
|
||||
/*
|
||||
** This routine is called to do the work of a DROP TABLE statement.
|
||||
** pName is the name of the table to be dropped.
|
||||
@@ -1961,6 +2095,7 @@ void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){
|
||||
if( noErr ) db->suppressErr--;
|
||||
|
||||
if( pTab==0 ){
|
||||
if( noErr ) sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase);
|
||||
goto exit_drop_table;
|
||||
}
|
||||
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
|
||||
@@ -2007,7 +2142,8 @@ void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){
|
||||
if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0
|
||||
&& sqlite3StrNICmp(pTab->zName, "sqlite_stat", 11)!=0 ){
|
||||
sqlite3ErrorMsg(pParse, "table %s may not be dropped", pTab->zName);
|
||||
goto exit_drop_table;
|
||||
}
|
||||
@@ -2031,75 +2167,11 @@ void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){
|
||||
*/
|
||||
v = sqlite3GetVdbe(pParse);
|
||||
if( v ){
|
||||
Trigger *pTrigger;
|
||||
Db *pDb = &db->aDb[iDb];
|
||||
sqlite3BeginWriteOperation(pParse, 1, iDb);
|
||||
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
if( IsVirtual(pTab) ){
|
||||
sqlite3VdbeAddOp0(v, OP_VBegin);
|
||||
}
|
||||
#endif
|
||||
sqlite3ClearStatTables(pParse, iDb, "tbl", pTab->zName);
|
||||
sqlite3FkDropTable(pParse, pName, pTab);
|
||||
|
||||
/* Drop all triggers associated with the table being dropped. Code
|
||||
** is generated to remove entries from sqlite_master and/or
|
||||
** sqlite_temp_master if required.
|
||||
*/
|
||||
pTrigger = sqlite3TriggerList(pParse, pTab);
|
||||
while( pTrigger ){
|
||||
assert( pTrigger->pSchema==pTab->pSchema ||
|
||||
pTrigger->pSchema==db->aDb[1].pSchema );
|
||||
sqlite3DropTriggerPtr(pParse, pTrigger);
|
||||
pTrigger = pTrigger->pNext;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_AUTOINCREMENT
|
||||
/* Remove any entries of the sqlite_sequence table associated with
|
||||
** the table being dropped. This is done before the table is dropped
|
||||
** at the btree level, in case the sqlite_sequence table needs to
|
||||
** move as a result of the drop (can happen in auto-vacuum mode).
|
||||
*/
|
||||
if( pTab->tabFlags & TF_Autoincrement ){
|
||||
sqlite3NestedParse(pParse,
|
||||
"DELETE FROM %s.sqlite_sequence WHERE name=%Q",
|
||||
pDb->zName, pTab->zName
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Drop all SQLITE_MASTER table and index entries that refer to the
|
||||
** table. The program name loops through the master table and deletes
|
||||
** every row that refers to a table of the same name as the one being
|
||||
** dropped. Triggers are handled seperately because a trigger can be
|
||||
** created in the temp database that refers to a table in another
|
||||
** database.
|
||||
*/
|
||||
sqlite3NestedParse(pParse,
|
||||
"DELETE FROM %Q.%s WHERE tbl_name=%Q and type!='trigger'",
|
||||
pDb->zName, SCHEMA_TABLE(iDb), pTab->zName);
|
||||
|
||||
/* Drop any statistics from the sqlite_stat1 table, if it exists */
|
||||
if( sqlite3FindTable(db, "sqlite_stat1", db->aDb[iDb].zName) ){
|
||||
sqlite3NestedParse(pParse,
|
||||
"DELETE FROM %Q.sqlite_stat1 WHERE tbl=%Q", pDb->zName, pTab->zName
|
||||
);
|
||||
}
|
||||
|
||||
if( !isView && !IsVirtual(pTab) ){
|
||||
destroyTable(pParse, pTab);
|
||||
}
|
||||
|
||||
/* Remove the table entry from SQLite's internal schema and modify
|
||||
** the schema cookie.
|
||||
*/
|
||||
if( IsVirtual(pTab) ){
|
||||
sqlite3VdbeAddOp4(v, OP_VDestroy, iDb, 0, 0, pTab->zName, 0);
|
||||
}
|
||||
sqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0);
|
||||
sqlite3ChangeCookie(pParse, iDb);
|
||||
sqlite3CodeDropTable(pParse, pTab, iDb, isView);
|
||||
}
|
||||
sqliteViewResetAll(db, iDb);
|
||||
|
||||
exit_drop_table:
|
||||
sqlite3SrcListDelete(db, pName);
|
||||
@@ -2209,6 +2281,7 @@ void sqlite3CreateForeignKey(
|
||||
pFKey->aAction[0] = (u8)(flags & 0xff); /* ON DELETE action */
|
||||
pFKey->aAction[1] = (u8)((flags >> 8 ) & 0xff); /* ON UPDATE action */
|
||||
|
||||
assert( sqlite3SchemaMutexHeld(db, 0, p->pSchema) );
|
||||
pNextTo = (FKey *)sqlite3HashInsert(&p->pSchema->fkeyHash,
|
||||
pFKey->zTo, sqlite3Strlen30(pFKey->zTo), (void *)pFKey
|
||||
);
|
||||
@@ -2266,11 +2339,15 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){
|
||||
Table *pTab = pIndex->pTable; /* The table that is indexed */
|
||||
int iTab = pParse->nTab++; /* Btree cursor used for pTab */
|
||||
int iIdx = pParse->nTab++; /* Btree cursor used for pIndex */
|
||||
int iSorter; /* Cursor opened by OpenSorter (if in use) */
|
||||
int addr1; /* Address of top of loop */
|
||||
int addr2; /* Address to jump to for next iteration */
|
||||
int tnum; /* Root page of index */
|
||||
Vdbe *v; /* Generate code into this virtual machine */
|
||||
KeyInfo *pKey; /* KeyInfo for index */
|
||||
#ifdef SQLITE_OMIT_MERGE_SORT
|
||||
int regIdxKey; /* Registers containing the index key */
|
||||
#endif
|
||||
int regRecord; /* Register holding assemblied index record */
|
||||
sqlite3 *db = pParse->db; /* The database connection */
|
||||
int iDb = sqlite3SchemaToIndex(db, pIndex->pSchema);
|
||||
@@ -2299,10 +2376,44 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){
|
||||
if( memRootPage>=0 ){
|
||||
sqlite3VdbeChangeP5(v, 1);
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_MERGE_SORT
|
||||
/* Open the sorter cursor if we are to use one. */
|
||||
iSorter = pParse->nTab++;
|
||||
sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, 0, (char*)pKey, P4_KEYINFO);
|
||||
#else
|
||||
iSorter = iTab;
|
||||
#endif
|
||||
|
||||
/* Open the table. Loop through all rows of the table, inserting index
|
||||
** records into the sorter. */
|
||||
sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead);
|
||||
addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0);
|
||||
regRecord = sqlite3GetTempReg(pParse);
|
||||
|
||||
#ifndef SQLITE_OMIT_MERGE_SORT
|
||||
sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1);
|
||||
sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord);
|
||||
sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1);
|
||||
sqlite3VdbeJumpHere(v, addr1);
|
||||
addr1 = sqlite3VdbeAddOp2(v, OP_SorterSort, iSorter, 0);
|
||||
if( pIndex->onError!=OE_None ){
|
||||
int j2 = sqlite3VdbeCurrentAddr(v) + 3;
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, j2);
|
||||
addr2 = sqlite3VdbeCurrentAddr(v);
|
||||
sqlite3VdbeAddOp3(v, OP_SorterCompare, iSorter, j2, regRecord);
|
||||
sqlite3HaltConstraint(
|
||||
pParse, OE_Abort, "indexed columns are not unique", P4_STATIC
|
||||
);
|
||||
}else{
|
||||
addr2 = sqlite3VdbeCurrentAddr(v);
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_SorterData, iSorter, regRecord);
|
||||
sqlite3VdbeAddOp3(v, OP_IdxInsert, iIdx, regRecord, 1);
|
||||
sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
|
||||
#else
|
||||
regIdxKey = sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1);
|
||||
addr2 = addr1 + 1;
|
||||
if( pIndex->onError!=OE_None ){
|
||||
const int regRowid = regIdxKey + pIndex->nColumn;
|
||||
const int j2 = sqlite3VdbeCurrentAddr(v) + 2;
|
||||
@@ -2321,13 +2432,16 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){
|
||||
sqlite3HaltConstraint(
|
||||
pParse, OE_Abort, "indexed columns are not unique", P4_STATIC);
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_IdxInsert, iIdx, regRecord);
|
||||
sqlite3VdbeAddOp3(v, OP_IdxInsert, iIdx, regRecord, 0);
|
||||
sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
|
||||
#endif
|
||||
sqlite3ReleaseTempReg(pParse, regRecord);
|
||||
sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1);
|
||||
sqlite3VdbeAddOp2(v, OP_SorterNext, iSorter, addr2);
|
||||
sqlite3VdbeJumpHere(v, addr1);
|
||||
|
||||
sqlite3VdbeAddOp1(v, OP_Close, iTab);
|
||||
sqlite3VdbeAddOp1(v, OP_Close, iIdx);
|
||||
sqlite3VdbeAddOp1(v, OP_Close, iSorter);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2397,6 +2511,7 @@ Index *sqlite3CreateIndex(
|
||||
assert( pName1 && pName2 );
|
||||
iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName);
|
||||
if( iDb<0 ) goto exit_create_index;
|
||||
assert( pName && pName->z );
|
||||
|
||||
#ifndef SQLITE_OMIT_TEMPDB
|
||||
/* If the index name was unqualified, check if the the table
|
||||
@@ -2424,6 +2539,7 @@ Index *sqlite3CreateIndex(
|
||||
assert( db->aDb[iDb].pSchema==pTab->pSchema );
|
||||
}else{
|
||||
assert( pName==0 );
|
||||
assert( pStart==0 );
|
||||
pTab = pParse->pNewTable;
|
||||
if( !pTab ) goto exit_create_index;
|
||||
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
|
||||
@@ -2466,6 +2582,7 @@ Index *sqlite3CreateIndex(
|
||||
if( pName ){
|
||||
zName = sqlite3NameFromToken(db, pName);
|
||||
if( zName==0 ) goto exit_create_index;
|
||||
assert( pName->z!=0 );
|
||||
if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){
|
||||
goto exit_create_index;
|
||||
}
|
||||
@@ -2478,6 +2595,9 @@ Index *sqlite3CreateIndex(
|
||||
if( sqlite3FindIndex(db, zName, pDb->zName)!=0 ){
|
||||
if( !ifNotExist ){
|
||||
sqlite3ErrorMsg(pParse, "index %s already exists", zName);
|
||||
}else{
|
||||
assert( !db->init.busy );
|
||||
sqlite3CodeVerifySchema(pParse, iDb);
|
||||
}
|
||||
goto exit_create_index;
|
||||
}
|
||||
@@ -2542,8 +2662,8 @@ Index *sqlite3CreateIndex(
|
||||
nCol = pList->nExpr;
|
||||
pIndex = sqlite3DbMallocZero(db,
|
||||
sizeof(Index) + /* Index structure */
|
||||
sizeof(tRowcnt)*(nCol+1) + /* Index.aiRowEst */
|
||||
sizeof(int)*nCol + /* Index.aiColumn */
|
||||
sizeof(int)*(nCol+1) + /* Index.aiRowEst */
|
||||
sizeof(char *)*nCol + /* Index.azColl */
|
||||
sizeof(u8)*nCol + /* Index.aSortOrder */
|
||||
nName + 1 + /* Index.zName */
|
||||
@@ -2552,10 +2672,10 @@ Index *sqlite3CreateIndex(
|
||||
if( db->mallocFailed ){
|
||||
goto exit_create_index;
|
||||
}
|
||||
pIndex->azColl = (char**)(&pIndex[1]);
|
||||
pIndex->aiRowEst = (tRowcnt*)(&pIndex[1]);
|
||||
pIndex->azColl = (char**)(&pIndex->aiRowEst[nCol+1]);
|
||||
pIndex->aiColumn = (int *)(&pIndex->azColl[nCol]);
|
||||
pIndex->aiRowEst = (unsigned *)(&pIndex->aiColumn[nCol]);
|
||||
pIndex->aSortOrder = (u8 *)(&pIndex->aiRowEst[nCol+1]);
|
||||
pIndex->aSortOrder = (u8 *)(&pIndex->aiColumn[nCol]);
|
||||
pIndex->zName = (char *)(&pIndex->aSortOrder[nCol]);
|
||||
zExtra = (char *)(&pIndex->zName[nName+1]);
|
||||
memcpy(pIndex->zName, zName, nName+1);
|
||||
@@ -2564,6 +2684,7 @@ Index *sqlite3CreateIndex(
|
||||
pIndex->onError = (u8)onError;
|
||||
pIndex->autoIndex = (u8)(pName==0);
|
||||
pIndex->pSchema = db->aDb[iDb].pSchema;
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
|
||||
/* Check to see if we should honor DESC requests on index columns
|
||||
*/
|
||||
@@ -2693,6 +2814,7 @@ Index *sqlite3CreateIndex(
|
||||
*/
|
||||
if( db->init.busy ){
|
||||
Index *p;
|
||||
assert( sqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) );
|
||||
p = sqlite3HashInsert(&pIndex->pSchema->idxHash,
|
||||
pIndex->zName, sqlite3Strlen30(pIndex->zName),
|
||||
pIndex);
|
||||
@@ -2744,7 +2866,7 @@ Index *sqlite3CreateIndex(
|
||||
/* A named index with an explicit CREATE INDEX statement */
|
||||
zStmt = sqlite3MPrintf(db, "CREATE%s INDEX %.*s",
|
||||
onError==OE_None ? "" : " UNIQUE",
|
||||
pEnd->z - pName->z + 1,
|
||||
(int)(pEnd->z - pName->z) + 1,
|
||||
pName->z);
|
||||
}else{
|
||||
/* An automatic index created by a PRIMARY KEY or UNIQUE constraint */
|
||||
@@ -2770,8 +2892,8 @@ Index *sqlite3CreateIndex(
|
||||
if( pTblName ){
|
||||
sqlite3RefillIndex(pParse, pIndex, iMem);
|
||||
sqlite3ChangeCookie(pParse, iDb);
|
||||
sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 0, 0,
|
||||
sqlite3MPrintf(db, "name='%q'", pIndex->zName), P4_DYNAMIC);
|
||||
sqlite3VdbeAddParseSchemaOp(v, iDb,
|
||||
sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName));
|
||||
sqlite3VdbeAddOp1(v, OP_Expire, 0);
|
||||
}
|
||||
}
|
||||
@@ -2830,16 +2952,16 @@ exit_create_index:
|
||||
** are based on typical values found in actual indices.
|
||||
*/
|
||||
void sqlite3DefaultRowEst(Index *pIdx){
|
||||
unsigned *a = pIdx->aiRowEst;
|
||||
tRowcnt *a = pIdx->aiRowEst;
|
||||
int i;
|
||||
tRowcnt 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;
|
||||
@@ -2868,6 +2990,8 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){
|
||||
if( pIndex==0 ){
|
||||
if( !ifExists ){
|
||||
sqlite3ErrorMsg(pParse, "no such index: %S", pName, 0);
|
||||
}else{
|
||||
sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase);
|
||||
}
|
||||
pParse->checkSchema = 1;
|
||||
goto exit_drop_index;
|
||||
@@ -2899,16 +3023,10 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){
|
||||
if( v ){
|
||||
sqlite3BeginWriteOperation(pParse, 1, iDb);
|
||||
sqlite3NestedParse(pParse,
|
||||
"DELETE FROM %Q.%s WHERE name=%Q",
|
||||
db->aDb[iDb].zName, SCHEMA_TABLE(iDb),
|
||||
pIndex->zName
|
||||
"DELETE FROM %Q.%s WHERE name=%Q AND type='index'",
|
||||
db->aDb[iDb].zName, SCHEMA_TABLE(iDb), pIndex->zName
|
||||
);
|
||||
if( sqlite3FindTable(db, "sqlite_stat1", db->aDb[iDb].zName) ){
|
||||
sqlite3NestedParse(pParse,
|
||||
"DELETE FROM %Q.sqlite_stat1 WHERE idx=%Q",
|
||||
db->aDb[iDb].zName, pIndex->zName
|
||||
);
|
||||
}
|
||||
sqlite3ClearStatTables(pParse, iDb, "idx", pIndex->zName);
|
||||
sqlite3ChangeCookie(pParse, iDb);
|
||||
destroyRootPage(pParse, pIndex->tnum, iDb);
|
||||
sqlite3VdbeAddOp4(v, OP_DropIndex, iDb, 0, 0, pIndex->zName, 0);
|
||||
@@ -3280,8 +3398,9 @@ void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){
|
||||
** operator with A. This routine shifts that operator over to B.
|
||||
*/
|
||||
void sqlite3SrcListShiftJoinType(SrcList *p){
|
||||
if( p && p->a ){
|
||||
if( p ){
|
||||
int i;
|
||||
assert( p->a || p->nSrc==0 );
|
||||
for(i=p->nSrc-1; i>0; i--){
|
||||
p->a[i].jointype = p->a[i-1].jointype;
|
||||
}
|
||||
@@ -3319,13 +3438,10 @@ void sqlite3BeginTransaction(Parse *pParse, int type){
|
||||
** Commit a transaction
|
||||
*/
|
||||
void sqlite3CommitTransaction(Parse *pParse){
|
||||
sqlite3 *db;
|
||||
Vdbe *v;
|
||||
|
||||
assert( pParse!=0 );
|
||||
db = pParse->db;
|
||||
assert( db!=0 );
|
||||
/* if( db->aDb[0].pBt==0 ) return; */
|
||||
assert( pParse->db!=0 );
|
||||
if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "COMMIT", 0, 0) ){
|
||||
return;
|
||||
}
|
||||
@@ -3339,13 +3455,10 @@ void sqlite3CommitTransaction(Parse *pParse){
|
||||
** Rollback a transaction
|
||||
*/
|
||||
void sqlite3RollbackTransaction(Parse *pParse){
|
||||
sqlite3 *db;
|
||||
Vdbe *v;
|
||||
|
||||
assert( pParse!=0 );
|
||||
db = pParse->db;
|
||||
assert( db!=0 );
|
||||
/* if( db->aDb[0].pBt==0 ) return; */
|
||||
assert( pParse->db!=0 );
|
||||
if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "ROLLBACK", 0, 0) ){
|
||||
return;
|
||||
}
|
||||
@@ -3391,7 +3504,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(db->pVfs, 0, db, &pBt, 0, flags);
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3ErrorMsg(pParse, "unable to open a temporary database "
|
||||
"file for storing temporary tables");
|
||||
@@ -3440,12 +3553,13 @@ void sqlite3CodeVerifySchema(Parse *pParse, int iDb){
|
||||
}
|
||||
if( iDb>=0 ){
|
||||
sqlite3 *db = pToplevel->db;
|
||||
int mask;
|
||||
yDbMask mask;
|
||||
|
||||
assert( iDb<db->nDb );
|
||||
assert( db->aDb[iDb].pBt!=0 || iDb==1 );
|
||||
assert( iDb<SQLITE_MAX_ATTACHED+2 );
|
||||
mask = 1<<iDb;
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
mask = ((yDbMask)1)<<iDb;
|
||||
if( (pToplevel->cookieMask & mask)==0 ){
|
||||
pToplevel->cookieMask |= mask;
|
||||
pToplevel->cookieValue[iDb] = db->aDb[iDb].pSchema->schema_cookie;
|
||||
@@ -3456,6 +3570,21 @@ void sqlite3CodeVerifySchema(Parse *pParse, int iDb){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** If argument zDb is NULL, then call sqlite3CodeVerifySchema() for each
|
||||
** attached database. Otherwise, invoke it for the database named zDb only.
|
||||
*/
|
||||
void sqlite3CodeVerifyNamedSchema(Parse *pParse, const char *zDb){
|
||||
sqlite3 *db = pParse->db;
|
||||
int i;
|
||||
for(i=0; i<db->nDb; i++){
|
||||
Db *pDb = &db->aDb[i];
|
||||
if( pDb->pBt && (!zDb || 0==sqlite3StrICmp(zDb, pDb->zName)) ){
|
||||
sqlite3CodeVerifySchema(pParse, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate VDBE code that prepares for doing an operation that
|
||||
** might change the database.
|
||||
@@ -3472,7 +3601,7 @@ void sqlite3CodeVerifySchema(Parse *pParse, int iDb){
|
||||
void sqlite3BeginWriteOperation(Parse *pParse, int setStatement, int iDb){
|
||||
Parse *pToplevel = sqlite3ParseToplevel(pParse);
|
||||
sqlite3CodeVerifySchema(pParse, iDb);
|
||||
pToplevel->writeMask |= 1<<iDb;
|
||||
pToplevel->writeMask |= ((yDbMask)1)<<iDb;
|
||||
pToplevel->isMultiWrite |= setStatement;
|
||||
}
|
||||
|
||||
@@ -3572,6 +3701,7 @@ static void reindexDatabases(Parse *pParse, char const *zColl){
|
||||
HashElem *k; /* For looping over tables in pDb */
|
||||
Table *pTab; /* A table in the database */
|
||||
|
||||
assert( sqlite3BtreeHoldsAllMutexes(db) ); /* Needed for schema access */
|
||||
for(iDb=0, pDb=db->aDb; iDb<db->nDb; iDb++, pDb++){
|
||||
assert( pDb!=0 );
|
||||
for(k=sqliteHashFirst(&pDb->pSchema->tblHash); k; k=sqliteHashNext(k)){
|
||||
|
||||
@@ -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.
|
||||
@@ -400,12 +400,12 @@ FuncDef *sqlite3FindFunction(
|
||||
/*
|
||||
** Free all resources held by the schema structure. The void* argument points
|
||||
** at a Schema struct. This function does not call sqlite3DbFree(db, ) on the
|
||||
** pointer itself, it just cleans up subsiduary resources (i.e. the contents
|
||||
** pointer itself, it just cleans up subsidiary resources (i.e. the contents
|
||||
** of the schema hash tables).
|
||||
**
|
||||
** The Schema.cache_size variable is not cleared.
|
||||
*/
|
||||
void sqlite3SchemaFree(void *p){
|
||||
void sqlite3SchemaClear(void *p){
|
||||
Hash temp1;
|
||||
Hash temp2;
|
||||
HashElem *pElem;
|
||||
@@ -427,7 +427,10 @@ void sqlite3SchemaFree(void *p){
|
||||
sqlite3HashClear(&temp1);
|
||||
sqlite3HashClear(&pSchema->fkeyHash);
|
||||
pSchema->pSeqTab = 0;
|
||||
pSchema->flags &= ~DB_SchemaLoaded;
|
||||
if( pSchema->flags & DB_SchemaLoaded ){
|
||||
pSchema->iGeneration++;
|
||||
pSchema->flags &= ~DB_SchemaLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -437,7 +440,7 @@ void sqlite3SchemaFree(void *p){
|
||||
Schema *sqlite3SchemaGet(sqlite3 *db, Btree *pBt){
|
||||
Schema * p;
|
||||
if( pBt ){
|
||||
p = (Schema *)sqlite3BtreeSchema(pBt, sizeof(Schema), sqlite3SchemaFree);
|
||||
p = (Schema *)sqlite3BtreeSchema(pBt, sizeof(Schema), sqlite3SchemaClear);
|
||||
}else{
|
||||
p = (Schema *)sqlite3DbMallocZero(0, sizeof(Schema));
|
||||
}
|
||||
|
||||
@@ -114,8 +114,8 @@ static const char * const azCompileOpt[] = {
|
||||
#ifdef SQLITE_ENABLE_RTREE
|
||||
"ENABLE_RTREE",
|
||||
#endif
|
||||
#ifdef SQLITE_ENABLE_STAT2
|
||||
"ENABLE_STAT2",
|
||||
#ifdef SQLITE_ENABLE_STAT3
|
||||
"ENABLE_STAT3",
|
||||
#endif
|
||||
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
|
||||
"ENABLE_UNLOCK_NOTIFY",
|
||||
@@ -144,6 +144,9 @@ static const char * const azCompileOpt[] = {
|
||||
#ifdef SQLITE_LOCK_TRACE
|
||||
"LOCK_TRACE",
|
||||
#endif
|
||||
#ifdef SQLITE_MAX_SCHEMA_RETRY
|
||||
"MAX_SCHEMA_RETRY=" CTIMEOPT_VAL(SQLITE_MAX_SCHEMA_RETRY),
|
||||
#endif
|
||||
#ifdef SQLITE_MEMDEBUG
|
||||
"MEMDEBUG",
|
||||
#endif
|
||||
@@ -174,6 +177,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
|
||||
@@ -254,6 +260,9 @@ static const char * const azCompileOpt[] = {
|
||||
#ifdef SQLITE_OMIT_MEMORYDB
|
||||
"OMIT_MEMORYDB",
|
||||
#endif
|
||||
#ifdef SQLITE_OMIT_MERGE_SORT
|
||||
"OMIT_MERGE_SORT",
|
||||
#endif
|
||||
#ifdef SQLITE_OMIT_OR_OPTIMIZATION
|
||||
"OMIT_OR_OPTIMIZATION",
|
||||
#endif
|
||||
|
||||
@@ -50,22 +50,6 @@
|
||||
|
||||
#ifndef SQLITE_OMIT_DATETIME_FUNCS
|
||||
|
||||
/*
|
||||
** On recent Windows platforms, the localtime_s() function is available
|
||||
** as part of the "Secure CRT". It is essentially equivalent to
|
||||
** localtime_r() available under most POSIX platforms, except that the
|
||||
** order of the parameters is reversed.
|
||||
**
|
||||
** See http://msdn.microsoft.com/en-us/library/a442x3ye(VS.80).aspx.
|
||||
**
|
||||
** If the user has not indicated to use localtime_r() or localtime_s()
|
||||
** already, check for an MSVC build environment that provides
|
||||
** localtime_s().
|
||||
*/
|
||||
#if !defined(HAVE_LOCALTIME_R) && !defined(HAVE_LOCALTIME_S) && \
|
||||
defined(_MSC_VER) && defined(_CRT_INSECURE_DEPRECATE)
|
||||
#define HAVE_LOCALTIME_S 1
|
||||
#endif
|
||||
|
||||
/*
|
||||
** A structure for holding a single date and time.
|
||||
@@ -133,12 +117,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:
|
||||
@@ -311,12 +289,18 @@ static int parseYyyyMmDd(const char *zDate, DateTime *p){
|
||||
}
|
||||
|
||||
/*
|
||||
** Set the time to the current time reported by the VFS
|
||||
** Set the time to the current time reported by the VFS.
|
||||
**
|
||||
** Return the number of errors.
|
||||
*/
|
||||
static void setDateTimeToCurrent(sqlite3_context *context, DateTime *p){
|
||||
static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){
|
||||
sqlite3 *db = sqlite3_context_db_handle(context);
|
||||
sqlite3OsCurrentTimeInt64(db->pVfs, &p->iJD);
|
||||
p->validJD = 1;
|
||||
if( sqlite3OsCurrentTimeInt64(db->pVfs, &p->iJD)==SQLITE_OK ){
|
||||
p->validJD = 1;
|
||||
return 0;
|
||||
}else{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -340,17 +324,14 @@ 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 ){
|
||||
return 0;
|
||||
}else if( sqlite3StrICmp(zDate,"now")==0){
|
||||
setDateTimeToCurrent(context, p);
|
||||
return 0;
|
||||
}else if( sqlite3IsNumber(zDate, &isRealNum, SQLITE_UTF8) ){
|
||||
double r;
|
||||
getValue(zDate, &r);
|
||||
return setDateTimeToCurrent(context, p);
|
||||
}else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8) ){
|
||||
p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5);
|
||||
p->validJD = 1;
|
||||
return 0;
|
||||
@@ -419,15 +400,85 @@ static void clearYMD_HMS_TZ(DateTime *p){
|
||||
p->validTZ = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** On recent Windows platforms, the localtime_s() function is available
|
||||
** as part of the "Secure CRT". It is essentially equivalent to
|
||||
** localtime_r() available under most POSIX platforms, except that the
|
||||
** order of the parameters is reversed.
|
||||
**
|
||||
** See http://msdn.microsoft.com/en-us/library/a442x3ye(VS.80).aspx.
|
||||
**
|
||||
** If the user has not indicated to use localtime_r() or localtime_s()
|
||||
** already, check for an MSVC build environment that provides
|
||||
** localtime_s().
|
||||
*/
|
||||
#if !defined(HAVE_LOCALTIME_R) && !defined(HAVE_LOCALTIME_S) && \
|
||||
defined(_MSC_VER) && defined(_CRT_INSECURE_DEPRECATE)
|
||||
#define HAVE_LOCALTIME_S 1
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_OMIT_LOCALTIME
|
||||
/*
|
||||
** Compute the difference (in milliseconds)
|
||||
** between localtime and UTC (a.k.a. GMT)
|
||||
** for the time value p where p is in UTC.
|
||||
** The following routine implements the rough equivalent of localtime_r()
|
||||
** using whatever operating-system specific localtime facility that
|
||||
** is available. This routine returns 0 on success and
|
||||
** non-zero on any kind of error.
|
||||
**
|
||||
** If the sqlite3GlobalConfig.bLocaltimeFault variable is true then this
|
||||
** routine will always fail.
|
||||
*/
|
||||
static sqlite3_int64 localtimeOffset(DateTime *p){
|
||||
static int osLocaltime(time_t *t, struct tm *pTm){
|
||||
int rc;
|
||||
#if (!defined(HAVE_LOCALTIME_R) || !HAVE_LOCALTIME_R) \
|
||||
&& (!defined(HAVE_LOCALTIME_S) || !HAVE_LOCALTIME_S)
|
||||
struct tm *pX;
|
||||
#if SQLITE_THREADSAFE>0
|
||||
sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
|
||||
#endif
|
||||
sqlite3_mutex_enter(mutex);
|
||||
pX = localtime(t);
|
||||
#ifndef SQLITE_OMIT_BUILTIN_TEST
|
||||
if( sqlite3GlobalConfig.bLocaltimeFault ) pX = 0;
|
||||
#endif
|
||||
if( pX ) *pTm = *pX;
|
||||
sqlite3_mutex_leave(mutex);
|
||||
rc = pX==0;
|
||||
#else
|
||||
#ifndef SQLITE_OMIT_BUILTIN_TEST
|
||||
if( sqlite3GlobalConfig.bLocaltimeFault ) return 1;
|
||||
#endif
|
||||
#if defined(HAVE_LOCALTIME_R) && HAVE_LOCALTIME_R
|
||||
rc = localtime_r(t, pTm)==0;
|
||||
#else
|
||||
rc = localtime_s(pTm, t);
|
||||
#endif /* HAVE_LOCALTIME_R */
|
||||
#endif /* HAVE_LOCALTIME_R || HAVE_LOCALTIME_S */
|
||||
return rc;
|
||||
}
|
||||
#endif /* SQLITE_OMIT_LOCALTIME */
|
||||
|
||||
|
||||
#ifndef SQLITE_OMIT_LOCALTIME
|
||||
/*
|
||||
** Compute the difference (in milliseconds) between localtime and UTC
|
||||
** (a.k.a. GMT) for the time value p where p is in UTC. If no error occurs,
|
||||
** return this value and set *pRc to SQLITE_OK.
|
||||
**
|
||||
** Or, if an error does occur, set *pRc to SQLITE_ERROR. The returned value
|
||||
** is undefined in this case.
|
||||
*/
|
||||
static sqlite3_int64 localtimeOffset(
|
||||
DateTime *p, /* Date at which to calculate offset */
|
||||
sqlite3_context *pCtx, /* Write error here if one occurs */
|
||||
int *pRc /* OUT: Error code. SQLITE_OK or ERROR */
|
||||
){
|
||||
DateTime x, y;
|
||||
time_t t;
|
||||
struct tm sLocal;
|
||||
|
||||
/* Initialize the contents of sLocal to avoid a compiler warning. */
|
||||
memset(&sLocal, 0, sizeof(sLocal));
|
||||
|
||||
x = *p;
|
||||
computeYMD_HMS(&x);
|
||||
if( x.Y<1971 || x.Y>=2038 ){
|
||||
@@ -445,47 +496,23 @@ static sqlite3_int64 localtimeOffset(DateTime *p){
|
||||
x.validJD = 0;
|
||||
computeJD(&x);
|
||||
t = (time_t)(x.iJD/1000 - 21086676*(i64)10000);
|
||||
#ifdef HAVE_LOCALTIME_R
|
||||
{
|
||||
struct tm sLocal;
|
||||
localtime_r(&t, &sLocal);
|
||||
y.Y = sLocal.tm_year + 1900;
|
||||
y.M = sLocal.tm_mon + 1;
|
||||
y.D = sLocal.tm_mday;
|
||||
y.h = sLocal.tm_hour;
|
||||
y.m = sLocal.tm_min;
|
||||
y.s = sLocal.tm_sec;
|
||||
if( osLocaltime(&t, &sLocal) ){
|
||||
sqlite3_result_error(pCtx, "local time unavailable", -1);
|
||||
*pRc = SQLITE_ERROR;
|
||||
return 0;
|
||||
}
|
||||
#elif defined(HAVE_LOCALTIME_S) && HAVE_LOCALTIME_S
|
||||
{
|
||||
struct tm sLocal;
|
||||
localtime_s(&sLocal, &t);
|
||||
y.Y = sLocal.tm_year + 1900;
|
||||
y.M = sLocal.tm_mon + 1;
|
||||
y.D = sLocal.tm_mday;
|
||||
y.h = sLocal.tm_hour;
|
||||
y.m = sLocal.tm_min;
|
||||
y.s = sLocal.tm_sec;
|
||||
}
|
||||
#else
|
||||
{
|
||||
struct tm *pTm;
|
||||
sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
|
||||
pTm = localtime(&t);
|
||||
y.Y = pTm->tm_year + 1900;
|
||||
y.M = pTm->tm_mon + 1;
|
||||
y.D = pTm->tm_mday;
|
||||
y.h = pTm->tm_hour;
|
||||
y.m = pTm->tm_min;
|
||||
y.s = pTm->tm_sec;
|
||||
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
|
||||
}
|
||||
#endif
|
||||
y.Y = sLocal.tm_year + 1900;
|
||||
y.M = sLocal.tm_mon + 1;
|
||||
y.D = sLocal.tm_mday;
|
||||
y.h = sLocal.tm_hour;
|
||||
y.m = sLocal.tm_min;
|
||||
y.s = sLocal.tm_sec;
|
||||
y.validYMD = 1;
|
||||
y.validHMS = 1;
|
||||
y.validJD = 0;
|
||||
y.validTZ = 0;
|
||||
computeJD(&y);
|
||||
*pRc = SQLITE_OK;
|
||||
return y.iJD - x.iJD;
|
||||
}
|
||||
#endif /* SQLITE_OMIT_LOCALTIME */
|
||||
@@ -509,9 +536,12 @@ static sqlite3_int64 localtimeOffset(DateTime *p){
|
||||
** localtime
|
||||
** utc
|
||||
**
|
||||
** Return 0 on success and 1 if there is any kind of error.
|
||||
** Return 0 on success and 1 if there is any kind of error. If the error
|
||||
** is in a system call (i.e. localtime()), then an error message is written
|
||||
** to context pCtx. If the error is an unrecognized modifier, no error is
|
||||
** written to pCtx.
|
||||
*/
|
||||
static int parseModifier(const char *zMod, DateTime *p){
|
||||
static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){
|
||||
int rc = 1;
|
||||
int n;
|
||||
double r;
|
||||
@@ -531,9 +561,8 @@ static int parseModifier(const char *zMod, DateTime *p){
|
||||
*/
|
||||
if( strcmp(z, "localtime")==0 ){
|
||||
computeJD(p);
|
||||
p->iJD += localtimeOffset(p);
|
||||
p->iJD += localtimeOffset(p, pCtx, &rc);
|
||||
clearYMD_HMS_TZ(p);
|
||||
rc = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -554,11 +583,12 @@ static int parseModifier(const char *zMod, DateTime *p){
|
||||
else if( strcmp(z, "utc")==0 ){
|
||||
sqlite3_int64 c1;
|
||||
computeJD(p);
|
||||
c1 = localtimeOffset(p);
|
||||
p->iJD -= c1;
|
||||
clearYMD_HMS_TZ(p);
|
||||
p->iJD += c1 - localtimeOffset(p);
|
||||
rc = 0;
|
||||
c1 = localtimeOffset(p, pCtx, &rc);
|
||||
if( rc==SQLITE_OK ){
|
||||
p->iJD -= c1;
|
||||
clearYMD_HMS_TZ(p);
|
||||
p->iJD += c1 - localtimeOffset(p, pCtx, &rc);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
@@ -571,8 +601,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 +658,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
|
||||
@@ -723,8 +757,9 @@ static int isDate(
|
||||
int eType;
|
||||
memset(p, 0, sizeof(*p));
|
||||
if( argc==0 ){
|
||||
setDateTimeToCurrent(context, p);
|
||||
}else if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT
|
||||
return setDateTimeToCurrent(context, p);
|
||||
}
|
||||
if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT
|
||||
|| eType==SQLITE_INTEGER ){
|
||||
p->iJD = (sqlite3_int64)(sqlite3_value_double(argv[0])*86400000.0 + 0.5);
|
||||
p->validJD = 1;
|
||||
@@ -735,9 +770,8 @@ static int isDate(
|
||||
}
|
||||
}
|
||||
for(i=1; i<argc; i++){
|
||||
if( (z = sqlite3_value_text(argv[i]))==0 || parseModifier((char*)z, p) ){
|
||||
return 1;
|
||||
}
|
||||
z = sqlite3_value_text(argv[i]);
|
||||
if( z==0 || parseModifier(context, (char*)z, p) ) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1037,31 +1071,28 @@ static void currentTimeFunc(
|
||||
char *zFormat = (char *)sqlite3_user_data(context);
|
||||
sqlite3 *db;
|
||||
sqlite3_int64 iT;
|
||||
struct tm *pTm;
|
||||
struct tm sNow;
|
||||
char zBuf[20];
|
||||
|
||||
UNUSED_PARAMETER(argc);
|
||||
UNUSED_PARAMETER(argv);
|
||||
|
||||
db = sqlite3_context_db_handle(context);
|
||||
sqlite3OsCurrentTimeInt64(db->pVfs, &iT);
|
||||
if( sqlite3OsCurrentTimeInt64(db->pVfs, &iT) ) return;
|
||||
t = iT/1000 - 10000*(sqlite3_int64)21086676;
|
||||
#ifdef HAVE_GMTIME_R
|
||||
{
|
||||
struct tm sNow;
|
||||
gmtime_r(&t, &sNow);
|
||||
strftime(zBuf, 20, zFormat, &sNow);
|
||||
}
|
||||
pTm = gmtime_r(&t, &sNow);
|
||||
#else
|
||||
{
|
||||
struct tm *pTm;
|
||||
sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
|
||||
pTm = gmtime(&t);
|
||||
strftime(zBuf, 20, zFormat, pTm);
|
||||
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
|
||||
}
|
||||
sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
|
||||
pTm = gmtime(&t);
|
||||
if( pTm ) memcpy(&sNow, pTm, sizeof(sNow));
|
||||
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
|
||||
#endif
|
||||
|
||||
sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
|
||||
if( pTm ){
|
||||
strftime(zBuf, 20, zFormat, &sNow);
|
||||
sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -15,9 +15,18 @@
|
||||
#include "sqliteInt.h"
|
||||
|
||||
/*
|
||||
** Look up every table that is named in pSrc. If any table is not found,
|
||||
** add an error message to pParse->zErrMsg and return NULL. If all tables
|
||||
** are found, return a pointer to the last table.
|
||||
** While a SrcList can in general represent multiple tables and subqueries
|
||||
** (as in the FROM clause of a SELECT statement) in this case it contains
|
||||
** the name of a single table, as one might find in an INSERT, DELETE,
|
||||
** or UPDATE statement. Look up that table in the symbol table and
|
||||
** return a pointer. Set an error message and return NULL if the table
|
||||
** name is not found or if any other error occurs.
|
||||
**
|
||||
** The following fields are initialized appropriate in pSrc:
|
||||
**
|
||||
** pSrc->a[0].pTab Pointer to the Table object
|
||||
** pSrc->a[0].pIndex Pointer to the INDEXED BY index, if there is one
|
||||
**
|
||||
*/
|
||||
Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){
|
||||
struct SrcList_item *pItem = pSrc->a;
|
||||
@@ -362,7 +371,9 @@ void sqlite3DeleteFrom(
|
||||
/* Collect rowids of every row to be deleted.
|
||||
*/
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet);
|
||||
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere,0,WHERE_DUPLICATES_OK);
|
||||
pWInfo = sqlite3WhereBegin(
|
||||
pParse, pTabList, pWhere, 0, 0, WHERE_DUPLICATES_OK
|
||||
);
|
||||
if( pWInfo==0 ) goto delete_from_cleanup;
|
||||
regRowid = sqlite3ExprCodeGetColumn(pParse, pTab, -1, iCur, iRowid);
|
||||
sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, regRowid);
|
||||
@@ -392,6 +403,7 @@ void sqlite3DeleteFrom(
|
||||
const char *pVTab = (const char *)sqlite3GetVTable(db, pTab);
|
||||
sqlite3VtabMakeWritable(pParse, pTab);
|
||||
sqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iRowid, pVTab, P4_VTAB);
|
||||
sqlite3VdbeChangeP5(v, OE_Abort);
|
||||
sqlite3MayAbort(pParse);
|
||||
}else
|
||||
#endif
|
||||
@@ -536,7 +548,7 @@ void sqlite3GenerateRowDelete(
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, 0);
|
||||
sqlite3VdbeAddOp2(v, OP_Delete, iCur, (count?OPFLAG_NCHANGE:0));
|
||||
if( count ){
|
||||
sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC);
|
||||
sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,8 +638,14 @@ int sqlite3GenerateIndexKey(
|
||||
}
|
||||
}
|
||||
if( doMakeRec ){
|
||||
const char *zAff;
|
||||
if( pTab->pSelect || (pParse->db->flags & SQLITE_IdxRealAsInt)!=0 ){
|
||||
zAff = 0;
|
||||
}else{
|
||||
zAff = sqlite3IndexAffinityStr(v, pIdx);
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol+1, regOut);
|
||||
sqlite3VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v, pIdx), 0);
|
||||
sqlite3VdbeChangeP4(v, -1, zAff, P4_TRANSIENT);
|
||||
}
|
||||
sqlite3ReleaseTempRange(pParse, regBase, nCol+1);
|
||||
return regBase;
|
||||
|
||||
@@ -92,7 +92,7 @@ Expr *sqlite3ExprSetCollByToken(Parse *pParse, Expr *pExpr, Token *pCollName){
|
||||
CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){
|
||||
CollSeq *pColl = 0;
|
||||
Expr *p = pExpr;
|
||||
while( ALWAYS(p) ){
|
||||
while( p ){
|
||||
int op;
|
||||
pColl = p->pColl;
|
||||
if( pColl ) break;
|
||||
@@ -389,6 +389,7 @@ Expr *sqlite3ExprAlloc(
|
||||
if( op!=TK_INTEGER || pToken->z==0
|
||||
|| sqlite3GetInt32(pToken->z, &iValue)==0 ){
|
||||
nExtra = pToken->n+1;
|
||||
assert( iValue>=0 );
|
||||
}
|
||||
}
|
||||
pNew = sqlite3DbMallocZero(db, sizeof(Expr)+nExtra);
|
||||
@@ -402,7 +403,8 @@ Expr *sqlite3ExprAlloc(
|
||||
}else{
|
||||
int c;
|
||||
pNew->u.zToken = (char*)&pNew[1];
|
||||
memcpy(pNew->u.zToken, pToken->z, pToken->n);
|
||||
assert( pToken->z!=0 || pToken->n==0 );
|
||||
if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n);
|
||||
pNew->u.zToken[pToken->n] = 0;
|
||||
if( dequote && nExtra>=3
|
||||
&& ((c = pToken->z[0])=='\'' || c=='"' || c=='[' || c=='`') ){
|
||||
@@ -484,6 +486,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;
|
||||
}
|
||||
|
||||
@@ -551,53 +556,53 @@ void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){
|
||||
/* Wildcard of the form "?". Assign the next variable number */
|
||||
assert( z[0]=='?' );
|
||||
pExpr->iColumn = (ynVar)(++pParse->nVar);
|
||||
}else if( z[0]=='?' ){
|
||||
/* 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);
|
||||
pExpr->iColumn = (ynVar)i;
|
||||
testcase( i==0 );
|
||||
testcase( i==1 );
|
||||
testcase( i==db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]-1 );
|
||||
testcase( i==db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] );
|
||||
if( bOk==0 || i<1 || i>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){
|
||||
sqlite3ErrorMsg(pParse, "variable number must be between ?1 and ?%d",
|
||||
db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]);
|
||||
}
|
||||
if( i>pParse->nVar ){
|
||||
pParse->nVar = (int)i;
|
||||
}
|
||||
}else{
|
||||
/* Wildcards like ":aaa", "$aaa" or "@aaa". Reuse the same variable
|
||||
** number as the prior appearance of the same name, or if the name
|
||||
** has never appeared before, reuse the same variable number
|
||||
*/
|
||||
int i;
|
||||
u32 n;
|
||||
n = sqlite3Strlen30(z);
|
||||
for(i=0; i<pParse->nVarExpr; i++){
|
||||
Expr *pE = pParse->apVarExpr[i];
|
||||
assert( pE!=0 );
|
||||
if( memcmp(pE->u.zToken, z, n)==0 && pE->u.zToken[n]==0 ){
|
||||
pExpr->iColumn = pE->iColumn;
|
||||
break;
|
||||
ynVar x = 0;
|
||||
u32 n = sqlite3Strlen30(z);
|
||||
if( z[0]=='?' ){
|
||||
/* Wildcard of the form "?nnn". Convert "nnn" to an integer and
|
||||
** use it as the variable number */
|
||||
i64 i;
|
||||
int bOk = 0==sqlite3Atoi64(&z[1], &i, n-1, SQLITE_UTF8);
|
||||
pExpr->iColumn = x = (ynVar)i;
|
||||
testcase( i==0 );
|
||||
testcase( i==1 );
|
||||
testcase( i==db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]-1 );
|
||||
testcase( i==db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] );
|
||||
if( bOk==0 || i<1 || i>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){
|
||||
sqlite3ErrorMsg(pParse, "variable number must be between ?1 and ?%d",
|
||||
db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]);
|
||||
x = 0;
|
||||
}
|
||||
if( i>pParse->nVar ){
|
||||
pParse->nVar = (int)i;
|
||||
}
|
||||
}else{
|
||||
/* Wildcards like ":aaa", "$aaa" or "@aaa". Reuse the same variable
|
||||
** number as the prior appearance of the same name, or if the name
|
||||
** has never appeared before, reuse the same variable number
|
||||
*/
|
||||
ynVar i;
|
||||
for(i=0; i<pParse->nzVar; i++){
|
||||
if( pParse->azVar[i] && memcmp(pParse->azVar[i],z,n+1)==0 ){
|
||||
pExpr->iColumn = x = (ynVar)i+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( x==0 ) x = pExpr->iColumn = (ynVar)(++pParse->nVar);
|
||||
}
|
||||
if( i>=pParse->nVarExpr ){
|
||||
pExpr->iColumn = (ynVar)(++pParse->nVar);
|
||||
if( pParse->nVarExpr>=pParse->nVarExprAlloc-1 ){
|
||||
pParse->nVarExprAlloc += pParse->nVarExprAlloc + 10;
|
||||
pParse->apVarExpr =
|
||||
sqlite3DbReallocOrFree(
|
||||
db,
|
||||
pParse->apVarExpr,
|
||||
pParse->nVarExprAlloc*sizeof(pParse->apVarExpr[0])
|
||||
);
|
||||
if( x>0 ){
|
||||
if( x>pParse->nzVar ){
|
||||
char **a;
|
||||
a = sqlite3DbRealloc(db, pParse->azVar, x*sizeof(a[0]));
|
||||
if( a==0 ) return; /* Error reported through db->mallocFailed */
|
||||
pParse->azVar = a;
|
||||
memset(&a[pParse->nzVar], 0, (x-pParse->nzVar)*sizeof(a[0]));
|
||||
pParse->nzVar = x;
|
||||
}
|
||||
if( !db->mallocFailed ){
|
||||
assert( pParse->apVarExpr!=0 );
|
||||
pParse->apVarExpr[pParse->nVarExpr++] = pExpr;
|
||||
if( z[0]!='?' || pParse->azVar[x-1]==0 ){
|
||||
sqlite3DbFree(db, pParse->azVar[x-1]);
|
||||
pParse->azVar[x-1] = sqlite3DbStrNDup(db, z, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -611,6 +616,8 @@ void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){
|
||||
*/
|
||||
void sqlite3ExprDelete(sqlite3 *db, Expr *p){
|
||||
if( p==0 ) return;
|
||||
/* Sanity check: Assert that the IntValue is non-negative if it exists */
|
||||
assert( !ExprHasProperty(p, EP_IntValue) || p->u.iValue>=0 );
|
||||
if( !ExprHasAnyProperty(p, EP_TokenOnly) ){
|
||||
sqlite3ExprDelete(db, p->pLeft);
|
||||
sqlite3ExprDelete(db, p->pRight);
|
||||
@@ -895,7 +902,9 @@ SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){
|
||||
pNewItem->zAlias = sqlite3DbStrDup(db, pOldItem->zAlias);
|
||||
pNewItem->jointype = pOldItem->jointype;
|
||||
pNewItem->iCursor = pOldItem->iCursor;
|
||||
pNewItem->isPopulated = pOldItem->isPopulated;
|
||||
pNewItem->addrFillSub = pOldItem->addrFillSub;
|
||||
pNewItem->regReturn = pOldItem->regReturn;
|
||||
pNewItem->isCorrelated = pOldItem->isCorrelated;
|
||||
pNewItem->zIndex = sqlite3DbStrDup(db, pOldItem->zIndex);
|
||||
pNewItem->notIndexed = pOldItem->notIndexed;
|
||||
pNewItem->pIndex = pOldItem->pIndex;
|
||||
@@ -1195,16 +1204,17 @@ int sqlite3ExprIsConstantOrFunction(Expr *p){
|
||||
*/
|
||||
int sqlite3ExprIsInteger(Expr *p, int *pValue){
|
||||
int rc = 0;
|
||||
|
||||
/* If an expression is an integer literal that fits in a signed 32-bit
|
||||
** integer, then the EP_IntValue flag will have already been set */
|
||||
assert( p->op!=TK_INTEGER || (p->flags & EP_IntValue)!=0
|
||||
|| sqlite3GetInt32(p->u.zToken, &rc)==0 );
|
||||
|
||||
if( p->flags & EP_IntValue ){
|
||||
*pValue = p->u.iValue;
|
||||
return 1;
|
||||
}
|
||||
switch( p->op ){
|
||||
case TK_INTEGER: {
|
||||
rc = sqlite3GetInt32(p->u.zToken, pValue);
|
||||
assert( rc==0 );
|
||||
break;
|
||||
}
|
||||
case TK_UPLUS: {
|
||||
rc = sqlite3ExprIsInteger(p->pLeft, pValue);
|
||||
break;
|
||||
@@ -1219,13 +1229,6 @@ int sqlite3ExprIsInteger(Expr *p, int *pValue){
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
if( rc ){
|
||||
assert( ExprHasAnyProperty(p, EP_Reduced|EP_TokenOnly)
|
||||
|| (p->flags2 & EP2_MallocedToken)==0 );
|
||||
p->op = TK_INTEGER;
|
||||
p->flags |= EP_IntValue;
|
||||
p->u.iValue = *pValue;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -1440,11 +1443,19 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, int *prNotFound){
|
||||
p = (ExprHasProperty(pX, EP_xIsSelect) ? pX->x.pSelect : 0);
|
||||
if( ALWAYS(pParse->nErr==0) && isCandidateForInOpt(p) ){
|
||||
sqlite3 *db = pParse->db; /* Database connection */
|
||||
Expr *pExpr = p->pEList->a[0].pExpr; /* Expression <column> */
|
||||
int iCol = pExpr->iColumn; /* Index of column <column> */
|
||||
Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */
|
||||
Table *pTab = p->pSrc->a[0].pTab; /* Table <table>. */
|
||||
Table *pTab; /* Table <table>. */
|
||||
Expr *pExpr; /* Expression <column> */
|
||||
int iCol; /* Index of column <column> */
|
||||
int iDb; /* Database idx for pTab */
|
||||
|
||||
assert( p ); /* Because of isCandidateForInOpt(p) */
|
||||
assert( p->pEList!=0 ); /* Because of isCandidateForInOpt(p) */
|
||||
assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */
|
||||
assert( p->pSrc!=0 ); /* Because of isCandidateForInOpt(p) */
|
||||
pTab = p->pSrc->a[0].pTab;
|
||||
pExpr = p->pEList->a[0].pExpr;
|
||||
iCol = pExpr->iColumn;
|
||||
|
||||
/* Code an OP_VerifyCookie and OP_TableLock for <table>. */
|
||||
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
|
||||
@@ -1460,8 +1471,7 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, int *prNotFound){
|
||||
int iMem = ++pParse->nMem;
|
||||
int iAddr;
|
||||
|
||||
iAddr = sqlite3VdbeAddOp1(v, OP_If, iMem);
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 1, iMem);
|
||||
iAddr = sqlite3VdbeAddOp1(v, OP_Once, iMem);
|
||||
|
||||
sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead);
|
||||
eType = IN_INDEX_ROWID;
|
||||
@@ -1492,8 +1502,7 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, int *prNotFound){
|
||||
char *pKey;
|
||||
|
||||
pKey = (char *)sqlite3IndexKeyinfo(pParse, pIdx);
|
||||
iAddr = sqlite3VdbeAddOp1(v, OP_If, iMem);
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 1, iMem);
|
||||
iAddr = sqlite3VdbeAddOp1(v, OP_Once, iMem);
|
||||
|
||||
sqlite3VdbeAddOp4(v, OP_OpenRead, iTab, pIdx->tnum, iDb,
|
||||
pKey,P4_KEYINFO_HANDOFF);
|
||||
@@ -1535,8 +1544,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
|
||||
@@ -1574,7 +1583,7 @@ int sqlite3CodeSubselect(
|
||||
int rMayHaveNull, /* Register that records whether NULLs exist in RHS */
|
||||
int isRowid /* If true, LHS of IN operator is a rowid */
|
||||
){
|
||||
int testAddr = 0; /* One-time test address */
|
||||
int testAddr = -1; /* One-time test address */
|
||||
int rReg = 0; /* Register storing resulting */
|
||||
Vdbe *v = sqlite3GetVdbe(pParse);
|
||||
if( NEVER(v==0) ) return 0;
|
||||
@@ -1592,17 +1601,25 @@ int sqlite3CodeSubselect(
|
||||
*/
|
||||
if( !ExprHasAnyProperty(pExpr, EP_VarSelect) && !pParse->pTriggerTab ){
|
||||
int mem = ++pParse->nMem;
|
||||
sqlite3VdbeAddOp1(v, OP_If, mem);
|
||||
testAddr = sqlite3VdbeAddOp2(v, OP_Integer, 1, mem);
|
||||
assert( testAddr>0 || pParse->db->mallocFailed );
|
||||
testAddr = sqlite3VdbeAddOp1(v, OP_Once, mem);
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_EXPLAIN
|
||||
if( pParse->explain==2 ){
|
||||
char *zMsg = sqlite3MPrintf(
|
||||
pParse->db, "EXECUTE %s%s SUBQUERY %d", testAddr>=0?"":"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 +1642,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 +1659,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;
|
||||
}
|
||||
@@ -1680,9 +1699,9 @@ int sqlite3CodeSubselect(
|
||||
** this code only executes once. Because for a non-constant
|
||||
** expression we need to rerun this code each time.
|
||||
*/
|
||||
if( testAddr && !sqlite3ExprIsConstant(pE2) ){
|
||||
sqlite3VdbeChangeToNoop(v, testAddr-1, 2);
|
||||
testAddr = 0;
|
||||
if( testAddr>=0 && !sqlite3ExprIsConstant(pE2) ){
|
||||
sqlite3VdbeChangeToNoop(v, testAddr);
|
||||
testAddr = -1;
|
||||
}
|
||||
|
||||
/* Evaluate the expression and insert it into the temp table */
|
||||
@@ -1741,6 +1760,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;
|
||||
}
|
||||
@@ -1750,8 +1770,8 @@ int sqlite3CodeSubselect(
|
||||
}
|
||||
}
|
||||
|
||||
if( testAddr ){
|
||||
sqlite3VdbeJumpHere(v, testAddr-1);
|
||||
if( testAddr>=0 ){
|
||||
sqlite3VdbeJumpHere(v, testAddr);
|
||||
}
|
||||
sqlite3ExprCachePop(pParse, 1);
|
||||
|
||||
@@ -1917,7 +1937,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,24 +1951,24 @@ 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;
|
||||
if( pExpr->flags & EP_IntValue ){
|
||||
int i = pExpr->u.iValue;
|
||||
assert( i>=0 );
|
||||
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 = c==2 ? SMALLEST_INT64 : -value; }
|
||||
zV = dup8bytes(v, (char*)&value);
|
||||
sqlite3VdbeAddOp4(v, OP_Int64, 0, iMem, 0, zV, P4_INT64);
|
||||
}else{
|
||||
@@ -2233,73 +2253,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".
|
||||
@@ -2340,7 +2293,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
|
||||
inReg = pCol->iMem;
|
||||
break;
|
||||
}else if( pAggInfo->useSortingIdx ){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdx,
|
||||
sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab,
|
||||
pCol->iSorterColumn, target);
|
||||
break;
|
||||
}
|
||||
@@ -2399,7 +2352,9 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
|
||||
assert( pExpr->u.zToken[0]!=0 );
|
||||
sqlite3VdbeAddOp2(v, OP_Variable, pExpr->iColumn, target);
|
||||
if( pExpr->u.zToken[1]!=0 ){
|
||||
sqlite3VdbeChangeP4(v, -1, pExpr->u.zToken, 0);
|
||||
assert( pExpr->u.zToken[0]=='?'
|
||||
|| strcmp(pExpr->u.zToken, pParse->azVar[pExpr->iColumn-1])==0 );
|
||||
sqlite3VdbeChangeP4(v, -1, pParse->azVar[pExpr->iColumn-1], P4_STATIC);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2408,7 +2363,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 +2795,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 +2893,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 +3047,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 +3086,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;
|
||||
@@ -3286,6 +3258,7 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
|
||||
exprCodeBetween(pParse, pExpr, dest, 1, jumpIfNull);
|
||||
break;
|
||||
}
|
||||
#ifndef SQLITE_OMIT_SUBQUERY
|
||||
case TK_IN: {
|
||||
int destIfFalse = sqlite3VdbeMakeLabel(v);
|
||||
int destIfNull = jumpIfNull ? dest : destIfFalse;
|
||||
@@ -3294,6 +3267,7 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
|
||||
sqlite3VdbeResolveLabel(v, destIfFalse);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default: {
|
||||
r1 = sqlite3ExprCodeTemp(pParse, pExpr, ®Free1);
|
||||
sqlite3VdbeAddOp3(v, OP_If, r1, dest, jumpIfNull!=0);
|
||||
@@ -3427,6 +3401,7 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
|
||||
exprCodeBetween(pParse, pExpr, dest, 0, jumpIfNull);
|
||||
break;
|
||||
}
|
||||
#ifndef SQLITE_OMIT_SUBQUERY
|
||||
case TK_IN: {
|
||||
if( jumpIfNull ){
|
||||
sqlite3ExprCodeIN(pParse, pExpr, dest, dest);
|
||||
@@ -3437,6 +3412,7 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default: {
|
||||
r1 = sqlite3ExprCodeTemp(pParse, pExpr, ®Free1);
|
||||
sqlite3VdbeAddOp3(v, OP_IfNot, r1, dest, jumpIfNull!=0);
|
||||
@@ -3486,7 +3462,7 @@ int sqlite3ExprCompare(Expr *pA, Expr *pB){
|
||||
}
|
||||
}else if( pA->op!=TK_COLUMN && pA->u.zToken ){
|
||||
if( ExprHasProperty(pB, EP_IntValue) || NEVER(pB->u.zToken==0) ) return 2;
|
||||
if( sqlite3StrICmp(pA->u.zToken,pB->u.zToken)!=0 ){
|
||||
if( strcmp(pA->u.zToken,pB->u.zToken)!=0 ){
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,25 +380,37 @@ 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
|
||||
** to increment the constraint-counter (i.e. this is an INSERT operation),
|
||||
** then check if the row being inserted matches itself. If so, do not
|
||||
** increment the constraint-counter. */
|
||||
** increment the constraint-counter.
|
||||
**
|
||||
** If any of the parent-key values are NULL, then the row cannot match
|
||||
** itself. So set JUMPIFNULL to make sure we do the OP_Found if any
|
||||
** of the parent-key values are NULL (at this point it is known that
|
||||
** none of the child key values are).
|
||||
*/
|
||||
if( pTab==pFKey->pFrom && nIncr==1 ){
|
||||
int iJump = sqlite3VdbeCurrentAddr(v) + nCol + 1;
|
||||
for(i=0; i<nCol; i++){
|
||||
int iChild = aiCol[i]+1+regData;
|
||||
int iParent = pIdx->aiColumn[i]+1+regData;
|
||||
assert( aiCol[i]!=pTab->iPKey );
|
||||
if( pIdx->aiColumn[i]==pTab->iPKey ){
|
||||
/* The parent key is a composite key that includes the IPK column */
|
||||
iParent = regData;
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_Ne, iChild, iJump, iParent);
|
||||
sqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL);
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, iOk);
|
||||
}
|
||||
|
||||
sqlite3VdbeAddOp3(v, OP_MakeRecord, regTemp, nCol, regRec);
|
||||
sqlite3VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v, pIdx), 0);
|
||||
sqlite3VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v,pIdx), P4_TRANSIENT);
|
||||
sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regRec, 0);
|
||||
|
||||
sqlite3ReleaseTempReg(pParse, regRec);
|
||||
@@ -548,7 +560,7 @@ static void fkScanChildren(
|
||||
** clause. If the constraint is not deferred, throw an exception for
|
||||
** each row found. Otherwise, for deferred constraints, increment the
|
||||
** deferred constraint counter by nIncr for each row selected. */
|
||||
pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0);
|
||||
pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0);
|
||||
if( nIncr>0 && pFKey->isDeferred==0 ){
|
||||
sqlite3ParseToplevel(pParse)->mayAbort = 1;
|
||||
}
|
||||
@@ -687,7 +699,6 @@ void sqlite3FkCheck(
|
||||
int regNew /* New row data is stored here */
|
||||
){
|
||||
sqlite3 *db = pParse->db; /* Database handle */
|
||||
Vdbe *v; /* VM to write code to */
|
||||
FKey *pFKey; /* Used to iterate through FKs */
|
||||
int iDb; /* Index of database containing pTab */
|
||||
const char *zDb; /* Name of database containing pTab */
|
||||
@@ -699,7 +710,6 @@ void sqlite3FkCheck(
|
||||
/* If foreign-keys are disabled, this function is a no-op. */
|
||||
if( (db->flags&SQLITE_ForeignKeys)==0 ) return;
|
||||
|
||||
v = sqlite3GetVdbe(pParse);
|
||||
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
|
||||
zDb = db->aDb[iDb].zName;
|
||||
|
||||
@@ -724,7 +734,24 @@ void sqlite3FkCheck(
|
||||
pTo = sqlite3LocateTable(pParse, 0, pFKey->zTo, zDb);
|
||||
}
|
||||
if( !pTo || locateFkeyIndex(pParse, pTo, pFKey, &pIdx, &aiFree) ){
|
||||
assert( isIgnoreErrors==0 || (regOld!=0 && regNew==0) );
|
||||
if( !isIgnoreErrors || db->mallocFailed ) return;
|
||||
if( pTo==0 ){
|
||||
/* If isIgnoreErrors is true, then a table is being dropped. In this
|
||||
** case SQLite runs a "DELETE FROM xxx" on the table being dropped
|
||||
** before actually dropping it in order to check FK constraints.
|
||||
** If the parent table of an FK constraint on the current table is
|
||||
** missing, behave as if it is empty. i.e. decrement the relevant
|
||||
** FK counter for each row of the current table with non-NULL keys.
|
||||
*/
|
||||
Vdbe *v = sqlite3GetVdbe(pParse);
|
||||
int iJump = sqlite3VdbeCurrentAddr(v) + pFKey->nCol + 1;
|
||||
for(i=0; i<pFKey->nCol; i++){
|
||||
int iReg = pFKey->aCol[i].iFrom + regOld + 1;
|
||||
sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iJump);
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, -1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
assert( pFKey->nCol==1 || (aiFree && pIdx) );
|
||||
@@ -1097,6 +1124,7 @@ static Trigger *fkActionTrigger(
|
||||
fkTriggerDelete(db, pTrigger);
|
||||
return 0;
|
||||
}
|
||||
assert( pStep!=0 );
|
||||
|
||||
switch( action ){
|
||||
case OE_Restrict:
|
||||
@@ -1156,6 +1184,7 @@ void sqlite3FkDelete(sqlite3 *db, Table *pTab){
|
||||
FKey *pFKey; /* Iterator variable */
|
||||
FKey *pNext; /* Copy of pFKey->pNextFrom */
|
||||
|
||||
assert( db==0 || sqlite3SchemaMutexHeld(db, 0, pTab->pSchema) );
|
||||
for(pFKey=pTab->pFKey; pFKey; pFKey=pNext){
|
||||
|
||||
/* Remove the FK from the fkeyHash hash table. */
|
||||
|
||||
@@ -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);
|
||||
@@ -332,16 +332,15 @@ static void upperFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
|
||||
if( z2 ){
|
||||
z1 = contextMalloc(context, ((i64)n)+1);
|
||||
if( z1 ){
|
||||
memcpy(z1, z2, n+1);
|
||||
for(i=0; z1[i]; i++){
|
||||
z1[i] = (char)sqlite3Toupper(z1[i]);
|
||||
for(i=0; i<n; i++){
|
||||
z1[i] = (char)sqlite3Toupper(z2[i]);
|
||||
}
|
||||
sqlite3_result_text(context, z1, -1, sqlite3_free);
|
||||
sqlite3_result_text(context, z1, n, sqlite3_free);
|
||||
}
|
||||
}
|
||||
}
|
||||
static void lowerFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
|
||||
u8 *z1;
|
||||
char *z1;
|
||||
const char *z2;
|
||||
int i, n;
|
||||
UNUSED_PARAMETER(argc);
|
||||
@@ -352,11 +351,10 @@ static void lowerFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
|
||||
if( z2 ){
|
||||
z1 = contextMalloc(context, ((i64)n)+1);
|
||||
if( z1 ){
|
||||
memcpy(z1, z2, n+1);
|
||||
for(i=0; z1[i]; i++){
|
||||
z1[i] = sqlite3Tolower(z1[i]);
|
||||
for(i=0; i<n; i++){
|
||||
z1[i] = sqlite3Tolower(z2[i]);
|
||||
}
|
||||
sqlite3_result_text(context, (char *)z1, -1, sqlite3_free);
|
||||
sqlite3_result_text(context, z1, n, sqlite3_free);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,10 +504,10 @@ struct compareInfo {
|
||||
** whereas only characters less than 0x80 do in ASCII.
|
||||
*/
|
||||
#if defined(SQLITE_EBCDIC)
|
||||
# define sqlite3Utf8Read(A,C) (*(A++))
|
||||
# define GlogUpperToLower(A) A = sqlite3UpperToLower[A]
|
||||
# define sqlite3Utf8Read(A,C) (*(A++))
|
||||
# define GlogUpperToLower(A) A = sqlite3UpperToLower[A]
|
||||
#else
|
||||
# define GlogUpperToLower(A) if( A<0x80 ){ A = sqlite3UpperToLower[A]; }
|
||||
# define GlogUpperToLower(A) if( !((A)&~0x7f) ){ A = sqlite3UpperToLower[A]; }
|
||||
#endif
|
||||
|
||||
static const struct compareInfo globInfo = { '*', '?', '[', 0 };
|
||||
@@ -552,9 +550,9 @@ static int patternCompare(
|
||||
const u8 *zPattern, /* The glob pattern */
|
||||
const u8 *zString, /* The string to compare against the glob */
|
||||
const struct compareInfo *pInfo, /* Information about how to do the compare */
|
||||
const int esc /* The escape character */
|
||||
u32 esc /* The escape character */
|
||||
){
|
||||
int c, c2;
|
||||
u32 c, c2;
|
||||
int invert;
|
||||
int seen;
|
||||
u8 matchOne = pInfo->matchOne;
|
||||
@@ -608,7 +606,7 @@ static int patternCompare(
|
||||
return 0;
|
||||
}
|
||||
}else if( c==matchSet ){
|
||||
int prior_c = 0;
|
||||
u32 prior_c = 0;
|
||||
assert( esc==0 ); /* This only occurs for GLOB, not LIKE */
|
||||
seen = 0;
|
||||
invert = 0;
|
||||
@@ -684,7 +682,7 @@ static void likeFunc(
|
||||
sqlite3_value **argv
|
||||
){
|
||||
const unsigned char *zA, *zB;
|
||||
int escape = 0;
|
||||
u32 escape = 0;
|
||||
int nPat;
|
||||
sqlite3 *db = sqlite3_context_db_handle(context);
|
||||
|
||||
@@ -774,6 +772,21 @@ static void sourceidFunc(
|
||||
sqlite3_result_text(context, sqlite3_sourceid(), -1, SQLITE_STATIC);
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of the sqlite_log() function. This is a wrapper around
|
||||
** sqlite3_log(). The return value is NULL. The function exists purely for
|
||||
** its side-effects.
|
||||
*/
|
||||
static void errlogFunc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
UNUSED_PARAMETER(argc);
|
||||
UNUSED_PARAMETER(context);
|
||||
sqlite3_log(sqlite3_value_int(argv[0]), "%s", sqlite3_value_text(argv[1]));
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of the sqlite_compileoption_used() function.
|
||||
** The result is an integer that identifies if the compiler option
|
||||
@@ -1239,13 +1252,8 @@ static void sumStep(sqlite3_context *context, int argc, sqlite3_value **argv){
|
||||
if( type==SQLITE_INTEGER ){
|
||||
i64 v = sqlite3_value_int64(argv[0]);
|
||||
p->rSum += v;
|
||||
if( (p->approx|p->overflow)==0 ){
|
||||
i64 iNewSum = p->iSum + v;
|
||||
int s1 = (int)(p->iSum >> (sizeof(i64)*8-1));
|
||||
int s2 = (int)(v >> (sizeof(i64)*8-1));
|
||||
int s3 = (int)(iNewSum >> (sizeof(i64)*8-1));
|
||||
p->overflow = ((s1&s2&~s3) | (~s1&~s2&s3))?1:0;
|
||||
p->iSum = iNewSum;
|
||||
if( (p->approx|p->overflow)==0 && sqlite3AddInt64(&p->iSum, v) ){
|
||||
p->overflow = 1;
|
||||
}
|
||||
}else{
|
||||
p->rSum += sqlite3_value_double(argv[0]);
|
||||
@@ -1450,10 +1458,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,15 +1545,16 @@ 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 ),
|
||||
FUNCTION(sqlite_version, 0, 0, 0, versionFunc ),
|
||||
FUNCTION(sqlite_source_id, 0, 0, 0, sourceidFunc ),
|
||||
FUNCTION(sqlite_log, 2, 0, 0, errlogFunc ),
|
||||
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
|
||||
FUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ),
|
||||
FUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ),
|
||||
@@ -1567,7 +1576,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),
|
||||
|
||||
@@ -129,7 +129,9 @@ const unsigned char sqlite3CtypeMap[256] = {
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef SQLITE_USE_URI
|
||||
# define SQLITE_USE_URI 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
** The following singleton contains the global configuration for
|
||||
@@ -139,8 +141,9 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = {
|
||||
SQLITE_DEFAULT_MEMSTATUS, /* bMemstat */
|
||||
1, /* bCoreMutex */
|
||||
SQLITE_THREADSAFE==1, /* bFullMutex */
|
||||
SQLITE_USE_URI, /* bOpenUri */
|
||||
0x7ffffffe, /* mxStrlen */
|
||||
100, /* szLookaside */
|
||||
128, /* szLookaside */
|
||||
500, /* nLookaside */
|
||||
{0,0,0,0,0,0,0,0}, /* m */
|
||||
{0,0,0,0,0,0,0,0,0}, /* mutex */
|
||||
@@ -166,6 +169,7 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = {
|
||||
0, /* nRefInitMutex */
|
||||
0, /* xLog */
|
||||
0, /* pLogArg */
|
||||
0, /* bLocaltimeFault */
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ void sqlite3TableAffinityStr(Vdbe *v, Table *pTab){
|
||||
pTab->zColAff = zColAff;
|
||||
}
|
||||
|
||||
sqlite3VdbeChangeP4(v, -1, pTab->zColAff, 0);
|
||||
sqlite3VdbeChangeP4(v, -1, pTab->zColAff, P4_TRANSIENT);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -237,6 +237,7 @@ void sqlite3AutoincrementBegin(Parse *pParse){
|
||||
for(p = pParse->pAinc; p; p = p->pNext){
|
||||
pDb = &db->aDb[p->iDb];
|
||||
memId = p->regCtr;
|
||||
assert( sqlite3SchemaMutexHeld(db, 0, pDb->pSchema) );
|
||||
sqlite3OpenTable(pParse, 0, p->iDb, pDb->pSchema->pSeqTab, OP_OpenRead);
|
||||
addr = sqlite3VdbeCurrentAddr(v);
|
||||
sqlite3VdbeAddOp4(v, OP_String8, 0, memId-1, 0, p->pTab->zName, 0);
|
||||
@@ -287,6 +288,7 @@ void sqlite3AutoincrementEnd(Parse *pParse){
|
||||
int memId = p->regCtr;
|
||||
|
||||
iRec = sqlite3GetTempReg(pParse);
|
||||
assert( sqlite3SchemaMutexHeld(db, 0, pDb->pSchema) );
|
||||
sqlite3OpenTable(pParse, 0, p->iDb, pDb->pSchema->pSeqTab, OP_OpenWrite);
|
||||
j1 = sqlite3VdbeAddOp1(v, OP_NotNull, memId+1);
|
||||
j2 = sqlite3VdbeAddOp0(v, OP_Rewind);
|
||||
@@ -465,7 +467,6 @@ void sqlite3Insert(
|
||||
int regIns; /* Block of regs holding rowid+data being inserted */
|
||||
int regRowid; /* registers holding insert rowid */
|
||||
int regData; /* register holding first column to insert */
|
||||
int regRecord; /* Holds the assemblied row record */
|
||||
int regEof = 0; /* Register recording end of SELECT data */
|
||||
int *aRegIdx = 0; /* One register allocated to each index */
|
||||
|
||||
@@ -794,7 +795,6 @@ void sqlite3Insert(
|
||||
/* Allocate registers for holding the rowid of the new row,
|
||||
** the content of the new row, and the assemblied row record.
|
||||
*/
|
||||
regRecord = ++pParse->nMem;
|
||||
regRowid = regIns = pParse->nMem+1;
|
||||
pParse->nMem += pTab->nCol + 1;
|
||||
if( IsVirtual(pTab) ){
|
||||
@@ -969,6 +969,7 @@ void sqlite3Insert(
|
||||
const char *pVTab = (const char *)sqlite3GetVTable(db, pTab);
|
||||
sqlite3VtabMakeWritable(pParse, pTab);
|
||||
sqlite3VdbeAddOp4(v, OP_VUpdate, 1, pTab->nCol+2, regIns, pVTab, P4_VTAB);
|
||||
sqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError);
|
||||
sqlite3MayAbort(pParse);
|
||||
}else
|
||||
#endif
|
||||
@@ -1188,7 +1189,7 @@ void sqlite3GenerateConstraintChecks(
|
||||
case OE_Rollback:
|
||||
case OE_Fail: {
|
||||
char *zMsg;
|
||||
j1 = sqlite3VdbeAddOp3(v, OP_HaltIfNull,
|
||||
sqlite3VdbeAddOp3(v, OP_HaltIfNull,
|
||||
SQLITE_CONSTRAINT, onError, regData+i);
|
||||
zMsg = sqlite3MPrintf(pParse->db, "%s.%s may not be NULL",
|
||||
pTab->zName, pTab->aCol[i].zName);
|
||||
@@ -1328,7 +1329,7 @@ void sqlite3GenerateConstraintChecks(
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_SCopy, regRowid, regIdx+i);
|
||||
sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn+1, aRegIdx[iCur]);
|
||||
sqlite3VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v, pIdx), 0);
|
||||
sqlite3VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v, pIdx), P4_TRANSIENT);
|
||||
sqlite3ExprCacheAffinityChange(pParse, regIdx, pIdx->nColumn+1);
|
||||
|
||||
/* Find out what action to take in case there is an indexing conflict */
|
||||
@@ -1468,7 +1469,7 @@ void sqlite3CompleteInsertion(
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, baseCur, regRec, regRowid);
|
||||
if( !pParse->nested ){
|
||||
sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC);
|
||||
sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT);
|
||||
}
|
||||
sqlite3VdbeChangeP5(v, pik_flags);
|
||||
}
|
||||
@@ -1734,6 +1735,21 @@ static int xferOptimization(
|
||||
return 0; /* Tables have different CHECK constraints. Ticket #2252 */
|
||||
}
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_FOREIGN_KEY
|
||||
/* Disallow the transfer optimization if the destination table constains
|
||||
** any foreign key constraints. This is more restrictive than necessary.
|
||||
** But the main beneficiary of the transfer optimization is the VACUUM
|
||||
** command, and the VACUUM command disables foreign key constraints. So
|
||||
** the extra complication to make this rule less restrictive is probably
|
||||
** not worth the effort. Ticket [6284df89debdfa61db8073e062908af0c9b6118e]
|
||||
*/
|
||||
if( (pParse->db->flags & SQLITE_ForeignKeys)!=0 && pDest->pFKey!=0 ){
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if( (pParse->db->flags & SQLITE_CountRows)!=0 ){
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If we get this far, it means either:
|
||||
**
|
||||
|
||||
@@ -716,7 +716,9 @@ void Parse(
|
||||
){
|
||||
YYMINORTYPE yyminorunion;
|
||||
int yyact; /* The parser action. */
|
||||
#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY)
|
||||
int yyendofinput; /* True if we are at the end of input */
|
||||
#endif
|
||||
#ifdef YYERRORSYMBOL
|
||||
int yyerrorhit = 0; /* True if yymajor has invoked an error */
|
||||
#endif
|
||||
@@ -739,7 +741,9 @@ void Parse(
|
||||
yypParser->yystack[0].major = 0;
|
||||
}
|
||||
yyminorunion.yy0 = yyminor;
|
||||
#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY)
|
||||
yyendofinput = (yymajor==0);
|
||||
#endif
|
||||
ParseARG_STORE;
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -751,7 +755,6 @@ void Parse(
|
||||
do{
|
||||
yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor);
|
||||
if( yyact<YYNSTATE ){
|
||||
assert( !yyendofinput ); /* Impossible to shift the $ token */
|
||||
yy_shift(yypParser,yyact,yymajor,&yyminorunion);
|
||||
yypParser->yyerrcnt--;
|
||||
yymajor = YYNOCODE;
|
||||
|
||||
@@ -71,6 +71,11 @@
|
||||
# define sqlite3_complete16 0
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_OMIT_DECLTYPE
|
||||
# define sqlite3_column_decltype16 0
|
||||
# define sqlite3_column_decltype 0
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_OMIT_PROGRESS_CALLBACK
|
||||
# define sqlite3_progress_handler 0
|
||||
#endif
|
||||
@@ -79,6 +84,8 @@
|
||||
# define sqlite3_create_module 0
|
||||
# define sqlite3_create_module_v2 0
|
||||
# define sqlite3_declare_vtab 0
|
||||
# define sqlite3_vtab_config 0
|
||||
# define sqlite3_vtab_on_conflict 0
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_OMIT_SHARED_CACHE
|
||||
@@ -102,6 +109,7 @@
|
||||
#define sqlite3_blob_open 0
|
||||
#define sqlite3_blob_read 0
|
||||
#define sqlite3_blob_write 0
|
||||
#define sqlite3_blob_reopen 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -327,6 +335,49 @@ 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
|
||||
sqlite3_blob_reopen,
|
||||
sqlite3_vtab_config,
|
||||
sqlite3_vtab_on_conflict,
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -352,7 +403,7 @@ static int sqlite3LoadExtension(
|
||||
int (*xInit)(sqlite3*,char**,const sqlite3_api_routines*);
|
||||
char *zErrmsg = 0;
|
||||
void **aHandle;
|
||||
const int nMsg = 300;
|
||||
int nMsg = 300 + sqlite3Strlen30(zFile);
|
||||
|
||||
if( pzErrMsg ) *pzErrMsg = 0;
|
||||
|
||||
@@ -389,6 +440,7 @@ static int sqlite3LoadExtension(
|
||||
sqlite3OsDlSym(pVfs, handle, zProc);
|
||||
if( xInit==0 ){
|
||||
if( pzErrMsg ){
|
||||
nMsg += sqlite3Strlen30(zProc);
|
||||
*pzErrMsg = zErrmsg = sqlite3_malloc(nMsg);
|
||||
if( zErrmsg ){
|
||||
sqlite3_snprintf(nMsg, zErrmsg,
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -239,15 +266,20 @@ static int mallocWithAlarm(int n, void **pp){
|
||||
sqlite3StatusSet(SQLITE_STATUS_MALLOC_SIZE, n);
|
||||
if( mem0.alarmCallback!=0 ){
|
||||
int nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED);
|
||||
if( nUsed+nFull >= mem0.alarmThreshold ){
|
||||
if( nUsed >= mem0.alarmThreshold - nFull ){
|
||||
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 <= (u32)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 ){
|
||||
@@ -486,13 +507,13 @@ void sqlite3DbFree(sqlite3 *db, void *p){
|
||||
** Change the size of an existing memory allocation
|
||||
*/
|
||||
void *sqlite3Realloc(void *pOld, int nBytes){
|
||||
int nOld, nNew;
|
||||
int nOld, nNew, nDiff;
|
||||
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,15 +521,19 @@ 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;
|
||||
}else if( sqlite3GlobalConfig.bMemstat ){
|
||||
sqlite3_mutex_enter(mem0.mutex);
|
||||
sqlite3StatusSet(SQLITE_STATUS_MALLOC_SIZE, nBytes);
|
||||
if( sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED)+nNew-nOld >=
|
||||
mem0.alarmThreshold ){
|
||||
sqlite3MallocAlarm(nNew-nOld);
|
||||
nDiff = nNew - nOld;
|
||||
if( sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED) >=
|
||||
mem0.alarmThreshold-nDiff ){
|
||||
sqlite3MallocAlarm(nDiff);
|
||||
}
|
||||
assert( sqlite3MemdebugHasType(pOld, MEMTYPE_HEAP) );
|
||||
assert( sqlite3MemdebugNoType(pOld, ~MEMTYPE_HEAP) );
|
||||
@@ -525,6 +550,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 +617,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 ){
|
||||
|
||||
@@ -433,7 +433,7 @@ static void *memsys3MallocUnsafe(int nByte){
|
||||
** This function assumes that the necessary mutexes, if any, are
|
||||
** already held by the caller. Hence "Unsafe".
|
||||
*/
|
||||
void memsys3FreeUnsafe(void *pOld){
|
||||
static void memsys3FreeUnsafe(void *pOld){
|
||||
Mem3Block *p = (Mem3Block*)pOld;
|
||||
int i;
|
||||
u32 size, x;
|
||||
@@ -508,7 +508,7 @@ static void *memsys3Malloc(int nBytes){
|
||||
/*
|
||||
** Free memory.
|
||||
*/
|
||||
void memsys3Free(void *pPrior){
|
||||
static void memsys3Free(void *pPrior){
|
||||
assert( pPrior );
|
||||
memsys3Enter();
|
||||
memsys3FreeUnsafe(pPrior);
|
||||
@@ -518,7 +518,7 @@ void memsys3Free(void *pPrior){
|
||||
/*
|
||||
** Change the size of an existing memory allocation
|
||||
*/
|
||||
void *memsys3Realloc(void *pPrior, int nBytes){
|
||||
static void *memsys3Realloc(void *pPrior, int nBytes){
|
||||
int nOld;
|
||||
void *p;
|
||||
if( pPrior==0 ){
|
||||
|
||||
@@ -127,7 +127,7 @@ static SQLITE_WSD struct Mem5Global {
|
||||
*/
|
||||
u8 *aCtrl;
|
||||
|
||||
} mem5 = { 0 };
|
||||
} mem5;
|
||||
|
||||
/*
|
||||
** Access the static variable through a macro for SQLITE_OMIT_WSD
|
||||
@@ -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;
|
||||
@@ -442,7 +442,7 @@ static int memsys5Roundup(int n){
|
||||
*/
|
||||
static int memsys5Log(int iValue){
|
||||
int iLog;
|
||||
for(iLog=0; (1<<iLog)<iValue; iLog++);
|
||||
for(iLog=0; (iLog<(int)((sizeof(int)*8)-1)) && (1<<iLog)<iValue; iLog++);
|
||||
return iLog;
|
||||
}
|
||||
|
||||
@@ -473,6 +473,7 @@ static int memsys5Init(void *NotUsed){
|
||||
zByte = (u8*)sqlite3GlobalConfig.pHeap;
|
||||
assert( zByte!=0 ); /* sqlite3_config() does not allow otherwise */
|
||||
|
||||
/* boundaries on sqlite3GlobalConfig.mnReq are enforced in sqlite3_config() */
|
||||
nMinLog = memsys5Log(sqlite3GlobalConfig.mnReq);
|
||||
mem5.szAtom = (1<<nMinLog);
|
||||
while( (int)sizeof(Mem5Link)>mem5.szAtom ){
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -60,12 +60,15 @@
|
||||
*/
|
||||
#define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8)
|
||||
#define sqlite3_mutex_free(X)
|
||||
#define sqlite3_mutex_enter(X)
|
||||
#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_leave(X)
|
||||
#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()
|
||||
#define MUTEX_LOGIC(X)
|
||||
#else
|
||||
#define MUTEX_LOGIC(X) X
|
||||
#endif /* defined(SQLITE_MUTEX_OMIT) */
|
||||
|
||||
@@ -31,11 +31,16 @@
|
||||
struct sqlite3_mutex {
|
||||
HMTX mutex; /* Mutex controlling the lock */
|
||||
int id; /* Mutex type */
|
||||
int nRef; /* Number of references */
|
||||
TID owner; /* Thread holding this mutex */
|
||||
#ifdef SQLITE_DEBUG
|
||||
int trace; /* True to trace changes */
|
||||
#endif
|
||||
};
|
||||
|
||||
#define OS2_MUTEX_INITIALIZER 0,0,0,0
|
||||
#ifdef SQLITE_DEBUG
|
||||
#define SQLITE3_MUTEX_INITIALIZER { 0, 0, 0 }
|
||||
#else
|
||||
#define SQLITE3_MUTEX_INITIALIZER { 0, 0 }
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Initialize and deinitialize the mutex subsystem.
|
||||
@@ -51,11 +56,14 @@ static int os2MutexEnd(void){ return SQLITE_OK; }
|
||||
** to sqlite3_mutex_alloc() is one of these integer constants:
|
||||
**
|
||||
** <ul>
|
||||
** <li> SQLITE_MUTEX_FAST 0
|
||||
** <li> SQLITE_MUTEX_RECURSIVE 1
|
||||
** <li> SQLITE_MUTEX_STATIC_MASTER 2
|
||||
** <li> SQLITE_MUTEX_STATIC_MEM 3
|
||||
** <li> SQLITE_MUTEX_STATIC_PRNG 4
|
||||
** <li> SQLITE_MUTEX_FAST
|
||||
** <li> SQLITE_MUTEX_RECURSIVE
|
||||
** <li> SQLITE_MUTEX_STATIC_MASTER
|
||||
** <li> SQLITE_MUTEX_STATIC_MEM
|
||||
** <li> SQLITE_MUTEX_STATIC_MEM2
|
||||
** <li> SQLITE_MUTEX_STATIC_PRNG
|
||||
** <li> SQLITE_MUTEX_STATIC_LRU
|
||||
** <li> SQLITE_MUTEX_STATIC_LRU2
|
||||
** </ul>
|
||||
**
|
||||
** The first two constants cause sqlite3_mutex_alloc() to create
|
||||
@@ -69,7 +77,7 @@ static int os2MutexEnd(void){ return SQLITE_OK; }
|
||||
** might return such a mutex in response to SQLITE_MUTEX_FAST.
|
||||
**
|
||||
** The other allowed parameters to sqlite3_mutex_alloc() each return
|
||||
** a pointer to a static preexisting mutex. Three static mutexes are
|
||||
** a pointer to a static preexisting mutex. Six static mutexes are
|
||||
** used by the current version of SQLite. Future versions of SQLite
|
||||
** may add additional static mutexes. Static mutexes are for internal
|
||||
** use by SQLite only. Applications that use SQLite mutexes should
|
||||
@@ -99,13 +107,13 @@ static sqlite3_mutex *os2MutexAlloc(int iType){
|
||||
}
|
||||
default: {
|
||||
static volatile int isInit = 0;
|
||||
static sqlite3_mutex staticMutexes[] = {
|
||||
{ OS2_MUTEX_INITIALIZER, },
|
||||
{ OS2_MUTEX_INITIALIZER, },
|
||||
{ OS2_MUTEX_INITIALIZER, },
|
||||
{ OS2_MUTEX_INITIALIZER, },
|
||||
{ OS2_MUTEX_INITIALIZER, },
|
||||
{ OS2_MUTEX_INITIALIZER, },
|
||||
static sqlite3_mutex staticMutexes[6] = {
|
||||
SQLITE3_MUTEX_INITIALIZER,
|
||||
SQLITE3_MUTEX_INITIALIZER,
|
||||
SQLITE3_MUTEX_INITIALIZER,
|
||||
SQLITE3_MUTEX_INITIALIZER,
|
||||
SQLITE3_MUTEX_INITIALIZER,
|
||||
SQLITE3_MUTEX_INITIALIZER,
|
||||
};
|
||||
if ( !isInit ){
|
||||
APIRET rc;
|
||||
@@ -151,9 +159,14 @@ static sqlite3_mutex *os2MutexAlloc(int iType){
|
||||
** SQLite is careful to deallocate every mutex that it allocates.
|
||||
*/
|
||||
static void os2MutexFree(sqlite3_mutex *p){
|
||||
if( p==0 ) return;
|
||||
assert( p->nRef==0 );
|
||||
#ifdef SQLITE_DEBUG
|
||||
TID tid;
|
||||
PID pid;
|
||||
ULONG ulCount;
|
||||
DosQueryMutexSem(p->mutex, &pid, &tid, &ulCount);
|
||||
assert( ulCount==0 );
|
||||
assert( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE );
|
||||
#endif
|
||||
DosCloseMutexSem( p->mutex );
|
||||
sqlite3_free( p );
|
||||
}
|
||||
@@ -168,26 +181,29 @@ static int os2MutexHeld(sqlite3_mutex *p){
|
||||
PID pid;
|
||||
ULONG ulCount;
|
||||
PTIB ptib;
|
||||
if( p!=0 ) {
|
||||
DosQueryMutexSem(p->mutex, &pid, &tid, &ulCount);
|
||||
} else {
|
||||
DosGetInfoBlocks(&ptib, NULL);
|
||||
tid = ptib->tib_ptib2->tib2_ultid;
|
||||
}
|
||||
return p==0 || (p->nRef!=0 && p->owner==tid);
|
||||
DosQueryMutexSem(p->mutex, &pid, &tid, &ulCount);
|
||||
if( ulCount==0 || ( ulCount>1 && p->id!=SQLITE_MUTEX_RECURSIVE ) )
|
||||
return 0;
|
||||
DosGetInfoBlocks(&ptib, NULL);
|
||||
return tid==ptib->tib_ptib2->tib2_ultid;
|
||||
}
|
||||
static int os2MutexNotheld(sqlite3_mutex *p){
|
||||
TID tid;
|
||||
PID pid;
|
||||
ULONG ulCount;
|
||||
PTIB ptib;
|
||||
if( p!= 0 ) {
|
||||
DosQueryMutexSem(p->mutex, &pid, &tid, &ulCount);
|
||||
} else {
|
||||
DosGetInfoBlocks(&ptib, NULL);
|
||||
tid = ptib->tib_ptib2->tib2_ultid;
|
||||
}
|
||||
return p==0 || p->nRef==0 || p->owner!=tid;
|
||||
DosQueryMutexSem(p->mutex, &pid, &tid, &ulCount);
|
||||
if( ulCount==0 )
|
||||
return 1;
|
||||
DosGetInfoBlocks(&ptib, NULL);
|
||||
return tid!=ptib->tib_ptib2->tib2_ultid;
|
||||
}
|
||||
static void os2MutexTrace(sqlite3_mutex *p, char *pAction){
|
||||
TID tid;
|
||||
PID pid;
|
||||
ULONG ulCount;
|
||||
DosQueryMutexSem(p->mutex, &pid, &tid, &ulCount);
|
||||
printf("%s mutex %p (%d) with nRef=%ld\n", pAction, (void*)p, p->trace, ulCount);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -203,32 +219,21 @@ static int os2MutexNotheld(sqlite3_mutex *p){
|
||||
** more than once, the behavior is undefined.
|
||||
*/
|
||||
static void os2MutexEnter(sqlite3_mutex *p){
|
||||
TID tid;
|
||||
PID holder1;
|
||||
ULONG holder2;
|
||||
if( p==0 ) return;
|
||||
assert( p->id==SQLITE_MUTEX_RECURSIVE || os2MutexNotheld(p) );
|
||||
DosRequestMutexSem(p->mutex, SEM_INDEFINITE_WAIT);
|
||||
DosQueryMutexSem(p->mutex, &holder1, &tid, &holder2);
|
||||
p->owner = tid;
|
||||
p->nRef++;
|
||||
#ifdef SQLITE_DEBUG
|
||||
if( p->trace ) os2MutexTrace(p, "enter");
|
||||
#endif
|
||||
}
|
||||
static int os2MutexTry(sqlite3_mutex *p){
|
||||
int rc;
|
||||
TID tid;
|
||||
PID holder1;
|
||||
ULONG holder2;
|
||||
if( p==0 ) return SQLITE_OK;
|
||||
int rc = SQLITE_BUSY;
|
||||
assert( p->id==SQLITE_MUTEX_RECURSIVE || os2MutexNotheld(p) );
|
||||
if( DosRequestMutexSem(p->mutex, SEM_IMMEDIATE_RETURN) == NO_ERROR) {
|
||||
DosQueryMutexSem(p->mutex, &holder1, &tid, &holder2);
|
||||
p->owner = tid;
|
||||
p->nRef++;
|
||||
if( DosRequestMutexSem(p->mutex, SEM_IMMEDIATE_RETURN) == NO_ERROR ) {
|
||||
rc = SQLITE_OK;
|
||||
} else {
|
||||
rc = SQLITE_BUSY;
|
||||
#ifdef SQLITE_DEBUG
|
||||
if( p->trace ) os2MutexTrace(p, "try");
|
||||
#endif
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -239,16 +244,11 @@ static int os2MutexTry(sqlite3_mutex *p){
|
||||
** is not currently allocated. SQLite will never do either.
|
||||
*/
|
||||
static void os2MutexLeave(sqlite3_mutex *p){
|
||||
TID tid;
|
||||
PID holder1;
|
||||
ULONG holder2;
|
||||
if( p==0 ) return;
|
||||
assert( p->nRef>0 );
|
||||
DosQueryMutexSem(p->mutex, &holder1, &tid, &holder2);
|
||||
assert( p->owner==tid );
|
||||
p->nRef--;
|
||||
assert( p->nRef==0 || p->id==SQLITE_MUTEX_RECURSIVE );
|
||||
assert( os2MutexHeld(p) );
|
||||
DosReleaseMutexSem(p->mutex);
|
||||
#ifdef SQLITE_DEBUG
|
||||
if( p->trace ) os2MutexTrace(p, "leave");
|
||||
#endif
|
||||
}
|
||||
|
||||
sqlite3_mutex_methods const *sqlite3DefaultMutex(void){
|
||||
@@ -263,6 +263,9 @@ sqlite3_mutex_methods const *sqlite3DefaultMutex(void){
|
||||
#ifdef SQLITE_DEBUG
|
||||
os2MutexHeld,
|
||||
os2MutexNotheld
|
||||
#else
|
||||
0,
|
||||
0
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -280,7 +280,7 @@ static int winMutexTry(sqlite3_mutex *p){
|
||||
#endif
|
||||
#ifdef SQLITE_DEBUG
|
||||
if( rc==SQLITE_OK && p->trace ){
|
||||
printf("enter mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef);
|
||||
printf("try mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef);
|
||||
}
|
||||
#endif
|
||||
return rc;
|
||||
|
||||
@@ -136,7 +136,7 @@ int sqlite3OsOpen(
|
||||
** down into the VFS layer. Some SQLITE_OPEN_ flags (for example,
|
||||
** SQLITE_OPEN_FULLMUTEX or SQLITE_OPEN_SHAREDCACHE) are blocked before
|
||||
** reaching the VFS. */
|
||||
rc = pVfs->xOpen(pVfs, zPath, pFile, flags & 0x87f3f, pFlagsOut);
|
||||
rc = pVfs->xOpen(pVfs, zPath, pFile, flags & 0x87f7f, pFlagsOut);
|
||||
assert( rc==SQLITE_OK || pFile->pMethods==0 );
|
||||
return rc;
|
||||
}
|
||||
@@ -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{
|
||||
@@ -202,7 +208,7 @@ int sqlite3OsOpenMalloc(
|
||||
){
|
||||
int rc = SQLITE_NOMEM;
|
||||
sqlite3_file *pFile;
|
||||
pFile = (sqlite3_file *)sqlite3Malloc(pVfs->szOsFile);
|
||||
pFile = (sqlite3_file *)sqlite3MallocZero(pVfs->szOsFile);
|
||||
if( pFile ){
|
||||
rc = sqlite3OsOpen(pVfs, zFile, pFile, flags, pOutFlags);
|
||||
if( rc!=SQLITE_OK ){
|
||||
@@ -291,12 +297,12 @@ static void vfsUnlink(sqlite3_vfs *pVfs){
|
||||
** true.
|
||||
*/
|
||||
int sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){
|
||||
sqlite3_mutex *mutex = 0;
|
||||
MUTEX_LOGIC(sqlite3_mutex *mutex;)
|
||||
#ifndef SQLITE_OMIT_AUTOINIT
|
||||
int rc = sqlite3_initialize();
|
||||
if( rc ) return rc;
|
||||
#endif
|
||||
mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
|
||||
MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
|
||||
sqlite3_mutex_enter(mutex);
|
||||
vfsUnlink(pVfs);
|
||||
if( makeDflt || vfsList==0 ){
|
||||
|
||||
@@ -29,11 +29,14 @@
|
||||
# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead."
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
int sqlite3OSTrace = 0;
|
||||
#define OSTRACE(X) if( sqlite3OSTrace ) sqlite3DebugPrintf X
|
||||
#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG)
|
||||
# ifndef SQLITE_DEBUG_OS_TRACE
|
||||
# define SQLITE_DEBUG_OS_TRACE 0
|
||||
# endif
|
||||
int sqlite3OSTrace = SQLITE_DEBUG_OS_TRACE;
|
||||
# define OSTRACE(X) if( sqlite3OSTrace ) sqlite3DebugPrintf X
|
||||
#else
|
||||
#define OSTRACE(X)
|
||||
# define OSTRACE(X)
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
||||
@@ -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 */
|
||||
@@ -669,8 +670,8 @@ struct Pager {
|
||||
char *zJournal; /* Name of the journal file */
|
||||
int (*xBusyHandler)(void*); /* Function to call when busy */
|
||||
void *pBusyHandlerArg; /* Context argument for xBusyHandler */
|
||||
int nHit, nMiss; /* Total cache hits and misses */
|
||||
#ifdef SQLITE_TEST
|
||||
int nHit, nMiss; /* Cache hits and missing */
|
||||
int nRead, nWrite; /* Database pages read/written */
|
||||
#endif
|
||||
void (*xReiniter)(DbPage*); /* Call this routine when reloading pages */
|
||||
@@ -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;
|
||||
@@ -2694,7 +2703,6 @@ static int pager_playback(Pager *pPager, int isHot){
|
||||
rc = pager_playback_one_page(pPager,&pPager->journalOff,0,1,0);
|
||||
if( rc!=SQLITE_OK ){
|
||||
if( rc==SQLITE_DONE ){
|
||||
rc = SQLITE_OK;
|
||||
pPager->journalOff = szJ;
|
||||
break;
|
||||
}else if( rc==SQLITE_IOERR_SHORT_READ ){
|
||||
@@ -2746,10 +2754,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');
|
||||
@@ -2842,6 +2850,28 @@ static int readDbPage(PgHdr *pPg){
|
||||
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);
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
/*
|
||||
** This function is invoked once for each page that has already been
|
||||
@@ -2917,19 +2947,47 @@ static int pagerRollbackWal(Pager *pPager){
|
||||
** 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.
|
||||
**
|
||||
** 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 );
|
||||
assert( pList );
|
||||
#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( isCommit ){
|
||||
/* If a WAL transaction is being committed, there is no point in writing
|
||||
** any pages with page numbers greater than nTruncate into the WAL file.
|
||||
** They will never be read by any client. So remove them from the pDirty
|
||||
** list here. */
|
||||
PgHdr *p;
|
||||
PgHdr **ppNext = &pList;
|
||||
for(p=pList; (*ppNext = p); p=p->pDirty){
|
||||
if( p->pgno<=nTruncate ) ppNext = &p->pDirty;
|
||||
}
|
||||
assert( pList );
|
||||
}
|
||||
|
||||
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 +2997,9 @@ static int pagerWalFrames(
|
||||
}
|
||||
|
||||
#ifdef SQLITE_CHECK_PAGES
|
||||
{
|
||||
PgHdr *p;
|
||||
for(p=pList; p; p=p->pDirty) pager_set_pagehash(p);
|
||||
pList = sqlite3PcacheDirtyList(pPager->pPCache);
|
||||
for(p=pList; p; p=p->pDirty){
|
||||
pager_set_pagehash(p);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2971,12 +3029,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 +3092,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 +3317,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 +3538,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;
|
||||
}
|
||||
|
||||
@@ -3646,15 +3739,13 @@ static int pagerSyncHotJournal(Pager *pPager){
|
||||
int sqlite3PagerClose(Pager *pPager){
|
||||
u8 *pTmp = (u8 *)pPager->pTmpSpace;
|
||||
|
||||
assert( assert_pager_state(pPager) );
|
||||
disable_simulated_io_errors();
|
||||
sqlite3BeginBenignMalloc();
|
||||
/* 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 +3911,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 +3923,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 +4025,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);
|
||||
@@ -4077,7 +4169,7 @@ static int pagerStress(void *p, PgHdr *pPg){
|
||||
**
|
||||
** Spilling is also prohibited when in an error state since that could
|
||||
** lead to database corruption. In the current implementaton it
|
||||
** is impossible for sqlite3PCacheFetch() to be called with createFlag==1
|
||||
** is impossible for sqlite3PcacheFetch() to be called with createFlag==1
|
||||
** while in the error state, hence it is impossible for this routine to
|
||||
** be called in the error state. Nevertheless, we include a NEVER()
|
||||
** test for the error state as a safeguard against future changes.
|
||||
@@ -4208,6 +4300,8 @@ int sqlite3PagerOpen(
|
||||
int noReadlock = (flags & PAGER_NO_READLOCK)!=0; /* True to omit read-lock */
|
||||
int pcacheSize = sqlite3PcacheSize(); /* Bytes to allocate for PCache */
|
||||
u32 szPageDflt = SQLITE_DEFAULT_PAGE_SIZE; /* Default page size */
|
||||
const char *zUri = 0; /* URI args to copy */
|
||||
int nUri = 0; /* Number of bytes of URI args at *zUri */
|
||||
|
||||
/* Figure out how much space is required for each journal file-handle
|
||||
** (there are two of them, the main journal and the sub-journal). This
|
||||
@@ -4226,28 +4320,33 @@ 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.
|
||||
*/
|
||||
if( zFilename && zFilename[0] ){
|
||||
const char *z;
|
||||
nPathname = pVfs->mxPathname+1;
|
||||
zPathname = sqlite3Malloc(nPathname*2);
|
||||
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);
|
||||
z = zUri = &zFilename[sqlite3Strlen30(zFilename)+1];
|
||||
while( *z ){
|
||||
z += sqlite3Strlen30(z)+1;
|
||||
z += sqlite3Strlen30(z)+1;
|
||||
}
|
||||
nUri = &z[1] - zUri;
|
||||
if( rc==SQLITE_OK && nPathname+8>pVfs->mxPathname ){
|
||||
/* This branch is taken when the journal path required by
|
||||
** the database being opened will be more than pVfs->mxPathname
|
||||
@@ -4280,7 +4379,7 @@ int sqlite3PagerOpen(
|
||||
ROUND8(pcacheSize) + /* PCache object */
|
||||
ROUND8(pVfs->szOsFile) + /* The main db file */
|
||||
journalFileSize * 2 + /* The two journal files */
|
||||
nPathname + 1 + /* zFilename */
|
||||
nPathname + 1 + nUri + /* zFilename */
|
||||
nPathname + 8 + 1 /* zJournal */
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
+ nPathname + 4 + 1 /* zWal */
|
||||
@@ -4301,19 +4400,18 @@ int sqlite3PagerOpen(
|
||||
|
||||
/* Fill in the Pager.zFilename and Pager.zJournal buffers, if required. */
|
||||
if( zPathname ){
|
||||
pPager->zJournal = (char*)(pPtr += nPathname + 1);
|
||||
assert( nPathname>0 );
|
||||
pPager->zJournal = (char*)(pPtr += nPathname + 1 + nUri);
|
||||
memcpy(pPager->zFilename, zPathname, nPathname);
|
||||
memcpy(&pPager->zFilename[nPathname+1], zUri, nUri);
|
||||
memcpy(pPager->zJournal, zPathname, nPathname);
|
||||
memcpy(&pPager->zJournal[nPathname], "-journal", 8);
|
||||
if( pPager->zFilename[0]==0 ){
|
||||
pPager->zJournal[0] = 0;
|
||||
}
|
||||
sqlite3FileSuffix3(pPager->zFilename, pPager->zJournal);
|
||||
#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);
|
||||
sqlite3FileSuffix3(pPager->zFilename, pPager->zWal);
|
||||
#endif
|
||||
sqlite3_free(zPathname);
|
||||
}
|
||||
@@ -4322,9 +4420,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 +4527,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 +4628,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 +4878,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) ){
|
||||
@@ -4903,14 +5005,13 @@ int sqlite3PagerAcquire(
|
||||
/* In this case the pcache already contains an initialized copy of
|
||||
** the page. Return without further ado. */
|
||||
assert( pgno<=PAGER_MAX_PGNO && pgno!=PAGER_MJ_PGNO(pPager) );
|
||||
PAGER_INCR(pPager->nHit);
|
||||
pPager->nHit++;
|
||||
return SQLITE_OK;
|
||||
|
||||
}else{
|
||||
/* The pager cache has created a new page. Its content needs to
|
||||
** be initialized. */
|
||||
|
||||
PAGER_INCR(pPager->nMiss);
|
||||
pPg = *ppPage;
|
||||
pPg->pPager = pPager;
|
||||
|
||||
@@ -4946,6 +5047,7 @@ int sqlite3PagerAcquire(
|
||||
IOTRACE(("ZERO %p %d\n", pPager, pgno));
|
||||
}else{
|
||||
assert( pPg->pPager==pPager );
|
||||
pPager->nMiss++;
|
||||
rc = readDbPage(pPg);
|
||||
if( rc!=SQLITE_OK ){
|
||||
goto pager_acquire_err;
|
||||
@@ -5207,29 +5309,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 +5558,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 +5605,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 +5622,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 +5648,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;
|
||||
}
|
||||
@@ -5648,11 +5748,21 @@ int sqlite3PagerCommitPhaseOne(
|
||||
}else{
|
||||
if( pagerUseWal(pPager) ){
|
||||
PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache);
|
||||
if( pList ){
|
||||
PgHdr *pPageOne = 0;
|
||||
if( pList==0 ){
|
||||
/* Must have at least one page for the WAL commit flag.
|
||||
** Ticket [2d1a5c67dfc2363e44f29d9bbd57f] 2011-05-18 */
|
||||
rc = sqlite3PagerGet(pPager, 1, &pPageOne);
|
||||
pList = pPageOne;
|
||||
pList->pDirty = 0;
|
||||
}
|
||||
assert( rc==SQLITE_OK );
|
||||
if( ALWAYS(pList) ){
|
||||
rc = pagerWalFrames(pPager, pList, pPager->dbSize, 1,
|
||||
(pPager->fullSync ? pPager->sync_flags : 0)
|
||||
(pPager->fullSync ? pPager->syncFlags : 0)
|
||||
);
|
||||
}
|
||||
sqlite3PagerUnref(pPageOne);
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3PcacheCleanAll(pPager->pPCache);
|
||||
}
|
||||
@@ -5780,8 +5890,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 +6003,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);
|
||||
}
|
||||
@@ -5962,6 +6082,31 @@ int *sqlite3PagerStats(Pager *pPager){
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Parameter eStat must be either SQLITE_DBSTATUS_CACHE_HIT or
|
||||
** SQLITE_DBSTATUS_CACHE_MISS. Before returning, *pnVal is incremented by the
|
||||
** current cache hit or miss count, according to the value of eStat. If the
|
||||
** reset parameter is non-zero, the cache hit or miss count is zeroed before
|
||||
** returning.
|
||||
*/
|
||||
void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, int *pnVal){
|
||||
int *piStat;
|
||||
|
||||
assert( eStat==SQLITE_DBSTATUS_CACHE_HIT
|
||||
|| eStat==SQLITE_DBSTATUS_CACHE_MISS
|
||||
);
|
||||
if( eStat==SQLITE_DBSTATUS_CACHE_HIT ){
|
||||
piStat = &pPager->nHit;
|
||||
}else{
|
||||
piStat = &pPager->nMiss;
|
||||
}
|
||||
|
||||
*pnVal += *piStat;
|
||||
if( reset ){
|
||||
*piStat = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if this is an in-memory pager.
|
||||
*/
|
||||
@@ -6352,7 +6497,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;
|
||||
@@ -6499,6 +6645,7 @@ int sqlite3PagerOkToChangeJournalMode(Pager *pPager){
|
||||
i64 sqlite3PagerJournalSizeLimit(Pager *pPager, i64 iLimit){
|
||||
if( iLimit>=-1 ){
|
||||
pPager->journalSizeLimit = iLimit;
|
||||
sqlite3WalLimit(pPager->pWal, iLimit);
|
||||
}
|
||||
return pPager->journalSizeLimit;
|
||||
}
|
||||
@@ -6515,15 +6662,19 @@ sqlite3_backup **sqlite3PagerBackupPtr(Pager *pPager){
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
/*
|
||||
** This function is called when the user invokes "PRAGMA checkpoint".
|
||||
** This function is called when the user invokes "PRAGMA wal_checkpoint",
|
||||
** "PRAGMA wal_blocking_checkpoint" or calls the sqlite3_wal_checkpoint()
|
||||
** or wal_blocking_checkpoint() API functions.
|
||||
**
|
||||
** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL or RESTART.
|
||||
*/
|
||||
int sqlite3PagerCheckpoint(Pager *pPager){
|
||||
int sqlite3PagerCheckpoint(Pager *pPager, int eMode, int *pnLog, int *pnCkpt){
|
||||
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, eMode,
|
||||
pPager->xBusyHandler, pPager->pBusyHandlerArg,
|
||||
pPager->ckptSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace,
|
||||
pnLog, pnCkpt
|
||||
);
|
||||
}
|
||||
return rc;
|
||||
@@ -6539,9 +6690,62 @@ 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 exclusive lock failed, release the
|
||||
** pending 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->journalSizeLimit, &pPager->pWal
|
||||
);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** The caller must be holding a SHARED lock on the database file to call
|
||||
** this function.
|
||||
@@ -6575,11 +6779,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 +6818,7 @@ int sqlite3PagerCloseWal(Pager *pPager){
|
||||
);
|
||||
}
|
||||
if( rc==SQLITE_OK && logexists ){
|
||||
rc = sqlite3WalOpen(pPager->pVfs, pPager->fd,
|
||||
pPager->zWal, &pPager->pWal);
|
||||
rc = pagerOpenWal(pPager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6627,22 +6826,23 @@ 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;
|
||||
}
|
||||
|
||||
/*
|
||||
** Unless this is an in-memory or temporary database, clear the pager cache.
|
||||
*/
|
||||
void sqlite3PagerClearCache(Pager *pPager){
|
||||
if( !MEMDB && pPager->tempFile==0 ) pager_reset(pPager);
|
||||
}
|
||||
|
||||
#ifdef SQLITE_HAS_CODEC
|
||||
/*
|
||||
** This function is called by the wal module when writing page content
|
||||
|
||||
@@ -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*);
|
||||
@@ -137,7 +138,7 @@ int sqlite3PagerOpenSavepoint(Pager *pPager, int n);
|
||||
int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
|
||||
int sqlite3PagerSharedLock(Pager *pPager);
|
||||
|
||||
int sqlite3PagerCheckpoint(Pager *pPager);
|
||||
int sqlite3PagerCheckpoint(Pager *pPager, int, int*, int*);
|
||||
int sqlite3PagerWalSupported(Pager *pPager);
|
||||
int sqlite3PagerWalCallback(Pager *pPager);
|
||||
int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen);
|
||||
@@ -154,6 +155,8 @@ const char *sqlite3PagerJournalname(Pager*);
|
||||
int sqlite3PagerNosync(Pager*);
|
||||
void *sqlite3PagerTempSpace(Pager*);
|
||||
int sqlite3PagerIsMemdb(Pager*);
|
||||
void sqlite3PagerCacheStat(Pager *, int, int, int *);
|
||||
void sqlite3PagerClearCache(Pager *);
|
||||
|
||||
/* Functions used to truncate the database file. */
|
||||
void sqlite3PagerTruncateImage(Pager*,Pgno);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -249,6 +253,13 @@ int sqlite3PcacheFetch(
|
||||
}
|
||||
if( pPg ){
|
||||
int rc;
|
||||
#ifdef SQLITE_LOG_CACHE_SPILL
|
||||
sqlite3_log(SQLITE_FULL,
|
||||
"spill page %d making room for %d - cache used: %d/%d",
|
||||
pPg->pgno, pgno,
|
||||
sqlite3GlobalConfig.pcache.xPagecount(pCache->pCache),
|
||||
pCache->nMax);
|
||||
#endif
|
||||
rc = pCache->xStress(pCache->pStress, pPg);
|
||||
if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){
|
||||
return rc;
|
||||
|
||||
@@ -22,24 +22,63 @@
|
||||
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 +114,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 +160,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 +173,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 +202,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 +242,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{
|
||||
@@ -224,15 +289,22 @@ static int pcache1MemSize(void *p){
|
||||
*/
|
||||
static PgHdr1 *pcache1AllocPage(PCache1 *pCache){
|
||||
int nByte = sizeof(PgHdr1) + pCache->szPage;
|
||||
void *pPg = pcache1Alloc(nByte);
|
||||
PgHdr1 *p;
|
||||
PgHdr1 *p = 0;
|
||||
void *pPg;
|
||||
|
||||
/* The group mutex must be released before pcache1Alloc() is called. This
|
||||
** is because it may call sqlite3_release_memory(), which assumes that
|
||||
** this mutex is not held. */
|
||||
assert( sqlite3_mutex_held(pCache->pGroup->mutex) );
|
||||
pcache1LeaveMutex(pCache->pGroup);
|
||||
pPg = pcache1Alloc(nByte);
|
||||
pcache1EnterMutex(pCache->pGroup);
|
||||
|
||||
if( pPg ){
|
||||
p = PAGE_TO_PGHDR1(pCache, pPg);
|
||||
if( pCache->bPurgeable ){
|
||||
pcache1.nCurrentPage++;
|
||||
pCache->pGroup->nCurrentPage++;
|
||||
}
|
||||
}else{
|
||||
p = 0;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
@@ -246,10 +318,12 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache){
|
||||
*/
|
||||
static void pcache1FreePage(PgHdr1 *p){
|
||||
if( ALWAYS(p) ){
|
||||
if( p->pCache->bPurgeable ){
|
||||
pcache1.nCurrentPage--;
|
||||
}
|
||||
PCache1 *pCache = p->pCache;
|
||||
assert( sqlite3_mutex_held(p->pCache->pGroup->mutex) );
|
||||
pcache1Free(PGHDR1_TO_PAGE(p));
|
||||
if( pCache->bPurgeable ){
|
||||
pCache->pGroup->nCurrentPage--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,20 +333,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 +375,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 +416,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 +455,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 +471,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 +490,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 +528,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 +553,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.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 +607,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 +623,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 +654,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 +675,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 +685,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 +737,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 +760,9 @@ static void *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag){
|
||||
** attempt to allocate a new one.
|
||||
*/
|
||||
if( !pPage ){
|
||||
if( createFlag==1 ) sqlite3BeginBenignMalloc();
|
||||
pPage = pcache1AllocPage(pCache);
|
||||
if( createFlag==1 ) sqlite3EndBenignMalloc();
|
||||
}
|
||||
|
||||
if( pPage ){
|
||||
@@ -612,8 +781,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 +794,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 +840,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 +857,7 @@ static void pcache1Rekey(
|
||||
pCache->iMaxKey = iNew;
|
||||
}
|
||||
|
||||
pcache1LeaveMutex();
|
||||
pcache1LeaveMutex(pCache->pGroup);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -704,12 +869,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 +884,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 +931,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 +961,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
|
||||
|
||||
@@ -13,10 +13,6 @@
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
/* Ignore this whole file if pragmas are disabled
|
||||
*/
|
||||
#if !defined(SQLITE_OMIT_PRAGMA)
|
||||
|
||||
/*
|
||||
** Interpret the given string as a safety level. Return 0 for OFF,
|
||||
** 1 for ON or NORMAL and 2 for FULL. Return 1 for an empty or
|
||||
@@ -35,7 +31,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++){
|
||||
@@ -49,10 +45,16 @@ static u8 getSafetyLevel(const char *z){
|
||||
/*
|
||||
** Interpret the given string as a boolean value.
|
||||
*/
|
||||
static u8 getBoolean(const char *z){
|
||||
u8 sqlite3GetBoolean(const char *z){
|
||||
return getSafetyLevel(z)&1;
|
||||
}
|
||||
|
||||
/* The sqlite3GetBoolean() function is used by other modules but the
|
||||
** remainder of this file is specific to PRAGMA processing. So omit
|
||||
** the rest of the file if PRAGMAs are omitted from the build.
|
||||
*/
|
||||
#if !defined(SQLITE_OMIT_PRAGMA)
|
||||
|
||||
/*
|
||||
** Interpret the given string as a locking mode value.
|
||||
*/
|
||||
@@ -76,7 +78,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 */
|
||||
@@ -115,7 +117,7 @@ static int invalidateTempStorage(Parse *pParse){
|
||||
}
|
||||
sqlite3BtreeClose(db->aDb[1].pBt);
|
||||
db->aDb[1].pBt = 0;
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@@ -172,6 +174,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 },
|
||||
@@ -218,7 +221,7 @@ static int flagPragma(Parse *pParse, const char *zLeft, const char *zRight){
|
||||
mask &= ~(SQLITE_ForeignKeys);
|
||||
}
|
||||
|
||||
if( getBoolean(zRight) ){
|
||||
if( sqlite3GetBoolean(zRight) ){
|
||||
db->flags |= mask;
|
||||
}else{
|
||||
db->flags &= ~mask;
|
||||
@@ -383,11 +386,11 @@ void sqlite3Pragma(
|
||||
sqlite3VdbeChangeP1(v, addr+1, iDb);
|
||||
sqlite3VdbeChangeP1(v, addr+6, SQLITE_DEFAULT_CACHE_SIZE);
|
||||
}else{
|
||||
int size = atoi(zRight);
|
||||
if( size<0 ) size = -size;
|
||||
int size = sqlite3AbsInt32(sqlite3Atoi(zRight));
|
||||
sqlite3BeginWriteOperation(pParse, 0, iDb);
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, size, 1);
|
||||
sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_DEFAULT_CACHE_SIZE, 1);
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
pDb->pSchema->cache_size = size;
|
||||
sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
|
||||
}
|
||||
@@ -412,35 +415,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
|
||||
@@ -454,7 +435,7 @@ void sqlite3Pragma(
|
||||
int b = -1;
|
||||
assert( pBt!=0 );
|
||||
if( zRight ){
|
||||
b = getBoolean(zRight);
|
||||
b = sqlite3GetBoolean(zRight);
|
||||
}
|
||||
if( pId2->n==0 && b>=0 ){
|
||||
int ii;
|
||||
@@ -467,19 +448,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( sqlite3Tolower(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
|
||||
|
||||
/*
|
||||
@@ -538,8 +533,10 @@ void sqlite3Pragma(
|
||||
int eMode; /* One of the PAGER_JOURNALMODE_XXX symbols */
|
||||
int ii; /* Loop counter */
|
||||
|
||||
/* Force the schema to be loaded on all databases. This cases all
|
||||
** database files to be opened and the journal_modes set. */
|
||||
/* Force the schema to be loaded on all databases. This causes all
|
||||
** database files to be opened and the journal_modes set. This is
|
||||
** necessary because subsequent processing must know if the databases
|
||||
** are in WAL mode. */
|
||||
if( sqlite3ReadSchema(pParse) ){
|
||||
goto pragma_out;
|
||||
}
|
||||
@@ -587,7 +584,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);
|
||||
@@ -698,11 +695,11 @@ void sqlite3Pragma(
|
||||
*/
|
||||
if( sqlite3StrICmp(zLeft,"cache_size")==0 ){
|
||||
if( sqlite3ReadSchema(pParse) ) goto pragma_out;
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
if( !zRight ){
|
||||
returnSingleInt(pParse, "cache_size", pDb->pSchema->cache_size);
|
||||
}else{
|
||||
int size = atoi(zRight);
|
||||
if( size<0 ) size = -size;
|
||||
int size = sqlite3AbsInt32(sqlite3Atoi(zRight));
|
||||
pDb->pSchema->cache_size = size;
|
||||
sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
|
||||
}
|
||||
@@ -1040,7 +1037,7 @@ void sqlite3Pragma(
|
||||
#ifndef NDEBUG
|
||||
if( sqlite3StrICmp(zLeft, "parser_trace")==0 ){
|
||||
if( zRight ){
|
||||
if( getBoolean(zRight) ){
|
||||
if( sqlite3GetBoolean(zRight) ){
|
||||
sqlite3ParserTrace(stderr, "parser: ");
|
||||
}else{
|
||||
sqlite3ParserTrace(0, 0);
|
||||
@@ -1054,7 +1051,7 @@ void sqlite3Pragma(
|
||||
*/
|
||||
if( sqlite3StrICmp(zLeft, "case_sensitive_like")==0 ){
|
||||
if( zRight ){
|
||||
sqlite3RegisterLikeFunctions(db, getBoolean(zRight));
|
||||
sqlite3RegisterLikeFunctions(db, sqlite3GetBoolean(zRight));
|
||||
}
|
||||
}else
|
||||
|
||||
@@ -1083,7 +1080,7 @@ void sqlite3Pragma(
|
||||
{ OP_ResultRow, 3, 1, 0},
|
||||
};
|
||||
|
||||
int isQuick = (zLeft[0]=='q');
|
||||
int isQuick = (sqlite3Tolower(zLeft[0])=='q');
|
||||
|
||||
/* Initialize the VDBE program */
|
||||
if( sqlite3ReadSchema(pParse) ) goto pragma_out;
|
||||
@@ -1094,7 +1091,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;
|
||||
}
|
||||
@@ -1119,6 +1116,7 @@ void sqlite3Pragma(
|
||||
** Begin by filling registers 2, 3, ... with the root pages numbers
|
||||
** for all tables and indices in the database.
|
||||
*/
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
pTbls = &db->aDb[i].pSchema->tblHash;
|
||||
for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){
|
||||
Table *pTab = sqliteHashData(x);
|
||||
@@ -1184,7 +1182,7 @@ void sqlite3Pragma(
|
||||
addr = sqlite3VdbeAddOpList(v, ArraySize(idxErr), idxErr);
|
||||
sqlite3VdbeChangeP4(v, addr+1, "rowid ", P4_STATIC);
|
||||
sqlite3VdbeChangeP4(v, addr+3, " missing from index ", P4_STATIC);
|
||||
sqlite3VdbeChangeP4(v, addr+4, pIdx->zName, P4_STATIC);
|
||||
sqlite3VdbeChangeP4(v, addr+4, pIdx->zName, P4_TRANSIENT);
|
||||
sqlite3VdbeJumpHere(v, addr+9);
|
||||
sqlite3VdbeJumpHere(v, jmp2);
|
||||
}
|
||||
@@ -1214,7 +1212,7 @@ void sqlite3Pragma(
|
||||
sqlite3VdbeJumpHere(v, addr+4);
|
||||
sqlite3VdbeChangeP4(v, addr+6,
|
||||
"wrong # of entries in index ", P4_STATIC);
|
||||
sqlite3VdbeChangeP4(v, addr+7, pIdx->zName, P4_STATIC);
|
||||
sqlite3VdbeChangeP4(v, addr+7, pIdx->zName, P4_TRANSIENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1351,7 +1349,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{
|
||||
@@ -1393,13 +1391,29 @@ void sqlite3Pragma(
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
/*
|
||||
** PRAGMA [database.]wal_checkpoint
|
||||
** PRAGMA [database.]wal_checkpoint = passive|full|restart
|
||||
**
|
||||
** Checkpoint the database.
|
||||
*/
|
||||
if( sqlite3StrICmp(zLeft, "wal_checkpoint")==0 ){
|
||||
int iBt = (pId2->z?iDb:SQLITE_MAX_ATTACHED);
|
||||
int eMode = SQLITE_CHECKPOINT_PASSIVE;
|
||||
if( zRight ){
|
||||
if( sqlite3StrICmp(zRight, "full")==0 ){
|
||||
eMode = SQLITE_CHECKPOINT_FULL;
|
||||
}else if( sqlite3StrICmp(zRight, "restart")==0 ){
|
||||
eMode = SQLITE_CHECKPOINT_RESTART;
|
||||
}
|
||||
}
|
||||
if( sqlite3ReadSchema(pParse) ) goto pragma_out;
|
||||
sqlite3VdbeAddOp3(v, OP_Checkpoint, pId2->z?iDb:SQLITE_MAX_ATTACHED, 0, 0);
|
||||
sqlite3VdbeSetNumCols(v, 3);
|
||||
pParse->nMem = 3;
|
||||
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "busy", SQLITE_STATIC);
|
||||
sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "log", SQLITE_STATIC);
|
||||
sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "checkpointed", SQLITE_STATIC);
|
||||
|
||||
sqlite3VdbeAddOp3(v, OP_Checkpoint, iBt, eMode, 1);
|
||||
sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3);
|
||||
}else
|
||||
|
||||
/*
|
||||
@@ -1412,8 +1426,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 ?
|
||||
@@ -1521,7 +1534,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:
|
||||
|
||||