track 3.7.6.2
@@ -165,7 +165,7 @@ 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 \
|
||||
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 \
|
||||
@@ -306,6 +306,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 \
|
||||
@@ -355,6 +356,7 @@ 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 \
|
||||
@@ -370,10 +372,12 @@ TESTSRC = \
|
||||
$(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_wholenumber.c \
|
||||
$(TOP)/src/test_wsd.c
|
||||
|
||||
# Source code to the library files needed by the test fixture
|
||||
@@ -414,6 +418,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 \
|
||||
@@ -811,6 +816,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
|
||||
|
||||
|
||||
|
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 |
@@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.62 for sqlite 3.7.5.
|
||||
# Generated by GNU Autoconf 2.62 for sqlite 3.7.6.2.
|
||||
#
|
||||
# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
|
||||
# 2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
|
||||
@@ -743,8 +743,8 @@ SHELL=${CONFIG_SHELL-/bin/sh}
|
||||
# Identity of this package.
|
||||
PACKAGE_NAME='sqlite'
|
||||
PACKAGE_TARNAME='sqlite'
|
||||
PACKAGE_VERSION='3.7.5'
|
||||
PACKAGE_STRING='sqlite 3.7.5'
|
||||
PACKAGE_VERSION='3.7.6.2'
|
||||
PACKAGE_STRING='sqlite 3.7.6.2'
|
||||
PACKAGE_BUGREPORT=''
|
||||
|
||||
# Factoring default headers for most tests.
|
||||
@@ -1485,7 +1485,7 @@ if test "$ac_init_help" = "long"; then
|
||||
# Omit some internal or obsolete options to make the list less imposing.
|
||||
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||
cat <<_ACEOF
|
||||
\`configure' configures sqlite 3.7.5 to adapt to many kinds of systems.
|
||||
\`configure' configures sqlite 3.7.6.2 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@@ -1550,7 +1550,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of sqlite 3.7.5:";;
|
||||
short | recursive ) echo "Configuration of sqlite 3.7.6.2:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@@ -1666,7 +1666,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
sqlite configure 3.7.5
|
||||
sqlite configure 3.7.6.2
|
||||
generated by GNU Autoconf 2.62
|
||||
|
||||
Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
|
||||
@@ -1680,7 +1680,7 @@ cat >config.log <<_ACEOF
|
||||
This file contains any messages produced by compilers while
|
||||
running configure, to aid debugging if configure makes a mistake.
|
||||
|
||||
It was created by sqlite $as_me 3.7.5, which was
|
||||
It was created by sqlite $as_me 3.7.6.2, which was
|
||||
generated by GNU Autoconf 2.62. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
@@ -13942,7 +13942,7 @@ exec 6>&1
|
||||
# report actual input values of CONFIG_FILES etc. instead of their
|
||||
# values after options handling.
|
||||
ac_log="
|
||||
This file was extended by sqlite $as_me 3.7.5, which was
|
||||
This file was extended by sqlite $as_me 3.7.6.2, which was
|
||||
generated by GNU Autoconf 2.62. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -13995,7 +13995,7 @@ Report bugs to <bug-autoconf@gnu.org>."
|
||||
_ACEOF
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_version="\\
|
||||
sqlite config.status 3.7.5
|
||||
sqlite config.status 3.7.6.2
|
||||
configured by $0, generated by GNU Autoconf 2.62,
|
||||
with options \\"`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"
|
||||
|
||||
|
||||
@@ -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 lower class 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.
|
||||
@@ -448,6 +448,8 @@ static int fts3DisconnectMethod(sqlite3_vtab *pVtab){
|
||||
sqlite3_finalize(p->aStmt[i]);
|
||||
}
|
||||
sqlite3_free(p->zSegmentsTbl);
|
||||
sqlite3_free(p->zReadExprlist);
|
||||
sqlite3_free(p->zWriteExprlist);
|
||||
|
||||
/* Invoke the tokenizer destructor to free the tokenizer. */
|
||||
p->pTokenizer->pModule->xDestroy(p->pTokenizer);
|
||||
@@ -665,6 +667,141 @@ static int fts3IsSpecialColumn(
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
** Append the output of a printf() style formatting to an existing string.
|
||||
*/
|
||||
static void fts3Appendf(
|
||||
int *pRc, /* IN/OUT: Error code */
|
||||
char **pz, /* IN/OUT: Pointer to string buffer */
|
||||
const char *zFormat, /* Printf format string to append */
|
||||
... /* Arguments for printf format string */
|
||||
){
|
||||
if( *pRc==SQLITE_OK ){
|
||||
va_list ap;
|
||||
char *z;
|
||||
va_start(ap, zFormat);
|
||||
z = sqlite3_vmprintf(zFormat, ap);
|
||||
if( z && *pz ){
|
||||
char *z2 = sqlite3_mprintf("%s%s", *pz, z);
|
||||
sqlite3_free(z);
|
||||
z = z2;
|
||||
}
|
||||
if( z==0 ) *pRc = SQLITE_NOMEM;
|
||||
sqlite3_free(*pz);
|
||||
*pz = z;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a copy of input string zInput enclosed in double-quotes (") and
|
||||
** with all double quote characters escaped. For example:
|
||||
**
|
||||
** fts3QuoteId("un \"zip\"") -> "un \"\"zip\"\""
|
||||
**
|
||||
** The pointer returned points to memory obtained from sqlite3_malloc(). It
|
||||
** is the callers responsibility to call sqlite3_free() to release this
|
||||
** memory.
|
||||
*/
|
||||
static char *fts3QuoteId(char const *zInput){
|
||||
int nRet;
|
||||
char *zRet;
|
||||
nRet = 2 + strlen(zInput)*2 + 1;
|
||||
zRet = sqlite3_malloc(nRet);
|
||||
if( zRet ){
|
||||
int i;
|
||||
char *z = zRet;
|
||||
*(z++) = '"';
|
||||
for(i=0; zInput[i]; i++){
|
||||
if( zInput[i]=='"' ) *(z++) = '"';
|
||||
*(z++) = zInput[i];
|
||||
}
|
||||
*(z++) = '"';
|
||||
*(z++) = '\0';
|
||||
}
|
||||
return zRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a list of comma separated SQL expressions that could be used
|
||||
** in a SELECT statement such as the following:
|
||||
**
|
||||
** SELECT <list of expressions> FROM %_content AS x ...
|
||||
**
|
||||
** to return the docid, followed by each column of text data in order
|
||||
** from left to write. If parameter zFunc is not NULL, then instead of
|
||||
** being returned directly each column of text data is passed to an SQL
|
||||
** function named zFunc first. For example, if zFunc is "unzip" and the
|
||||
** table has the three user-defined columns "a", "b", and "c", the following
|
||||
** string is returned:
|
||||
**
|
||||
** "docid, unzip(x.'a'), unzip(x.'b'), unzip(x.'c')"
|
||||
**
|
||||
** The pointer returned points to a buffer allocated by sqlite3_malloc(). It
|
||||
** is the responsibility of the caller to eventually free it.
|
||||
**
|
||||
** If *pRc is not SQLITE_OK when this function is called, it is a no-op (and
|
||||
** a NULL pointer is returned). Otherwise, if an OOM error is encountered
|
||||
** by this function, NULL is returned and *pRc is set to SQLITE_NOMEM. If
|
||||
** no error occurs, *pRc is left unmodified.
|
||||
*/
|
||||
static char *fts3ReadExprList(Fts3Table *p, const char *zFunc, int *pRc){
|
||||
char *zRet = 0;
|
||||
char *zFree = 0;
|
||||
char *zFunction;
|
||||
int i;
|
||||
|
||||
if( !zFunc ){
|
||||
zFunction = "";
|
||||
}else{
|
||||
zFree = zFunction = fts3QuoteId(zFunc);
|
||||
}
|
||||
fts3Appendf(pRc, &zRet, "docid");
|
||||
for(i=0; i<p->nColumn; i++){
|
||||
fts3Appendf(pRc, &zRet, ",%s(x.'c%d%q')", zFunction, i, p->azColumn[i]);
|
||||
}
|
||||
sqlite3_free(zFree);
|
||||
return zRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a list of N comma separated question marks, where N is the number
|
||||
** of columns in the %_content table (one for the docid plus one for each
|
||||
** user-defined text column).
|
||||
**
|
||||
** If argument zFunc is not NULL, then all but the first question mark
|
||||
** is preceded by zFunc and an open bracket, and followed by a closed
|
||||
** bracket. For example, if zFunc is "zip" and the FTS3 table has three
|
||||
** user-defined text columns, the following string is returned:
|
||||
**
|
||||
** "?, zip(?), zip(?), zip(?)"
|
||||
**
|
||||
** The pointer returned points to a buffer allocated by sqlite3_malloc(). It
|
||||
** is the responsibility of the caller to eventually free it.
|
||||
**
|
||||
** If *pRc is not SQLITE_OK when this function is called, it is a no-op (and
|
||||
** a NULL pointer is returned). Otherwise, if an OOM error is encountered
|
||||
** by this function, NULL is returned and *pRc is set to SQLITE_NOMEM. If
|
||||
** no error occurs, *pRc is left unmodified.
|
||||
*/
|
||||
static char *fts3WriteExprList(Fts3Table *p, const char *zFunc, int *pRc){
|
||||
char *zRet = 0;
|
||||
char *zFree = 0;
|
||||
char *zFunction;
|
||||
int i;
|
||||
|
||||
if( !zFunc ){
|
||||
zFunction = "";
|
||||
}else{
|
||||
zFree = zFunction = fts3QuoteId(zFunc);
|
||||
}
|
||||
fts3Appendf(pRc, &zRet, "?");
|
||||
for(i=0; i<p->nColumn; i++){
|
||||
fts3Appendf(pRc, &zRet, ",%s(?)", zFunction);
|
||||
}
|
||||
sqlite3_free(zFree);
|
||||
return zRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is the implementation of both the xConnect and xCreate
|
||||
** methods of the FTS3 virtual table.
|
||||
@@ -701,6 +838,9 @@ static int fts3InitVtab(
|
||||
const char **aCol; /* Array of column names */
|
||||
sqlite3_tokenizer *pTokenizer = 0; /* Tokenizer for this table */
|
||||
|
||||
char *zCompress = 0;
|
||||
char *zUncompress = 0;
|
||||
|
||||
assert( strlen(argv[0])==4 );
|
||||
assert( (sqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4)
|
||||
|| (sqlite3_strnicmp(argv[0], "fts3", 4)==0 && !isFts4)
|
||||
@@ -751,6 +891,12 @@ static int fts3InitVtab(
|
||||
*pzErr = sqlite3_mprintf("unrecognized matchinfo: %s", zVal);
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
}else if( nKey==8 && 0==sqlite3_strnicmp(z, "compress", 8) ){
|
||||
zCompress = zVal;
|
||||
zVal = 0;
|
||||
}else if( nKey==10 && 0==sqlite3_strnicmp(z, "uncompress", 10) ){
|
||||
zUncompress = zVal;
|
||||
zVal = 0;
|
||||
}else{
|
||||
*pzErr = sqlite3_mprintf("unrecognized parameter: %s", z);
|
||||
rc = SQLITE_ERROR;
|
||||
@@ -825,6 +971,15 @@ static int fts3InitVtab(
|
||||
assert( zCsr <= &((char *)p)[nByte] );
|
||||
}
|
||||
|
||||
if( (zCompress==0)!=(zUncompress==0) ){
|
||||
char const *zMiss = (zCompress==0 ? "compress" : "uncompress");
|
||||
rc = SQLITE_ERROR;
|
||||
*pzErr = sqlite3_mprintf("missing %s parameter in fts4 constructor", zMiss);
|
||||
}
|
||||
p->zReadExprlist = fts3ReadExprList(p, zUncompress, &rc);
|
||||
p->zWriteExprlist = fts3WriteExprList(p, zCompress, &rc);
|
||||
if( rc!=SQLITE_OK ) goto fts3_init_out;
|
||||
|
||||
/* If this is an xCreate call, create the underlying tables in the
|
||||
** database. TODO: For xConnect(), it could verify that said tables exist.
|
||||
*/
|
||||
@@ -842,7 +997,8 @@ static int fts3InitVtab(
|
||||
fts3DeclareVtab(&rc, p);
|
||||
|
||||
fts3_init_out:
|
||||
|
||||
sqlite3_free(zCompress);
|
||||
sqlite3_free(zUncompress);
|
||||
sqlite3_free((void *)aCol);
|
||||
if( rc!=SQLITE_OK ){
|
||||
if( p ){
|
||||
@@ -1935,132 +2091,135 @@ static int fts3DeferredTermSelect(
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** An Fts3SegReaderArray is used to store an array of Fts3SegReader objects.
|
||||
** Elements are added to the array using fts3SegReaderArrayAdd().
|
||||
*/
|
||||
struct Fts3SegReaderArray {
|
||||
int nSegment; /* Number of valid entries in apSegment[] */
|
||||
int nAlloc; /* Allocated size of apSegment[] */
|
||||
int nCost; /* The cost of executing SegReaderIterate() */
|
||||
Fts3SegReader *apSegment[1]; /* Array of seg-reader objects */
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
** Free an Fts3SegReaderArray object. Also free all seg-readers in the
|
||||
** array (using sqlite3Fts3SegReaderFree()).
|
||||
*/
|
||||
static void fts3SegReaderArrayFree(Fts3SegReaderArray *pArray){
|
||||
if( pArray ){
|
||||
int i;
|
||||
for(i=0; i<pArray->nSegment; i++){
|
||||
sqlite3Fts3SegReaderFree(pArray->apSegment[i]);
|
||||
}
|
||||
sqlite3_free(pArray);
|
||||
}
|
||||
}
|
||||
|
||||
static int fts3SegReaderArrayAdd(
|
||||
Fts3SegReaderArray **ppArray,
|
||||
Fts3SegReader *pNew
|
||||
int sqlite3Fts3SegReaderCursor(
|
||||
Fts3Table *p, /* FTS3 table handle */
|
||||
int iLevel, /* Level of segments to scan */
|
||||
const char *zTerm, /* Term to query for */
|
||||
int nTerm, /* Size of zTerm in bytes */
|
||||
int isPrefix, /* True for a prefix search */
|
||||
int isScan, /* True to scan from zTerm to EOF */
|
||||
Fts3SegReaderCursor *pCsr /* Cursor object to populate */
|
||||
){
|
||||
Fts3SegReaderArray *pArray = *ppArray;
|
||||
int rc = SQLITE_OK;
|
||||
int rc2;
|
||||
int iAge = 0;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
Fts3SegReader *pPending = 0;
|
||||
|
||||
if( !pArray || pArray->nAlloc==pArray->nSegment ){
|
||||
int nNew = (pArray ? pArray->nAlloc+16 : 16);
|
||||
pArray = (Fts3SegReaderArray *)sqlite3_realloc(pArray,
|
||||
sizeof(Fts3SegReaderArray) + (nNew-1) * sizeof(Fts3SegReader*)
|
||||
);
|
||||
if( !pArray ){
|
||||
sqlite3Fts3SegReaderFree(pNew);
|
||||
return SQLITE_NOMEM;
|
||||
assert( iLevel==FTS3_SEGCURSOR_ALL
|
||||
|| iLevel==FTS3_SEGCURSOR_PENDING
|
||||
|| iLevel>=0
|
||||
);
|
||||
assert( FTS3_SEGCURSOR_PENDING<0 );
|
||||
assert( FTS3_SEGCURSOR_ALL<0 );
|
||||
assert( iLevel==FTS3_SEGCURSOR_ALL || (zTerm==0 && isPrefix==1) );
|
||||
assert( isPrefix==0 || isScan==0 );
|
||||
|
||||
|
||||
memset(pCsr, 0, sizeof(Fts3SegReaderCursor));
|
||||
|
||||
/* If iLevel is less than 0, include a seg-reader for the pending-terms. */
|
||||
assert( isScan==0 || fts3HashCount(&p->pendingTerms)==0 );
|
||||
if( iLevel<0 && isScan==0 ){
|
||||
rc = sqlite3Fts3SegReaderPending(p, zTerm, nTerm, isPrefix, &pPending);
|
||||
if( rc==SQLITE_OK && pPending ){
|
||||
int nByte = (sizeof(Fts3SegReader *) * 16);
|
||||
pCsr->apSegment = (Fts3SegReader **)sqlite3_malloc(nByte);
|
||||
if( pCsr->apSegment==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
pCsr->apSegment[0] = pPending;
|
||||
pCsr->nSegment = 1;
|
||||
pPending = 0;
|
||||
}
|
||||
}
|
||||
if( nNew==16 ){
|
||||
pArray->nSegment = 0;
|
||||
pArray->nCost = 0;
|
||||
}
|
||||
pArray->nAlloc = nNew;
|
||||
*ppArray = pArray;
|
||||
}
|
||||
|
||||
pArray->apSegment[pArray->nSegment++] = pNew;
|
||||
return SQLITE_OK;
|
||||
if( iLevel!=FTS3_SEGCURSOR_PENDING ){
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3Fts3AllSegdirs(p, iLevel, &pStmt);
|
||||
}
|
||||
while( rc==SQLITE_OK && SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){
|
||||
|
||||
/* Read the values returned by the SELECT into local variables. */
|
||||
sqlite3_int64 iStartBlock = sqlite3_column_int64(pStmt, 1);
|
||||
sqlite3_int64 iLeavesEndBlock = sqlite3_column_int64(pStmt, 2);
|
||||
sqlite3_int64 iEndBlock = sqlite3_column_int64(pStmt, 3);
|
||||
int nRoot = sqlite3_column_bytes(pStmt, 4);
|
||||
char const *zRoot = sqlite3_column_blob(pStmt, 4);
|
||||
|
||||
/* If nSegment is a multiple of 16 the array needs to be extended. */
|
||||
if( (pCsr->nSegment%16)==0 ){
|
||||
Fts3SegReader **apNew;
|
||||
int nByte = (pCsr->nSegment + 16)*sizeof(Fts3SegReader*);
|
||||
apNew = (Fts3SegReader **)sqlite3_realloc(pCsr->apSegment, nByte);
|
||||
if( !apNew ){
|
||||
rc = SQLITE_NOMEM;
|
||||
goto finished;
|
||||
}
|
||||
pCsr->apSegment = apNew;
|
||||
}
|
||||
|
||||
/* If zTerm is not NULL, and this segment is not stored entirely on its
|
||||
** root node, the range of leaves scanned can be reduced. Do this. */
|
||||
if( iStartBlock && zTerm ){
|
||||
sqlite3_int64 *pi = (isPrefix ? &iLeavesEndBlock : 0);
|
||||
rc = fts3SelectLeaf(p, zTerm, nTerm, zRoot, nRoot, &iStartBlock, pi);
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
if( isPrefix==0 && isScan==0 ) iLeavesEndBlock = iStartBlock;
|
||||
}
|
||||
|
||||
rc = sqlite3Fts3SegReaderNew(iAge, iStartBlock, iLeavesEndBlock,
|
||||
iEndBlock, zRoot, nRoot, &pCsr->apSegment[pCsr->nSegment]
|
||||
);
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
pCsr->nSegment++;
|
||||
iAge++;
|
||||
}
|
||||
}
|
||||
|
||||
finished:
|
||||
rc2 = sqlite3_reset(pStmt);
|
||||
if( rc==SQLITE_DONE ) rc = rc2;
|
||||
sqlite3Fts3SegReaderFree(pPending);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int fts3TermSegReaderArray(
|
||||
|
||||
static int fts3TermSegReaderCursor(
|
||||
Fts3Cursor *pCsr, /* Virtual table cursor handle */
|
||||
const char *zTerm, /* Term to query for */
|
||||
int nTerm, /* Size of zTerm in bytes */
|
||||
int isPrefix, /* True for a prefix search */
|
||||
Fts3SegReaderArray **ppArray /* OUT: Allocated seg-reader array */
|
||||
Fts3SegReaderCursor **ppSegcsr /* OUT: Allocated seg-reader cursor */
|
||||
){
|
||||
Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
|
||||
int rc; /* Return code */
|
||||
Fts3SegReaderArray *pArray = 0; /* Array object to build */
|
||||
Fts3SegReader *pReader = 0; /* Seg-reader to add to pArray */
|
||||
sqlite3_stmt *pStmt = 0; /* SQL statement to scan %_segdir table */
|
||||
int iAge = 0; /* Used to assign ages to segments */
|
||||
Fts3SegReaderCursor *pSegcsr; /* Object to allocate and return */
|
||||
int rc = SQLITE_NOMEM; /* Return code */
|
||||
|
||||
/* Allocate a seg-reader to scan the pending terms, if any. */
|
||||
rc = sqlite3Fts3SegReaderPending(p, zTerm, nTerm, isPrefix, &pReader);
|
||||
if( rc==SQLITE_OK && pReader ) {
|
||||
rc = fts3SegReaderArrayAdd(&pArray, pReader);
|
||||
}
|
||||
|
||||
/* Loop through the entire %_segdir table. For each segment, create a
|
||||
** Fts3SegReader to iterate through the subset of the segment leaves
|
||||
** that may contain a term that matches zTerm/nTerm. For non-prefix
|
||||
** searches, this is always a single leaf. For prefix searches, this
|
||||
** may be a contiguous block of leaves.
|
||||
*/
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3Fts3AllSegdirs(p, &pStmt);
|
||||
}
|
||||
while( rc==SQLITE_OK && SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){
|
||||
Fts3SegReader *pNew = 0;
|
||||
int nRoot = sqlite3_column_bytes(pStmt, 4);
|
||||
char const *zRoot = sqlite3_column_blob(pStmt, 4);
|
||||
if( sqlite3_column_int64(pStmt, 1)==0 ){
|
||||
/* The entire segment is stored on the root node (which must be a
|
||||
** leaf). Do not bother inspecting any data in this case, just
|
||||
** create a Fts3SegReader to scan the single leaf.
|
||||
*/
|
||||
rc = sqlite3Fts3SegReaderNew(iAge, 0, 0, 0, zRoot, nRoot, &pNew);
|
||||
}else{
|
||||
sqlite3_int64 i1; /* First leaf that may contain zTerm */
|
||||
sqlite3_int64 i2; /* Final leaf that may contain zTerm */
|
||||
rc = fts3SelectLeaf(p, zTerm, nTerm, zRoot, nRoot, &i1, (isPrefix?&i2:0));
|
||||
if( isPrefix==0 ) i2 = i1;
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3Fts3SegReaderNew(iAge, i1, i2, 0, 0, 0, &pNew);
|
||||
}
|
||||
pSegcsr = sqlite3_malloc(sizeof(Fts3SegReaderCursor));
|
||||
if( pSegcsr ){
|
||||
Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
|
||||
int i;
|
||||
int nCost = 0;
|
||||
rc = sqlite3Fts3SegReaderCursor(
|
||||
p, FTS3_SEGCURSOR_ALL, zTerm, nTerm, isPrefix, 0, pSegcsr);
|
||||
|
||||
for(i=0; rc==SQLITE_OK && i<pSegcsr->nSegment; i++){
|
||||
rc = sqlite3Fts3SegReaderCost(pCsr, pSegcsr->apSegment[i], &nCost);
|
||||
}
|
||||
assert( (pNew==0)==(rc!=SQLITE_OK) );
|
||||
|
||||
/* If a new Fts3SegReader was allocated, add it to the array. */
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts3SegReaderArrayAdd(&pArray, pNew);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3Fts3SegReaderCost(pCsr, pNew, &pArray->nCost);
|
||||
}
|
||||
iAge++;
|
||||
pSegcsr->nCost = nCost;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_DONE ){
|
||||
rc = sqlite3_reset(pStmt);
|
||||
}else{
|
||||
sqlite3_reset(pStmt);
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
fts3SegReaderArrayFree(pArray);
|
||||
pArray = 0;
|
||||
}
|
||||
*ppArray = pArray;
|
||||
*ppSegcsr = pSegcsr;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void fts3SegReaderCursorFree(Fts3SegReaderCursor *pSegcsr){
|
||||
sqlite3Fts3SegReaderFinish(pSegcsr);
|
||||
sqlite3_free(pSegcsr);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function retreives the doclist for the specified term (or term
|
||||
** prefix) from the database.
|
||||
@@ -2081,11 +2240,11 @@ static int fts3TermSelect(
|
||||
char **ppOut /* OUT: Malloced result buffer */
|
||||
){
|
||||
int rc; /* Return code */
|
||||
Fts3SegReaderArray *pArray; /* Seg-reader array for this term */
|
||||
TermSelect tsc; /* Context object for fts3TermSelectCb() */
|
||||
Fts3SegFilter filter; /* Segment term filter configuration */
|
||||
Fts3SegReaderCursor *pSegcsr; /* Seg-reader cursor for this term */
|
||||
TermSelect tsc; /* Context object for fts3TermSelectCb() */
|
||||
Fts3SegFilter filter; /* Segment term filter configuration */
|
||||
|
||||
pArray = pTok->pArray;
|
||||
pSegcsr = pTok->pSegcsr;
|
||||
memset(&tsc, 0, sizeof(TermSelect));
|
||||
tsc.isReqPos = isReqPos;
|
||||
|
||||
@@ -2097,13 +2256,18 @@ static int fts3TermSelect(
|
||||
filter.zTerm = pTok->z;
|
||||
filter.nTerm = pTok->n;
|
||||
|
||||
rc = sqlite3Fts3SegReaderIterate(p, pArray->apSegment, pArray->nSegment,
|
||||
&filter, fts3TermSelectCb, (void *)&tsc
|
||||
);
|
||||
rc = sqlite3Fts3SegReaderStart(p, pSegcsr, &filter);
|
||||
while( SQLITE_OK==rc
|
||||
&& SQLITE_ROW==(rc = sqlite3Fts3SegReaderStep(p, pSegcsr))
|
||||
){
|
||||
rc = fts3TermSelectCb(p, (void *)&tsc,
|
||||
pSegcsr->zTerm, pSegcsr->nTerm, pSegcsr->aDoclist, pSegcsr->nDoclist
|
||||
);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts3TermSelectMerge(&tsc);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
*ppOut = tsc.aaOutput[0];
|
||||
*pnOut = tsc.anOutput[0];
|
||||
@@ -2114,8 +2278,8 @@ static int fts3TermSelect(
|
||||
}
|
||||
}
|
||||
|
||||
fts3SegReaderArrayFree(pArray);
|
||||
pTok->pArray = 0;
|
||||
fts3SegReaderCursorFree(pSegcsr);
|
||||
pTok->pSegcsr = 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -2238,13 +2402,13 @@ static int fts3PhraseSelect(
|
||||
*/
|
||||
for(ii=0; ii<pPhrase->nToken; ii++){
|
||||
Fts3PhraseToken *pTok = &pPhrase->aToken[ii];
|
||||
if( pTok->pArray==0 ){
|
||||
if( pTok->pSegcsr==0 ){
|
||||
if( (pCsr->eEvalmode==FTS3_EVAL_FILTER)
|
||||
|| (pCsr->eEvalmode==FTS3_EVAL_NEXT && pCsr->pDeferred==0)
|
||||
|| (pCsr->eEvalmode==FTS3_EVAL_MATCHINFO && pTok->bFulltext)
|
||||
){
|
||||
rc = fts3TermSegReaderArray(
|
||||
pCsr, pTok->z, pTok->n, pTok->isPrefix, &pTok->pArray
|
||||
rc = fts3TermSegReaderCursor(
|
||||
pCsr, pTok->z, pTok->n, pTok->isPrefix, &pTok->pSegcsr
|
||||
);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}
|
||||
@@ -2275,10 +2439,10 @@ static int fts3PhraseSelect(
|
||||
|
||||
/* Find the remaining token with the lowest cost. */
|
||||
for(jj=0; jj<pPhrase->nToken; jj++){
|
||||
Fts3SegReaderArray *pArray = pPhrase->aToken[jj].pArray;
|
||||
if( pArray && pArray->nCost<nMinCost ){
|
||||
Fts3SegReaderCursor *pSegcsr = pPhrase->aToken[jj].pSegcsr;
|
||||
if( pSegcsr && pSegcsr->nCost<nMinCost ){
|
||||
iTok = jj;
|
||||
nMinCost = pArray->nCost;
|
||||
nMinCost = pSegcsr->nCost;
|
||||
}
|
||||
}
|
||||
pTok = &pPhrase->aToken[iTok];
|
||||
@@ -2297,12 +2461,12 @@ static int fts3PhraseSelect(
|
||||
if( pCsr->eEvalmode==FTS3_EVAL_NEXT && pTok->pDeferred ){
|
||||
rc = fts3DeferredTermSelect(pTok->pDeferred, isTermPos, &nList, &pList);
|
||||
}else{
|
||||
if( pTok->pArray ){
|
||||
if( pTok->pSegcsr ){
|
||||
rc = fts3TermSelect(p, pTok, iCol, isTermPos, &nList, &pList);
|
||||
}
|
||||
pTok->bFulltext = 1;
|
||||
}
|
||||
assert( rc!=SQLITE_OK || pCsr->eEvalmode || pTok->pArray==0 );
|
||||
assert( rc!=SQLITE_OK || pCsr->eEvalmode || pTok->pSegcsr==0 );
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
|
||||
if( isFirst ){
|
||||
@@ -2480,9 +2644,9 @@ static int fts3ExprAllocateSegReaders(
|
||||
|
||||
for(ii=0; rc==SQLITE_OK && ii<pPhrase->nToken; ii++){
|
||||
Fts3PhraseToken *pTok = &pPhrase->aToken[ii];
|
||||
if( pTok->pArray==0 ){
|
||||
rc = fts3TermSegReaderArray(
|
||||
pCsr, pTok->z, pTok->n, pTok->isPrefix, &pTok->pArray
|
||||
if( pTok->pSegcsr==0 ){
|
||||
rc = fts3TermSegReaderCursor(
|
||||
pCsr, pTok->z, pTok->n, pTok->isPrefix, &pTok->pSegcsr
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2506,8 +2670,8 @@ static void fts3ExprFreeSegReaders(Fts3Expr *pExpr){
|
||||
if( pPhrase ){
|
||||
int kk;
|
||||
for(kk=0; kk<pPhrase->nToken; kk++){
|
||||
fts3SegReaderArrayFree(pPhrase->aToken[kk].pArray);
|
||||
pPhrase->aToken[kk].pArray = 0;
|
||||
fts3SegReaderCursorFree(pPhrase->aToken[kk].pSegcsr);
|
||||
pPhrase->aToken[kk].pSegcsr = 0;
|
||||
}
|
||||
}
|
||||
fts3ExprFreeSegReaders(pExpr->pLeft);
|
||||
@@ -2527,10 +2691,8 @@ static int fts3ExprCost(Fts3Expr *pExpr){
|
||||
int ii;
|
||||
nCost = 0;
|
||||
for(ii=0; ii<pPhrase->nToken; ii++){
|
||||
Fts3SegReaderArray *pArray = pPhrase->aToken[ii].pArray;
|
||||
if( pArray ){
|
||||
nCost += pPhrase->aToken[ii].pArray->nCost;
|
||||
}
|
||||
Fts3SegReaderCursor *pSegcsr = pPhrase->aToken[ii].pSegcsr;
|
||||
if( pSegcsr ) nCost += pSegcsr->nCost;
|
||||
}
|
||||
}else{
|
||||
nCost = fts3ExprCost(pExpr->pLeft) + fts3ExprCost(pExpr->pRight);
|
||||
@@ -2872,8 +3034,8 @@ static int fts3FilterMethod(
|
||||
sqlite3_value **apVal /* Arguments for the indexing scheme */
|
||||
){
|
||||
const char *azSql[] = {
|
||||
"SELECT * FROM %Q.'%q_content' WHERE docid = ?", /* non-full-table-scan */
|
||||
"SELECT * FROM %Q.'%q_content'", /* full-table-scan */
|
||||
"SELECT %s FROM %Q.'%q_content' AS x WHERE docid = ?", /* non-full-scan */
|
||||
"SELECT %s FROM %Q.'%q_content' AS x ", /* full-scan */
|
||||
};
|
||||
int rc; /* Return code */
|
||||
char *zSql; /* SQL statement used to access %_content */
|
||||
@@ -2928,7 +3090,8 @@ static int fts3FilterMethod(
|
||||
** full-text query or docid lookup, the statement retrieves a single
|
||||
** row by docid.
|
||||
*/
|
||||
zSql = sqlite3_mprintf(azSql[idxNum==FTS3_FULLSCAN_SEARCH], p->zDb, p->zName);
|
||||
zSql = (char *)azSql[idxNum==FTS3_FULLSCAN_SEARCH];
|
||||
zSql = sqlite3_mprintf(zSql, p->zReadExprlist, p->zDb, p->zName);
|
||||
if( !zSql ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
@@ -3446,6 +3609,9 @@ int sqlite3Fts3Init(sqlite3 *db){
|
||||
sqlite3Fts3IcuTokenizerModule(&pIcu);
|
||||
#endif
|
||||
|
||||
rc = sqlite3Fts3InitAux(db);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
sqlite3Fts3SimpleTokenizerModule(&pSimple);
|
||||
sqlite3Fts3PorterTokenizerModule(&pPorter);
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ typedef struct Fts3PhraseToken Fts3PhraseToken;
|
||||
typedef struct Fts3SegFilter Fts3SegFilter;
|
||||
typedef struct Fts3DeferredToken Fts3DeferredToken;
|
||||
typedef struct Fts3SegReader Fts3SegReader;
|
||||
typedef struct Fts3SegReaderArray Fts3SegReaderArray;
|
||||
typedef struct Fts3SegReaderCursor Fts3SegReaderCursor;
|
||||
|
||||
/*
|
||||
** A connection to a fulltext index is an instance of the following
|
||||
@@ -130,6 +130,9 @@ struct Fts3Table {
|
||||
*/
|
||||
sqlite3_stmt *aStmt[24];
|
||||
|
||||
char *zReadExprlist;
|
||||
char *zWriteExprlist;
|
||||
|
||||
int nNodeSize; /* Soft limit for node size */
|
||||
u8 bHasStat; /* True if %_stat table exists */
|
||||
u8 bHasDocsize; /* True if %_docsize table exists */
|
||||
@@ -217,7 +220,7 @@ struct Fts3PhraseToken {
|
||||
int n; /* Number of bytes in buffer z */
|
||||
int isPrefix; /* True if token ends with a "*" character */
|
||||
int bFulltext; /* True if full-text index was used */
|
||||
Fts3SegReaderArray *pArray; /* Segment-reader for this token */
|
||||
Fts3SegReaderCursor *pSegcsr; /* Segment-reader for this token */
|
||||
Fts3DeferredToken *pDeferred; /* Deferred token object for this token */
|
||||
};
|
||||
|
||||
@@ -285,12 +288,8 @@ int sqlite3Fts3SegReaderNew(int, sqlite3_int64,
|
||||
sqlite3_int64, sqlite3_int64, const char *, int, Fts3SegReader**);
|
||||
int sqlite3Fts3SegReaderPending(Fts3Table*,const char*,int,int,Fts3SegReader**);
|
||||
void sqlite3Fts3SegReaderFree(Fts3SegReader *);
|
||||
int sqlite3Fts3SegReaderIterate(
|
||||
Fts3Table *, Fts3SegReader **, int, Fts3SegFilter *,
|
||||
int (*)(Fts3Table *, void *, char *, int, char *, int), void *
|
||||
);
|
||||
int sqlite3Fts3SegReaderCost(Fts3Cursor *, Fts3SegReader *, int *);
|
||||
int sqlite3Fts3AllSegdirs(Fts3Table*, sqlite3_stmt **);
|
||||
int sqlite3Fts3AllSegdirs(Fts3Table*, int, sqlite3_stmt **);
|
||||
int sqlite3Fts3ReadLock(Fts3Table *);
|
||||
int sqlite3Fts3ReadBlock(Fts3Table*, sqlite3_int64, char **, int*);
|
||||
|
||||
@@ -302,14 +301,23 @@ int sqlite3Fts3DeferToken(Fts3Cursor *, Fts3PhraseToken *, int);
|
||||
int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *);
|
||||
void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *);
|
||||
char *sqlite3Fts3DeferredDoclist(Fts3DeferredToken *, int *);
|
||||
|
||||
void sqlite3Fts3SegmentsClose(Fts3Table *);
|
||||
|
||||
#define FTS3_SEGCURSOR_PENDING -1
|
||||
#define FTS3_SEGCURSOR_ALL -2
|
||||
|
||||
int sqlite3Fts3SegReaderStart(Fts3Table*, Fts3SegReaderCursor*, Fts3SegFilter*);
|
||||
int sqlite3Fts3SegReaderStep(Fts3Table *, Fts3SegReaderCursor *);
|
||||
void sqlite3Fts3SegReaderFinish(Fts3SegReaderCursor *);
|
||||
int sqlite3Fts3SegReaderCursor(
|
||||
Fts3Table *, int, const char *, int, int, int, Fts3SegReaderCursor *);
|
||||
|
||||
/* 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
|
||||
|
||||
/* Type passed as 4th argument to SegmentReaderIterate() */
|
||||
struct Fts3SegFilter {
|
||||
@@ -319,6 +327,25 @@ struct Fts3SegFilter {
|
||||
int flags;
|
||||
};
|
||||
|
||||
struct Fts3SegReaderCursor {
|
||||
/* 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 */
|
||||
|
||||
/* Cost of running this iterator. Used by fts3.c only. */
|
||||
int nCost;
|
||||
|
||||
/* 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 *);
|
||||
@@ -355,4 +382,7 @@ void sqlite3Fts3ExprFree(Fts3Expr *);
|
||||
int sqlite3Fts3ExprInitTestInterface(sqlite3 *db);
|
||||
#endif
|
||||
|
||||
/* fts3_aux.c */
|
||||
int sqlite3Fts3InitAux(sqlite3 *db);
|
||||
|
||||
#endif /* _FTSINT_H */
|
||||
|
||||
@@ -0,0 +1,470 @@
|
||||
/*
|
||||
** 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.
|
||||
**
|
||||
******************************************************************************
|
||||
**
|
||||
*/
|
||||
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
|
||||
#include "fts3Int.h"
|
||||
#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 */
|
||||
Fts3SegReaderCursor 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;
|
||||
|
||||
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);
|
||||
|
||||
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, 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 */
|
||||
};
|
||||
int rc; /* Return code */
|
||||
|
||||
rc = sqlite3_create_module(db, "fts4aux", &fts3aux_module, 0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */
|
||||
@@ -880,13 +880,13 @@ static int fts3ExprLocalHitsCb(
|
||||
void *pCtx /* Pointer to MatchInfo structure */
|
||||
){
|
||||
MatchInfo *p = (MatchInfo *)pCtx;
|
||||
int iStart = iPhrase * p->nCol * 3;
|
||||
int i;
|
||||
|
||||
for(i=0; i<p->nCol; i++) p->aMatchinfo[iStart+i*3] = 0;
|
||||
|
||||
if( pExpr->aDoclist ){
|
||||
char *pCsr;
|
||||
int iStart = 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);
|
||||
if( pCsr ){
|
||||
@@ -960,6 +960,7 @@ static int fts3MatchinfoSelectDoctotal(
|
||||
|
||||
a = sqlite3_column_blob(pStmt, 0);
|
||||
a += sqlite3Fts3GetVarint(a, &nDoc);
|
||||
if( nDoc==0 ) return SQLITE_CORRUPT;
|
||||
*pnDoc = (u32)nDoc;
|
||||
|
||||
if( paLen ) *paLen = a;
|
||||
@@ -1166,9 +1167,11 @@ static int fts3MatchinfoValues(
|
||||
if( rc==SQLITE_OK ){
|
||||
int iCol;
|
||||
for(iCol=0; iCol<pInfo->nCol; iCol++){
|
||||
u32 iVal;
|
||||
sqlite3_int64 nToken;
|
||||
a += sqlite3Fts3GetVarint(a, &nToken);
|
||||
pInfo->aMatchinfo[iCol] = (u32)(((u32)(nToken&0xffffffff)+nDoc/2)/nDoc);
|
||||
iVal = (u32)(((u32)(nToken&0xffffffff)+nDoc/2)/nDoc);
|
||||
pInfo->aMatchinfo[iCol] = iVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ static int fts3SqlStmt(
|
||||
/* 4 */ "DELETE FROM %Q.'%q_segdir'",
|
||||
/* 5 */ "DELETE FROM %Q.'%q_docsize'",
|
||||
/* 6 */ "DELETE FROM %Q.'%q_stat'",
|
||||
/* 7 */ "SELECT * FROM %Q.'%q_content' WHERE rowid=?",
|
||||
/* 7 */ "SELECT %s FROM %Q.'%q_content' AS x WHERE rowid=?",
|
||||
/* 8 */ "SELECT (SELECT max(idx) FROM %Q.'%q_segdir' WHERE level = ?) + 1",
|
||||
/* 9 */ "INSERT INTO %Q.'%q_segments'(blockid, block) VALUES(?, ?)",
|
||||
/* 10 */ "SELECT coalesce((SELECT max(blockid) FROM %Q.'%q_segments') + 1, 1)",
|
||||
@@ -229,7 +229,7 @@ static int fts3SqlStmt(
|
||||
|
||||
/* 16 */ "DELETE FROM %Q.'%q_segdir' WHERE level = ?",
|
||||
/* 17 */ "DELETE FROM %Q.'%q_segments' WHERE blockid BETWEEN ? AND ?",
|
||||
/* 18 */ "INSERT INTO %Q.'%q_content' VALUES(%z)",
|
||||
/* 18 */ "INSERT INTO %Q.'%q_content' VALUES(%s)",
|
||||
/* 19 */ "DELETE FROM %Q.'%q_docsize' WHERE docid = ?",
|
||||
/* 20 */ "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)",
|
||||
/* 21 */ "SELECT size FROM %Q.'%q_docsize' WHERE docid=?",
|
||||
@@ -246,20 +246,9 @@ static int fts3SqlStmt(
|
||||
if( !pStmt ){
|
||||
char *zSql;
|
||||
if( eStmt==SQL_CONTENT_INSERT ){
|
||||
int i; /* Iterator variable */
|
||||
char *zVarlist; /* The "?, ?, ..." string */
|
||||
zVarlist = (char *)sqlite3_malloc(2*p->nColumn+2);
|
||||
if( !zVarlist ){
|
||||
*pp = 0;
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
zVarlist[0] = '?';
|
||||
zVarlist[p->nColumn*2+1] = '\0';
|
||||
for(i=1; i<=p->nColumn; i++){
|
||||
zVarlist[i*2-1] = ',';
|
||||
zVarlist[i*2] = '?';
|
||||
}
|
||||
zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, zVarlist);
|
||||
zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist);
|
||||
}else if( eStmt==SQL_SELECT_CONTENT_BY_ROWID ){
|
||||
zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist, p->zDb, p->zName);
|
||||
}else{
|
||||
zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName);
|
||||
}
|
||||
@@ -300,7 +289,7 @@ static int fts3SelectDocsize(
|
||||
sqlite3_bind_int64(pStmt, 1, iDocid);
|
||||
}
|
||||
rc = sqlite3_step(pStmt);
|
||||
if( rc!=SQLITE_ROW ){
|
||||
if( rc!=SQLITE_ROW || sqlite3_column_type(pStmt, 0)!=SQLITE_BLOB ){
|
||||
rc = sqlite3_reset(pStmt);
|
||||
if( rc==SQLITE_OK ) rc = SQLITE_CORRUPT;
|
||||
pStmt = 0;
|
||||
@@ -401,8 +390,17 @@ int sqlite3Fts3ReadLock(Fts3Table *p){
|
||||
** 3: end_block
|
||||
** 4: root
|
||||
*/
|
||||
int sqlite3Fts3AllSegdirs(Fts3Table *p, sqlite3_stmt **ppStmt){
|
||||
return fts3SqlStmt(p, SQL_SELECT_ALL_LEVEL, ppStmt, 0);
|
||||
int sqlite3Fts3AllSegdirs(Fts3Table *p, int iLevel, sqlite3_stmt **ppStmt){
|
||||
int rc;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
if( iLevel<0 ){
|
||||
rc = fts3SqlStmt(p, SQL_SELECT_ALL_LEVEL, &pStmt, 0);
|
||||
}else{
|
||||
rc = fts3SqlStmt(p, SQL_SELECT_LEVEL, &pStmt, 0);
|
||||
if( rc==SQLITE_OK ) sqlite3_bind_int(pStmt, 1, iLevel);
|
||||
}
|
||||
*ppStmt = pStmt;
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
@@ -1104,16 +1102,18 @@ int sqlite3Fts3SegReaderCost(
|
||||
sqlite3_stmt *pStmt;
|
||||
sqlite3_int64 nDoc = 0;
|
||||
sqlite3_int64 nByte = 0;
|
||||
const char *pEnd;
|
||||
const char *a;
|
||||
|
||||
rc = sqlite3Fts3SelectDoctotal(p, &pStmt);
|
||||
if( rc ) return rc;
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
a = sqlite3_column_blob(pStmt, 0);
|
||||
if( a ){
|
||||
const char *pEnd = &a[sqlite3_column_bytes(pStmt, 0)];
|
||||
a += sqlite3Fts3GetVarint(a, &nDoc);
|
||||
while( a<pEnd ){
|
||||
a += sqlite3Fts3GetVarint(a, &nByte);
|
||||
}
|
||||
assert( a );
|
||||
|
||||
pEnd = &a[sqlite3_column_bytes(pStmt, 0)];
|
||||
a += sqlite3Fts3GetVarint(a, &nDoc);
|
||||
while( a<pEnd ){
|
||||
a += sqlite3Fts3GetVarint(a, &nByte);
|
||||
}
|
||||
if( nDoc==0 || nByte==0 ){
|
||||
sqlite3_reset(pStmt);
|
||||
@@ -1303,42 +1303,6 @@ int sqlite3Fts3SegReaderPending(
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** The second argument to this function is expected to be a statement of
|
||||
** the form:
|
||||
**
|
||||
** SELECT
|
||||
** idx, -- col 0
|
||||
** start_block, -- col 1
|
||||
** leaves_end_block, -- col 2
|
||||
** end_block, -- col 3
|
||||
** root -- col 4
|
||||
** FROM %_segdir ...
|
||||
**
|
||||
** This function allocates and initializes a Fts3SegReader structure to
|
||||
** iterate through the terms stored in the segment identified by the
|
||||
** current row that pStmt is pointing to.
|
||||
**
|
||||
** If successful, the Fts3SegReader is left pointing to the first term
|
||||
** in the segment and SQLITE_OK is returned. Otherwise, an SQLite error
|
||||
** code is returned.
|
||||
*/
|
||||
static int fts3SegReaderNew(
|
||||
sqlite3_stmt *pStmt, /* See above */
|
||||
int iAge, /* Segment "age". */
|
||||
Fts3SegReader **ppReader /* OUT: Allocated Fts3SegReader */
|
||||
){
|
||||
return sqlite3Fts3SegReaderNew(iAge,
|
||||
sqlite3_column_int64(pStmt, 1),
|
||||
sqlite3_column_int64(pStmt, 2),
|
||||
sqlite3_column_int64(pStmt, 3),
|
||||
sqlite3_column_blob(pStmt, 4),
|
||||
sqlite3_column_bytes(pStmt, 4),
|
||||
ppReader
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
** Compare the entries pointed to by two Fts3SegReader structures.
|
||||
** Comparison is as follows:
|
||||
@@ -1943,25 +1907,6 @@ static int fts3IsEmpty(Fts3Table *p, sqlite3_value **apVal, int *pisEmpty){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Set *pnSegment to the number of segments of level iLevel in the database.
|
||||
**
|
||||
** Return SQLITE_OK if successful, or an SQLite error code if not.
|
||||
*/
|
||||
static int fts3SegmentCount(Fts3Table *p, int iLevel, int *pnSegment){
|
||||
sqlite3_stmt *pStmt;
|
||||
int rc;
|
||||
|
||||
assert( iLevel>=0 );
|
||||
rc = fts3SqlStmt(p, SQL_SELECT_LEVEL_COUNT, &pStmt, 0);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
sqlite3_bind_int(pStmt, 1, iLevel);
|
||||
if( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
*pnSegment = sqlite3_column_int(pStmt, 0);
|
||||
}
|
||||
return sqlite3_reset(pStmt);
|
||||
}
|
||||
|
||||
/*
|
||||
** Set *pnSegment to the total number of segments in the database. Set
|
||||
** *pnMax to the largest segment level in the database (segment levels
|
||||
@@ -2020,15 +1965,18 @@ static int fts3DeleteSegdir(
|
||||
return rc;
|
||||
}
|
||||
|
||||
if( iLevel>=0 ){
|
||||
if( iLevel==FTS3_SEGCURSOR_ALL ){
|
||||
fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGDIR, 0);
|
||||
}else if( iLevel==FTS3_SEGCURSOR_PENDING ){
|
||||
sqlite3Fts3PendingTermsClear(p);
|
||||
}else{
|
||||
assert( iLevel>=0 );
|
||||
rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_BY_LEVEL, &pDelete, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_bind_int(pDelete, 1, iLevel);
|
||||
sqlite3_step(pDelete);
|
||||
rc = sqlite3_reset(pDelete);
|
||||
}
|
||||
}else{
|
||||
fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGDIR, 0);
|
||||
}
|
||||
|
||||
return rc;
|
||||
@@ -2077,85 +2025,15 @@ static void fts3ColumnFilter(
|
||||
*pnList = nList;
|
||||
}
|
||||
|
||||
/*
|
||||
** sqlite3Fts3SegReaderIterate() callback used when merging multiple
|
||||
** segments to create a single, larger segment.
|
||||
*/
|
||||
static int fts3MergeCallback(
|
||||
Fts3Table *p, /* FTS3 Virtual table handle */
|
||||
void *pContext, /* Pointer to SegmentWriter* to write with */
|
||||
char *zTerm, /* Term to write to the db */
|
||||
int nTerm, /* Number of bytes in zTerm */
|
||||
char *aDoclist, /* Doclist associated with zTerm */
|
||||
int nDoclist /* Number of bytes in doclist */
|
||||
){
|
||||
SegmentWriter **ppW = (SegmentWriter **)pContext;
|
||||
return fts3SegWriterAdd(p, ppW, 1, zTerm, nTerm, aDoclist, nDoclist);
|
||||
}
|
||||
|
||||
/*
|
||||
** sqlite3Fts3SegReaderIterate() callback used when flushing the contents
|
||||
** of the pending-terms hash table to the database.
|
||||
*/
|
||||
static int fts3FlushCallback(
|
||||
Fts3Table *p, /* FTS3 Virtual table handle */
|
||||
void *pContext, /* Pointer to SegmentWriter* to write with */
|
||||
char *zTerm, /* Term to write to the db */
|
||||
int nTerm, /* Number of bytes in zTerm */
|
||||
char *aDoclist, /* Doclist associated with zTerm */
|
||||
int nDoclist /* Number of bytes in doclist */
|
||||
){
|
||||
SegmentWriter **ppW = (SegmentWriter **)pContext;
|
||||
return fts3SegWriterAdd(p, ppW, 0, zTerm, nTerm, aDoclist, nDoclist);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is used to iterate through a contiguous set of terms
|
||||
** stored in the full-text index. It merges data contained in one or
|
||||
** more segments to support this.
|
||||
**
|
||||
** The second argument is passed an array of pointers to SegReader objects
|
||||
** allocated with sqlite3Fts3SegReaderNew(). This function merges the range
|
||||
** of terms selected by each SegReader. If a single term is present in
|
||||
** more than one segment, the associated doclists are merged. For each
|
||||
** term and (possibly merged) doclist in the merged range, the callback
|
||||
** function xFunc is invoked with its arguments set as follows.
|
||||
**
|
||||
** arg 0: Copy of 'p' parameter passed to this function
|
||||
** arg 1: Copy of 'pContext' parameter passed to this function
|
||||
** arg 2: Pointer to buffer containing term
|
||||
** arg 3: Size of arg 2 buffer in bytes
|
||||
** arg 4: Pointer to buffer containing doclist
|
||||
** arg 5: Size of arg 2 buffer in bytes
|
||||
**
|
||||
** The 4th argument to this function is a pointer to a structure of type
|
||||
** Fts3SegFilter, defined in fts3Int.h. The contents of this structure
|
||||
** further restrict the range of terms that callbacks are made for and
|
||||
** modify the behaviour of this function. See comments above structure
|
||||
** definition for details.
|
||||
*/
|
||||
int sqlite3Fts3SegReaderIterate(
|
||||
int sqlite3Fts3SegReaderStart(
|
||||
Fts3Table *p, /* Virtual table handle */
|
||||
Fts3SegReader **apSegment, /* Array of Fts3SegReader objects */
|
||||
int nSegment, /* Size of apSegment array */
|
||||
Fts3SegFilter *pFilter, /* Restrictions on range of iteration */
|
||||
int (*xFunc)(Fts3Table *, void *, char *, int, char *, int), /* Callback */
|
||||
void *pContext /* Callback context (2nd argument) */
|
||||
Fts3SegReaderCursor *pCsr, /* Cursor object */
|
||||
Fts3SegFilter *pFilter /* Restrictions on range of iteration */
|
||||
){
|
||||
int i; /* Iterator variable */
|
||||
char *aBuffer = 0; /* Buffer to merge doclists in */
|
||||
int nAlloc = 0; /* Allocated size of aBuffer buffer */
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
int i;
|
||||
|
||||
int isIgnoreEmpty = (pFilter->flags & FTS3_SEGMENT_IGNORE_EMPTY);
|
||||
int isRequirePos = (pFilter->flags & FTS3_SEGMENT_REQUIRE_POS);
|
||||
int isColFilter = (pFilter->flags & FTS3_SEGMENT_COLUMN_FILTER);
|
||||
int isPrefix = (pFilter->flags & FTS3_SEGMENT_PREFIX);
|
||||
|
||||
/* If there are zero segments, this function is a no-op. This scenario
|
||||
** comes about only when reading from an empty database.
|
||||
*/
|
||||
if( nSegment==0 ) goto finished;
|
||||
/* Initialize the cursor object */
|
||||
pCsr->pFilter = pFilter;
|
||||
|
||||
/* If the Fts3SegFilter defines a specific term (or term prefix) to search
|
||||
** for, then advance each segment iterator until it points to a term of
|
||||
@@ -2163,21 +2041,59 @@ int sqlite3Fts3SegReaderIterate(
|
||||
** unnecessary merge/sort operations for the case where single segment
|
||||
** b-tree leaf nodes contain more than one term.
|
||||
*/
|
||||
for(i=0; i<nSegment; i++){
|
||||
for(i=0; i<pCsr->nSegment; i++){
|
||||
int nTerm = pFilter->nTerm;
|
||||
const char *zTerm = pFilter->zTerm;
|
||||
Fts3SegReader *pSeg = apSegment[i];
|
||||
Fts3SegReader *pSeg = pCsr->apSegment[i];
|
||||
do {
|
||||
rc = fts3SegReaderNext(p, pSeg);
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
int rc = fts3SegReaderNext(p, pSeg);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}while( zTerm && fts3SegReaderTermCmp(pSeg, zTerm, nTerm)<0 );
|
||||
}
|
||||
fts3SegReaderSort(
|
||||
pCsr->apSegment, pCsr->nSegment, pCsr->nSegment, fts3SegReaderCmp);
|
||||
|
||||
fts3SegReaderSort(apSegment, nSegment, nSegment, fts3SegReaderCmp);
|
||||
while( apSegment[0]->aNode ){
|
||||
int nTerm = apSegment[0]->nTerm;
|
||||
char *zTerm = apSegment[0]->zTerm;
|
||||
int nMerge = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
int sqlite3Fts3SegReaderStep(
|
||||
Fts3Table *p, /* Virtual table handle */
|
||||
Fts3SegReaderCursor *pCsr /* Cursor object */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
int isIgnoreEmpty = (pCsr->pFilter->flags & FTS3_SEGMENT_IGNORE_EMPTY);
|
||||
int isRequirePos = (pCsr->pFilter->flags & FTS3_SEGMENT_REQUIRE_POS);
|
||||
int isColFilter = (pCsr->pFilter->flags & FTS3_SEGMENT_COLUMN_FILTER);
|
||||
int isPrefix = (pCsr->pFilter->flags & FTS3_SEGMENT_PREFIX);
|
||||
int isScan = (pCsr->pFilter->flags & FTS3_SEGMENT_SCAN);
|
||||
|
||||
Fts3SegReader **apSegment = pCsr->apSegment;
|
||||
int nSegment = pCsr->nSegment;
|
||||
Fts3SegFilter *pFilter = pCsr->pFilter;
|
||||
|
||||
if( pCsr->nSegment==0 ) return SQLITE_OK;
|
||||
|
||||
do {
|
||||
int nMerge;
|
||||
int i;
|
||||
|
||||
/* Advance the first pCsr->nAdvance entries in the apSegment[] array
|
||||
** forward. Then sort the list in order of current term again.
|
||||
*/
|
||||
for(i=0; i<pCsr->nAdvance; i++){
|
||||
rc = fts3SegReaderNext(p, apSegment[i]);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}
|
||||
fts3SegReaderSort(apSegment, nSegment, pCsr->nAdvance, fts3SegReaderCmp);
|
||||
pCsr->nAdvance = 0;
|
||||
|
||||
/* If all the seg-readers are at EOF, we're finished. return SQLITE_OK. */
|
||||
assert( rc==SQLITE_OK );
|
||||
if( apSegment[0]->aNode==0 ) break;
|
||||
|
||||
pCsr->nTerm = apSegment[0]->nTerm;
|
||||
pCsr->zTerm = apSegment[0]->zTerm;
|
||||
|
||||
/* If this is a prefix-search, and if the term that apSegment[0] points
|
||||
** to does not share a suffix with pFilter->zTerm/nTerm, then all
|
||||
@@ -2186,35 +2102,36 @@ int sqlite3Fts3SegReaderIterate(
|
||||
** Similarly, if this is a search for an exact match, and the first term
|
||||
** of segment apSegment[0] is not a match, exit early.
|
||||
*/
|
||||
if( pFilter->zTerm ){
|
||||
if( nTerm<pFilter->nTerm
|
||||
|| (!isPrefix && nTerm>pFilter->nTerm)
|
||||
|| memcmp(zTerm, pFilter->zTerm, pFilter->nTerm)
|
||||
){
|
||||
goto finished;
|
||||
if( pFilter->zTerm && !isScan ){
|
||||
if( pCsr->nTerm<pFilter->nTerm
|
||||
|| (!isPrefix && pCsr->nTerm>pFilter->nTerm)
|
||||
|| memcmp(pCsr->zTerm, pFilter->zTerm, pFilter->nTerm)
|
||||
){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nMerge = 1;
|
||||
while( nMerge<nSegment
|
||||
&& apSegment[nMerge]->aNode
|
||||
&& apSegment[nMerge]->nTerm==nTerm
|
||||
&& 0==memcmp(zTerm, apSegment[nMerge]->zTerm, nTerm)
|
||||
&& apSegment[nMerge]->nTerm==pCsr->nTerm
|
||||
&& 0==memcmp(pCsr->zTerm, apSegment[nMerge]->zTerm, pCsr->nTerm)
|
||||
){
|
||||
nMerge++;
|
||||
}
|
||||
|
||||
assert( isIgnoreEmpty || (isRequirePos && !isColFilter) );
|
||||
if( nMerge==1 && !isIgnoreEmpty ){
|
||||
Fts3SegReader *p0 = apSegment[0];
|
||||
rc = xFunc(p, pContext, zTerm, nTerm, p0->aDoclist, p0->nDoclist);
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
pCsr->aDoclist = apSegment[0]->aDoclist;
|
||||
pCsr->nDoclist = apSegment[0]->nDoclist;
|
||||
rc = SQLITE_ROW;
|
||||
}else{
|
||||
int nDoclist = 0; /* Size of doclist */
|
||||
sqlite3_int64 iPrev = 0; /* Previous docid stored in doclist */
|
||||
|
||||
/* The current term of the first nMerge entries in the array
|
||||
** of Fts3SegReader objects is the same. The doclists must be merged
|
||||
** and a single term added to the new segment.
|
||||
** and a single term returned with the merged doclist.
|
||||
*/
|
||||
for(i=0; i<nMerge; i++){
|
||||
fts3SegReaderFirstDocid(apSegment[i]);
|
||||
@@ -2242,54 +2159,57 @@ int sqlite3Fts3SegReaderIterate(
|
||||
|
||||
if( !isIgnoreEmpty || nList>0 ){
|
||||
nByte = sqlite3Fts3VarintLen(iDocid-iPrev) + (isRequirePos?nList+1:0);
|
||||
if( nDoclist+nByte>nAlloc ){
|
||||
if( nDoclist+nByte>pCsr->nBuffer ){
|
||||
char *aNew;
|
||||
nAlloc = (nDoclist+nByte)*2;
|
||||
aNew = sqlite3_realloc(aBuffer, nAlloc);
|
||||
pCsr->nBuffer = (nDoclist+nByte)*2;
|
||||
aNew = sqlite3_realloc(pCsr->aBuffer, pCsr->nBuffer);
|
||||
if( !aNew ){
|
||||
rc = SQLITE_NOMEM;
|
||||
goto finished;
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
aBuffer = aNew;
|
||||
pCsr->aBuffer = aNew;
|
||||
}
|
||||
nDoclist += sqlite3Fts3PutVarint(&aBuffer[nDoclist], iDocid-iPrev);
|
||||
nDoclist += sqlite3Fts3PutVarint(
|
||||
&pCsr->aBuffer[nDoclist], iDocid-iPrev
|
||||
);
|
||||
iPrev = iDocid;
|
||||
if( isRequirePos ){
|
||||
memcpy(&aBuffer[nDoclist], pList, nList);
|
||||
memcpy(&pCsr->aBuffer[nDoclist], pList, nList);
|
||||
nDoclist += nList;
|
||||
aBuffer[nDoclist++] = '\0';
|
||||
pCsr->aBuffer[nDoclist++] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
fts3SegReaderSort(apSegment, nMerge, j, fts3SegReaderDoclistCmp);
|
||||
}
|
||||
|
||||
if( nDoclist>0 ){
|
||||
rc = xFunc(p, pContext, zTerm, nTerm, aBuffer, nDoclist);
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
pCsr->aDoclist = pCsr->aBuffer;
|
||||
pCsr->nDoclist = nDoclist;
|
||||
rc = SQLITE_ROW;
|
||||
}
|
||||
}
|
||||
pCsr->nAdvance = nMerge;
|
||||
}while( rc==SQLITE_OK );
|
||||
|
||||
/* If there is a term specified to filter on, and this is not a prefix
|
||||
** search, return now. The callback that corresponds to the required
|
||||
** term (if such a term exists in the index) has already been made.
|
||||
*/
|
||||
if( pFilter->zTerm && !isPrefix ){
|
||||
goto finished;
|
||||
}
|
||||
|
||||
for(i=0; i<nMerge; i++){
|
||||
rc = fts3SegReaderNext(p, apSegment[i]);
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
}
|
||||
fts3SegReaderSort(apSegment, nSegment, nMerge, fts3SegReaderCmp);
|
||||
}
|
||||
|
||||
finished:
|
||||
sqlite3_free(aBuffer);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void sqlite3Fts3SegReaderFinish(
|
||||
Fts3SegReaderCursor *pCsr /* Cursor object */
|
||||
){
|
||||
if( pCsr ){
|
||||
int i;
|
||||
for(i=0; i<pCsr->nSegment; i++){
|
||||
sqlite3Fts3SegReaderFree(pCsr->apSegment[i]);
|
||||
}
|
||||
sqlite3_free(pCsr->apSegment);
|
||||
sqlite3_free(pCsr->aBuffer);
|
||||
|
||||
pCsr->nSegment = 0;
|
||||
pCsr->apSegment = 0;
|
||||
pCsr->aBuffer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Merge all level iLevel segments in the database into a single
|
||||
** iLevel+1 segment. Or, if iLevel<0, merge all segments into a
|
||||
@@ -2302,100 +2222,60 @@ int sqlite3Fts3SegReaderIterate(
|
||||
** an SQLite error code is returned.
|
||||
*/
|
||||
static int fts3SegmentMerge(Fts3Table *p, int iLevel){
|
||||
int i; /* Iterator variable */
|
||||
int rc; /* Return code */
|
||||
int iIdx; /* Index of new segment */
|
||||
int iIdx = 0; /* Index of new segment */
|
||||
int iNewLevel = 0; /* Level to create new segment at */
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
SegmentWriter *pWriter = 0;
|
||||
int nSegment = 0; /* Number of segments being merged */
|
||||
Fts3SegReader **apSegment = 0; /* Array of Segment iterators */
|
||||
Fts3SegReader *pPending = 0; /* Iterator for pending-terms */
|
||||
SegmentWriter *pWriter = 0; /* Used to write the new, merged, segment */
|
||||
Fts3SegFilter filter; /* Segment term filter condition */
|
||||
Fts3SegReaderCursor csr; /* Cursor to iterate through level(s) */
|
||||
|
||||
if( iLevel<0 ){
|
||||
rc = sqlite3Fts3SegReaderCursor(p, iLevel, 0, 0, 1, 0, &csr);
|
||||
if( rc!=SQLITE_OK || csr.nSegment==0 ) goto finished;
|
||||
|
||||
if( iLevel==FTS3_SEGCURSOR_ALL ){
|
||||
/* This call is to merge all segments in the database to a single
|
||||
** segment. The level of the new segment is equal to the the numerically
|
||||
** greatest segment level currently present in the database. The index
|
||||
** of the new segment is always 0.
|
||||
*/
|
||||
iIdx = 0;
|
||||
rc = sqlite3Fts3SegReaderPending(p, 0, 0, 1, &pPending);
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
rc = fts3SegmentCountMax(p, &nSegment, &iNewLevel);
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
nSegment += (pPending!=0);
|
||||
if( nSegment<=1 ){
|
||||
return SQLITE_DONE;
|
||||
** of the new segment is always 0. */
|
||||
int nDummy; /* TODO: Remove this */
|
||||
if( csr.nSegment==1 ){
|
||||
rc = SQLITE_DONE;
|
||||
goto finished;
|
||||
}
|
||||
rc = fts3SegmentCountMax(p, &nDummy, &iNewLevel);
|
||||
}else{
|
||||
/* This call is to merge all segments at level iLevel. Find the next
|
||||
** available segment index at level iLevel+1. The call to
|
||||
** fts3AllocateSegdirIdx() will merge the segments at level iLevel+1 to
|
||||
** a single iLevel+2 segment if necessary.
|
||||
*/
|
||||
** a single iLevel+2 segment if necessary. */
|
||||
iNewLevel = iLevel+1;
|
||||
rc = fts3AllocateSegdirIdx(p, iNewLevel, &iIdx);
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
rc = fts3SegmentCount(p, iLevel, &nSegment);
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
}
|
||||
assert( nSegment>0 );
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
assert( csr.nSegment>0 );
|
||||
assert( iNewLevel>=0 );
|
||||
|
||||
/* Allocate space for an array of pointers to segment iterators. */
|
||||
apSegment = (Fts3SegReader**)sqlite3_malloc(sizeof(Fts3SegReader *)*nSegment);
|
||||
if( !apSegment ){
|
||||
rc = SQLITE_NOMEM;
|
||||
goto finished;
|
||||
}
|
||||
memset(apSegment, 0, sizeof(Fts3SegReader *)*nSegment);
|
||||
|
||||
/* Allocate a Fts3SegReader structure for each segment being merged. A
|
||||
** Fts3SegReader stores the state data required to iterate through all
|
||||
** entries on all leaves of a single segment.
|
||||
*/
|
||||
assert( SQL_SELECT_LEVEL+1==SQL_SELECT_ALL_LEVEL);
|
||||
rc = fts3SqlStmt(p, SQL_SELECT_LEVEL+(iLevel<0), &pStmt, 0);
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
sqlite3_bind_int(pStmt, 1, iLevel);
|
||||
for(i=0; SQLITE_ROW==(sqlite3_step(pStmt)); i++){
|
||||
rc = fts3SegReaderNew(pStmt, i, &apSegment[i]);
|
||||
if( rc!=SQLITE_OK ){
|
||||
goto finished;
|
||||
}
|
||||
}
|
||||
rc = sqlite3_reset(pStmt);
|
||||
if( pPending ){
|
||||
apSegment[i] = pPending;
|
||||
pPending = 0;
|
||||
}
|
||||
pStmt = 0;
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
|
||||
memset(&filter, 0, sizeof(Fts3SegFilter));
|
||||
filter.flags = FTS3_SEGMENT_REQUIRE_POS;
|
||||
filter.flags |= (iLevel<0 ? FTS3_SEGMENT_IGNORE_EMPTY : 0);
|
||||
rc = sqlite3Fts3SegReaderIterate(p, apSegment, nSegment,
|
||||
&filter, fts3MergeCallback, (void *)&pWriter
|
||||
);
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
filter.flags |= (iLevel==FTS3_SEGCURSOR_ALL ? FTS3_SEGMENT_IGNORE_EMPTY : 0);
|
||||
|
||||
rc = fts3DeleteSegdir(p, iLevel, apSegment, nSegment);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts3SegWriterFlush(p, pWriter, iNewLevel, iIdx);
|
||||
rc = sqlite3Fts3SegReaderStart(p, &csr, &filter);
|
||||
while( SQLITE_OK==rc ){
|
||||
rc = sqlite3Fts3SegReaderStep(p, &csr);
|
||||
if( rc!=SQLITE_ROW ) break;
|
||||
rc = fts3SegWriterAdd(p, &pWriter, 1,
|
||||
csr.zTerm, csr.nTerm, csr.aDoclist, csr.nDoclist);
|
||||
}
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
assert( pWriter );
|
||||
|
||||
rc = fts3DeleteSegdir(p, iLevel, csr.apSegment, csr.nSegment);
|
||||
if( rc!=SQLITE_OK ) goto finished;
|
||||
rc = fts3SegWriterFlush(p, pWriter, iNewLevel, iIdx);
|
||||
|
||||
finished:
|
||||
fts3SegWriterFree(pWriter);
|
||||
if( apSegment ){
|
||||
for(i=0; i<nSegment; i++){
|
||||
sqlite3Fts3SegReaderFree(apSegment[i]);
|
||||
}
|
||||
sqlite3_free(apSegment);
|
||||
}
|
||||
sqlite3Fts3SegReaderFree(pPending);
|
||||
sqlite3_reset(pStmt);
|
||||
sqlite3Fts3SegReaderFinish(&csr);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -2404,55 +2284,7 @@ static int fts3SegmentMerge(Fts3Table *p, int iLevel){
|
||||
** Flush the contents of pendingTerms to a level 0 segment.
|
||||
*/
|
||||
int sqlite3Fts3PendingTermsFlush(Fts3Table *p){
|
||||
int rc; /* Return Code */
|
||||
int idx; /* Index of new segment created */
|
||||
SegmentWriter *pWriter = 0; /* Used to write the segment */
|
||||
Fts3SegReader *pReader = 0; /* Used to iterate through the hash table */
|
||||
|
||||
/* Allocate a SegReader object to iterate through the contents of the
|
||||
** pending-terms table. If an error occurs, or if there are no terms
|
||||
** in the pending-terms table, return immediately.
|
||||
*/
|
||||
rc = sqlite3Fts3SegReaderPending(p, 0, 0, 1, &pReader);
|
||||
if( rc!=SQLITE_OK || pReader==0 ){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Determine the next index at level 0. If level 0 is already full, this
|
||||
** call may merge all existing level 0 segments into a single level 1
|
||||
** segment.
|
||||
*/
|
||||
rc = fts3AllocateSegdirIdx(p, 0, &idx);
|
||||
|
||||
/* If no errors have occured, iterate through the contents of the
|
||||
** pending-terms hash table using the Fts3SegReader iterator. The callback
|
||||
** writes each term (along with its doclist) to the database via the
|
||||
** SegmentWriter handle pWriter.
|
||||
*/
|
||||
if( rc==SQLITE_OK ){
|
||||
void *c = (void *)&pWriter; /* SegReaderIterate() callback context */
|
||||
Fts3SegFilter f; /* SegReaderIterate() parameters */
|
||||
|
||||
memset(&f, 0, sizeof(Fts3SegFilter));
|
||||
f.flags = FTS3_SEGMENT_REQUIRE_POS;
|
||||
rc = sqlite3Fts3SegReaderIterate(p, &pReader, 1, &f, fts3FlushCallback, c);
|
||||
}
|
||||
assert( pWriter || rc!=SQLITE_OK );
|
||||
|
||||
/* If no errors have occured, flush the SegmentWriter object to the
|
||||
** database. Then delete the SegmentWriter and Fts3SegReader objects
|
||||
** allocated by this function.
|
||||
*/
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts3SegWriterFlush(p, pWriter, 0, idx);
|
||||
}
|
||||
fts3SegWriterFree(pWriter);
|
||||
sqlite3Fts3SegReaderFree(pReader);
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3Fts3PendingTermsClear(p);
|
||||
}
|
||||
return rc;
|
||||
return fts3SegmentMerge(p, FTS3_SEGCURSOR_PENDING);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2619,7 +2451,7 @@ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){
|
||||
if( !zVal ){
|
||||
return SQLITE_NOMEM;
|
||||
}else if( nVal==8 && 0==sqlite3_strnicmp(zVal, "optimize", 8) ){
|
||||
rc = fts3SegmentMerge(p, -1);
|
||||
rc = fts3SegmentMerge(p, FTS3_SEGCURSOR_ALL);
|
||||
if( rc==SQLITE_DONE ){
|
||||
rc = SQLITE_OK;
|
||||
}else{
|
||||
@@ -2877,7 +2709,7 @@ int sqlite3Fts3Optimize(Fts3Table *p){
|
||||
int rc;
|
||||
rc = sqlite3_exec(p->db, "SAVEPOINT fts3", 0, 0, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts3SegmentMerge(p, -1);
|
||||
rc = fts3SegmentMerge(p, FTS3_SEGCURSOR_ALL);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1268,7 +1268,7 @@ static int rtreeFilter(
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
memset(pCsr->aConstraint, 0, sizeof(RtreeConstraint)*argc);
|
||||
assert( (idxStr==0 && argc==0) || strlen(idxStr)==argc*2 );
|
||||
assert( (idxStr==0 && argc==0) || (int)strlen(idxStr)==argc*2 );
|
||||
for(ii=0; ii<argc; ii++){
|
||||
RtreeConstraint *p = &pCsr->aConstraint[ii];
|
||||
p->op = idxStr[ii*2];
|
||||
@@ -1353,7 +1353,7 @@ static int rtreeFilter(
|
||||
*/
|
||||
static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
int rc = SQLITE_OK;
|
||||
int ii, cCol;
|
||||
int ii;
|
||||
|
||||
int iIdx = 0;
|
||||
char zIdxStr[RTREE_MAX_DIMENSIONS*8+1];
|
||||
@@ -1361,7 +1361,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
UNUSED_PARAMETER(tab);
|
||||
|
||||
assert( pIdxInfo->idxStr==0 );
|
||||
for(ii=0; ii<pIdxInfo->nConstraint; ii++){
|
||||
for(ii=0; ii<pIdxInfo->nConstraint && iIdx<(int)(sizeof(zIdxStr)-1); ii++){
|
||||
struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii];
|
||||
|
||||
if( p->usable && p->iColumn==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){
|
||||
@@ -1385,9 +1385,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
}
|
||||
|
||||
if( p->usable && (p->iColumn>0 || p->op==SQLITE_INDEX_CONSTRAINT_MATCH) ){
|
||||
int j, opmsk;
|
||||
static const unsigned char compatible[] = { 0, 0, 1, 1, 2, 2 };
|
||||
u8 op = 0;
|
||||
u8 op;
|
||||
switch( p->op ){
|
||||
case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; break;
|
||||
case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; break;
|
||||
@@ -1399,37 +1397,10 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
op = RTREE_MATCH;
|
||||
break;
|
||||
}
|
||||
assert( op!=0 );
|
||||
|
||||
/* Make sure this particular constraint has not been used before.
|
||||
** If it has been used before, ignore it.
|
||||
**
|
||||
** A <= or < can be used if there is a prior >= or >.
|
||||
** A >= or > can be used if there is a prior < or <=.
|
||||
** A <= or < is disqualified if there is a prior <=, <, or ==.
|
||||
** A >= or > is disqualified if there is a prior >=, >, or ==.
|
||||
** A == is disqualifed if there is any prior constraint.
|
||||
*/
|
||||
assert( compatible[RTREE_EQ & 7]==0 );
|
||||
assert( compatible[RTREE_LT & 7]==1 );
|
||||
assert( compatible[RTREE_LE & 7]==1 );
|
||||
assert( compatible[RTREE_GT & 7]==2 );
|
||||
assert( compatible[RTREE_GE & 7]==2 );
|
||||
cCol = p->iColumn - 1 + 'a';
|
||||
opmsk = compatible[op & 7];
|
||||
for(j=0; j<iIdx; j+=2){
|
||||
if( zIdxStr[j+1]==cCol && (compatible[zIdxStr[j] & 7] & opmsk)!=0 ){
|
||||
op = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( op ){
|
||||
assert( iIdx<sizeof(zIdxStr)-1 );
|
||||
zIdxStr[iIdx++] = op;
|
||||
zIdxStr[iIdx++] = cCol;
|
||||
pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2);
|
||||
pIdxInfo->aConstraintUsage[ii].omit = 1;
|
||||
}
|
||||
zIdxStr[iIdx++] = op;
|
||||
zIdxStr[iIdx++] = p->iColumn - 1 + 'a';
|
||||
pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2);
|
||||
pIdxInfo->aConstraintUsage[ii].omit = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3133,7 +3104,7 @@ static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){
|
||||
int jj;
|
||||
|
||||
nodeGetCell(&tree, &node, ii, &cell);
|
||||
sqlite3_snprintf(512-nCell,&zCell[nCell],"%d", cell.iRowid);
|
||||
sqlite3_snprintf(512-nCell,&zCell[nCell],"%lld", cell.iRowid);
|
||||
nCell = strlen(zCell);
|
||||
for(jj=0; jj<tree.nDim*2; jj++){
|
||||
sqlite3_snprintf(512-nCell,&zCell[nCell]," %f",(double)cell.aCoord[jj].f);
|
||||
|
||||
@@ -106,4 +106,51 @@ do_eqp_test rtree6.2.5 {
|
||||
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
|
||||
} {}
|
||||
|
||||
|
||||
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
|
||||
@@ -53,7 +53,7 @@ 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.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 \
|
||||
@@ -187,6 +187,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 \
|
||||
@@ -236,6 +237,7 @@ 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 \
|
||||
@@ -252,9 +254,11 @@ TESTSRC = \
|
||||
$(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
|
||||
@@ -293,6 +297,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 \
|
||||
@@ -462,6 +467,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
|
||||
|
||||
@@ -531,8 +539,8 @@ test: testfixture$(EXE) sqlite3$(EXE)
|
||||
# threadtest runs a few thread-safety tests that are implemented in C. This
|
||||
# target is invoked by the releasetest.tcl script.
|
||||
#
|
||||
threadtest3$(EXE): sqlite3.c $(TOP)/test/threadtest3.c
|
||||
$(TCCX) -O2 sqlite3.c $(TOP)/test/threadtest3.c \
|
||||
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)
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA1
|
||||
|
||||
C SQLite\sversion\s3.7.5\srelease\scandidate\s2
|
||||
D 2011-01-28T17:03:50.592
|
||||
C Version\s3.7.6.2
|
||||
D 2011-04-17T17:25:17.173
|
||||
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
||||
F Makefile.in de6498556d536ae60bb8bb10e8c1ba011448658c
|
||||
F Makefile.in 7a4d9524721d40ef9ee26f93f9bd6a51dba106f2
|
||||
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
||||
F Makefile.vxworks c85ec1d8597fe2f7bc225af12ac1666e21379151
|
||||
F README cd04a36fbc7ea56932a4052d7d0b7f09f27c33d6
|
||||
F VERSION de8d3477dbf0d6cc226ccc6e046273627eb55fc5
|
||||
F VERSION 7fbd89ecdebc63a53a5cedadb822b74f5ae42a24
|
||||
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
|
||||
F addopcodes.awk 17dc593f791f874d2c23a0f9360850ded0286531
|
||||
F art/2005osaward.gif 0d1851b2a7c1c9d0ccce545f3e14bca42d7fd248
|
||||
@@ -25,7 +22,7 @@ F art/src_logo.gif 9341ef09f0e53cd44c0c9b6fc3c16f7f3d6c2ad9
|
||||
F config.guess 226d9a188c6196f3033ffc651cbc9dcee1a42977
|
||||
F config.h.in 868fdb48c028421a203470e15c69ada15b9ba673
|
||||
F config.sub 9ebe4c3b3dab6431ece34f16828b594fb420da55
|
||||
F configure 0eb10c03a6536d8e5ce52ab70fda0a152d8a3262 x
|
||||
F configure eb34ce3c85e7658b2eab3439f0e5b5c10b166b13 x
|
||||
F configure.ac 87a3c71bbe9c925381c154413eea7f3cdc397244
|
||||
F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad
|
||||
F doc/lemon.html f0f682f50210928c07e562621c3b7e8ab912a538
|
||||
@@ -64,37 +61,39 @@ F ext/fts2/mkfts2amal.tcl 974d5d438cb3f7c4a652639262f82418c1e4cff0
|
||||
F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
|
||||
F ext/fts3/README.tokenizers 998756696647400de63d5ba60e9655036cb966e9
|
||||
F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
|
||||
F ext/fts3/fts3.c 28ada7d1c700e57b072b2c95d70565b05925fa46
|
||||
F ext/fts3/fts3.c 5653c5654ac9b65bf3646af7e1d695c7e9b991a0
|
||||
F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
|
||||
F ext/fts3/fts3Int.h a6c69c1c5e2c8c19172ddff42d262c087dcd7337
|
||||
F ext/fts3/fts3Int.h 945926ea4b6a686c3e9834640a252d9870b7191e
|
||||
F ext/fts3/fts3_aux.c 9e931f55eed8498dafe7bc1160f10cbb1a652fdf
|
||||
F ext/fts3/fts3_expr.c 5f49e0deaf723724b08100bb3ff40aab02ad0c93
|
||||
F ext/fts3/fts3_hash.c 3c8f6387a4a7f5305588b203fa7c887d753e1f1c
|
||||
F ext/fts3/fts3_hash.h 8331fb2206c609f9fc4c4735b9ab5ad6137c88ec
|
||||
F ext/fts3/fts3_icu.c ac494aed69835008185299315403044664bda295
|
||||
F ext/fts3/fts3_porter.c d61cfd81fb0fd8fbcb25adcaee0ba671aefaa5c2
|
||||
F ext/fts3/fts3_snippet.c 196c5e6cde57bfc1907c2d60e9c29590e4f93fb6
|
||||
F ext/fts3/fts3_snippet.c e857c6a89d81d3b89df59f3b44b35c68d8ed5c62
|
||||
F ext/fts3/fts3_tokenizer.c 055f3dc7369585350b28db1ee0f3b214dca6724d
|
||||
F ext/fts3/fts3_tokenizer.h 13ffd9fcb397fec32a05ef5cd9e0fa659bf3dbd3
|
||||
F ext/fts3/fts3_tokenizer1.c 6e5cbaa588924ac578263a598e4fb9f5c9bb179d
|
||||
F ext/fts3/fts3_write.c 3eea26b9ca4219e1711b0db74fd5a9d448a6afbb
|
||||
F ext/fts3/fts3_write.c 813495ed106eb9461044e3c0374f4db69b37eb09
|
||||
F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
|
||||
F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
|
||||
F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9
|
||||
F ext/icu/icu.c 850e9a36567bbcce6bd85a4b68243cad8e3c2de2
|
||||
F ext/icu/icu.c eb9ae1d79046bd7871aa97ee6da51eb770134b5a
|
||||
F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
|
||||
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
|
||||
F ext/rtree/rtree.c 05b293c85403cf39bb5af0e7c010b0cafeab5e47
|
||||
F ext/rtree/rtree.c f5fa951eba03c41d292958064604a033021acdee
|
||||
F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e
|
||||
F ext/rtree/rtree1.test dbd4250ac0ad367a262eb9676f7e3080b0368206
|
||||
F ext/rtree/rtree2.test acbb3a4ce0f4fbc2c304d2b4b784cfa161856bba
|
||||
F ext/rtree/rtree3.test a494da55c30ee0bc9b01a91c80c81b387b22d2dc
|
||||
F ext/rtree/rtree4.test 0061e6f464fd3dc6a79f82454c5a1c3dadbe42af
|
||||
F ext/rtree/rtree5.test ce3d7ccae2cfd9d2e1052b462424964c9bdcda12
|
||||
F ext/rtree/rtree6.test 309806a2a27ef5897d4dd6aee2e8006bf754cc22
|
||||
F ext/rtree/rtree6.test 0b380bd9af93f3bc496eef42502a336f58949c1b
|
||||
F ext/rtree/rtree7.test bcb647b42920b3b5d025846689147778485cc318
|
||||
F ext/rtree/rtree8.test 9772e16da71e17e02bdebf0a5188590f289ab37d
|
||||
F ext/rtree/rtree9.test df9843d1a9195249c8d3b4ea6aedda2d5c73e9c2
|
||||
F ext/rtree/rtreeA.test ace05e729a36e342d40cf94e9efc7b4723d9dcdf
|
||||
F ext/rtree/rtreeB.test b1916a9cecb86b02529c4cc5a546e8d6e7ff10da
|
||||
F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195
|
||||
F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea
|
||||
F ext/rtree/sqlite3rtree.h 1af0899c63a688e272d69d8e746f24e76f10a3f0
|
||||
@@ -102,7 +101,7 @@ F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
|
||||
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F main.mk 05d0f3475dd331896bd607cfb45c5e21b94589ad
|
||||
F main.mk bd4e376deea4704b2bd9c77a4e6f0fa3de25c495
|
||||
F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a
|
||||
F mkextu.sh 416f9b7089d80e5590a29692c9d9280a10dbad9f
|
||||
F mkextw.sh 4123480947681d9b434a5e7b1ee08135abe409ac
|
||||
@@ -115,157 +114,166 @@ F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca
|
||||
F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
|
||||
F sqlite3.1 6be1ad09113570e1fc8dcaff84c9b0b337db5ffc
|
||||
F sqlite3.pc.in ae6f59a76e862f5c561eb32a380228a02afc3cad
|
||||
F src/alter.c 6a0c176e64a34929a4436048066a84ef4f1445b3
|
||||
F src/analyze.c a038162344265ac21dfb24b3fcc06c666ebb9c07
|
||||
F src/attach.c 252c4f7e36cc219349451ed63e278c60e80b26f3
|
||||
F src/alter.c 280f5c04b11b492703a342222b3de0a999445280
|
||||
F src/analyze.c a425d62e8fa9ebcb4359ab84ff0c62c6563d2e2a
|
||||
F src/attach.c 7f97ca76ef2453440170929531a9c778267c0830
|
||||
F src/auth.c 523da7fb4979469955d822ff9298352d6b31de34
|
||||
F src/backup.c 6728d6d48d55b449af76a3e51c0808849cb32a2e
|
||||
F src/backup.c 986c15232757f2873dff35ee3b35cbf935fc573c
|
||||
F src/bitvec.c af50f1c8c0ff54d6bdb7a80e2fceca5a93670bef
|
||||
F src/btmutex.c 96a12f50f7a17475155971a241d85ec5171573ff
|
||||
F src/btree.c 9004c98fc576306eee4fc0562ffeb362ef53912c
|
||||
F src/btree.h 10f9296bf4edf034f5adce921b7b4383a56a1c90
|
||||
F src/btreeInt.h 20f73dc93b1eeb83afd7259fbc6bd7dcf2df7fe4
|
||||
F src/build.c 00a327120d81ace6267e714ae8010c997d55de5d
|
||||
F src/callback.c a1d1b1c9c85415dff013af033e2fed9c8382d33b
|
||||
F src/btmutex.c 976f45a12e37293e32cae0281b15a21d48a8aaa7
|
||||
F src/btree.c 6a9164af8a2ef4612ee30b253635a9bd8e5e1b1b
|
||||
F src/btree.h 11753dd46597a20702bca8746cb4caa4486a82b5
|
||||
F src/btreeInt.h 67978c014fa4f7cc874032dd3aacadd8db656bc3
|
||||
F src/build.c f09c46c66a1e7668c6ee25c9a2518aaa6842044c
|
||||
F src/callback.c 0425c6320730e6d3981acfb9202c1bed9016ad1a
|
||||
F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac
|
||||
F src/ctime.c 7deec4534f3b5a0c3b4a4cbadf809d321f64f9c4
|
||||
F src/date.c 1548fdac51377e4e7833251de878b4058c148e1b
|
||||
F src/delete.c 7ed8a8c8b5f748ece92df173d7e0f7810c899ebd
|
||||
F src/expr.c 1810f3056b11de99cc10e24629edf00e5fbd3a75
|
||||
F src/delete.c 7a24fcc9a31664d145acb97ce56b6d9f249a25e4
|
||||
F src/expr.c e3cf0957c6b8faaaf7386a3bc69e53c0dc9705be
|
||||
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
|
||||
F src/fkey.c 17950a28f28b23e8ad3feaac5fc88c324d2f600a
|
||||
F src/func.c cb41f614edc43b00bfeb030f9768e80eaff47edd
|
||||
F src/fkey.c a43ba8a005fb5efd1deeee06853e3a6120d46a91
|
||||
F src/func.c 3a8cb2fb2de3e3aed7f39106daf4878d9d17fcce
|
||||
F src/global.c 02335177cf6946fe5525c6f0755cf181140debf3
|
||||
F src/hash.c 458488dcc159c301b8e7686280ab209f1fb915af
|
||||
F src/hash.h 2894c932d84d9f892d4b4023a75e501f83050970
|
||||
F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08
|
||||
F src/insert.c a4995747c062256582a90b4f87f716e11b067050
|
||||
F src/insert.c acfb89fe4a73d703e425e167bfcc72985f4299ae
|
||||
F src/journal.c 552839e54d1bf76fb8f7abe51868b66acacf6a0e
|
||||
F src/legacy.c a199d7683d60cef73089e892409113e69c23a99f
|
||||
F src/lempar.c 7f026423f4d71d989e719a743f98a1cbd4e6d99e
|
||||
F src/loadext.c 8af9fcc75708d60b88636ccba38b4a7b3c155c3e
|
||||
F src/main.c 6653e46db7ecb5a7449d8a12900147192f748b97
|
||||
F src/malloc.c 92d59a007d7a42857d4e9454aa25b6b703286be1
|
||||
F src/loadext.c 3ae0d52da013a6326310655be6473fd472347b85
|
||||
F src/main.c a8571665d43ff18f89a49d47a281605ce5ea825e
|
||||
F src/malloc.c 788f2ed928786dfe305b6783d551d6b1a9080976
|
||||
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
||||
F src/mem1.c 00bd8265c81abb665c48fea1e0c234eb3b922206
|
||||
F src/mem2.c e307323e86b5da1853d7111b68fd6b84ad6f09cf
|
||||
F src/mem3.c 9b237d911ba9904142a804be727cc6664873f8a3
|
||||
F src/mem5.c 6fe00f46997bebb690397cb029719f711e7640e3
|
||||
F src/mem5.c c2c63b7067570b00bf33d751c39af24182316f7f
|
||||
F src/memjournal.c 0ebce851677a7ac035ba1512a7e65851b34530c6
|
||||
F src/mutex.c 6949180803ff05a7d0e2b9334a95b4fb5a00e23f
|
||||
F src/mutex.h fe2ef5e1c4dae531d5a544f9241f19c56d26803d
|
||||
F src/mutex_noop.c d5cfbca87168c661a0b118cd8e329a908e453151
|
||||
F src/mutex_os2.c 6a62583e374ba3ac1a3fcc0da2bfdac7d3942689
|
||||
F src/mutex_os2.c 882d735098c07c8c6a5472b8dd66e19675fe117f
|
||||
F src/mutex_unix.c b4f4e923bb8de93ec3f251fadb50855f23df9579
|
||||
F src/mutex_w32.c 3ade5ae71449d1d023f0ebb3184c2ae6aa9307e4
|
||||
F src/mutex_w32.c 5e54f3ba275bcb5d00248b8c23107df2e2f73e33
|
||||
F src/notify.c 976dd0f6171d4588e89e874fcc765e92914b6d30
|
||||
F src/os.c 22ac61d06e72a0dac900400147333b07b13d8e1d
|
||||
F src/os.h 9dbed8c2b9c1f2f2ebabc09e49829d4777c26bf9
|
||||
F src/os_common.h a8f95b81eca8a1ab8593d23e94f8a35f35d4078f
|
||||
F src/os_os2.c 2e452c9f2ca507623ad351c33a8a8b27849b1863
|
||||
F src/os_unix.c 1be46a35bad4bec5171e4de88aaff817260eb378
|
||||
F src/os_win.c 9abdcdd925416d854eabb0996c96debd92abfef5
|
||||
F src/pager.c b0fcbe3038fd08b111e1cf1deddd5f42418004d8
|
||||
F src/pager.h 0ea59db2a33bc6c2c02cae34de33367e1effdf76
|
||||
F src/os_os2.c 4a75888ba3dfc820ad5e8177025972d74d7f2440
|
||||
F src/os_unix.c 4389231d079f8e323d9e24db6658bfd31f6d27a2
|
||||
F src/os_win.c 24d72407a90551969744cf9bcbb1b4c72c5fa845
|
||||
F src/pager.c 055239dcdfe12b3f5d97f6f01f85da01e2d6d912
|
||||
F src/pager.h 3f8c783de1d4706b40b1ac15b64f5f896bcc78d1
|
||||
F src/parse.y 12b7ebd61ea54f0e1b1083ff69cc2c8ce9353d58
|
||||
F src/pcache.c 09d38c44ab275db581f7a2f6ff8b9bc7f8c0faaa
|
||||
F src/pcache.h c683390d50f856d4cd8e24342ae62027d1bb6050
|
||||
F src/pcache1.c d548e31beafa792d1994b663a29a5303569efc4e
|
||||
F src/pragma.c 8a6cd3c787f882fa44f6490d2411fc26839ce8f3
|
||||
F src/prepare.c 395b3fab1b93f45b6aa194b23ebc201221c47b99
|
||||
F src/printf.c df2ff3bb5409e8958136933342c46464fbd017e7
|
||||
F src/pragma.c 49c90ab27a4339d4b5bc0b03c08cbcf20ed8d454
|
||||
F src/prepare.c e64261559a3187698a3e7e6c8b001a4f4f98dab4
|
||||
F src/printf.c 585a36b6a963df832cfb69505afa3a34ed5ef8a1
|
||||
F src/random.c cd4a67b3953b88019f8cd4ccd81394a8ddfaba50
|
||||
F src/resolve.c 1c0f32b64f8e3f555fe1f732f9d6f501a7f05706
|
||||
F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697
|
||||
F src/select.c 8a7ba246b0b4bb45df7fbc52681728a0e3deaaa7
|
||||
F src/shell.c 83c6f0cc5a79a081c7b9ddfe4f557b47e0bad976
|
||||
F src/sqlite.h.in 76955fcd1c5371268ecc8afe0ce6c49ea750ae38
|
||||
F src/select.c 649a6f10f7eb7b52a5a28847773cb9968a828ae8
|
||||
F src/shell.c 72e7e176bf46d5c6518d15ac4ad6847c4bb5df79
|
||||
F src/sqlite.h.in 4d28db70c37a1b17942820308eb59f211140da43
|
||||
F src/sqlite3ext.h c90bd5507099f62043832d73f6425d8d5c5da754
|
||||
F src/sqliteInt.h 45926deaf59b1ce3f55d21d5f91a8cecb6a7eb4c
|
||||
F src/sqliteLimit.h a17dcd3fb775d63b64a43a55c54cb282f9726f44
|
||||
F src/status.c 4997380fbb915426fef9e500b4872e79c99267fc
|
||||
F src/sqliteInt.h ac8f3f5846275c634f6649969304a9e97f6f9854
|
||||
F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
|
||||
F src/status.c 7ac64842c86cec2fc1a1d0e5c16d3beb8ad332bf
|
||||
F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
|
||||
F src/tclsqlite.c 549859dc2c143f3deb6a92636a2d27973652c164
|
||||
F src/test1.c 771407a49ae199241f0efb7055634e4a1899c026
|
||||
F src/tclsqlite.c 501c9a200fd998a268be475be5858febc90b725b
|
||||
F src/test1.c 9ca440e80e16e53920904a0a5ac7feffb9b2c9a1
|
||||
F src/test2.c 80d323d11e909cf0eb1b6fbb4ac22276483bcf31
|
||||
F src/test3.c 056093cfef69ff4227a6bdb9108564dc7f45e4bc
|
||||
F src/test4.c 0528360b5025688002a5feb6be906ddce52eaaee
|
||||
F src/test4.c d1e5a5e904d4b444cf572391fdcb017638e36ff7
|
||||
F src/test5.c e1a19845625144caf038031234a12185e40d315c
|
||||
F src/test6.c c7256cc21d2409486d094277d5b017e8eced44ba
|
||||
F src/test7.c d67f65d82815eb0b57699b122ca0145dbea55bbb
|
||||
F src/test7.c 2e0781754905c8adc3268d8f0967e7633af58843
|
||||
F src/test8.c 6b1d12912a04fe6fca8c45bb9c3ea022f4352228
|
||||
F src/test9.c bea1e8cf52aa93695487badedd6e1886c321ea60
|
||||
F src/test_async.c 0612a752896fad42d55c3999a5122af10dcf22ad
|
||||
F src/test_autoext.c 30e7bd98ab6d70a62bb9ba572e4c7df347fe645e
|
||||
F src/test_backup.c c129c91127e9b46e335715ae2e75756e25ba27de
|
||||
F src/test_btree.c 47cd771250f09cdc6e12dda5bc71bc0b3abc96e2
|
||||
F src/test_config.c 9f025a7f3686c94e82dc6d6bd3cbf0f89cd67487
|
||||
F src/test_demovfs.c 0aed671636735116fc872c5b03706fd5612488b5
|
||||
F src/test_config.c d536042f27226b4639f0f87d4795fd37428a9ddf
|
||||
F src/test_demovfs.c 31050680fa6925b4f677cfd4fa965b5f19195e50
|
||||
F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc
|
||||
F src/test_func.c 13b582345fb1185a93e46c53310fae8547dcce20
|
||||
F src/test_hexio.c 1237f000ec7a491009b1233f5c626ea71bce1ea2
|
||||
F src/test_func.c cbdec5cededa0761daedde5baf06004a9bf416b5
|
||||
F src/test_fuzzer.c f884f6f32e8513d34248d6e1ac8a32047fead254
|
||||
F src/test_hexio.c c4773049603151704a6ab25ac5e936b5109caf5a
|
||||
F src/test_init.c 5d624ffd0409d424cf9adbfe1f056b200270077c
|
||||
F src/test_intarray.c d879bbf8e4ce085ab966d1f3c896a7c8b4f5fc99
|
||||
F src/test_intarray.h 489edb9068bb926583445cb02589344961054207
|
||||
F src/test_journal.c 785edd54f963aefb3c1628124170a56697c68c70
|
||||
F src/test_loadext.c df586c27176e3c2cb2e099c78da67bf14379a56e
|
||||
F src/test_malloc.c fd6188b1501c0010fb4241ddc9f0d5ac402c688d
|
||||
F src/test_multiplex.c 5990431a852aa21c9a67da748f23d2cf1e21f8fc
|
||||
F src/test_multiplex.c fdabd793ee7a9642c5a8a470def2347144c46d05
|
||||
F src/test_multiplex.h e99c571bc4968b7a9363b661481f3934bfead61d
|
||||
F src/test_mutex.c a6bd7b9cf6e19d989e31392b06ac8d189f0d573e
|
||||
F src/test_onefile.c 40cf9e212a377a6511469384a64b01e6e34b2eec
|
||||
F src/test_osinst.c f408c6a181f2fb04c56273afd5c3e1e82f60392c
|
||||
F src/test_osinst.c 62b0b8ef21ce754cc94e17bb42377ed8795dba32
|
||||
F src/test_pcache.c 7bf828972ac0d2403f5cfa4cd14da41f8ebe73d8
|
||||
F src/test_quota.c b5576f17d701af461effd7ca1e71f0d100071192
|
||||
F src/test_rtree.c 30c981837445a4e187ee850a49c4760d9642f7c3
|
||||
F src/test_schema.c 8c06ef9ddb240c7a0fcd31bc221a6a2aade58bf0
|
||||
F src/test_server.c bbba05c144b5fc4b52ff650a4328027b3fa5fcc6
|
||||
F src/test_server.c 2f99eb2837dfa06a4aacf24af24c6affdf66a84f
|
||||
F src/test_stat.c f682704b5d1ba8e1d4e7e882a6d7922e2dcf066c
|
||||
F src/test_superlock.c 2b97936ca127d13962c3605dbc9a4ef269c424cd
|
||||
F src/test_syscall.c 162c4ec0137a549c009bb9ecab550527743cfc5d
|
||||
F src/test_tclvar.c f4dc67d5f780707210d6bb0eb6016a431c04c7fa
|
||||
F src/test_thread.c bedd05cad673dba53326f3aa468cc803038896c0
|
||||
F src/test_thread.c 361ae0a0f1cbf5a28ad0388a258b104017a370c0
|
||||
F src/test_vfs.c 2ed8853c1e51ac6f9ea091f7ce4e0d618bba8b86
|
||||
F src/test_vfstrace.c 2265c9895f350c8d3c39b079998fbe7481505cc1
|
||||
F src/test_wholenumber.c 6129adfbe7c7444f2e60cc785927f3aa74e12290
|
||||
F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
|
||||
F src/tokenize.c 604607d6813e9551cf5189d899e0a25c12681080
|
||||
F src/trigger.c b8bedb9c0084ceb51a40f54fcca2ce048c8de852
|
||||
F src/update.c 227e6cd512108b84f69421fc6c7aa1b83d60d6e0
|
||||
F src/utf.c 1baeeac91707a4df97ccc6141ec0f808278af685
|
||||
F src/util.c ab1c92426494f499f42b9e307537b03e923d75c1
|
||||
F src/vacuum.c 924bd1bcee2dfb05376f79845bd3b4cec7b54b2f
|
||||
F src/vdbe.c 5d310eaf1a4d8383602126fa82e01291ab7d3cf3
|
||||
F src/vdbe.h 4de0efb4b0fdaaa900cf419b35c458933ef1c6d2
|
||||
F src/vdbeInt.h 6e6f28e9bccc6c703dca1372fd661c57b5c15fb0
|
||||
F src/vdbeapi.c 8e9324fd35eb70d0b5904bd1af40f2598744dc4d
|
||||
F src/vdbeaux.c 33448d23b857654dd69ed2103611f5c733606f68
|
||||
F src/vdbeblob.c 18955f0ee6b133cd08e1592010cb9a6b11e9984c
|
||||
F src/vdbemem.c 411649a35686f54268ccabeda175322c4697f5a6
|
||||
F src/vdbetrace.c 3ba13bc32bdf16d2bdea523245fd16736bed67b5
|
||||
F src/vtab.c b297e8fa656ab5e66244ab15680d68db0adbec30
|
||||
F src/wal.c dbca424f71678f663a286ab2a98f947af1d412a7
|
||||
F src/wal.h c1aac6593a0b02b15dc625987e619edeab39292e
|
||||
F src/trigger.c 144cc18bb701f3286484aae4292a9531f09278c8
|
||||
F src/update.c 81911be16ece3c3e7716aa18565b4814ec41f8b9
|
||||
F src/utf.c d83650c3ea08f7407bd9d0839d9885241c209c60
|
||||
F src/util.c 465fe10aabf0ca7d7826a156dab919b0b65c525a
|
||||
F src/vacuum.c 05513dca036a1e7848fe18d5ed1265ac0b32365e
|
||||
F src/vdbe.c 05deeec6659f2579674a5e6510b3ada2a442f8d5
|
||||
F src/vdbe.h 8a675fefdf7119441fe817c800a9a52440c2e797
|
||||
F src/vdbeInt.h fe8f58d305e629fff02f61f655aca1d299f1f6ae
|
||||
F src/vdbeapi.c e0e2672e0a96ae3f8575c8ecd02912a3e8a554a1
|
||||
F src/vdbeaux.c 9ae5074b19bdff2d8806a278533956fb281510d5
|
||||
F src/vdbeblob.c c3ccb7c8732858c680f442932e66ad06bb036562
|
||||
F src/vdbemem.c 0498796b6ffbe45e32960d6a1f5adfb6e419883b
|
||||
F src/vdbetrace.c 5d0dc3d5fd54878cc8d6d28eb41deb8d5885b114
|
||||
F src/vtab.c b0abc931f95af94c9ffdf9f747eb191cda953123
|
||||
F src/wal.c 7334009b396285b658a95a3b6bc6d2b016a1f794
|
||||
F src/wal.h 7a5fbb00114b7f2cd40c7e1003d4c41ce9d26840
|
||||
F src/walker.c 3112bb3afe1d85dc52317cb1d752055e9a781f8f
|
||||
F src/where.c af069e6b53234118014dabfece96a9515b69d76b
|
||||
F src/where.c 55403ce19c506be6a321c7f129aff693d6103db5
|
||||
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
|
||||
F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87
|
||||
F test/all.test 51756962d522e474338e9b2ebb26e7364d4aa125
|
||||
F test/alter.test 15f9224868b290d6bf7a63f31437f31aee070636
|
||||
F test/alter.test 4e47fb9ea59348b88fce4e8bb49de530128b104c
|
||||
F test/alter2.test 75f731508f1bf27ba09a6075c66cd02216ba464b
|
||||
F test/alter3.test 8677e48d95536f7a6ed86a1a774744dadcc22b07
|
||||
F test/alter4.test 1e5dd6b951e9f65ca66422edff02e56df82dd403
|
||||
F test/altermalloc.test e81ac9657ed25c6c5bb09bebfa5a047cd8e4acfc
|
||||
F test/analyze.test c1eb87067fc16ece7c07e823d6395fd831b270c5
|
||||
F test/analyze2.test 3bde8f0879d9c1f2df3af21fcf42e706d8ee1e43
|
||||
F test/analyze3.test 820ddfb7591b49607fbaf77240c7955ac3cabb04
|
||||
F test/analyze.test c8cb89e8736336f1f0646c8123e6028a14c7b55e
|
||||
F test/analyze2.test 8f2b1534d43f5547ce9a6b736c021d4192c75be3
|
||||
F test/analyze3.test d61f55d8b472fc6e713160b1e577f7a68e63f38b
|
||||
F test/analyze4.test 757b37875cf9bb528d46f74497bc789c88365045
|
||||
F test/analyze5.test 1de8d66b11aae5a1453aa042d62e834a476bac9c
|
||||
F test/analyze6.test c125622a813325bba1b4999040ddc213773c2290
|
||||
F test/analyze7.test 5508e7828164ea0b518ed219bed7320a481863d4
|
||||
F test/async.test ad4ba51b77cd118911a3fe1356b0809da9c108c3
|
||||
F test/async2.test bf5e2ca2c96763b4cba3d016249ad7259a5603b6
|
||||
F test/async3.test 93edaa9122f498e56ea98c36c72abc407f4fb11e
|
||||
F test/async4.test 1787e3952128aa10238bf39945126de7ca23685a
|
||||
F test/async5.test f3592d79c84d6e83a5f50d3fd500445f7d97dfdf
|
||||
F test/attach.test ce9660e51768fab93cf129787be886c5d6c4fd81
|
||||
F test/attach.test 2bb09073d7d5499127db00f50780766dcea913e1
|
||||
F test/attach2.test a295d2d7061adcee5884ef4a93c7c96a82765437
|
||||
F test/attach3.test bd9830bc3a0d22ed1310c9bff6896927937017dc
|
||||
F test/attach4.test 31f9eb0ca7bdbc393cc4657b877903a226a83d4b
|
||||
F test/attachmalloc.test 1d5b821a676f7bf0b00d87cc106b78966789ba57
|
||||
F test/auth.test 26cc6f219580191539bf335abe03e55e49310846
|
||||
F test/auth.test b047105c32da7db70b842fd24056723125ecc2ff
|
||||
F test/auth2.test 270baddc8b9c273682760cffba6739d907bd2882
|
||||
F test/auth3.test a4755e6a2a2fea547ffe63c874eb569e60a28eb5
|
||||
F test/autoinc.test 85ef3180a737e6580086a018c09c6f1a52759b46
|
||||
@@ -273,12 +281,13 @@ F test/autoindex1.test 860fc83f4fefb0c68ad062afc3ff43faa1534fc4
|
||||
F test/autovacuum.test bb7c0885e6f8f1d633045de48f2b66082162766d
|
||||
F test/autovacuum_ioerr2.test 598b0663074d3673a9c1bc9a16e80971313bafe6
|
||||
F test/avtrans.test 0252654f4295ddda3b2cce0e894812259e655a85
|
||||
F test/backcompat.test 541314d69ec9db3e03630b7616696ddc5048efb1
|
||||
F test/backcompat.test 0f23ff8d516acdf42f3d866a66d85306de2d02bc
|
||||
F test/backup.test 004d3b78bffd990741ab50133ed4347c25c172b1
|
||||
F test/backup2.test b7c69f937c912e85ac8a5dbd1e1cf290302b2d49
|
||||
F test/backup_ioerr.test 1f012e692f42c0442ae652443258f70e9f20fa38
|
||||
F test/backup_malloc.test 7162d604ec2b4683c4b3799a48657fb8b5e2d450
|
||||
F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f
|
||||
F test/badutf2.test f5bc7f2d280670ecd79b9cf4f0f1760c607fe51f
|
||||
F test/between.test 16b1776c6323faadb097a52d673e8e3d8be7d070
|
||||
F test/bigfile.test a8ec8073a20207456dab01a29ad9cde42b0dd103
|
||||
F test/bigrow.test f0aeb7573dcb8caaafea76454be3ade29b7fc747
|
||||
@@ -297,11 +306,11 @@ F test/boundary4.test 89e02fa66397b8a325d5eb102b5806f961f8ec4b
|
||||
F test/busy.test 76b4887f8b9160ba903c1ac22e8ff406ad6ae2f0
|
||||
F test/cache.test 754baab2f18089fc9bcba7afaeb4dc907c6c6de2
|
||||
F test/capi2.test 835d4cee9f542ea50fa8d01f3fe6de80b0627360
|
||||
F test/capi3.test 1945a2ba75e3f4c49d5beb8fc092115b6292d471
|
||||
F test/capi3.test 5c1ea6c940f2d7c4d5af8ef1ec2f92a267d2e37a
|
||||
F test/capi3b.test efb2b9cfd127efa84433cd7a2d72ce0454ae0dc4
|
||||
F test/capi3c.test bea67403a5e37a4b33230ee4723e315a2ffb31e7
|
||||
F test/capi3d.test cd36571f014f34bdc4421967f6453cbb597d5d16
|
||||
F test/capi3e.test 4fda47388ddfbfe807987aa62f46fcbceec9327f
|
||||
F test/capi3e.test f7408dda65c92b9056199fdc180f893015f83dde
|
||||
F test/cast.test 166951664a0b0a2e0f8fb5997a152490c6363932
|
||||
F test/check.test db2b29d557544347d28e25b8406f5d5ecc3d1bc3
|
||||
F test/coalesce.test cee0dccb9fbd2d494b77234bccf9dc6c6786eb91
|
||||
@@ -320,17 +329,17 @@ F test/colname.test 08948a4809d22817e0e5de89c7c0a8bd90cb551b
|
||||
F test/conflict.test cabc41f7616675df71b4fddabca3bd5d9221915a
|
||||
F test/corrupt.test 1a5bef8b2d178859af69814ecedcd37219a89968
|
||||
F test/corrupt2.test 808a28d0ca3b97e9aa8c91cd2b485ea2700b76d1
|
||||
F test/corrupt3.test a399dacccb91c732f6b071c913e70d195af8c058
|
||||
F test/corrupt3.test e3006aaf579d2ed7f1b94bf4cc695d3c784fa5af
|
||||
F test/corrupt4.test b963f9e01e0f92d15c76fb0747876fd4b96dc30a
|
||||
F test/corrupt5.test c23da7bfb20917cc7fdbb13ee25c7cc4e9fffeff
|
||||
F test/corrupt6.test 4e4161aef1f30b9f34582bb4142334b7f47eacae
|
||||
F test/corrupt7.test a90caf89c7d7cb7893ea4d92529bd0c129317ee4
|
||||
F test/corrupt8.test 48eb37ffb9a03bceada62219e2bd4c92f4b0cb75
|
||||
F test/corrupt9.test fad0bc26a5c972580a8d763c62f24094f4e8ef25
|
||||
F test/corruptA.test 38b4f81c16099f6d8fa8b37e188fde76b8243994
|
||||
F test/corruptA.test 856ea7a2eb5c1c767abbdf02679ac6cb158e4643
|
||||
F test/corruptB.test 44133515cf46c4d7bba691e3bcfa478080413af0
|
||||
F test/corruptC.test 483aa35dadfd96bdf549e38d75ffc2942576477e
|
||||
F test/corruptD.test 3ae6e2dc6e2226c6935a8a40d4b5ee3eba75f8c0
|
||||
F test/corruptD.test 63a449a3146b460231535d04a409d14ce71795ad
|
||||
F test/corruptE.test 7290b61145d954be549340e462ca84826d8a31a3
|
||||
F test/count.test 454e1ce985c94d13efeac405ce54439f49336163
|
||||
F test/crash.test 1b6ac8410689ff78028887f445062dc897c9ac89
|
||||
@@ -356,11 +365,11 @@ F test/descidx2.test 9f1a0c83fd57f8667c82310ca21b30a350888b5d
|
||||
F test/descidx3.test fe720e8b37d59f4cef808b0bf4e1b391c2e56b6f
|
||||
F test/diskfull.test 0cede7ef9d8f415d9d3944005c76be7589bb5ebb
|
||||
F test/distinctagg.test 1a6ef9c87a58669438fc771450d7a72577417376
|
||||
F test/e_createtable.test b8f5286879315d5b7f4cc5ead1afda4846f0c0bb
|
||||
F test/e_createtable.test b40fc61bc4f1ad2a3c84590bd1d711507263d921
|
||||
F test/e_delete.test 55d868b647acc091c261a10b9b0cb0ab660a6acb
|
||||
F test/e_droptrigger.test ddd4b28ed8a3d81bd5153fa0ab7559529a2ca03a
|
||||
F test/e_dropview.test b347bab30fc8de67b131594b3cd6f3d3bdaa753d
|
||||
F test/e_expr.test 4e004d1f5187d4bbc9ca3d55660a8d164dd59f4e
|
||||
F test/e_expr.test 9e8b9790803df4de23c2d68d566959934a6179d4
|
||||
F test/e_fkey.test 38039b840ab19331000b0f0eb1d82baa7208a67a
|
||||
F test/e_fts3.test 75bb0aee26384ef586165e21018a17f7cd843469
|
||||
F test/e_insert.test 7390c2da39f16a134dc9a439144768c727757d2c
|
||||
@@ -369,23 +378,25 @@ F test/e_resolve.test dcce9308fb13b934ce29591105d031d3e14fbba6
|
||||
F test/e_select.test bf385ae3aa0f014c4933ae66fd3e1302138493eb
|
||||
F test/e_select2.test 5c3d3da19c7b3e90ae444579db2b70098599ab92
|
||||
F test/e_update.test 963d6876064e65f318d1c93aaed36a02b9b389bf
|
||||
F test/e_vacuum.test 057cc29445746fc1d2542984ff0253d511a234bd
|
||||
F test/e_vacuum.test 6c09c2af7f2f140518f371c5342100118f779dcf
|
||||
F test/enc.test e54531cd6bf941ee6760be041dff19a104c7acea
|
||||
F test/enc2.test 6d91a5286f59add0cfcbb2d0da913b76f2242398
|
||||
F test/enc3.test 5c550d59ff31dccdba5d1a02ae11c7047d77c041
|
||||
F test/enc4.test 4b575ef09e0eff896e73bd24076f96c2aa6a42de
|
||||
F test/eqp.test 69670e7919030f21de29fb99bf1d68f97aedcbdb
|
||||
F test/eqp.test f14fadd76da53405e9885e2431cacf7191d83cdb
|
||||
F test/eval.test bc269c365ba877554948441e91ad5373f9f91be3
|
||||
F test/exclusive.test 53e1841b422e554cecf0160f937c473d6d0e3062
|
||||
F test/exclusive2.test b65264c3e76e1db6c6eda15c02000a40743f6541
|
||||
F test/exclusive2.test 343d55130c12c67b8bf10407acec043a6c26c86b
|
||||
F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7
|
||||
F test/expr.test 620a636cf7b7d4e5834a0b9d83a4da372e24a7b7
|
||||
F test/exists.test 5e2d64b4eb5a9d08876599bdae2e1213d2d12e2a
|
||||
F test/expr.test 19e8ac40313e2282a47b586d11c4892040990d3a
|
||||
F test/fallocate.test 43dc34b8c24be6baffadc3b4401ee15710ce83c6
|
||||
F test/filectrl.test 97003734290887566e01dded09dc9e99cb937e9e
|
||||
F test/filefmt.test f77c92141960b7933bc6691631d2ad62257ef40a
|
||||
F test/filefmt.test f178cfc29501a14565954c961b226e61877dd32c
|
||||
F test/fkey1.test 01c7de578e11747e720c2d9aeef27f239853c4da
|
||||
F test/fkey2.test 080969fe219b3b082b0e097ac18c6af2e5b0631f
|
||||
F test/fkey3.test 42f88d6048d8dc079e2a8cf7baad1cc1483a7620
|
||||
F test/fkey4.test c6c8f9f9be885f95c85c7bceb26f243ad906fd49
|
||||
F test/fkey_malloc.test a5ede29bd2f6e56dea78c3d43fb86dd696c068c8
|
||||
F test/format4.test 1f0cac8ff3895e9359ed87e41aaabee982a812eb
|
||||
F test/fts1a.test 46090311f85da51bb33bd5ce84f7948359c6d8d7
|
||||
@@ -440,18 +451,21 @@ F test/fts3am.test 218aa6ba0dfc50c7c16b2022aac5c6be593d08d8
|
||||
F test/fts3an.test a49ccadc07a2f7d646ec1b81bc09da2d85a85b18
|
||||
F test/fts3ao.test b83f99f70e9eec85f27d75801a974b3f820e01f9
|
||||
F test/fts3atoken.test 25c2070e1e8755d414bf9c8200427b277a9f99fa
|
||||
F test/fts3aux1.test 719c35cbbcc04dde8e5a54a6f69851a0af9ed1f2
|
||||
F test/fts3b.test e93bbb653e52afde110ad53bbd793f14fe7a8984
|
||||
F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958
|
||||
F test/fts3corrupt.test d874ba27975aa8e5514bf58bf97b473404de0dbb
|
||||
F test/fts3comp1.test a0f5b16a2df44dd0b15751787130af2183167c0c
|
||||
F test/fts3corrupt.test 7890cc202406858386ddf390a879dcf80bc10abf
|
||||
F test/fts3corrupt2.test 6d96efae2f8a6af3eeaf283aba437e6d0e5447ba
|
||||
F test/fts3cov.test e0fb00d8b715ddae4a94c305992dfc3ef70353d7
|
||||
F test/fts3d.test 95fb3c862cbc4297c93fceb9a635543744e9ef52
|
||||
F test/fts3defer.test d6cb0db9b5997ecf863d96ff419f83f8f2c87f4f
|
||||
F test/fts3defer2.test da840efaedebfdd54293d04b36098e2d9872caa6
|
||||
F test/fts3defer2.test 288bef6de15557319b8c12d476ebdc83688ef96c
|
||||
F test/fts3e.test 1f6c6ac9cc8b772ca256e6b22aaeed50c9350851
|
||||
F test/fts3expr.test 5e745b2b6348499d9ef8d59015de3182072c564c
|
||||
F test/fts3expr2.test 18da930352e5693eaa163a3eacf96233b7290d1a
|
||||
F test/fts3fault.test f83e556465bb69dc8bc676339eca408dce4ca246
|
||||
F test/fts3fault2.test dc96203af6ba31ce20163fc35460e1556e8edf4d
|
||||
F test/fts3malloc.test 9c8cc3f885bb4dfc66d0460c52f68f45e4710d1b
|
||||
F test/fts3matchinfo.test cc0b009edbbf575283d5fdb53271179e0d8019ba
|
||||
F test/fts3near.test 2e318ee434d32babd27c167142e2b94ddbab4844
|
||||
@@ -468,6 +482,7 @@ F test/fuzz2.test 207d0f9d06db3eaf47a6b7bfc835b8e2fc397167
|
||||
F test/fuzz3.test aec64345184d1662bd30e6a17851ff659d596dc5
|
||||
F test/fuzz_common.tcl a87dfbb88c2a6b08a38e9a070dabd129e617b45b
|
||||
F test/fuzz_malloc.test dd7001ac86d09c154a7dff064f4739c60e2b312c
|
||||
F test/fuzzer1.test 3105b5a89a6cb0d475f0877debec942fe4143462
|
||||
F test/hook.test f04c3412463f8ec117c1c704c74ca0f627ce733a
|
||||
F test/icu.test 70df4faca133254c042d02ae342c0a141f2663f4
|
||||
F test/in.test 19b642bb134308980a92249750ea4ce3f6c75c2d
|
||||
@@ -480,12 +495,12 @@ F test/incrblob3.test aedbb35ea1b6450c33b98f2b6ed98e5020be8dc7
|
||||
F test/incrblob_err.test c577c91d4ed9e8336cdb188b15d6ee2a6fe9604e
|
||||
F test/incrblobfault.test 917c0292224c64a56ef7215fd633a3a82f805be0
|
||||
F test/incrvacuum.test 453d1e490d8f5ad2c9b3a54282a0690d6ae56462
|
||||
F test/incrvacuum2.test 9e22a794899c91b7d8c8e12eaacac8df249faafe
|
||||
F test/incrvacuum2.test ae04573b73ad52179f56e194fff0fbe43b509d23
|
||||
F test/incrvacuum_ioerr.test 57d2f5777ab13fa03b87b262a4ea1bad5cfc0291
|
||||
F test/index.test df7c00c6edd9504ab71c83a9514f1c5ca0fa54d8
|
||||
F test/index.test b5429732b3b983fa810e3ac867d7ca85dae35097
|
||||
F test/index2.test ee83c6b5e3173a3d7137140d945d9a5d4fdfb9d6
|
||||
F test/index3.test 423a25c789fc8cc51aaf2a4370bbdde2d9e9eed7
|
||||
F test/indexedby.test d7367c5a0e8ed8db642824a68126753e0808c706
|
||||
F test/indexedby.test be501e381b82b2f8ab406309ba7aac46e221f4ad
|
||||
F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7
|
||||
F test/insert.test aef273dd1cee84cc92407469e6bd1b3cdcb76908
|
||||
F test/insert2.test 4f3a04d168c728ed5ec2c88842e772606c7ce435
|
||||
@@ -516,7 +531,7 @@ F test/jrnlmode3.test c6522b276ba315fd1416198de6fc1da9e72409fb
|
||||
F test/keyword1.test a2400977a2e4fde43bf33754c2929fda34dbca05
|
||||
F test/lastinsert.test 474d519c68cb79d07ecae56a763aa7f322c72f51
|
||||
F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200
|
||||
F test/like.test 0f64aeaed50b6e3ebaef3af0b3b8f894aed5acca
|
||||
F test/like.test a47f52692aac96ba82508efba74819214cdebc17
|
||||
F test/like2.test 3b2ee13149ba4a8a60b59756f4e5d345573852da
|
||||
F test/limit.test 2db7b3b34fb925b8e847d583d2eb67531d0ce67e
|
||||
F test/loadext.test 0393ce12d9616aa87597dd0ec88181de181f6db0
|
||||
@@ -532,7 +547,7 @@ F test/lock_common.tcl d279887a0ab16cdb6d935c1203e64113c5a000e9
|
||||
F test/lookaside.test 93f07bac140c5bb1d49f3892d2684decafdc7af2
|
||||
F test/main.test 9d7bbfcc1b52c88ba7b2ba6554068ecf9939f252
|
||||
F test/make-where7.tcl 05c16b5d4f5d6512881dfec560cb793915932ef9
|
||||
F test/malloc.test 927e6c8668a1d48c23aa6189bda02aff5a1b83de
|
||||
F test/malloc.test e56c9c3358da2c18385aea15a42dc970913986c2
|
||||
F test/malloc3.test 4128b1e6ffa506103b278ad97af89174f310c7ca
|
||||
F test/malloc4.test 957337613002b7058a85116493a262f679f3a261
|
||||
F test/malloc5.test 4d16d1bb26d2deddd7c4f480deec341f9b2d0e22
|
||||
@@ -552,15 +567,16 @@ F test/mallocH.test 79b65aed612c9b3ed2dcdaa727c85895fd1bfbdb
|
||||
F test/mallocI.test a88c2b9627c8506bf4703d8397420043a786cdb6
|
||||
F test/mallocJ.test b5d1839da331d96223e5f458856f8ffe1366f62e
|
||||
F test/mallocK.test d79968641d1b70d88f6c01bdb9a7eb4a55582cc9
|
||||
F test/malloc_common.tcl 660b82ab528521cc4a48ff6df05ca3b6a00d47c5
|
||||
F test/malloc_common.tcl 50d0ed21eed0ae9548b58935bd29ac89a05a54fa
|
||||
F test/manydb.test b3d3bc4c25657e7f68d157f031eb4db7b3df0d3c
|
||||
F test/memdb.test 0825155b2290e900264daaaf0334b6dfe69ea498
|
||||
F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f
|
||||
F test/memdb.test 708a028d6d373e5b3842e4bdc8ba80998c9a4da6
|
||||
F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2
|
||||
F test/memsubsys1.test 679db68394a5692791737b150852173b3e2fea10
|
||||
F test/memsubsys2.test 72a731225997ad5e8df89fdbeae9224616b6aecc
|
||||
F test/minmax.test 722d80816f7e096bf2c04f4111f1a6c1ba65453d
|
||||
F test/minmax2.test 33504c01a03bd99226144e4b03f7631a274d66e0
|
||||
F test/minmax3.test 66a60eb0f20281b0753249d347c5de0766954cee
|
||||
F test/minmax3.test cc1e8b010136db0d01a6f2a29ba5a9f321034354
|
||||
F test/misc1.test e56baf44656dd68d6475a4b44521045a60241e9b
|
||||
F test/misc2.test a628db7b03e18973e5d446c67696b03de718c9fd
|
||||
F test/misc3.test 72c5dc87a78e7865c5ec7a969fc572913dbe96b6
|
||||
@@ -569,7 +585,7 @@ F test/misc5.test 45b2e3ed5f79af2b4f38ae362eaf4c49674575bd
|
||||
F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91
|
||||
F test/misc7.test 29032efcd3d826fbd409e2a7af873e7939f4a4e3
|
||||
F test/misuse.test 30b3a458e5a70c31e74c291937b6c82204c59f33
|
||||
F test/multiplex.test 92a4839213fd8cba8b59f86d42b7a1da1857db39
|
||||
F test/multiplex.test a88f3e2c16e567e72be7296195c59fbdd6a8d3d4
|
||||
F test/mutex1.test 78b2b9bb320e51d156c4efdb71b99b051e7a4b41
|
||||
F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660
|
||||
F test/nan.test a44e04df1486fcfb02d32468cbcd3c8e1e433723
|
||||
@@ -579,17 +595,18 @@ F test/notify3.test d60923e186e0900f4812a845fcdfd8eea096e33a
|
||||
F test/notnull.test cc7c78340328e6112a13c3e311a9ab3127114347
|
||||
F test/null.test a8b09b8ed87852742343b33441a9240022108993
|
||||
F test/openv2.test af02ed0a9cbc0d2a61b8f35171d4d117e588e4ec
|
||||
F test/pager1.test 7006a8b5dd3df1fe0d51d7da014333d7dc099778
|
||||
F test/oserror.test 498d8337e9d15543eb7b004fef8594bf204ff43c
|
||||
F test/pager1.test d8672fd0af5f4f9b99b06283d00f01547809bebe
|
||||
F test/pager2.test 745b911dde3d1f24ae0870bd433dfa83d7c658c1
|
||||
F test/pager3.test 3856d9c80839be0668efee1b74811b1b7f7fc95f
|
||||
F test/pagerfault.test 9de4d3e0c59970b4c6cb8dac511fa242f335d8a7
|
||||
F test/pagerfault2.test 1f79ea40d1133b2683a2f811b00f2399f7ec2401
|
||||
F test/pagerfault3.test 9b413f48a3e9a9a8c26968118f8db19fd7bfb8c7
|
||||
F test/pagerfault3.test f16e2efcb5fc9996d1356f7cbc44c998318ae1d7
|
||||
F test/pageropt.test 8146bf448cf09e87bb1867c2217b921fb5857806
|
||||
F test/pagesize.test 76aa9f23ecb0741a4ed9d2e16c5fa82671f28efb
|
||||
F test/pcache.test 065aa286e722ab24f2e51792c1f093bf60656b16
|
||||
F test/pcache2.test 0d85f2ab6963aee28c671d4c71bec038c00a1d16
|
||||
F test/permutations.test c0ce0f3b741dd92a6d4c2671dbacba4b92dd81eb
|
||||
F test/permutations.test 5b2a4cb756ffb2407cb4743163668d1d769febb6
|
||||
F test/pragma.test fdfc09067ea104a0c247a1a79d8093b56656f850
|
||||
F test/pragma2.test 5364893491b9231dd170e3459bfc2e2342658b47
|
||||
F test/printf.test 05970cde31b1a9f54bd75af60597be75a5c54fea
|
||||
@@ -603,7 +620,7 @@ F test/randexpr1.test 1084050991e9ba22c1c10edd8d84673b501cc25a
|
||||
F test/rdonly.test c267d050a1d9a6a321de502b737daf28821a518d
|
||||
F test/reindex.test 44edd3966b474468b823d481eafef0c305022254
|
||||
F test/releasetest.mk 2eced2f9ae701fd0a29e714a241760503ccba25a
|
||||
F test/releasetest.tcl 627ccd04a113a193c375594bd5d6d051d8220658
|
||||
F test/releasetest.tcl c0c0865f1dff08dde08a964ef49e83217ebedbf8
|
||||
F test/rollback.test 1a83118ea6db4e7d8c10eaa63871b5e90502ffdc
|
||||
F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81
|
||||
F test/rowid.test e58e0acef38b527ed1b0b70d3ada588f804af287
|
||||
@@ -659,26 +676,28 @@ F test/stmt.test 25d64e3dbf9a3ce89558667d7f39d966fe2a71b9
|
||||
F test/subquery.test b524f57c9574b2c0347045b4510ef795d4686796
|
||||
F test/subselect.test d24fd8757daf97dafd2e889c73ea4c4272dcf4e4
|
||||
F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a
|
||||
F test/superlock.test 8468e057d8a5531ff99e504e77fcc585a0291bf2
|
||||
F test/superlock.test 5d7a4954b0059c903f82c7b67867bc5451a7c082
|
||||
F test/sync.test ded6b39d8d8ca3c0c5518516c6371b3316d3e3a3
|
||||
F test/syscall.test 707c95e4ab7863e13f1293c6b0c76bead30249b3
|
||||
F test/sysfault.test c79441d88d23696fbec7b147dba98d42a04f523f
|
||||
F test/table.test 04ba066432430657712d167ebf28080fe878d305
|
||||
F test/tableapi.test 7262a8cbaa9965d429f1cbd2747edc185fa56516
|
||||
F test/tableapi.test 2674633fa95d80da917571ebdd759a14d9819126
|
||||
F test/tclsqlite.test 8c154101e704170c2be10f137a5499ac2c6da8d3
|
||||
F test/tempdb.test 19d0f66e2e3eeffd68661a11c83ba5e6ace9128c
|
||||
F test/temptable.test f42121a0d29a62f00f93274464164177ab1cc24a
|
||||
F test/temptrigger.test b0273db072ce5f37cf19140ceb1f0d524bbe9f05
|
||||
F test/tester.tcl dafe0d30279f6d380d5d2a535781dda91b8cfc3f
|
||||
F test/tester.tcl 6fa3d2f581b479a3a088b1b5b0d145e548ebe662
|
||||
F test/thread001.test a3e6a7254d1cb057836cb3145b60c10bf5b7e60f
|
||||
F test/thread002.test afd20095e6e845b405df4f2c920cb93301ca69db
|
||||
F test/thread003.test b824d4f52b870ae39fc5bae4d8070eca73085dca
|
||||
F test/thread004.test f51dfc3936184aaf73ee85f315224baad272a87f
|
||||
F test/thread005.test bf5c374ca65dd89fd56c8fe511ccfb46875bda5e
|
||||
F test/thread1.test 862dd006d189e8b0946935db17399dcac2f8ef91
|
||||
F test/thread2.test e08034b83fe9693ade77049732518e5b3d2d700d
|
||||
F test/thread_common.tcl 2aa6f2fdcd4d6e461169c3e5ca098eebf643b863
|
||||
F test/thread1.test df115faa10a4ba1d456e9d4d9ec165016903eae4
|
||||
F test/thread2.test f35d2106452b77523b3a2b7d1dcde2e5ee8f9e46
|
||||
F test/thread_common.tcl 334639cadcb9f912bf82aa73f49efd5282e6cadd
|
||||
F test/threadtest1.c 6029d9c5567db28e6dc908a0c63099c3ba6c383b
|
||||
F test/threadtest2.c ace893054fa134af3fc8d6e7cfecddb8e3acefb9
|
||||
F test/threadtest3.c d6d209190c7110f9a7e6a8154bdc3260efdbf8b7
|
||||
F test/threadtest3.c 0ed13e09690f6204d7455fac3b0e8ece490f6eef
|
||||
F test/tkt-02a8e81d44.test 58494de77be2cf249228ada3f313fa399821c6ab
|
||||
F test/tkt-26ff0c2d1e.test 888324e751512972c6e0d1a09df740d8f5aaf660
|
||||
F test/tkt-2ea2425d34.test 1cf13e6f75d149b3209a0cb32927a82d3d79fb28
|
||||
@@ -691,6 +710,7 @@ F test/tkt-4a03edc4c8.test 2865e4edbc075b954daa82f8da7cc973033ec76e
|
||||
F test/tkt-5d863f876e.test 884072c2de496ddbb90c387c9ebc0d4f44a91b8e
|
||||
F test/tkt-5e10420e8d.test 904d1687b3c06d43e5b3555bbcf6802e7c0ffd84
|
||||
F test/tkt-5ee23731f.test 3581260f2a71e51db94e1506ba6b0f7311d002a9
|
||||
F test/tkt-752e1646fc.test ea78d88d14fe9866bdd991c634483334639e13bf
|
||||
F test/tkt-78e04e52ea.test ab52f0c1e2de6e46c910f4cc16b086bba05952b7
|
||||
F test/tkt-80ba201079.test a09684db1a0bd55b8838f606adccee456a51ddbf
|
||||
F test/tkt-80e031a00f.test 9a154173461a4dbe2de49cda73963e04842d52f7
|
||||
@@ -698,11 +718,13 @@ F test/tkt-8454a207b9.test c583a9f814a82a2b5ba95207f55001c9f0cd816c
|
||||
F test/tkt-94c04eaadb.test be5ea61cb04dfdc047d19b5c5a9e75fa3da67a7f
|
||||
F test/tkt-9d68c883.test 458f7d82a523d7644b54b497c986378a7d8c8b67
|
||||
F test/tkt-b351d95f9.test d14a503c414c5c58fdde3e80f9a3cfef986498c0
|
||||
F test/tkt-b72787b1.test e6b62b2b2785c04d0d698d6a603507e384165049
|
||||
F test/tkt-cbd054fa6b.test f14f97ea43662e6f70c9e63287081e8be5d9d589
|
||||
F test/tkt-d11f09d36e.test fb44f7961aa6d4b632fb7b9768239832210b5fc7
|
||||
F test/tkt-d82e3f3721.test 731359dfdcdb36fea0559cd33fec39dd0ceae8e6
|
||||
F test/tkt-f3e5abed55.test 19fb59268da6f20a69a181b9c14154132d1c65e3
|
||||
F test/tkt-f777251dc7a.test 6f24c053bc5cdb7e1e19be9a72c8887cf41d5e87
|
||||
F test/tkt-f7b4edec.test d998a08ff2b18b7f62edce8e3044317c45efe6c7
|
||||
F test/tkt-f973c7ac31.test 1da0ed15ec2c7749fb5ce2828cd69d07153ad9f4
|
||||
F test/tkt-fc62af4523.test 72825d3febdedcd5593a27989fc05accdbfc2bb4
|
||||
F test/tkt1435.test f8c52c41de6e5ca02f1845f3a46e18e25cadac00
|
||||
@@ -774,7 +796,7 @@ F test/tkt3773.test 430b06567ce40285dfd2c4834a2a61816403efeb
|
||||
F test/tkt3791.test a6624b9a80b216a26cf473607f42f3e51898c267
|
||||
F test/tkt3793.test 754b73f0e6a9349c70dc57e522cf3247272ecd5d
|
||||
F test/tkt3810.test 90fa0635dfa7da9680c8cd3513350a49b3a8ae12
|
||||
F test/tkt3824.test 3da2f5c81b057e3ff355f5dfc9aa0cf0a92e0206
|
||||
F test/tkt3824.test 150aa00bb6220672e5f0eb14dc8eaa36750425f0
|
||||
F test/tkt3832.test 2300d10d57562b89875b72148338ac3e14f8847d
|
||||
F test/tkt3838.test f956f0719b5f805b12dd1dbf19f19d298bacebc3
|
||||
F test/tkt3841.test 4659845bc53f809a5932c61c6ce8c5bb9d6b947f
|
||||
@@ -790,7 +812,7 @@ F test/tkt3997.test a335fa41ca3985660a139df7b734a26ef53284bd
|
||||
F test/tkt4018.test 7c2c9ba4df489c676a0a7a0e809a1fb9b2185bd1
|
||||
F test/tokenize.test ce430a7aed48fc98301611429595883fdfcab5d7
|
||||
F test/trace.test 4b36a41a3e9c7842151af6da5998f5080cdad9e5
|
||||
F test/trace2.test 092bc2c5776272700450d60a36919921095bdc21
|
||||
F test/trace2.test 0ce11265c83333d8f5beeca19e71ed93a88d386c
|
||||
F test/trans.test 6e1b4c6a42dba31bd65f8fa5e61a2708e08ddde6
|
||||
F test/trans2.test d5337e61de45e66b1fcbf9db833fa8c82e624b22
|
||||
F test/trans3.test d728abaa318ca364dc370e06576aa7e5fbed7e97
|
||||
@@ -807,14 +829,17 @@ F test/triggerA.test eaf11a29db2a11967d2d4b49d37f92bce598194e
|
||||
F test/triggerB.test 56780c031b454abac2340dbb3b71ac5c56c3d7fe
|
||||
F test/triggerC.test 8a691ff6dd47df2e57395bbec4b62101fac0f363
|
||||
F test/triggerD.test c6add3817351451e419f6ff9e9a259b02b6e2de7
|
||||
F test/tt3_checkpoint.c 415eccce672d681b297485fc20f44cdf0eac93af
|
||||
F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff
|
||||
F test/types2.test 3555aacf8ed8dc883356e59efc314707e6247a84
|
||||
F test/types3.test a0f66bf12f80fad89493535474f7a6d16fa58150
|
||||
F test/unique.test 083c7fff74695bcc27a71d75699deba3595bc9c2
|
||||
F test/unixexcl.test 9d80a54d86d2261f660758928959368ffc36151e
|
||||
F test/unordered.test c479d3027f9c4db05b44b83010735c6708abcc91
|
||||
F test/update.test 8bc86fd7ef1a00014f76dc6a6a7c974df4aef172
|
||||
F test/utf16align.test 54cd35a27c005a9b6e7815d887718780b6a462ae
|
||||
F test/vacuum.test 29b60e8cc9e573b39676df6c4a75fe9e02d04a09
|
||||
F test/vacuum2.test 2165164ed2463816e8c4648d0a779a863ce1a76c
|
||||
F test/vacuum2.test 91a84c9b08adfc4472097d2e8deb0150214e0e76
|
||||
F test/vacuum3.test f39ad1428347c5808cd2da7578c470f186a4d0ce
|
||||
F test/vacuum4.test d3f8ecff345f166911568f397d2432c16d2867d9
|
||||
F test/varint.test ab7b110089a08b9926ed7390e7e97bdefeb74102
|
||||
@@ -837,18 +862,19 @@ F test/vtabE.test 7c4693638d7797ce2eda17af74292b97e705cc61
|
||||
F test/vtab_alter.test 9e374885248f69e251bdaacf480b04a197f125e5
|
||||
F test/vtab_err.test 0d4d8eb4def1d053ac7c5050df3024fd47a3fbd8
|
||||
F test/vtab_shared.test 0eff9ce4f19facbe0a3e693f6c14b80711a4222d
|
||||
F test/wal.test 70227190e713b3e7eb2a7d5ec3510b66db01f327
|
||||
F test/wal2.test 3de797854de175323e7351b5f2514a30d1ee1410
|
||||
F test/wal3.test ac51126c36814bce334f66a0a4dbbfa56d429733
|
||||
F test/wal.test 5617ad308bfdb8a8885220d8a261a6096a8d7e57
|
||||
F test/wal2.test e561a8c6fdd1c2cd1876f3e39757934e7b7361f8
|
||||
F test/wal3.test 5c396cc22497244d627306f4c1d360167353f8dd
|
||||
F test/wal4.test 3404b048fa5e10605facaf70384e6d2943412e30
|
||||
F test/wal5.test 1bbfaa316dc2a1d0d1fac3f4500c38a90055a41b
|
||||
F test/wal6.test 07aa31ca8892d0527f2c5c5a9a2a87aa421dfaa8
|
||||
F test/wal_common.tcl 895d76138043b86bdccf36494054bdabcf65837b
|
||||
F test/wal_common.tcl a98f17fba96206122eff624db0ab13ec377be4fe
|
||||
F test/walbak.test 4df1c7369da0301caeb9a48fa45997fd592380e4
|
||||
F test/walbig.test e882bc1d014afffbfa2b6ba36e0f07d30a633ad0
|
||||
F test/walcksum.test a37b36375c595e61bdb7e1ec49b5f0979b6fc7ce
|
||||
F test/walcrash.test e763841551d6b23677ccb419797c1589dcbdbaf5
|
||||
F test/walcrash2.test 019d60b89d96c1937adb2b30b850ac7e86e5a142
|
||||
F test/walfault.test 81ed760def1c1573151d416b0d09178cf006f9fd
|
||||
F test/walfault.test 58fce626359c9376fe35101b5c0f2df8040aa839
|
||||
F test/walhook.test ed00a40ba7255da22d6b66433ab61fab16a63483
|
||||
F test/walmode.test 22ddccd073c817ac9ead62b88ac446e8dedc7d2c
|
||||
F test/walnoshm.test a074428046408f4eb5c6a00e09df8cc97ff93317
|
||||
@@ -857,14 +883,14 @@ F test/walslow.test d21625e2e99e11c032ce949e8a94661576548933
|
||||
F test/walthread.test a25a393c068a2b42b44333fa3fdaae9072f1617c
|
||||
F test/where.test de337a3fe0a459ec7c93db16a519657a90552330
|
||||
F test/where2.test 43d4becaf5a5df854e6c21d624a1cb84c6904554
|
||||
F test/where3.test 8ebedae552e13fc7f2b4e8df6cbe72a095347400
|
||||
F test/where3.test 8e1175c7ef710c70502858fc4fb08d784b3620b9
|
||||
F test/where4.test e9b9e2f2f98f00379e6031db6a6fca29bae782a2
|
||||
F test/where5.test fdf66f96d29a064b63eb543e28da4dfdccd81ad2
|
||||
F test/where6.test 5da5a98cec820d488e82708301b96cb8c18a258b
|
||||
F test/where7.test aa4cfcd6f66e2a4ef87b6717327325bf4d547502
|
||||
F test/where8.test a6c740fd286d7883e274e17b6230a9d672a7ab1f
|
||||
F test/where8m.test da346596e19d54f0aba35ebade032a7c47d79739
|
||||
F test/where9.test 7ee38c3fd67e76789a6ec769f62f6433d3d4a5cf
|
||||
F test/where9.test 24f19ad14bb1b831564ced5273e681e495662848
|
||||
F test/whereA.test 24c234263c8fe358f079d5e57d884fb569d2da0a
|
||||
F test/whereB.test 0def95db3bdec220a731c7e4bec5930327c1d8c5
|
||||
F test/wherelimit.test 5e9fd41e79bb2b2d588ed999d641d9c965619b31
|
||||
@@ -878,13 +904,14 @@ F tool/lempar.c 01ca97f87610d1dac6d8cd96ab109ab1130e76dc
|
||||
F tool/mkkeywordhash.c d2e6b4a5965e23afb80fbe74bb54648cd371f309
|
||||
F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e
|
||||
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
|
||||
F tool/mksqlite3c.tcl e0db70c2c52b0e3d0867ca931229e5b90ffe7837
|
||||
F tool/mksqlite3c.tcl 623e26cc8c83322e4151d3ad85ac69d41221bae8
|
||||
F tool/mksqlite3h.tcl d76c226a5e8e1f3b5f6593bcabe5e98b3b1ec9ff
|
||||
F tool/mksqlite3internalh.tcl 7b43894e21bcb1bb39e11547ce7e38a063357e87
|
||||
F tool/omittest.tcl 27d6f6e3b1e95aeb26a1c140e6eb57771c6d794a
|
||||
F tool/omittest.tcl b1dd290c1596e0f31fd335160a74ec5dfea3df4a
|
||||
F tool/opcodeDoc.awk b3a2a3d5d3075b8bd90b7afe24283efdd586659c
|
||||
F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a
|
||||
F tool/shell1.test c31b0814a9c543db51ca0cc63edb5e77ea532303
|
||||
F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5
|
||||
F tool/shell1.test 7a389c6aaad05621be39501d6f8db410da464dcd
|
||||
F tool/shell2.test 5dc76b8005b465f420fed8241621da7513060ff3
|
||||
F tool/shell3.test 4fad469e8003938426355afdf34155f08c587836
|
||||
F tool/shell4.test 35f9c3d452b4e76d5013c63e1fd07478a62f14ce
|
||||
@@ -900,15 +927,9 @@ F tool/speedtest16.c c8a9c793df96db7e4933f0852abb7a03d48f2e81
|
||||
F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
|
||||
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
|
||||
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
|
||||
F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c
|
||||
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
|
||||
P 682fe41efd3578e8c9abc7138b61f361c3adbe95
|
||||
R 7fccaf9f7c013ddef8926255f6aeb773
|
||||
P c429edf30accac12d5deef10e59cd31146036f11
|
||||
R 050fbfdb8675b7eaafb0b1677e5953e7
|
||||
U drh
|
||||
Z 4223173e58f25d45d236e232b9c90989
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GnuPG v1.4.6 (GNU/Linux)
|
||||
|
||||
iD8DBQFNQvb6oxKgR168RlERAtOTAJ9BaewewKXL3RGZUy5ycaHwjeEmJACeOPbw
|
||||
/JKqOugR+37RH7HnLCo9DBk=
|
||||
=kgPx
|
||||
-----END PGP SIGNATURE-----
|
||||
Z 289c52539a2a59a211a989409cbc5426
|
||||
|
||||
@@ -1 +1 @@
|
||||
ed759d5a9edb3bba5f48f243df47be29e3fe8cd7
|
||||
154ddbc17120be2915eb03edc52af1225eb7cb5e
|
||||
|
||||
@@ -370,6 +370,22 @@ static void reloadTableSchema(Parse *pParse, Table *pTab, const char *zName){
|
||||
#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.
|
||||
@@ -420,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
|
||||
@@ -759,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);
|
||||
|
||||
@@ -34,7 +34,8 @@ static void openStatTable(
|
||||
Parse *pParse, /* Parsing context */
|
||||
int iDb, /* The database we are looking in */
|
||||
int iStatCur, /* Open the sqlite_stat1 table on this cursor */
|
||||
const char *zWhere /* Delete entries associated with this table */
|
||||
const char *zWhere, /* Delete entries for this table or index */
|
||||
const char *zWhereType /* Either "tbl" or "idx" */
|
||||
){
|
||||
static const struct {
|
||||
const char *zName;
|
||||
@@ -79,7 +80,7 @@ static void openStatTable(
|
||||
sqlite3TableLock(pParse, iDb, aRoot[i], 1, zTab);
|
||||
if( zWhere ){
|
||||
sqlite3NestedParse(pParse,
|
||||
"DELETE FROM %Q.%s WHERE tbl=%Q", pDb->zName, zTab, zWhere
|
||||
"DELETE FROM %Q.%s WHERE %s=%Q", pDb->zName, zTab, zWhereType, zWhere
|
||||
);
|
||||
}else{
|
||||
/* The sqlite_stat[12] table already exists. Delete all rows. */
|
||||
@@ -103,6 +104,7 @@ static void openStatTable(
|
||||
static void analyzeOneTable(
|
||||
Parse *pParse, /* Parser context */
|
||||
Table *pTab, /* Table whose indices are to be analyzed */
|
||||
Index *pOnlyIdx, /* If not NULL, only analyze this one index */
|
||||
int iStatCur, /* Index of VdbeCursor that writes the sqlite_stat1 table */
|
||||
int iMem /* Available memory locations begin here */
|
||||
){
|
||||
@@ -113,8 +115,7 @@ static void analyzeOneTable(
|
||||
int i; /* Loop counter */
|
||||
int topOfLoop; /* The top of the loop */
|
||||
int endOfLoop; /* The end of the loop */
|
||||
int addr = 0; /* The address of an instruction */
|
||||
int jZeroRows = 0; /* Jump from here if number of rows is zero */
|
||||
int jZeroRows = -1; /* Jump from here if number of rows is zero */
|
||||
int iDb; /* Index of database containing pTab */
|
||||
int regTabname = iMem++; /* Register containing table name */
|
||||
int regIdxname = iMem++; /* Register containing index name */
|
||||
@@ -125,6 +126,7 @@ static void analyzeOneTable(
|
||||
int regRowid = iMem++; /* Rowid for the inserted record */
|
||||
|
||||
#ifdef SQLITE_ENABLE_STAT2
|
||||
int addr = 0; /* Instruction address */
|
||||
int regTemp2 = iMem++; /* Temporary use register */
|
||||
int regSamplerecno = iMem++; /* Index of next sample to record */
|
||||
int regRecno = iMem++; /* Current sample index */
|
||||
@@ -147,6 +149,7 @@ static void analyzeOneTable(
|
||||
assert( sqlite3BtreeHoldsAllMutexes(db) );
|
||||
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
|
||||
assert( iDb>=0 );
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
#ifndef SQLITE_OMIT_AUTHORIZATION
|
||||
if( sqlite3AuthCheck(pParse, SQLITE_ANALYZE, pTab->zName, 0,
|
||||
db->aDb[iDb].zName ) ){
|
||||
@@ -160,9 +163,12 @@ static void analyzeOneTable(
|
||||
iIdxCur = pParse->nTab++;
|
||||
sqlite3VdbeAddOp4(v, OP_String8, 0, regTabname, 0, pTab->zName, 0);
|
||||
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
|
||||
int nCol = pIdx->nColumn;
|
||||
KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx);
|
||||
int nCol;
|
||||
KeyInfo *pKey;
|
||||
|
||||
if( pOnlyIdx && pOnlyIdx!=pIdx ) continue;
|
||||
nCol = pIdx->nColumn;
|
||||
pKey = sqlite3IndexKeyinfo(pParse, pIdx);
|
||||
if( iMem+1+(nCol*2)>pParse->nMem ){
|
||||
pParse->nMem = iMem+1+(nCol*2);
|
||||
}
|
||||
@@ -319,7 +325,7 @@ static void analyzeOneTable(
|
||||
** is never possible.
|
||||
*/
|
||||
sqlite3VdbeAddOp2(v, OP_SCopy, iMem, regSampleno);
|
||||
if( jZeroRows==0 ){
|
||||
if( jZeroRows<0 ){
|
||||
jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, iMem);
|
||||
}
|
||||
for(i=0; i<nCol; i++){
|
||||
@@ -345,10 +351,10 @@ static void analyzeOneTable(
|
||||
VdbeComment((v, "%s", pTab->zName));
|
||||
sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regSampleno);
|
||||
sqlite3VdbeAddOp1(v, OP_Close, iIdxCur);
|
||||
jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regSampleno);
|
||||
}else{
|
||||
assert( jZeroRows>0 );
|
||||
addr = sqlite3VdbeAddOp0(v, OP_Goto);
|
||||
sqlite3VdbeJumpHere(v, jZeroRows);
|
||||
jZeroRows = sqlite3VdbeAddOp0(v, OP_Goto);
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname);
|
||||
sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regRec, "aaa", 0);
|
||||
@@ -356,9 +362,7 @@ static void analyzeOneTable(
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regRowid);
|
||||
sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
|
||||
if( pParse->nMem<regRec ) pParse->nMem = regRec;
|
||||
if( jZeroRows ){
|
||||
sqlite3VdbeJumpHere(v, addr);
|
||||
}
|
||||
sqlite3VdbeJumpHere(v, jZeroRows);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -385,20 +389,22 @@ static void analyzeDatabase(Parse *pParse, int iDb){
|
||||
sqlite3BeginWriteOperation(pParse, 0, iDb);
|
||||
iStatCur = pParse->nTab;
|
||||
pParse->nTab += 2;
|
||||
openStatTable(pParse, iDb, iStatCur, 0);
|
||||
openStatTable(pParse, iDb, iStatCur, 0, 0);
|
||||
iMem = pParse->nMem+1;
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){
|
||||
Table *pTab = (Table*)sqliteHashData(k);
|
||||
analyzeOneTable(pParse, pTab, iStatCur, iMem);
|
||||
analyzeOneTable(pParse, pTab, 0, iStatCur, iMem);
|
||||
}
|
||||
loadAnalysis(pParse, iDb);
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate code that will do an analysis of a single table in
|
||||
** a database.
|
||||
** a database. If pOnlyIdx is not NULL then it is a single index
|
||||
** in pTab that should be analyzed.
|
||||
*/
|
||||
static void analyzeTable(Parse *pParse, Table *pTab){
|
||||
static void analyzeTable(Parse *pParse, Table *pTab, Index *pOnlyIdx){
|
||||
int iDb;
|
||||
int iStatCur;
|
||||
|
||||
@@ -408,8 +414,12 @@ static void analyzeTable(Parse *pParse, Table *pTab){
|
||||
sqlite3BeginWriteOperation(pParse, 0, iDb);
|
||||
iStatCur = pParse->nTab;
|
||||
pParse->nTab += 2;
|
||||
openStatTable(pParse, iDb, iStatCur, pTab->zName);
|
||||
analyzeOneTable(pParse, pTab, iStatCur, pParse->nMem+1);
|
||||
if( pOnlyIdx ){
|
||||
openStatTable(pParse, iDb, iStatCur, pOnlyIdx->zName, "idx");
|
||||
}else{
|
||||
openStatTable(pParse, iDb, iStatCur, pTab->zName, "tbl");
|
||||
}
|
||||
analyzeOneTable(pParse, pTab, pOnlyIdx, iStatCur, pParse->nMem+1);
|
||||
loadAnalysis(pParse, iDb);
|
||||
}
|
||||
|
||||
@@ -431,6 +441,7 @@ void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){
|
||||
int i;
|
||||
char *z, *zDb;
|
||||
Table *pTab;
|
||||
Index *pIdx;
|
||||
Token *pTableName;
|
||||
|
||||
/* Read the database schema. If an error occurs, leave an error message
|
||||
@@ -455,11 +466,12 @@ void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){
|
||||
}else{
|
||||
z = sqlite3NameFromToken(db, pName1);
|
||||
if( z ){
|
||||
pTab = sqlite3LocateTable(pParse, 0, z, 0);
|
||||
sqlite3DbFree(db, z);
|
||||
if( pTab ){
|
||||
analyzeTable(pParse, pTab);
|
||||
if( (pIdx = sqlite3FindIndex(db, z, 0))!=0 ){
|
||||
analyzeTable(pParse, pIdx->pTable, pIdx);
|
||||
}else if( (pTab = sqlite3LocateTable(pParse, 0, z, 0))!=0 ){
|
||||
analyzeTable(pParse, pTab, 0);
|
||||
}
|
||||
sqlite3DbFree(db, z);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
@@ -469,11 +481,12 @@ void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){
|
||||
zDb = db->aDb[iDb].zName;
|
||||
z = sqlite3NameFromToken(db, pTableName);
|
||||
if( z ){
|
||||
pTab = sqlite3LocateTable(pParse, 0, z, zDb);
|
||||
sqlite3DbFree(db, z);
|
||||
if( pTab ){
|
||||
analyzeTable(pParse, pTab);
|
||||
if( (pIdx = sqlite3FindIndex(db, z, zDb))!=0 ){
|
||||
analyzeTable(pParse, pIdx->pTable, pIdx);
|
||||
}else if( (pTab = sqlite3LocateTable(pParse, 0, z, zDb))!=0 ){
|
||||
analyzeTable(pParse, pTab, 0);
|
||||
}
|
||||
sqlite3DbFree(db, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -535,6 +548,10 @@ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){
|
||||
if( pIndex==0 ) break;
|
||||
pIndex->aiRowEst[i] = v;
|
||||
if( *z==' ' ) z++;
|
||||
if( memcmp(z, "unordered", 10)==0 ){
|
||||
pIndex->bUnordered = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -589,9 +606,9 @@ int sqlite3AnalysisLoad(sqlite3 *db, int iDb){
|
||||
|
||||
assert( iDb>=0 && iDb<db->nDb );
|
||||
assert( db->aDb[iDb].pBt!=0 );
|
||||
assert( sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) );
|
||||
|
||||
/* Clear any prior statistics */
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){
|
||||
Index *pIdx = sqliteHashData(i);
|
||||
sqlite3DefaultRowEst(pIdx);
|
||||
|
||||
@@ -176,7 +176,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;
|
||||
}
|
||||
}
|
||||
@@ -200,7 +202,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;
|
||||
@@ -272,7 +274,7 @@ static void detachFunc(
|
||||
sqlite3BtreeClose(pDb->pBt);
|
||||
pDb->pBt = 0;
|
||||
pDb->pSchema = 0;
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
return;
|
||||
|
||||
detach_error:
|
||||
@@ -312,9 +314,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 ){
|
||||
|
||||
@@ -219,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;
|
||||
@@ -237,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
|
||||
@@ -401,7 +416,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
|
||||
int nDestTruncate;
|
||||
|
||||
if( p->pDestDb ){
|
||||
sqlite3ResetInternalSchema(p->pDestDb, 0);
|
||||
sqlite3ResetInternalSchema(p->pDestDb, -1);
|
||||
}
|
||||
|
||||
/* Set nDestTruncate to the final number of pages in the destination
|
||||
@@ -488,7 +503,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
|
||||
|
||||
/* Finish committing the transaction to the destination database. */
|
||||
if( SQLITE_OK==rc
|
||||
&& SQLITE_OK==(rc = sqlite3BtreeCommitPhaseTwo(p->pDest))
|
||||
&& SQLITE_OK==(rc = sqlite3BtreeCommitPhaseTwo(p->pDest, 0))
|
||||
){
|
||||
rc = SQLITE_DONE;
|
||||
}
|
||||
@@ -502,7 +517,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 );
|
||||
}
|
||||
|
||||
@@ -607,7 +622,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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1228,7 +1228,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;
|
||||
}
|
||||
@@ -1249,7 +1249,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 */
|
||||
|
||||
@@ -1292,7 +1292,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 ){
|
||||
@@ -2127,7 +2127,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
|
||||
@@ -2182,6 +2181,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
|
||||
@@ -2381,7 +2381,7 @@ static int lockBtree(BtShared *pBt){
|
||||
pageSize-usableSize);
|
||||
return rc;
|
||||
}
|
||||
if( (pBt->db->flags & SQLITE_RecoveryMode)==0 && nPageHeader>nPageFile ){
|
||||
if( (pBt->db->flags & SQLITE_RecoveryMode)==0 && nPage>nPageFile ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
goto page1_init_failed;
|
||||
}
|
||||
@@ -3160,10 +3160,21 @@ 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){
|
||||
int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){
|
||||
|
||||
if( p->inTrans==TRANS_NONE ) return SQLITE_OK;
|
||||
sqlite3BtreeEnter(p);
|
||||
@@ -3178,7 +3189,7 @@ int sqlite3BtreeCommitPhaseTwo(Btree *p){
|
||||
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;
|
||||
}
|
||||
@@ -3198,7 +3209,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;
|
||||
@@ -4808,7 +4819,7 @@ static int allocateBtreePage(
|
||||
goto end_allocate_page;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -4893,19 +4904,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;
|
||||
@@ -4928,11 +4933,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 ){
|
||||
@@ -5001,6 +5007,7 @@ end_allocate_page:
|
||||
}else{
|
||||
*ppPage = 0;
|
||||
}
|
||||
assert( rc!=SQLITE_OK || sqlite3PagerIswriteable((*ppPage)->pDbPage) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -5490,7 +5497,7 @@ 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);
|
||||
@@ -5533,7 +5540,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() */
|
||||
@@ -5747,7 +5755,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]);
|
||||
@@ -6014,7 +6022,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 );
|
||||
@@ -6179,9 +6187,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;
|
||||
@@ -6260,7 +6266,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) );
|
||||
@@ -6705,7 +6711,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;
|
||||
@@ -6845,7 +6851,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;
|
||||
@@ -7932,8 +7938,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;
|
||||
@@ -7941,7 +7949,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);
|
||||
}
|
||||
@@ -7981,7 +7989,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 *)){
|
||||
|
||||
@@ -39,18 +39,6 @@
|
||||
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(
|
||||
@@ -87,7 +75,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);
|
||||
@@ -207,7 +195,7 @@ void sqlite3BtreeCursorList(Btree*);
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
int sqlite3BtreeCheckpoint(Btree*);
|
||||
int sqlite3BtreeCheckpoint(Btree*, int, int *, int *);
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -224,30 +212,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 {
|
||||
@@ -426,7 +426,7 @@ struct BtShared {
|
||||
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 */
|
||||
|
||||
@@ -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
|
||||
@@ -269,9 +272,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 +337,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 +371,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 +402,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 +523,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 +558,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,
|
||||
@@ -784,6 +813,9 @@ void sqlite3StartTable(
|
||||
if( pTable ){
|
||||
if( !noErr ){
|
||||
sqlite3ErrorMsg(pParse, "table %T already exists", pName);
|
||||
}else{
|
||||
assert( !db->init.busy );
|
||||
sqlite3CodeVerifySchema(pParse, iDb);
|
||||
}
|
||||
goto begin_table_error;
|
||||
}
|
||||
@@ -814,6 +846,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
|
||||
@@ -1274,6 +1307,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);
|
||||
@@ -1381,7 +1415,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 );
|
||||
@@ -1576,6 +1610,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)",
|
||||
@@ -1596,6 +1631,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 ){
|
||||
@@ -1780,6 +1816,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;
|
||||
@@ -1800,6 +1837,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);
|
||||
@@ -1833,10 +1871,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);
|
||||
@@ -1962,6 +2003,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);
|
||||
@@ -2210,6 +2252,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
|
||||
);
|
||||
@@ -2479,6 +2522,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;
|
||||
}
|
||||
@@ -2565,6 +2611,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
|
||||
*/
|
||||
@@ -2694,6 +2741,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);
|
||||
@@ -2870,6 +2918,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;
|
||||
@@ -3442,12 +3492,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;
|
||||
@@ -3458,6 +3509,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.
|
||||
@@ -3474,7 +3540,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;
|
||||
}
|
||||
|
||||
@@ -3574,6 +3640,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)){
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -536,7 +545,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,7 +636,7 @@ int sqlite3GenerateIndexKey(
|
||||
}
|
||||
if( doMakeRec ){
|
||||
sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol+1, regOut);
|
||||
sqlite3VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v, pIdx), 0);
|
||||
sqlite3VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v, pIdx), 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);
|
||||
@@ -614,6 +615,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);
|
||||
@@ -1198,16 +1201,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;
|
||||
@@ -1222,13 +1226,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;
|
||||
}
|
||||
|
||||
@@ -1953,6 +1950,7 @@ 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{
|
||||
@@ -1963,7 +1961,7 @@ static void codeInteger(Parse *pParse, Expr *pExpr, int negFlag, int iMem){
|
||||
c = sqlite3Atoi64(z, &value, sqlite3Strlen30(z), SQLITE_UTF8);
|
||||
if( c==0 || (c==2 && negFlag) ){
|
||||
char *zV;
|
||||
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{
|
||||
@@ -2347,7 +2345,7 @@ 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);
|
||||
sqlite3VdbeChangeP4(v, -1, pExpr->u.zToken, P4_TRANSIENT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3251,6 +3249,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;
|
||||
@@ -3259,6 +3258,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);
|
||||
@@ -3392,6 +3392,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);
|
||||
@@ -3402,6 +3403,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);
|
||||
|
||||
@@ -398,7 +398,7 @@ static void fkLookupParent(
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -687,7 +687,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 +698,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;
|
||||
|
||||
@@ -1156,6 +1154,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. */
|
||||
|
||||
@@ -1239,13 +1239,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]);
|
||||
|
||||
@@ -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) ){
|
||||
@@ -1188,7 +1188,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 +1328,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 +1468,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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -375,6 +375,13 @@ int sqlite3_config(int op, ...){
|
||||
sqlite3GlobalConfig.nHeap = va_arg(ap, int);
|
||||
sqlite3GlobalConfig.mnReq = va_arg(ap, int);
|
||||
|
||||
if( sqlite3GlobalConfig.mnReq<1 ){
|
||||
sqlite3GlobalConfig.mnReq = 1;
|
||||
}else if( sqlite3GlobalConfig.mnReq>(1<<12) ){
|
||||
/* cap min request size at 2^12 */
|
||||
sqlite3GlobalConfig.mnReq = (1<<12);
|
||||
}
|
||||
|
||||
if( sqlite3GlobalConfig.pHeap==0 ){
|
||||
/* If the heap pointer is NULL, then restore the malloc implementation
|
||||
** back to NULL pointers too. This will cause the malloc to go
|
||||
@@ -508,14 +515,42 @@ int sqlite3_db_config(sqlite3 *db, int op, ...){
|
||||
va_start(ap, op);
|
||||
switch( op ){
|
||||
case SQLITE_DBCONFIG_LOOKASIDE: {
|
||||
void *pBuf = va_arg(ap, void*); /* IMP: R-21112-12275 */
|
||||
void *pBuf = va_arg(ap, void*); /* IMP: R-26835-10964 */
|
||||
int sz = va_arg(ap, int); /* IMP: R-47871-25994 */
|
||||
int cnt = va_arg(ap, int); /* IMP: R-04460-53386 */
|
||||
rc = setupLookaside(db, pBuf, sz, cnt);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
static const struct {
|
||||
int op; /* The opcode */
|
||||
u32 mask; /* Mask of the bit in sqlite3.flags to set/clear */
|
||||
} aFlagOp[] = {
|
||||
{ SQLITE_DBCONFIG_ENABLE_FKEY, SQLITE_ForeignKeys },
|
||||
{ SQLITE_DBCONFIG_ENABLE_TRIGGER, SQLITE_EnableTrigger },
|
||||
};
|
||||
unsigned int i;
|
||||
rc = SQLITE_ERROR; /* IMP: R-42790-23372 */
|
||||
for(i=0; i<ArraySize(aFlagOp); i++){
|
||||
if( aFlagOp[i].op==op ){
|
||||
int onoff = va_arg(ap, int);
|
||||
int *pRes = va_arg(ap, int*);
|
||||
int oldFlags = db->flags;
|
||||
if( onoff>0 ){
|
||||
db->flags |= aFlagOp[i].mask;
|
||||
}else if( onoff==0 ){
|
||||
db->flags &= ~aFlagOp[i].mask;
|
||||
}
|
||||
if( oldFlags!=db->flags ){
|
||||
sqlite3ExpirePreparedStatements(db);
|
||||
}
|
||||
if( pRes ){
|
||||
*pRes = (db->flags & aFlagOp[i].mask)!=0;
|
||||
}
|
||||
rc = SQLITE_OK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -652,7 +687,8 @@ int sqlite3_close(sqlite3 *db){
|
||||
}
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
/* Force xDestroy calls on all virtual tables */
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
|
||||
/* If a transaction is open, the ResetInternalSchema() call above
|
||||
** will not have called the xDisconnect() method on any virtual
|
||||
@@ -695,7 +731,7 @@ int sqlite3_close(sqlite3 *db){
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
|
||||
/* Tell the code in notify.c that the connection no longer holds any
|
||||
** locks and does not require any further unlock-notify callbacks.
|
||||
@@ -786,7 +822,7 @@ void sqlite3RollbackAll(sqlite3 *db){
|
||||
|
||||
if( db->flags&SQLITE_InternChanges ){
|
||||
sqlite3ExpirePreparedStatements(db);
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
}
|
||||
|
||||
/* Any deferred constraint violations have now been resolved. */
|
||||
@@ -855,7 +891,7 @@ static int sqliteDefaultBusyCallback(
|
||||
{ 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 };
|
||||
static const u8 totals[] =
|
||||
{ 0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228 };
|
||||
# define NDELAY (sizeof(delays)/sizeof(delays[0]))
|
||||
# define NDELAY ArraySize(delays)
|
||||
sqlite3 *db = (sqlite3 *)ptr;
|
||||
int timeout = db->busyTimeout;
|
||||
int delay, prior;
|
||||
@@ -1340,19 +1376,33 @@ void *sqlite3_wal_hook(
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Checkpoint database zDb. If zDb is NULL, or if the buffer zDb points
|
||||
** to contains a zero-length string, all attached databases are
|
||||
** checkpointed.
|
||||
** Checkpoint database zDb.
|
||||
*/
|
||||
int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){
|
||||
int sqlite3_wal_checkpoint_v2(
|
||||
sqlite3 *db, /* Database handle */
|
||||
const char *zDb, /* Name of attached database (or NULL) */
|
||||
int eMode, /* SQLITE_CHECKPOINT_* value */
|
||||
int *pnLog, /* OUT: Size of WAL log in frames */
|
||||
int *pnCkpt /* OUT: Total number of frames checkpointed */
|
||||
){
|
||||
#ifdef SQLITE_OMIT_WAL
|
||||
return SQLITE_OK;
|
||||
#else
|
||||
int rc; /* Return code */
|
||||
int iDb = SQLITE_MAX_ATTACHED; /* sqlite3.aDb[] index of db to checkpoint */
|
||||
|
||||
/* Initialize the output variables to -1 in case an error occurs. */
|
||||
if( pnLog ) *pnLog = -1;
|
||||
if( pnCkpt ) *pnCkpt = -1;
|
||||
|
||||
assert( SQLITE_CHECKPOINT_FULL>SQLITE_CHECKPOINT_PASSIVE );
|
||||
assert( SQLITE_CHECKPOINT_FULL<SQLITE_CHECKPOINT_RESTART );
|
||||
assert( SQLITE_CHECKPOINT_PASSIVE+2==SQLITE_CHECKPOINT_RESTART );
|
||||
if( eMode<SQLITE_CHECKPOINT_PASSIVE || eMode>SQLITE_CHECKPOINT_RESTART ){
|
||||
return SQLITE_MISUSE;
|
||||
}
|
||||
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
if( zDb && zDb[0] ){
|
||||
iDb = sqlite3FindDbName(db, zDb);
|
||||
@@ -1361,7 +1411,7 @@ int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){
|
||||
rc = SQLITE_ERROR;
|
||||
sqlite3Error(db, SQLITE_ERROR, "unknown database: %s", zDb);
|
||||
}else{
|
||||
rc = sqlite3Checkpoint(db, iDb);
|
||||
rc = sqlite3Checkpoint(db, iDb, eMode, pnLog, pnCkpt);
|
||||
sqlite3Error(db, rc, 0);
|
||||
}
|
||||
rc = sqlite3ApiExit(db, rc);
|
||||
@@ -1370,6 +1420,16 @@ int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Checkpoint database zDb. If zDb is NULL, or if the buffer zDb points
|
||||
** to contains a zero-length string, all attached databases are
|
||||
** checkpointed.
|
||||
*/
|
||||
int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){
|
||||
return sqlite3_wal_checkpoint_v2(db, zDb, SQLITE_CHECKPOINT_PASSIVE, 0, 0);
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
/*
|
||||
** Run a checkpoint on database iDb. This is a no-op if database iDb is
|
||||
@@ -1387,20 +1447,31 @@ int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){
|
||||
** If iDb is passed SQLITE_MAX_ATTACHED, then all attached databases are
|
||||
** checkpointed. If an error is encountered it is returned immediately -
|
||||
** no attempt is made to checkpoint any remaining databases.
|
||||
**
|
||||
** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL or RESTART.
|
||||
*/
|
||||
int sqlite3Checkpoint(sqlite3 *db, int iDb){
|
||||
int sqlite3Checkpoint(sqlite3 *db, int iDb, int eMode, int *pnLog, int *pnCkpt){
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
int i; /* Used to iterate through attached dbs */
|
||||
int bBusy = 0; /* True if SQLITE_BUSY has been encountered */
|
||||
|
||||
assert( sqlite3_mutex_held(db->mutex) );
|
||||
assert( !pnLog || *pnLog==-1 );
|
||||
assert( !pnCkpt || *pnCkpt==-1 );
|
||||
|
||||
for(i=0; i<db->nDb && rc==SQLITE_OK; i++){
|
||||
if( i==iDb || iDb==SQLITE_MAX_ATTACHED ){
|
||||
rc = sqlite3BtreeCheckpoint(db->aDb[i].pBt);
|
||||
rc = sqlite3BtreeCheckpoint(db->aDb[i].pBt, eMode, pnLog, pnCkpt);
|
||||
pnLog = 0;
|
||||
pnCkpt = 0;
|
||||
if( rc==SQLITE_BUSY ){
|
||||
bBusy = 1;
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
return (rc==SQLITE_OK && bBusy) ? SQLITE_BUSY : rc;
|
||||
}
|
||||
#endif /* SQLITE_OMIT_WAL */
|
||||
|
||||
@@ -1653,8 +1724,8 @@ static const int aHardLimit[] = {
|
||||
#if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>1000
|
||||
# error SQLITE_MAX_FUNCTION_ARG must be between 0 and 1000
|
||||
#endif
|
||||
#if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>30
|
||||
# error SQLITE_MAX_ATTACHED must be between 0 and 30
|
||||
#if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>62
|
||||
# error SQLITE_MAX_ATTACHED must be between 0 and 62
|
||||
#endif
|
||||
#if SQLITE_MAX_LIKE_PATTERN_LENGTH<1
|
||||
# error SQLITE_MAX_LIKE_PATTERN_LENGTH must be at least 1
|
||||
@@ -1773,7 +1844,8 @@ static int openDatabase(
|
||||
** The SQLITE_OPEN_NOMUTEX and SQLITE_OPEN_FULLMUTEX flags were
|
||||
** dealt with in the previous code block. Besides these, the only
|
||||
** valid input flags for sqlite3_open_v2() are SQLITE_OPEN_READONLY,
|
||||
** SQLITE_OPEN_READWRITE, and SQLITE_OPEN_CREATE. Silently mask
|
||||
** SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE, SQLITE_OPEN_SHAREDCACHE,
|
||||
** SQLITE_OPEN_PRIVATECACHE, and some reserved bits. Silently mask
|
||||
** off all other flags.
|
||||
*/
|
||||
flags &= ~( SQLITE_OPEN_DELETEONCLOSE |
|
||||
@@ -1812,7 +1884,7 @@ static int openDatabase(
|
||||
db->autoCommit = 1;
|
||||
db->nextAutovac = -1;
|
||||
db->nextPagesize = 0;
|
||||
db->flags |= SQLITE_ShortColNames | SQLITE_AutoIndex
|
||||
db->flags |= SQLITE_ShortColNames | SQLITE_AutoIndex | SQLITE_EnableTrigger
|
||||
#if SQLITE_DEFAULT_FILE_FORMAT<4
|
||||
| SQLITE_LegacyFileFmt
|
||||
#endif
|
||||
|
||||
@@ -404,7 +404,7 @@ void sqlite3ScratchFree(void *p){
|
||||
pSlot->pNext = mem0.pScratchFree;
|
||||
mem0.pScratchFree = pSlot;
|
||||
mem0.nScratchFree++;
|
||||
assert( mem0.nScratchFree<=sqlite3GlobalConfig.nScratch );
|
||||
assert( mem0.nScratchFree <= (u32)sqlite3GlobalConfig.nScratch );
|
||||
sqlite3StatusAdd(SQLITE_STATUS_SCRATCH_USED, -1);
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
}else{
|
||||
|
||||
@@ -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
|
||||
@@ -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 ){
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2746,7 +2746,7 @@ static int winGetLastError(sqlite3_vfs *pVfs, int nBuf, char *zBuf){
|
||||
*/
|
||||
int sqlite3_os_init(void){
|
||||
static sqlite3_vfs winVfs = {
|
||||
2, /* iVersion */
|
||||
3, /* iVersion */
|
||||
sizeof(winFile), /* szOsFile */
|
||||
MAX_PATH, /* mxPathname */
|
||||
0, /* pNext */
|
||||
@@ -2765,6 +2765,9 @@ int sqlite3_os_init(void){
|
||||
winCurrentTime, /* xCurrentTime */
|
||||
winGetLastError, /* xGetLastError */
|
||||
winCurrentTimeInt64, /* xCurrentTimeInt64 */
|
||||
0, /* xSetSystemCall */
|
||||
0, /* xGetSystemCall */
|
||||
0, /* xNextSystemCall */
|
||||
};
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
|
||||
@@ -2851,6 +2851,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
|
||||
@@ -2921,34 +2943,11 @@ static int pagerRollbackWal(Pager *pPager){
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Update the value of the change-counter at offsets 24 and 92 in
|
||||
** the header and the sqlite version number at offset 96.
|
||||
**
|
||||
** This is an unconditional update. See also the pager_incr_changecounter()
|
||||
** routine which only updates the change-counter if the update is actually
|
||||
** needed, as determined by the pPager->changeCountDone state variable.
|
||||
*/
|
||||
static void pager_write_changecounter(PgHdr *pPg){
|
||||
u32 change_counter;
|
||||
|
||||
/* Increment the value just read and write it back to byte 24. */
|
||||
change_counter = sqlite3Get4byte((u8*)pPg->pPager->dbFileVers)+1;
|
||||
put32bits(((char*)pPg->pData)+24, change_counter);
|
||||
|
||||
/* Also store the SQLite version number in bytes 96..99 and in
|
||||
** bytes 92..95 store the change counter for which the version number
|
||||
** is valid. */
|
||||
put32bits(((char*)pPg->pData)+92, change_counter);
|
||||
put32bits(((char*)pPg->pData)+96, SQLITE_VERSION_NUMBER);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a wrapper around sqlite3WalFrames(). As well as logging
|
||||
** the contents of the list of pages headed by pList (connected by pDirty),
|
||||
** this function notifies any active backup processes that the pages have
|
||||
** changed.
|
||||
** changed.
|
||||
**
|
||||
** The list of pages passed into this routine is always sorted by page number.
|
||||
** Hence, if page 1 appears anywhere on the list, it will be the first page.
|
||||
@@ -2973,6 +2972,19 @@ static int pagerWalFrames(
|
||||
}
|
||||
#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, syncFlags
|
||||
@@ -2985,6 +2997,7 @@ static int pagerWalFrames(
|
||||
}
|
||||
|
||||
#ifdef SQLITE_CHECK_PAGES
|
||||
pList = sqlite3PcacheDirtyList(pPager->pPCache);
|
||||
for(p=pList; p; p=p->pDirty){
|
||||
pager_set_pagehash(p);
|
||||
}
|
||||
@@ -6600,14 +6613,20 @@ 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->ckptSyncFlags,
|
||||
pPager->pageSize, zBuf);
|
||||
rc = sqlite3WalCheckpoint(pPager->pWal, eMode,
|
||||
pPager->xBusyHandler, pPager->pBusyHandlerArg,
|
||||
pPager->ckptSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace,
|
||||
pnLog, pnCkpt
|
||||
);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -6635,8 +6654,8 @@ static int pagerExclusiveLock(Pager *pPager){
|
||||
assert( pPager->eLock==SHARED_LOCK || pPager->eLock==EXCLUSIVE_LOCK );
|
||||
rc = pagerLockDb(pPager, EXCLUSIVE_LOCK);
|
||||
if( rc!=SQLITE_OK ){
|
||||
/* If the attempt to grab the pending lock failed, release the
|
||||
** exclusive lock that may have been obtained instead. */
|
||||
/* If the attempt to grab the exclusive lock failed, release the
|
||||
** pending lock that may have been obtained instead. */
|
||||
pagerUnlockDb(pPager, SHARED_LOCK);
|
||||
}
|
||||
|
||||
|
||||
@@ -138,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);
|
||||
|
||||
@@ -115,7 +115,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;
|
||||
}
|
||||
@@ -384,11 +384,11 @@ void sqlite3Pragma(
|
||||
sqlite3VdbeChangeP1(v, addr+1, iDb);
|
||||
sqlite3VdbeChangeP1(v, addr+6, SQLITE_DEFAULT_CACHE_SIZE);
|
||||
}else{
|
||||
int size = sqlite3Atoi(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);
|
||||
}
|
||||
@@ -691,11 +691,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 = sqlite3Atoi(zRight);
|
||||
if( size<0 ) size = -size;
|
||||
int size = sqlite3AbsInt32(sqlite3Atoi(zRight));
|
||||
pDb->pSchema->cache_size = size;
|
||||
sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
|
||||
}
|
||||
@@ -1112,6 +1112,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);
|
||||
@@ -1177,7 +1178,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);
|
||||
}
|
||||
@@ -1207,7 +1208,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1386,13 +1387,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
|
||||
|
||||
/*
|
||||
|
||||
@@ -34,7 +34,7 @@ static void corruptSchema(
|
||||
"%s - %s", *pData->pzErrMsg, zExtra);
|
||||
}
|
||||
}
|
||||
pData->rc = db->mallocFailed ? SQLITE_NOMEM : SQLITE_CORRUPT;
|
||||
pData->rc = db->mallocFailed ? SQLITE_NOMEM : SQLITE_CORRUPT_BKPT;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -141,7 +141,7 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
|
||||
int meta[5];
|
||||
InitData initData;
|
||||
char const *zMasterSchema;
|
||||
char const *zMasterName = SCHEMA_TABLE(iDb);
|
||||
char const *zMasterName;
|
||||
int openedTransaction = 0;
|
||||
|
||||
/*
|
||||
@@ -278,9 +278,8 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
|
||||
pDb->pSchema->enc = ENC(db);
|
||||
|
||||
if( pDb->pSchema->cache_size==0 ){
|
||||
size = meta[BTREE_DEFAULT_CACHE_SIZE-1];
|
||||
size = sqlite3AbsInt32(meta[BTREE_DEFAULT_CACHE_SIZE-1]);
|
||||
if( size==0 ){ size = SQLITE_DEFAULT_CACHE_SIZE; }
|
||||
if( size<0 ) size = -size;
|
||||
pDb->pSchema->cache_size = size;
|
||||
sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
|
||||
}
|
||||
@@ -339,7 +338,7 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
|
||||
}
|
||||
if( db->mallocFailed ){
|
||||
rc = SQLITE_NOMEM;
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
}
|
||||
if( rc==SQLITE_OK || (db->flags&SQLITE_RecoveryMode)){
|
||||
/* Black magic: If the SQLITE_RecoveryMode flag is set, then consider
|
||||
@@ -471,7 +470,9 @@ static void schemaIsValid(Parse *pParse){
|
||||
** value stored as part of the in-memory schema representation,
|
||||
** set Parse.rc to SQLITE_SCHEMA. */
|
||||
sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&cookie);
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
if( cookie!=db->aDb[iDb].pSchema->schema_cookie ){
|
||||
sqlite3ResetInternalSchema(db, iDb);
|
||||
pParse->rc = SQLITE_SCHEMA;
|
||||
}
|
||||
|
||||
@@ -613,9 +614,6 @@ static int sqlite3Prepare(
|
||||
if( pParse->checkSchema ){
|
||||
schemaIsValid(pParse);
|
||||
}
|
||||
if( pParse->rc==SQLITE_SCHEMA ){
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
}
|
||||
if( db->mallocFailed ){
|
||||
pParse->rc = SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
@@ -400,7 +400,11 @@ void sqlite3VXPrintf(
|
||||
v = va_arg(ap,int);
|
||||
}
|
||||
if( v<0 ){
|
||||
longvalue = -v;
|
||||
if( v==SMALLEST_INT64 ){
|
||||
longvalue = ((u64)1)<<63;
|
||||
}else{
|
||||
longvalue = -v;
|
||||
}
|
||||
prefix = '-';
|
||||
}else{
|
||||
longvalue = v;
|
||||
|
||||
@@ -806,6 +806,22 @@ static void explainTempTable(Parse *pParse, const char *zUsage){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Assign expression b to lvalue a. A second, no-op, version of this macro
|
||||
** is provided when SQLITE_OMIT_EXPLAIN is defined. This allows the code
|
||||
** in sqlite3Select() to assign values to structure member variables that
|
||||
** only exist if SQLITE_OMIT_EXPLAIN is not defined without polluting the
|
||||
** code with #ifndef directives.
|
||||
*/
|
||||
# define explainSetInteger(a, b) a = b
|
||||
|
||||
#else
|
||||
/* No-op versions of the explainXXX() functions and macros. */
|
||||
# define explainTempTable(y,z)
|
||||
# define explainSetInteger(y,z)
|
||||
#endif
|
||||
|
||||
#if !defined(SQLITE_OMIT_EXPLAIN) && !defined(SQLITE_OMIT_COMPOUND_SELECT)
|
||||
/*
|
||||
** Unless an "EXPLAIN QUERY PLAN" command is being processed, this function
|
||||
** is a no-op. Otherwise, it adds a single row of output to the EQP result,
|
||||
@@ -837,21 +853,9 @@ static void explainComposite(
|
||||
sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Assign expression b to lvalue a. A second, no-op, version of this macro
|
||||
** is provided when SQLITE_OMIT_EXPLAIN is defined. This allows the code
|
||||
** in sqlite3Select() to assign values to structure member variables that
|
||||
** only exist if SQLITE_OMIT_EXPLAIN is not defined without polluting the
|
||||
** code with #ifndef directives.
|
||||
*/
|
||||
# define explainSetInteger(a, b) a = b
|
||||
|
||||
#else
|
||||
/* No-op versions of the explainXXX() functions and macros. */
|
||||
# define explainTempTable(y,z)
|
||||
# define explainComposite(v,w,x,y,z)
|
||||
# define explainSetInteger(y,z)
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -2652,6 +2656,9 @@ static void substSelect(
|
||||
** appear as unmodified result columns in the outer query. But
|
||||
** have other optimizations in mind to deal with that case.
|
||||
**
|
||||
** (21) The subquery does not use LIMIT or the outer query is not
|
||||
** DISTINCT. (See ticket [752e1646fc]).
|
||||
**
|
||||
** In this routine, the "p" parameter is a pointer to the outer query.
|
||||
** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query
|
||||
** uses aggregates and subqueryIsAgg is true if the subquery uses aggregates.
|
||||
@@ -2720,6 +2727,9 @@ static int flattenSubquery(
|
||||
}
|
||||
if( isAgg && pSub->pOrderBy ) return 0; /* Restriction (16) */
|
||||
if( pSub->pLimit && p->pWhere ) return 0; /* Restriction (19) */
|
||||
if( pSub->pLimit && (p->selFlags & SF_Distinct)!=0 ){
|
||||
return 0; /* Restriction (21) */
|
||||
}
|
||||
|
||||
/* OBSOLETE COMMENT 1:
|
||||
** Restriction 3: If the subquery is a join, make sure the subquery is
|
||||
@@ -3612,6 +3622,32 @@ static void updateAccumulator(Parse *pParse, AggInfo *pAggInfo){
|
||||
sqlite3ExprCacheClear(pParse);
|
||||
}
|
||||
|
||||
/*
|
||||
** Add a single OP_Explain instruction to the VDBE to explain a simple
|
||||
** count(*) query ("SELECT count(*) FROM pTab").
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_EXPLAIN
|
||||
static void explainSimpleCount(
|
||||
Parse *pParse, /* Parse context */
|
||||
Table *pTab, /* Table being queried */
|
||||
Index *pIdx /* Index used to optimize scan, or NULL */
|
||||
){
|
||||
if( pParse->explain==2 ){
|
||||
char *zEqp = sqlite3MPrintf(pParse->db, "SCAN TABLE %s %s%s(~%d rows)",
|
||||
pTab->zName,
|
||||
pIdx ? "USING COVERING INDEX " : "",
|
||||
pIdx ? pIdx->zName : "",
|
||||
pTab->nRowEst
|
||||
);
|
||||
sqlite3VdbeAddOp4(
|
||||
pParse->pVdbe, OP_Explain, pParse->iSelectId, 0, 0, zEqp, P4_DYNAMIC
|
||||
);
|
||||
}
|
||||
}
|
||||
#else
|
||||
# define explainSimpleCount(a,b,c)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Generate code for the SELECT statement given in the p argument.
|
||||
**
|
||||
@@ -4223,6 +4259,7 @@ int sqlite3Select(
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_Count, iCsr, sAggInfo.aFunc[0].iMem);
|
||||
sqlite3VdbeAddOp1(v, OP_Close, iCsr);
|
||||
explainSimpleCount(pParse, pTab, pBest);
|
||||
}else
|
||||
#endif /* SQLITE_OMIT_BTREECOUNT */
|
||||
{
|
||||
|
||||
@@ -71,6 +71,9 @@ extern int isatty();
|
||||
#define isatty(x) 1
|
||||
#endif
|
||||
|
||||
/* True if the timer is enabled */
|
||||
static int enableTimer = 0;
|
||||
|
||||
#if !defined(_WIN32) && !defined(WIN32) && !defined(__OS2__) && !defined(__RTP__) && !defined(_WRS_KERNEL)
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
@@ -78,9 +81,6 @@ extern int isatty();
|
||||
/* Saved resource information for the beginning of an operation */
|
||||
static struct rusage sBegin;
|
||||
|
||||
/* True if the timer is enabled */
|
||||
static int enableTimer = 0;
|
||||
|
||||
/*
|
||||
** Begin timing an operation
|
||||
*/
|
||||
@@ -124,9 +124,6 @@ static FILETIME ftUserBegin;
|
||||
typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME);
|
||||
static GETPROCTIMES getProcessTimesAddr = NULL;
|
||||
|
||||
/* True if the timer is enabled */
|
||||
static int enableTimer = 0;
|
||||
|
||||
/*
|
||||
** Check to see if we have timer support. Return 1 if necessary
|
||||
** support found (or found previously).
|
||||
@@ -419,6 +416,7 @@ struct callback_data {
|
||||
** .explain ON */
|
||||
char outfile[FILENAME_MAX]; /* Filename for *out */
|
||||
const char *zDbFilename; /* name of the database file */
|
||||
const char *zVfs; /* Name of VFS to use */
|
||||
sqlite3_stmt *pStmt; /* Current statement if any. */
|
||||
FILE *pLog; /* Write log output here */
|
||||
};
|
||||
@@ -1850,7 +1848,7 @@ static int do_meta_command(char *zLine, struct callback_data *p){
|
||||
}else
|
||||
#endif
|
||||
|
||||
if( c=='l' && strncmp(azArg[0], "log", n)==0 && nArg>=1 ){
|
||||
if( c=='l' && strncmp(azArg[0], "log", n)==0 && nArg>=2 ){
|
||||
const char *zFile = azArg[1];
|
||||
if( p->pLog && p->pLog!=stdout && p->pLog!=stderr ){
|
||||
fclose(p->pLog);
|
||||
@@ -2171,12 +2169,136 @@ static int do_meta_command(char *zLine, struct callback_data *p){
|
||||
sqlite3_free_table(azResult);
|
||||
}else
|
||||
|
||||
if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 && nArg>=2 ){
|
||||
static const struct {
|
||||
const char *zCtrlName; /* Name of a test-control option */
|
||||
int ctrlCode; /* Integer code for that option */
|
||||
} aCtrl[] = {
|
||||
{ "prng_save", SQLITE_TESTCTRL_PRNG_SAVE },
|
||||
{ "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE },
|
||||
{ "prng_reset", SQLITE_TESTCTRL_PRNG_RESET },
|
||||
{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST },
|
||||
{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL },
|
||||
{ "benign_malloc_hooks", SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS },
|
||||
{ "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE },
|
||||
{ "assert", SQLITE_TESTCTRL_ASSERT },
|
||||
{ "always", SQLITE_TESTCTRL_ALWAYS },
|
||||
{ "reserve", SQLITE_TESTCTRL_RESERVE },
|
||||
{ "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS },
|
||||
{ "iskeyword", SQLITE_TESTCTRL_ISKEYWORD },
|
||||
{ "pghdrsz", SQLITE_TESTCTRL_PGHDRSZ },
|
||||
{ "scratchmalloc", SQLITE_TESTCTRL_SCRATCHMALLOC },
|
||||
};
|
||||
int testctrl = -1;
|
||||
int rc = 0;
|
||||
int i, n;
|
||||
open_db(p);
|
||||
|
||||
/* convert testctrl text option to value. allow any unique prefix
|
||||
** of the option name, or a numerical value. */
|
||||
n = strlen30(azArg[1]);
|
||||
for(i=0; i<(int)(sizeof(aCtrl)/sizeof(aCtrl[0])); i++){
|
||||
if( strncmp(azArg[1], aCtrl[i].zCtrlName, n)==0 ){
|
||||
if( testctrl<0 ){
|
||||
testctrl = aCtrl[i].ctrlCode;
|
||||
}else{
|
||||
fprintf(stderr, "ambiguous option name: \"%s\"\n", azArg[i]);
|
||||
testctrl = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( testctrl<0 ) testctrl = atoi(azArg[1]);
|
||||
if( (testctrl<SQLITE_TESTCTRL_FIRST) || (testctrl>SQLITE_TESTCTRL_LAST) ){
|
||||
fprintf(stderr,"Error: invalid testctrl option: %s\n", azArg[1]);
|
||||
}else{
|
||||
switch(testctrl){
|
||||
|
||||
/* sqlite3_test_control(int, db, int) */
|
||||
case SQLITE_TESTCTRL_OPTIMIZATIONS:
|
||||
case SQLITE_TESTCTRL_RESERVE:
|
||||
if( nArg==3 ){
|
||||
int opt = (int)strtol(azArg[2], 0, 0);
|
||||
rc = sqlite3_test_control(testctrl, p->db, opt);
|
||||
printf("%d (0x%08x)\n", rc, rc);
|
||||
} else {
|
||||
fprintf(stderr,"Error: testctrl %s takes a single int option\n",
|
||||
azArg[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
/* sqlite3_test_control(int) */
|
||||
case SQLITE_TESTCTRL_PRNG_SAVE:
|
||||
case SQLITE_TESTCTRL_PRNG_RESTORE:
|
||||
case SQLITE_TESTCTRL_PRNG_RESET:
|
||||
case SQLITE_TESTCTRL_PGHDRSZ:
|
||||
if( nArg==2 ){
|
||||
rc = sqlite3_test_control(testctrl);
|
||||
printf("%d (0x%08x)\n", rc, rc);
|
||||
} else {
|
||||
fprintf(stderr,"Error: testctrl %s takes no options\n", azArg[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
/* sqlite3_test_control(int, uint) */
|
||||
case SQLITE_TESTCTRL_PENDING_BYTE:
|
||||
if( nArg==3 ){
|
||||
unsigned int opt = (unsigned int)atoi(azArg[2]);
|
||||
rc = sqlite3_test_control(testctrl, opt);
|
||||
printf("%d (0x%08x)\n", rc, rc);
|
||||
} else {
|
||||
fprintf(stderr,"Error: testctrl %s takes a single unsigned"
|
||||
" int option\n", azArg[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
/* sqlite3_test_control(int, int) */
|
||||
case SQLITE_TESTCTRL_ASSERT:
|
||||
case SQLITE_TESTCTRL_ALWAYS:
|
||||
if( nArg==3 ){
|
||||
int opt = atoi(azArg[2]);
|
||||
rc = sqlite3_test_control(testctrl, opt);
|
||||
printf("%d (0x%08x)\n", rc, rc);
|
||||
} else {
|
||||
fprintf(stderr,"Error: testctrl %s takes a single int option\n",
|
||||
azArg[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
/* sqlite3_test_control(int, char *) */
|
||||
#ifdef SQLITE_N_KEYWORD
|
||||
case SQLITE_TESTCTRL_ISKEYWORD:
|
||||
if( nArg==3 ){
|
||||
const char *opt = azArg[2];
|
||||
rc = sqlite3_test_control(testctrl, opt);
|
||||
printf("%d (0x%08x)\n", rc, rc);
|
||||
} else {
|
||||
fprintf(stderr,"Error: testctrl %s takes a single char * option\n",
|
||||
azArg[1]);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case SQLITE_TESTCTRL_BITVEC_TEST:
|
||||
case SQLITE_TESTCTRL_FAULT_INSTALL:
|
||||
case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS:
|
||||
case SQLITE_TESTCTRL_SCRATCHMALLOC:
|
||||
default:
|
||||
fprintf(stderr,"Error: CLI support for testctrl %s not implemented\n",
|
||||
azArg[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else
|
||||
|
||||
if( c=='t' && n>4 && strncmp(azArg[0], "timeout", n)==0 && nArg==2 ){
|
||||
open_db(p);
|
||||
sqlite3_busy_timeout(p->db, atoi(azArg[1]));
|
||||
}else
|
||||
|
||||
if( HAS_TIMER && c=='t' && n>=5 && strncmp(azArg[0], "timer", n)==0 && nArg==2 ){
|
||||
if( HAS_TIMER && c=='t' && n>=5 && strncmp(azArg[0], "timer", n)==0
|
||||
&& nArg==2
|
||||
){
|
||||
enableTimer = booleanValue(azArg[1]);
|
||||
}else
|
||||
|
||||
@@ -2363,7 +2485,9 @@ static int process_input(struct callback_data *p, FILE *in){
|
||||
}
|
||||
}
|
||||
if( zSql ){
|
||||
if( !_all_whitespace(zSql) ) fprintf(stderr, "Error: incomplete SQL: %s\n", zSql);
|
||||
if( !_all_whitespace(zSql) ){
|
||||
fprintf(stderr, "Error: incomplete SQL: %s\n", zSql);
|
||||
}
|
||||
free(zSql);
|
||||
}
|
||||
free(zLine);
|
||||
@@ -2499,6 +2623,10 @@ static const char zOptions[] =
|
||||
" -stats print memory stats before each finalize\n"
|
||||
" -nullvalue 'text' set text string for NULL values\n"
|
||||
" -version show SQLite version\n"
|
||||
" -vfs NAME use NAME as the default VFS\n"
|
||||
#ifdef SQLITE_ENABLE_VFSTRACE
|
||||
" -vfstrace enable tracing of all VFS calls\n"
|
||||
#endif
|
||||
;
|
||||
static void usage(int showDetail){
|
||||
fprintf(stderr,
|
||||
@@ -2583,6 +2711,25 @@ int main(int argc, char **argv){
|
||||
#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
|
||||
sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64);
|
||||
#endif
|
||||
#ifdef SQLITE_ENABLE_VFSTRACE
|
||||
}else if( strcmp(argv[i],"-vfstrace")==0 ){
|
||||
extern int vfstrace_register(
|
||||
const char *zTraceName,
|
||||
const char *zOldVfsName,
|
||||
int (*xOut)(const char*,void*),
|
||||
void *pOutArg,
|
||||
int makeDefault
|
||||
);
|
||||
vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1);
|
||||
#endif
|
||||
}else if( strcmp(argv[i],"-vfs")==0 ){
|
||||
sqlite3_vfs *pVfs = sqlite3_vfs_find(argv[++i]);
|
||||
if( pVfs ){
|
||||
sqlite3_vfs_register(pVfs, 1);
|
||||
}else{
|
||||
fprintf(stderr, "no such VFS: \"%s\"\n", argv[i]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if( i<argc ){
|
||||
@@ -2691,6 +2838,10 @@ int main(int argc, char **argv){
|
||||
stdin_is_interactive = 0;
|
||||
}else if( strcmp(z,"-heap")==0 ){
|
||||
i++;
|
||||
}else if( strcmp(z,"-vfs")==0 ){
|
||||
i++;
|
||||
}else if( strcmp(z,"-vfstrace")==0 ){
|
||||
i++;
|
||||
}else if( strcmp(z,"-help")==0 || strcmp(z, "--help")==0 ){
|
||||
usage(1);
|
||||
}else{
|
||||
|
||||
@@ -478,6 +478,8 @@ int sqlite3_exec(
|
||||
#define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */
|
||||
#define SQLITE_OPEN_WAL 0x00080000 /* VFS only */
|
||||
|
||||
/* Reserved: 0x00F00000 */
|
||||
|
||||
/*
|
||||
** CAPI3REF: Device Characteristics
|
||||
**
|
||||
@@ -725,7 +727,7 @@ struct sqlite3_io_methods {
|
||||
** when [PRAGMA synchronous | PRAGMA synchronous=OFF] is set, but most
|
||||
** VFSes do not need this signal and should silently ignore this opcode.
|
||||
** Applications should not call [sqlite3_file_control()] with this
|
||||
** opcode as doing so may disrupt the operation of the specilized VFSes
|
||||
** opcode as doing so may disrupt the operation of the specialized VFSes
|
||||
** that do require it.
|
||||
*/
|
||||
#define SQLITE_FCNTL_LOCKSTATE 1
|
||||
@@ -891,10 +893,23 @@ typedef struct sqlite3_mutex sqlite3_mutex;
|
||||
** 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.
|
||||
**
|
||||
** ^The xSetSystemCall(), xGetSystemCall(), and xNestSystemCall() interfaces
|
||||
** are not used by the SQLite core. These optional interfaces are provided
|
||||
** by some VFSes to facilitate testing of the VFS code. By overriding
|
||||
** system calls with functions under its control, a test program can
|
||||
** simulate faults and error conditions that would otherwise be difficult
|
||||
** or impossible to induce. The set of system calls that can be overridden
|
||||
** varies from one VFS to another, and from one version of the same VFS to the
|
||||
** next. Applications that use these interfaces must be prepared for any
|
||||
** or all of these interfaces to be NULL or for their behavior to change
|
||||
** from one release to the next. Applications must not attempt to access
|
||||
** any of these methods if the iVersion of the VFS is less than 3.
|
||||
*/
|
||||
typedef struct sqlite3_vfs sqlite3_vfs;
|
||||
typedef void (*sqlite3_syscall_ptr)(void);
|
||||
struct sqlite3_vfs {
|
||||
int iVersion; /* Structure version number (currently 2) */
|
||||
int iVersion; /* Structure version number (currently 3) */
|
||||
int szOsFile; /* Size of subclassed sqlite3_file */
|
||||
int mxPathname; /* Maximum file pathname length */
|
||||
sqlite3_vfs *pNext; /* Next registered VFS */
|
||||
@@ -920,6 +935,13 @@ struct sqlite3_vfs {
|
||||
int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*);
|
||||
/*
|
||||
** The methods above are in versions 1 and 2 of the sqlite_vfs object.
|
||||
** Those below are for version 3 and greater.
|
||||
*/
|
||||
int (*xSetSystemCall)(sqlite3_vfs*, const char *zName, sqlite3_syscall_ptr);
|
||||
sqlite3_syscall_ptr (*xGetSystemCall)(sqlite3_vfs*, const char *zName);
|
||||
const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName);
|
||||
/*
|
||||
** The methods above are in versions 1 through 3 of the sqlite_vfs object.
|
||||
** New fields may be appended in figure versions. The iVersion
|
||||
** value will increment whenever this happens.
|
||||
*/
|
||||
@@ -1104,17 +1126,12 @@ int sqlite3_config(int, ...);
|
||||
** The sqlite3_db_config() interface is used to make configuration
|
||||
** changes to a [database connection]. The interface is similar to
|
||||
** [sqlite3_config()] except that the changes apply to a single
|
||||
** [database connection] (specified in the first argument). The
|
||||
** sqlite3_db_config() interface should only be used immediately after
|
||||
** the database connection is created using [sqlite3_open()],
|
||||
** [sqlite3_open16()], or [sqlite3_open_v2()].
|
||||
** [database connection] (specified in the first argument).
|
||||
**
|
||||
** The second argument to sqlite3_db_config(D,V,...) is the
|
||||
** configuration verb - an integer code that indicates what
|
||||
** aspect of the [database connection] is being configured.
|
||||
** The only choice for this value is [SQLITE_DBCONFIG_LOOKASIDE].
|
||||
** New verbs are likely to be added in future releases of SQLite.
|
||||
** Additional arguments depend on the verb.
|
||||
** [SQLITE_DBCONFIG_LOOKASIDE | configuration verb] - an integer code
|
||||
** that indicates what aspect of the [database connection] is being configured.
|
||||
** Subsequent arguments vary depending on the configuration verb.
|
||||
**
|
||||
** ^Calls to sqlite3_db_config() return SQLITE_OK if and only if
|
||||
** the call is considered successful.
|
||||
@@ -1292,7 +1309,7 @@ struct sqlite3_mem_methods {
|
||||
** <dt>SQLITE_CONFIG_SCRATCH</dt>
|
||||
** <dd> ^This option specifies a static memory buffer that SQLite can use for
|
||||
** scratch memory. There are three arguments: A pointer an 8-byte
|
||||
** aligned memory buffer from which the scrach allocations will be
|
||||
** aligned memory buffer from which the scratch allocations will be
|
||||
** drawn, the size of each scratch allocation (sz),
|
||||
** and the maximum number of scratch allocations (N). The sz
|
||||
** argument must be a multiple of 16.
|
||||
@@ -1339,7 +1356,9 @@ struct sqlite3_mem_methods {
|
||||
** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory
|
||||
** allocator is engaged to handle all of SQLites memory allocation needs.
|
||||
** The first pointer (the memory pointer) must be aligned to an 8-byte
|
||||
** boundary or subsequent behavior of SQLite will be undefined.</dd>
|
||||
** boundary or subsequent behavior of SQLite will be undefined.
|
||||
** The minimum allocation size is capped at 2^12. Reasonable values
|
||||
** for the minimum allocation size are 2^5 through 2^8.</dd>
|
||||
**
|
||||
** <dt>SQLITE_CONFIG_MUTEX</dt>
|
||||
** <dd> ^(This option takes a single argument which is a pointer to an
|
||||
@@ -1442,7 +1461,7 @@ struct sqlite3_mem_methods {
|
||||
** <dd> ^This option takes three additional arguments that determine the
|
||||
** [lookaside memory allocator] configuration for the [database connection].
|
||||
** ^The first argument (the third parameter to [sqlite3_db_config()] is a
|
||||
** pointer to an memory buffer to use for lookaside memory.
|
||||
** pointer to a memory buffer to use for lookaside memory.
|
||||
** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb
|
||||
** may be NULL in which case SQLite will allocate the
|
||||
** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the
|
||||
@@ -1460,9 +1479,31 @@ struct sqlite3_mem_methods {
|
||||
** memory is in use leaves the configuration unchanged and returns
|
||||
** [SQLITE_BUSY].)^</dd>
|
||||
**
|
||||
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
|
||||
** <dd> ^This option is used to enable or disable the enforcement of
|
||||
** [foreign key constraints]. There should be two additional arguments.
|
||||
** The first argument is an integer which is 0 to disable FK enforcement,
|
||||
** positive to enable FK enforcement or negative to leave FK enforcement
|
||||
** unchanged. The second parameter is a pointer to an integer into which
|
||||
** is written 0 or 1 to indicate whether FK enforcement is off or on
|
||||
** following this call. The second parameter may be a NULL pointer, in
|
||||
** which case the FK enforcement setting is not reported back. </dd>
|
||||
**
|
||||
** <dt>SQLITE_DBCONFIG_ENABLE_TRIGGER</dt>
|
||||
** <dd> ^This option is used to enable or disable [CREATE TRIGGER | triggers].
|
||||
** There should be two additional arguments.
|
||||
** The first argument is an integer which is 0 to disable triggers,
|
||||
** positive to enable triggers or negative to leave the setting unchanged.
|
||||
** The second parameter is a pointer to an integer into which
|
||||
** is written 0 or 1 to indicate whether triggers are disabled or enabled
|
||||
** following this call. The second parameter may be a NULL pointer, in
|
||||
** which case the trigger setting is not reported back. </dd>
|
||||
**
|
||||
** </dl>
|
||||
*/
|
||||
#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */
|
||||
#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */
|
||||
#define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */
|
||||
#define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */
|
||||
|
||||
|
||||
/*
|
||||
@@ -2064,7 +2105,7 @@ void sqlite3_randomness(int N, void *P);
|
||||
/*
|
||||
** CAPI3REF: Compile-Time Authorization Callbacks
|
||||
**
|
||||
** ^This routine registers a authorizer callback with a particular
|
||||
** ^This routine registers an authorizer callback with a particular
|
||||
** [database connection], supplied in the first argument.
|
||||
** ^The authorizer callback is invoked as SQL statements are being compiled
|
||||
** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()],
|
||||
@@ -2666,7 +2707,7 @@ const char *sqlite3_sql(sqlite3_stmt *pStmt);
|
||||
/*
|
||||
** CAPI3REF: Determine If An SQL Statement Writes The Database
|
||||
**
|
||||
** ^The sqlite3_stmt_readonly(X) interface returns true (non-zero) if
|
||||
** ^The sqlite3_stmt_readonly(X) interface returns true (non-zero) if
|
||||
** and only if the [prepared statement] X makes no direct changes to
|
||||
** the content of the database file.
|
||||
**
|
||||
@@ -2710,7 +2751,7 @@ int sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
|
||||
** whether or not it requires a protected sqlite3_value.
|
||||
**
|
||||
** The terms "protected" and "unprotected" refer to whether or not
|
||||
** a mutex is held. A internal mutex is held for a protected
|
||||
** a mutex is held. An internal mutex is held for a protected
|
||||
** sqlite3_value object but no mutex is held for an unprotected
|
||||
** sqlite3_value object. If SQLite is compiled to be single-threaded
|
||||
** (with [SQLITE_THREADSAFE=0] and with [sqlite3_threadsafe()] returning 0)
|
||||
@@ -2934,7 +2975,9 @@ int sqlite3_column_count(sqlite3_stmt *pStmt);
|
||||
** column number. ^The leftmost column is number 0.
|
||||
**
|
||||
** ^The returned string pointer is valid until either the [prepared statement]
|
||||
** is destroyed by [sqlite3_finalize()] or until the next call to
|
||||
** is destroyed by [sqlite3_finalize()] or until the statement is automatically
|
||||
** reprepared by the first call to [sqlite3_step()] for a particular run
|
||||
** or until the next call to
|
||||
** sqlite3_column_name() or sqlite3_column_name16() on the same column.
|
||||
**
|
||||
** ^If sqlite3_malloc() fails during the processing of either routine
|
||||
@@ -2960,7 +3003,9 @@ const void *sqlite3_column_name16(sqlite3_stmt*, int N);
|
||||
** the database name, the _table_ routines return the table name, and
|
||||
** the origin_ routines return the column name.
|
||||
** ^The returned string is valid until the [prepared statement] is destroyed
|
||||
** using [sqlite3_finalize()] or until the same information is requested
|
||||
** using [sqlite3_finalize()] or until the statement is automatically
|
||||
** reprepared by the first call to [sqlite3_step()] for a particular run
|
||||
** or until the same information is requested
|
||||
** again in a different encoding.
|
||||
**
|
||||
** ^The names returned are the original un-aliased names of the
|
||||
@@ -3392,7 +3437,7 @@ int sqlite3_reset(sqlite3_stmt *pStmt);
|
||||
** are used to add SQL functions or aggregates or to redefine the behavior
|
||||
** of existing SQL functions or aggregates. The only differences between
|
||||
** these routines are the text encoding expected for
|
||||
** the the second parameter (the name of the function being created)
|
||||
** the second parameter (the name of the function being created)
|
||||
** and the presence or absence of a destructor callback for
|
||||
** the application data pointer.
|
||||
**
|
||||
@@ -3437,7 +3482,7 @@ int sqlite3_reset(sqlite3_stmt *pStmt);
|
||||
** callback only; NULL pointers must be passed as the xStep and xFinal
|
||||
** parameters. ^An aggregate SQL function requires an implementation of xStep
|
||||
** and xFinal and NULL pointer must be passed for xFunc. ^To delete an existing
|
||||
** SQL function or aggregate, pass NULL poiners for all three function
|
||||
** SQL function or aggregate, pass NULL pointers for all three function
|
||||
** callbacks.
|
||||
**
|
||||
** ^(If the ninth parameter to sqlite3_create_function_v2() is not NULL,
|
||||
@@ -3871,7 +3916,7 @@ void sqlite3_result_zeroblob(sqlite3_context*, int n);
|
||||
** ^The [SQLITE_UTF16_ALIGNED] value for eTextRep forces strings to begin
|
||||
** on an even byte address.
|
||||
**
|
||||
** ^The fourth argument, pArg, is a application data pointer that is passed
|
||||
** ^The fourth argument, pArg, is an application data pointer that is passed
|
||||
** through as the first argument to the collating function callback.
|
||||
**
|
||||
** ^The fifth argument, xCallback, is a pointer to the collating function.
|
||||
@@ -3887,7 +3932,7 @@ void sqlite3_result_zeroblob(sqlite3_context*, int n);
|
||||
** by the eTextRep argument. The collating function must return an
|
||||
** integer that is negative, zero, or positive
|
||||
** if the first string is less than, equal to, or greater than the second,
|
||||
** respectively. A collating function must alway return the same answer
|
||||
** respectively. A collating function must always return the same answer
|
||||
** given the same inputs. If two or more collating functions are registered
|
||||
** to the same collation name (using different eTextRep values) then all
|
||||
** must give an equivalent answer when invoked with equivalent strings.
|
||||
@@ -4299,7 +4344,7 @@ int sqlite3_release_memory(int);
|
||||
** <li> Memory accounting is disabled using a combination of the
|
||||
** [sqlite3_config]([SQLITE_CONFIG_MEMSTATUS],...) start-time option and
|
||||
** the [SQLITE_DEFAULT_MEMSTATUS] compile-time option.
|
||||
** <li> An alternative page cache implementation is specifed using
|
||||
** <li> An alternative page cache implementation is specified using
|
||||
** [sqlite3_config]([SQLITE_CONFIG_PCACHE],...).
|
||||
** <li> The page cache allocates from its own memory pool supplied
|
||||
** by [sqlite3_config]([SQLITE_CONFIG_PAGECACHE],...) rather than
|
||||
@@ -4520,7 +4565,7 @@ typedef struct sqlite3_module sqlite3_module;
|
||||
** CAPI3REF: Virtual Table Object
|
||||
** KEYWORDS: sqlite3_module {virtual table module}
|
||||
**
|
||||
** This structure, sometimes called a a "virtual table module",
|
||||
** This structure, sometimes called a "virtual table module",
|
||||
** defines the implementation of a [virtual tables].
|
||||
** This structure consists mostly of methods for the module.
|
||||
**
|
||||
@@ -4832,7 +4877,7 @@ typedef struct sqlite3_blob sqlite3_blob;
|
||||
** This is true if any column of the row is changed, even a column
|
||||
** other than the one the BLOB handle is open on.)^
|
||||
** ^Calls to [sqlite3_blob_read()] and [sqlite3_blob_write()] for
|
||||
** a expired BLOB handle fail with an return code of [SQLITE_ABORT].
|
||||
** an expired BLOB handle fail with a return code of [SQLITE_ABORT].
|
||||
** ^(Changes written into a BLOB prior to the BLOB expiring are not
|
||||
** rolled back by the expiration of the BLOB. Such changes will eventually
|
||||
** commit if the transaction continues to completion.)^
|
||||
@@ -5534,24 +5579,21 @@ int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
|
||||
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt>
|
||||
** <dd>This parameter returns the number malloc attempts that were
|
||||
** satisfied using lookaside memory. Only the high-water value is meaningful;
|
||||
** the current value is always zero.
|
||||
** checked out.</dd>)^
|
||||
** the current value is always zero.)^
|
||||
**
|
||||
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt>
|
||||
** <dd>This parameter returns the number malloc attempts that might have
|
||||
** been satisfied using lookaside memory but failed due to the amount of
|
||||
** memory requested being larger than the lookaside slot size.
|
||||
** Only the high-water value is meaningful;
|
||||
** the current value is always zero.
|
||||
** checked out.</dd>)^
|
||||
** the current value is always zero.)^
|
||||
**
|
||||
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL</dt>
|
||||
** <dd>This parameter returns the number malloc attempts that might have
|
||||
** been satisfied using lookaside memory but failed due to all lookaside
|
||||
** memory already being in use.
|
||||
** Only the high-water value is meaningful;
|
||||
** the current value is always zero.
|
||||
** checked out.</dd>)^
|
||||
** the current value is always zero.)^
|
||||
**
|
||||
** ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt>
|
||||
** <dd>This parameter returns the approximate number of of bytes of heap
|
||||
@@ -6250,9 +6292,102 @@ int sqlite3_wal_autocheckpoint(sqlite3 *db, int N);
|
||||
** from SQL. ^The [sqlite3_wal_autocheckpoint()] interface and the
|
||||
** [wal_autocheckpoint pragma] can be used to cause this interface to be
|
||||
** run whenever the WAL reaches a certain size threshold.
|
||||
**
|
||||
** See also: [sqlite3_wal_checkpoint_v2()]
|
||||
*/
|
||||
int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Checkpoint a database
|
||||
**
|
||||
** Run a checkpoint operation on WAL database zDb attached to database
|
||||
** handle db. The specific operation is determined by the value of the
|
||||
** eMode parameter:
|
||||
**
|
||||
** <dl>
|
||||
** <dt>SQLITE_CHECKPOINT_PASSIVE<dd>
|
||||
** Checkpoint as many frames as possible without waiting for any database
|
||||
** readers or writers to finish. Sync the db file if all frames in the log
|
||||
** are checkpointed. This mode is the same as calling
|
||||
** sqlite3_wal_checkpoint(). The busy-handler callback is never invoked.
|
||||
**
|
||||
** <dt>SQLITE_CHECKPOINT_FULL<dd>
|
||||
** This mode blocks (calls the busy-handler callback) until there is no
|
||||
** database writer and all readers are reading from the most recent database
|
||||
** snapshot. It then checkpoints all frames in the log file and syncs the
|
||||
** database file. This call blocks database writers while it is running,
|
||||
** but not database readers.
|
||||
**
|
||||
** <dt>SQLITE_CHECKPOINT_RESTART<dd>
|
||||
** This mode works the same way as SQLITE_CHECKPOINT_FULL, except after
|
||||
** checkpointing the log file it blocks (calls the busy-handler callback)
|
||||
** until all readers are reading from the database file only. This ensures
|
||||
** that the next client to write to the database file restarts the log file
|
||||
** from the beginning. This call blocks database writers while it is running,
|
||||
** but not database readers.
|
||||
** </dl>
|
||||
**
|
||||
** If pnLog is not NULL, then *pnLog is set to the total number of frames in
|
||||
** the log file before returning. If pnCkpt is not NULL, then *pnCkpt is set to
|
||||
** the total number of checkpointed frames (including any that were already
|
||||
** checkpointed when this function is called). *pnLog and *pnCkpt may be
|
||||
** populated even if sqlite3_wal_checkpoint_v2() returns other than SQLITE_OK.
|
||||
** If no values are available because of an error, they are both set to -1
|
||||
** before returning to communicate this to the caller.
|
||||
**
|
||||
** All calls obtain an exclusive "checkpoint" lock on the database file. If
|
||||
** any other process is running a checkpoint operation at the same time, the
|
||||
** lock cannot be obtained and SQLITE_BUSY is returned. Even if there is a
|
||||
** busy-handler configured, it will not be invoked in this case.
|
||||
**
|
||||
** The SQLITE_CHECKPOINT_FULL and RESTART modes also obtain the exclusive
|
||||
** "writer" lock on the database file. If the writer lock cannot be obtained
|
||||
** immediately, and a busy-handler is configured, it is invoked and the writer
|
||||
** lock retried until either the busy-handler returns 0 or the lock is
|
||||
** successfully obtained. The busy-handler is also invoked while waiting for
|
||||
** database readers as described above. If the busy-handler returns 0 before
|
||||
** the writer lock is obtained or while waiting for database readers, the
|
||||
** checkpoint operation proceeds from that point in the same way as
|
||||
** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible
|
||||
** without blocking any further. SQLITE_BUSY is returned in this case.
|
||||
**
|
||||
** If parameter zDb is NULL or points to a zero length string, then the
|
||||
** specified operation is attempted on all WAL databases. In this case the
|
||||
** values written to output parameters *pnLog and *pnCkpt are undefined. If
|
||||
** an SQLITE_BUSY error is encountered when processing one or more of the
|
||||
** attached WAL databases, the operation is still attempted on any remaining
|
||||
** attached databases and SQLITE_BUSY is returned to the caller. If any other
|
||||
** error occurs while processing an attached database, processing is abandoned
|
||||
** and the error code returned to the caller immediately. If no error
|
||||
** (SQLITE_BUSY or otherwise) is encountered while processing the attached
|
||||
** databases, SQLITE_OK is returned.
|
||||
**
|
||||
** If database zDb is the name of an attached database that is not in WAL
|
||||
** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. If
|
||||
** zDb is not NULL (or a zero length string) and is not the name of any
|
||||
** attached database, SQLITE_ERROR is returned to the caller.
|
||||
*/
|
||||
int sqlite3_wal_checkpoint_v2(
|
||||
sqlite3 *db, /* Database handle */
|
||||
const char *zDb, /* Name of attached database (or NULL) */
|
||||
int eMode, /* SQLITE_CHECKPOINT_* value */
|
||||
int *pnLog, /* OUT: Size of WAL log in frames */
|
||||
int *pnCkpt /* OUT: Total number of frames checkpointed */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Checkpoint operation parameters
|
||||
**
|
||||
** These constants can be used as the 3rd parameter to
|
||||
** [sqlite3_wal_checkpoint_v2()]. See the [sqlite3_wal_checkpoint_v2()]
|
||||
** documentation for additional information about the meaning and use of
|
||||
** each of these values.
|
||||
*/
|
||||
#define SQLITE_CHECKPOINT_PASSIVE 0
|
||||
#define SQLITE_CHECKPOINT_FULL 1
|
||||
#define SQLITE_CHECKPOINT_RESTART 2
|
||||
|
||||
|
||||
/*
|
||||
** Undo the hack that converts floating point types to integer for
|
||||
** builds on processors without floating point support.
|
||||
|
||||
@@ -668,9 +668,24 @@ struct Db {
|
||||
|
||||
/*
|
||||
** An instance of the following structure stores a database schema.
|
||||
**
|
||||
** Most Schema objects are associated with a Btree. The exception is
|
||||
** the Schema for the TEMP databaes (sqlite3.aDb[1]) which is free-standing.
|
||||
** In shared cache mode, a single Schema object can be shared by multiple
|
||||
** Btrees that refer to the same underlying BtShared object.
|
||||
**
|
||||
** Schema objects are automatically deallocated when the last Btree that
|
||||
** references them is destroyed. The TEMP Schema is manually freed by
|
||||
** sqlite3_close().
|
||||
*
|
||||
** A thread must be holding a mutex on the corresponding Btree in order
|
||||
** to access Schema content. This implies that the thread must also be
|
||||
** holding a mutex on the sqlite3 connection pointer that owns the Btree.
|
||||
** For a TEMP Schema, on the connection mutex is required.
|
||||
*/
|
||||
struct Schema {
|
||||
int schema_cookie; /* Database schema version number for this file */
|
||||
int iGeneration; /* Generation counter. Incremented with each change */
|
||||
Hash tblHash; /* All tables indexed by name */
|
||||
Hash idxHash; /* All (named) indices indexed by name */
|
||||
Hash trigHash; /* All triggers indexed by name */
|
||||
@@ -924,6 +939,7 @@ struct sqlite3 {
|
||||
#define SQLITE_AutoIndex 0x08000000 /* Enable automatic indexes */
|
||||
#define SQLITE_PreferBuiltin 0x10000000 /* Preference to built-in funcs */
|
||||
#define SQLITE_LoadExtension 0x20000000 /* Enable load_extension */
|
||||
#define SQLITE_EnableTrigger 0x40000000 /* True to enable triggers */
|
||||
|
||||
/*
|
||||
** Bits of the sqlite3.flags field that are used by the
|
||||
@@ -1182,7 +1198,7 @@ struct CollSeq {
|
||||
** schema is shared, as the implementation often stores the database
|
||||
** connection handle passed to it via the xConnect() or xCreate() method
|
||||
** during initialization internally. This database connection handle may
|
||||
** then used by the virtual table implementation to access real tables
|
||||
** then be used by the virtual table implementation to access real tables
|
||||
** within the database. So that they appear as part of the callers
|
||||
** transaction, these accesses need to be made via the same database
|
||||
** connection as that used to execute SQL operations on the virtual table.
|
||||
@@ -1460,6 +1476,7 @@ struct Index {
|
||||
int tnum; /* Page containing root of this index in database file */
|
||||
u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */
|
||||
u8 autoIndex; /* True if is automatically created (ex: by UNIQUE) */
|
||||
u8 bUnordered; /* Use this index for == or IN queries only */
|
||||
char *zColAff; /* String defining the affinity of each column */
|
||||
Index *pNext; /* The next index associated with the same table */
|
||||
Schema *pSchema; /* Schema containing this index */
|
||||
@@ -1623,7 +1640,7 @@ struct Expr {
|
||||
u16 flags; /* Various flags. EP_* See below */
|
||||
union {
|
||||
char *zToken; /* Token value. Zero terminated and dequoted */
|
||||
int iValue; /* Integer value if EP_IntValue */
|
||||
int iValue; /* Non-negative integer value if EP_IntValue */
|
||||
} u;
|
||||
|
||||
/* If the EP_TokenOnly flag is set in the Expr.flags mask, then no
|
||||
@@ -2123,6 +2140,15 @@ struct TriggerPrg {
|
||||
TriggerPrg *pNext; /* Next entry in Parse.pTriggerPrg list */
|
||||
};
|
||||
|
||||
/*
|
||||
** The yDbMask datatype for the bitmask of all attached databases.
|
||||
*/
|
||||
#if SQLITE_MAX_ATTACHED>30
|
||||
typedef sqlite3_uint64 yDbMask;
|
||||
#else
|
||||
typedef unsigned int yDbMask;
|
||||
#endif
|
||||
|
||||
/*
|
||||
** An SQL parser context. A copy of this structure is passed through
|
||||
** the parser and down into all the parser action routine in order to
|
||||
@@ -2171,8 +2197,8 @@ struct Parse {
|
||||
int iReg; /* Reg with value of this column. 0 means none. */
|
||||
int lru; /* Least recently used entry has the smallest value */
|
||||
} aColCache[SQLITE_N_COLCACHE]; /* One for each column cache entry */
|
||||
u32 writeMask; /* Start a write transaction on these databases */
|
||||
u32 cookieMask; /* Bitmask of schema verified databases */
|
||||
yDbMask writeMask; /* Start a write transaction on these databases */
|
||||
yDbMask cookieMask; /* Bitmask of schema verified databases */
|
||||
u8 isMultiWrite; /* True if statement may affect/insert multiple rows */
|
||||
u8 mayAbort; /* True if statement may throw an ABORT exception */
|
||||
int cookieGoto; /* Address of OP_Goto to cookie verifier subroutine */
|
||||
@@ -2742,6 +2768,7 @@ void sqlite3PrngRestoreState(void);
|
||||
void sqlite3PrngResetState(void);
|
||||
void sqlite3RollbackAll(sqlite3*);
|
||||
void sqlite3CodeVerifySchema(Parse*, int);
|
||||
void sqlite3CodeVerifyNamedSchema(Parse*, const char *zDb);
|
||||
void sqlite3BeginTransaction(Parse*, int);
|
||||
void sqlite3CommitTransaction(Parse*);
|
||||
void sqlite3RollbackTransaction(Parse*);
|
||||
@@ -2903,6 +2930,10 @@ Expr *sqlite3ExprSetCollByToken(Parse *pParse, Expr*, Token*);
|
||||
int sqlite3CheckCollSeq(Parse *, CollSeq *);
|
||||
int sqlite3CheckObjectName(Parse *, const char *);
|
||||
void sqlite3VdbeSetChanges(sqlite3 *, int);
|
||||
int sqlite3AddInt64(i64*,i64);
|
||||
int sqlite3SubInt64(i64*,i64);
|
||||
int sqlite3MulInt64(i64*,i64);
|
||||
int sqlite3AbsInt32(int);
|
||||
|
||||
const void *sqlite3ValueText(sqlite3_value*, u8);
|
||||
int sqlite3ValueBytes(sqlite3_value*, u8);
|
||||
@@ -2927,7 +2958,7 @@ extern SQLITE_WSD FuncDefHash sqlite3GlobalFunctions;
|
||||
extern int sqlite3PendingByte;
|
||||
#endif
|
||||
#endif
|
||||
void sqlite3RootPageMoved(Db*, int, int);
|
||||
void sqlite3RootPageMoved(sqlite3*, int, int, int);
|
||||
void sqlite3Reindex(Parse*, Token*, Token*);
|
||||
void sqlite3AlterFunctions(void);
|
||||
void sqlite3AlterRenameTable(Parse*, SrcList*, Token*);
|
||||
@@ -2954,7 +2985,7 @@ void sqlite3DefaultRowEst(Index*);
|
||||
void sqlite3RegisterLikeFunctions(sqlite3*, int);
|
||||
int sqlite3IsLikeFunction(sqlite3*,Expr*,int*,char*);
|
||||
void sqlite3MinimumFileFormat(Parse*, int, int);
|
||||
void sqlite3SchemaFree(void *);
|
||||
void sqlite3SchemaClear(void *);
|
||||
Schema *sqlite3SchemaGet(sqlite3 *, Btree *);
|
||||
int sqlite3SchemaToIndex(sqlite3 *db, Schema *);
|
||||
KeyInfo *sqlite3IndexKeyinfo(Parse *, Index *);
|
||||
@@ -3041,7 +3072,7 @@ CollSeq *sqlite3BinaryCompareCollSeq(Parse *, Expr *, Expr *);
|
||||
int sqlite3TempInMemory(const sqlite3*);
|
||||
VTable *sqlite3GetVTable(sqlite3*, Table*);
|
||||
const char *sqlite3JournalModename(int);
|
||||
int sqlite3Checkpoint(sqlite3*, int);
|
||||
int sqlite3Checkpoint(sqlite3*, int, int, int*, int*);
|
||||
int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int);
|
||||
|
||||
/* Declarations for functions in fkey.c. All of these are replaced by
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
|
||||
/*
|
||||
** The maximum number of attached databases. This must be between 0
|
||||
** and 30. The upper bound on 30 is because a 32-bit integer bitmap
|
||||
** and 62. The upper bound on 62 is because a 64-bit integer bitmap
|
||||
** is used internally to track attached databases.
|
||||
*/
|
||||
#ifndef SQLITE_MAX_ATTACHED
|
||||
|
||||
@@ -163,6 +163,7 @@ int sqlite3_db_status(
|
||||
int i; /* Used to iterate through schemas */
|
||||
int nByte = 0; /* Used to accumulate return value */
|
||||
|
||||
sqlite3BtreeEnterAll(db);
|
||||
db->pnBytesFreed = &nByte;
|
||||
for(i=0; i<db->nDb; i++){
|
||||
Schema *pSchema = db->aDb[i].pSchema;
|
||||
@@ -189,6 +190,7 @@ int sqlite3_db_status(
|
||||
}
|
||||
}
|
||||
db->pnBytesFreed = 0;
|
||||
sqlite3BtreeLeaveAll(db);
|
||||
|
||||
*pHighwater = 0;
|
||||
*pCurrent = nByte;
|
||||
|
||||
@@ -2468,7 +2468,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
||||
}else{
|
||||
pDb->zProfile = 0;
|
||||
}
|
||||
#ifndef SQLITE_OMIT_TRACE
|
||||
#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT)
|
||||
if( pDb->zProfile ){
|
||||
pDb->interp = interp;
|
||||
sqlite3_profile(pDb->db, DbProfileHandler, pDb);
|
||||
@@ -2652,7 +2652,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
||||
}else{
|
||||
pDb->zTrace = 0;
|
||||
}
|
||||
#ifndef SQLITE_OMIT_TRACE
|
||||
#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT)
|
||||
if( pDb->zTrace ){
|
||||
pDb->interp = interp;
|
||||
sqlite3_trace(pDb->db, DbTraceHandler, pDb);
|
||||
@@ -3581,6 +3581,9 @@ static void init_all(Tcl_Interp *interp){
|
||||
extern int Sqlitequota_Init(Tcl_Interp*);
|
||||
extern int Sqlitemultiplex_Init(Tcl_Interp*);
|
||||
extern int SqliteSuperlock_Init(Tcl_Interp*);
|
||||
extern int SqlitetestSyscall_Init(Tcl_Interp*);
|
||||
extern int Sqlitetestfuzzer_Init(Tcl_Interp*);
|
||||
extern int Sqlitetestwholenumber_Init(Tcl_Interp*);
|
||||
|
||||
#ifdef SQLITE_ENABLE_ZIPVFS
|
||||
extern int Zipvfs_Init(Tcl_Interp*);
|
||||
@@ -3618,6 +3621,9 @@ static void init_all(Tcl_Interp *interp){
|
||||
Sqlitequota_Init(interp);
|
||||
Sqlitemultiplex_Init(interp);
|
||||
SqliteSuperlock_Init(interp);
|
||||
SqlitetestSyscall_Init(interp);
|
||||
Sqlitetestfuzzer_Init(interp);
|
||||
Sqlitetestwholenumber_Init(interp);
|
||||
|
||||
Tcl_CreateObjCommand(interp,"load_testfixture_extensions",init_all_cmd,0,0);
|
||||
|
||||
|
||||
@@ -4888,6 +4888,44 @@ static int file_control_chunksize_test(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: file_control_sizehint_test DB DBNAME SIZE
|
||||
**
|
||||
** This TCL command runs the sqlite3_file_control interface and
|
||||
** verifies correct operation of the SQLITE_GET_LOCKPROXYFILE and
|
||||
** SQLITE_SET_LOCKPROXYFILE verbs.
|
||||
*/
|
||||
static int file_control_sizehint_test(
|
||||
ClientData clientData, /* Pointer to sqlite3_enable_XXX function */
|
||||
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
|
||||
int objc, /* Number of arguments */
|
||||
Tcl_Obj *CONST objv[] /* Command arguments */
|
||||
){
|
||||
sqlite3_int64 nSize; /* Hinted size */
|
||||
char *zDb; /* Db name ("main", "temp" etc.) */
|
||||
sqlite3 *db; /* Database handle */
|
||||
int rc; /* file_control() return code */
|
||||
|
||||
if( objc!=4 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME SIZE");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db)
|
||||
|| Tcl_GetWideIntFromObj(interp, objv[3], &nSize)
|
||||
){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
zDb = Tcl_GetString(objv[2]);
|
||||
if( zDb[0]=='\0' ) zDb = NULL;
|
||||
|
||||
rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_SIZE_HINT, (void *)&nSize);
|
||||
if( rc ){
|
||||
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: file_control_lockproxy_test DB PWD
|
||||
**
|
||||
@@ -5186,6 +5224,73 @@ static int test_wal_checkpoint(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: sqlite3_wal_checkpoint_v2 db MODE ?NAME?
|
||||
**
|
||||
** This command calls the wal_checkpoint_v2() function with the specified
|
||||
** mode argument (passive, full or restart). If present, the database name
|
||||
** NAME is passed as the second argument to wal_checkpoint_v2(). If it the
|
||||
** NAME argument is not present, a NULL pointer is passed instead.
|
||||
**
|
||||
** If wal_checkpoint_v2() returns any value other than SQLITE_BUSY or
|
||||
** SQLITE_OK, then this command returns TCL_ERROR. The Tcl result is set
|
||||
** to the error message obtained from sqlite3_errmsg().
|
||||
**
|
||||
** Otherwise, this command returns a list of three integers. The first integer
|
||||
** is 1 if SQLITE_BUSY was returned, or 0 otherwise. The following two integers
|
||||
** are the values returned via the output paramaters by wal_checkpoint_v2() -
|
||||
** the number of frames in the log and the number of frames in the log
|
||||
** that have been checkpointed.
|
||||
*/
|
||||
static int test_wal_checkpoint_v2(
|
||||
ClientData clientData, /* Unused */
|
||||
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
|
||||
int objc, /* Number of arguments */
|
||||
Tcl_Obj *CONST objv[] /* Command arguments */
|
||||
){
|
||||
char *zDb = 0;
|
||||
sqlite3 *db;
|
||||
int rc;
|
||||
|
||||
int eMode;
|
||||
int nLog = -555;
|
||||
int nCkpt = -555;
|
||||
Tcl_Obj *pRet;
|
||||
|
||||
const char * aMode[] = { "passive", "full", "restart", 0 };
|
||||
assert( SQLITE_CHECKPOINT_PASSIVE==0 );
|
||||
assert( SQLITE_CHECKPOINT_FULL==1 );
|
||||
assert( SQLITE_CHECKPOINT_RESTART==2 );
|
||||
|
||||
if( objc!=3 && objc!=4 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB MODE ?NAME?");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( objc==4 ){
|
||||
zDb = Tcl_GetString(objv[3]);
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db)
|
||||
|| Tcl_GetIndexFromObj(interp, objv[2], aMode, "mode", 0, &eMode)
|
||||
){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
rc = sqlite3_wal_checkpoint_v2(db, zDb, eMode, &nLog, &nCkpt);
|
||||
if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){
|
||||
Tcl_SetResult(interp, (char *)sqlite3_errmsg(db), TCL_VOLATILE);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
pRet = Tcl_NewObj();
|
||||
Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(rc==SQLITE_BUSY?1:0));
|
||||
Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(nLog));
|
||||
Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(nCkpt));
|
||||
Tcl_SetObjResult(interp, pRet);
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: test_sqlite3_log ?SCRIPT?
|
||||
*/
|
||||
@@ -5541,6 +5646,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
|
||||
{ "file_control_lasterrno_test", file_control_lasterrno_test, 0 },
|
||||
{ "file_control_lockproxy_test", file_control_lockproxy_test, 0 },
|
||||
{ "file_control_chunksize_test", file_control_chunksize_test, 0 },
|
||||
{ "file_control_sizehint_test", file_control_sizehint_test, 0 },
|
||||
{ "sqlite3_vfs_list", vfs_list, 0 },
|
||||
{ "sqlite3_create_function_v2", test_create_function_v2, 0 },
|
||||
|
||||
@@ -5572,8 +5678,11 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
|
||||
{ "sqlite3_unlock_notify", test_unlock_notify, 0 },
|
||||
#endif
|
||||
{ "sqlite3_wal_checkpoint", test_wal_checkpoint, 0 },
|
||||
{ "sqlite3_wal_checkpoint_v2",test_wal_checkpoint_v2, 0 },
|
||||
{ "test_sqlite3_log", test_sqlite3_log, 0 },
|
||||
#ifndef SQLITE_OMIT_EXPLAIN
|
||||
{ "print_explain_query_plan", test_print_eqp, 0 },
|
||||
#endif
|
||||
};
|
||||
static int bitmask_size = sizeof(Bitmask)*8;
|
||||
int i;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include "tcl.h"
|
||||
#if defined(SQLITE_OS_UNIX) && OS_UNIX==1 && SQLITE_THREADSAFE
|
||||
#if SQLITE_OS_UNIX && SQLITE_THREADSAFE
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
** the SQLITE_SERVER option.
|
||||
*/
|
||||
#if defined(SQLITE_SERVER) && !defined(SQLITE_OMIT_SHARED_CACHE) && \
|
||||
defined(SQLITE_OS_UNIX) && OS_UNIX && SQLITE_THREADSAFE
|
||||
SQLITE_OS_UNIX && SQLITE_THREADSAFE
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -91,6 +91,12 @@ static void set_options(Tcl_Interp *interp){
|
||||
Tcl_SetVar2(interp, "sqlite_options", "mutex", "1", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_MUTEX_NOOP
|
||||
Tcl_SetVar2(interp, "sqlite_options", "mutex_noop", "1", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
Tcl_SetVar2(interp, "sqlite_options", "mutex_noop", "0", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_OMIT_ALTERTABLE
|
||||
Tcl_SetVar2(interp, "sqlite_options", "altertable", "0", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
** operations.
|
||||
*/
|
||||
|
||||
#if !defined(SQLITE_TEST) || defined(SQLITE_OS_UNIX)
|
||||
#if !defined(SQLITE_TEST) || SQLITE_OS_UNIX
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
@@ -637,14 +637,14 @@ sqlite3_vfs *sqlite3_demovfs(void){
|
||||
return &demovfs;
|
||||
}
|
||||
|
||||
#endif /* !defined(SQLITE_TEST) || defined(SQLITE_OS_UNIX) */
|
||||
#endif /* !defined(SQLITE_TEST) || SQLITE_OS_UNIX */
|
||||
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
|
||||
#include <tcl.h>
|
||||
|
||||
#ifdef SQLITE_OS_UNIX
|
||||
#if SQLITE_OS_UNIX
|
||||
static int register_demovfs(
|
||||
ClientData clientData, /* Pointer to sqlite3_enable_XXX function */
|
||||
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
|
||||
|
||||
@@ -149,8 +149,13 @@ static void test_destructor_count(
|
||||
** arguments. It returns the text value returned by the sqlite3_errmsg16()
|
||||
** API function.
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_BUILTIN_TEST
|
||||
void sqlite3BeginBenignMalloc(void);
|
||||
void sqlite3EndBenignMalloc(void);
|
||||
#else
|
||||
#define sqlite3BeginBenignMalloc()
|
||||
#define sqlite3EndBenignMalloc()
|
||||
#endif
|
||||
static void test_agg_errmsg16_step(sqlite3_context *a, int b,sqlite3_value **c){
|
||||
}
|
||||
static void test_agg_errmsg16_final(sqlite3_context *ctx){
|
||||
|
||||
@@ -0,0 +1,944 @@
|
||||
/*
|
||||
** 2011 March 24
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
**
|
||||
** Code for demonstartion virtual table that generates variations
|
||||
** on an input word at increasing edit distances from the original.
|
||||
**
|
||||
** A fuzzer virtual table is created like this:
|
||||
**
|
||||
** CREATE VIRTUAL TABLE temp.f USING fuzzer;
|
||||
**
|
||||
** The name of the new virtual table in the example above is "f".
|
||||
** Note that all fuzzer virtual tables must be TEMP tables. The
|
||||
** "temp." prefix in front of the table name is required when the
|
||||
** table is being created. The "temp." prefix can be omitted when
|
||||
** using the table as long as the name is unambiguous.
|
||||
**
|
||||
** Before being used, the fuzzer needs to be programmed by giving it
|
||||
** character transformations and a cost associated with each transformation.
|
||||
** Examples:
|
||||
**
|
||||
** INSERT INTO f(cFrom,cTo,Cost) VALUES('','a',100);
|
||||
**
|
||||
** The above statement says that the cost of inserting a letter 'a' is
|
||||
** 100. (All costs are integers. We recommend that costs be scaled so
|
||||
** that the average cost is around 100.)
|
||||
**
|
||||
** INSERT INTO f(cFrom,cTo,Cost) VALUES('b','',87);
|
||||
**
|
||||
** The above statement says that the cost of deleting a single letter
|
||||
** 'b' is 87.
|
||||
**
|
||||
** INSERT INTO f(cFrom,cTo,Cost) VALUES('o','oe',38);
|
||||
** INSERT INTO f(cFrom,cTo,Cost) VALUES('oe','o',40);
|
||||
**
|
||||
** This third example says that the cost of transforming the single
|
||||
** letter "o" into the two-letter sequence "oe" is 38 and that the
|
||||
** cost of transforming "oe" back into "o" is 40.
|
||||
**
|
||||
** After all the transformation costs have been set, the fuzzer table
|
||||
** can be queried as follows:
|
||||
**
|
||||
** SELECT word, distance FROM f
|
||||
** WHERE word MATCH 'abcdefg'
|
||||
** AND distance<200;
|
||||
**
|
||||
** This first query outputs the string "abcdefg" and all strings that
|
||||
** can be derived from that string by appling the specified transformations.
|
||||
** The strings are output together with their total transformation cost
|
||||
** (called "distance") and appear in order of increasing cost. No string
|
||||
** is output more than once. If there are multiple ways to transform the
|
||||
** target string into the output string then the lowest cost transform is
|
||||
** the one that is returned. In the example, the search is limited to
|
||||
** strings with a total distance of less than 200.
|
||||
**
|
||||
** It is important to put some kind of a limit on the fuzzer output. This
|
||||
** can be either in the form of a LIMIT clause at the end of the query,
|
||||
** or better, a "distance<NNN" constraint where NNN is some number. The
|
||||
** running time and memory requirement is exponential in the value of NNN
|
||||
** so you want to make sure that NNN is not too big. A value of NNN that
|
||||
** is about twice the average transformation cost seems to give good results.
|
||||
**
|
||||
** The fuzzer table can be useful for tasks such as spelling correction.
|
||||
** Suppose there is a second table vocabulary(w) where the w column contains
|
||||
** all correctly spelled words. Let $word be a word you want to look up.
|
||||
**
|
||||
** SELECT vocabulary.w FROM f, vocabulary
|
||||
** WHERE f.word MATCH $word
|
||||
** AND f.distance<=200
|
||||
** AND f.word=vocabulary.w
|
||||
** LIMIT 20
|
||||
**
|
||||
** The query above gives the 20 closest words to the $word being tested.
|
||||
** (Note that for good performance, the vocubulary.w column should be
|
||||
** indexed.)
|
||||
**
|
||||
** A similar query can be used to find all words in the dictionary that
|
||||
** begin with some prefix $prefix:
|
||||
**
|
||||
** SELECT vocabulary.w FROM f, vocabulary
|
||||
** WHERE f.word MATCH $prefix
|
||||
** AND f.distance<=200
|
||||
** AND vocabulary.w BETWEEN f.word AND (f.word || x'F7BFBFBF')
|
||||
** LIMIT 50
|
||||
**
|
||||
** This last query will show up to 50 words out of the vocabulary that
|
||||
** match or nearly match the $prefix.
|
||||
*/
|
||||
#include "sqlite3.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
|
||||
/*
|
||||
** Forward declaration of objects used by this implementation
|
||||
*/
|
||||
typedef struct fuzzer_vtab fuzzer_vtab;
|
||||
typedef struct fuzzer_cursor fuzzer_cursor;
|
||||
typedef struct fuzzer_rule fuzzer_rule;
|
||||
typedef struct fuzzer_seen fuzzer_seen;
|
||||
typedef struct fuzzer_stem fuzzer_stem;
|
||||
|
||||
/*
|
||||
** Type of the "cost" of an edit operation. Might be changed to
|
||||
** "float" or "double" or "sqlite3_int64" in the future.
|
||||
*/
|
||||
typedef int fuzzer_cost;
|
||||
|
||||
|
||||
/*
|
||||
** Each transformation rule is stored as an instance of this object.
|
||||
** All rules are kept on a linked list sorted by rCost.
|
||||
*/
|
||||
struct fuzzer_rule {
|
||||
fuzzer_rule *pNext; /* Next rule in order of increasing rCost */
|
||||
fuzzer_cost rCost; /* Cost of this transformation */
|
||||
int nFrom, nTo; /* Length of the zFrom and zTo strings */
|
||||
char *zFrom; /* Transform from */
|
||||
char zTo[4]; /* Transform to (extra space appended) */
|
||||
};
|
||||
|
||||
/*
|
||||
** A stem object is used to generate variants. It is also used to record
|
||||
** previously generated outputs.
|
||||
**
|
||||
** Every stem is added to a hash table as it is output. Generation of
|
||||
** duplicate stems is suppressed.
|
||||
**
|
||||
** Active stems (those that might generate new outputs) are kepts on a linked
|
||||
** list sorted by increasing cost. The cost is the sum of rBaseCost and
|
||||
** pRule->rCost.
|
||||
*/
|
||||
struct fuzzer_stem {
|
||||
char *zBasis; /* Word being fuzzed */
|
||||
int nBasis; /* Length of the zBasis string */
|
||||
const fuzzer_rule *pRule; /* Current rule to apply */
|
||||
int n; /* Apply pRule at this character offset */
|
||||
fuzzer_cost rBaseCost; /* Base cost of getting to zBasis */
|
||||
fuzzer_cost rCostX; /* Precomputed rBaseCost + pRule->rCost */
|
||||
fuzzer_stem *pNext; /* Next stem in rCost order */
|
||||
fuzzer_stem *pHash; /* Next stem with same hash on zBasis */
|
||||
};
|
||||
|
||||
/*
|
||||
** A fuzzer virtual-table object
|
||||
*/
|
||||
struct fuzzer_vtab {
|
||||
sqlite3_vtab base; /* Base class - must be first */
|
||||
char *zClassName; /* Name of this class. Default: "fuzzer" */
|
||||
fuzzer_rule *pRule; /* All active rules in this fuzzer */
|
||||
fuzzer_rule *pNewRule; /* New rules to add when last cursor expires */
|
||||
int nCursor; /* Number of active cursors */
|
||||
};
|
||||
|
||||
#define FUZZER_HASH 4001 /* Hash table size */
|
||||
#define FUZZER_NQUEUE 20 /* Number of slots on the stem queue */
|
||||
|
||||
/* A fuzzer cursor object */
|
||||
struct fuzzer_cursor {
|
||||
sqlite3_vtab_cursor base; /* Base class - must be first */
|
||||
sqlite3_int64 iRowid; /* The rowid of the current word */
|
||||
fuzzer_vtab *pVtab; /* The virtual table this cursor belongs to */
|
||||
fuzzer_cost rLimit; /* Maximum cost of any term */
|
||||
fuzzer_stem *pStem; /* Stem with smallest rCostX */
|
||||
fuzzer_stem *pDone; /* Stems already processed to completion */
|
||||
fuzzer_stem *aQueue[FUZZER_NQUEUE]; /* Queue of stems with higher rCostX */
|
||||
int mxQueue; /* Largest used index in aQueue[] */
|
||||
char *zBuf; /* Temporary use buffer */
|
||||
int nBuf; /* Bytes allocated for zBuf */
|
||||
int nStem; /* Number of stems allocated */
|
||||
fuzzer_rule nullRule; /* Null rule used first */
|
||||
fuzzer_stem *apHash[FUZZER_HASH]; /* Hash of previously generated terms */
|
||||
};
|
||||
|
||||
/* Methods for the fuzzer module */
|
||||
static int fuzzerConnect(
|
||||
sqlite3 *db,
|
||||
void *pAux,
|
||||
int argc, const char *const*argv,
|
||||
sqlite3_vtab **ppVtab,
|
||||
char **pzErr
|
||||
){
|
||||
fuzzer_vtab *pNew;
|
||||
int n;
|
||||
if( strcmp(argv[1],"temp")!=0 ){
|
||||
*pzErr = sqlite3_mprintf("%s virtual tables must be TEMP", argv[0]);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
n = strlen(argv[0]) + 1;
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) + n );
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
pNew->zClassName = (char*)&pNew[1];
|
||||
memcpy(pNew->zClassName, argv[0], n);
|
||||
sqlite3_declare_vtab(db, "CREATE TABLE x(word,distance,cFrom,cTo,cost)");
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
*ppVtab = &pNew->base;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
/* Note that for this virtual table, the xCreate and xConnect
|
||||
** methods are identical. */
|
||||
|
||||
static int fuzzerDisconnect(sqlite3_vtab *pVtab){
|
||||
fuzzer_vtab *p = (fuzzer_vtab*)pVtab;
|
||||
assert( p->nCursor==0 );
|
||||
do{
|
||||
while( p->pRule ){
|
||||
fuzzer_rule *pRule = p->pRule;
|
||||
p->pRule = pRule->pNext;
|
||||
sqlite3_free(pRule);
|
||||
}
|
||||
p->pRule = p->pNewRule;
|
||||
p->pNewRule = 0;
|
||||
}while( p->pRule );
|
||||
sqlite3_free(p);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
/* The xDisconnect and xDestroy methods are also the same */
|
||||
|
||||
/*
|
||||
** The two input rule lists are both sorted in order of increasing
|
||||
** cost. Merge them together into a single list, sorted by cost, and
|
||||
** return a pointer to the head of that list.
|
||||
*/
|
||||
static fuzzer_rule *fuzzerMergeRules(fuzzer_rule *pA, fuzzer_rule *pB){
|
||||
fuzzer_rule head;
|
||||
fuzzer_rule *pTail;
|
||||
|
||||
pTail = &head;
|
||||
while( pA && pB ){
|
||||
if( pA->rCost<=pB->rCost ){
|
||||
pTail->pNext = pA;
|
||||
pTail = pA;
|
||||
pA = pA->pNext;
|
||||
}else{
|
||||
pTail->pNext = pB;
|
||||
pTail = pB;
|
||||
pB = pB->pNext;
|
||||
}
|
||||
}
|
||||
if( pA==0 ){
|
||||
pTail->pNext = pB;
|
||||
}else{
|
||||
pTail->pNext = pA;
|
||||
}
|
||||
return head.pNext;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Open a new fuzzer cursor.
|
||||
*/
|
||||
static int fuzzerOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
|
||||
fuzzer_vtab *p = (fuzzer_vtab*)pVTab;
|
||||
fuzzer_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
pCur->pVtab = p;
|
||||
*ppCursor = &pCur->base;
|
||||
if( p->nCursor==0 && p->pNewRule ){
|
||||
unsigned int i;
|
||||
fuzzer_rule *pX;
|
||||
fuzzer_rule *a[15];
|
||||
for(i=0; i<sizeof(a)/sizeof(a[0]); i++) a[i] = 0;
|
||||
while( (pX = p->pNewRule)!=0 ){
|
||||
p->pNewRule = pX->pNext;
|
||||
pX->pNext = 0;
|
||||
for(i=0; a[i] && i<sizeof(a)/sizeof(a[0])-1; i++){
|
||||
pX = fuzzerMergeRules(a[i], pX);
|
||||
a[i] = 0;
|
||||
}
|
||||
a[i] = fuzzerMergeRules(a[i], pX);
|
||||
}
|
||||
for(pX=a[0], i=1; i<sizeof(a)/sizeof(a[0]); i++){
|
||||
pX = fuzzerMergeRules(a[i], pX);
|
||||
}
|
||||
p->pRule = fuzzerMergeRules(p->pRule, pX);
|
||||
}
|
||||
p->nCursor++;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Free all stems in a list.
|
||||
*/
|
||||
static void fuzzerClearStemList(fuzzer_stem *pStem){
|
||||
while( pStem ){
|
||||
fuzzer_stem *pNext = pStem->pNext;
|
||||
sqlite3_free(pStem);
|
||||
pStem = pNext;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Free up all the memory allocated by a cursor. Set it rLimit to 0
|
||||
** to indicate that it is at EOF.
|
||||
*/
|
||||
static void fuzzerClearCursor(fuzzer_cursor *pCur, int clearHash){
|
||||
int i;
|
||||
fuzzerClearStemList(pCur->pStem);
|
||||
fuzzerClearStemList(pCur->pDone);
|
||||
for(i=0; i<FUZZER_NQUEUE; i++) fuzzerClearStemList(pCur->aQueue[i]);
|
||||
pCur->rLimit = (fuzzer_cost)0;
|
||||
if( clearHash && pCur->nStem ){
|
||||
pCur->mxQueue = 0;
|
||||
pCur->pStem = 0;
|
||||
pCur->pDone = 0;
|
||||
memset(pCur->aQueue, 0, sizeof(pCur->aQueue));
|
||||
memset(pCur->apHash, 0, sizeof(pCur->apHash));
|
||||
}
|
||||
pCur->nStem = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a fuzzer cursor.
|
||||
*/
|
||||
static int fuzzerClose(sqlite3_vtab_cursor *cur){
|
||||
fuzzer_cursor *pCur = (fuzzer_cursor *)cur;
|
||||
fuzzerClearCursor(pCur, 0);
|
||||
sqlite3_free(pCur->zBuf);
|
||||
pCur->pVtab->nCursor--;
|
||||
sqlite3_free(pCur);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Compute the current output term for a fuzzer_stem.
|
||||
*/
|
||||
static int fuzzerRender(
|
||||
fuzzer_stem *pStem, /* The stem to be rendered */
|
||||
char **pzBuf, /* Write results into this buffer. realloc if needed */
|
||||
int *pnBuf /* Size of the buffer */
|
||||
){
|
||||
const fuzzer_rule *pRule = pStem->pRule;
|
||||
int n;
|
||||
char *z;
|
||||
|
||||
n = pStem->nBasis + pRule->nTo - pRule->nFrom;
|
||||
if( (*pnBuf)<n+1 ){
|
||||
(*pzBuf) = sqlite3_realloc((*pzBuf), n+100);
|
||||
if( (*pzBuf)==0 ) return SQLITE_NOMEM;
|
||||
(*pnBuf) = n+100;
|
||||
}
|
||||
n = pStem->n;
|
||||
z = *pzBuf;
|
||||
if( n<0 ){
|
||||
memcpy(z, pStem->zBasis, pStem->nBasis+1);
|
||||
}else{
|
||||
memcpy(z, pStem->zBasis, n);
|
||||
memcpy(&z[n], pRule->zTo, pRule->nTo);
|
||||
memcpy(&z[n+pRule->nTo], &pStem->zBasis[n+pRule->nFrom],
|
||||
pStem->nBasis-n-pRule->nFrom+1);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Compute a hash on zBasis.
|
||||
*/
|
||||
static unsigned int fuzzerHash(const char *z){
|
||||
unsigned int h = 0;
|
||||
while( *z ){ h = (h<<3) ^ (h>>29) ^ *(z++); }
|
||||
return h % FUZZER_HASH;
|
||||
}
|
||||
|
||||
/*
|
||||
** Current cost of a stem
|
||||
*/
|
||||
static fuzzer_cost fuzzerCost(fuzzer_stem *pStem){
|
||||
return pStem->rCostX = pStem->rBaseCost + pStem->pRule->rCost;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/*
|
||||
** Print a description of a fuzzer_stem on stderr.
|
||||
*/
|
||||
static void fuzzerStemPrint(
|
||||
const char *zPrefix,
|
||||
fuzzer_stem *pStem,
|
||||
const char *zSuffix
|
||||
){
|
||||
if( pStem->n<0 ){
|
||||
fprintf(stderr, "%s[%s](%d)-->self%s",
|
||||
zPrefix,
|
||||
pStem->zBasis, pStem->rBaseCost,
|
||||
zSuffix
|
||||
);
|
||||
}else{
|
||||
char *zBuf = 0;
|
||||
int nBuf = 0;
|
||||
if( fuzzerRender(pStem, &zBuf, &nBuf)!=SQLITE_OK ) return;
|
||||
fprintf(stderr, "%s[%s](%d)-->{%s}(%d)%s",
|
||||
zPrefix,
|
||||
pStem->zBasis, pStem->rBaseCost, zBuf, pStem->,
|
||||
zSuffix
|
||||
);
|
||||
sqlite3_free(zBuf);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Return 1 if the string to which the cursor is point has already
|
||||
** been emitted. Return 0 if not. Return -1 on a memory allocation
|
||||
** failures.
|
||||
*/
|
||||
static int fuzzerSeen(fuzzer_cursor *pCur, fuzzer_stem *pStem){
|
||||
unsigned int h;
|
||||
fuzzer_stem *pLookup;
|
||||
|
||||
if( fuzzerRender(pStem, &pCur->zBuf, &pCur->nBuf)==SQLITE_NOMEM ){
|
||||
return -1;
|
||||
}
|
||||
h = fuzzerHash(pCur->zBuf);
|
||||
pLookup = pCur->apHash[h];
|
||||
while( pLookup && strcmp(pLookup->zBasis, pCur->zBuf)!=0 ){
|
||||
pLookup = pLookup->pHash;
|
||||
}
|
||||
return pLookup!=0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Advance a fuzzer_stem to its next value. Return 0 if there are
|
||||
** no more values that can be generated by this fuzzer_stem. Return
|
||||
** -1 on a memory allocation failure.
|
||||
*/
|
||||
static int fuzzerAdvance(fuzzer_cursor *pCur, fuzzer_stem *pStem){
|
||||
const fuzzer_rule *pRule;
|
||||
while( (pRule = pStem->pRule)!=0 ){
|
||||
while( pStem->n < pStem->nBasis - pRule->nFrom ){
|
||||
pStem->n++;
|
||||
if( pRule->nFrom==0
|
||||
|| memcmp(&pStem->zBasis[pStem->n], pRule->zFrom, pRule->nFrom)==0
|
||||
){
|
||||
/* Found a rewrite case. Make sure it is not a duplicate */
|
||||
int rc = fuzzerSeen(pCur, pStem);
|
||||
if( rc<0 ) return -1;
|
||||
if( rc==0 ){
|
||||
fuzzerCost(pStem);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
pStem->n = -1;
|
||||
pStem->pRule = pRule->pNext;
|
||||
if( pStem->pRule && fuzzerCost(pStem)>pCur->rLimit ) pStem->pRule = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** The two input stem lists are both sorted in order of increasing
|
||||
** rCostX. Merge them together into a single list, sorted by rCostX, and
|
||||
** return a pointer to the head of that new list.
|
||||
*/
|
||||
static fuzzer_stem *fuzzerMergeStems(fuzzer_stem *pA, fuzzer_stem *pB){
|
||||
fuzzer_stem head;
|
||||
fuzzer_stem *pTail;
|
||||
|
||||
pTail = &head;
|
||||
while( pA && pB ){
|
||||
if( pA->rCostX<=pB->rCostX ){
|
||||
pTail->pNext = pA;
|
||||
pTail = pA;
|
||||
pA = pA->pNext;
|
||||
}else{
|
||||
pTail->pNext = pB;
|
||||
pTail = pB;
|
||||
pB = pB->pNext;
|
||||
}
|
||||
}
|
||||
if( pA==0 ){
|
||||
pTail->pNext = pB;
|
||||
}else{
|
||||
pTail->pNext = pA;
|
||||
}
|
||||
return head.pNext;
|
||||
}
|
||||
|
||||
/*
|
||||
** Load pCur->pStem with the lowest-cost stem. Return a pointer
|
||||
** to the lowest-cost stem.
|
||||
*/
|
||||
static fuzzer_stem *fuzzerLowestCostStem(fuzzer_cursor *pCur){
|
||||
fuzzer_stem *pBest, *pX;
|
||||
int iBest;
|
||||
int i;
|
||||
|
||||
if( pCur->pStem==0 ){
|
||||
iBest = -1;
|
||||
pBest = 0;
|
||||
for(i=0; i<=pCur->mxQueue; i++){
|
||||
pX = pCur->aQueue[i];
|
||||
if( pX==0 ) continue;
|
||||
if( pBest==0 || pBest->rCostX>pX->rCostX ){
|
||||
pBest = pX;
|
||||
iBest = i;
|
||||
}
|
||||
}
|
||||
if( pBest ){
|
||||
pCur->aQueue[iBest] = pBest->pNext;
|
||||
pBest->pNext = 0;
|
||||
pCur->pStem = pBest;
|
||||
}
|
||||
}
|
||||
return pCur->pStem;
|
||||
}
|
||||
|
||||
/*
|
||||
** Insert pNew into queue of pending stems. Then find the stem
|
||||
** with the lowest rCostX and move it into pCur->pStem.
|
||||
** list. The insert is done such the pNew is in the correct order
|
||||
** according to fuzzer_stem.zBaseCost+fuzzer_stem.pRule->rCost.
|
||||
*/
|
||||
static fuzzer_stem *fuzzerInsert(fuzzer_cursor *pCur, fuzzer_stem *pNew){
|
||||
fuzzer_stem *pX;
|
||||
int i;
|
||||
|
||||
/* If pCur->pStem exists and is greater than pNew, then make pNew
|
||||
** the new pCur->pStem and insert the old pCur->pStem instead.
|
||||
*/
|
||||
if( (pX = pCur->pStem)!=0 && pX->rCostX>pNew->rCostX ){
|
||||
pNew->pNext = 0;
|
||||
pCur->pStem = pNew;
|
||||
pNew = pX;
|
||||
}
|
||||
|
||||
/* Insert the new value */
|
||||
pNew->pNext = 0;
|
||||
pX = pNew;
|
||||
for(i=0; i<=pCur->mxQueue; i++){
|
||||
if( pCur->aQueue[i] ){
|
||||
pX = fuzzerMergeStems(pX, pCur->aQueue[i]);
|
||||
pCur->aQueue[i] = 0;
|
||||
}else{
|
||||
pCur->aQueue[i] = pX;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( i>pCur->mxQueue ){
|
||||
if( i<FUZZER_NQUEUE ){
|
||||
pCur->mxQueue = i;
|
||||
pCur->aQueue[i] = pX;
|
||||
}else{
|
||||
assert( pCur->mxQueue==FUZZER_NQUEUE-1 );
|
||||
pX = fuzzerMergeStems(pX, pCur->aQueue[FUZZER_NQUEUE-1]);
|
||||
pCur->aQueue[FUZZER_NQUEUE-1] = pX;
|
||||
}
|
||||
}
|
||||
|
||||
return fuzzerLowestCostStem(pCur);
|
||||
}
|
||||
|
||||
/*
|
||||
** Allocate a new fuzzer_stem. Add it to the hash table but do not
|
||||
** link it into either the pCur->pStem or pCur->pDone lists.
|
||||
*/
|
||||
static fuzzer_stem *fuzzerNewStem(
|
||||
fuzzer_cursor *pCur,
|
||||
const char *zWord,
|
||||
fuzzer_cost rBaseCost
|
||||
){
|
||||
fuzzer_stem *pNew;
|
||||
unsigned int h;
|
||||
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) + strlen(zWord) + 1 );
|
||||
if( pNew==0 ) return 0;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
pNew->zBasis = (char*)&pNew[1];
|
||||
pNew->nBasis = strlen(zWord);
|
||||
memcpy(pNew->zBasis, zWord, pNew->nBasis+1);
|
||||
pNew->pRule = pCur->pVtab->pRule;
|
||||
pNew->n = -1;
|
||||
pNew->rBaseCost = pNew->rCostX = rBaseCost;
|
||||
h = fuzzerHash(pNew->zBasis);
|
||||
pNew->pHash = pCur->apHash[h];
|
||||
pCur->apHash[h] = pNew;
|
||||
pCur->nStem++;
|
||||
return pNew;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Advance a cursor to its next row of output
|
||||
*/
|
||||
static int fuzzerNext(sqlite3_vtab_cursor *cur){
|
||||
fuzzer_cursor *pCur = (fuzzer_cursor*)cur;
|
||||
int rc;
|
||||
fuzzer_stem *pStem, *pNew;
|
||||
|
||||
pCur->iRowid++;
|
||||
|
||||
/* Use the element the cursor is currently point to to create
|
||||
** a new stem and insert the new stem into the priority queue.
|
||||
*/
|
||||
pStem = pCur->pStem;
|
||||
if( pStem->rCostX>0 ){
|
||||
rc = fuzzerRender(pStem, &pCur->zBuf, &pCur->nBuf);
|
||||
if( rc==SQLITE_NOMEM ) return SQLITE_NOMEM;
|
||||
pNew = fuzzerNewStem(pCur, pCur->zBuf, pStem->rCostX);
|
||||
if( pNew ){
|
||||
if( fuzzerAdvance(pCur, pNew)==0 ){
|
||||
pNew->pNext = pCur->pDone;
|
||||
pCur->pDone = pNew;
|
||||
}else{
|
||||
if( fuzzerInsert(pCur, pNew)==pNew ){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
/* Adjust the priority queue so that the first element of the
|
||||
** stem list is the next lowest cost word.
|
||||
*/
|
||||
while( (pStem = pCur->pStem)!=0 ){
|
||||
if( fuzzerAdvance(pCur, pStem) ){
|
||||
pCur->pStem = 0;
|
||||
pStem = fuzzerInsert(pCur, pStem);
|
||||
if( (rc = fuzzerSeen(pCur, pStem))!=0 ){
|
||||
if( rc<0 ) return SQLITE_NOMEM;
|
||||
continue;
|
||||
}
|
||||
return SQLITE_OK; /* New word found */
|
||||
}
|
||||
pCur->pStem = 0;
|
||||
pStem->pNext = pCur->pDone;
|
||||
pCur->pDone = pStem;
|
||||
if( fuzzerLowestCostStem(pCur) ){
|
||||
rc = fuzzerSeen(pCur, pCur->pStem);
|
||||
if( rc<0 ) return SQLITE_NOMEM;
|
||||
if( rc==0 ){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Reach this point only if queue has been exhausted and there is
|
||||
** nothing left to be output. */
|
||||
pCur->rLimit = (fuzzer_cost)0;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Called to "rewind" a cursor back to the beginning so that
|
||||
** it starts its output over again. Always called at least once
|
||||
** prior to any fuzzerColumn, fuzzerRowid, or fuzzerEof call.
|
||||
*/
|
||||
static int fuzzerFilter(
|
||||
sqlite3_vtab_cursor *pVtabCursor,
|
||||
int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv
|
||||
){
|
||||
fuzzer_cursor *pCur = (fuzzer_cursor *)pVtabCursor;
|
||||
const char *zWord = 0;
|
||||
fuzzer_stem *pStem;
|
||||
|
||||
fuzzerClearCursor(pCur, 1);
|
||||
pCur->rLimit = 2147483647;
|
||||
if( idxNum==1 ){
|
||||
zWord = (const char*)sqlite3_value_text(argv[0]);
|
||||
}else if( idxNum==2 ){
|
||||
pCur->rLimit = (fuzzer_cost)sqlite3_value_int(argv[0]);
|
||||
}else if( idxNum==3 ){
|
||||
zWord = (const char*)sqlite3_value_text(argv[0]);
|
||||
pCur->rLimit = (fuzzer_cost)sqlite3_value_int(argv[1]);
|
||||
}
|
||||
if( zWord==0 ) zWord = "";
|
||||
pCur->pStem = pStem = fuzzerNewStem(pCur, zWord, (fuzzer_cost)0);
|
||||
if( pStem==0 ) return SQLITE_NOMEM;
|
||||
pCur->nullRule.pNext = pCur->pVtab->pRule;
|
||||
pCur->nullRule.rCost = 0;
|
||||
pCur->nullRule.nFrom = 0;
|
||||
pCur->nullRule.nTo = 0;
|
||||
pCur->nullRule.zFrom = "";
|
||||
pStem->pRule = &pCur->nullRule;
|
||||
pStem->n = pStem->nBasis;
|
||||
pCur->iRowid = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Only the word and distance columns have values. All other columns
|
||||
** return NULL
|
||||
*/
|
||||
static int fuzzerColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
|
||||
fuzzer_cursor *pCur = (fuzzer_cursor*)cur;
|
||||
if( i==0 ){
|
||||
/* the "word" column */
|
||||
if( fuzzerRender(pCur->pStem, &pCur->zBuf, &pCur->nBuf)==SQLITE_NOMEM ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
sqlite3_result_text(ctx, pCur->zBuf, -1, SQLITE_TRANSIENT);
|
||||
}else if( i==1 ){
|
||||
/* the "distance" column */
|
||||
sqlite3_result_int(ctx, pCur->pStem->rCostX);
|
||||
}else{
|
||||
/* All other columns are NULL */
|
||||
sqlite3_result_null(ctx);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** The rowid.
|
||||
*/
|
||||
static int fuzzerRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
|
||||
fuzzer_cursor *pCur = (fuzzer_cursor*)cur;
|
||||
*pRowid = pCur->iRowid;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** When the fuzzer_cursor.rLimit value is 0 or less, that is a signal
|
||||
** that the cursor has nothing more to output.
|
||||
*/
|
||||
static int fuzzerEof(sqlite3_vtab_cursor *cur){
|
||||
fuzzer_cursor *pCur = (fuzzer_cursor*)cur;
|
||||
return pCur->rLimit<=(fuzzer_cost)0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Search for terms of these forms:
|
||||
**
|
||||
** word MATCH $str
|
||||
** distance < $value
|
||||
** distance <= $value
|
||||
**
|
||||
** The distance< and distance<= are both treated as distance<=.
|
||||
** The query plan number is as follows:
|
||||
**
|
||||
** 0: None of the terms above are found
|
||||
** 1: There is a "word MATCH" term with $str in filter.argv[0].
|
||||
** 2: There is a "distance<" term with $value in filter.argv[0].
|
||||
** 3: Both "word MATCH" and "distance<" with $str in argv[0] and
|
||||
** $value in argv[1].
|
||||
*/
|
||||
static int fuzzerBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
int iPlan = 0;
|
||||
int iDistTerm = -1;
|
||||
int i;
|
||||
const struct sqlite3_index_constraint *pConstraint;
|
||||
pConstraint = pIdxInfo->aConstraint;
|
||||
for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
|
||||
if( pConstraint->usable==0 ) continue;
|
||||
if( (iPlan & 1)==0
|
||||
&& pConstraint->iColumn==0
|
||||
&& pConstraint->op==SQLITE_INDEX_CONSTRAINT_MATCH
|
||||
){
|
||||
iPlan |= 1;
|
||||
pIdxInfo->aConstraintUsage[i].argvIndex = 1;
|
||||
pIdxInfo->aConstraintUsage[i].omit = 1;
|
||||
}
|
||||
if( (iPlan & 2)==0
|
||||
&& pConstraint->iColumn==1
|
||||
&& (pConstraint->op==SQLITE_INDEX_CONSTRAINT_LT
|
||||
|| pConstraint->op==SQLITE_INDEX_CONSTRAINT_LE)
|
||||
){
|
||||
iPlan |= 2;
|
||||
iDistTerm = i;
|
||||
}
|
||||
}
|
||||
if( iPlan==2 ){
|
||||
pIdxInfo->aConstraintUsage[iDistTerm].argvIndex = 1;
|
||||
}else if( iPlan==3 ){
|
||||
pIdxInfo->aConstraintUsage[iDistTerm].argvIndex = 2;
|
||||
}
|
||||
pIdxInfo->idxNum = iPlan;
|
||||
if( pIdxInfo->nOrderBy==1
|
||||
&& pIdxInfo->aOrderBy[0].iColumn==1
|
||||
&& pIdxInfo->aOrderBy[0].desc==0
|
||||
){
|
||||
pIdxInfo->orderByConsumed = 1;
|
||||
}
|
||||
pIdxInfo->estimatedCost = (double)10000;
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Disallow all attempts to DELETE or UPDATE. Only INSERTs are allowed.
|
||||
**
|
||||
** On an insert, the cFrom, cTo, and cost columns are used to construct
|
||||
** a new rule. All other columns are ignored. The rule is ignored
|
||||
** if cFrom and cTo are identical. A NULL value for cFrom or cTo is
|
||||
** interpreted as an empty string. The cost must be positive.
|
||||
*/
|
||||
static int fuzzerUpdate(
|
||||
sqlite3_vtab *pVTab,
|
||||
int argc,
|
||||
sqlite3_value **argv,
|
||||
sqlite_int64 *pRowid
|
||||
){
|
||||
fuzzer_vtab *p = (fuzzer_vtab*)pVTab;
|
||||
fuzzer_rule *pRule;
|
||||
const char *zFrom;
|
||||
int nFrom;
|
||||
const char *zTo;
|
||||
int nTo;
|
||||
fuzzer_cost rCost;
|
||||
if( argc!=7 ){
|
||||
sqlite3_free(pVTab->zErrMsg);
|
||||
pVTab->zErrMsg = sqlite3_mprintf("cannot delete from a %s virtual table",
|
||||
p->zClassName);
|
||||
return SQLITE_CONSTRAINT;
|
||||
}
|
||||
if( sqlite3_value_type(argv[0])!=SQLITE_NULL ){
|
||||
sqlite3_free(pVTab->zErrMsg);
|
||||
pVTab->zErrMsg = sqlite3_mprintf("cannot update a %s virtual table",
|
||||
p->zClassName);
|
||||
return SQLITE_CONSTRAINT;
|
||||
}
|
||||
zFrom = (char*)sqlite3_value_text(argv[4]);
|
||||
if( zFrom==0 ) zFrom = "";
|
||||
zTo = (char*)sqlite3_value_text(argv[5]);
|
||||
if( zTo==0 ) zTo = "";
|
||||
if( strcmp(zFrom,zTo)==0 ){
|
||||
/* Silently ignore null transformations */
|
||||
return SQLITE_OK;
|
||||
}
|
||||
rCost = sqlite3_value_int(argv[6]);
|
||||
if( rCost<=0 ){
|
||||
sqlite3_free(pVTab->zErrMsg);
|
||||
pVTab->zErrMsg = sqlite3_mprintf("cost must be positive");
|
||||
return SQLITE_CONSTRAINT;
|
||||
}
|
||||
nFrom = strlen(zFrom);
|
||||
nTo = strlen(zTo);
|
||||
pRule = sqlite3_malloc( sizeof(*pRule) + nFrom + nTo );
|
||||
if( pRule==0 ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
pRule->zFrom = &pRule->zTo[nTo+1];
|
||||
pRule->nFrom = nFrom;
|
||||
memcpy(pRule->zFrom, zFrom, nFrom+1);
|
||||
memcpy(pRule->zTo, zTo, nTo+1);
|
||||
pRule->nTo = nTo;
|
||||
pRule->rCost = rCost;
|
||||
pRule->pNext = p->pNewRule;
|
||||
p->pNewRule = pRule;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** A virtual table module that provides read-only access to a
|
||||
** Tcl global variable namespace.
|
||||
*/
|
||||
static sqlite3_module fuzzerModule = {
|
||||
0, /* iVersion */
|
||||
fuzzerConnect,
|
||||
fuzzerConnect,
|
||||
fuzzerBestIndex,
|
||||
fuzzerDisconnect,
|
||||
fuzzerDisconnect,
|
||||
fuzzerOpen, /* xOpen - open a cursor */
|
||||
fuzzerClose, /* xClose - close a cursor */
|
||||
fuzzerFilter, /* xFilter - configure scan constraints */
|
||||
fuzzerNext, /* xNext - advance a cursor */
|
||||
fuzzerEof, /* xEof - check for end of scan */
|
||||
fuzzerColumn, /* xColumn - read data */
|
||||
fuzzerRowid, /* xRowid - read data */
|
||||
fuzzerUpdate, /* xUpdate - INSERT */
|
||||
0, /* xBegin */
|
||||
0, /* xSync */
|
||||
0, /* xCommit */
|
||||
0, /* xRollback */
|
||||
0, /* xFindMethod */
|
||||
0, /* xRename */
|
||||
};
|
||||
|
||||
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
||||
|
||||
|
||||
/*
|
||||
** Register the fuzzer virtual table
|
||||
*/
|
||||
int fuzzer_register(sqlite3 *db){
|
||||
int rc = SQLITE_OK;
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
rc = sqlite3_create_module(db, "fuzzer", &fuzzerModule, 0);
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
#include <tcl.h>
|
||||
/*
|
||||
** Decode a pointer to an sqlite3 object.
|
||||
*/
|
||||
extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb);
|
||||
|
||||
/*
|
||||
** Register the echo virtual table module.
|
||||
*/
|
||||
static int register_fuzzer_module(
|
||||
ClientData clientData, /* Pointer to sqlite3_enable_XXX function */
|
||||
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
|
||||
int objc, /* Number of arguments */
|
||||
Tcl_Obj *CONST objv[] /* Command arguments */
|
||||
){
|
||||
sqlite3 *db;
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
|
||||
fuzzer_register(db);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Register commands with the TCL interpreter.
|
||||
*/
|
||||
int Sqlitetestfuzzer_Init(Tcl_Interp *interp){
|
||||
static struct {
|
||||
char *zName;
|
||||
Tcl_ObjCmdProc *xProc;
|
||||
void *clientData;
|
||||
} aObjCmd[] = {
|
||||
{ "register_fuzzer_module", register_fuzzer_module, 0 },
|
||||
};
|
||||
int i;
|
||||
for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
|
||||
Tcl_CreateObjCommand(interp, aObjCmd[i].zName,
|
||||
aObjCmd[i].xProc, aObjCmd[i].clientData, 0);
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
#endif /* SQLITE_TEST */
|
||||
@@ -312,8 +312,13 @@ static int utf8_to_utf8(
|
||||
sqlite3TestBinToHex(z,nOut);
|
||||
Tcl_AppendResult(interp, (char*)z, 0);
|
||||
sqlite3_free(z);
|
||||
#endif
|
||||
return TCL_OK;
|
||||
#else
|
||||
Tcl_AppendResult(interp,
|
||||
"[utf8_to_utf8] unavailable - SQLITE_DEBUG not defined", 0
|
||||
);
|
||||
return TCL_ERROR;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int getFts3Varint(const char *p, sqlite_int64 *v){
|
||||
|
||||
@@ -22,18 +22,51 @@
|
||||
#include "sqlite3.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "sqliteInt.h"
|
||||
#include "test_multiplex.h"
|
||||
|
||||
#ifndef SQLITE_CORE
|
||||
#define SQLITE_CORE 1 /* Disable the API redefinition in sqlite3ext.h */
|
||||
#endif
|
||||
#include "sqlite3ext.h"
|
||||
|
||||
/*
|
||||
** These should be defined to be the same as the values in
|
||||
** sqliteInt.h. They are defined seperately here so that
|
||||
** the multiplex VFS shim can be built as a loadable
|
||||
** module.
|
||||
*/
|
||||
#define UNUSED_PARAMETER(x) (void)(x)
|
||||
#define MAX_PAGE_SIZE 0x10000
|
||||
#define DEFAULT_SECTOR_SIZE 0x1000
|
||||
|
||||
/*
|
||||
** For a build without mutexes, no-op the mutex calls.
|
||||
*/
|
||||
#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
|
||||
#define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8)
|
||||
#define sqlite3_mutex_free(X)
|
||||
#define sqlite3_mutex_enter(X)
|
||||
#define sqlite3_mutex_try(X) SQLITE_OK
|
||||
#define sqlite3_mutex_leave(X)
|
||||
#define sqlite3_mutex_held(X) ((void)(X),1)
|
||||
#define sqlite3_mutex_notheld(X) ((void)(X),1)
|
||||
#endif /* SQLITE_THREADSAFE==0 */
|
||||
|
||||
|
||||
/************************ Shim Definitions ******************************/
|
||||
|
||||
#define SQLITE_MULTIPLEX_VFS_NAME "multiplex"
|
||||
|
||||
/* This is the limit on the chunk size. It may be changed by calling
|
||||
** the sqlite3_multiplex_set() interface.
|
||||
** the xFileControl() interface. It will be rounded up to a
|
||||
** multiple of MAX_PAGE_SIZE. We default it here to 1GB.
|
||||
*/
|
||||
#define SQLITE_MULTIPLEX_CHUNK_SIZE 0x40000000
|
||||
#define SQLITE_MULTIPLEX_CHUNK_SIZE (MAX_PAGE_SIZE*16384)
|
||||
|
||||
/* Default limit on number of chunks. Care should be taken
|
||||
** so that values for chunks numbers fit in the SQLITE_MULTIPLEX_EXT_FMT
|
||||
** format specifier. It may be changed by calling
|
||||
** the sqlite3_multiplex_set() interface.
|
||||
** the xFileControl() interface.
|
||||
*/
|
||||
#define SQLITE_MULTIPLEX_MAX_CHUNKS 32
|
||||
|
||||
@@ -64,10 +97,13 @@ typedef struct multiplexConn multiplexConn;
|
||||
*/
|
||||
struct multiplexGroup {
|
||||
sqlite3_file **pReal; /* Handles to each chunk */
|
||||
char *bOpen; /* 0 if chunk not opened */
|
||||
char *bOpen; /* array of bools - 0 if chunk not opened */
|
||||
char *zName; /* Base filename of this group */
|
||||
int nName; /* Length of base filename */
|
||||
int flags; /* Flags used for original opening */
|
||||
int nChunkSize; /* Chunk size used for this group */
|
||||
int nMaxChunks; /* Max number of chunks for this group */
|
||||
int bEnabled; /* TRUE to use Multiplex VFS for this file */
|
||||
multiplexGroup *pNext, *pPrev; /* Doubly linked list of all group objects */
|
||||
};
|
||||
|
||||
@@ -126,11 +162,6 @@ static struct {
|
||||
*/
|
||||
multiplexGroup *pGroups;
|
||||
|
||||
/* Chunk params.
|
||||
*/
|
||||
int nChunkSize;
|
||||
int nMaxChunks;
|
||||
|
||||
/* Storage for temp file names. Allocated during
|
||||
** initialization to the max pathname of the underlying VFS.
|
||||
*/
|
||||
@@ -146,13 +177,28 @@ static struct {
|
||||
static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); }
|
||||
static void multiplexLeave(void){ sqlite3_mutex_leave(gMultiplex.pMutex); }
|
||||
|
||||
/*
|
||||
** Compute a string length that is limited to what can be stored in
|
||||
** lower 30 bits of a 32-bit signed integer.
|
||||
**
|
||||
** The value returned will never be negative. Nor will it ever be greater
|
||||
** than the actual length of the string. For very long strings (greater
|
||||
** than 1GiB) the value returned might be less than the true string length.
|
||||
*/
|
||||
int multiplexStrlen30(const char *z){
|
||||
const char *z2 = z;
|
||||
if( z==0 ) return 0;
|
||||
while( *z2 ){ z2++; }
|
||||
return 0x3fffffff & (int)(z2 - z);
|
||||
}
|
||||
|
||||
/* Translate an sqlite3_file* that is really a multiplexGroup* into
|
||||
** the sqlite3_file* for the underlying original VFS.
|
||||
*/
|
||||
static sqlite3_file *multiplexSubOpen(multiplexConn *pConn, int iChunk, int *rc, int *pOutFlags){
|
||||
multiplexGroup *pGroup = pConn->pGroup;
|
||||
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */
|
||||
if( iChunk<gMultiplex.nMaxChunks ){
|
||||
if( iChunk<pGroup->nMaxChunks ){
|
||||
sqlite3_file *pSubOpen = pGroup->pReal[iChunk]; /* Real file descriptor */
|
||||
if( !pGroup->bOpen[iChunk] ){
|
||||
memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
|
||||
@@ -177,6 +223,62 @@ static sqlite3_file *multiplexSubOpen(multiplexConn *pConn, int iChunk, int *rc,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
** This is the implementation of the multiplex_control() SQL function.
|
||||
*/
|
||||
static void multiplexControlFunc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
sqlite3 *db = sqlite3_context_db_handle(context);
|
||||
int op;
|
||||
int iVal;
|
||||
|
||||
if( !db || argc!=2 ){
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
/* extract params */
|
||||
op = sqlite3_value_int(argv[0]);
|
||||
iVal = sqlite3_value_int(argv[1]);
|
||||
/* map function op to file_control op */
|
||||
switch( op ){
|
||||
case 1:
|
||||
op = MULTIPLEX_CTRL_ENABLE;
|
||||
break;
|
||||
case 2:
|
||||
op = MULTIPLEX_CTRL_SET_CHUNK_SIZE;
|
||||
break;
|
||||
case 3:
|
||||
op = MULTIPLEX_CTRL_SET_MAX_CHUNKS;
|
||||
break;
|
||||
default:
|
||||
rc = SQLITE_NOTFOUND;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_file_control(db, 0, op, &iVal);
|
||||
}
|
||||
sqlite3_result_error_code(context, rc);
|
||||
}
|
||||
|
||||
/*
|
||||
** This is the entry point to register the auto-extension for the
|
||||
** multiplex_control() function.
|
||||
*/
|
||||
static int multiplexFuncInit(
|
||||
sqlite3 *db,
|
||||
char **pzErrMsg,
|
||||
const sqlite3_api_routines *pApi
|
||||
){
|
||||
int rc;
|
||||
rc = sqlite3_create_function(db, "multiplex_control", 2, SQLITE_ANY,
|
||||
0, multiplexControlFunc, 0, 0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/************************* VFS Method Wrappers *****************************/
|
||||
|
||||
/*
|
||||
@@ -198,7 +300,7 @@ static int multiplexOpen(
|
||||
multiplexGroup *pGroup; /* Corresponding multiplexGroup object */
|
||||
sqlite3_file *pSubOpen; /* Real file descriptor */
|
||||
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */
|
||||
int nName = sqlite3Strlen30(zName);
|
||||
int nName = multiplexStrlen30(zName);
|
||||
int i;
|
||||
int sz;
|
||||
|
||||
@@ -210,11 +312,11 @@ static int multiplexOpen(
|
||||
multiplexEnter();
|
||||
pMultiplexOpen = (multiplexConn*)pConn;
|
||||
/* allocate space for group */
|
||||
sz = sizeof(multiplexGroup) /* multiplexGroup */
|
||||
+ (sizeof(sqlite3_file *)*gMultiplex.nMaxChunks) /* pReal[] */
|
||||
+ (pOrigVfs->szOsFile*gMultiplex.nMaxChunks) /* *pReal */
|
||||
+ gMultiplex.nMaxChunks /* bOpen[] */
|
||||
+ nName + 1; /* zName */
|
||||
sz = sizeof(multiplexGroup) /* multiplexGroup */
|
||||
+ (sizeof(sqlite3_file *)*SQLITE_MULTIPLEX_MAX_CHUNKS) /* pReal[] */
|
||||
+ (pOrigVfs->szOsFile*SQLITE_MULTIPLEX_MAX_CHUNKS) /* *pReal */
|
||||
+ SQLITE_MULTIPLEX_MAX_CHUNKS /* bOpen[] */
|
||||
+ nName + 1; /* zName */
|
||||
#ifndef SQLITE_MULTIPLEX_EXT_OVWR
|
||||
sz += SQLITE_MULTIPLEX_EXT_SZ;
|
||||
assert(nName+SQLITE_MULTIPLEX_EXT_SZ < pOrigVfs->mxPathname);
|
||||
@@ -230,14 +332,18 @@ static int multiplexOpen(
|
||||
char *p = (char *)&pGroup[1];
|
||||
pMultiplexOpen->pGroup = pGroup;
|
||||
memset(pGroup, 0, sz);
|
||||
pGroup->bEnabled = -1;
|
||||
pGroup->nChunkSize = SQLITE_MULTIPLEX_CHUNK_SIZE;
|
||||
pGroup->nMaxChunks = SQLITE_MULTIPLEX_MAX_CHUNKS;
|
||||
pGroup->pReal = (sqlite3_file **)p;
|
||||
p += (sizeof(sqlite3_file *)*gMultiplex.nMaxChunks);
|
||||
for(i=0; i<gMultiplex.nMaxChunks; i++){
|
||||
p += (sizeof(sqlite3_file *)*pGroup->nMaxChunks);
|
||||
for(i=0; i<pGroup->nMaxChunks; i++){
|
||||
pGroup->pReal[i] = (sqlite3_file *)p;
|
||||
p += pOrigVfs->szOsFile;
|
||||
}
|
||||
/* bOpen[] vals should all be zero from memset above */
|
||||
pGroup->bOpen = p;
|
||||
p += gMultiplex.nMaxChunks;
|
||||
p += pGroup->nMaxChunks;
|
||||
pGroup->zName = p;
|
||||
/* save off base filename, name length, and original open flags */
|
||||
memcpy(pGroup->zName, zName, nName+1);
|
||||
@@ -245,6 +351,14 @@ static int multiplexOpen(
|
||||
pGroup->flags = flags;
|
||||
pSubOpen = multiplexSubOpen(pMultiplexOpen, 0, &rc, pOutFlags);
|
||||
if( pSubOpen ){
|
||||
/* if this file is already larger than chunk size, disable
|
||||
** the multiplex feature.
|
||||
*/
|
||||
sqlite3_int64 sz;
|
||||
int rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
|
||||
if( (rc2==SQLITE_OK) && (sz>pGroup->nChunkSize) ){
|
||||
pGroup->bEnabled = 0;
|
||||
}
|
||||
if( pSubOpen->pMethods->iVersion==1 ){
|
||||
pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV1;
|
||||
}else{
|
||||
@@ -274,24 +388,29 @@ static int multiplexDelete(
|
||||
){
|
||||
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */
|
||||
int rc = SQLITE_OK;
|
||||
int nName = sqlite3Strlen30(zName);
|
||||
int nName = multiplexStrlen30(zName);
|
||||
int i;
|
||||
|
||||
UNUSED_PARAMETER(pVfs);
|
||||
|
||||
multiplexEnter();
|
||||
memcpy(gMultiplex.zName, zName, nName+1);
|
||||
for(i=0; i<gMultiplex.nMaxChunks; i++){
|
||||
for(i=0; i<SQLITE_MULTIPLEX_MAX_CHUNKS; i++){
|
||||
int rc2;
|
||||
int exists = 0;
|
||||
if( i ){
|
||||
#ifdef SQLITE_MULTIPLEX_EXT_OVWR
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
|
||||
gMultiplex.zName+nName-SQLITE_MULTIPLEX_EXT_SZ,
|
||||
SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#else
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+nName, SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
|
||||
gMultiplex.zName+nName,
|
||||
SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#endif
|
||||
}
|
||||
rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, SQLITE_ACCESS_EXISTS, &exists);
|
||||
rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName,
|
||||
SQLITE_ACCESS_EXISTS, &exists);
|
||||
if( rc2==SQLITE_OK && exists){
|
||||
/* if it exists, delete it */
|
||||
rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, syncDir);
|
||||
@@ -353,7 +472,7 @@ static int multiplexClose(sqlite3_file *pConn){
|
||||
int i;
|
||||
multiplexEnter();
|
||||
/* close any open handles */
|
||||
for(i=0; i<gMultiplex.nMaxChunks; i++){
|
||||
for(i=0; i<pGroup->nMaxChunks; i++){
|
||||
if( pGroup->bOpen[i] ){
|
||||
sqlite3_file *pSubOpen = pGroup->pReal[i];
|
||||
int rc2 = pSubOpen->pMethods->xClose(pSubOpen);
|
||||
@@ -384,23 +503,29 @@ static int multiplexRead(
|
||||
sqlite3_int64 iOfst
|
||||
){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
multiplexGroup *pGroup = p->pGroup;
|
||||
int rc = SQLITE_OK;
|
||||
multiplexEnter();
|
||||
while( iAmt > 0 ){
|
||||
int i = (int)(iOfst/gMultiplex.nChunkSize);
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize;
|
||||
if( extra<0 ) extra = 0;
|
||||
iAmt -= extra;
|
||||
rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
pBuf = (char *)pBuf + iAmt;
|
||||
iOfst += iAmt;
|
||||
iAmt = extra;
|
||||
}else{
|
||||
rc = SQLITE_IOERR_READ;
|
||||
break;
|
||||
if( !pGroup->bEnabled ){
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
rc = ( !pSubOpen ) ? SQLITE_IOERR_READ : pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst);
|
||||
}else{
|
||||
while( iAmt > 0 ){
|
||||
int i = (int)(iOfst / pGroup->nChunkSize);
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
int extra = ((int)(iOfst % pGroup->nChunkSize) + iAmt) - pGroup->nChunkSize;
|
||||
if( extra<0 ) extra = 0;
|
||||
iAmt -= extra;
|
||||
rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst % pGroup->nChunkSize);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
pBuf = (char *)pBuf + iAmt;
|
||||
iOfst += iAmt;
|
||||
iAmt = extra;
|
||||
}else{
|
||||
rc = SQLITE_IOERR_READ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
multiplexLeave();
|
||||
@@ -418,23 +543,29 @@ static int multiplexWrite(
|
||||
sqlite3_int64 iOfst
|
||||
){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
multiplexGroup *pGroup = p->pGroup;
|
||||
int rc = SQLITE_OK;
|
||||
multiplexEnter();
|
||||
while( iAmt > 0 ){
|
||||
int i = (int)(iOfst/gMultiplex.nChunkSize);
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize;
|
||||
if( extra<0 ) extra = 0;
|
||||
iAmt -= extra;
|
||||
rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
pBuf = (char *)pBuf + iAmt;
|
||||
iOfst += iAmt;
|
||||
iAmt = extra;
|
||||
}else{
|
||||
rc = SQLITE_IOERR_WRITE;
|
||||
break;
|
||||
if( !pGroup->bEnabled ){
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
rc = ( !pSubOpen ) ? SQLITE_IOERR_WRITE : pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst);
|
||||
}else{
|
||||
while( iAmt > 0 ){
|
||||
int i = (int)(iOfst / pGroup->nChunkSize);
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
int extra = ((int)(iOfst % pGroup->nChunkSize) + iAmt) - pGroup->nChunkSize;
|
||||
if( extra<0 ) extra = 0;
|
||||
iAmt -= extra;
|
||||
rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst % pGroup->nChunkSize);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
pBuf = (char *)pBuf + iAmt;
|
||||
iOfst += iAmt;
|
||||
iAmt = extra;
|
||||
}else{
|
||||
rc = SQLITE_IOERR_WRITE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
multiplexLeave();
|
||||
@@ -449,35 +580,44 @@ static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
multiplexGroup *pGroup = p->pGroup;
|
||||
int rc = SQLITE_OK;
|
||||
int rc2;
|
||||
int i;
|
||||
sqlite3_file *pSubOpen;
|
||||
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */
|
||||
multiplexEnter();
|
||||
memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
|
||||
/* delete the chunks above the truncate limit */
|
||||
for(i=(int)(size/gMultiplex.nChunkSize)+1; i<gMultiplex.nMaxChunks; i++){
|
||||
/* close any open chunks before deleting them */
|
||||
if( pGroup->bOpen[i] ){
|
||||
pSubOpen = pGroup->pReal[i];
|
||||
rc2 = pSubOpen->pMethods->xClose(pSubOpen);
|
||||
if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE;
|
||||
pGroup->bOpen[i] = 0;
|
||||
}
|
||||
#ifdef SQLITE_MULTIPLEX_EXT_OVWR
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#else
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#endif
|
||||
rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, 0);
|
||||
if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE;
|
||||
}
|
||||
pSubOpen = multiplexSubOpen(p, (int)(size/gMultiplex.nChunkSize), &rc2, NULL);
|
||||
if( pSubOpen ){
|
||||
rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size%gMultiplex.nChunkSize);
|
||||
if( rc2!=SQLITE_OK ) rc = rc2;
|
||||
if( !pGroup->bEnabled ){
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
rc = ( !pSubOpen ) ? SQLITE_IOERR_TRUNCATE : pSubOpen->pMethods->xTruncate(pSubOpen, size);
|
||||
}else{
|
||||
rc = SQLITE_IOERR_TRUNCATE;
|
||||
int rc2;
|
||||
int i;
|
||||
sqlite3_file *pSubOpen;
|
||||
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */
|
||||
memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
|
||||
/* delete the chunks above the truncate limit */
|
||||
for(i=(int)(size / pGroup->nChunkSize)+1; i<pGroup->nMaxChunks; i++){
|
||||
/* close any open chunks before deleting them */
|
||||
if( pGroup->bOpen[i] ){
|
||||
pSubOpen = pGroup->pReal[i];
|
||||
rc2 = pSubOpen->pMethods->xClose(pSubOpen);
|
||||
if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE;
|
||||
pGroup->bOpen[i] = 0;
|
||||
}
|
||||
#ifdef SQLITE_MULTIPLEX_EXT_OVWR
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
|
||||
gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ,
|
||||
SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#else
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
|
||||
gMultiplex.zName+pGroup->nName,
|
||||
SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#endif
|
||||
rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, 0);
|
||||
if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE;
|
||||
}
|
||||
pSubOpen = multiplexSubOpen(p, (int)(size / pGroup->nChunkSize), &rc2, NULL);
|
||||
if( pSubOpen ){
|
||||
rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size % pGroup->nChunkSize);
|
||||
if( rc2!=SQLITE_OK ) rc = rc2;
|
||||
}else{
|
||||
rc = SQLITE_IOERR_TRUNCATE;
|
||||
}
|
||||
}
|
||||
multiplexLeave();
|
||||
return rc;
|
||||
@@ -491,7 +631,7 @@ static int multiplexSync(sqlite3_file *pConn, int flags){
|
||||
int rc = SQLITE_OK;
|
||||
int i;
|
||||
multiplexEnter();
|
||||
for(i=0; i<gMultiplex.nMaxChunks; i++){
|
||||
for(i=0; i<pGroup->nMaxChunks; i++){
|
||||
/* if we don't have it open, we don't need to sync it */
|
||||
if( pGroup->bOpen[i] ){
|
||||
sqlite3_file *pSubOpen = pGroup->pReal[i];
|
||||
@@ -513,46 +653,56 @@ static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){
|
||||
int rc2;
|
||||
int i;
|
||||
multiplexEnter();
|
||||
*pSize = 0;
|
||||
for(i=0; i<gMultiplex.nMaxChunks; i++){
|
||||
sqlite3_file *pSubOpen = NULL;
|
||||
/* if not opened already, check to see if the chunk exists */
|
||||
if( pGroup->bOpen[i] ){
|
||||
pSubOpen = pGroup->pReal[i];
|
||||
}else{
|
||||
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */
|
||||
int exists = 0;
|
||||
memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
|
||||
if( i ){
|
||||
if( !pGroup->bEnabled ){
|
||||
sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
rc = ( !pSubOpen ) ? SQLITE_IOERR_FSTAT : pSubOpen->pMethods->xFileSize(pSubOpen, pSize);
|
||||
}else{
|
||||
*pSize = 0;
|
||||
for(i=0; i<pGroup->nMaxChunks; i++){
|
||||
sqlite3_file *pSubOpen = NULL;
|
||||
/* if not opened already, check to see if the chunk exists */
|
||||
if( pGroup->bOpen[i] ){
|
||||
pSubOpen = pGroup->pReal[i];
|
||||
}else{
|
||||
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */
|
||||
int exists = 0;
|
||||
memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
|
||||
if( i ){
|
||||
#ifdef SQLITE_MULTIPLEX_EXT_OVWR
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
|
||||
gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ,
|
||||
SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#else
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
|
||||
gMultiplex.zName+pGroup->nName,
|
||||
SQLITE_MULTIPLEX_EXT_FMT, i);
|
||||
#endif
|
||||
}
|
||||
rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName,
|
||||
SQLITE_ACCESS_EXISTS, &exists);
|
||||
if( rc2==SQLITE_OK && exists){
|
||||
/* if it exists, open it */
|
||||
pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
|
||||
}else{
|
||||
/* stop at first "gap" */
|
||||
break;
|
||||
}
|
||||
}
|
||||
rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, SQLITE_ACCESS_EXISTS, &exists);
|
||||
if( rc2==SQLITE_OK && exists){
|
||||
/* if it exists, open it */
|
||||
pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
sqlite3_int64 sz;
|
||||
rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
|
||||
if( rc2!=SQLITE_OK ){
|
||||
rc = rc2;
|
||||
}else{
|
||||
if( sz>pGroup->nChunkSize ){
|
||||
rc = SQLITE_IOERR_FSTAT;
|
||||
}
|
||||
*pSize += sz;
|
||||
}
|
||||
}else{
|
||||
/* stop at first "gap" */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( pSubOpen ){
|
||||
sqlite3_int64 sz;
|
||||
rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
|
||||
if( rc2!=SQLITE_OK ){
|
||||
rc = rc2;
|
||||
}else{
|
||||
if( sz>gMultiplex.nChunkSize ){
|
||||
rc = SQLITE_IOERR_FSTAT;
|
||||
}
|
||||
*pSize += sz;
|
||||
}
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
multiplexLeave();
|
||||
return rc;
|
||||
@@ -594,18 +744,62 @@ static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){
|
||||
return SQLITE_IOERR_CHECKRESERVEDLOCK;
|
||||
}
|
||||
|
||||
/* Pass xFileControl requests through to the original VFS unchanged.
|
||||
/* Pass xFileControl requests through to the original VFS unchanged,
|
||||
** except for any MULTIPLEX_CTRL_* requests here.
|
||||
*/
|
||||
static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){
|
||||
multiplexConn *p = (multiplexConn*)pConn;
|
||||
int rc;
|
||||
multiplexGroup *pGroup = p->pGroup;
|
||||
int rc = SQLITE_ERROR;
|
||||
sqlite3_file *pSubOpen;
|
||||
if ( op==SQLITE_FCNTL_SIZE_HINT || op==SQLITE_FCNTL_CHUNK_SIZE ) return SQLITE_OK;
|
||||
pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
|
||||
|
||||
if( !gMultiplex.isInitialized ) return SQLITE_MISUSE;
|
||||
switch( op ){
|
||||
case MULTIPLEX_CTRL_ENABLE:
|
||||
if( pArg ) {
|
||||
int bEnabled = *(int *)pArg;
|
||||
pGroup->bEnabled = bEnabled;
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
break;
|
||||
case MULTIPLEX_CTRL_SET_CHUNK_SIZE:
|
||||
if( pArg ) {
|
||||
int nChunkSize = *(int *)pArg;
|
||||
if( nChunkSize<1 ){
|
||||
rc = SQLITE_MISUSE;
|
||||
}else{
|
||||
/* Round up to nearest multiple of MAX_PAGE_SIZE. */
|
||||
nChunkSize = (nChunkSize + (MAX_PAGE_SIZE-1));
|
||||
nChunkSize &= ~(MAX_PAGE_SIZE-1);
|
||||
pGroup->nChunkSize = nChunkSize;
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MULTIPLEX_CTRL_SET_MAX_CHUNKS:
|
||||
if( pArg ) {
|
||||
int nMaxChunks = *(int *)pArg;
|
||||
if(( nMaxChunks<1 ) || ( nMaxChunks>SQLITE_MULTIPLEX_MAX_CHUNKS )){
|
||||
rc = SQLITE_MISUSE;
|
||||
}else{
|
||||
pGroup->nMaxChunks = nMaxChunks;
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SQLITE_FCNTL_SIZE_HINT:
|
||||
case SQLITE_FCNTL_CHUNK_SIZE:
|
||||
/* no-op these */
|
||||
rc = SQLITE_OK;
|
||||
break;
|
||||
default:
|
||||
pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
|
||||
if( pSubOpen ){
|
||||
rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return SQLITE_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Pass xSectorSize requests through to the original VFS unchanged.
|
||||
@@ -617,7 +811,7 @@ static int multiplexSectorSize(sqlite3_file *pConn){
|
||||
if( pSubOpen ){
|
||||
return pSubOpen->pMethods->xSectorSize(pSubOpen);
|
||||
}
|
||||
return SQLITE_DEFAULT_SECTOR_SIZE;
|
||||
return DEFAULT_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
/* Pass xDeviceCharacteristics requests through to the original VFS unchanged.
|
||||
@@ -692,9 +886,10 @@ static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){
|
||||
|
||||
/************************** Public Interfaces *****************************/
|
||||
/*
|
||||
** Initialize the multiplex VFS shim. Use the VFS named zOrigVfsName
|
||||
** as the VFS that does the actual work. Use the default if
|
||||
** zOrigVfsName==NULL.
|
||||
** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize()
|
||||
**
|
||||
** Use the VFS named zOrigVfsName as the VFS that does the actual work.
|
||||
** Use the default if zOrigVfsName==NULL.
|
||||
**
|
||||
** The multiplex VFS shim is named "multiplex". It will become the default
|
||||
** VFS if makeDefault is non-zero.
|
||||
@@ -717,14 +912,12 @@ int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){
|
||||
sqlite3_mutex_free(gMultiplex.pMutex);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
gMultiplex.nChunkSize = SQLITE_MULTIPLEX_CHUNK_SIZE;
|
||||
gMultiplex.nMaxChunks = SQLITE_MULTIPLEX_MAX_CHUNKS;
|
||||
gMultiplex.pGroups = NULL;
|
||||
gMultiplex.isInitialized = 1;
|
||||
gMultiplex.pOrigVfs = pOrigVfs;
|
||||
gMultiplex.sThisVfs = *pOrigVfs;
|
||||
gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn);
|
||||
gMultiplex.sThisVfs.zName = "multiplex";
|
||||
gMultiplex.sThisVfs.zName = SQLITE_MULTIPLEX_VFS_NAME;
|
||||
gMultiplex.sThisVfs.xOpen = multiplexOpen;
|
||||
gMultiplex.sThisVfs.xDelete = multiplexDelete;
|
||||
gMultiplex.sThisVfs.xAccess = multiplexAccess;
|
||||
@@ -759,11 +952,14 @@ int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){
|
||||
gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier;
|
||||
gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap;
|
||||
sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault);
|
||||
|
||||
sqlite3_auto_extension((void*)multiplexFuncInit);
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Shutdown the multiplex system.
|
||||
** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown()
|
||||
**
|
||||
** All SQLite database connections must be closed before calling this
|
||||
** routine.
|
||||
@@ -782,31 +978,9 @@ int sqlite3_multiplex_shutdown(void){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Adjust chunking params. VFS should be initialized first.
|
||||
** No files should be open. Re-intializing will reset these
|
||||
** to the default.
|
||||
*/
|
||||
int sqlite3_multiplex_set(
|
||||
int nChunkSize, /* Max chunk size */
|
||||
int nMaxChunks /* Max number of chunks */
|
||||
){
|
||||
if( !gMultiplex.isInitialized ) return SQLITE_MISUSE;
|
||||
if( gMultiplex.pGroups ) return SQLITE_MISUSE;
|
||||
if( nChunkSize<32 ) return SQLITE_MISUSE;
|
||||
if( nMaxChunks<1 ) return SQLITE_MISUSE;
|
||||
if( nMaxChunks>99 ) return SQLITE_MISUSE;
|
||||
multiplexEnter();
|
||||
gMultiplex.nChunkSize = nChunkSize;
|
||||
gMultiplex.nMaxChunks = nMaxChunks;
|
||||
multiplexLeave();
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/***************************** Test Code ***********************************/
|
||||
#ifdef SQLITE_TEST
|
||||
#include <tcl.h>
|
||||
|
||||
extern const char *sqlite3TestErrorName(int);
|
||||
|
||||
|
||||
@@ -866,36 +1040,6 @@ static int test_multiplex_shutdown(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: sqlite3_multiplex_set CHUNK_SIZE MAX_CHUNKS
|
||||
*/
|
||||
static int test_multiplex_set(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int nChunkSize; /* Max chunk size */
|
||||
int nMaxChunks; /* Max number of chunks */
|
||||
int rc; /* Value returned by sqlite3_multiplex_set() */
|
||||
|
||||
UNUSED_PARAMETER(clientData);
|
||||
|
||||
/* Process arguments */
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "CHUNK_SIZE MAX_CHUNKS");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( Tcl_GetIntFromObj(interp, objv[1], &nChunkSize) ) return TCL_ERROR;
|
||||
if( Tcl_GetIntFromObj(interp, objv[2], &nMaxChunks) ) return TCL_ERROR;
|
||||
|
||||
/* Invoke sqlite3_multiplex_set() */
|
||||
rc = sqlite3_multiplex_set(nChunkSize, nMaxChunks);
|
||||
|
||||
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: sqlite3_multiplex_dump
|
||||
*/
|
||||
@@ -929,16 +1073,16 @@ static int test_multiplex_dump(
|
||||
Tcl_NewIntObj(pGroup->flags));
|
||||
|
||||
/* count number of chunks with open handles */
|
||||
for(i=0; i<gMultiplex.nMaxChunks; i++){
|
||||
for(i=0; i<pGroup->nMaxChunks; i++){
|
||||
if( pGroup->bOpen[i] ) nChunks++;
|
||||
}
|
||||
Tcl_ListObjAppendElement(interp, pGroupTerm,
|
||||
Tcl_NewIntObj(nChunks));
|
||||
|
||||
Tcl_ListObjAppendElement(interp, pGroupTerm,
|
||||
Tcl_NewIntObj(gMultiplex.nChunkSize));
|
||||
Tcl_NewIntObj(pGroup->nChunkSize));
|
||||
Tcl_ListObjAppendElement(interp, pGroupTerm,
|
||||
Tcl_NewIntObj(gMultiplex.nMaxChunks));
|
||||
Tcl_NewIntObj(pGroup->nMaxChunks));
|
||||
|
||||
Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
|
||||
}
|
||||
@@ -947,6 +1091,68 @@ static int test_multiplex_dump(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Tclcmd: test_multiplex_control HANDLE DBNAME SUB-COMMAND ?INT-VALUE?
|
||||
*/
|
||||
static int test_multiplex_control(
|
||||
ClientData cd,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int rc; /* Return code from file_control() */
|
||||
int idx; /* Index in aSub[] */
|
||||
Tcl_CmdInfo cmdInfo; /* Command info structure for HANDLE */
|
||||
sqlite3 *db; /* Underlying db handle for HANDLE */
|
||||
int iValue = 0;
|
||||
void *pArg = 0;
|
||||
|
||||
struct SubCommand {
|
||||
const char *zName;
|
||||
int op;
|
||||
int argtype;
|
||||
} aSub[] = {
|
||||
{ "enable", MULTIPLEX_CTRL_ENABLE, 1 },
|
||||
{ "chunk_size", MULTIPLEX_CTRL_SET_CHUNK_SIZE, 1 },
|
||||
{ "max_chunks", MULTIPLEX_CTRL_SET_MAX_CHUNKS, 1 },
|
||||
{ 0, 0, 0 }
|
||||
};
|
||||
|
||||
if( objc!=5 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "HANDLE DBNAME SUB-COMMAND INT-VALUE");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){
|
||||
Tcl_AppendResult(interp, "expected database handle, got \"", 0);
|
||||
Tcl_AppendResult(interp, Tcl_GetString(objv[1]), "\"", 0);
|
||||
return TCL_ERROR;
|
||||
}else{
|
||||
db = *(sqlite3 **)cmdInfo.objClientData;
|
||||
}
|
||||
|
||||
rc = Tcl_GetIndexFromObjStruct(
|
||||
interp, objv[3], aSub, sizeof(aSub[0]), "sub-command", 0, &idx
|
||||
);
|
||||
if( rc!=TCL_OK ) return rc;
|
||||
|
||||
switch( aSub[idx].argtype ){
|
||||
case 1:
|
||||
if( Tcl_GetIntFromObj(interp, objv[4], &iValue) ){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
pArg = (void *)&iValue;
|
||||
break;
|
||||
default:
|
||||
Tcl_WrongNumArgs(interp, 4, objv, "SUB-COMMAND");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
rc = sqlite3_file_control(db, Tcl_GetString(objv[2]), aSub[idx].op, pArg);
|
||||
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
|
||||
return (rc==SQLITE_OK) ? TCL_OK : TCL_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
** This routine registers the custom TCL commands defined in this
|
||||
** module. This should be the only procedure visible from outside
|
||||
@@ -959,8 +1165,8 @@ int Sqlitemultiplex_Init(Tcl_Interp *interp){
|
||||
} aCmd[] = {
|
||||
{ "sqlite3_multiplex_initialize", test_multiplex_initialize },
|
||||
{ "sqlite3_multiplex_shutdown", test_multiplex_shutdown },
|
||||
{ "sqlite3_multiplex_set", test_multiplex_set },
|
||||
{ "sqlite3_multiplex_dump", test_multiplex_dump },
|
||||
{ "sqlite3_multiplex_control", test_multiplex_control },
|
||||
};
|
||||
int i;
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
** 2011 March 18
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
**
|
||||
** This file contains a VFS "shim" - a layer that sits in between the
|
||||
** pager and the real VFS.
|
||||
**
|
||||
** This particular shim enforces a multiplex system on DB files.
|
||||
** This shim shards/partitions a single DB file into smaller
|
||||
** "chunks" such that the total DB file size may exceed the maximum
|
||||
** file size of the underlying file system.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef _TEST_MULTIPLEX_H
|
||||
#define _TEST_MULTIPLEX_H
|
||||
|
||||
/*
|
||||
** CAPI: File-control Operations Supported by Multiplex VFS
|
||||
**
|
||||
** Values interpreted by the xFileControl method of a Multiplex VFS db file-handle.
|
||||
**
|
||||
** MULTIPLEX_CTRL_ENABLE:
|
||||
** This file control is used to enable or disable the multiplex
|
||||
** shim.
|
||||
**
|
||||
** MULTIPLEX_CTRL_SET_CHUNK_SIZE:
|
||||
** This file control is used to set the maximum allowed chunk
|
||||
** size for a multiplex file set. The chunk size should be
|
||||
** a multiple of SQLITE_MAX_PAGE_SIZE, and will be rounded up
|
||||
** if not.
|
||||
**
|
||||
** MULTIPLEX_CTRL_SET_MAX_CHUNKS:
|
||||
** This file control is used to set the maximum number of chunks
|
||||
** allowed to be used for a mutliplex file set.
|
||||
*/
|
||||
#define MULTIPLEX_CTRL_ENABLE 214014
|
||||
#define MULTIPLEX_CTRL_SET_CHUNK_SIZE 214015
|
||||
#define MULTIPLEX_CTRL_SET_MAX_CHUNKS 214016
|
||||
|
||||
/*
|
||||
** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize()
|
||||
**
|
||||
** Use the VFS named zOrigVfsName as the VFS that does the actual work.
|
||||
** Use the default if zOrigVfsName==NULL.
|
||||
**
|
||||
** The multiplex VFS shim is named "multiplex". It will become the default
|
||||
** VFS if makeDefault is non-zero.
|
||||
**
|
||||
** An auto-extension is registered which will make the function
|
||||
** multiplex_control() available to database connections. This
|
||||
** function gives access to the xFileControl interface of the
|
||||
** multiplex VFS shim.
|
||||
**
|
||||
** SELECT multiplex_control(<op>,<val>);
|
||||
**
|
||||
** <op>=1 MULTIPLEX_CTRL_ENABLE
|
||||
** <val>=0 disable
|
||||
** <val>=1 enable
|
||||
**
|
||||
** <op>=2 MULTIPLEX_CTRL_SET_CHUNK_SIZE
|
||||
** <val> int, chunk size
|
||||
**
|
||||
** <op>=3 MULTIPLEX_CTRL_SET_MAX_CHUNKS
|
||||
** <val> int, max chunks
|
||||
**
|
||||
** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once
|
||||
** during start-up.
|
||||
*/
|
||||
extern int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault);
|
||||
|
||||
/*
|
||||
** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown()
|
||||
**
|
||||
** All SQLite database connections must be closed before calling this
|
||||
** routine.
|
||||
**
|
||||
** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while
|
||||
** shutting down in order to free all remaining multiplex groups.
|
||||
*/
|
||||
extern int sqlite3_multiplex_shutdown(void);
|
||||
|
||||
#endif
|
||||
@@ -213,14 +213,14 @@ static sqlite3_io_methods vfslog_io_methods = {
|
||||
vfslogShmUnmap /* xShmUnmap */
|
||||
};
|
||||
|
||||
#if defined(SQLITE_OS_UNIX) && !defined(NO_GETTOD)
|
||||
#if SQLITE_OS_UNIX && !defined(NO_GETTOD)
|
||||
#include <sys/time.h>
|
||||
static sqlite3_uint64 vfslog_time(){
|
||||
struct timeval sTime;
|
||||
gettimeofday(&sTime, 0);
|
||||
return sTime.tv_usec + (sqlite3_uint64)sTime.tv_sec * 1000000;
|
||||
}
|
||||
#elif defined(SQLITE_OS_WIN)
|
||||
#elif SQLITE_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <time.h>
|
||||
static sqlite3_uint64 vfslog_time(){
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
** and only if the SQLITE_SERVER macro is defined.
|
||||
*/
|
||||
#if defined(SQLITE_SERVER) && !defined(SQLITE_OMIT_SHARED_CACHE)
|
||||
#if defined(SQLITE_OS_UNIX) && OS_UNIX && SQLITE_THREADSAFE
|
||||
#if SQLITE_OS_UNIX && SQLITE_THREADSAFE
|
||||
|
||||
/*
|
||||
** We require only pthreads and the public interface of SQLite.
|
||||
@@ -453,7 +453,6 @@ void *sqlite3_server(void *NotUsed){
|
||||
pthread_mutex_unlock(&pMsg->clientMutex);
|
||||
pthread_cond_signal(&pMsg->clientWakeup);
|
||||
}
|
||||
sqlite3_thread_cleanup();
|
||||
pthread_mutex_unlock(&g.serverMutex);
|
||||
return 0;
|
||||
}
|
||||
@@ -487,5 +486,5 @@ void sqlite3_server_stop(void){
|
||||
pthread_mutex_unlock(&g.serverMutex);
|
||||
}
|
||||
|
||||
#endif /* defined(SQLITE_OS_UNIX) && OS_UNIX && SQLITE_THREADSAFE */
|
||||
#endif /* SQLITE_OS_UNIX && SQLITE_THREADSAFE */
|
||||
#endif /* defined(SQLITE_SERVER) */
|
||||
|
||||
@@ -0,0 +1,674 @@
|
||||
/*
|
||||
** 2011 March 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.
|
||||
**
|
||||
*************************************************************************
|
||||
**
|
||||
** The code in this file implements a Tcl interface used to test error
|
||||
** handling in the os_unix.c module. Wrapper functions that support fault
|
||||
** injection are registered as the low-level OS functions using the
|
||||
** xSetSystemCall() method of the VFS. The Tcl interface is as follows:
|
||||
**
|
||||
**
|
||||
** test_syscall install LIST
|
||||
** Install wrapper functions for all system calls in argument LIST.
|
||||
** LIST must be a list consisting of zero or more of the following
|
||||
** literal values:
|
||||
**
|
||||
** open close access getcwd stat fstat
|
||||
** ftruncate fcntl read pread pread64 write
|
||||
** pwrite pwrite64 fchmod fallocate
|
||||
**
|
||||
** test_syscall uninstall
|
||||
** Uninstall all wrapper functions.
|
||||
**
|
||||
** test_syscall fault ?COUNT PERSIST?
|
||||
** If [test_syscall fault] is invoked without the two arguments, fault
|
||||
** injection is disabled. Otherwise, fault injection is configured to
|
||||
** cause a failure on the COUNT'th next call to a system call with a
|
||||
** wrapper function installed. A COUNT value of 1 means fail the next
|
||||
** system call.
|
||||
**
|
||||
** Argument PERSIST is interpreted as a boolean. If true, the all
|
||||
** system calls following the initial failure also fail. Otherwise, only
|
||||
** the single transient failure is injected.
|
||||
**
|
||||
** test_syscall errno CALL ERRNO
|
||||
** Set the value that the global "errno" is set to following a fault
|
||||
** in call CALL. Argument CALL must be one of the system call names
|
||||
** listed above (under [test_syscall install]). ERRNO is a symbolic
|
||||
** name (i.e. "EACCES"). Not all errno codes are supported. Add extra
|
||||
** to the aErrno table in function test_syscall_errno() below as
|
||||
** required.
|
||||
**
|
||||
** test_syscall reset ?SYSTEM-CALL?
|
||||
** With no argument, this is an alias for the [uninstall] command. However,
|
||||
** this command uses a VFS call of the form:
|
||||
**
|
||||
** xSetSystemCall(pVfs, 0, 0);
|
||||
**
|
||||
** To restore the default system calls. The [uninstall] command restores
|
||||
** each system call individually by calling (i.e.):
|
||||
**
|
||||
** xSetSystemCall(pVfs, "open", 0);
|
||||
**
|
||||
** With an argument, this command attempts to reset the system call named
|
||||
** by the parameter using the same method as [uninstall].
|
||||
**
|
||||
** test_syscall exists SYSTEM-CALL
|
||||
** Return true if the named system call exists. Or false otherwise.
|
||||
**
|
||||
** test_syscall list
|
||||
** Return a list of all system calls. The list is constructed using
|
||||
** the xNextSystemCall() VFS method.
|
||||
*/
|
||||
|
||||
#include "sqlite3.h"
|
||||
#include "tcl.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "sqliteInt.h"
|
||||
#if SQLITE_OS_UNIX
|
||||
|
||||
/* From test1.c */
|
||||
extern const char *sqlite3TestErrorName(int);
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
|
||||
static struct TestSyscallGlobal {
|
||||
int bPersist; /* 1 for persistent errors, 0 for transient */
|
||||
int nCount; /* Fail after this many more calls */
|
||||
int nFail; /* Number of failures that have occurred */
|
||||
} gSyscall = { 0, 0 };
|
||||
|
||||
static int ts_open(const char *, int, int);
|
||||
static int ts_close(int fd);
|
||||
static int ts_access(const char *zPath, int mode);
|
||||
static char *ts_getcwd(char *zPath, size_t nPath);
|
||||
static int ts_stat(const char *zPath, struct stat *p);
|
||||
static int ts_fstat(int fd, struct stat *p);
|
||||
static int ts_ftruncate(int fd, off_t n);
|
||||
static int ts_fcntl(int fd, int cmd, ... );
|
||||
static int ts_read(int fd, void *aBuf, size_t nBuf);
|
||||
static int ts_pread(int fd, void *aBuf, size_t nBuf, off_t off);
|
||||
static int ts_pread64(int fd, void *aBuf, size_t nBuf, off_t off);
|
||||
static int ts_write(int fd, const void *aBuf, size_t nBuf);
|
||||
static int ts_pwrite(int fd, const void *aBuf, size_t nBuf, off_t off);
|
||||
static int ts_pwrite64(int fd, const void *aBuf, size_t nBuf, off_t off);
|
||||
static int ts_fchmod(int fd, mode_t mode);
|
||||
static int ts_fallocate(int fd, off_t off, off_t len);
|
||||
|
||||
|
||||
struct TestSyscallArray {
|
||||
const char *zName;
|
||||
sqlite3_syscall_ptr xTest;
|
||||
sqlite3_syscall_ptr xOrig;
|
||||
int default_errno; /* Default value for errno following errors */
|
||||
int custom_errno; /* Current value for errno if error */
|
||||
} aSyscall[] = {
|
||||
/* 0 */ { "open", (sqlite3_syscall_ptr)ts_open, 0, EACCES, 0 },
|
||||
/* 1 */ { "close", (sqlite3_syscall_ptr)ts_close, 0, 0, 0 },
|
||||
/* 2 */ { "access", (sqlite3_syscall_ptr)ts_access, 0, 0, 0 },
|
||||
/* 3 */ { "getcwd", (sqlite3_syscall_ptr)ts_getcwd, 0, 0, 0 },
|
||||
/* 4 */ { "stat", (sqlite3_syscall_ptr)ts_stat, 0, 0, 0 },
|
||||
/* 5 */ { "fstat", (sqlite3_syscall_ptr)ts_fstat, 0, 0, 0 },
|
||||
/* 6 */ { "ftruncate", (sqlite3_syscall_ptr)ts_ftruncate, 0, EIO, 0 },
|
||||
/* 7 */ { "fcntl", (sqlite3_syscall_ptr)ts_fcntl, 0, EACCES, 0 },
|
||||
/* 8 */ { "read", (sqlite3_syscall_ptr)ts_read, 0, 0, 0 },
|
||||
/* 9 */ { "pread", (sqlite3_syscall_ptr)ts_pread, 0, 0, 0 },
|
||||
/* 10 */ { "pread64", (sqlite3_syscall_ptr)ts_pread64, 0, 0, 0 },
|
||||
/* 11 */ { "write", (sqlite3_syscall_ptr)ts_write, 0, 0, 0 },
|
||||
/* 12 */ { "pwrite", (sqlite3_syscall_ptr)ts_pwrite, 0, 0, 0 },
|
||||
/* 13 */ { "pwrite64", (sqlite3_syscall_ptr)ts_pwrite64, 0, 0, 0 },
|
||||
/* 14 */ { "fchmod", (sqlite3_syscall_ptr)ts_fchmod, 0, 0, 0 },
|
||||
/* 15 */ { "fallocate", (sqlite3_syscall_ptr)ts_fallocate, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
#define orig_open ((int(*)(const char *, int, int))aSyscall[0].xOrig)
|
||||
#define orig_close ((int(*)(int))aSyscall[1].xOrig)
|
||||
#define orig_access ((int(*)(const char*,int))aSyscall[2].xOrig)
|
||||
#define orig_getcwd ((char*(*)(char*,size_t))aSyscall[3].xOrig)
|
||||
#define orig_stat ((int(*)(const char*,struct stat*))aSyscall[4].xOrig)
|
||||
#define orig_fstat ((int(*)(int,struct stat*))aSyscall[5].xOrig)
|
||||
#define orig_ftruncate ((int(*)(int,off_t))aSyscall[6].xOrig)
|
||||
#define orig_fcntl ((int(*)(int,int,...))aSyscall[7].xOrig)
|
||||
#define orig_read ((ssize_t(*)(int,void*,size_t))aSyscall[8].xOrig)
|
||||
#define orig_pread ((ssize_t(*)(int,void*,size_t,off_t))aSyscall[9].xOrig)
|
||||
#define orig_pread64 ((ssize_t(*)(int,void*,size_t,off_t))aSyscall[10].xOrig)
|
||||
#define orig_write ((ssize_t(*)(int,const void*,size_t))aSyscall[11].xOrig)
|
||||
#define orig_pwrite ((ssize_t(*)(int,const void*,size_t,off_t))\
|
||||
aSyscall[12].xOrig)
|
||||
#define orig_pwrite64 ((ssize_t(*)(int,const void*,size_t,off_t))\
|
||||
aSyscall[13].xOrig)
|
||||
#define orig_fchmod ((int(*)(int,mode_t))aSyscall[14].xOrig)
|
||||
#define orig_fallocate ((int(*)(int,off_t,off_t))aSyscall[15].xOrig)
|
||||
|
||||
/*
|
||||
** This function is called exactly once from within each invocation of a
|
||||
** system call wrapper in this file. It returns 1 if the function should
|
||||
** fail, or 0 if it should succeed.
|
||||
*/
|
||||
static int tsIsFail(void){
|
||||
gSyscall.nCount--;
|
||||
if( gSyscall.nCount==0 || (gSyscall.nFail && gSyscall.bPersist) ){
|
||||
gSyscall.nFail++;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the current error-number value for function zFunc. zFunc must be
|
||||
** the name of a system call in the aSyscall[] table.
|
||||
**
|
||||
** Usually, the current error-number is the value that errno should be set
|
||||
** to if the named system call fails. The exception is "fallocate". See
|
||||
** comments above the implementation of ts_fallocate() for details.
|
||||
*/
|
||||
static int tsErrno(const char *zFunc){
|
||||
int i;
|
||||
int nFunc = strlen(zFunc);
|
||||
for(i=0; aSyscall[i].zName; i++){
|
||||
if( strlen(aSyscall[i].zName)!=nFunc ) continue;
|
||||
if( memcmp(aSyscall[i].zName, zFunc, nFunc) ) continue;
|
||||
return aSyscall[i].custom_errno;
|
||||
}
|
||||
|
||||
assert(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around tsIsFail(). If tsIsFail() returns non-zero, set the
|
||||
** value of errno before returning.
|
||||
*/
|
||||
static int tsIsFailErrno(const char *zFunc){
|
||||
if( tsIsFail() ){
|
||||
errno = tsErrno(zFunc);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around open().
|
||||
*/
|
||||
static int ts_open(const char *zFile, int flags, int mode){
|
||||
if( tsIsFailErrno("open") ){
|
||||
return -1;
|
||||
}
|
||||
return orig_open(zFile, flags, mode);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around close().
|
||||
*/
|
||||
static int ts_close(int fd){
|
||||
if( tsIsFail() ){
|
||||
/* Even if simulating an error, close the original file-descriptor.
|
||||
** This is to stop the test process from running out of file-descriptors
|
||||
** when running a long test. If a call to close() appears to fail, SQLite
|
||||
** never attempts to use the file-descriptor afterwards (or even to close
|
||||
** it a second time). */
|
||||
orig_close(fd);
|
||||
return -1;
|
||||
}
|
||||
return orig_close(fd);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around access().
|
||||
*/
|
||||
static int ts_access(const char *zPath, int mode){
|
||||
if( tsIsFail() ){
|
||||
return -1;
|
||||
}
|
||||
return orig_access(zPath, mode);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around getcwd().
|
||||
*/
|
||||
static char *ts_getcwd(char *zPath, size_t nPath){
|
||||
if( tsIsFail() ){
|
||||
return NULL;
|
||||
}
|
||||
return orig_getcwd(zPath, nPath);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around stat().
|
||||
*/
|
||||
static int ts_stat(const char *zPath, struct stat *p){
|
||||
if( tsIsFail() ){
|
||||
return -1;
|
||||
}
|
||||
return orig_stat(zPath, p);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around fstat().
|
||||
*/
|
||||
static int ts_fstat(int fd, struct stat *p){
|
||||
if( tsIsFailErrno("fstat") ){
|
||||
return -1;
|
||||
}
|
||||
return orig_fstat(fd, p);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around ftruncate().
|
||||
*/
|
||||
static int ts_ftruncate(int fd, off_t n){
|
||||
if( tsIsFailErrno("ftruncate") ){
|
||||
return -1;
|
||||
}
|
||||
return orig_ftruncate(fd, n);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around fcntl().
|
||||
*/
|
||||
static int ts_fcntl(int fd, int cmd, ... ){
|
||||
va_list ap;
|
||||
void *pArg;
|
||||
if( tsIsFailErrno("fcntl") ){
|
||||
return -1;
|
||||
}
|
||||
va_start(ap, cmd);
|
||||
pArg = va_arg(ap, void *);
|
||||
return orig_fcntl(fd, cmd, pArg);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around read().
|
||||
*/
|
||||
static int ts_read(int fd, void *aBuf, size_t nBuf){
|
||||
if( tsIsFailErrno("read") ){
|
||||
return -1;
|
||||
}
|
||||
return orig_read(fd, aBuf, nBuf);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around pread().
|
||||
*/
|
||||
static int ts_pread(int fd, void *aBuf, size_t nBuf, off_t off){
|
||||
if( tsIsFailErrno("pread") ){
|
||||
return -1;
|
||||
}
|
||||
return orig_pread(fd, aBuf, nBuf, off);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around pread64().
|
||||
*/
|
||||
static int ts_pread64(int fd, void *aBuf, size_t nBuf, off_t off){
|
||||
if( tsIsFailErrno("pread64") ){
|
||||
return -1;
|
||||
}
|
||||
return orig_pread64(fd, aBuf, nBuf, off);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around write().
|
||||
*/
|
||||
static int ts_write(int fd, const void *aBuf, size_t nBuf){
|
||||
if( tsIsFailErrno("write") ){
|
||||
return -1;
|
||||
}
|
||||
return orig_write(fd, aBuf, nBuf);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around pwrite().
|
||||
*/
|
||||
static int ts_pwrite(int fd, const void *aBuf, size_t nBuf, off_t off){
|
||||
if( tsIsFailErrno("pwrite") ){
|
||||
return -1;
|
||||
}
|
||||
return orig_pwrite(fd, aBuf, nBuf, off);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around pwrite64().
|
||||
*/
|
||||
static int ts_pwrite64(int fd, const void *aBuf, size_t nBuf, off_t off){
|
||||
if( tsIsFailErrno("pwrite64") ){
|
||||
return -1;
|
||||
}
|
||||
return orig_pwrite64(fd, aBuf, nBuf, off);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around fchmod().
|
||||
*/
|
||||
static int ts_fchmod(int fd, mode_t mode){
|
||||
if( tsIsFail() ){
|
||||
return -1;
|
||||
}
|
||||
return orig_fchmod(fd, mode);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around fallocate().
|
||||
**
|
||||
** SQLite assumes that the fallocate() function is compatible with
|
||||
** posix_fallocate(). According to the Linux man page (2009-09-30):
|
||||
**
|
||||
** posix_fallocate() returns zero on success, or an error number on
|
||||
** failure. Note that errno is not set.
|
||||
*/
|
||||
static int ts_fallocate(int fd, off_t off, off_t len){
|
||||
if( tsIsFail() ){
|
||||
return tsErrno("fallocate");
|
||||
}
|
||||
return orig_fallocate(fd, off, len);
|
||||
}
|
||||
|
||||
static int test_syscall_install(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3_vfs *pVfs;
|
||||
int nElem;
|
||||
int i;
|
||||
Tcl_Obj **apElem;
|
||||
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "SYSCALL-LIST");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( Tcl_ListObjGetElements(interp, objv[2], &nElem, &apElem) ){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
pVfs = sqlite3_vfs_find(0);
|
||||
|
||||
for(i=0; i<nElem; i++){
|
||||
int iCall;
|
||||
int rc = Tcl_GetIndexFromObjStruct(interp,
|
||||
apElem[i], aSyscall, sizeof(aSyscall[0]), "system-call", 0, &iCall
|
||||
);
|
||||
if( rc ) return rc;
|
||||
if( aSyscall[iCall].xOrig==0 ){
|
||||
aSyscall[iCall].xOrig = pVfs->xGetSystemCall(pVfs, aSyscall[iCall].zName);
|
||||
pVfs->xSetSystemCall(pVfs, aSyscall[iCall].zName, aSyscall[iCall].xTest);
|
||||
}
|
||||
aSyscall[iCall].custom_errno = aSyscall[iCall].default_errno;
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int test_syscall_uninstall(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3_vfs *pVfs;
|
||||
int i;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
pVfs = sqlite3_vfs_find(0);
|
||||
for(i=0; aSyscall[i].zName; i++){
|
||||
if( aSyscall[i].xOrig ){
|
||||
pVfs->xSetSystemCall(pVfs, aSyscall[i].zName, 0);
|
||||
aSyscall[i].xOrig = 0;
|
||||
}
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int test_syscall_reset(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3_vfs *pVfs;
|
||||
int i;
|
||||
int rc;
|
||||
|
||||
if( objc!=2 && objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
pVfs = sqlite3_vfs_find(0);
|
||||
if( objc==2 ){
|
||||
rc = pVfs->xSetSystemCall(pVfs, 0, 0);
|
||||
for(i=0; aSyscall[i].zName; i++) aSyscall[i].xOrig = 0;
|
||||
}else{
|
||||
int nFunc;
|
||||
char *zFunc = Tcl_GetStringFromObj(objv[2], &nFunc);
|
||||
rc = pVfs->xSetSystemCall(pVfs, Tcl_GetString(objv[2]), 0);
|
||||
for(i=0; rc==SQLITE_OK && aSyscall[i].zName; i++){
|
||||
if( strlen(aSyscall[i].zName)!=nFunc ) continue;
|
||||
if( memcmp(aSyscall[i].zName, zFunc, nFunc) ) continue;
|
||||
aSyscall[i].xOrig = 0;
|
||||
}
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3TestErrorName(rc), -1));
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
Tcl_ResetResult(interp);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int test_syscall_exists(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3_vfs *pVfs;
|
||||
sqlite3_syscall_ptr x;
|
||||
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
pVfs = sqlite3_vfs_find(0);
|
||||
x = pVfs->xGetSystemCall(pVfs, Tcl_GetString(objv[2]));
|
||||
|
||||
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(x!=0));
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int test_syscall_fault(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int nCount = 0;
|
||||
int bPersist = 0;
|
||||
|
||||
if( objc!=2 && objc!=4 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "?COUNT PERSIST?");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( objc==4 ){
|
||||
if( Tcl_GetIntFromObj(interp, objv[2], &nCount)
|
||||
|| Tcl_GetBooleanFromObj(interp, objv[3], &bPersist)
|
||||
){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
Tcl_SetObjResult(interp, Tcl_NewIntObj(gSyscall.nFail));
|
||||
gSyscall.nCount = nCount;
|
||||
gSyscall.bPersist = bPersist;
|
||||
gSyscall.nFail = 0;
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int test_syscall_errno(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int iCall;
|
||||
int iErrno;
|
||||
int rc;
|
||||
|
||||
struct Errno {
|
||||
const char *z;
|
||||
int i;
|
||||
} aErrno[] = {
|
||||
{ "EACCES", EACCES },
|
||||
{ "EINTR", EINTR },
|
||||
{ "EIO", EIO },
|
||||
{ "EOVERFLOW", EOVERFLOW },
|
||||
{ "ENOMEM", ENOMEM },
|
||||
{ "EAGAIN", EAGAIN },
|
||||
{ "ETIMEDOUT", ETIMEDOUT },
|
||||
{ "EBUSY", EBUSY },
|
||||
{ "EPERM", EPERM },
|
||||
{ "EDEADLK", EDEADLK },
|
||||
{ "ENOLCK", ENOLCK },
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
if( objc!=4 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "SYSCALL ERRNO");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
rc = Tcl_GetIndexFromObjStruct(interp,
|
||||
objv[2], aSyscall, sizeof(aSyscall[0]), "system-call", 0, &iCall
|
||||
);
|
||||
if( rc!=TCL_OK ) return rc;
|
||||
rc = Tcl_GetIndexFromObjStruct(interp,
|
||||
objv[3], aErrno, sizeof(aErrno[0]), "errno", 0, &iErrno
|
||||
);
|
||||
if( rc!=TCL_OK ) return rc;
|
||||
|
||||
aSyscall[iCall].custom_errno = aErrno[iErrno].i;
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int test_syscall_list(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
const char *zSys;
|
||||
sqlite3_vfs *pVfs;
|
||||
Tcl_Obj *pList;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
pVfs = sqlite3_vfs_find(0);
|
||||
pList = Tcl_NewObj();
|
||||
Tcl_IncrRefCount(pList);
|
||||
for(zSys = pVfs->xNextSystemCall(pVfs, 0);
|
||||
zSys!=0;
|
||||
zSys = pVfs->xNextSystemCall(pVfs, zSys)
|
||||
){
|
||||
Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj(zSys, -1));
|
||||
}
|
||||
|
||||
Tcl_SetObjResult(interp, pList);
|
||||
Tcl_DecrRefCount(pList);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int test_syscall_defaultvfs(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3_vfs *pVfs;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
pVfs = sqlite3_vfs_find(0);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(pVfs->zName, -1));
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int test_syscall(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
struct SyscallCmd {
|
||||
const char *zName;
|
||||
Tcl_ObjCmdProc *xCmd;
|
||||
} aCmd[] = {
|
||||
{ "fault", test_syscall_fault },
|
||||
{ "install", test_syscall_install },
|
||||
{ "uninstall", test_syscall_uninstall },
|
||||
{ "reset", test_syscall_reset },
|
||||
{ "errno", test_syscall_errno },
|
||||
{ "exists", test_syscall_exists },
|
||||
{ "list", test_syscall_list },
|
||||
{ "defaultvfs", test_syscall_defaultvfs },
|
||||
{ 0, 0 }
|
||||
};
|
||||
int iCmd;
|
||||
int rc;
|
||||
|
||||
if( objc<2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ...");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
rc = Tcl_GetIndexFromObjStruct(interp,
|
||||
objv[1], aCmd, sizeof(aCmd[0]), "sub-command", 0, &iCmd
|
||||
);
|
||||
if( rc!=TCL_OK ) return rc;
|
||||
return aCmd[iCmd].xCmd(clientData, interp, objc, objv);
|
||||
}
|
||||
|
||||
int SqlitetestSyscall_Init(Tcl_Interp *interp){
|
||||
struct SyscallCmd {
|
||||
const char *zName;
|
||||
Tcl_ObjCmdProc *xCmd;
|
||||
} aCmd[] = {
|
||||
{ "test_syscall", test_syscall},
|
||||
};
|
||||
int i;
|
||||
|
||||
for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
|
||||
Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xCmd, 0, 0);
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
#else
|
||||
int SqlitetestSyscall_Init(Tcl_Interp *interp){
|
||||
return TCL_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -53,7 +53,7 @@ struct EvalEvent {
|
||||
|
||||
static Tcl_ObjCmdProc sqlthread_proc;
|
||||
static Tcl_ObjCmdProc clock_seconds_proc;
|
||||
#if defined(SQLITE_OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
|
||||
#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
|
||||
static Tcl_ObjCmdProc blocking_step_proc;
|
||||
static Tcl_ObjCmdProc blocking_prepare_v2_proc;
|
||||
#endif
|
||||
@@ -116,7 +116,7 @@ static Tcl_ThreadCreateType tclScriptThread(ClientData pSqlThread){
|
||||
interp = Tcl_CreateInterp();
|
||||
Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0);
|
||||
Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, pSqlThread, 0);
|
||||
#if defined(SQLITE_OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
|
||||
#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
|
||||
Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0);
|
||||
Tcl_CreateObjCommand(interp,
|
||||
"sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0);
|
||||
@@ -392,7 +392,7 @@ static int clock_seconds_proc(
|
||||
** should be considered if these functions are to be extended (i.e. to
|
||||
** support windows) in the future.
|
||||
*/
|
||||
#if defined(SQLITE_OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
|
||||
#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
|
||||
|
||||
/* BEGIN_SQLITE_BLOCKING_STEP */
|
||||
/* This example uses the pthreads API */
|
||||
@@ -614,7 +614,7 @@ static int blocking_prepare_v2_proc(
|
||||
int SqlitetestThread_Init(Tcl_Interp *interp){
|
||||
Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, 0, 0);
|
||||
Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0);
|
||||
#if defined(SQLITE_OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
|
||||
#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
|
||||
Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0);
|
||||
Tcl_CreateObjCommand(interp,
|
||||
"sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0);
|
||||
|
||||
@@ -0,0 +1,773 @@
|
||||
/*
|
||||
** 2011 March 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.
|
||||
**
|
||||
******************************************************************************
|
||||
**
|
||||
** This file contains code implements a VFS shim that writes diagnostic
|
||||
** output for each VFS call, similar to "strace".
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "sqlite3.h"
|
||||
|
||||
/*
|
||||
** An instance of this structure is attached to the each trace VFS to
|
||||
** provide auxiliary information.
|
||||
*/
|
||||
typedef struct vfstrace_info vfstrace_info;
|
||||
struct vfstrace_info {
|
||||
sqlite3_vfs *pRootVfs; /* The underlying real VFS */
|
||||
int (*xOut)(const char*, void*); /* Send output here */
|
||||
void *pOutArg; /* First argument to xOut */
|
||||
const char *zVfsName; /* Name of this trace-VFS */
|
||||
sqlite3_vfs *pTraceVfs; /* Pointer back to the trace VFS */
|
||||
};
|
||||
|
||||
/*
|
||||
** The sqlite3_file object for the trace VFS
|
||||
*/
|
||||
typedef struct vfstrace_file vfstrace_file;
|
||||
struct vfstrace_file {
|
||||
sqlite3_file base; /* Base class. Must be first */
|
||||
vfstrace_info *pInfo; /* The trace-VFS to which this file belongs */
|
||||
const char *zFName; /* Base name of the file */
|
||||
sqlite3_file *pReal; /* The real underlying file */
|
||||
};
|
||||
|
||||
/*
|
||||
** Method declarations for vfstrace_file.
|
||||
*/
|
||||
static int vfstraceClose(sqlite3_file*);
|
||||
static int vfstraceRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
|
||||
static int vfstraceWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64);
|
||||
static int vfstraceTruncate(sqlite3_file*, sqlite3_int64 size);
|
||||
static int vfstraceSync(sqlite3_file*, int flags);
|
||||
static int vfstraceFileSize(sqlite3_file*, sqlite3_int64 *pSize);
|
||||
static int vfstraceLock(sqlite3_file*, int);
|
||||
static int vfstraceUnlock(sqlite3_file*, int);
|
||||
static int vfstraceCheckReservedLock(sqlite3_file*, int *);
|
||||
static int vfstraceFileControl(sqlite3_file*, int op, void *pArg);
|
||||
static int vfstraceSectorSize(sqlite3_file*);
|
||||
static int vfstraceDeviceCharacteristics(sqlite3_file*);
|
||||
static int vfstraceShmLock(sqlite3_file*,int,int,int);
|
||||
static int vfstraceShmMap(sqlite3_file*,int,int,int, void volatile **);
|
||||
static void vfstraceShmBarrier(sqlite3_file*);
|
||||
static int vfstraceShmUnmap(sqlite3_file*,int);
|
||||
|
||||
/*
|
||||
** Method declarations for vfstrace_vfs.
|
||||
*/
|
||||
static int vfstraceOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
|
||||
static int vfstraceDelete(sqlite3_vfs*, const char *zName, int syncDir);
|
||||
static int vfstraceAccess(sqlite3_vfs*, const char *zName, int flags, int *);
|
||||
static int vfstraceFullPathname(sqlite3_vfs*, const char *zName, int, char *);
|
||||
static void *vfstraceDlOpen(sqlite3_vfs*, const char *zFilename);
|
||||
static void vfstraceDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
|
||||
static void (*vfstraceDlSym(sqlite3_vfs*,void*, const char *zSymbol))(void);
|
||||
static void vfstraceDlClose(sqlite3_vfs*, void*);
|
||||
static int vfstraceRandomness(sqlite3_vfs*, int nByte, char *zOut);
|
||||
static int vfstraceSleep(sqlite3_vfs*, int microseconds);
|
||||
static int vfstraceCurrentTime(sqlite3_vfs*, double*);
|
||||
static int vfstraceGetLastError(sqlite3_vfs*, int, char*);
|
||||
static int vfstraceCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
|
||||
static int vfstraceSetSystemCall(sqlite3_vfs*,const char*, sqlite3_syscall_ptr);
|
||||
static sqlite3_syscall_ptr vfstraceGetSystemCall(sqlite3_vfs*, const char *);
|
||||
static const char *vfstraceNextSystemCall(sqlite3_vfs*, const char *zName);
|
||||
|
||||
/*
|
||||
** Return a pointer to the tail of the pathname. Examples:
|
||||
**
|
||||
** /home/drh/xyzzy.txt -> xyzzy.txt
|
||||
** xyzzy.txt -> xyzzy.txt
|
||||
*/
|
||||
static const char *fileTail(const char *z){
|
||||
int i;
|
||||
if( z==0 ) return 0;
|
||||
i = strlen(z)-1;
|
||||
while( i>0 && z[i-1]!='/' ){ i--; }
|
||||
return &z[i];
|
||||
}
|
||||
|
||||
/*
|
||||
** Send trace output defined by zFormat and subsequent arguments.
|
||||
*/
|
||||
static void vfstrace_printf(
|
||||
vfstrace_info *pInfo,
|
||||
const char *zFormat,
|
||||
...
|
||||
){
|
||||
va_list ap;
|
||||
char *zMsg;
|
||||
va_start(ap, zFormat);
|
||||
zMsg = sqlite3_vmprintf(zFormat, ap);
|
||||
va_end(ap);
|
||||
pInfo->xOut(zMsg, pInfo->pOutArg);
|
||||
sqlite3_free(zMsg);
|
||||
}
|
||||
|
||||
/*
|
||||
** Convert value rc into a string and print it using zFormat. zFormat
|
||||
** should have exactly one %s
|
||||
*/
|
||||
static void vfstrace_print_errcode(
|
||||
vfstrace_info *pInfo,
|
||||
const char *zFormat,
|
||||
int rc
|
||||
){
|
||||
char zBuf[50];
|
||||
char *zVal;
|
||||
switch( rc ){
|
||||
case SQLITE_OK: zVal = "SQLITE_OK"; break;
|
||||
case SQLITE_ERROR: zVal = "SQLITE_ERROR"; break;
|
||||
case SQLITE_PERM: zVal = "SQLITE_PERM"; break;
|
||||
case SQLITE_ABORT: zVal = "SQLITE_ABORT"; break;
|
||||
case SQLITE_BUSY: zVal = "SQLITE_BUSY"; break;
|
||||
case SQLITE_NOMEM: zVal = "SQLITE_NOMEM"; break;
|
||||
case SQLITE_READONLY: zVal = "SQLITE_READONLY"; break;
|
||||
case SQLITE_INTERRUPT: zVal = "SQLITE_INTERRUPT"; break;
|
||||
case SQLITE_IOERR: zVal = "SQLITE_IOERR"; break;
|
||||
case SQLITE_CORRUPT: zVal = "SQLITE_CORRUPT"; break;
|
||||
case SQLITE_FULL: zVal = "SQLITE_FULL"; break;
|
||||
case SQLITE_CANTOPEN: zVal = "SQLITE_CANTOPEN"; break;
|
||||
case SQLITE_PROTOCOL: zVal = "SQLITE_PROTOCOL"; break;
|
||||
case SQLITE_EMPTY: zVal = "SQLITE_EMPTY"; break;
|
||||
case SQLITE_SCHEMA: zVal = "SQLITE_SCHEMA"; break;
|
||||
case SQLITE_CONSTRAINT: zVal = "SQLITE_CONSTRAINT"; break;
|
||||
case SQLITE_MISMATCH: zVal = "SQLITE_MISMATCH"; break;
|
||||
case SQLITE_MISUSE: zVal = "SQLITE_MISUSE"; break;
|
||||
case SQLITE_NOLFS: zVal = "SQLITE_NOLFS"; break;
|
||||
case SQLITE_IOERR_READ: zVal = "SQLITE_IOERR_READ"; break;
|
||||
case SQLITE_IOERR_SHORT_READ: zVal = "SQLITE_IOERR_SHORT_READ"; break;
|
||||
case SQLITE_IOERR_WRITE: zVal = "SQLITE_IOERR_WRITE"; break;
|
||||
case SQLITE_IOERR_FSYNC: zVal = "SQLITE_IOERR_FSYNC"; break;
|
||||
case SQLITE_IOERR_DIR_FSYNC: zVal = "SQLITE_IOERR_DIR_FSYNC"; break;
|
||||
case SQLITE_IOERR_TRUNCATE: zVal = "SQLITE_IOERR_TRUNCATE"; break;
|
||||
case SQLITE_IOERR_FSTAT: zVal = "SQLITE_IOERR_FSTAT"; break;
|
||||
case SQLITE_IOERR_UNLOCK: zVal = "SQLITE_IOERR_UNLOCK"; break;
|
||||
case SQLITE_IOERR_RDLOCK: zVal = "SQLITE_IOERR_RDLOCK"; break;
|
||||
case SQLITE_IOERR_DELETE: zVal = "SQLITE_IOERR_DELETE"; break;
|
||||
case SQLITE_IOERR_BLOCKED: zVal = "SQLITE_IOERR_BLOCKED"; break;
|
||||
case SQLITE_IOERR_NOMEM: zVal = "SQLITE_IOERR_NOMEM"; break;
|
||||
case SQLITE_IOERR_ACCESS: zVal = "SQLITE_IOERR_ACCESS"; break;
|
||||
case SQLITE_IOERR_CHECKRESERVEDLOCK:
|
||||
zVal = "SQLITE_IOERR_CHECKRESERVEDLOCK"; break;
|
||||
case SQLITE_IOERR_LOCK: zVal = "SQLITE_IOERR_LOCK"; break;
|
||||
case SQLITE_IOERR_CLOSE: zVal = "SQLITE_IOERR_CLOSE"; break;
|
||||
case SQLITE_IOERR_DIR_CLOSE: zVal = "SQLITE_IOERR_DIR_CLOSE"; break;
|
||||
case SQLITE_IOERR_SHMOPEN: zVal = "SQLITE_IOERR_SHMOPEN"; break;
|
||||
case SQLITE_IOERR_SHMSIZE: zVal = "SQLITE_IOERR_SHMSIZE"; break;
|
||||
case SQLITE_IOERR_SHMLOCK: zVal = "SQLITE_IOERR_SHMLOCK"; break;
|
||||
case SQLITE_LOCKED_SHAREDCACHE: zVal = "SQLITE_LOCKED_SHAREDCACHE"; break;
|
||||
case SQLITE_BUSY_RECOVERY: zVal = "SQLITE_BUSY_RECOVERY"; break;
|
||||
case SQLITE_CANTOPEN_NOTEMPDIR: zVal = "SQLITE_CANTOPEN_NOTEMPDIR"; break;
|
||||
default: {
|
||||
sqlite3_snprintf(sizeof(zBuf), zBuf, "%d", rc);
|
||||
zVal = zBuf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
vfstrace_printf(pInfo, zFormat, zVal);
|
||||
}
|
||||
|
||||
/*
|
||||
** Append to a buffer.
|
||||
*/
|
||||
static void strappend(char *z, int *pI, const char *zAppend){
|
||||
int i = *pI;
|
||||
while( zAppend[0] ){ z[i++] = *(zAppend++); }
|
||||
z[i] = 0;
|
||||
*pI = i;
|
||||
}
|
||||
|
||||
/*
|
||||
** Close an vfstrace-file.
|
||||
*/
|
||||
static int vfstraceClose(sqlite3_file *pFile){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xClose(%s)", pInfo->zVfsName, p->zFName);
|
||||
rc = p->pReal->pMethods->xClose(p->pReal);
|
||||
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_free((void*)p->base.pMethods);
|
||||
p->base.pMethods = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read data from an vfstrace-file.
|
||||
*/
|
||||
static int vfstraceRead(
|
||||
sqlite3_file *pFile,
|
||||
void *zBuf,
|
||||
int iAmt,
|
||||
sqlite_int64 iOfst
|
||||
){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xRead(%s,n=%d,ofst=%lld)",
|
||||
pInfo->zVfsName, p->zFName, iAmt, iOfst);
|
||||
rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
|
||||
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write data to an vfstrace-file.
|
||||
*/
|
||||
static int vfstraceWrite(
|
||||
sqlite3_file *pFile,
|
||||
const void *zBuf,
|
||||
int iAmt,
|
||||
sqlite_int64 iOfst
|
||||
){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xWrite(%s,n=%d,ofst=%lld)",
|
||||
pInfo->zVfsName, p->zFName, iAmt, iOfst);
|
||||
rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);
|
||||
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Truncate an vfstrace-file.
|
||||
*/
|
||||
static int vfstraceTruncate(sqlite3_file *pFile, sqlite_int64 size){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xTruncate(%s,%lld)", pInfo->zVfsName, p->zFName,
|
||||
size);
|
||||
rc = p->pReal->pMethods->xTruncate(p->pReal, size);
|
||||
vfstrace_printf(pInfo, " -> %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Sync an vfstrace-file.
|
||||
*/
|
||||
static int vfstraceSync(sqlite3_file *pFile, int flags){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
int i;
|
||||
char zBuf[100];
|
||||
memcpy(zBuf, "|0", 3);
|
||||
i = 0;
|
||||
if( flags & SQLITE_SYNC_FULL ) strappend(zBuf, &i, "|FULL");
|
||||
else if( flags & SQLITE_SYNC_NORMAL ) strappend(zBuf, &i, "|NORMAL");
|
||||
if( flags & SQLITE_SYNC_DATAONLY ) strappend(zBuf, &i, "|DATAONLY");
|
||||
if( flags & ~(SQLITE_SYNC_FULL|SQLITE_SYNC_DATAONLY) ){
|
||||
sqlite3_snprintf(sizeof(zBuf)-i, &zBuf[i], "|0x%x", flags);
|
||||
}
|
||||
vfstrace_printf(pInfo, "%s.xSync(%s,%s)", pInfo->zVfsName, p->zFName,
|
||||
&zBuf[1]);
|
||||
rc = p->pReal->pMethods->xSync(p->pReal, flags);
|
||||
vfstrace_printf(pInfo, " -> %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the current file-size of an vfstrace-file.
|
||||
*/
|
||||
static int vfstraceFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xFileSize(%s)", pInfo->zVfsName, p->zFName);
|
||||
rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
|
||||
vfstrace_print_errcode(pInfo, " -> %s,", rc);
|
||||
vfstrace_printf(pInfo, " size=%lld\n", *pSize);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the name of a lock.
|
||||
*/
|
||||
static const char *lockName(int eLock){
|
||||
const char *azLockNames[] = {
|
||||
"NONE", "SHARED", "RESERVED", "PENDING", "EXCLUSIVE"
|
||||
};
|
||||
if( eLock<0 || eLock>=sizeof(azLockNames)/sizeof(azLockNames[0]) ){
|
||||
return "???";
|
||||
}else{
|
||||
return azLockNames[eLock];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Lock an vfstrace-file.
|
||||
*/
|
||||
static int vfstraceLock(sqlite3_file *pFile, int eLock){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xLock(%s,%s)", pInfo->zVfsName, p->zFName,
|
||||
lockName(eLock));
|
||||
rc = p->pReal->pMethods->xLock(p->pReal, eLock);
|
||||
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Unlock an vfstrace-file.
|
||||
*/
|
||||
static int vfstraceUnlock(sqlite3_file *pFile, int eLock){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xUnlock(%s,%s)", pInfo->zVfsName, p->zFName,
|
||||
lockName(eLock));
|
||||
rc = p->pReal->pMethods->xUnlock(p->pReal, eLock);
|
||||
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Check if another file-handle holds a RESERVED lock on an vfstrace-file.
|
||||
*/
|
||||
static int vfstraceCheckReservedLock(sqlite3_file *pFile, int *pResOut){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xCheckReservedLock(%s,%d)",
|
||||
pInfo->zVfsName, p->zFName);
|
||||
rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut);
|
||||
vfstrace_print_errcode(pInfo, " -> %s", rc);
|
||||
vfstrace_printf(pInfo, ", out=%d\n", *pResOut);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** File control method. For custom operations on an vfstrace-file.
|
||||
*/
|
||||
static int vfstraceFileControl(sqlite3_file *pFile, int op, void *pArg){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
char zBuf[100];
|
||||
char *zOp;
|
||||
switch( op ){
|
||||
case SQLITE_FCNTL_LOCKSTATE: zOp = "LOCKSTATE"; break;
|
||||
case SQLITE_GET_LOCKPROXYFILE: zOp = "GET_LOCKPROXYFILE"; break;
|
||||
case SQLITE_SET_LOCKPROXYFILE: zOp = "SET_LOCKPROXYFILE"; break;
|
||||
case SQLITE_LAST_ERRNO: zOp = "LAST_ERRNO"; break;
|
||||
case SQLITE_FCNTL_SIZE_HINT: {
|
||||
sqlite3_snprintf(sizeof(zBuf), zBuf, "SIZE_HINT,%lld",
|
||||
*(sqlite3_int64*)pArg);
|
||||
zOp = zBuf;
|
||||
break;
|
||||
}
|
||||
case SQLITE_FCNTL_CHUNK_SIZE: {
|
||||
sqlite3_snprintf(sizeof(zBuf), zBuf, "CHUNK_SIZE,%d", *(int*)pArg);
|
||||
zOp = zBuf;
|
||||
break;
|
||||
}
|
||||
case SQLITE_FCNTL_FILE_POINTER: zOp = "FILE_POINTER"; break;
|
||||
case SQLITE_FCNTL_SYNC_OMITTED: zOp = "SYNC_OMITTED"; break;
|
||||
case 0xca093fa0: zOp = "DB_UNCHANGED"; break;
|
||||
default: {
|
||||
sqlite3_snprintf(sizeof zBuf, zBuf, "%d", op);
|
||||
zOp = zBuf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
vfstrace_printf(pInfo, "%s.xFileControl(%s,%s)",
|
||||
pInfo->zVfsName, p->zFName, zOp);
|
||||
rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg);
|
||||
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the sector-size in bytes for an vfstrace-file.
|
||||
*/
|
||||
static int vfstraceSectorSize(sqlite3_file *pFile){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xSectorSize(%s)", pInfo->zVfsName, p->zFName);
|
||||
rc = p->pReal->pMethods->xSectorSize(p->pReal);
|
||||
vfstrace_printf(pInfo, " -> %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the device characteristic flags supported by an vfstrace-file.
|
||||
*/
|
||||
static int vfstraceDeviceCharacteristics(sqlite3_file *pFile){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xDeviceCharacteristics(%s)",
|
||||
pInfo->zVfsName, p->zFName);
|
||||
rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal);
|
||||
vfstrace_printf(pInfo, " -> 0x%08x\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Shared-memory operations.
|
||||
*/
|
||||
static int vfstraceShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
char zLck[100];
|
||||
int i = 0;
|
||||
memcpy(zLck, "|0", 3);
|
||||
if( flags & SQLITE_SHM_UNLOCK ) strappend(zLck, &i, "|UNLOCK");
|
||||
if( flags & SQLITE_SHM_LOCK ) strappend(zLck, &i, "|LOCK");
|
||||
if( flags & SQLITE_SHM_SHARED ) strappend(zLck, &i, "|SHARED");
|
||||
if( flags & SQLITE_SHM_EXCLUSIVE ) strappend(zLck, &i, "|EXCLUSIVE");
|
||||
if( flags & ~(0xf) ){
|
||||
sqlite3_snprintf(sizeof(zLck)-i, &zLck[i], "|0x%x", flags);
|
||||
}
|
||||
vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=%d,n=%d,%s)",
|
||||
pInfo->zVfsName, p->zFName, ofst, n, &zLck[1]);
|
||||
rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags);
|
||||
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
|
||||
return rc;
|
||||
}
|
||||
static int vfstraceShmMap(
|
||||
sqlite3_file *pFile,
|
||||
int iRegion,
|
||||
int szRegion,
|
||||
int isWrite,
|
||||
void volatile **pp
|
||||
){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xShmMap(%s,iRegion=%d,szRegion=%d,isWrite=%d,*)",
|
||||
pInfo->zVfsName, p->zFName, iRegion, szRegion, isWrite);
|
||||
rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp);
|
||||
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
|
||||
return rc;
|
||||
}
|
||||
static void vfstraceShmBarrier(sqlite3_file *pFile){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
vfstrace_printf(pInfo, "%s.xShmBarrier(%s)\n", pInfo->zVfsName, p->zFName);
|
||||
p->pReal->pMethods->xShmBarrier(p->pReal);
|
||||
}
|
||||
static int vfstraceShmUnmap(sqlite3_file *pFile, int delFlag){
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = p->pInfo;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xShmUnmap(%s,delFlag=%d)",
|
||||
pInfo->zVfsName, p->zFName, delFlag);
|
||||
rc = p->pReal->pMethods->xShmUnmap(p->pReal, delFlag);
|
||||
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** Open an vfstrace file handle.
|
||||
*/
|
||||
static int vfstraceOpen(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zName,
|
||||
sqlite3_file *pFile,
|
||||
int flags,
|
||||
int *pOutFlags
|
||||
){
|
||||
int rc;
|
||||
vfstrace_file *p = (vfstrace_file *)pFile;
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
p->pInfo = pInfo;
|
||||
p->zFName = zName ? fileTail(zName) : "<temp>";
|
||||
p->pReal = (sqlite3_file *)&p[1];
|
||||
rc = pRoot->xOpen(pRoot, zName, p->pReal, flags, pOutFlags);
|
||||
vfstrace_printf(pInfo, "%s.xOpen(%s,flags=0x%x)",
|
||||
pInfo->zVfsName, p->zFName, flags);
|
||||
if( p->pReal->pMethods ){
|
||||
sqlite3_io_methods *pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
const sqlite3_io_methods *pSub = p->pReal->pMethods;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
pNew->iVersion = pSub->iVersion;
|
||||
pNew->xClose = vfstraceClose;
|
||||
pNew->xRead = vfstraceRead;
|
||||
pNew->xWrite = vfstraceWrite;
|
||||
pNew->xTruncate = vfstraceTruncate;
|
||||
pNew->xSync = vfstraceSync;
|
||||
pNew->xFileSize = vfstraceFileSize;
|
||||
pNew->xLock = vfstraceLock;
|
||||
pNew->xUnlock = vfstraceUnlock;
|
||||
pNew->xCheckReservedLock = vfstraceCheckReservedLock;
|
||||
pNew->xFileControl = vfstraceFileControl;
|
||||
pNew->xSectorSize = vfstraceSectorSize;
|
||||
pNew->xDeviceCharacteristics = vfstraceDeviceCharacteristics;
|
||||
if( pNew->iVersion>=2 ){
|
||||
pNew->xShmMap = pSub->xShmMap ? vfstraceShmMap : 0;
|
||||
pNew->xShmLock = pSub->xShmLock ? vfstraceShmLock : 0;
|
||||
pNew->xShmBarrier = pSub->xShmBarrier ? vfstraceShmBarrier : 0;
|
||||
pNew->xShmUnmap = pSub->xShmUnmap ? vfstraceShmUnmap : 0;
|
||||
}
|
||||
pFile->pMethods = pNew;
|
||||
}
|
||||
vfstrace_print_errcode(pInfo, " -> %s", rc);
|
||||
if( pOutFlags ){
|
||||
vfstrace_printf(pInfo, ", outFlags=0x%x\n", *pOutFlags);
|
||||
}else{
|
||||
vfstrace_printf(pInfo, "\n");
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Delete the file located at zPath. If the dirSync argument is true,
|
||||
** ensure the file-system modifications are synced to disk before
|
||||
** returning.
|
||||
*/
|
||||
static int vfstraceDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xDelete(\"%s\",%d)",
|
||||
pInfo->zVfsName, zPath, dirSync);
|
||||
rc = pRoot->xDelete(pRoot, zPath, dirSync);
|
||||
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Test for access permissions. Return true if the requested permission
|
||||
** is available, or false otherwise.
|
||||
*/
|
||||
static int vfstraceAccess(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zPath,
|
||||
int flags,
|
||||
int *pResOut
|
||||
){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xDelete(\"%s\",%d)",
|
||||
pInfo->zVfsName, zPath, flags);
|
||||
rc = pRoot->xAccess(pRoot, zPath, flags, pResOut);
|
||||
vfstrace_print_errcode(pInfo, " -> %s", rc);
|
||||
vfstrace_printf(pInfo, ", out=%d\n", *pResOut);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Populate buffer zOut with the full canonical pathname corresponding
|
||||
** to the pathname in zPath. zOut is guaranteed to point to a buffer
|
||||
** of at least (DEVSYM_MAX_PATHNAME+1) bytes.
|
||||
*/
|
||||
static int vfstraceFullPathname(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zPath,
|
||||
int nOut,
|
||||
char *zOut
|
||||
){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
int rc;
|
||||
vfstrace_printf(pInfo, "%s.xFullPathname(\"%s\")",
|
||||
pInfo->zVfsName, zPath);
|
||||
rc = pRoot->xFullPathname(pRoot, zPath, nOut, zOut);
|
||||
vfstrace_print_errcode(pInfo, " -> %s", rc);
|
||||
vfstrace_printf(pInfo, ", out=\"%.*s\"\n", nOut, zOut);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open the dynamic library located at zPath and return a handle.
|
||||
*/
|
||||
static void *vfstraceDlOpen(sqlite3_vfs *pVfs, const char *zPath){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
vfstrace_printf(pInfo, "%s.xDlOpen(\"%s\")\n", pInfo->zVfsName, zPath);
|
||||
return pRoot->xDlOpen(pRoot, zPath);
|
||||
}
|
||||
|
||||
/*
|
||||
** Populate the buffer zErrMsg (size nByte bytes) with a human readable
|
||||
** utf-8 string describing the most recent error encountered associated
|
||||
** with dynamic libraries.
|
||||
*/
|
||||
static void vfstraceDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
vfstrace_printf(pInfo, "%s.xDlError(%d)", pInfo->zVfsName, nByte);
|
||||
pRoot->xDlError(pRoot, nByte, zErrMsg);
|
||||
vfstrace_printf(pInfo, " -> \"%s\"", zErrMsg);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
|
||||
*/
|
||||
static void (*vfstraceDlSym(sqlite3_vfs *pVfs,void *p,const char *zSym))(void){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
vfstrace_printf(pInfo, "%s.xDlSym(\"%s\")\n", pInfo->zVfsName, zSym);
|
||||
return pRoot->xDlSym(pRoot, p, zSym);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close the dynamic library handle pHandle.
|
||||
*/
|
||||
static void vfstraceDlClose(sqlite3_vfs *pVfs, void *pHandle){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
vfstrace_printf(pInfo, "%s.xDlOpen()\n", pInfo->zVfsName);
|
||||
pRoot->xDlClose(pRoot, pHandle);
|
||||
}
|
||||
|
||||
/*
|
||||
** Populate the buffer pointed to by zBufOut with nByte bytes of
|
||||
** random data.
|
||||
*/
|
||||
static int vfstraceRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
vfstrace_printf(pInfo, "%s.xRandomness(%d)\n", pInfo->zVfsName, nByte);
|
||||
return pRoot->xRandomness(pRoot, nByte, zBufOut);
|
||||
}
|
||||
|
||||
/*
|
||||
** Sleep for nMicro microseconds. Return the number of microseconds
|
||||
** actually slept.
|
||||
*/
|
||||
static int vfstraceSleep(sqlite3_vfs *pVfs, int nMicro){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
return pRoot->xSleep(pRoot, nMicro);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the current time as a Julian Day number in *pTimeOut.
|
||||
*/
|
||||
static int vfstraceCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
return pRoot->xCurrentTime(pRoot, pTimeOut);
|
||||
}
|
||||
static int vfstraceCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
return pRoot->xCurrentTimeInt64(pRoot, pTimeOut);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return th3 emost recent error code and message
|
||||
*/
|
||||
static int vfstraceGetLastError(sqlite3_vfs *pVfs, int iErr, char *zErr){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
return pRoot->xGetLastError(pRoot, iErr, zErr);
|
||||
}
|
||||
|
||||
/*
|
||||
** Override system calls.
|
||||
*/
|
||||
static int vfstraceSetSystemCall(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zName,
|
||||
sqlite3_syscall_ptr pFunc
|
||||
){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
return pRoot->xSetSystemCall(pRoot, zName, pFunc);
|
||||
}
|
||||
static sqlite3_syscall_ptr vfstraceGetSystemCall(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zName
|
||||
){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
return pRoot->xGetSystemCall(pRoot, zName);
|
||||
}
|
||||
static const char *vfstraceNextSystemCall(sqlite3_vfs *pVfs, const char *zName){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
return pRoot->xNextSystemCall(pRoot, zName);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Clients invoke this routine to construct a new trace-vfs shim.
|
||||
**
|
||||
** Return SQLITE_OK on success.
|
||||
**
|
||||
** SQLITE_NOMEM is returned in the case of a memory allocation error.
|
||||
** SQLITE_NOTFOUND is returned if zOldVfsName does not exist.
|
||||
*/
|
||||
int vfstrace_register(
|
||||
const char *zTraceName, /* Name of the newly constructed VFS */
|
||||
const char *zOldVfsName, /* Name of the underlying VFS */
|
||||
int (*xOut)(const char*,void*), /* Output routine. ex: fputs */
|
||||
void *pOutArg, /* 2nd argument to xOut. ex: stderr */
|
||||
int makeDefault /* True to make the new VFS the default */
|
||||
){
|
||||
sqlite3_vfs *pNew;
|
||||
sqlite3_vfs *pRoot;
|
||||
vfstrace_info *pInfo;
|
||||
int nName;
|
||||
int nByte;
|
||||
|
||||
pRoot = sqlite3_vfs_find(zOldVfsName);
|
||||
if( pRoot==0 ) return SQLITE_NOTFOUND;
|
||||
nName = strlen(zTraceName);
|
||||
nByte = sizeof(*pNew) + sizeof(*pInfo) + nName + 1;
|
||||
pNew = sqlite3_malloc( nByte );
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, nByte);
|
||||
pInfo = (vfstrace_info*)&pNew[1];
|
||||
pNew->iVersion = pRoot->iVersion;
|
||||
pNew->szOsFile = pRoot->szOsFile + sizeof(vfstrace_file);
|
||||
pNew->mxPathname = pRoot->mxPathname;
|
||||
pNew->zName = (char*)&pInfo[1];
|
||||
memcpy((char*)&pInfo[1], zTraceName, nName+1);
|
||||
pNew->pAppData = pInfo;
|
||||
pNew->xOpen = vfstraceOpen;
|
||||
pNew->xDelete = vfstraceDelete;
|
||||
pNew->xAccess = vfstraceAccess;
|
||||
pNew->xFullPathname = vfstraceFullPathname;
|
||||
pNew->xDlOpen = pRoot->xDlOpen==0 ? 0 : vfstraceDlOpen;
|
||||
pNew->xDlError = pRoot->xDlError==0 ? 0 : vfstraceDlError;
|
||||
pNew->xDlSym = pRoot->xDlSym==0 ? 0 : vfstraceDlSym;
|
||||
pNew->xDlClose = pRoot->xDlClose==0 ? 0 : vfstraceDlClose;
|
||||
pNew->xRandomness = vfstraceRandomness;
|
||||
pNew->xSleep = vfstraceSleep;
|
||||
pNew->xCurrentTime = vfstraceCurrentTime;
|
||||
pNew->xGetLastError = pRoot->xGetLastError==0 ? 0 : vfstraceGetLastError;
|
||||
if( pNew->iVersion>=2 ){
|
||||
pNew->xCurrentTimeInt64 = pRoot->xCurrentTimeInt64==0 ? 0 :
|
||||
vfstraceCurrentTimeInt64;
|
||||
if( pNew->iVersion>=3 ){
|
||||
pNew->xSetSystemCall = pRoot->xSetSystemCall==0 ? 0 :
|
||||
vfstraceSetSystemCall;
|
||||
pNew->xGetSystemCall = pRoot->xGetSystemCall==0 ? 0 :
|
||||
vfstraceGetSystemCall;
|
||||
pNew->xNextSystemCall = pRoot->xNextSystemCall==0 ? 0 :
|
||||
vfstraceNextSystemCall;
|
||||
}
|
||||
}
|
||||
pInfo->pRootVfs = pRoot;
|
||||
pInfo->xOut = xOut;
|
||||
pInfo->pOutArg = pOutArg;
|
||||
pInfo->zVfsName = pNew->zName;
|
||||
pInfo->pTraceVfs = pNew;
|
||||
vfstrace_printf(pInfo, "%s.enabled_for(\"%s\")\n",
|
||||
pInfo->zVfsName, pRoot->zName);
|
||||
return sqlite3_vfs_register(pNew, makeDefault);
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
** 2011 April 02
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
**
|
||||
** This file implements a virtual table that returns the whole numbers
|
||||
** between 1 and 4294967295, inclusive.
|
||||
**
|
||||
** Example:
|
||||
**
|
||||
** CREATE VIRTUAL TABLE nums USING wholenumber;
|
||||
** SELECT value FROM nums WHERE value<10;
|
||||
**
|
||||
** Results in:
|
||||
**
|
||||
** 1 2 3 4 5 6 7 8 9
|
||||
*/
|
||||
#include "sqlite3.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
|
||||
|
||||
/* A wholenumber cursor object */
|
||||
typedef struct wholenumber_cursor wholenumber_cursor;
|
||||
struct wholenumber_cursor {
|
||||
sqlite3_vtab_cursor base; /* Base class - must be first */
|
||||
unsigned iValue; /* Current value */
|
||||
unsigned mxValue; /* Maximum value */
|
||||
};
|
||||
|
||||
/* Methods for the wholenumber module */
|
||||
static int wholenumberConnect(
|
||||
sqlite3 *db,
|
||||
void *pAux,
|
||||
int argc, const char *const*argv,
|
||||
sqlite3_vtab **ppVtab,
|
||||
char **pzErr
|
||||
){
|
||||
sqlite3_vtab *pNew;
|
||||
pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
sqlite3_declare_vtab(db, "CREATE TABLE x(value)");
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
return SQLITE_OK;
|
||||
}
|
||||
/* Note that for this virtual table, the xCreate and xConnect
|
||||
** methods are identical. */
|
||||
|
||||
static int wholenumberDisconnect(sqlite3_vtab *pVtab){
|
||||
sqlite3_free(pVtab);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
/* The xDisconnect and xDestroy methods are also the same */
|
||||
|
||||
|
||||
/*
|
||||
** Open a new wholenumber cursor.
|
||||
*/
|
||||
static int wholenumberOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
wholenumber_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
*ppCursor = &pCur->base;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a wholenumber cursor.
|
||||
*/
|
||||
static int wholenumberClose(sqlite3_vtab_cursor *cur){
|
||||
sqlite3_free(cur);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Advance a cursor to its next row of output
|
||||
*/
|
||||
static int wholenumberNext(sqlite3_vtab_cursor *cur){
|
||||
wholenumber_cursor *pCur = (wholenumber_cursor*)cur;
|
||||
pCur->iValue++;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the value associated with a wholenumber.
|
||||
*/
|
||||
static int wholenumberColumn(
|
||||
sqlite3_vtab_cursor *cur,
|
||||
sqlite3_context *ctx,
|
||||
int i
|
||||
){
|
||||
wholenumber_cursor *pCur = (wholenumber_cursor*)cur;
|
||||
sqlite3_result_int64(ctx, pCur->iValue);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** The rowid.
|
||||
*/
|
||||
static int wholenumberRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
|
||||
wholenumber_cursor *pCur = (wholenumber_cursor*)cur;
|
||||
*pRowid = pCur->iValue;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** When the wholenumber_cursor.rLimit value is 0 or less, that is a signal
|
||||
** that the cursor has nothing more to output.
|
||||
*/
|
||||
static int wholenumberEof(sqlite3_vtab_cursor *cur){
|
||||
wholenumber_cursor *pCur = (wholenumber_cursor*)cur;
|
||||
return pCur->iValue>pCur->mxValue || pCur->iValue==0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Called to "rewind" a cursor back to the beginning so that
|
||||
** it starts its output over again. Always called at least once
|
||||
** prior to any wholenumberColumn, wholenumberRowid, or wholenumberEof call.
|
||||
**
|
||||
** idxNum Constraints
|
||||
** ------ ---------------------
|
||||
** 0 (none)
|
||||
** 1 value > $argv0
|
||||
** 2 value >= $argv0
|
||||
** 4 value < $argv0
|
||||
** 8 value <= $argv0
|
||||
**
|
||||
** 5 value > $argv0 AND value < $argv1
|
||||
** 6 value >= $argv0 AND value < $argv1
|
||||
** 9 value > $argv0 AND value <= $argv1
|
||||
** 10 value >= $argv0 AND value <= $argv1
|
||||
*/
|
||||
static int wholenumberFilter(
|
||||
sqlite3_vtab_cursor *pVtabCursor,
|
||||
int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv
|
||||
){
|
||||
wholenumber_cursor *pCur = (wholenumber_cursor *)pVtabCursor;
|
||||
sqlite3_int64 v;
|
||||
int i = 0;
|
||||
pCur->iValue = 1;
|
||||
pCur->mxValue = 0xffffffff; /* 4294967295 */
|
||||
if( idxNum & 3 ){
|
||||
v = sqlite3_value_int64(argv[0]) + (idxNum&1);
|
||||
if( v>pCur->iValue && v<=pCur->mxValue ) pCur->iValue = v;
|
||||
i++;
|
||||
}
|
||||
if( idxNum & 12 ){
|
||||
v = sqlite3_value_int64(argv[i]) - ((idxNum>>2)&1);
|
||||
if( v>=pCur->iValue && v<pCur->mxValue ) pCur->mxValue = v;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Search for terms of these forms:
|
||||
**
|
||||
** (1) value > $value
|
||||
** (2) value >= $value
|
||||
** (4) value < $value
|
||||
** (8) value <= $value
|
||||
**
|
||||
** idxNum is an ORed combination of 1 or 2 with 4 or 8.
|
||||
*/
|
||||
static int wholenumberBestIndex(
|
||||
sqlite3_vtab *tab,
|
||||
sqlite3_index_info *pIdxInfo
|
||||
){
|
||||
int i;
|
||||
int idxNum = 0;
|
||||
int argvIdx = 1;
|
||||
int ltIdx = -1;
|
||||
int gtIdx = -1;
|
||||
const struct sqlite3_index_constraint *pConstraint;
|
||||
pConstraint = pIdxInfo->aConstraint;
|
||||
for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
|
||||
if( pConstraint->usable==0 ) continue;
|
||||
if( (idxNum & 3)==0 && pConstraint->op==SQLITE_INDEX_CONSTRAINT_GT ){
|
||||
idxNum |= 1;
|
||||
ltIdx = i;
|
||||
}
|
||||
if( (idxNum & 3)==0 && pConstraint->op==SQLITE_INDEX_CONSTRAINT_GE ){
|
||||
idxNum |= 2;
|
||||
ltIdx = i;
|
||||
}
|
||||
if( (idxNum & 12)==0 && pConstraint->op==SQLITE_INDEX_CONSTRAINT_LT ){
|
||||
idxNum |= 4;
|
||||
gtIdx = i;
|
||||
}
|
||||
if( (idxNum & 12)==0 && pConstraint->op==SQLITE_INDEX_CONSTRAINT_LE ){
|
||||
idxNum |= 8;
|
||||
gtIdx = i;
|
||||
}
|
||||
}
|
||||
pIdxInfo->idxNum = idxNum;
|
||||
if( ltIdx>=0 ){
|
||||
pIdxInfo->aConstraintUsage[ltIdx].argvIndex = argvIdx++;
|
||||
pIdxInfo->aConstraintUsage[ltIdx].omit = 1;
|
||||
}
|
||||
if( gtIdx>=0 ){
|
||||
pIdxInfo->aConstraintUsage[gtIdx].argvIndex = argvIdx;
|
||||
pIdxInfo->aConstraintUsage[gtIdx].omit = 1;
|
||||
}
|
||||
if( pIdxInfo->nOrderBy==1
|
||||
&& pIdxInfo->aOrderBy[0].desc==0
|
||||
){
|
||||
pIdxInfo->orderByConsumed = 1;
|
||||
}
|
||||
pIdxInfo->estimatedCost = (double)1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** A virtual table module that provides read-only access to a
|
||||
** Tcl global variable namespace.
|
||||
*/
|
||||
static sqlite3_module wholenumberModule = {
|
||||
0, /* iVersion */
|
||||
wholenumberConnect,
|
||||
wholenumberConnect,
|
||||
wholenumberBestIndex,
|
||||
wholenumberDisconnect,
|
||||
wholenumberDisconnect,
|
||||
wholenumberOpen, /* xOpen - open a cursor */
|
||||
wholenumberClose, /* xClose - close a cursor */
|
||||
wholenumberFilter, /* xFilter - configure scan constraints */
|
||||
wholenumberNext, /* xNext - advance a cursor */
|
||||
wholenumberEof, /* xEof - check for end of scan */
|
||||
wholenumberColumn, /* xColumn - read data */
|
||||
wholenumberRowid, /* xRowid - read data */
|
||||
0, /* xUpdate */
|
||||
0, /* xBegin */
|
||||
0, /* xSync */
|
||||
0, /* xCommit */
|
||||
0, /* xRollback */
|
||||
0, /* xFindMethod */
|
||||
0, /* xRename */
|
||||
};
|
||||
|
||||
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
||||
|
||||
|
||||
/*
|
||||
** Register the wholenumber virtual table
|
||||
*/
|
||||
int wholenumber_register(sqlite3 *db){
|
||||
int rc = SQLITE_OK;
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
rc = sqlite3_create_module(db, "wholenumber", &wholenumberModule, 0);
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
#include <tcl.h>
|
||||
/*
|
||||
** Decode a pointer to an sqlite3 object.
|
||||
*/
|
||||
extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb);
|
||||
|
||||
/*
|
||||
** Register the echo virtual table module.
|
||||
*/
|
||||
static int register_wholenumber_module(
|
||||
ClientData clientData, /* Pointer to sqlite3_enable_XXX function */
|
||||
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
|
||||
int objc, /* Number of arguments */
|
||||
Tcl_Obj *CONST objv[] /* Command arguments */
|
||||
){
|
||||
sqlite3 *db;
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
|
||||
wholenumber_register(db);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Register commands with the TCL interpreter.
|
||||
*/
|
||||
int Sqlitetestwholenumber_Init(Tcl_Interp *interp){
|
||||
static struct {
|
||||
char *zName;
|
||||
Tcl_ObjCmdProc *xProc;
|
||||
void *clientData;
|
||||
} aObjCmd[] = {
|
||||
{ "register_wholenumber_module", register_wholenumber_module, 0 },
|
||||
};
|
||||
int i;
|
||||
for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
|
||||
Tcl_CreateObjCommand(interp, aObjCmd[i].zName,
|
||||
aObjCmd[i].xProc, aObjCmd[i].clientData, 0);
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
#endif /* SQLITE_TEST */
|
||||
@@ -54,6 +54,7 @@ Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){
|
||||
|
||||
if( pTmpSchema!=pTab->pSchema ){
|
||||
HashElem *p;
|
||||
assert( sqlite3SchemaMutexHeld(pParse->db, 0, pTmpSchema) );
|
||||
for(p=sqliteHashFirst(&pTmpSchema->trigHash); p; p=sqliteHashNext(p)){
|
||||
Trigger *pTrig = (Trigger *)sqliteHashData(p);
|
||||
if( pTrig->pTabSchema==pTab->pSchema
|
||||
@@ -165,10 +166,14 @@ void sqlite3BeginTrigger(
|
||||
if( !zName || SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){
|
||||
goto trigger_cleanup;
|
||||
}
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
if( sqlite3HashFind(&(db->aDb[iDb].pSchema->trigHash),
|
||||
zName, sqlite3Strlen30(zName)) ){
|
||||
if( !noErr ){
|
||||
sqlite3ErrorMsg(pParse, "trigger %T already exists", pName);
|
||||
}else{
|
||||
assert( !db->init.busy );
|
||||
sqlite3CodeVerifySchema(pParse, iDb);
|
||||
}
|
||||
goto trigger_cleanup;
|
||||
}
|
||||
@@ -262,7 +267,6 @@ void sqlite3FinishTrigger(
|
||||
int iDb; /* Database containing the trigger */
|
||||
Token nameToken; /* Trigger name for error reporting */
|
||||
|
||||
pTrig = pParse->pNewTrigger;
|
||||
pParse->pNewTrigger = 0;
|
||||
if( NEVER(pParse->nErr) || !pTrig ) goto triggerfinish_cleanup;
|
||||
zName = pTrig->zName;
|
||||
@@ -305,6 +309,7 @@ void sqlite3FinishTrigger(
|
||||
if( db->init.busy ){
|
||||
Trigger *pLink = pTrig;
|
||||
Hash *pHash = &db->aDb[iDb].pSchema->trigHash;
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
pTrig = sqlite3HashInsert(pHash, zName, sqlite3Strlen30(zName), pTrig);
|
||||
if( pTrig ){
|
||||
db->mallocFailed = 1;
|
||||
@@ -486,15 +491,19 @@ void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr){
|
||||
zDb = pName->a[0].zDatabase;
|
||||
zName = pName->a[0].zName;
|
||||
nName = sqlite3Strlen30(zName);
|
||||
assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) );
|
||||
for(i=OMIT_TEMPDB; i<db->nDb; i++){
|
||||
int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
|
||||
if( zDb && sqlite3StrICmp(db->aDb[j].zName, zDb) ) continue;
|
||||
assert( sqlite3SchemaMutexHeld(db, j, 0) );
|
||||
pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName, nName);
|
||||
if( pTrigger ) break;
|
||||
}
|
||||
if( !pTrigger ){
|
||||
if( !noErr ){
|
||||
sqlite3ErrorMsg(pParse, "no such trigger: %S", pName, 0);
|
||||
}else{
|
||||
sqlite3CodeVerifyNamedSchema(pParse, zDb);
|
||||
}
|
||||
pParse->checkSchema = 1;
|
||||
goto drop_trigger_cleanup;
|
||||
@@ -562,7 +571,7 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){
|
||||
sqlite3BeginWriteOperation(pParse, 0, iDb);
|
||||
sqlite3OpenMasterTable(pParse, iDb);
|
||||
base = sqlite3VdbeAddOpList(v, ArraySize(dropTrigger), dropTrigger);
|
||||
sqlite3VdbeChangeP4(v, base+1, pTrigger->zName, 0);
|
||||
sqlite3VdbeChangeP4(v, base+1, pTrigger->zName, P4_TRANSIENT);
|
||||
sqlite3VdbeChangeP4(v, base+4, "trigger", P4_STATIC);
|
||||
sqlite3ChangeCookie(pParse, iDb);
|
||||
sqlite3VdbeAddOp2(v, OP_Close, 0, 0);
|
||||
@@ -577,8 +586,11 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){
|
||||
** Remove a trigger from the hash tables of the sqlite* pointer.
|
||||
*/
|
||||
void sqlite3UnlinkAndDeleteTrigger(sqlite3 *db, int iDb, const char *zName){
|
||||
Hash *pHash = &(db->aDb[iDb].pSchema->trigHash);
|
||||
Trigger *pTrigger;
|
||||
Hash *pHash;
|
||||
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
pHash = &(db->aDb[iDb].pSchema->trigHash);
|
||||
pTrigger = sqlite3HashInsert(pHash, zName, sqlite3Strlen30(zName), 0);
|
||||
if( ALWAYS(pTrigger) ){
|
||||
if( pTrigger->pSchema==pTrigger->pTabSchema ){
|
||||
@@ -624,8 +636,12 @@ Trigger *sqlite3TriggersExist(
|
||||
int *pMask /* OUT: Mask of TRIGGER_BEFORE|TRIGGER_AFTER */
|
||||
){
|
||||
int mask = 0;
|
||||
Trigger *pList = sqlite3TriggerList(pParse, pTab);
|
||||
Trigger *pList = 0;
|
||||
Trigger *p;
|
||||
|
||||
if( (pParse->db->flags & SQLITE_EnableTrigger)!=0 ){
|
||||
pList = sqlite3TriggerList(pParse, pTab);
|
||||
}
|
||||
assert( pList==0 || IsVirtual(pTab)==0 );
|
||||
for(p=pList; p; p=p->pNext){
|
||||
if( p->op==op && checkColumnOverlap(p->pColumns, pChanges) ){
|
||||
|
||||
@@ -128,7 +128,6 @@ void sqlite3Update(
|
||||
int regNew;
|
||||
int regOld = 0;
|
||||
int regRowSet = 0; /* Rowset of rows to be updated */
|
||||
int regRec; /* Register used for new table record to insert */
|
||||
|
||||
memset(&sContext, 0, sizeof(sContext));
|
||||
db = pParse->db;
|
||||
@@ -286,7 +285,6 @@ void sqlite3Update(
|
||||
}
|
||||
regNew = pParse->nMem + 1;
|
||||
pParse->nMem += pTab->nCol;
|
||||
regRec = ++pParse->nMem;
|
||||
|
||||
/* Start the view context. */
|
||||
if( isView ){
|
||||
@@ -396,7 +394,7 @@ void sqlite3Update(
|
||||
pTrigger, pChanges, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onError
|
||||
);
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( aXRef[i]<0 || oldmask==0xffffffff || (oldmask & (1<<i)) ){
|
||||
if( aXRef[i]<0 || oldmask==0xffffffff || (i<32 && (oldmask & (1<<i))) ){
|
||||
sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, i, regOld+i);
|
||||
}else{
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regOld+i);
|
||||
|
||||
@@ -167,7 +167,7 @@ int sqlite3Utf8Read(
|
||||
const unsigned char *zIn, /* First byte of UTF-8 character */
|
||||
const unsigned char **pzNext /* Write first byte past UTF-8 char here */
|
||||
){
|
||||
int c;
|
||||
unsigned int c;
|
||||
|
||||
/* Same as READ_UTF8() above but without the zTerm parameter.
|
||||
** For this routine, we assume the UTF8 string is always zero-terminated.
|
||||
@@ -410,15 +410,15 @@ int sqlite3Utf8CharLen(const char *zIn, int nByte){
|
||||
** This has the effect of making sure that the string is well-formed
|
||||
** UTF-8. Miscoded characters are removed.
|
||||
**
|
||||
** The translation is done in-place (since it is impossible for the
|
||||
** correct UTF-8 encoding to be longer than a malformed encoding).
|
||||
** The translation is done in-place and aborted if the output
|
||||
** overruns the input.
|
||||
*/
|
||||
int sqlite3Utf8To8(unsigned char *zIn){
|
||||
unsigned char *zOut = zIn;
|
||||
unsigned char *zStart = zIn;
|
||||
u32 c;
|
||||
|
||||
while( zIn[0] ){
|
||||
while( zIn[0] && zOut<=zIn ){
|
||||
c = sqlite3Utf8Read(zIn, (const u8**)&zIn);
|
||||
if( c!=0xfffd ){
|
||||
WRITE_UTF8(zOut, c);
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
*/
|
||||
#ifdef SQLITE_COVERAGE_TEST
|
||||
void sqlite3Coverage(int x){
|
||||
static int dummy = 0;
|
||||
dummy += x;
|
||||
static unsigned dummy = 0;
|
||||
dummy += (unsigned)x;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -441,14 +441,17 @@ static int compare2pow63(const char *zNum, int incr){
|
||||
|
||||
|
||||
/*
|
||||
** Convert zNum to a 64-bit signed integer and write
|
||||
** the value of the integer into *pNum.
|
||||
** If zNum is exactly 9223372036854665808, return 2.
|
||||
** This is a special case as the context will determine
|
||||
** if it is too big (used as a negative).
|
||||
** If zNum is not an integer or is an integer that
|
||||
** is too large to be expressed with 64 bits,
|
||||
** then return 1. Otherwise return 0.
|
||||
** Convert zNum to a 64-bit signed integer.
|
||||
**
|
||||
** If the zNum value is representable as a 64-bit twos-complement
|
||||
** integer, then write that value into *pNum and return 0.
|
||||
**
|
||||
** If zNum is exactly 9223372036854665808, return 2. This special
|
||||
** case is broken out because while 9223372036854665808 cannot be a
|
||||
** signed 64-bit integer, its negative -9223372036854665808 can be.
|
||||
**
|
||||
** If zNum is too big for a 64-bit integer and is not
|
||||
** 9223372036854665808 then return 1.
|
||||
**
|
||||
** length is the number of bytes in the string (bytes, not characters).
|
||||
** The string is not necessarily zero-terminated. The encoding is
|
||||
@@ -456,7 +459,7 @@ static int compare2pow63(const char *zNum, int incr){
|
||||
*/
|
||||
int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc){
|
||||
int incr = (enc==SQLITE_UTF8?1:2);
|
||||
i64 v = 0;
|
||||
u64 u = 0;
|
||||
int neg = 0; /* assume positive */
|
||||
int i;
|
||||
int c = 0;
|
||||
@@ -464,20 +467,26 @@ int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc){
|
||||
const char *zEnd = zNum + length;
|
||||
if( enc==SQLITE_UTF16BE ) zNum++;
|
||||
while( zNum<zEnd && sqlite3Isspace(*zNum) ) zNum+=incr;
|
||||
if( zNum>=zEnd ) goto do_atoi_calc;
|
||||
if( *zNum=='-' ){
|
||||
neg = 1;
|
||||
zNum+=incr;
|
||||
}else if( *zNum=='+' ){
|
||||
zNum+=incr;
|
||||
if( zNum<zEnd ){
|
||||
if( *zNum=='-' ){
|
||||
neg = 1;
|
||||
zNum+=incr;
|
||||
}else if( *zNum=='+' ){
|
||||
zNum+=incr;
|
||||
}
|
||||
}
|
||||
do_atoi_calc:
|
||||
zStart = zNum;
|
||||
while( zNum<zEnd && zNum[0]=='0' ){ zNum+=incr; } /* Skip leading zeros. */
|
||||
for(i=0; &zNum[i]<zEnd && (c=zNum[i])>='0' && c<='9'; i+=incr){
|
||||
v = v*10 + c - '0';
|
||||
u = u*10 + c - '0';
|
||||
}
|
||||
if( u>LARGEST_INT64 ){
|
||||
*pNum = SMALLEST_INT64;
|
||||
}else if( neg ){
|
||||
*pNum = -(i64)u;
|
||||
}else{
|
||||
*pNum = (i64)u;
|
||||
}
|
||||
*pNum = neg ? -v : v;
|
||||
testcase( i==18 );
|
||||
testcase( i==19 );
|
||||
testcase( i==20 );
|
||||
@@ -487,14 +496,25 @@ do_atoi_calc:
|
||||
return 1;
|
||||
}else if( i<19*incr ){
|
||||
/* Less than 19 digits, so we know that it fits in 64 bits */
|
||||
assert( u<=LARGEST_INT64 );
|
||||
return 0;
|
||||
}else{
|
||||
/* 19-digit numbers must be no larger than 9223372036854775807 if positive
|
||||
** or 9223372036854775808 if negative. Note that 9223372036854665808
|
||||
** is 2^63. Return 1 if to large */
|
||||
c=compare2pow63(zNum, incr);
|
||||
if( c==0 && neg==0 ) return 2; /* too big, exactly 9223372036854665808 */
|
||||
return c<neg ? 0 : 1;
|
||||
/* zNum is a 19-digit numbers. Compare it against 9223372036854775808. */
|
||||
c = compare2pow63(zNum, incr);
|
||||
if( c<0 ){
|
||||
/* zNum is less than 9223372036854775808 so it fits */
|
||||
assert( u<=LARGEST_INT64 );
|
||||
return 0;
|
||||
}else if( c>0 ){
|
||||
/* zNum is greater than 9223372036854775808 so it overflows */
|
||||
return 1;
|
||||
}else{
|
||||
/* zNum is exactly 9223372036854775808. Fits if negative. The
|
||||
** special case 2 overflow if positive */
|
||||
assert( u-1==LARGEST_INT64 );
|
||||
assert( (*pNum)==SMALLEST_INT64 );
|
||||
return neg ? 0 : 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1060,3 +1080,71 @@ int sqlite3SafetyCheckSickOrOk(sqlite3 *db){
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Attempt to add, substract, or multiply the 64-bit signed value iB against
|
||||
** the other 64-bit signed integer at *pA and store the result in *pA.
|
||||
** Return 0 on success. Or if the operation would have resulted in an
|
||||
** overflow, leave *pA unchanged and return 1.
|
||||
*/
|
||||
int sqlite3AddInt64(i64 *pA, i64 iB){
|
||||
i64 iA = *pA;
|
||||
testcase( iA==0 ); testcase( iA==1 );
|
||||
testcase( iB==-1 ); testcase( iB==0 );
|
||||
if( iB>=0 ){
|
||||
testcase( iA>0 && LARGEST_INT64 - iA == iB );
|
||||
testcase( iA>0 && LARGEST_INT64 - iA == iB - 1 );
|
||||
if( iA>0 && LARGEST_INT64 - iA < iB ) return 1;
|
||||
*pA += iB;
|
||||
}else{
|
||||
testcase( iA<0 && -(iA + LARGEST_INT64) == iB + 1 );
|
||||
testcase( iA<0 && -(iA + LARGEST_INT64) == iB + 2 );
|
||||
if( iA<0 && -(iA + LARGEST_INT64) > iB + 1 ) return 1;
|
||||
*pA += iB;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int sqlite3SubInt64(i64 *pA, i64 iB){
|
||||
testcase( iB==SMALLEST_INT64+1 );
|
||||
if( iB==SMALLEST_INT64 ){
|
||||
testcase( (*pA)==(-1) ); testcase( (*pA)==0 );
|
||||
if( (*pA)>=0 ) return 1;
|
||||
*pA -= iB;
|
||||
return 0;
|
||||
}else{
|
||||
return sqlite3AddInt64(pA, -iB);
|
||||
}
|
||||
}
|
||||
#define TWOPOWER32 (((i64)1)<<32)
|
||||
#define TWOPOWER31 (((i64)1)<<31)
|
||||
int sqlite3MulInt64(i64 *pA, i64 iB){
|
||||
i64 iA = *pA;
|
||||
i64 iA1, iA0, iB1, iB0, r;
|
||||
|
||||
iA1 = iA/TWOPOWER32;
|
||||
iA0 = iA % TWOPOWER32;
|
||||
iB1 = iB/TWOPOWER32;
|
||||
iB0 = iB % TWOPOWER32;
|
||||
if( iA1*iB1 != 0 ) return 1;
|
||||
assert( iA1*iB0==0 || iA0*iB1==0 );
|
||||
r = iA1*iB0 + iA0*iB1;
|
||||
testcase( r==(-TWOPOWER31)-1 );
|
||||
testcase( r==(-TWOPOWER31) );
|
||||
testcase( r==TWOPOWER31 );
|
||||
testcase( r==TWOPOWER31-1 );
|
||||
if( r<(-TWOPOWER31) || r>=TWOPOWER31 ) return 1;
|
||||
r *= TWOPOWER32;
|
||||
if( sqlite3AddInt64(&r, iA0*iB0) ) return 1;
|
||||
*pA = r;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Compute the absolute value of a 32-bit signed integer, of possible. Or
|
||||
** if the integer has a value of -2147483648, return +2147483647
|
||||
*/
|
||||
int sqlite3AbsInt32(int x){
|
||||
if( x>=0 ) return x;
|
||||
if( x==(int)0x80000000 ) return 0x7fffffff;
|
||||
return -x;
|
||||
}
|
||||
|
||||
@@ -335,8 +335,11 @@ end_of_vacuum:
|
||||
pDb->pSchema = 0;
|
||||
}
|
||||
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
/* This both clears the schemas and reduces the size of the db->aDb[]
|
||||
** array. */
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* SQLITE_OMIT_VACUUM && SQLITE_OMIT_ATTACH */
|
||||
|
||||
@@ -551,7 +551,7 @@ int sqlite3VdbeExec(
|
||||
Op *pOp; /* Current operation */
|
||||
int rc = SQLITE_OK; /* Value to return */
|
||||
sqlite3 *db = p->db; /* The database */
|
||||
u8 resetSchemaOnFault = 0; /* Reset schema after an error if true */
|
||||
u8 resetSchemaOnFault = 0; /* Reset schema after an error if positive */
|
||||
u8 encoding = ENC(db); /* The database encoding */
|
||||
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
|
||||
int checkProgress; /* True if progress callbacks are enabled */
|
||||
@@ -571,7 +571,7 @@ int sqlite3VdbeExec(
|
||||
/*** INSERT STACK UNION HERE ***/
|
||||
|
||||
assert( p->magic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */
|
||||
sqlite3VdbeMutexArrayEnter(p);
|
||||
sqlite3VdbeEnter(p);
|
||||
if( p->rc==SQLITE_NOMEM ){
|
||||
/* This happens if a malloc() inside a call to sqlite3_column_text() or
|
||||
** sqlite3_column_text16() failed. */
|
||||
@@ -1246,19 +1246,12 @@ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */
|
||||
iA = pIn1->u.i;
|
||||
iB = pIn2->u.i;
|
||||
switch( pOp->opcode ){
|
||||
case OP_Add: iB += iA; break;
|
||||
case OP_Subtract: iB -= iA; break;
|
||||
case OP_Multiply: iB *= iA; break;
|
||||
case OP_Add: if( sqlite3AddInt64(&iB,iA) ) goto fp_math; break;
|
||||
case OP_Subtract: if( sqlite3SubInt64(&iB,iA) ) goto fp_math; break;
|
||||
case OP_Multiply: if( sqlite3MulInt64(&iB,iA) ) goto fp_math; break;
|
||||
case OP_Divide: {
|
||||
if( iA==0 ) goto arithmetic_result_is_null;
|
||||
/* Dividing the largest possible negative 64-bit integer (1<<63) by
|
||||
** -1 returns an integer too large to store in a 64-bit data-type. On
|
||||
** some architectures, the value overflows to (1<<63). On others,
|
||||
** a SIGFPE is issued. The following statement normalizes this
|
||||
** behavior so that all architectures behave as if integer
|
||||
** overflow occurred.
|
||||
*/
|
||||
if( iA==-1 && iB==SMALLEST_INT64 ) iA = 1;
|
||||
if( iA==-1 && iB==SMALLEST_INT64 ) goto fp_math;
|
||||
iB /= iA;
|
||||
break;
|
||||
}
|
||||
@@ -1272,6 +1265,7 @@ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */
|
||||
pOut->u.i = iB;
|
||||
MemSetTypeFlag(pOut, MEM_Int);
|
||||
}else{
|
||||
fp_math:
|
||||
rA = sqlite3VdbeRealValue(pIn1);
|
||||
rB = sqlite3VdbeRealValue(pIn2);
|
||||
switch( pOp->opcode ){
|
||||
@@ -1431,6 +1425,15 @@ case OP_Function: {
|
||||
if( sqlite3VdbeMemTooBig(pOut) ){
|
||||
goto too_big;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* The app-defined function has done something that as caused this
|
||||
** statement to expire. (Perhaps the function called sqlite3_exec()
|
||||
** with a CREATE TABLE statement.)
|
||||
*/
|
||||
if( p->expired ) rc = SQLITE_ABORT;
|
||||
#endif
|
||||
|
||||
REGISTER_TRACE(pOp->p3, pOut);
|
||||
UPDATE_MAX_BLOBSIZE(pOut);
|
||||
break;
|
||||
@@ -1466,8 +1469,10 @@ case OP_BitAnd: /* same as TK_BITAND, in1, in2, out3 */
|
||||
case OP_BitOr: /* same as TK_BITOR, in1, in2, out3 */
|
||||
case OP_ShiftLeft: /* same as TK_LSHIFT, in1, in2, out3 */
|
||||
case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */
|
||||
i64 a;
|
||||
i64 b;
|
||||
i64 iA;
|
||||
u64 uA;
|
||||
i64 iB;
|
||||
u8 op;
|
||||
|
||||
pIn1 = &aMem[pOp->p1];
|
||||
pIn2 = &aMem[pOp->p2];
|
||||
@@ -1476,16 +1481,38 @@ case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */
|
||||
sqlite3VdbeMemSetNull(pOut);
|
||||
break;
|
||||
}
|
||||
a = sqlite3VdbeIntValue(pIn2);
|
||||
b = sqlite3VdbeIntValue(pIn1);
|
||||
switch( pOp->opcode ){
|
||||
case OP_BitAnd: a &= b; break;
|
||||
case OP_BitOr: a |= b; break;
|
||||
case OP_ShiftLeft: a <<= b; break;
|
||||
default: assert( pOp->opcode==OP_ShiftRight );
|
||||
a >>= b; break;
|
||||
iA = sqlite3VdbeIntValue(pIn2);
|
||||
iB = sqlite3VdbeIntValue(pIn1);
|
||||
op = pOp->opcode;
|
||||
if( op==OP_BitAnd ){
|
||||
iA &= iB;
|
||||
}else if( op==OP_BitOr ){
|
||||
iA |= iB;
|
||||
}else if( iB!=0 ){
|
||||
assert( op==OP_ShiftRight || op==OP_ShiftLeft );
|
||||
|
||||
/* If shifting by a negative amount, shift in the other direction */
|
||||
if( iB<0 ){
|
||||
assert( OP_ShiftRight==OP_ShiftLeft+1 );
|
||||
op = 2*OP_ShiftLeft + 1 - op;
|
||||
iB = iB>(-64) ? -iB : 64;
|
||||
}
|
||||
|
||||
if( iB>=64 ){
|
||||
iA = (iA>=0 || op==OP_ShiftLeft) ? 0 : -1;
|
||||
}else{
|
||||
memcpy(&uA, &iA, sizeof(uA));
|
||||
if( op==OP_ShiftLeft ){
|
||||
uA <<= iB;
|
||||
}else{
|
||||
uA >>= iB;
|
||||
/* Sign-extend on a right shift of a negative number */
|
||||
if( iA<0 ) uA |= ((((u64)0xffffffff)<<32)|0xffffffff) << (64-iB);
|
||||
}
|
||||
memcpy(&iA, &uA, sizeof(iA));
|
||||
}
|
||||
}
|
||||
pOut->u.i = a;
|
||||
pOut->u.i = iA;
|
||||
MemSetTypeFlag(pOut, MEM_Int);
|
||||
break;
|
||||
}
|
||||
@@ -2411,7 +2438,6 @@ case OP_MakeRecord: {
|
||||
*/
|
||||
nData = 0; /* Number of bytes of data space */
|
||||
nHdr = 0; /* Number of bytes of header space */
|
||||
nByte = 0; /* Data space required for this record */
|
||||
nZero = 0; /* Number of zero bytes at the end of the record */
|
||||
nField = pOp->p1;
|
||||
zAffinity = pOp->p4.z;
|
||||
@@ -2632,7 +2658,7 @@ case OP_Savepoint: {
|
||||
}
|
||||
if( p1==SAVEPOINT_ROLLBACK && (db->flags&SQLITE_InternChanges)!=0 ){
|
||||
sqlite3ExpirePreparedStatements(db);
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
db->flags = (db->flags | SQLITE_InternChanges);
|
||||
}
|
||||
}
|
||||
@@ -2772,7 +2798,7 @@ case OP_Transaction: {
|
||||
Btree *pBt;
|
||||
|
||||
assert( pOp->p1>=0 && pOp->p1<db->nDb );
|
||||
assert( (p->btreeMask & (1<<pOp->p1))!=0 );
|
||||
assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 );
|
||||
pBt = db->aDb[pOp->p1].pBt;
|
||||
|
||||
if( pBt ){
|
||||
@@ -2828,7 +2854,7 @@ case OP_ReadCookie: { /* out2-prerelease */
|
||||
assert( pOp->p3<SQLITE_N_BTREE_META );
|
||||
assert( iDb>=0 && iDb<db->nDb );
|
||||
assert( db->aDb[iDb].pBt!=0 );
|
||||
assert( (p->btreeMask & (1<<iDb))!=0 );
|
||||
assert( (p->btreeMask & (((yDbMask)1)<<iDb))!=0 );
|
||||
|
||||
sqlite3BtreeGetMeta(db->aDb[iDb].pBt, iCookie, (u32 *)&iMeta);
|
||||
pOut->u.i = iMeta;
|
||||
@@ -2849,9 +2875,10 @@ case OP_SetCookie: { /* in3 */
|
||||
Db *pDb;
|
||||
assert( pOp->p2<SQLITE_N_BTREE_META );
|
||||
assert( pOp->p1>=0 && pOp->p1<db->nDb );
|
||||
assert( (p->btreeMask & (1<<pOp->p1))!=0 );
|
||||
assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 );
|
||||
pDb = &db->aDb[pOp->p1];
|
||||
assert( pDb->pBt!=0 );
|
||||
assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) );
|
||||
pIn3 = &aMem[pOp->p3];
|
||||
sqlite3VdbeMemIntegerify(pIn3);
|
||||
/* See note about index shifting on OP_ReadCookie */
|
||||
@@ -2873,10 +2900,12 @@ case OP_SetCookie: { /* in3 */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: VerifyCookie P1 P2 *
|
||||
/* Opcode: VerifyCookie P1 P2 P3 * *
|
||||
**
|
||||
** Check the value of global database parameter number 0 (the
|
||||
** schema version) and make sure it is equal to P2.
|
||||
** schema version) and make sure it is equal to P2 and that the
|
||||
** generation counter on the local schema parse equals P3.
|
||||
**
|
||||
** P1 is the database number which is 0 for the main database file
|
||||
** and 1 for the file holding temporary tables and some higher number
|
||||
** for auxiliary databases.
|
||||
@@ -2891,16 +2920,20 @@ case OP_SetCookie: { /* in3 */
|
||||
*/
|
||||
case OP_VerifyCookie: {
|
||||
int iMeta;
|
||||
int iGen;
|
||||
Btree *pBt;
|
||||
|
||||
assert( pOp->p1>=0 && pOp->p1<db->nDb );
|
||||
assert( (p->btreeMask & (1<<pOp->p1))!=0 );
|
||||
assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 );
|
||||
assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) );
|
||||
pBt = db->aDb[pOp->p1].pBt;
|
||||
if( pBt ){
|
||||
sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&iMeta);
|
||||
iGen = db->aDb[pOp->p1].pSchema->iGeneration;
|
||||
}else{
|
||||
iMeta = 0;
|
||||
iGen = iMeta = 0;
|
||||
}
|
||||
if( iMeta!=pOp->p2 ){
|
||||
if( iMeta!=pOp->p2 || iGen!=pOp->p3 ){
|
||||
sqlite3DbFree(db, p->zErrMsg);
|
||||
p->zErrMsg = sqlite3DbStrDup(db, "database schema has changed");
|
||||
/* If the schema-cookie from the database file matches the cookie
|
||||
@@ -2920,7 +2953,7 @@ case OP_VerifyCookie: {
|
||||
sqlite3ResetInternalSchema(db, pOp->p1);
|
||||
}
|
||||
|
||||
sqlite3ExpirePreparedStatements(db);
|
||||
p->expired = 1;
|
||||
rc = SQLITE_SCHEMA;
|
||||
}
|
||||
break;
|
||||
@@ -2996,12 +3029,13 @@ case OP_OpenWrite: {
|
||||
p2 = pOp->p2;
|
||||
iDb = pOp->p3;
|
||||
assert( iDb>=0 && iDb<db->nDb );
|
||||
assert( (p->btreeMask & (1<<iDb))!=0 );
|
||||
assert( (p->btreeMask & (((yDbMask)1)<<iDb))!=0 );
|
||||
pDb = &db->aDb[iDb];
|
||||
pX = pDb->pBt;
|
||||
assert( pX!=0 );
|
||||
if( pOp->opcode==OP_OpenWrite ){
|
||||
wrFlag = 1;
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
if( pDb->pSchema->file_format < p->minWriteFileFormat ){
|
||||
p->minWriteFileFormat = pDb->pSchema->file_format;
|
||||
}
|
||||
@@ -3685,7 +3719,6 @@ case OP_NewRowid: { /* out2-prerelease */
|
||||
** and try again, up to 100 times.
|
||||
*/
|
||||
assert( pC->isTable );
|
||||
cnt = 0;
|
||||
|
||||
#ifdef SQLITE_32BIT_ROWID
|
||||
# define MAX_ROWID 0x7fffffff
|
||||
@@ -4492,14 +4525,16 @@ case OP_Destroy: { /* out2-prerelease */
|
||||
}else{
|
||||
iDb = pOp->p3;
|
||||
assert( iCnt==1 );
|
||||
assert( (p->btreeMask & (1<<iDb))!=0 );
|
||||
assert( (p->btreeMask & (((yDbMask)1)<<iDb))!=0 );
|
||||
rc = sqlite3BtreeDropTable(db->aDb[iDb].pBt, pOp->p1, &iMoved);
|
||||
pOut->flags = MEM_Int;
|
||||
pOut->u.i = iMoved;
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
if( rc==SQLITE_OK && iMoved!=0 ){
|
||||
sqlite3RootPageMoved(&db->aDb[iDb], iMoved, pOp->p1);
|
||||
resetSchemaOnFault = 1;
|
||||
sqlite3RootPageMoved(db, iDb, iMoved, pOp->p1);
|
||||
/* All OP_Destroy operations occur on the same btree */
|
||||
assert( resetSchemaOnFault==0 || resetSchemaOnFault==iDb+1 );
|
||||
resetSchemaOnFault = iDb+1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -4528,7 +4563,7 @@ case OP_Clear: {
|
||||
int nChange;
|
||||
|
||||
nChange = 0;
|
||||
assert( (p->btreeMask & (1<<pOp->p2))!=0 );
|
||||
assert( (p->btreeMask & (((yDbMask)1)<<pOp->p2))!=0 );
|
||||
rc = sqlite3BtreeClearTable(
|
||||
db->aDb[pOp->p2].pBt, pOp->p1, (pOp->p3 ? &nChange : 0)
|
||||
);
|
||||
@@ -4573,7 +4608,7 @@ case OP_CreateTable: { /* out2-prerelease */
|
||||
|
||||
pgno = 0;
|
||||
assert( pOp->p1>=0 && pOp->p1<db->nDb );
|
||||
assert( (p->btreeMask & (1<<pOp->p1))!=0 );
|
||||
assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 );
|
||||
pDb = &db->aDb[pOp->p1];
|
||||
assert( pDb->pBt!=0 );
|
||||
if( pOp->opcode==OP_CreateTable ){
|
||||
@@ -4587,14 +4622,10 @@ case OP_CreateTable: { /* out2-prerelease */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: ParseSchema P1 P2 * P4 *
|
||||
/* Opcode: ParseSchema P1 * * P4 *
|
||||
**
|
||||
** Read and parse all entries from the SQLITE_MASTER table of database P1
|
||||
** that match the WHERE clause P4. P2 is the "force" flag. Always do
|
||||
** the parsing if P2 is true. If P2 is false, then this routine is a
|
||||
** no-op if the schema is not currently loaded. In other words, if P2
|
||||
** is false, the SQLITE_MASTER table is only parsed if the rest of the
|
||||
** schema is already loaded into the symbol table.
|
||||
** that match the WHERE clause P4.
|
||||
**
|
||||
** This opcode invokes the parser to create a new virtual machine,
|
||||
** then runs the new virtual machine. It is thus a re-entrant opcode.
|
||||
@@ -4605,33 +4636,20 @@ case OP_ParseSchema: {
|
||||
char *zSql;
|
||||
InitData initData;
|
||||
|
||||
/* Any prepared statement that invokes this opcode will hold mutexes
|
||||
** on every btree. This is a prerequisite for invoking
|
||||
** sqlite3InitCallback().
|
||||
*/
|
||||
#ifdef SQLITE_DEBUG
|
||||
for(iDb=0; iDb<db->nDb; iDb++){
|
||||
assert( iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) );
|
||||
}
|
||||
#endif
|
||||
|
||||
iDb = pOp->p1;
|
||||
assert( iDb>=0 && iDb<db->nDb );
|
||||
|
||||
/* If pOp->p2 is 0, then this opcode is being executed to read a
|
||||
** single row, for example the row corresponding to a new index
|
||||
** created by this VDBE, from the sqlite_master table. It only
|
||||
** does this if the corresponding in-memory schema is currently
|
||||
** loaded. Otherwise, the new index definition can be loaded along
|
||||
** with the rest of the schema when it is required.
|
||||
**
|
||||
** Although the mutex on the BtShared object that corresponds to
|
||||
** database iDb (the database containing the sqlite_master table
|
||||
** read by this instruction) is currently held, it is necessary to
|
||||
** obtain the mutexes on all attached databases before checking if
|
||||
** the schema of iDb is loaded. This is because, at the start of
|
||||
** the sqlite3_exec() call below, SQLite will invoke
|
||||
** sqlite3BtreeEnterAll(). If all mutexes are not already held, the
|
||||
** iDb mutex may be temporarily released to avoid deadlock. If
|
||||
** this happens, then some other thread may delete the in-memory
|
||||
** schema of database iDb before the SQL statement runs. The schema
|
||||
** will not be reloaded becuase the db->init.busy flag is set. This
|
||||
** can result in a "no such table: sqlite_master" or "malformed
|
||||
** database schema" error being returned to the user.
|
||||
*/
|
||||
assert( sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) );
|
||||
sqlite3BtreeEnterAll(db);
|
||||
if( pOp->p2 || DbHasProperty(db, iDb, DB_SchemaLoaded) ){
|
||||
assert( DbHasProperty(db, iDb, DB_SchemaLoaded) );
|
||||
/* Used to be a conditional */ {
|
||||
zMaster = SCHEMA_TABLE(iDb);
|
||||
initData.db = db;
|
||||
initData.iDb = pOp->p1;
|
||||
@@ -4652,7 +4670,6 @@ case OP_ParseSchema: {
|
||||
db->init.busy = 0;
|
||||
}
|
||||
}
|
||||
sqlite3BtreeLeaveAll(db);
|
||||
if( rc==SQLITE_NOMEM ){
|
||||
goto no_mem;
|
||||
}
|
||||
@@ -4753,7 +4770,7 @@ case OP_IntegrityCk: {
|
||||
}
|
||||
aRoot[j] = 0;
|
||||
assert( pOp->p5<db->nDb );
|
||||
assert( (p->btreeMask & (1<<pOp->p5))!=0 );
|
||||
assert( (p->btreeMask & (((yDbMask)1)<<pOp->p5))!=0 );
|
||||
z = sqlite3BtreeIntegrityCheck(db->aDb[pOp->p5].pBt, aRoot, nRoot,
|
||||
(int)pnErr->u.i, &nErr);
|
||||
sqlite3DbFree(db, aRoot);
|
||||
@@ -5181,7 +5198,9 @@ case OP_AggStep: {
|
||||
sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3_value_text(&ctx.s));
|
||||
rc = ctx.isError;
|
||||
}
|
||||
|
||||
sqlite3VdbeMemRelease(&ctx.s);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5215,13 +5234,36 @@ case OP_AggFinal: {
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
/* Opcode: Checkpoint P1 * * * *
|
||||
/* Opcode: Checkpoint P1 P2 P3 * *
|
||||
**
|
||||
** Checkpoint database P1. This is a no-op if P1 is not currently in
|
||||
** WAL mode.
|
||||
** WAL mode. Parameter P2 is one of SQLITE_CHECKPOINT_PASSIVE, FULL
|
||||
** or RESTART. Write 1 or 0 into mem[P3] if the checkpoint returns
|
||||
** SQLITE_BUSY or not, respectively. Write the number of pages in the
|
||||
** WAL after the checkpoint into mem[P3+1] and the number of pages
|
||||
** in the WAL that have been checkpointed after the checkpoint
|
||||
** completes into mem[P3+2]. However on an error, mem[P3+1] and
|
||||
** mem[P3+2] are initialized to -1.
|
||||
*/
|
||||
case OP_Checkpoint: {
|
||||
rc = sqlite3Checkpoint(db, pOp->p1);
|
||||
int i; /* Loop counter */
|
||||
int aRes[3]; /* Results */
|
||||
Mem *pMem; /* Write results here */
|
||||
|
||||
aRes[0] = 0;
|
||||
aRes[1] = aRes[2] = -1;
|
||||
assert( pOp->p2==SQLITE_CHECKPOINT_PASSIVE
|
||||
|| pOp->p2==SQLITE_CHECKPOINT_FULL
|
||||
|| pOp->p2==SQLITE_CHECKPOINT_RESTART
|
||||
);
|
||||
rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]);
|
||||
if( rc==SQLITE_BUSY ){
|
||||
rc = SQLITE_OK;
|
||||
aRes[0] = 1;
|
||||
}
|
||||
for(i=0, pMem = &aMem[pOp->p3]; i<3; i++, pMem++){
|
||||
sqlite3VdbeMemSetInt64(pMem, (i64)aRes[i]);
|
||||
}
|
||||
break;
|
||||
};
|
||||
#endif
|
||||
@@ -5256,25 +5298,6 @@ case OP_JournalMode: { /* out2-prerelease */
|
||||
);
|
||||
assert( pOp->p1>=0 && pOp->p1<db->nDb );
|
||||
|
||||
/* This opcode is used in two places: PRAGMA journal_mode and ATTACH.
|
||||
** In PRAGMA journal_mode, the sqlite3VdbeUsesBtree() routine is called
|
||||
** when the statment is prepared and so p->aMutex.nMutex>0. All mutexes
|
||||
** are already acquired. But when used in ATTACH, sqlite3VdbeUsesBtree()
|
||||
** is not called when the statement is prepared because it requires the
|
||||
** iDb index of the database as a parameter, and the database has not
|
||||
** yet been attached so that index is unavailable. We have to wait
|
||||
** until runtime (now) to get the mutex on the newly attached database.
|
||||
** No other mutexes are required by the ATTACH command so this is safe
|
||||
** to do.
|
||||
*/
|
||||
assert( (p->btreeMask & (1<<pOp->p1))!=0 || p->aMutex.nMutex==0 );
|
||||
if( p->aMutex.nMutex==0 ){
|
||||
/* This occurs right after ATTACH. Get a mutex on the newly ATTACHed
|
||||
** database. */
|
||||
sqlite3VdbeUsesBtree(p, pOp->p1);
|
||||
sqlite3VdbeMutexArrayEnter(p);
|
||||
}
|
||||
|
||||
pBt = db->aDb[pOp->p1].pBt;
|
||||
pPager = sqlite3BtreePager(pBt);
|
||||
eOld = sqlite3PagerGetJournalMode(pPager);
|
||||
@@ -5372,7 +5395,7 @@ case OP_IncrVacuum: { /* jump */
|
||||
Btree *pBt;
|
||||
|
||||
assert( pOp->p1>=0 && pOp->p1<db->nDb );
|
||||
assert( (p->btreeMask & (1<<pOp->p1))!=0 );
|
||||
assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 );
|
||||
pBt = db->aDb[pOp->p1].pBt;
|
||||
rc = sqlite3BtreeIncrVacuum(pBt);
|
||||
if( rc==SQLITE_DONE ){
|
||||
@@ -5421,7 +5444,7 @@ case OP_TableLock: {
|
||||
if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommitted) ){
|
||||
int p1 = pOp->p1;
|
||||
assert( p1>=0 && p1<db->nDb );
|
||||
assert( (p->btreeMask & (1<<p1))!=0 );
|
||||
assert( (p->btreeMask & (((yDbMask)1)<<p1))!=0 );
|
||||
assert( isWriteLock==0 || isWriteLock==1 );
|
||||
rc = sqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock);
|
||||
if( (rc&0xFF)==SQLITE_LOCKED ){
|
||||
@@ -5910,13 +5933,15 @@ vdbe_error_halt:
|
||||
sqlite3VdbeHalt(p);
|
||||
if( rc==SQLITE_IOERR_NOMEM ) db->mallocFailed = 1;
|
||||
rc = SQLITE_ERROR;
|
||||
if( resetSchemaOnFault ) sqlite3ResetInternalSchema(db, 0);
|
||||
if( resetSchemaOnFault>0 ){
|
||||
sqlite3ResetInternalSchema(db, resetSchemaOnFault-1);
|
||||
}
|
||||
|
||||
/* This is the only way out of this procedure. We have to
|
||||
** release the mutexes on btrees that were acquired at the
|
||||
** top. */
|
||||
vdbe_return:
|
||||
sqlite3BtreeMutexArrayLeave(&p->aMutex);
|
||||
sqlite3VdbeLeave(p);
|
||||
return rc;
|
||||
|
||||
/* Jump to here if a string or blob larger than SQLITE_MAX_LENGTH
|
||||
|
||||
@@ -108,7 +108,7 @@ typedef struct VdbeOpList VdbeOpList;
|
||||
#define P4_KEYINFO (-6) /* P4 is a pointer to a KeyInfo structure */
|
||||
#define P4_VDBEFUNC (-7) /* P4 is a pointer to a VdbeFunc structure */
|
||||
#define P4_MEM (-8) /* P4 is a pointer to a Mem* structure */
|
||||
#define P4_TRANSIENT (-9) /* P4 is a pointer to a transient string */
|
||||
#define P4_TRANSIENT 0 /* P4 is a pointer to a transient string */
|
||||
#define P4_VTAB (-10) /* P4 is a pointer to an sqlite3_vtab structure */
|
||||
#define P4_MPRINTF (-11) /* P4 is a string obtained from sqlite3_mprintf() */
|
||||
#define P4_REAL (-12) /* P4 is a 64-bit floating point value */
|
||||
|
||||
@@ -302,10 +302,10 @@ struct Vdbe {
|
||||
u8 readOnly; /* True for read-only statements */
|
||||
u8 isPrepareV2; /* True if prepared with prepare_v2() */
|
||||
int nChange; /* Number of db changes made since last reset */
|
||||
int btreeMask; /* Bitmask of db->aDb[] entries referenced */
|
||||
yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */
|
||||
yDbMask lockMask; /* Subset of btreeMask that requires a lock */
|
||||
int iStatement; /* Statement number (or 0 if has not opened stmt) */
|
||||
int aCounter[3]; /* Counters used by sqlite3_stmt_status() */
|
||||
BtreeMutexArray aMutex; /* An array of Btree used here and needing locks */
|
||||
#ifndef SQLITE_OMIT_TRACE
|
||||
i64 startTime; /* Time when query started - used for profiling */
|
||||
#endif
|
||||
@@ -388,6 +388,14 @@ void sqlite3VdbeFrameDelete(VdbeFrame*);
|
||||
int sqlite3VdbeFrameRestore(VdbeFrame *);
|
||||
void sqlite3VdbeMemStoreType(Mem *pMem);
|
||||
|
||||
#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0
|
||||
void sqlite3VdbeEnter(Vdbe*);
|
||||
void sqlite3VdbeLeave(Vdbe*);
|
||||
#else
|
||||
# define sqlite3VdbeEnter(X)
|
||||
# define sqlite3VdbeLeave(X)
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
void sqlite3VdbeMemPrepareToChange(Vdbe*,Mem*);
|
||||
#endif
|
||||
@@ -398,12 +406,6 @@ int sqlite3VdbeCheckFk(Vdbe *, int);
|
||||
# define sqlite3VdbeCheckFk(p,i) 0
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_OMIT_SHARED_CACHE
|
||||
void sqlite3VdbeMutexArrayEnter(Vdbe *p);
|
||||
#else
|
||||
# define sqlite3VdbeMutexArrayEnter(p)
|
||||
#endif
|
||||
|
||||
int sqlite3VdbeMemTranslate(Mem*, u8);
|
||||
#ifdef SQLITE_DEBUG
|
||||
void sqlite3VdbePrintSql(Vdbe*);
|
||||
|
||||
@@ -682,13 +682,11 @@ int sqlite3_data_count(sqlite3_stmt *pStmt){
|
||||
*/
|
||||
static Mem *columnMem(sqlite3_stmt *pStmt, int i){
|
||||
Vdbe *pVm;
|
||||
int vals;
|
||||
Mem *pOut;
|
||||
|
||||
pVm = (Vdbe *)pStmt;
|
||||
if( pVm && pVm->pResultSet!=0 && i<pVm->nResColumn && i>=0 ){
|
||||
sqlite3_mutex_enter(pVm->db->mutex);
|
||||
vals = sqlite3_data_count(pStmt);
|
||||
pOut = &pVm->pResultSet[i];
|
||||
}else{
|
||||
/* If the value passed as the second argument is out of range, return
|
||||
@@ -706,7 +704,11 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){
|
||||
#if defined(SQLITE_DEBUG) && defined(__GNUC__)
|
||||
__attribute__((aligned(8)))
|
||||
#endif
|
||||
= {0, "", (double)0, {0}, 0, MEM_Null, SQLITE_NULL, 0, 0, 0 };
|
||||
= {0, "", (double)0, {0}, 0, MEM_Null, SQLITE_NULL, 0,
|
||||
#ifdef SQLITE_DEBUG
|
||||
0, 0, /* pScopyFrom, pFiller */
|
||||
#endif
|
||||
0, 0 };
|
||||
|
||||
if( pVm && ALWAYS(pVm->db) ){
|
||||
sqlite3_mutex_enter(pVm->db->mutex);
|
||||
|
||||
@@ -157,6 +157,12 @@ int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){
|
||||
pOp->p4.p = 0;
|
||||
pOp->p4type = P4_NOTUSED;
|
||||
p->expired = 0;
|
||||
if( op==OP_ParseSchema ){
|
||||
/* Any program that uses the OP_ParseSchema opcode needs to lock
|
||||
** all btrees. */
|
||||
int j;
|
||||
for(j=0; j<p->db->nDb; j++) sqlite3VdbeUsesBtree(p, j);
|
||||
}
|
||||
#ifdef SQLITE_DEBUG
|
||||
pOp->zComment = 0;
|
||||
if( sqlite3VdbeAddopTrace ) sqlite3VdbePrintOp(0, i, &p->aOp[i]);
|
||||
@@ -457,7 +463,7 @@ VdbeOp *sqlite3VdbeTakeOpArray(Vdbe *p, int *pnOp, int *pnMaxArg){
|
||||
assert( aOp && !p->db->mallocFailed );
|
||||
|
||||
/* Check that sqlite3VdbeUsesBtree() was not called on this VM */
|
||||
assert( p->aMutex.nMutex==0 );
|
||||
assert( p->btreeMask==0 );
|
||||
|
||||
resolveP2Values(p, pnMaxArg);
|
||||
*pnOp = p->nOp;
|
||||
@@ -559,6 +565,7 @@ void sqlite3VdbeChangeP5(Vdbe *p, u8 val){
|
||||
** the address of the next instruction to be coded.
|
||||
*/
|
||||
void sqlite3VdbeJumpHere(Vdbe *p, int addr){
|
||||
assert( addr>=0 );
|
||||
sqlite3VdbeChangeP2(p, addr, p->nOp);
|
||||
}
|
||||
|
||||
@@ -944,22 +951,80 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){
|
||||
/*
|
||||
** Declare to the Vdbe that the BTree object at db->aDb[i] is used.
|
||||
**
|
||||
** The prepared statement has to know in advance which Btree objects
|
||||
** will be used so that it can acquire mutexes on them all in sorted
|
||||
** order (via sqlite3VdbeMutexArrayEnter(). Mutexes are acquired
|
||||
** in order (and released in reverse order) to avoid deadlocks.
|
||||
** The prepared statements need to know in advance the complete set of
|
||||
** attached databases that they will be using. A mask of these databases
|
||||
** is maintained in p->btreeMask and is used for locking and other purposes.
|
||||
*/
|
||||
void sqlite3VdbeUsesBtree(Vdbe *p, int i){
|
||||
int mask;
|
||||
assert( i>=0 && i<p->db->nDb && i<sizeof(u32)*8 );
|
||||
assert( i>=0 && i<p->db->nDb && i<(int)sizeof(yDbMask)*8 );
|
||||
assert( i<(int)sizeof(p->btreeMask)*8 );
|
||||
mask = ((u32)1)<<i;
|
||||
if( (p->btreeMask & mask)==0 ){
|
||||
p->btreeMask |= mask;
|
||||
sqlite3BtreeMutexArrayInsert(&p->aMutex, p->db->aDb[i].pBt);
|
||||
p->btreeMask |= ((yDbMask)1)<<i;
|
||||
if( i!=1 && sqlite3BtreeSharable(p->db->aDb[i].pBt) ){
|
||||
p->lockMask |= ((yDbMask)1)<<i;
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0
|
||||
/*
|
||||
** If SQLite is compiled to support shared-cache mode and to be threadsafe,
|
||||
** this routine obtains the mutex associated with each BtShared structure
|
||||
** that may be accessed by the VM passed as an argument. In doing so it also
|
||||
** sets the BtShared.db member of each of the BtShared structures, ensuring
|
||||
** that the correct busy-handler callback is invoked if required.
|
||||
**
|
||||
** If SQLite is not threadsafe but does support shared-cache mode, then
|
||||
** sqlite3BtreeEnter() is invoked to set the BtShared.db variables
|
||||
** of all of BtShared structures accessible via the database handle
|
||||
** associated with the VM.
|
||||
**
|
||||
** If SQLite is not threadsafe and does not support shared-cache mode, this
|
||||
** function is a no-op.
|
||||
**
|
||||
** The p->btreeMask field is a bitmask of all btrees that the prepared
|
||||
** statement p will ever use. Let N be the number of bits in p->btreeMask
|
||||
** corresponding to btrees that use shared cache. Then the runtime of
|
||||
** this routine is N*N. But as N is rarely more than 1, this should not
|
||||
** be a problem.
|
||||
*/
|
||||
void sqlite3VdbeEnter(Vdbe *p){
|
||||
int i;
|
||||
yDbMask mask;
|
||||
sqlite3 *db;
|
||||
Db *aDb;
|
||||
int nDb;
|
||||
if( p->lockMask==0 ) return; /* The common case */
|
||||
db = p->db;
|
||||
aDb = db->aDb;
|
||||
nDb = db->nDb;
|
||||
for(i=0, mask=1; i<nDb; i++, mask += mask){
|
||||
if( i!=1 && (mask & p->lockMask)!=0 && ALWAYS(aDb[i].pBt!=0) ){
|
||||
sqlite3BtreeEnter(aDb[i].pBt);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0
|
||||
/*
|
||||
** Unlock all of the btrees previously locked by a call to sqlite3VdbeEnter().
|
||||
*/
|
||||
void sqlite3VdbeLeave(Vdbe *p){
|
||||
int i;
|
||||
yDbMask mask;
|
||||
sqlite3 *db;
|
||||
Db *aDb;
|
||||
int nDb;
|
||||
if( p->lockMask==0 ) return; /* The common case */
|
||||
db = p->db;
|
||||
aDb = db->aDb;
|
||||
nDb = db->nDb;
|
||||
for(i=0, mask=1; i<nDb; i++, mask += mask){
|
||||
if( i!=1 && (mask & p->lockMask)!=0 && ALWAYS(aDb[i].pBt!=0) ){
|
||||
sqlite3BtreeLeave(aDb[i].pBt);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(VDBE_PROFILE) || defined(SQLITE_DEBUG)
|
||||
/*
|
||||
@@ -1517,7 +1582,7 @@ int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){
|
||||
*/
|
||||
static void closeAllCursors(Vdbe *p){
|
||||
if( p->pFrame ){
|
||||
VdbeFrame *pFrame = p->pFrame;
|
||||
VdbeFrame *pFrame;
|
||||
for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent);
|
||||
sqlite3VdbeFrameRestore(pFrame);
|
||||
}
|
||||
@@ -1703,7 +1768,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
|
||||
for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
|
||||
Btree *pBt = db->aDb[i].pBt;
|
||||
if( pBt ){
|
||||
rc = sqlite3BtreeCommitPhaseTwo(pBt);
|
||||
rc = sqlite3BtreeCommitPhaseTwo(pBt, 0);
|
||||
}
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -1835,7 +1900,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
|
||||
for(i=0; i<db->nDb; i++){
|
||||
Btree *pBt = db->aDb[i].pBt;
|
||||
if( pBt ){
|
||||
sqlite3BtreeCommitPhaseTwo(pBt);
|
||||
sqlite3BtreeCommitPhaseTwo(pBt, 1);
|
||||
}
|
||||
}
|
||||
sqlite3EndBenignMalloc();
|
||||
@@ -1958,33 +2023,6 @@ int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** If SQLite is compiled to support shared-cache mode and to be threadsafe,
|
||||
** this routine obtains the mutex associated with each BtShared structure
|
||||
** that may be accessed by the VM passed as an argument. In doing so it
|
||||
** sets the BtShared.db member of each of the BtShared structures, ensuring
|
||||
** that the correct busy-handler callback is invoked if required.
|
||||
**
|
||||
** If SQLite is not threadsafe but does support shared-cache mode, then
|
||||
** sqlite3BtreeEnterAll() is invoked to set the BtShared.db variables
|
||||
** of all of BtShared structures accessible via the database handle
|
||||
** associated with the VM. Of course only a subset of these structures
|
||||
** will be accessed by the VM, and we could use Vdbe.btreeMask to figure
|
||||
** that subset out, but there is no advantage to doing so.
|
||||
**
|
||||
** If SQLite is not threadsafe and does not support shared-cache mode, this
|
||||
** function is a no-op.
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_SHARED_CACHE
|
||||
void sqlite3VdbeMutexArrayEnter(Vdbe *p){
|
||||
#if SQLITE_THREADSAFE
|
||||
sqlite3BtreeMutexArrayEnter(&p->aMutex);
|
||||
#else
|
||||
sqlite3BtreeEnterAll(p->db);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** This function is called when a transaction opened by the database
|
||||
** handle associated with the VM passed as an argument is about to be
|
||||
@@ -2057,7 +2095,7 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
int isSpecialError; /* Set to true if a 'special' error */
|
||||
|
||||
/* Lock all btrees used by the statement */
|
||||
sqlite3VdbeMutexArrayEnter(p);
|
||||
sqlite3VdbeEnter(p);
|
||||
|
||||
/* Check for one of the special errors */
|
||||
mrc = p->rc & 0xff;
|
||||
@@ -2108,17 +2146,22 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
&& db->writeVdbeCnt==(p->readOnly==0)
|
||||
){
|
||||
if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){
|
||||
if( sqlite3VdbeCheckFk(p, 1) ){
|
||||
sqlite3BtreeMutexArrayLeave(&p->aMutex);
|
||||
return SQLITE_ERROR;
|
||||
rc = sqlite3VdbeCheckFk(p, 1);
|
||||
if( rc!=SQLITE_OK ){
|
||||
if( NEVER(p->readOnly) ){
|
||||
sqlite3VdbeLeave(p);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
rc = SQLITE_CONSTRAINT;
|
||||
}else{
|
||||
/* The auto-commit flag is true, the vdbe program was successful
|
||||
** or hit an 'OR FAIL' constraint and there are no deferred foreign
|
||||
** key constraints to hold up the transaction. This means a commit
|
||||
** is required. */
|
||||
rc = vdbeCommit(db, p);
|
||||
}
|
||||
/* The auto-commit flag is true, the vdbe program was successful
|
||||
** or hit an 'OR FAIL' constraint and there are no deferred foreign
|
||||
** key constraints to hold up the transaction. This means a commit
|
||||
** is required. */
|
||||
rc = vdbeCommit(db, p);
|
||||
if( rc==SQLITE_BUSY ){
|
||||
sqlite3BtreeMutexArrayLeave(&p->aMutex);
|
||||
if( rc==SQLITE_BUSY && p->readOnly ){
|
||||
sqlite3VdbeLeave(p);
|
||||
return SQLITE_BUSY;
|
||||
}else if( rc!=SQLITE_OK ){
|
||||
p->rc = rc;
|
||||
@@ -2185,12 +2228,12 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
|
||||
/* Rollback or commit any schema changes that occurred. */
|
||||
if( p->rc!=SQLITE_OK && db->flags&SQLITE_InternChanges ){
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
db->flags = (db->flags | SQLITE_InternChanges);
|
||||
}
|
||||
|
||||
/* Release the locks */
|
||||
sqlite3BtreeMutexArrayLeave(&p->aMutex);
|
||||
sqlite3VdbeLeave(p);
|
||||
}
|
||||
|
||||
/* We have successfully halted and closed the VM. Record this fact. */
|
||||
@@ -2216,7 +2259,7 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
}
|
||||
|
||||
assert( db->activeVdbeCnt>0 || db->autoCommit==0 || db->nStatement==0 );
|
||||
return SQLITE_OK;
|
||||
return (p->rc==SQLITE_BUSY ? SQLITE_BUSY : SQLITE_OK);
|
||||
}
|
||||
|
||||
|
||||
@@ -2492,7 +2535,13 @@ u32 sqlite3VdbeSerialType(Mem *pMem, int file_format){
|
||||
if( file_format>=4 && (i&1)==i ){
|
||||
return 8+(u32)i;
|
||||
}
|
||||
u = i<0 ? -i : i;
|
||||
if( i<0 ){
|
||||
if( i<(-MAX_6BYTE) ) return 6;
|
||||
/* Previous test prevents: u = -(-9223372036854775808) */
|
||||
u = -i;
|
||||
}else{
|
||||
u = i;
|
||||
}
|
||||
if( u<=127 ) return 1;
|
||||
if( u<=32767 ) return 2;
|
||||
if( u<=8388607 ) return 3;
|
||||
|
||||
@@ -266,6 +266,7 @@ int sqlite3_blob_open(
|
||||
/* Configure the OP_VerifyCookie */
|
||||
sqlite3VdbeChangeP1(v, 1, iDb);
|
||||
sqlite3VdbeChangeP2(v, 1, pTab->pSchema->schema_cookie);
|
||||
sqlite3VdbeChangeP3(v, 1, pTab->pSchema->iGeneration);
|
||||
|
||||
/* Make sure a mutex is held on the table to be accessed */
|
||||
sqlite3VdbeUsesBtree(v, iDb);
|
||||
|
||||
@@ -367,7 +367,7 @@ i64 sqlite3VdbeIntValue(Mem *pMem){
|
||||
}else if( flags & MEM_Real ){
|
||||
return doubleToInt64(pMem->r);
|
||||
}else if( flags & (MEM_Str|MEM_Blob) ){
|
||||
i64 value;
|
||||
i64 value = 0;
|
||||
assert( pMem->z || pMem->n==0 );
|
||||
testcase( pMem->z==0 );
|
||||
sqlite3Atoi64(pMem->z, &value, pMem->n, pMem->enc);
|
||||
@@ -1077,11 +1077,19 @@ int sqlite3ValueFromExpr(
|
||||
/* This branch happens for multiple negative signs. Ex: -(-5) */
|
||||
if( SQLITE_OK==sqlite3ValueFromExpr(db,pExpr->pLeft,enc,affinity,&pVal) ){
|
||||
sqlite3VdbeMemNumerify(pVal);
|
||||
pVal->u.i = -1 * pVal->u.i;
|
||||
/* (double)-1 In case of SQLITE_OMIT_FLOATING_POINT... */
|
||||
pVal->r = (double)-1 * pVal->r;
|
||||
if( pVal->u.i==SMALLEST_INT64 ){
|
||||
pVal->flags &= MEM_Int;
|
||||
pVal->flags |= MEM_Real;
|
||||
pVal->r = (double)LARGEST_INT64;
|
||||
}else{
|
||||
pVal->u.i = -pVal->u.i;
|
||||
}
|
||||
pVal->r = -pVal->r;
|
||||
sqlite3ValueApplyAffinity(pVal, affinity, enc);
|
||||
}
|
||||
}else if( op==TK_NULL ){
|
||||
pVal = sqlite3ValueNew(db);
|
||||
if( pVal==0 ) goto no_mem;
|
||||
}
|
||||
#ifndef SQLITE_OMIT_BLOB_LITERAL
|
||||
else if( op==TK_BLOB ){
|
||||
|
||||
@@ -85,7 +85,7 @@ char *sqlite3VdbeExpandSql(
|
||||
const char *zStart = zRawSql;
|
||||
while( *(zRawSql++)!='\n' && *zRawSql );
|
||||
sqlite3StrAccumAppend(&out, "-- ", 3);
|
||||
sqlite3StrAccumAppend(&out, zStart, zRawSql-zStart);
|
||||
sqlite3StrAccumAppend(&out, zStart, (int)(zRawSql-zStart));
|
||||
}
|
||||
}else{
|
||||
while( zRawSql[0] ){
|
||||
|
||||
@@ -48,7 +48,7 @@ static int createModule(
|
||||
if( pDel==pMod ){
|
||||
db->mallocFailed = 1;
|
||||
}
|
||||
sqlite3ResetInternalSchema(db, 0);
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
}else if( xDestroy ){
|
||||
xDestroy(pAux);
|
||||
}
|
||||
@@ -145,10 +145,9 @@ static VTable *vtabDisconnectAll(sqlite3 *db, Table *p){
|
||||
** that contains table p is held by the caller. See header comments
|
||||
** above function sqlite3VtabUnlockList() for an explanation of why
|
||||
** this makes it safe to access the sqlite3.pDisconnect list of any
|
||||
** database connection that may have an entry in the p->pVTable list. */
|
||||
assert( db==0 ||
|
||||
sqlite3BtreeHoldsMutex(db->aDb[sqlite3SchemaToIndex(db, p->pSchema)].pBt)
|
||||
);
|
||||
** database connection that may have an entry in the p->pVTable list.
|
||||
*/
|
||||
assert( db==0 || sqlite3SchemaMutexHeld(db, 0, p->pSchema) );
|
||||
|
||||
while( pVTable ){
|
||||
sqlite3 *db2 = pVTable->db;
|
||||
@@ -372,7 +371,7 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
|
||||
|
||||
sqlite3VdbeAddOp2(v, OP_Expire, 0, 0);
|
||||
zWhere = sqlite3MPrintf(db, "name='%q' AND type='table'", pTab->zName);
|
||||
sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 1, 0, zWhere, P4_DYNAMIC);
|
||||
sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 0, 0, zWhere, P4_DYNAMIC);
|
||||
sqlite3VdbeAddOp4(v, OP_VCreate, iDb, 0, 0,
|
||||
pTab->zName, sqlite3Strlen30(pTab->zName) + 1);
|
||||
}
|
||||
@@ -387,6 +386,7 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
|
||||
Schema *pSchema = pTab->pSchema;
|
||||
const char *zName = pTab->zName;
|
||||
int nName = sqlite3Strlen30(zName);
|
||||
assert( sqlite3SchemaMutexHeld(db, 0, pSchema) );
|
||||
pOld = sqlite3HashInsert(&pSchema->tblHash, zName, nName, pTab);
|
||||
if( pOld ){
|
||||
db->mallocFailed = 1;
|
||||
|
||||
@@ -1558,6 +1558,34 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Attempt to obtain the exclusive WAL lock defined by parameters lockIdx and
|
||||
** n. If the attempt fails and parameter xBusy is not NULL, then it is a
|
||||
** busy-handler function. Invoke it and retry the lock until either the
|
||||
** lock is successfully obtained or the busy-handler returns 0.
|
||||
*/
|
||||
static int walBusyLock(
|
||||
Wal *pWal, /* WAL connection */
|
||||
int (*xBusy)(void*), /* Function to call when busy */
|
||||
void *pBusyArg, /* Context argument for xBusyHandler */
|
||||
int lockIdx, /* Offset of first byte to lock */
|
||||
int n /* Number of bytes to lock */
|
||||
){
|
||||
int rc;
|
||||
do {
|
||||
rc = walLockExclusive(pWal, lockIdx, n);
|
||||
}while( xBusy && rc==SQLITE_BUSY && xBusy(pBusyArg) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** The cache of the wal-index header must be valid to call this function.
|
||||
** Return the page-size in bytes used by the database.
|
||||
*/
|
||||
static int walPagesize(Wal *pWal){
|
||||
return (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16);
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy as much content as we can from the WAL back into the database file
|
||||
** in response to an sqlite3_wal_checkpoint() request or the equivalent.
|
||||
@@ -1591,8 +1619,10 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){
|
||||
*/
|
||||
static int walCheckpoint(
|
||||
Wal *pWal, /* Wal connection */
|
||||
int eMode, /* One of PASSIVE, FULL or RESTART */
|
||||
int (*xBusyCall)(void*), /* Function to call when busy */
|
||||
void *pBusyArg, /* Context argument for xBusyHandler */
|
||||
int sync_flags, /* Flags for OsSync() (or 0) */
|
||||
int nBuf, /* Size of zBuf in bytes */
|
||||
u8 *zBuf /* Temporary buffer to use */
|
||||
){
|
||||
int rc; /* Return code */
|
||||
@@ -1604,8 +1634,9 @@ static int walCheckpoint(
|
||||
u32 mxPage; /* Max database page to write */
|
||||
int i; /* Loop counter */
|
||||
volatile WalCkptInfo *pInfo; /* The checkpoint status information */
|
||||
int (*xBusy)(void*) = 0; /* Function to call when waiting for locks */
|
||||
|
||||
szPage = (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16);
|
||||
szPage = walPagesize(pWal);
|
||||
testcase( szPage<=32768 );
|
||||
testcase( szPage>=65536 );
|
||||
pInfo = walCkptInfo(pWal);
|
||||
@@ -1618,11 +1649,7 @@ static int walCheckpoint(
|
||||
}
|
||||
assert( pIter );
|
||||
|
||||
/*** TODO: Move this test out to the caller. Make it an assert() here ***/
|
||||
if( szPage!=nBuf ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
goto walcheckpoint_out;
|
||||
}
|
||||
if( eMode!=SQLITE_CHECKPOINT_PASSIVE ) xBusy = xBusyCall;
|
||||
|
||||
/* Compute in mxSafeFrame the index of the last frame of the WAL that is
|
||||
** safe to write into the database. Frames beyond mxSafeFrame might
|
||||
@@ -1633,14 +1660,15 @@ static int walCheckpoint(
|
||||
mxPage = pWal->hdr.nPage;
|
||||
for(i=1; i<WAL_NREADER; i++){
|
||||
u32 y = pInfo->aReadMark[i];
|
||||
if( mxSafeFrame>=y ){
|
||||
if( mxSafeFrame>y ){
|
||||
assert( y<=pWal->hdr.mxFrame );
|
||||
rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
|
||||
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(i), 1);
|
||||
if( rc==SQLITE_OK ){
|
||||
pInfo->aReadMark[i] = READMARK_NOT_USED;
|
||||
walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
|
||||
}else if( rc==SQLITE_BUSY ){
|
||||
mxSafeFrame = y;
|
||||
xBusy = 0;
|
||||
}else{
|
||||
goto walcheckpoint_out;
|
||||
}
|
||||
@@ -1648,7 +1676,7 @@ static int walCheckpoint(
|
||||
}
|
||||
|
||||
if( pInfo->nBackfill<mxSafeFrame
|
||||
&& (rc = walLockExclusive(pWal, WAL_READ_LOCK(0), 1))==SQLITE_OK
|
||||
&& (rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(0), 1))==SQLITE_OK
|
||||
){
|
||||
i64 nSize; /* Current size of database file */
|
||||
u32 nBackfill = pInfo->nBackfill;
|
||||
@@ -1701,13 +1729,32 @@ static int walCheckpoint(
|
||||
|
||||
/* Release the reader lock held while backfilling */
|
||||
walUnlockExclusive(pWal, WAL_READ_LOCK(0), 1);
|
||||
}else if( rc==SQLITE_BUSY ){
|
||||
}
|
||||
|
||||
if( rc==SQLITE_BUSY ){
|
||||
/* Reset the return code so as not to report a checkpoint failure
|
||||
** just because active readers prevent any backfill.
|
||||
*/
|
||||
** just because there are active readers. */
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
|
||||
/* If this is an SQLITE_CHECKPOINT_RESTART operation, and the entire wal
|
||||
** file has been copied into the database file, then block until all
|
||||
** readers have finished using the wal file. This ensures that the next
|
||||
** process to write to the database restarts the wal file.
|
||||
*/
|
||||
if( rc==SQLITE_OK && eMode!=SQLITE_CHECKPOINT_PASSIVE ){
|
||||
assert( pWal->writeLock );
|
||||
if( pInfo->nBackfill<pWal->hdr.mxFrame ){
|
||||
rc = SQLITE_BUSY;
|
||||
}else if( eMode==SQLITE_CHECKPOINT_RESTART ){
|
||||
assert( mxSafeFrame==pWal->hdr.mxFrame );
|
||||
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(1), WAL_NREADER-1);
|
||||
if( rc==SQLITE_OK ){
|
||||
walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walcheckpoint_out:
|
||||
walIteratorFree(pIter);
|
||||
return rc;
|
||||
@@ -1739,7 +1786,9 @@ int sqlite3WalClose(
|
||||
if( pWal->exclusiveMode==WAL_NORMAL_MODE ){
|
||||
pWal->exclusiveMode = WAL_EXCLUSIVE_MODE;
|
||||
}
|
||||
rc = sqlite3WalCheckpoint(pWal, sync_flags, nBuf, zBuf);
|
||||
rc = sqlite3WalCheckpoint(
|
||||
pWal, SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0
|
||||
);
|
||||
if( rc==SQLITE_OK ){
|
||||
isDelete = 1;
|
||||
}
|
||||
@@ -1951,10 +2000,31 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
|
||||
|
||||
assert( pWal->readLock<0 ); /* Not currently locked */
|
||||
|
||||
/* Take steps to avoid spinning forever if there is a protocol error. */
|
||||
/* Take steps to avoid spinning forever if there is a protocol error.
|
||||
**
|
||||
** Circumstances that cause a RETRY should only last for the briefest
|
||||
** instances of time. No I/O or other system calls are done while the
|
||||
** locks are held, so the locks should not be held for very long. But
|
||||
** if we are unlucky, another process that is holding a lock might get
|
||||
** paged out or take a page-fault that is time-consuming to resolve,
|
||||
** during the few nanoseconds that it is holding the lock. In that case,
|
||||
** it might take longer than normal for the lock to free.
|
||||
**
|
||||
** After 5 RETRYs, we begin calling sqlite3OsSleep(). The first few
|
||||
** calls to sqlite3OsSleep() have a delay of 1 microsecond. Really this
|
||||
** is more of a scheduler yield than an actual delay. But on the 10th
|
||||
** an subsequent retries, the delays start becoming longer and longer,
|
||||
** so that on the 100th (and last) RETRY we delay for 21 milliseconds.
|
||||
** The total delay time before giving up is less than 1 second.
|
||||
*/
|
||||
if( cnt>5 ){
|
||||
if( cnt>100 ) return SQLITE_PROTOCOL;
|
||||
sqlite3OsSleep(pWal->pVfs, 1);
|
||||
int nDelay = 1; /* Pause time in microseconds */
|
||||
if( cnt>100 ){
|
||||
VVA_ONLY( pWal->lockError = 1; )
|
||||
return SQLITE_PROTOCOL;
|
||||
}
|
||||
if( cnt>=10 ) nDelay = (cnt-9)*238; /* Max delay 21ms. Total delay 996ms */
|
||||
sqlite3OsSleep(pWal->pVfs, nDelay);
|
||||
}
|
||||
|
||||
if( !useWal ){
|
||||
@@ -2036,22 +2106,9 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
|
||||
mxI = i;
|
||||
}
|
||||
}
|
||||
if( mxI==0 ){
|
||||
/* If we get here, it means that all of the aReadMark[] entries between
|
||||
** 1 and WAL_NREADER-1 are zero. Try to initialize aReadMark[1] to
|
||||
** be mxFrame, then retry.
|
||||
*/
|
||||
rc = walLockExclusive(pWal, WAL_READ_LOCK(1), 1);
|
||||
if( rc==SQLITE_OK ){
|
||||
pInfo->aReadMark[1] = pWal->hdr.mxFrame;
|
||||
walUnlockExclusive(pWal, WAL_READ_LOCK(1), 1);
|
||||
rc = WAL_RETRY;
|
||||
}else if( rc==SQLITE_BUSY ){
|
||||
rc = WAL_RETRY;
|
||||
}
|
||||
return rc;
|
||||
}else{
|
||||
if( mxReadMark < pWal->hdr.mxFrame ){
|
||||
/* There was once an "if" here. The extra "{" is to preserve indentation. */
|
||||
{
|
||||
if( mxReadMark < pWal->hdr.mxFrame || mxI==0 ){
|
||||
for(i=1; i<WAL_NREADER; i++){
|
||||
rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -2064,6 +2121,10 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
|
||||
}
|
||||
}
|
||||
}
|
||||
if( mxI==0 ){
|
||||
assert( rc==SQLITE_BUSY );
|
||||
return WAL_RETRY;
|
||||
}
|
||||
|
||||
rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
|
||||
if( rc ){
|
||||
@@ -2124,6 +2185,10 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
|
||||
do{
|
||||
rc = walTryBeginRead(pWal, pChanged, 0, ++cnt);
|
||||
}while( rc==WAL_RETRY );
|
||||
testcase( (rc&0xff)==SQLITE_BUSY );
|
||||
testcase( (rc&0xff)==SQLITE_IOERR );
|
||||
testcase( rc==SQLITE_PROTOCOL );
|
||||
testcase( rc==SQLITE_OK );
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -2441,6 +2506,8 @@ static int walRestartLog(Wal *pWal){
|
||||
volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
|
||||
assert( pInfo->nBackfill==pWal->hdr.mxFrame );
|
||||
if( pInfo->nBackfill>0 ){
|
||||
u32 salt1;
|
||||
sqlite3_randomness(4, &salt1);
|
||||
rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
|
||||
if( rc==SQLITE_OK ){
|
||||
/* If all readers are using WAL_READ_LOCK(0) (in other words if no
|
||||
@@ -2458,7 +2525,7 @@ static int walRestartLog(Wal *pWal){
|
||||
pWal->nCkpt++;
|
||||
pWal->hdr.mxFrame = 0;
|
||||
sqlite3Put4byte((u8*)&aSalt[0], 1 + sqlite3Get4byte((u8*)&aSalt[0]));
|
||||
sqlite3_randomness(4, &aSalt[1]);
|
||||
aSalt[1] = salt1;
|
||||
walIndexWriteHdr(pWal);
|
||||
pInfo->nBackfill = 0;
|
||||
for(i=1; i<WAL_NREADER; i++) pInfo->aReadMark[i] = READMARK_NOT_USED;
|
||||
@@ -2475,6 +2542,10 @@ static int walRestartLog(Wal *pWal){
|
||||
int notUsed;
|
||||
rc = walTryBeginRead(pWal, ¬Used, 1, ++cnt);
|
||||
}while( rc==WAL_RETRY );
|
||||
assert( (rc&0xff)!=SQLITE_BUSY ); /* BUSY not possible when useWal==1 */
|
||||
testcase( (rc&0xff)==SQLITE_IOERR );
|
||||
testcase( rc==SQLITE_PROTOCOL );
|
||||
testcase( rc==SQLITE_OK );
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -2654,17 +2725,27 @@ int sqlite3WalFrames(
|
||||
**
|
||||
** Obtain a CHECKPOINT lock and then backfill as much information as
|
||||
** we can from WAL into the database.
|
||||
**
|
||||
** If parameter xBusy is not NULL, it is a pointer to a busy-handler
|
||||
** callback. In this case this function runs a blocking checkpoint.
|
||||
*/
|
||||
int sqlite3WalCheckpoint(
|
||||
Wal *pWal, /* Wal connection */
|
||||
int eMode, /* PASSIVE, FULL or RESTART */
|
||||
int (*xBusy)(void*), /* Function to call when busy */
|
||||
void *pBusyArg, /* Context argument for xBusyHandler */
|
||||
int sync_flags, /* Flags to sync db file with (or 0) */
|
||||
int nBuf, /* Size of temporary buffer */
|
||||
u8 *zBuf /* Temporary buffer to use */
|
||||
u8 *zBuf, /* Temporary buffer to use */
|
||||
int *pnLog, /* OUT: Number of frames in WAL */
|
||||
int *pnCkpt /* OUT: Number of backfilled frames in WAL */
|
||||
){
|
||||
int rc; /* Return code */
|
||||
int isChanged = 0; /* True if a new wal-index header is loaded */
|
||||
int eMode2 = eMode; /* Mode to pass to walCheckpoint() */
|
||||
|
||||
assert( pWal->ckptLock==0 );
|
||||
assert( pWal->writeLock==0 );
|
||||
|
||||
WALTRACE(("WAL%p: checkpoint begins\n", pWal));
|
||||
rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
|
||||
@@ -2676,11 +2757,45 @@ int sqlite3WalCheckpoint(
|
||||
}
|
||||
pWal->ckptLock = 1;
|
||||
|
||||
/* Copy data from the log to the database file. */
|
||||
rc = walIndexReadHdr(pWal, &isChanged);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = walCheckpoint(pWal, sync_flags, nBuf, zBuf);
|
||||
/* If this is a blocking-checkpoint, then obtain the write-lock as well
|
||||
** to prevent any writers from running while the checkpoint is underway.
|
||||
** This has to be done before the call to walIndexReadHdr() below.
|
||||
**
|
||||
** If the writer lock cannot be obtained, then a passive checkpoint is
|
||||
** run instead. Since the checkpointer is not holding the writer lock,
|
||||
** there is no point in blocking waiting for any readers. Assuming no
|
||||
** other error occurs, this function will return SQLITE_BUSY to the caller.
|
||||
*/
|
||||
if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){
|
||||
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_WRITE_LOCK, 1);
|
||||
if( rc==SQLITE_OK ){
|
||||
pWal->writeLock = 1;
|
||||
}else if( rc==SQLITE_BUSY ){
|
||||
eMode2 = SQLITE_CHECKPOINT_PASSIVE;
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read the wal-index header. */
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = walIndexReadHdr(pWal, &isChanged);
|
||||
}
|
||||
|
||||
/* Copy data from the log to the database file. */
|
||||
if( rc==SQLITE_OK ){
|
||||
if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
}else{
|
||||
rc = walCheckpoint(pWal, eMode2, xBusy, pBusyArg, sync_flags, zBuf);
|
||||
}
|
||||
|
||||
/* If no error occurred, set the output variables. */
|
||||
if( rc==SQLITE_OK || rc==SQLITE_BUSY ){
|
||||
if( pnLog ) *pnLog = (int)pWal->hdr.mxFrame;
|
||||
if( pnCkpt ) *pnCkpt = (int)(walCkptInfo(pWal)->nBackfill);
|
||||
}
|
||||
}
|
||||
|
||||
if( isChanged ){
|
||||
/* If a new wal-index header was loaded before the checkpoint was
|
||||
** performed, then the pager-cache associated with pWal is now
|
||||
@@ -2692,10 +2807,11 @@ int sqlite3WalCheckpoint(
|
||||
}
|
||||
|
||||
/* Release the locks. */
|
||||
sqlite3WalEndWriteTransaction(pWal);
|
||||
walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1);
|
||||
pWal->ckptLock = 0;
|
||||
WALTRACE(("WAL%p: checkpoint %s\n", pWal, rc ? "failed" : "ok"));
|
||||
return rc;
|
||||
return (rc==SQLITE_OK && eMode!=eMode2 ? SQLITE_BUSY : rc);
|
||||
}
|
||||
|
||||
/* Return the value to pass to a sqlite3_wal_hook callback, the
|
||||
|
||||
@@ -20,22 +20,22 @@
|
||||
#include "sqliteInt.h"
|
||||
|
||||
#ifdef SQLITE_OMIT_WAL
|
||||
# define sqlite3WalOpen(x,y,z) 0
|
||||
# define sqlite3WalClose(w,x,y,z) 0
|
||||
# define sqlite3WalBeginReadTransaction(y,z) 0
|
||||
# define sqlite3WalOpen(x,y,z) 0
|
||||
# define sqlite3WalClose(w,x,y,z) 0
|
||||
# define sqlite3WalBeginReadTransaction(y,z) 0
|
||||
# define sqlite3WalEndReadTransaction(z)
|
||||
# define sqlite3WalRead(v,w,x,y,z) 0
|
||||
# define sqlite3WalDbsize(y) 0
|
||||
# define sqlite3WalBeginWriteTransaction(y) 0
|
||||
# define sqlite3WalEndWriteTransaction(x) 0
|
||||
# define sqlite3WalUndo(x,y,z) 0
|
||||
# define sqlite3WalRead(v,w,x,y,z) 0
|
||||
# define sqlite3WalDbsize(y) 0
|
||||
# define sqlite3WalBeginWriteTransaction(y) 0
|
||||
# define sqlite3WalEndWriteTransaction(x) 0
|
||||
# define sqlite3WalUndo(x,y,z) 0
|
||||
# define sqlite3WalSavepoint(y,z)
|
||||
# define sqlite3WalSavepointUndo(y,z) 0
|
||||
# define sqlite3WalFrames(u,v,w,x,y,z) 0
|
||||
# define sqlite3WalCheckpoint(u,v,w,x) 0
|
||||
# define sqlite3WalCallback(z) 0
|
||||
# define sqlite3WalExclusiveMode(y,z) 0
|
||||
# define sqlite3WalHeapMemory(z) 0
|
||||
# define sqlite3WalSavepointUndo(y,z) 0
|
||||
# define sqlite3WalFrames(u,v,w,x,y,z) 0
|
||||
# define sqlite3WalCheckpoint(r,s,t,u,v,w,x,y,z) 0
|
||||
# define sqlite3WalCallback(z) 0
|
||||
# define sqlite3WalExclusiveMode(y,z) 0
|
||||
# define sqlite3WalHeapMemory(z) 0
|
||||
#else
|
||||
|
||||
#define WAL_SAVEPOINT_NDATA 4
|
||||
@@ -86,9 +86,14 @@ int sqlite3WalFrames(Wal *pWal, int, PgHdr *, Pgno, int, int);
|
||||
/* Copy pages from the log to the database file */
|
||||
int sqlite3WalCheckpoint(
|
||||
Wal *pWal, /* Write-ahead log connection */
|
||||
int eMode, /* One of PASSIVE, FULL and RESTART */
|
||||
int (*xBusy)(void*), /* Function to call when busy */
|
||||
void *pBusyArg, /* Context argument for xBusyHandler */
|
||||
int sync_flags, /* Flags to sync db file with (or 0) */
|
||||
int nBuf, /* Size of buffer nBuf */
|
||||
u8 *zBuf /* Temporary buffer to use */
|
||||
u8 *zBuf, /* Temporary buffer to use */
|
||||
int *pnLog, /* OUT: Number of frames in WAL */
|
||||
int *pnCkpt /* OUT: Number of backfilled frames in WAL */
|
||||
);
|
||||
|
||||
/* Return the value to pass to a sqlite3_wal_hook callback, the
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
|
||||
/*
|
||||
** Trace output macros
|
||||
*/
|
||||
@@ -117,6 +118,11 @@ struct WhereTerm {
|
||||
#define TERM_ORINFO 0x10 /* Need to free the WhereTerm.u.pOrInfo object */
|
||||
#define TERM_ANDINFO 0x20 /* Need to free the WhereTerm.u.pAndInfo obj */
|
||||
#define TERM_OR_OK 0x40 /* Used during OR-clause processing */
|
||||
#ifdef SQLITE_ENABLE_STAT2
|
||||
# define TERM_VNULL 0x80 /* Manufactured x>NULL or x<=NULL term */
|
||||
#else
|
||||
# define TERM_VNULL 0x00 /* Disabled if not using stat2 */
|
||||
#endif
|
||||
|
||||
/*
|
||||
** An instance of the following structure holds all information about a
|
||||
@@ -210,6 +216,7 @@ struct WhereCost {
|
||||
#define WO_ISNULL 0x080
|
||||
#define WO_OR 0x100 /* Two or more OR-connected terms */
|
||||
#define WO_AND 0x200 /* Two or more AND-connected terms */
|
||||
#define WO_NOOP 0x800 /* This term does not restrict search space */
|
||||
|
||||
#define WO_ALL 0xfff /* Mask of all possible WO_* values */
|
||||
#define WO_SINGLE 0x0ff /* Mask of all non-compound WO_* values */
|
||||
@@ -392,7 +399,7 @@ static void whereSplit(WhereClause *pWC, Expr *pExpr, int op){
|
||||
*/
|
||||
static Bitmask getMask(WhereMaskSet *pMaskSet, int iCursor){
|
||||
int i;
|
||||
assert( pMaskSet->n<=sizeof(Bitmask)*8 );
|
||||
assert( pMaskSet->n<=(int)sizeof(Bitmask)*8 );
|
||||
for(i=0; i<pMaskSet->n; i++){
|
||||
if( pMaskSet->ix[i]==iCursor ){
|
||||
return ((Bitmask)1)<<i;
|
||||
@@ -1060,7 +1067,7 @@ static void exprAnalyzeOrTerm(
|
||||
}else{
|
||||
sqlite3ExprListDelete(db, pList);
|
||||
}
|
||||
pTerm->eOperator = 0; /* case 1 trumps case 2 */
|
||||
pTerm->eOperator = WO_NOOP; /* case 1 trumps case 2 */
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1324,6 +1331,47 @@ static void exprAnalyze(
|
||||
}
|
||||
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
||||
|
||||
#ifdef SQLITE_ENABLE_STAT2
|
||||
/* When sqlite_stat2 histogram data is available an operator of the
|
||||
** form "x IS NOT NULL" can sometimes be evaluated more efficiently
|
||||
** as "x>NULL" if x is not an INTEGER PRIMARY KEY. So construct a
|
||||
** virtual term of that form.
|
||||
**
|
||||
** Note that the virtual term must be tagged with TERM_VNULL. This
|
||||
** TERM_VNULL tag will suppress the not-null check at the beginning
|
||||
** of the loop. Without the TERM_VNULL flag, the not-null check at
|
||||
** the start of the loop will prevent any results from being returned.
|
||||
*/
|
||||
if( pExpr->op==TK_NOTNULL
|
||||
&& pExpr->pLeft->op==TK_COLUMN
|
||||
&& pExpr->pLeft->iColumn>=0
|
||||
){
|
||||
Expr *pNewExpr;
|
||||
Expr *pLeft = pExpr->pLeft;
|
||||
int idxNew;
|
||||
WhereTerm *pNewTerm;
|
||||
|
||||
pNewExpr = sqlite3PExpr(pParse, TK_GT,
|
||||
sqlite3ExprDup(db, pLeft, 0),
|
||||
sqlite3PExpr(pParse, TK_NULL, 0, 0, 0), 0);
|
||||
|
||||
idxNew = whereClauseInsert(pWC, pNewExpr,
|
||||
TERM_VIRTUAL|TERM_DYNAMIC|TERM_VNULL);
|
||||
if( idxNew ){
|
||||
pNewTerm = &pWC->a[idxNew];
|
||||
pNewTerm->prereqRight = 0;
|
||||
pNewTerm->leftCursor = pLeft->iTable;
|
||||
pNewTerm->u.leftColumn = pLeft->iColumn;
|
||||
pNewTerm->eOperator = WO_GT;
|
||||
pNewTerm->iParent = idxTerm;
|
||||
pTerm = &pWC->a[idxTerm];
|
||||
pTerm->nChild = 1;
|
||||
pTerm->wtFlags |= TERM_COPIED;
|
||||
pNewTerm->prereqAll = pTerm->prereqAll;
|
||||
}
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_STAT2 */
|
||||
|
||||
/* Prevent ON clause terms of a LEFT JOIN from being used to drive
|
||||
** an index for tables to the left of the join.
|
||||
*/
|
||||
@@ -1376,6 +1424,7 @@ static int isSortingIndex(
|
||||
int base, /* Cursor number for the table to be sorted */
|
||||
ExprList *pOrderBy, /* The ORDER BY clause */
|
||||
int nEqCol, /* Number of index columns with == constraints */
|
||||
int wsFlags, /* Index usages flags */
|
||||
int *pbRev /* Set to 1 if ORDER BY is DESC */
|
||||
){
|
||||
int i, j; /* Loop counters */
|
||||
@@ -1481,11 +1530,14 @@ static int isSortingIndex(
|
||||
return 1;
|
||||
}
|
||||
if( pIdx->onError!=OE_None && i==pIdx->nColumn
|
||||
&& (wsFlags & WHERE_COLUMN_NULL)==0
|
||||
&& !referencesOtherTables(pOrderBy, pMaskSet, j, base) ){
|
||||
/* All terms of this index match some prefix of the ORDER BY clause
|
||||
** and the index is UNIQUE and no terms on the tail of the ORDER BY
|
||||
** clause reference other tables in a join. If this is all true then
|
||||
** the order by clause is superfluous. */
|
||||
** the order by clause is superfluous. Not that if the matching
|
||||
** condition is IS NULL then the result is not necessarily unique
|
||||
** even on a UNIQUE index, so disallow those cases. */
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
@@ -1722,7 +1774,7 @@ static void bestAutomaticIndex(
|
||||
pWCEnd = &pWC->a[pWC->nTerm];
|
||||
for(pTerm=pWC->a; pTerm<pWCEnd; pTerm++){
|
||||
if( termCanDriveIndex(pTerm, pSrc, notReady) ){
|
||||
WHERETRACE(("auto-index reduces cost from %.2f to %.2f\n",
|
||||
WHERETRACE(("auto-index reduces cost from %.1f to %.1f\n",
|
||||
pCost->rCost, costTempIdx));
|
||||
pCost->rCost = costTempIdx;
|
||||
pCost->plan.nRow = logN + 1;
|
||||
@@ -1843,7 +1895,7 @@ static void constructAutomaticIndex(
|
||||
idxCols |= cMask;
|
||||
pIdx->aiColumn[n] = pTerm->u.leftColumn;
|
||||
pColl = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pX->pRight);
|
||||
pIdx->azColl[n] = pColl->zName;
|
||||
pIdx->azColl[n] = ALWAYS(pColl) ? pColl->zName : "BINARY";
|
||||
n++;
|
||||
}
|
||||
}
|
||||
@@ -2201,11 +2253,18 @@ static void bestVirtualIndex(
|
||||
/*
|
||||
** Argument pIdx is a pointer to an index structure that has an array of
|
||||
** SQLITE_INDEX_SAMPLES evenly spaced samples of the first indexed column
|
||||
** stored in Index.aSample. The domain of values stored in said column
|
||||
** may be thought of as divided into (SQLITE_INDEX_SAMPLES+1) regions.
|
||||
** Region 0 contains all values smaller than the first sample value. Region
|
||||
** 1 contains values larger than or equal to the value of the first sample,
|
||||
** but smaller than the value of the second. And so on.
|
||||
** stored in Index.aSample. These samples divide the domain of values stored
|
||||
** the index into (SQLITE_INDEX_SAMPLES+1) regions.
|
||||
** Region 0 contains all values less than the first sample value. Region
|
||||
** 1 contains values between the first and second samples. Region 2 contains
|
||||
** values between samples 2 and 3. And so on. Region SQLITE_INDEX_SAMPLES
|
||||
** contains values larger than the last sample.
|
||||
**
|
||||
** If the index contains many duplicates of a single value, then it is
|
||||
** possible that two or more adjacent samples can hold the same value.
|
||||
** When that is the case, the smallest possible region code is returned
|
||||
** when roundUp is false and the largest possible region code is returned
|
||||
** when roundUp is true.
|
||||
**
|
||||
** If successful, this function determines which of the regions value
|
||||
** pVal lies in, sets *piRegion to the region index (a value between 0
|
||||
@@ -2218,8 +2277,10 @@ static int whereRangeRegion(
|
||||
Parse *pParse, /* Database connection */
|
||||
Index *pIdx, /* Index to consider domain of */
|
||||
sqlite3_value *pVal, /* Value to consider */
|
||||
int roundUp, /* Return largest valid region if true */
|
||||
int *piRegion /* OUT: Region of domain in which value lies */
|
||||
){
|
||||
assert( roundUp==0 || roundUp==1 );
|
||||
if( ALWAYS(pVal) ){
|
||||
IndexSample *aSample = pIdx->aSample;
|
||||
int i = 0;
|
||||
@@ -2229,7 +2290,17 @@ static int whereRangeRegion(
|
||||
double r = sqlite3_value_double(pVal);
|
||||
for(i=0; i<SQLITE_INDEX_SAMPLES; i++){
|
||||
if( aSample[i].eType==SQLITE_NULL ) continue;
|
||||
if( aSample[i].eType>=SQLITE_TEXT || aSample[i].u.r>r ) break;
|
||||
if( aSample[i].eType>=SQLITE_TEXT ) break;
|
||||
if( roundUp ){
|
||||
if( aSample[i].u.r>r ) break;
|
||||
}else{
|
||||
if( aSample[i].u.r>=r ) break;
|
||||
}
|
||||
}
|
||||
}else if( eType==SQLITE_NULL ){
|
||||
i = 0;
|
||||
if( roundUp ){
|
||||
while( i<SQLITE_INDEX_SAMPLES && aSample[i].eType==SQLITE_NULL ) i++;
|
||||
}
|
||||
}else{
|
||||
sqlite3 *db = pParse->db;
|
||||
@@ -2260,7 +2331,7 @@ static int whereRangeRegion(
|
||||
n = sqlite3ValueBytes(pVal, pColl->enc);
|
||||
|
||||
for(i=0; i<SQLITE_INDEX_SAMPLES; i++){
|
||||
int r;
|
||||
int c;
|
||||
int eSampletype = aSample[i].eType;
|
||||
if( eSampletype==SQLITE_NULL || eSampletype<eType ) continue;
|
||||
if( (eSampletype!=eType) ) break;
|
||||
@@ -2274,14 +2345,14 @@ static int whereRangeRegion(
|
||||
assert( db->mallocFailed );
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
r = pColl->xCmp(pColl->pUser, nSample, zSample, n, z);
|
||||
c = pColl->xCmp(pColl->pUser, nSample, zSample, n, z);
|
||||
sqlite3DbFree(db, zSample);
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
r = pColl->xCmp(pColl->pUser, aSample[i].nByte, aSample[i].u.z, n, z);
|
||||
c = pColl->xCmp(pColl->pUser, aSample[i].nByte, aSample[i].u.z, n, z);
|
||||
}
|
||||
if( r>0 ) break;
|
||||
if( c-roundUp>=0 ) break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2364,9 +2435,9 @@ static int valueFromExpr(
|
||||
** constraints.
|
||||
**
|
||||
** In the absence of sqlite_stat2 ANALYZE data, each range inequality
|
||||
** reduces the search space by 2/3rds. Hence a single constraint (x>?)
|
||||
** results in a return of 33 and a range constraint (x>? AND x<?) results
|
||||
** in a return of 11.
|
||||
** reduces the search space by 3/4ths. Hence a single constraint (x>?)
|
||||
** results in a return of 25 and a range constraint (x>? AND x<?) results
|
||||
** in a return of 6.
|
||||
*/
|
||||
static int whereRangeScanEst(
|
||||
Parse *pParse, /* Parsing & code generating context */
|
||||
@@ -2386,15 +2457,21 @@ static int whereRangeScanEst(
|
||||
int iEst;
|
||||
int iLower = 0;
|
||||
int iUpper = SQLITE_INDEX_SAMPLES;
|
||||
int roundUpUpper = 0;
|
||||
int roundUpLower = 0;
|
||||
u8 aff = p->pTable->aCol[p->aiColumn[0]].affinity;
|
||||
|
||||
if( pLower ){
|
||||
Expr *pExpr = pLower->pExpr->pRight;
|
||||
rc = valueFromExpr(pParse, pExpr, aff, &pLowerVal);
|
||||
assert( pLower->eOperator==WO_GT || pLower->eOperator==WO_GE );
|
||||
roundUpLower = (pLower->eOperator==WO_GT) ?1:0;
|
||||
}
|
||||
if( rc==SQLITE_OK && pUpper ){
|
||||
Expr *pExpr = pUpper->pExpr->pRight;
|
||||
rc = valueFromExpr(pParse, pExpr, aff, &pUpperVal);
|
||||
assert( pUpper->eOperator==WO_LT || pUpper->eOperator==WO_LE );
|
||||
roundUpUpper = (pUpper->eOperator==WO_LE) ?1:0;
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK || (pLowerVal==0 && pUpperVal==0) ){
|
||||
@@ -2402,28 +2479,29 @@ static int whereRangeScanEst(
|
||||
sqlite3ValueFree(pUpperVal);
|
||||
goto range_est_fallback;
|
||||
}else if( pLowerVal==0 ){
|
||||
rc = whereRangeRegion(pParse, p, pUpperVal, &iUpper);
|
||||
rc = whereRangeRegion(pParse, p, pUpperVal, roundUpUpper, &iUpper);
|
||||
if( pLower ) iLower = iUpper/2;
|
||||
}else if( pUpperVal==0 ){
|
||||
rc = whereRangeRegion(pParse, p, pLowerVal, &iLower);
|
||||
rc = whereRangeRegion(pParse, p, pLowerVal, roundUpLower, &iLower);
|
||||
if( pUpper ) iUpper = (iLower + SQLITE_INDEX_SAMPLES + 1)/2;
|
||||
}else{
|
||||
rc = whereRangeRegion(pParse, p, pUpperVal, &iUpper);
|
||||
rc = whereRangeRegion(pParse, p, pUpperVal, roundUpUpper, &iUpper);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = whereRangeRegion(pParse, p, pLowerVal, &iLower);
|
||||
rc = whereRangeRegion(pParse, p, pLowerVal, roundUpLower, &iLower);
|
||||
}
|
||||
}
|
||||
WHERETRACE(("range scan regions: %d..%d\n", iLower, iUpper));
|
||||
|
||||
iEst = iUpper - iLower;
|
||||
testcase( iEst==SQLITE_INDEX_SAMPLES );
|
||||
assert( iEst<=SQLITE_INDEX_SAMPLES );
|
||||
if( iEst<1 ){
|
||||
iEst = 1;
|
||||
*piEst = 50/SQLITE_INDEX_SAMPLES;
|
||||
}else{
|
||||
*piEst = (iEst*100)/SQLITE_INDEX_SAMPLES;
|
||||
}
|
||||
|
||||
sqlite3ValueFree(pLowerVal);
|
||||
sqlite3ValueFree(pUpperVal);
|
||||
*piEst = (iEst * 100)/SQLITE_INDEX_SAMPLES;
|
||||
return rc;
|
||||
}
|
||||
range_est_fallback:
|
||||
@@ -2433,22 +2511,156 @@ range_est_fallback:
|
||||
UNUSED_PARAMETER(nEq);
|
||||
#endif
|
||||
assert( pLower || pUpper );
|
||||
if( pLower && pUpper ){
|
||||
*piEst = 11;
|
||||
}else{
|
||||
*piEst = 33;
|
||||
}
|
||||
*piEst = 100;
|
||||
if( pLower && (pLower->wtFlags & TERM_VNULL)==0 ) *piEst /= 4;
|
||||
if( pUpper ) *piEst /= 4;
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_ENABLE_STAT2
|
||||
/*
|
||||
** Estimate the number of rows that will be returned based on
|
||||
** an equality constraint x=VALUE and where that VALUE occurs in
|
||||
** the histogram data. This only works when x is the left-most
|
||||
** column of an index and sqlite_stat2 histogram data is available
|
||||
** for that index. When pExpr==NULL that means the constraint is
|
||||
** "x IS NULL" instead of "x=VALUE".
|
||||
**
|
||||
** Write the estimated row count into *pnRow and return SQLITE_OK.
|
||||
** If unable to make an estimate, leave *pnRow unchanged and return
|
||||
** non-zero.
|
||||
**
|
||||
** This routine can fail if it is unable to load a collating sequence
|
||||
** required for string comparison, or if unable to allocate memory
|
||||
** for a UTF conversion required for comparison. The error is stored
|
||||
** in the pParse structure.
|
||||
*/
|
||||
static int whereEqualScanEst(
|
||||
Parse *pParse, /* Parsing & code generating context */
|
||||
Index *p, /* The index whose left-most column is pTerm */
|
||||
Expr *pExpr, /* Expression for VALUE in the x=VALUE constraint */
|
||||
double *pnRow /* Write the revised row estimate here */
|
||||
){
|
||||
sqlite3_value *pRhs = 0; /* VALUE on right-hand side of pTerm */
|
||||
int iLower, iUpper; /* Range of histogram regions containing pRhs */
|
||||
u8 aff; /* Column affinity */
|
||||
int rc; /* Subfunction return code */
|
||||
double nRowEst; /* New estimate of the number of rows */
|
||||
|
||||
assert( p->aSample!=0 );
|
||||
aff = p->pTable->aCol[p->aiColumn[0]].affinity;
|
||||
if( pExpr ){
|
||||
rc = valueFromExpr(pParse, pExpr, aff, &pRhs);
|
||||
if( rc ) goto whereEqualScanEst_cancel;
|
||||
}else{
|
||||
pRhs = sqlite3ValueNew(pParse->db);
|
||||
}
|
||||
if( pRhs==0 ) return SQLITE_NOTFOUND;
|
||||
rc = whereRangeRegion(pParse, p, pRhs, 0, &iLower);
|
||||
if( rc ) goto whereEqualScanEst_cancel;
|
||||
rc = whereRangeRegion(pParse, p, pRhs, 1, &iUpper);
|
||||
if( rc ) goto whereEqualScanEst_cancel;
|
||||
WHERETRACE(("equality scan regions: %d..%d\n", iLower, iUpper));
|
||||
if( iLower>=iUpper ){
|
||||
nRowEst = p->aiRowEst[0]/(SQLITE_INDEX_SAMPLES*2);
|
||||
if( nRowEst<*pnRow ) *pnRow = nRowEst;
|
||||
}else{
|
||||
nRowEst = (iUpper-iLower)*p->aiRowEst[0]/SQLITE_INDEX_SAMPLES;
|
||||
*pnRow = nRowEst;
|
||||
}
|
||||
|
||||
whereEqualScanEst_cancel:
|
||||
sqlite3ValueFree(pRhs);
|
||||
return rc;
|
||||
}
|
||||
#endif /* defined(SQLITE_ENABLE_STAT2) */
|
||||
|
||||
#ifdef SQLITE_ENABLE_STAT2
|
||||
/*
|
||||
** Estimate the number of rows that will be returned based on
|
||||
** an IN constraint where the right-hand side of the IN operator
|
||||
** is a list of values. Example:
|
||||
**
|
||||
** WHERE x IN (1,2,3,4)
|
||||
**
|
||||
** Write the estimated row count into *pnRow and return SQLITE_OK.
|
||||
** If unable to make an estimate, leave *pnRow unchanged and return
|
||||
** non-zero.
|
||||
**
|
||||
** This routine can fail if it is unable to load a collating sequence
|
||||
** required for string comparison, or if unable to allocate memory
|
||||
** for a UTF conversion required for comparison. The error is stored
|
||||
** in the pParse structure.
|
||||
*/
|
||||
static int whereInScanEst(
|
||||
Parse *pParse, /* Parsing & code generating context */
|
||||
Index *p, /* The index whose left-most column is pTerm */
|
||||
ExprList *pList, /* The value list on the RHS of "x IN (v1,v2,v3,...)" */
|
||||
double *pnRow /* Write the revised row estimate here */
|
||||
){
|
||||
sqlite3_value *pVal = 0; /* One value from list */
|
||||
int iLower, iUpper; /* Range of histogram regions containing pRhs */
|
||||
u8 aff; /* Column affinity */
|
||||
int rc = SQLITE_OK; /* Subfunction return code */
|
||||
double nRowEst; /* New estimate of the number of rows */
|
||||
int nSpan = 0; /* Number of histogram regions spanned */
|
||||
int nSingle = 0; /* Histogram regions hit by a single value */
|
||||
int nNotFound = 0; /* Count of values that are not constants */
|
||||
int i; /* Loop counter */
|
||||
u8 aSpan[SQLITE_INDEX_SAMPLES+1]; /* Histogram regions that are spanned */
|
||||
u8 aSingle[SQLITE_INDEX_SAMPLES+1]; /* Histogram regions hit once */
|
||||
|
||||
assert( p->aSample!=0 );
|
||||
aff = p->pTable->aCol[p->aiColumn[0]].affinity;
|
||||
memset(aSpan, 0, sizeof(aSpan));
|
||||
memset(aSingle, 0, sizeof(aSingle));
|
||||
for(i=0; i<pList->nExpr; i++){
|
||||
sqlite3ValueFree(pVal);
|
||||
rc = valueFromExpr(pParse, pList->a[i].pExpr, aff, &pVal);
|
||||
if( rc ) break;
|
||||
if( pVal==0 || sqlite3_value_type(pVal)==SQLITE_NULL ){
|
||||
nNotFound++;
|
||||
continue;
|
||||
}
|
||||
rc = whereRangeRegion(pParse, p, pVal, 0, &iLower);
|
||||
if( rc ) break;
|
||||
rc = whereRangeRegion(pParse, p, pVal, 1, &iUpper);
|
||||
if( rc ) break;
|
||||
if( iLower>=iUpper ){
|
||||
aSingle[iLower] = 1;
|
||||
}else{
|
||||
assert( iLower>=0 && iUpper<=SQLITE_INDEX_SAMPLES );
|
||||
while( iLower<iUpper ) aSpan[iLower++] = 1;
|
||||
}
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
for(i=nSpan=0; i<=SQLITE_INDEX_SAMPLES; i++){
|
||||
if( aSpan[i] ){
|
||||
nSpan++;
|
||||
}else if( aSingle[i] ){
|
||||
nSingle++;
|
||||
}
|
||||
}
|
||||
nRowEst = (nSpan*2+nSingle)*p->aiRowEst[0]/(2*SQLITE_INDEX_SAMPLES)
|
||||
+ nNotFound*p->aiRowEst[1];
|
||||
if( nRowEst > p->aiRowEst[0] ) nRowEst = p->aiRowEst[0];
|
||||
*pnRow = nRowEst;
|
||||
WHERETRACE(("IN row estimate: nSpan=%d, nSingle=%d, nNotFound=%d, est=%g\n",
|
||||
nSpan, nSingle, nNotFound, nRowEst));
|
||||
}
|
||||
sqlite3ValueFree(pVal);
|
||||
return rc;
|
||||
}
|
||||
#endif /* defined(SQLITE_ENABLE_STAT2) */
|
||||
|
||||
|
||||
/*
|
||||
** Find the query plan for accessing a particular table. Write the
|
||||
** Find the best query plan for accessing a particular table. Write the
|
||||
** best query plan and its cost into the WhereCost object supplied as the
|
||||
** last parameter.
|
||||
**
|
||||
** The lowest cost plan wins. The cost is an estimate of the amount of
|
||||
** CPU and disk I/O need to process the request using the selected plan.
|
||||
** CPU and disk I/O needed to process the requested result.
|
||||
** Factors that influence cost include:
|
||||
**
|
||||
** * The estimated number of rows that will be retrieved. (The
|
||||
@@ -2467,7 +2679,7 @@ range_est_fallback:
|
||||
**
|
||||
** If a NOT INDEXED clause (pSrc->notIndexed!=0) was attached to the table
|
||||
** in the SELECT statement, then no indexes are considered. However, the
|
||||
** selected plan may still take advantage of the tables built-in rowid
|
||||
** selected plan may still take advantage of the built-in rowid primary key
|
||||
** index.
|
||||
*/
|
||||
static void bestBtreeIndex(
|
||||
@@ -2510,9 +2722,11 @@ static void bestBtreeIndex(
|
||||
wsFlagMask = ~(WHERE_ROWID_EQ|WHERE_ROWID_RANGE);
|
||||
eqTermMask = idxEqTermMask;
|
||||
}else{
|
||||
/* There is no INDEXED BY clause. Create a fake Index object to
|
||||
** represent the primary key */
|
||||
Index *pFirst; /* Any other index on the table */
|
||||
/* There is no INDEXED BY clause. Create a fake Index object in local
|
||||
** variable sPk to represent the rowid primary key index. Make this
|
||||
** fake index the first in a chain of Index objects with all of the real
|
||||
** indices to follow */
|
||||
Index *pFirst; /* First of real indices on the table */
|
||||
memset(&sPk, 0, sizeof(Index));
|
||||
sPk.nColumn = 1;
|
||||
sPk.aiColumn = &aiColumnPk;
|
||||
@@ -2523,6 +2737,8 @@ static void bestBtreeIndex(
|
||||
aiRowEstPk[1] = 1;
|
||||
pFirst = pSrc->pTab->pIndex;
|
||||
if( pSrc->notIndexed==0 ){
|
||||
/* The real indices of the table are only considered if the
|
||||
** NOT INDEXED qualifier is omitted from the FROM clause */
|
||||
sPk.pNext = pFirst;
|
||||
}
|
||||
pProbe = &sPk;
|
||||
@@ -2539,16 +2755,19 @@ static void bestBtreeIndex(
|
||||
const unsigned int * const aiRowEst = pProbe->aiRowEst;
|
||||
double cost; /* Cost of using pProbe */
|
||||
double nRow; /* Estimated number of rows in result set */
|
||||
double log10N; /* base-10 logarithm of nRow (inexact) */
|
||||
int rev; /* True to scan in reverse order */
|
||||
int wsFlags = 0;
|
||||
Bitmask used = 0;
|
||||
|
||||
/* The following variables are populated based on the properties of
|
||||
** scan being evaluated. They are then used to determine the expected
|
||||
** index being evaluated. They are then used to determine the expected
|
||||
** cost and number of rows returned.
|
||||
**
|
||||
** nEq:
|
||||
** Number of equality terms that can be implemented using the index.
|
||||
** In other words, the number of initial fields in the index that
|
||||
** are used in == or IN or NOT NULL constraints of the WHERE clause.
|
||||
**
|
||||
** nInMul:
|
||||
** The "in-multiplier". This is an estimate of how many seek operations
|
||||
@@ -2572,7 +2791,9 @@ static void bestBtreeIndex(
|
||||
**
|
||||
** bInEst:
|
||||
** Set to true if there was at least one "x IN (SELECT ...)" term used
|
||||
** in determining the value of nInMul.
|
||||
** in determining the value of nInMul. Note that the RHS of the
|
||||
** IN operator must be a SELECT, not a value list, for this variable
|
||||
** to be true.
|
||||
**
|
||||
** estBound:
|
||||
** An estimate on the amount of the table that must be searched. A
|
||||
@@ -2580,8 +2801,8 @@ static void bestBtreeIndex(
|
||||
** might reduce this to a value less than 100 to indicate that only
|
||||
** a fraction of the table needs searching. In the absence of
|
||||
** sqlite_stat2 ANALYZE data, a single inequality reduces the search
|
||||
** space to 1/3rd its original size. So an x>? constraint reduces
|
||||
** estBound to 33. Two constraints (x>? AND x<?) reduce estBound to 11.
|
||||
** space to 1/4rd its original size. So an x>? constraint reduces
|
||||
** estBound to 25. Two constraints (x>? AND x<?) reduce estBound to 6.
|
||||
**
|
||||
** bSort:
|
||||
** Boolean. True if there is an ORDER BY clause that will require an
|
||||
@@ -2589,25 +2810,31 @@ static void bestBtreeIndex(
|
||||
** correctly order records).
|
||||
**
|
||||
** bLookup:
|
||||
** Boolean. True if for each index entry visited a lookup on the
|
||||
** corresponding table b-tree is required. This is always false
|
||||
** for the rowid index. For other indexes, it is true unless all the
|
||||
** columns of the table used by the SELECT statement are present in
|
||||
** the index (such an index is sometimes described as a covering index).
|
||||
** Boolean. True if a table lookup is required for each index entry
|
||||
** visited. In other words, true if this is not a covering index.
|
||||
** This is always false for the rowid primary key index of a table.
|
||||
** For other indexes, it is true unless all the columns of the table
|
||||
** used by the SELECT statement are present in the index (such an
|
||||
** index is sometimes described as a covering index).
|
||||
** For example, given the index on (a, b), the second of the following
|
||||
** two queries requires table b-tree lookups, but the first does not.
|
||||
** two queries requires table b-tree lookups in order to find the value
|
||||
** of column c, but the first does not because columns a and b are
|
||||
** both available in the index.
|
||||
**
|
||||
** SELECT a, b FROM tbl WHERE a = 1;
|
||||
** SELECT a, b, c FROM tbl WHERE a = 1;
|
||||
*/
|
||||
int nEq;
|
||||
int bInEst = 0;
|
||||
int nInMul = 1;
|
||||
int estBound = 100;
|
||||
int nBound = 0; /* Number of range constraints seen */
|
||||
int bSort = 0;
|
||||
int bLookup = 0;
|
||||
WhereTerm *pTerm; /* A single term of the WHERE clause */
|
||||
int nEq; /* Number of == or IN terms matching index */
|
||||
int bInEst = 0; /* True if "x IN (SELECT...)" seen */
|
||||
int nInMul = 1; /* Number of distinct equalities to lookup */
|
||||
int estBound = 100; /* Estimated reduction in search space */
|
||||
int nBound = 0; /* Number of range constraints seen */
|
||||
int bSort = 0; /* True if external sort required */
|
||||
int bLookup = 0; /* True if not a covering index */
|
||||
WhereTerm *pTerm; /* A single term of the WHERE clause */
|
||||
#ifdef SQLITE_ENABLE_STAT2
|
||||
WhereTerm *pFirstTerm = 0; /* First term matching the index */
|
||||
#endif
|
||||
|
||||
/* Determine the values of nEq and nInMul */
|
||||
for(nEq=0; nEq<pProbe->nColumn; nEq++){
|
||||
@@ -2619,19 +2846,24 @@ static void bestBtreeIndex(
|
||||
Expr *pExpr = pTerm->pExpr;
|
||||
wsFlags |= WHERE_COLUMN_IN;
|
||||
if( ExprHasProperty(pExpr, EP_xIsSelect) ){
|
||||
/* "x IN (SELECT ...)": Assume the SELECT returns 25 rows */
|
||||
nInMul *= 25;
|
||||
bInEst = 1;
|
||||
}else if( ALWAYS(pExpr->x.pList) ){
|
||||
nInMul *= pExpr->x.pList->nExpr + 1;
|
||||
}else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){
|
||||
/* "x IN (value, value, ...)" */
|
||||
nInMul *= pExpr->x.pList->nExpr;
|
||||
}
|
||||
}else if( pTerm->eOperator & WO_ISNULL ){
|
||||
wsFlags |= WHERE_COLUMN_NULL;
|
||||
}
|
||||
#ifdef SQLITE_ENABLE_STAT2
|
||||
if( nEq==0 && pProbe->aSample ) pFirstTerm = pTerm;
|
||||
#endif
|
||||
used |= pTerm->prereqRight;
|
||||
}
|
||||
|
||||
/* Determine the value of estBound. */
|
||||
if( nEq<pProbe->nColumn ){
|
||||
if( nEq<pProbe->nColumn && pProbe->bUnordered==0 ){
|
||||
int j = pProbe->aiColumn[nEq];
|
||||
if( findTerm(pWC, iCur, j, notReady, WO_LT|WO_LE|WO_GT|WO_GE, pIdx) ){
|
||||
WhereTerm *pTop = findTerm(pWC, iCur, j, notReady, WO_LT|WO_LE, pIdx);
|
||||
@@ -2662,8 +2894,10 @@ static void bestBtreeIndex(
|
||||
** in wsFlags. Otherwise, if there is an ORDER BY clause but the index
|
||||
** will scan rows in a different order, set the bSort variable. */
|
||||
if( pOrderBy ){
|
||||
if( (wsFlags & (WHERE_COLUMN_IN|WHERE_COLUMN_NULL))==0
|
||||
&& isSortingIndex(pParse,pWC->pMaskSet,pProbe,iCur,pOrderBy,nEq,&rev)
|
||||
if( (wsFlags & WHERE_COLUMN_IN)==0
|
||||
&& pProbe->bUnordered==0
|
||||
&& isSortingIndex(pParse, pWC->pMaskSet, pProbe, iCur, pOrderBy,
|
||||
nEq, wsFlags, &rev)
|
||||
){
|
||||
wsFlags |= WHERE_ROWID_RANGE|WHERE_COLUMN_RANGE|WHERE_ORDERBY;
|
||||
wsFlags |= (rev ? WHERE_REVERSE : 0);
|
||||
@@ -2694,8 +2928,8 @@ static void bestBtreeIndex(
|
||||
}
|
||||
|
||||
/*
|
||||
** Estimate the number of rows of output. For an IN operator,
|
||||
** do not let the estimate exceed half the rows in the table.
|
||||
** Estimate the number of rows of output. For an "x IN (SELECT...)"
|
||||
** constraint, do not let the estimate exceed half the rows in the table.
|
||||
*/
|
||||
nRow = (double)(aiRowEst[nEq] * nInMul);
|
||||
if( bInEst && nRow*2>aiRowEst[0] ){
|
||||
@@ -2703,31 +2937,90 @@ static void bestBtreeIndex(
|
||||
nInMul = (int)(nRow / aiRowEst[nEq]);
|
||||
}
|
||||
|
||||
/* Assume constant cost to access a row and logarithmic cost to
|
||||
** do a binary search. Hence, the initial cost is the number of output
|
||||
** rows plus log2(table-size) times the number of binary searches.
|
||||
#ifdef SQLITE_ENABLE_STAT2
|
||||
/* If the constraint is of the form x=VALUE and histogram
|
||||
** data is available for column x, then it might be possible
|
||||
** to get a better estimate on the number of rows based on
|
||||
** VALUE and how common that value is according to the histogram.
|
||||
*/
|
||||
cost = nRow + nInMul*estLog(aiRowEst[0]);
|
||||
if( nRow>(double)1 && nEq==1 && pFirstTerm!=0 ){
|
||||
if( pFirstTerm->eOperator & (WO_EQ|WO_ISNULL) ){
|
||||
testcase( pFirstTerm->eOperator==WO_EQ );
|
||||
testcase( pFirstTerm->eOperator==WO_ISNULL );
|
||||
whereEqualScanEst(pParse, pProbe, pFirstTerm->pExpr->pRight, &nRow);
|
||||
}else if( pFirstTerm->eOperator==WO_IN && bInEst==0 ){
|
||||
whereInScanEst(pParse, pProbe, pFirstTerm->pExpr->x.pList, &nRow);
|
||||
}
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_STAT2 */
|
||||
|
||||
/* Adjust the number of rows and the cost downward to reflect rows
|
||||
/* Adjust the number of output rows and downward to reflect rows
|
||||
** that are excluded by range constraints.
|
||||
*/
|
||||
nRow = (nRow * (double)estBound) / (double)100;
|
||||
cost = (cost * (double)estBound) / (double)100;
|
||||
if( nRow<1 ) nRow = 1;
|
||||
|
||||
/* Add in the estimated cost of sorting the result
|
||||
/* Experiments run on real SQLite databases show that the time needed
|
||||
** to do a binary search to locate a row in a table or index is roughly
|
||||
** log10(N) times the time to move from one row to the next row within
|
||||
** a table or index. The actual times can vary, with the size of
|
||||
** records being an important factor. Both moves and searches are
|
||||
** slower with larger records, presumably because fewer records fit
|
||||
** on one page and hence more pages have to be fetched.
|
||||
**
|
||||
** The ANALYZE command and the sqlite_stat1 and sqlite_stat2 tables do
|
||||
** not give us data on the relative sizes of table and index records.
|
||||
** So this computation assumes table records are about twice as big
|
||||
** as index records
|
||||
*/
|
||||
if( (wsFlags & WHERE_NOT_FULLSCAN)==0 ){
|
||||
/* The cost of a full table scan is a number of move operations equal
|
||||
** to the number of rows in the table.
|
||||
**
|
||||
** We add an additional 4x penalty to full table scans. This causes
|
||||
** the cost function to err on the side of choosing an index over
|
||||
** choosing a full scan. This 4x full-scan penalty is an arguable
|
||||
** decision and one which we expect to revisit in the future. But
|
||||
** it seems to be working well enough at the moment.
|
||||
*/
|
||||
cost = aiRowEst[0]*4;
|
||||
}else{
|
||||
log10N = estLog(aiRowEst[0]);
|
||||
cost = nRow;
|
||||
if( pIdx ){
|
||||
if( bLookup ){
|
||||
/* For an index lookup followed by a table lookup:
|
||||
** nInMul index searches to find the start of each index range
|
||||
** + nRow steps through the index
|
||||
** + nRow table searches to lookup the table entry using the rowid
|
||||
*/
|
||||
cost += (nInMul + nRow)*log10N;
|
||||
}else{
|
||||
/* For a covering index:
|
||||
** nInMul index searches to find the initial entry
|
||||
** + nRow steps through the index
|
||||
*/
|
||||
cost += nInMul*log10N;
|
||||
}
|
||||
}else{
|
||||
/* For a rowid primary key lookup:
|
||||
** nInMult table searches to find the initial entry for each range
|
||||
** + nRow steps through the table
|
||||
*/
|
||||
cost += nInMul*log10N;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add in the estimated cost of sorting the result. Actual experimental
|
||||
** measurements of sorting performance in SQLite show that sorting time
|
||||
** adds C*N*log10(N) to the cost, where N is the number of rows to be
|
||||
** sorted and C is a factor between 1.95 and 4.3. We will split the
|
||||
** difference and select C of 3.0.
|
||||
*/
|
||||
if( bSort ){
|
||||
cost += cost*estLog(cost);
|
||||
cost += nRow*estLog(nRow)*3;
|
||||
}
|
||||
|
||||
/* If all information can be taken directly from the index, we avoid
|
||||
** doing table lookups. This reduces the cost by half. (Not really -
|
||||
** this needs to be fixed.)
|
||||
*/
|
||||
if( pIdx && bLookup==0 ){
|
||||
cost /= (double)2;
|
||||
}
|
||||
/**** Cost of using this index has now been computed ****/
|
||||
|
||||
/* If there are additional constraints on this table that cannot
|
||||
@@ -2768,15 +3061,19 @@ static void bestBtreeIndex(
|
||||
}
|
||||
}else if( pTerm->eOperator & (WO_LT|WO_LE|WO_GT|WO_GE) ){
|
||||
if( nSkipRange ){
|
||||
/* Ignore the first nBound range constraints since the index
|
||||
/* Ignore the first nSkipRange range constraints since the index
|
||||
** has already accounted for these */
|
||||
nSkipRange--;
|
||||
}else{
|
||||
/* Assume each additional range constraint reduces the result
|
||||
** set size by a factor of 3 */
|
||||
** set size by a factor of 3. Indexed range constraints reduce
|
||||
** the search space by a larger factor: 4. We make indexed range
|
||||
** more selective intentionally because of the subjective
|
||||
** observation that indexed range constraints really are more
|
||||
** selective in practice, on average. */
|
||||
nRow /= 3;
|
||||
}
|
||||
}else{
|
||||
}else if( pTerm->eOperator!=WO_NOOP ){
|
||||
/* Any other expression lowers the output row count by half */
|
||||
nRow /= 2;
|
||||
}
|
||||
@@ -2787,10 +3084,10 @@ static void bestBtreeIndex(
|
||||
|
||||
WHERETRACE((
|
||||
"%s(%s): nEq=%d nInMul=%d estBound=%d bSort=%d bLookup=%d wsFlags=0x%x\n"
|
||||
" notReady=0x%llx nRow=%.2f cost=%.2f used=0x%llx\n",
|
||||
" notReady=0x%llx log10N=%.1f nRow=%.1f cost=%.1f used=0x%llx\n",
|
||||
pSrc->pTab->zName, (pIdx ? pIdx->zName : "ipk"),
|
||||
nEq, nInMul, estBound, bSort, bLookup, wsFlags,
|
||||
notReady, nRow, cost, used
|
||||
notReady, log10N, nRow, cost, used
|
||||
));
|
||||
|
||||
/* If this index is the best we have seen so far, then record this
|
||||
@@ -3614,7 +3911,9 @@ static Bitmask codeOneLoopStart(
|
||||
if( pRangeStart ){
|
||||
Expr *pRight = pRangeStart->pExpr->pRight;
|
||||
sqlite3ExprCode(pParse, pRight, regBase+nEq);
|
||||
sqlite3ExprCodeIsNullJump(v, pRight, regBase+nEq, addrNxt);
|
||||
if( (pRangeStart->wtFlags & TERM_VNULL)==0 ){
|
||||
sqlite3ExprCodeIsNullJump(v, pRight, regBase+nEq, addrNxt);
|
||||
}
|
||||
if( zStartAff ){
|
||||
if( sqlite3CompareAffinity(pRight, zStartAff[nEq])==SQLITE_AFF_NONE){
|
||||
/* Since the comparison is to be performed with no conversions
|
||||
@@ -3653,7 +3952,9 @@ static Bitmask codeOneLoopStart(
|
||||
Expr *pRight = pRangeEnd->pExpr->pRight;
|
||||
sqlite3ExprCacheRemove(pParse, regBase+nEq, 1);
|
||||
sqlite3ExprCode(pParse, pRight, regBase+nEq);
|
||||
sqlite3ExprCodeIsNullJump(v, pRight, regBase+nEq, addrNxt);
|
||||
if( (pRangeEnd->wtFlags & TERM_VNULL)==0 ){
|
||||
sqlite3ExprCodeIsNullJump(v, pRight, regBase+nEq, addrNxt);
|
||||
}
|
||||
if( zEndAff ){
|
||||
if( sqlite3CompareAffinity(pRight, zEndAff[nEq])==SQLITE_AFF_NONE){
|
||||
/* Since the comparison is to be performed with no conversions
|
||||
@@ -3711,7 +4012,13 @@ static Bitmask codeOneLoopStart(
|
||||
/* Record the instruction used to terminate the loop. Disable
|
||||
** WHERE clause terms made redundant by the index range scan.
|
||||
*/
|
||||
pLevel->op = bRev ? OP_Prev : OP_Next;
|
||||
if( pLevel->plan.wsFlags & WHERE_UNIQUE ){
|
||||
pLevel->op = OP_Noop;
|
||||
}else if( bRev ){
|
||||
pLevel->op = OP_Prev;
|
||||
}else{
|
||||
pLevel->op = OP_Next;
|
||||
}
|
||||
pLevel->p1 = iIdxCur;
|
||||
}else
|
||||
|
||||
@@ -3757,7 +4064,6 @@ static Bitmask codeOneLoopStart(
|
||||
**
|
||||
*/
|
||||
WhereClause *pOrWc; /* The OR-clause broken out into subterms */
|
||||
WhereTerm *pFinal; /* Final subterm within the OR-clause. */
|
||||
SrcList *pOrTab; /* Shortened table list or OR-clause generation */
|
||||
|
||||
int regReturn = ++pParse->nMem; /* Register used with OP_Gosub */
|
||||
@@ -3773,7 +4079,6 @@ static Bitmask codeOneLoopStart(
|
||||
assert( pTerm->eOperator==WO_OR );
|
||||
assert( (pTerm->wtFlags & TERM_ORINFO)!=0 );
|
||||
pOrWc = &pTerm->u.pOrInfo->wc;
|
||||
pFinal = &pOrWc->a[pOrWc->nTerm-1];
|
||||
pLevel->op = OP_Return;
|
||||
pLevel->p1 = regReturn;
|
||||
|
||||
@@ -3882,7 +4187,6 @@ static Bitmask codeOneLoopStart(
|
||||
** the use of indices become tests that are evaluated against each row of
|
||||
** the relevant input tables.
|
||||
*/
|
||||
k = 0;
|
||||
for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){
|
||||
Expr *pE;
|
||||
testcase( pTerm->wtFlags & TERM_VIRTUAL ); /* IMP: R-30575-11662 */
|
||||
@@ -3900,7 +4204,6 @@ static Bitmask codeOneLoopStart(
|
||||
continue;
|
||||
}
|
||||
sqlite3ExprIfFalse(pParse, pE, addrCont, SQLITE_JUMPIFNULL);
|
||||
k = 1;
|
||||
pTerm->wtFlags |= TERM_CODED;
|
||||
}
|
||||
|
||||
@@ -4208,8 +4511,6 @@ WhereInfo *sqlite3WhereBegin(
|
||||
** clause.
|
||||
*/
|
||||
notReady = ~(Bitmask)0;
|
||||
pTabItem = pTabList->a;
|
||||
pLevel = pWInfo->a;
|
||||
andFlags = ~0;
|
||||
WHERETRACE(("*** Optimizer Start ***\n"));
|
||||
for(i=iFrom=0, pLevel=pWInfo->a; i<nTabList; i++, pLevel++){
|
||||
@@ -4320,8 +4621,8 @@ WhereInfo *sqlite3WhereBegin(
|
||||
** (1) The table must not depend on other tables that have not
|
||||
** yet run.
|
||||
**
|
||||
** (2) A full-table-scan plan cannot supercede another plan unless
|
||||
** it is an "optimal" plan as defined above.
|
||||
** (2) A full-table-scan plan cannot supercede indexed plan unless
|
||||
** the full-table-scan is an "optimal" plan as defined above.
|
||||
**
|
||||
** (3) All tables have an INDEXED BY clause or this table lacks an
|
||||
** INDEXED BY clause or this table uses the specific
|
||||
@@ -4337,6 +4638,7 @@ WhereInfo *sqlite3WhereBegin(
|
||||
*/
|
||||
if( (sCost.used¬Ready)==0 /* (1) */
|
||||
&& (bestJ<0 || (notIndexed&m)!=0 /* (2) */
|
||||
|| (bestPlan.plan.wsFlags & WHERE_NOT_FULLSCAN)==0
|
||||
|| (sCost.plan.wsFlags & WHERE_NOT_FULLSCAN)!=0)
|
||||
&& (nUnconstrained==0 || pTabItem->pIndex==0 /* (3) */
|
||||
|| NEVER((sCost.plan.wsFlags & WHERE_NOT_FULLSCAN)!=0))
|
||||
|
||||
@@ -840,4 +840,23 @@ do_test alter-14.2 {
|
||||
} {1 {Cannot add a PRIMARY KEY column}}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that it is not possible to use ALTER TABLE on any system table.
|
||||
#
|
||||
set system_table_list {1 sqlite_master}
|
||||
catchsql ANALYZE
|
||||
ifcapable analyze { lappend system_table_list 2 sqlite_stat1 }
|
||||
ifcapable stat2 { lappend system_table_list 3 sqlite_stat2 }
|
||||
|
||||
foreach {tn tbl} $system_table_list {
|
||||
do_test alter-15.$tn.1 {
|
||||
catchsql "ALTER TABLE $tbl RENAME TO xyz"
|
||||
} [list 1 "table $tbl may not be altered"]
|
||||
|
||||
do_test alter-15.$tn.2 {
|
||||
catchsql "ALTER TABLE $tbl ADD COLUMN xyz"
|
||||
} [list 1 "table $tbl may not be altered"]
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||