Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 827e656177 | |||
| 4f3c63e475 | |||
| 4e0dec47d7 | |||
| 03e7e9516f | |||
| 338ea13c0d | |||
| 2e7051b3ec | |||
| 98a0b0834d | |||
| c58011e862 | |||
| b5730347ad | |||
| 17a6893463 | |||
| d1881e6165 | |||
| b5b452cb58 | |||
| acbcdc49c6 | |||
| d9e5075ff6 | |||
| 2702114089 | |||
| 41a2b48bd0 | |||
| e5080e4e9e | |||
| 345fda3ef5 | |||
| ae85dc8b0b | |||
| 993c2a7c8f | |||
| 2fad28e310 | |||
| 660f68d6a2 | |||
| 4b2d64a9c9 | |||
| 41adf6c327 | |||
| a2e1bb5a58 | |||
| 68b8f0a1ef | |||
| acf4575752 | |||
| 144f1d64b5 | |||
| 78e6f71fcc | |||
| 8c82b3506e | |||
| af90b19472 | |||
| 66b89c8f68 | |||
| d9a4698e5f | |||
| 52d17876d1 | |||
| 8c3052c49b | |||
| 76634487b6 | |||
| ccb2a3daf6 | |||
| ed5423d5ae | |||
| 549cfc6c80 | |||
| 3cebbde37e | |||
| efa4e17240 | |||
| 01d01eeee0 | |||
| 42f04222c0 | |||
| 99e86e6b0b | |||
| 767c2001c6 | |||
| e907bfc2ac | |||
| bb0b679c02 | |||
| 58c2976fa9 | |||
| 9b1eb48b7d | |||
| 1afad7c98a | |||
| 611ad21d8b | |||
| 773ab7ae4c | |||
| 4c5043910b | |||
| b2b415b97f | |||
| 3e85251e81 | |||
| fe79b5325e | |||
| 7c68d60b6f | |||
| d42b00f654 | |||
| 98699b54bb | |||
| a18c5681e9 | |||
| 35d5d9e212 | |||
| 9b0d0a8bc5 | |||
| 05a1c8d08c | |||
| fdc99e9235 | |||
| e371033ddf | |||
| 6d31316cf3 | |||
| 6562e5af1f | |||
| a5c2ad06e6 | |||
| e09efa30c4 | |||
| ff6e9116e5 | |||
| 6ec2733b44 | |||
| 3d441f8082 | |||
| 63e2741e97 | |||
| 64bde80a16 | |||
| 4b1fb13436 | |||
| 3d0b5598c0 | |||
| b217a57eb9 | |||
| 80afdca0d8 | |||
| 61bc272814 | |||
| f4b283d086 | |||
| ea1fea5a50 | |||
| d317676578 | |||
| bbd4a757a7 |
+39
-6
@@ -47,25 +47,31 @@ LIBREADLINE = @TARGET_READLINE_LIBS@
|
||||
|
||||
# Object files for the SQLite library.
|
||||
#
|
||||
LIBOBJ = build.o dbbe.o delete.o expr.o insert.o \
|
||||
main.o parse.o select.o tokenize.o update.o \
|
||||
util.o vdbe.o where.o
|
||||
LIBOBJ = build.o dbbe.o dbbegdbm.o dbbemem.o delete.o expr.o insert.o \
|
||||
main.o parse.o printf.o random.o select.o table.o tokenize.o \
|
||||
update.o util.o vdbe.o where.o tclsqlite.o
|
||||
|
||||
# All of the source code files.
|
||||
#
|
||||
SRC = \
|
||||
$(TOP)/src/build.c \
|
||||
$(TOP)/src/dbbe.c \
|
||||
$(TOP)/src/dbbegdbm.c \
|
||||
$(TOP)/src/dbbemem.c \
|
||||
$(TOP)/src/dbbe.h \
|
||||
$(TOP)/src/dbbemem.c \
|
||||
$(TOP)/src/delete.c \
|
||||
$(TOP)/src/expr.c \
|
||||
$(TOP)/src/insert.c \
|
||||
$(TOP)/src/main.c \
|
||||
$(TOP)/src/parse.y \
|
||||
$(TOP)/src/printf.c \
|
||||
$(TOP)/src/random.c \
|
||||
$(TOP)/src/select.c \
|
||||
$(TOP)/src/shell.c \
|
||||
$(TOP)/src/sqlite.h.in \
|
||||
$(TOP)/src/sqliteInt.h \
|
||||
$(TOP)/src/table.c \
|
||||
$(TOP)/src/tclsqlite.c \
|
||||
$(TOP)/src/tokenize.c \
|
||||
$(TOP)/src/update.c \
|
||||
@@ -115,6 +121,12 @@ build.o: $(TOP)/src/build.c $(HDR)
|
||||
dbbe.o: $(TOP)/src/dbbe.c $(HDR)
|
||||
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/dbbe.c
|
||||
|
||||
dbbegdbm.o: $(TOP)/src/dbbegdbm.c $(HDR)
|
||||
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/dbbegdbm.c
|
||||
|
||||
dbbemem.o: $(TOP)/src/dbbemem.c $(HDR)
|
||||
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/dbbemem.c
|
||||
|
||||
main.o: $(TOP)/src/main.c $(HDR)
|
||||
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/main.c
|
||||
|
||||
@@ -127,8 +139,8 @@ parse.c: $(TOP)/src/parse.y lemon
|
||||
cp $(TOP)/src/parse.y .
|
||||
./lemon parse.y
|
||||
|
||||
sqlite.h: $(TOP)/src/sqlite.h.in
|
||||
sed s/--VERS--/`cat ${TOP}/VERSION`/ $(TOP)/src/sqlite.h.in >sqlite.h
|
||||
sqlite.h: $(TOP)/src/sqlite.h.in
|
||||
sed -e s/--VERS--/`cat ${TOP}/VERSION`/ $(TOP)/src/sqlite.h.in >sqlite.h
|
||||
|
||||
tokenize.o: $(TOP)/src/tokenize.c $(HDR)
|
||||
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/tokenize.c
|
||||
@@ -151,12 +163,24 @@ expr.o: $(TOP)/src/expr.c $(HDR)
|
||||
insert.o: $(TOP)/src/insert.c $(HDR)
|
||||
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/insert.c
|
||||
|
||||
random.o: $(TOP)/src/random.c $(HDR)
|
||||
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/random.c
|
||||
|
||||
select.o: $(TOP)/src/select.c $(HDR)
|
||||
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/select.c
|
||||
|
||||
table.o: $(TOP)/src/table.c $(HDR)
|
||||
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/table.c
|
||||
|
||||
update.o: $(TOP)/src/update.c $(HDR)
|
||||
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/update.c
|
||||
|
||||
tclsqlite.o: $(TOP)/src/tclsqlite.c $(HDR)
|
||||
$(TCC) $(GDBM_FLAGS) $(TCL_FLAGS) -c $(TOP)/src/tclsqlite.c
|
||||
|
||||
printf.o: $(TOP)/src/printf.c $(HDR)
|
||||
$(TCC) $(GDBM_FLAGS) $(TCL_FLAGS) -c $(TOP)/src/printf.c
|
||||
|
||||
gdbmdump: $(TOP)/tool/gdbmdump.c
|
||||
$(TCC) $(GDBM_FLAGS) -o gdbmdump $(TOP)/tool/gdbmdump.c $(LIBGDBM)
|
||||
|
||||
@@ -206,6 +230,9 @@ crosscompile.html: $(TOP)/www/crosscompile.tcl
|
||||
mingw.html: $(TOP)/www/mingw.tcl
|
||||
tclsh $(TOP)/www/mingw.tcl >mingw.html
|
||||
|
||||
tclsqlite.html: $(TOP)/www/tclsqlite.tcl
|
||||
tclsh $(TOP)/www/tclsqlite.tcl >tclsqlite.html
|
||||
|
||||
|
||||
# Files to be published on the website.
|
||||
#
|
||||
@@ -222,7 +249,8 @@ PUBLISH = \
|
||||
vdbe.html \
|
||||
c_interface.html \
|
||||
crosscompile.html \
|
||||
mingw.html
|
||||
mingw.html \
|
||||
tclsqlite.html
|
||||
|
||||
website: $(PUBLISH)
|
||||
|
||||
@@ -230,6 +258,11 @@ publish: $(PUBLISH)
|
||||
chmod 0644 $(PUBLISH)
|
||||
scp $(PUBLISH) hwaci@oak.he.net:public_html/sw/sqlite
|
||||
|
||||
install: sqlite libsqlite.a sqlite.h
|
||||
mv sqlite /usr/bin
|
||||
mv libsqlite.a /usr/lib
|
||||
mv sqlite.h /usr/include
|
||||
|
||||
clean:
|
||||
rm -f *.o sqlite libsqlite.a sqlite.h
|
||||
rm -f lemon lempar.c parse.* sqlite.tar.gz
|
||||
|
||||
@@ -453,7 +453,7 @@ echo > confdefs.h
|
||||
|
||||
# A filename unique to this package, relative to the directory that
|
||||
# configure is in, which we can look for to find out if srcdir is correct.
|
||||
ac_unique_file=src/sqlite.h
|
||||
ac_unique_file=src/sqlite.h.in
|
||||
|
||||
# Find the source files, if location was not specified.
|
||||
if test -z "$srcdir"; then
|
||||
@@ -525,7 +525,7 @@ fi
|
||||
|
||||
|
||||
# The following RCS revision string applies to configure.in
|
||||
# $Revision: 1.5 $
|
||||
# $Revision: 1.8 $
|
||||
|
||||
#########
|
||||
# Make sure we are not building in a subdirectory of the source tree.
|
||||
@@ -1342,7 +1342,7 @@ fi
|
||||
|
||||
fi
|
||||
if test "$found" = "no"; then
|
||||
for dir in /usr/local /usr/X11* /usr/pkg /usr/contrib /usr; do
|
||||
for dir in /usr/local /usr/X11 /usr/X11R6 /usr/pkg /usr/contrib /usr; do
|
||||
|
||||
ac_safe=`echo "$dir/include/tcl.h" | sed 'y%./+-%__p_%'`
|
||||
echo $ac_n "checking for $dir/include/tcl.h""... $ac_c" 1>&6
|
||||
@@ -1374,6 +1374,9 @@ fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if test "$found" = "no"; then
|
||||
TARGET_TCL_INC="-DNO_TCL=1"
|
||||
fi
|
||||
|
||||
|
||||
##########
|
||||
@@ -1387,14 +1390,14 @@ else
|
||||
LIBS=""
|
||||
|
||||
echo $ac_n "checking for library containing gdbm_open""... $ac_c" 1>&6
|
||||
echo "configure:1391: checking for library containing gdbm_open" >&5
|
||||
echo "configure:1394: checking for library containing gdbm_open" >&5
|
||||
if eval "test \"`echo '$''{'ac_cv_search_gdbm_open'+set}'`\" = set"; then
|
||||
echo $ac_n "(cached) $ac_c" 1>&6
|
||||
else
|
||||
ac_func_search_save_LIBS="$LIBS"
|
||||
ac_cv_search_gdbm_open="no"
|
||||
cat > conftest.$ac_ext <<EOF
|
||||
#line 1398 "configure"
|
||||
#line 1401 "configure"
|
||||
#include "confdefs.h"
|
||||
/* Override any gcc2 internal prototype to avoid an error. */
|
||||
/* We use char because int might match the return type of a gcc2
|
||||
@@ -1405,7 +1408,7 @@ int main() {
|
||||
gdbm_open()
|
||||
; return 0; }
|
||||
EOF
|
||||
if { (eval echo configure:1409: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
|
||||
if { (eval echo configure:1412: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
|
||||
rm -rf conftest*
|
||||
ac_cv_search_gdbm_open="none required"
|
||||
else
|
||||
@@ -1416,7 +1419,7 @@ rm -f conftest*
|
||||
test "$ac_cv_search_gdbm_open" = "no" && for i in gdbm; do
|
||||
LIBS="-l$i $ac_func_search_save_LIBS"
|
||||
cat > conftest.$ac_ext <<EOF
|
||||
#line 1420 "configure"
|
||||
#line 1423 "configure"
|
||||
#include "confdefs.h"
|
||||
/* Override any gcc2 internal prototype to avoid an error. */
|
||||
/* We use char because int might match the return type of a gcc2
|
||||
@@ -1427,7 +1430,7 @@ int main() {
|
||||
gdbm_open()
|
||||
; return 0; }
|
||||
EOF
|
||||
if { (eval echo configure:1431: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
|
||||
if { (eval echo configure:1434: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
|
||||
rm -rf conftest*
|
||||
ac_cv_search_gdbm_open="-l$i"
|
||||
break
|
||||
@@ -1455,7 +1458,7 @@ fi
|
||||
# Figure out where to get the GDBM header files.
|
||||
#
|
||||
echo $ac_n "checking GDBM header files""... $ac_c" 1>&6
|
||||
echo "configure:1459: checking GDBM header files" >&5
|
||||
echo "configure:1462: checking GDBM header files" >&5
|
||||
found=no
|
||||
if test "$config_TARGET_GDBM_INC" != ""; then
|
||||
TARGET_GDBM_INC=$config_TARGET_GDBM_INC
|
||||
@@ -1467,17 +1470,17 @@ else
|
||||
echo "$ac_t""not specified: still searching..." 1>&6
|
||||
ac_safe=`echo "gdbm.h" | sed 'y%./+-%__p_%'`
|
||||
echo $ac_n "checking for gdbm.h""... $ac_c" 1>&6
|
||||
echo "configure:1471: checking for gdbm.h" >&5
|
||||
echo "configure:1474: checking for gdbm.h" >&5
|
||||
if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
|
||||
echo $ac_n "(cached) $ac_c" 1>&6
|
||||
else
|
||||
cat > conftest.$ac_ext <<EOF
|
||||
#line 1476 "configure"
|
||||
#line 1479 "configure"
|
||||
#include "confdefs.h"
|
||||
#include <gdbm.h>
|
||||
EOF
|
||||
ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
|
||||
{ (eval echo configure:1481: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
|
||||
{ (eval echo configure:1484: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
|
||||
ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
|
||||
if test -z "$ac_err"; then
|
||||
rm -rf conftest*
|
||||
@@ -1504,7 +1507,7 @@ if test "$found" = "no"; then
|
||||
|
||||
ac_safe=`echo "$dir/include/gdbm.h" | sed 'y%./+-%__p_%'`
|
||||
echo $ac_n "checking for $dir/include/gdbm.h""... $ac_c" 1>&6
|
||||
echo "configure:1508: checking for $dir/include/gdbm.h" >&5
|
||||
echo "configure:1511: checking for $dir/include/gdbm.h" >&5
|
||||
if eval "test \"`echo '$''{'ac_cv_file_$ac_safe'+set}'`\" = set"; then
|
||||
echo $ac_n "(cached) $ac_c" 1>&6
|
||||
else
|
||||
@@ -1545,14 +1548,14 @@ else
|
||||
LIBS=""
|
||||
|
||||
echo $ac_n "checking for library containing readline""... $ac_c" 1>&6
|
||||
echo "configure:1549: checking for library containing readline" >&5
|
||||
echo "configure:1552: checking for library containing readline" >&5
|
||||
if eval "test \"`echo '$''{'ac_cv_search_readline'+set}'`\" = set"; then
|
||||
echo $ac_n "(cached) $ac_c" 1>&6
|
||||
else
|
||||
ac_func_search_save_LIBS="$LIBS"
|
||||
ac_cv_search_readline="no"
|
||||
cat > conftest.$ac_ext <<EOF
|
||||
#line 1556 "configure"
|
||||
#line 1559 "configure"
|
||||
#include "confdefs.h"
|
||||
/* Override any gcc2 internal prototype to avoid an error. */
|
||||
/* We use char because int might match the return type of a gcc2
|
||||
@@ -1563,7 +1566,7 @@ int main() {
|
||||
readline()
|
||||
; return 0; }
|
||||
EOF
|
||||
if { (eval echo configure:1567: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
|
||||
if { (eval echo configure:1570: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
|
||||
rm -rf conftest*
|
||||
ac_cv_search_readline="none required"
|
||||
else
|
||||
@@ -1574,7 +1577,7 @@ rm -f conftest*
|
||||
test "$ac_cv_search_readline" = "no" && for i in readline; do
|
||||
LIBS="-l$i $ac_func_search_save_LIBS"
|
||||
cat > conftest.$ac_ext <<EOF
|
||||
#line 1578 "configure"
|
||||
#line 1581 "configure"
|
||||
#include "confdefs.h"
|
||||
/* Override any gcc2 internal prototype to avoid an error. */
|
||||
/* We use char because int might match the return type of a gcc2
|
||||
@@ -1585,7 +1588,7 @@ int main() {
|
||||
readline()
|
||||
; return 0; }
|
||||
EOF
|
||||
if { (eval echo configure:1589: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
|
||||
if { (eval echo configure:1592: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
|
||||
rm -rf conftest*
|
||||
ac_cv_search_readline="-l$i"
|
||||
break
|
||||
@@ -1613,7 +1616,7 @@ fi
|
||||
# Figure out where to get the READLINE header files.
|
||||
#
|
||||
echo $ac_n "checking readline header files""... $ac_c" 1>&6
|
||||
echo "configure:1617: checking readline header files" >&5
|
||||
echo "configure:1620: checking readline header files" >&5
|
||||
found=no
|
||||
if test "$config_TARGET_READLINE_INC" != ""; then
|
||||
TARGET_READLINE_INC=$config_TARGET_READLINE_INC
|
||||
@@ -1625,17 +1628,17 @@ else
|
||||
echo "$ac_t""not specified: still searching..." 1>&6
|
||||
ac_safe=`echo "readline.h" | sed 'y%./+-%__p_%'`
|
||||
echo $ac_n "checking for readline.h""... $ac_c" 1>&6
|
||||
echo "configure:1629: checking for readline.h" >&5
|
||||
echo "configure:1632: checking for readline.h" >&5
|
||||
if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
|
||||
echo $ac_n "(cached) $ac_c" 1>&6
|
||||
else
|
||||
cat > conftest.$ac_ext <<EOF
|
||||
#line 1634 "configure"
|
||||
#line 1637 "configure"
|
||||
#include "confdefs.h"
|
||||
#include <readline.h>
|
||||
EOF
|
||||
ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
|
||||
{ (eval echo configure:1639: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
|
||||
{ (eval echo configure:1642: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
|
||||
ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
|
||||
if test -z "$ac_err"; then
|
||||
rm -rf conftest*
|
||||
@@ -1662,7 +1665,7 @@ if test "$found" = "no"; then
|
||||
|
||||
ac_safe=`echo "$dir/include/readline.h" | sed 'y%./+-%__p_%'`
|
||||
echo $ac_n "checking for $dir/include/readline.h""... $ac_c" 1>&6
|
||||
echo "configure:1666: checking for $dir/include/readline.h" >&5
|
||||
echo "configure:1669: checking for $dir/include/readline.h" >&5
|
||||
if eval "test \"`echo '$''{'ac_cv_file_$ac_safe'+set}'`\" = set"; then
|
||||
echo $ac_n "(cached) $ac_c" 1>&6
|
||||
else
|
||||
@@ -1691,7 +1694,7 @@ fi
|
||||
|
||||
ac_safe=`echo "$dir/include/readline/readline.h" | sed 'y%./+-%__p_%'`
|
||||
echo $ac_n "checking for $dir/include/readline/readline.h""... $ac_c" 1>&6
|
||||
echo "configure:1695: checking for $dir/include/readline/readline.h" >&5
|
||||
echo "configure:1698: checking for $dir/include/readline/readline.h" >&5
|
||||
if eval "test \"`echo '$''{'ac_cv_file_$ac_safe'+set}'`\" = set"; then
|
||||
echo $ac_n "(cached) $ac_c" 1>&6
|
||||
else
|
||||
@@ -1735,12 +1738,12 @@ fi
|
||||
# Figure out whether or not we have a "usleep()" function.
|
||||
#
|
||||
echo $ac_n "checking for usleep""... $ac_c" 1>&6
|
||||
echo "configure:1739: checking for usleep" >&5
|
||||
echo "configure:1742: checking for usleep" >&5
|
||||
if eval "test \"`echo '$''{'ac_cv_func_usleep'+set}'`\" = set"; then
|
||||
echo $ac_n "(cached) $ac_c" 1>&6
|
||||
else
|
||||
cat > conftest.$ac_ext <<EOF
|
||||
#line 1744 "configure"
|
||||
#line 1747 "configure"
|
||||
#include "confdefs.h"
|
||||
/* System header to define __stub macros and hopefully few prototypes,
|
||||
which can conflict with char usleep(); below. */
|
||||
@@ -1763,7 +1766,7 @@ usleep();
|
||||
|
||||
; return 0; }
|
||||
EOF
|
||||
if { (eval echo configure:1767: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
|
||||
if { (eval echo configure:1770: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
|
||||
rm -rf conftest*
|
||||
eval "ac_cv_func_usleep=yes"
|
||||
else
|
||||
|
||||
+6
-3
@@ -146,12 +146,12 @@
|
||||
# you don't need (for example BLT) by erasing or commenting out
|
||||
# the corresponding code.
|
||||
#
|
||||
AC_INIT(src/sqlite.h)
|
||||
AC_INIT(src/sqlite.h.in)
|
||||
|
||||
dnl Put the RCS revision string after AC_INIT so that it will also
|
||||
dnl show in in configure.
|
||||
# The following RCS revision string applies to configure.in
|
||||
# $Revision: 1.5 $
|
||||
# $Revision: 1.8 $
|
||||
|
||||
#########
|
||||
# Make sure we are not building in a subdirectory of the source tree.
|
||||
@@ -408,7 +408,7 @@ else
|
||||
AC_CHECK_HEADER(tcl.h, [found=yes])
|
||||
fi
|
||||
if test "$found" = "no"; then
|
||||
for dir in /usr/local /usr/X11* /usr/pkg /usr/contrib /usr; do
|
||||
for dir in /usr/local /usr/X11 /usr/X11R6 /usr/pkg /usr/contrib /usr; do
|
||||
AC_CHECK_FILE($dir/include/tcl.h, found=yes)
|
||||
if test "$found" = "yes"; then
|
||||
TARGET_TCL_INC="-I$dir/include"
|
||||
@@ -416,6 +416,9 @@ if test "$found" = "no"; then
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if test "$found" = "no"; then
|
||||
TARGET_TCL_INC="-DNO_TCL=1"
|
||||
fi
|
||||
AC_SUBST(TARGET_TCL_INC)
|
||||
|
||||
##########
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
The SQL database used for ACD contains 113 tables and indices implemented
|
||||
in GDBM. The following are statistics on the sizes of keys and data
|
||||
within these tables and indices.
|
||||
|
||||
Entries: 962080
|
||||
Size: 45573853
|
||||
Avg Size: 48
|
||||
Key Size: 11045299
|
||||
Avg Key Size: 12
|
||||
Max Key Size: 99
|
||||
|
||||
0..8 266 0%
|
||||
9..12 5485 0%
|
||||
13..16 73633 8%
|
||||
17..24 180918 27%
|
||||
25..32 209823 48%
|
||||
33..40 148995 64%
|
||||
41..48 76304 72%
|
||||
49..56 14346 73%
|
||||
57..64 15725 75%
|
||||
65..80 44916 80%
|
||||
81..96 127815 93%
|
||||
97..112 34769 96%
|
||||
113..128 13314 98%
|
||||
129..144 8098 99%
|
||||
145..160 3355 99%
|
||||
161..176 1159 99%
|
||||
177..192 629 99%
|
||||
193..208 221 99%
|
||||
209..224 210 99%
|
||||
225..240 129 99%
|
||||
241..256 57 99%
|
||||
257..288 496 99%
|
||||
289..320 60 99%
|
||||
321..352 37 99%
|
||||
353..384 46 99%
|
||||
385..416 22 99%
|
||||
417..448 24 99%
|
||||
449..480 26 99%
|
||||
481..512 27 99%
|
||||
513..1024 471 99%
|
||||
1025..2048 389 99%
|
||||
2049..4096 182 99%
|
||||
4097..8192 74 99%
|
||||
8193..16384 34 99%
|
||||
16385..32768 17 99%
|
||||
32769..65536 5 99%
|
||||
65537..131073 3 100%
|
||||
@@ -1,76 +1,97 @@
|
||||
C Version\s1.0\s(CVS\s499)
|
||||
D 2000-08-17T10:25:00
|
||||
C Version\s1.0.21\s(CVS\s482)
|
||||
D 2001-02-19T18:30:00
|
||||
F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4
|
||||
F Makefile.in 8a4cc1bdcbb870f76d0c769136cf973af07586a4
|
||||
F Makefile.in 7efa81e2985b45ba73db27d55b70cc927f5abfd7
|
||||
F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958
|
||||
F VERSION 61652cd1568dcf2614df833eba241755eee34e89
|
||||
F configure 51063d594190fa085f909cefc9427241088bec4f x
|
||||
F configure.in a04f02ba61ed09a00e862b4f78b91b06a559e0b5
|
||||
F VERSION 4686794d487da05ffcbf92801fd0754f6dfa0c39
|
||||
F configure 3dc1edb9dcf60215e31ff72b447935ab62211442 x
|
||||
F configure.in d892ca33db7e88a055519ce2f36dcb11020e8fff
|
||||
F doc/lemon.html e233a3e97a779c7a87e1bc4528c664a58e49dd47
|
||||
F src/build.c 4d90e9e94750ca80249fc7958c617021d8bb7a50
|
||||
F src/dbbe.c 226daaf8c095ceb4aff48cad188dad90643f9867
|
||||
F src/dbbe.h 6337132f904e72ecb28b07390021c241397e4cbf
|
||||
F src/delete.c 4d491eaf61b515516749c7ed68fa3b2ee8a09065
|
||||
F src/expr.c 2fa63f086707176d09092e71832f9bbdc6a8ac85
|
||||
F src/insert.c f146f149ad2422a1dc3bfa7a1651a25940f98958
|
||||
F src/main.c ba16b81890d962821bb90f0a4de9a29b0e495eb2
|
||||
F src/parse.y 5d199034de5d29ebedb42c1c51f34db4df40cbe5
|
||||
F src/select.c d382e96c2221d08367cc87976f2b574537c9de97
|
||||
F src/shell.c 0aabf9e5e89fc8ac7a56de7f05255ed6864170f2
|
||||
F doc/report1.txt ad0a41513479f1be0355d1f3f074e66779ff2282
|
||||
F src/TODO 38a68a489e56e9fd4a96263e0ff9404a47368ad4
|
||||
F src/build.c 7aa5879bf58ea6bbff22c26c59d1130021fa6ca4
|
||||
F src/dbbe.c 162d29b09ac379f160892c5795efc14099dcc8eb
|
||||
F src/dbbe.h 0435a36906a839cce062608f51bd9d3e79878fec
|
||||
F src/dbbegdbm.c 5bfcb1b4ee47a98c5eae83041e9716cd3233fd0e
|
||||
F src/dbbemem.c 215e107830ddf0d5a565bb7c20dd7200a925ef75
|
||||
F src/delete.c b83dc815f83220a82df13f1f1f479187d305fe6a
|
||||
F src/ex/README b745b00acce2d892f60c40111dacdfc48e0c1c7a
|
||||
F src/ex/db.c f1419ae6c93e40b5ac6e39fe7efd95d868e6f9d7
|
||||
F src/ex/db.h 3f2933ee20c147fe494835786e4c6f3a562def4e
|
||||
F src/ex/dbbebdb1.c 61ed414307f829478614def33de640bbe5b2f770
|
||||
F src/ex/dbbemird.c b00aef85656fa0a101dac2c32e12922ad106715a
|
||||
F src/ex/pg.c 2bbf6a94f37226d06337868b6bf4d7affc60197f
|
||||
F src/ex/pg.h 23a4ac807b0546ec2bb6239ec8bd3e06926572cd
|
||||
F src/ex/sizes.tcl f54bad4a2ac567624be59131a6ee42d71b41a3d7
|
||||
F src/expr.c 49bc261fdc4f4fb91c74cd668a9a952c00e85931
|
||||
F src/insert.c 4bc1cab84f7805d560a1417734a532843e30b762
|
||||
F src/main.c 5afe29c425b875acede20f609485866eb5b276f6
|
||||
F src/parse.y 25ee4d8efccc4b247c32fe4ab194e3dd8fd5a4ee
|
||||
F src/printf.c af0dc65c293427272e1949c7807b1d88f10004fd
|
||||
F src/random.c b36c3f57dc80c8f354e6bfbf39cf1e1de021d54a
|
||||
F src/select.c 0cadab95c8011ddbffe804de94f12f3c0e317863
|
||||
F src/shell.c 441e20913cde0bb71281f4027623c623530241cd
|
||||
F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e
|
||||
F src/sqlite.h.in fe04454ae51f9f3717179ffe5a10a25874643ced
|
||||
F src/sqliteInt.h f6d1e139b3bfa4ceff2136684e19d76b53178ec0
|
||||
F src/tclsqlite.c 89dc4ba2b521f3e919d6d7aaa4cc1c2aba8e16f3
|
||||
F src/tokenize.c 097bec5843d4a0fb4509e036fee93bac080c5e73
|
||||
F src/update.c 51b9ef7434b15e31096155da920302e9db0d27fc
|
||||
F src/util.c b75b33e6bd5d47898bb7ed9fdd0dea4fe7c19b00
|
||||
F src/vdbe.c bdedf21230581f0cf73a2dcd8fe23f30cf30ebe6
|
||||
F src/vdbe.h 6c5653241633c583549c2d8097394ab52550eb63
|
||||
F src/where.c 420f666a38b405cd58bd7af832ed99f1dbc7d336
|
||||
F test/all.test 0950c135cab7e60c07bd745ccfad1476211e5bd7
|
||||
F src/sqlite.h.in 7c1a53f020418d89d13ed2fe9c477ff54540755d
|
||||
F src/sqliteInt.h fd513fa6b7ac94919f85ebfa183aaa194284ce16
|
||||
F src/table.c 5be76051a8ed6f6bfa641f4adc52529efa34fbf9
|
||||
F src/tclsqlite.c 2a925e1835c348f07dd17c87d95ae9a577833407
|
||||
F src/test.file 55ca6286e3e4f4fba5d0448333fa99fc5a404a73
|
||||
F src/tokenize.c c7ad428f38e56342eb2025320480b5ae9ece1b90
|
||||
F src/update.c 9692fbac8e95fdbc5318d39db576aa6c57b9c8ab
|
||||
F src/util.c 0298100e6427a4b644f767ede12276fa7170fbb6
|
||||
F src/vdbe.c 6e613f25b0fe1c81b097f46a8fe68c68c39a6abf
|
||||
F src/vdbe.h d94224ad39c8e9de83dda8d8f960059eb71c0780
|
||||
F src/where.c e0865f52398662b541bea29ded6aa9687d9f3645
|
||||
F test/all.test 15cac2f6b2d4c55bf896212aff3cc9d6597b0490
|
||||
F test/copy.test b77a1214bd7756f2849d5c4fa6e715c0ff0c34eb
|
||||
F test/dbbe.test c6079572516aeb2739a35fd272b105c45c30cc76
|
||||
F test/dbbe.test 27deeebf2a01da97cabaab8dc7f34ca3b51a0123
|
||||
F test/delete.test 402ee3ccb6e544582d24c573ef70b34d09583ae7
|
||||
F test/expr.test 09b55ccf81cb8cc2f9cd83d592a2ba187ee48ba8
|
||||
F test/expr.test 48273bf48a15d226c35829f702af4254c0ff6795
|
||||
F test/func.test 02aed8845b98bde1043dda97455de1d37238ebb3
|
||||
F test/in.test 2c560c0f55fb777029fd9bb5378f2997582aa603
|
||||
F test/index.test 950be6116122c6e2db7c2c345eabcdb854ced1d0
|
||||
F test/index.test ee060ef8912be47ba616e50cce7985259a68d58a
|
||||
F test/insert.test 66f4c3bd600fec8eb1e733b928cbe6fa885eff0c
|
||||
F test/insert2.test 732405e30331635af8d159fccabe835eea5cd0c6
|
||||
F test/lock.test 72f3b11be44f29739cb270b14faea2687dedde7d
|
||||
F test/main.test b7366cc6f3690915a11834bc1090deeff08acaf9
|
||||
F test/lock.test bf6d3f8a0a91c6673ff5e357b7a1397e241f7f14
|
||||
F test/main.test 83cfa58b0b9b69924cc915d5c50b12f0ac9e40e7
|
||||
F test/select1.test 68ff778c24fc8982e63dda37acb5b0396913adf7
|
||||
F test/select2.test 45c28211702b5c82b06dd624a112ea17417f343c
|
||||
F test/select2.test 0c24b9bb8825ebb96e6cc65f1eb61bace0e02aa0
|
||||
F test/select3.test a9234b8424b6c6d71de534f43b91ade9be68e9cc
|
||||
F test/select4.test cb5374d7c87680e294ac749307459a5cc547609d
|
||||
F test/select5.test e2b9d51d88cbd6c307c2c05b0ef55fe7ba811ac2
|
||||
F test/sort.test d582086c4bb7df3fbf50aa72e69d7e235e9f8e31
|
||||
F test/subselect.test bf8b251a92fb091973c1c469ce499dc9648a41d5
|
||||
F test/table.test 620cd72a6c29da3b9153d15c9e94abbbb282373b
|
||||
F test/tester.tcl 95b286791e6256bb6db0165f9342c70fff549a62
|
||||
F test/update.test 62f6ce99ff31756aab0ca832ff6d34c5a87b6250
|
||||
F test/vacuum.test 8becf5cfeb897108b35cdd996793e7f1df2f28fd
|
||||
F test/table.test eaa25951c0f18615763cd3dc248ea4bc38739c05
|
||||
F test/tester.tcl e24caef6d07c58c16b24e7afc967464b288a4065
|
||||
F test/update.test 72c0c93310483b86dc904a992220c5b84c7ce100
|
||||
F test/vacuum.test 2127748ff4ddb409212efbb6d9fb9c469ea1b49c
|
||||
F test/where.test bbab5a308055fb6087dc23d600b4ad2b72797397
|
||||
F tool/gdbmdump.c 529e67c78d920606ba196326ea55b57b75fcc82b
|
||||
F tool/lemon.c 1f0e96515c12e9e413f5b8fdebc79ddcf18ddc9d
|
||||
F tool/gdbmstat.c 56a9033531e5f5a48413f6ec436d5fb0341632c1
|
||||
F tool/lemon.c b13a31798574af881753d38f4da7d505929259c3
|
||||
F tool/lempar.c a1eec94d6eacc12332368660ec65f3b248853833
|
||||
F tool/memleak.awk a0a11dd84bf4582acc81c3c61271021ae49b3f15
|
||||
F tool/opNames.awk 2bd9071a138e4e2be13dc98fe066398a61219e1e
|
||||
F tool/opcodeDoc.awk b3a2a3d5d3075b8bd90b7afe24283efdd586659c
|
||||
F tool/renumberOps.awk 6d067177ad5f8d711b79577b462da9b3634bd0a9
|
||||
F www/arch.fig 4e26e9dca3c49724fc8f554c695ddea9f2413156
|
||||
F www/arch.png c4d908b79065a72e7dcf19317f36d1324c550e87
|
||||
F www/arch.tcl 4f6a9afecc099a27bba17b4f8cc9561abc15dc40
|
||||
F www/c_interface.tcl 29593cf77025bab137b7ba64b9459eb5eb6b4873
|
||||
F www/changes.tcl 32a1a09297095ffa3e49473732e4b60fc6cdc7c1
|
||||
F www/crosscompile.tcl 19734ce7f18b16ff2ed8479412abf8aca56e1dcc
|
||||
F tool/report1.txt 9eae07f26a8fc53889b45fc833a66a33daa22816
|
||||
F www/arch.fig 4f246003b7da23bd63b8b0af0618afb4ee3055c8
|
||||
F www/arch.png 8dae0766d42ed3de9ed013c1341a5792bcf633e6
|
||||
F www/arch.tcl a40380c1fe0080c43e6cc5c20ed70731511b06be
|
||||
F www/c_interface.tcl 11be2d5826eb7d6efd629751d3b483c1ed78ba14
|
||||
F www/changes.tcl 50b0890caa8e2b9622237aef27bdf92c90255b35
|
||||
F www/crosscompile.tcl c99efacb3aefaa550c6e80d91b240f55eb9fd33e
|
||||
F www/dynload.tcl 02eb8273aa78cfa9070dd4501dca937fb22b466c
|
||||
F www/fileformat.tcl cfb7fba80b7275555281ba2f256c00734bcdd1c9
|
||||
F www/index.tcl 6dae7e07d110ce06c327223365dc1b475b4fb6ee
|
||||
F www/index.tcl 02fe82f3a50d5f5b831afeafb62fc64ea5e63b32
|
||||
F www/lang.tcl 9192e114b19987e630a41e879585b87006eb84a1
|
||||
F www/mingw.tcl fc5f4ba9d336b6e8c97347cc6496d6162461ef60
|
||||
F www/opcode.tcl cb3a1abf8b7b9be9f3a228d097d6bf8b742c2b6f
|
||||
F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f
|
||||
F www/vdbe.tcl bcbfc33bcdd0ebad95eab31286adb9e1bc289520
|
||||
P 5ec2b094781bb3c32b0ef0284aa4dc2b26d9e873
|
||||
R 55d9f580b0fd85533f8ccc00b91b305d
|
||||
F www/tclsqlite.tcl 06f81c401f79a04f2c5ebfb97e7c176225c0aef2
|
||||
F www/vdbe.tcl 0c8aaa529dd216ccbf7daaabd80985e413d5f9ad
|
||||
P 99aae9aaa9f53d1be6600f8598f66594692aae39
|
||||
R 59db407686a1360be2fb4d74bb42df04
|
||||
U drh
|
||||
Z f346b6602fac1a26cb3e2b4625824e0f
|
||||
Z 7f2f78067b526c415a8e7024c34c1b61
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
f37dd18e3fc6314eda2af786d74e0a445b9b2287
|
||||
7a1147ff52df5f78b1a2e3502c1b78e4d3906968
|
||||
@@ -0,0 +1,35 @@
|
||||
* Finish fleshing out the db.c file.
|
||||
- sqliteDbReadOvfl
|
||||
- sqliteDbSpreadLoad
|
||||
- sqliteDbSplit
|
||||
- sqliteDbNextIndexLevel
|
||||
- fix sqliteDbCursorNext to work right after sqliteDbCursorDelete
|
||||
* Compile db.c with -Wall and get no errors.
|
||||
* Make a pass over pg.c and db.c looking for errors.
|
||||
- correct handling of I/O errors, malloc failures, etc.
|
||||
- page leaks (not calling sqlitePgUnref)
|
||||
* Write a test interface for db.c.
|
||||
* Compile and link against the db test interface.
|
||||
* Generate tests for the db interface.
|
||||
* Add read/write locks to pg.c
|
||||
* Add an sqliteDbReorganize() function.
|
||||
* Integrate db into vdbe.
|
||||
* Modify code generation to take advantage of the new db interface.
|
||||
- Able to delete without disturbing scan order
|
||||
- Now keeps a count of number of table entries
|
||||
+ Special processing for count(*)
|
||||
+ Better selection of indices on a select
|
||||
- Transactions
|
||||
* Modify sqlite_master to store the table number.
|
||||
* Add a cache in DbCursor to speed up the sqliteDbReadOvfl() routine.
|
||||
* Add cache information to speed up sqliteDbCursorMoveTo().
|
||||
|
||||
Longer term:
|
||||
* Document all the changes and release Sqlite 2.0.
|
||||
* Techniques for optimizing querys by grouping data with similar
|
||||
indices.
|
||||
* "OPTIMIZE select" statement to automatically create and/or tune
|
||||
indices.
|
||||
* "CREATE INDEX FOR select" to automatically generate needed indices.
|
||||
* "VACUUM table USING index".
|
||||
* Parse and use constraints.
|
||||
+16
-14
@@ -33,7 +33,7 @@
|
||||
** COPY
|
||||
** VACUUM
|
||||
**
|
||||
** $Id: build.c,v 1.23 2000/08/03 15:09:20 drh Exp $
|
||||
** $Id: build.c,v 1.25 2001/01/15 22:51:09 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -48,20 +48,22 @@
|
||||
** no VDBE code was generated.
|
||||
*/
|
||||
void sqliteExec(Parse *pParse){
|
||||
int rc = SQLITE_OK;
|
||||
if( pParse->pVdbe ){
|
||||
if( pParse->explain ){
|
||||
sqliteVdbeList(pParse->pVdbe, pParse->xCallback, pParse->pArg,
|
||||
&pParse->zErrMsg);
|
||||
rc = sqliteVdbeList(pParse->pVdbe, pParse->xCallback, pParse->pArg,
|
||||
&pParse->zErrMsg);
|
||||
}else{
|
||||
FILE *trace = (pParse->db->flags & SQLITE_VdbeTrace)!=0 ? stderr : 0;
|
||||
sqliteVdbeTrace(pParse->pVdbe, trace);
|
||||
sqliteVdbeExec(pParse->pVdbe, pParse->xCallback, pParse->pArg,
|
||||
&pParse->zErrMsg, pParse->db->pBusyArg,
|
||||
pParse->db->xBusyCallback);
|
||||
rc = sqliteVdbeExec(pParse->pVdbe, pParse->xCallback, pParse->pArg,
|
||||
&pParse->zErrMsg, pParse->db->pBusyArg,
|
||||
pParse->db->xBusyCallback);
|
||||
}
|
||||
sqliteVdbeDelete(pParse->pVdbe);
|
||||
pParse->pVdbe = 0;
|
||||
pParse->colNamesSet = 0;
|
||||
pParse->rc = rc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +341,7 @@ void sqliteEndTable(Parse *pParse, Token *pEnd){
|
||||
*/
|
||||
if( !pParse->initFlag ){
|
||||
static VdbeOp addTable[] = {
|
||||
{ OP_Open, 0, 1, MASTER_NAME },
|
||||
{ OP_OpenTbl, 0, 1, MASTER_NAME },
|
||||
{ OP_New, 0, 0, 0},
|
||||
{ OP_String, 0, 0, "table" },
|
||||
{ OP_String, 0, 0, 0}, /* 3 */
|
||||
@@ -414,7 +416,7 @@ void sqliteDropTable(Parse *pParse, Token *pName){
|
||||
v = sqliteGetVdbe(pParse);
|
||||
if( v ){
|
||||
static VdbeOp dropTable[] = {
|
||||
{ OP_Open, 0, 1, MASTER_NAME },
|
||||
{ OP_OpenTbl, 0, 1, MASTER_NAME },
|
||||
{ OP_ListOpen, 0, 0, 0},
|
||||
{ OP_String, 0, 0, 0}, /* 2 */
|
||||
{ OP_Next, 0, ADDR(10), 0}, /* 3 */
|
||||
@@ -591,7 +593,7 @@ void sqliteCreateIndex(
|
||||
*/
|
||||
if( pParse->initFlag==0 ){
|
||||
static VdbeOp addTable[] = {
|
||||
{ OP_Open, 2, 1, MASTER_NAME},
|
||||
{ OP_OpenTbl, 2, 1, MASTER_NAME},
|
||||
{ OP_New, 2, 0, 0},
|
||||
{ OP_String, 0, 0, "index"},
|
||||
{ OP_String, 0, 0, 0}, /* 3 */
|
||||
@@ -608,8 +610,8 @@ void sqliteCreateIndex(
|
||||
|
||||
v = sqliteGetVdbe(pParse);
|
||||
if( v==0 ) goto exit_create_index;
|
||||
sqliteVdbeAddOp(v, OP_Open, 0, 0, pTab->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_Open, 1, 1, pIndex->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenTbl, 0, 0, pTab->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, 1, 1, pIndex->zName, 0);
|
||||
if( pStart && pEnd ){
|
||||
int base;
|
||||
n = (int)pEnd->z - (int)pStart->z + 1;
|
||||
@@ -668,7 +670,7 @@ void sqliteDropIndex(Parse *pParse, Token *pName){
|
||||
v = sqliteGetVdbe(pParse);
|
||||
if( v ){
|
||||
static VdbeOp dropIndex[] = {
|
||||
{ OP_Open, 0, 1, MASTER_NAME},
|
||||
{ OP_OpenTbl, 0, 1, MASTER_NAME},
|
||||
{ OP_ListOpen, 0, 0, 0},
|
||||
{ OP_String, 0, 0, 0}, /* 2 */
|
||||
{ OP_Next, 0, ADDR(9), 0}, /* 3 */
|
||||
@@ -841,9 +843,9 @@ void sqliteCopy(
|
||||
addr = sqliteVdbeAddOp(v, OP_FileOpen, 0, 0, 0, 0);
|
||||
sqliteVdbeChangeP3(v, addr, pFilename->z, pFilename->n);
|
||||
sqliteVdbeDequoteP3(v, addr);
|
||||
sqliteVdbeAddOp(v, OP_Open, 0, 1, pTab->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenTbl, 0, 1, pTab->zName, 0);
|
||||
for(i=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
|
||||
sqliteVdbeAddOp(v, OP_Open, i, 1, pIdx->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, i, 1, pIdx->zName, 0);
|
||||
}
|
||||
end = sqliteVdbeMakeLabel(v);
|
||||
addr = sqliteVdbeAddOp(v, OP_FileRead, pTab->nCol, end, 0, 0);
|
||||
|
||||
+89
-631
@@ -30,124 +30,18 @@
|
||||
** relatively simple to convert to a different database such
|
||||
** as NDBM, SDBM, or BerkeleyDB.
|
||||
**
|
||||
** $Id: dbbe.c,v 1.19 2000/08/17 09:50:00 drh Exp $
|
||||
** $Id: dbbe.c,v 1.23 2001/01/15 22:51:10 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <gdbm.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
/*
|
||||
** Information about each open disk file is an instance of this
|
||||
** structure. There will only be one such structure for each
|
||||
** disk file. If the VDBE opens the same file twice (as will happen
|
||||
** for a self-join, for example) then two DbbeCursor structures are
|
||||
** created but there is only a single BeFile structure with an
|
||||
** nRef of 2.
|
||||
*/
|
||||
typedef struct BeFile BeFile;
|
||||
struct BeFile {
|
||||
char *zName; /* Name of the file */
|
||||
GDBM_FILE dbf; /* The file itself */
|
||||
int nRef; /* Number of references */
|
||||
int delOnClose; /* Delete when closing */
|
||||
int writeable; /* Opened for writing */
|
||||
BeFile *pNext, *pPrev; /* Next and previous on list of open files */
|
||||
};
|
||||
|
||||
/*
|
||||
** The following structure holds the current state of the RC4 algorithm.
|
||||
** We use RC4 as a random number generator. Each call to RC4 gives
|
||||
** a random 8-bit number.
|
||||
**
|
||||
** Nothing in this file or anywhere else in SQLite does any kind of
|
||||
** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random
|
||||
** number generator) not as an encryption device.
|
||||
*/
|
||||
struct rc4 {
|
||||
int i, j;
|
||||
int s[256];
|
||||
};
|
||||
|
||||
/*
|
||||
** The complete database is an instance of the following structure.
|
||||
*/
|
||||
struct Dbbe {
|
||||
char *zDir; /* The directory containing the database */
|
||||
int write; /* True for write permission */
|
||||
BeFile *pOpen; /* List of open files */
|
||||
int nTemp; /* Number of temporary files created */
|
||||
FILE **apTemp; /* Space to hold temporary file pointers */
|
||||
char **azTemp; /* Names of the temporary files */
|
||||
struct rc4 rc4; /* The random number generator */
|
||||
};
|
||||
|
||||
/*
|
||||
** An cursor into a database file is an instance of the following structure.
|
||||
** There can only be a single BeFile structure for each disk file, but
|
||||
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
|
||||
** a cursor pointing to a particular part of the open BeFile. The
|
||||
** BeFile.nRef field hold a count of the number of DbbeCursor structures
|
||||
** associated with the same disk file.
|
||||
*/
|
||||
struct DbbeCursor {
|
||||
Dbbe *pBe; /* The database of which this record is a part */
|
||||
BeFile *pFile; /* The database file for this table */
|
||||
datum key; /* Most recently used key */
|
||||
datum data; /* Most recent data */
|
||||
int needRewind; /* Next key should be the first */
|
||||
int readPending; /* The fetch hasn't actually been done yet */
|
||||
};
|
||||
|
||||
/*
|
||||
** Initialize the RC4 PRNG. "seed" is a pointer to some random
|
||||
** data used to initialize the PRNG.
|
||||
*/
|
||||
static void rc4init(struct rc4 *p, char *seed, int seedlen){
|
||||
int i;
|
||||
char k[256];
|
||||
p->j = 0;
|
||||
p->i = 0;
|
||||
for(i=0; i<256; i++){
|
||||
p->s[i] = i;
|
||||
k[i] = seed[i%seedlen];
|
||||
}
|
||||
for(i=0; i<256; i++){
|
||||
int t;
|
||||
p->j = (p->j + p->s[i] + k[i]) & 0xff;
|
||||
t = p->s[p->j];
|
||||
p->s[p->j] = p->s[i];
|
||||
p->s[i] = t;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a single 8-bit random value from the RC4 PRNG.
|
||||
*/
|
||||
static int rc4byte(struct rc4 *p){
|
||||
int t;
|
||||
p->i = (p->i + 1) & 0xff;
|
||||
p->j = (p->j + p->s[p->i]) & 0xff;
|
||||
t = p->s[p->i];
|
||||
p->s[p->i] = p->s[p->j];
|
||||
p->s[p->j] = t;
|
||||
t = p->s[p->i] + p->s[p->j];
|
||||
return t & 0xff;
|
||||
}
|
||||
|
||||
/*
|
||||
** The "mkdir()" function only takes one argument under Windows.
|
||||
*/
|
||||
#if OS_WIN
|
||||
# define mkdir(A,B) mkdir(A)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** This routine opens a new database. For the GDBM driver
|
||||
** implemented here, the database name is the name of the directory
|
||||
** containing all the files of the database.
|
||||
** This routine opens a new database. It looks at the first
|
||||
** few characters of the database name to try to determine what
|
||||
** kind of database to open. If the first characters are "gdbm:",
|
||||
** then it uses the GDBM driver. If the first few characters are
|
||||
** "memory:" then it uses the in-memory driver. If there is no
|
||||
** match, the default to the GDBM driver.
|
||||
**
|
||||
** If successful, a pointer to the Dbbe structure is returned.
|
||||
** If there are errors, an appropriate error message is left
|
||||
@@ -159,522 +53,15 @@ Dbbe *sqliteDbbeOpen(
|
||||
int createFlag, /* True to create database if it doesn't exist */
|
||||
char **pzErrMsg /* Write error messages (if any) here */
|
||||
){
|
||||
Dbbe *pNew;
|
||||
struct stat statbuf;
|
||||
char *zMaster;
|
||||
|
||||
if( !writeFlag ) createFlag = 0;
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
if( createFlag ) mkdir(zName, 0750);
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
sqliteSetString(pzErrMsg, createFlag ?
|
||||
"can't find or create directory \"" : "can't find directory \"",
|
||||
zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
extern Dbbe *sqliteGdbmOpen(const char*,int,int,char**);
|
||||
if( strncmp(zName, "gdbm:", 5)==0 ){
|
||||
return sqliteGdbmOpen(&zName[5], writeFlag, createFlag, pzErrMsg);
|
||||
}
|
||||
if( !S_ISDIR(statbuf.st_mode) ){
|
||||
sqliteSetString(pzErrMsg, "not a directory: \"", zName, "\"", 0);
|
||||
return 0;
|
||||
if( strncmp(zName, "memory:", 7)==0 ){
|
||||
extern Dbbe *sqliteMemOpen(const char*,int,int,char**);
|
||||
return sqliteMemOpen(&zName[7], writeFlag, createFlag, pzErrMsg);
|
||||
}
|
||||
if( access(zName, writeFlag ? (X_OK|W_OK|R_OK) : (X_OK|R_OK)) ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied", 0);
|
||||
return 0;
|
||||
}
|
||||
zMaster = 0;
|
||||
sqliteSetString(&zMaster, zName, "/" MASTER_NAME ".tbl", 0);
|
||||
if( stat(zMaster, &statbuf)==0
|
||||
&& access(zMaster, writeFlag ? (W_OK|R_OK) : R_OK)!=0 ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied for ", zMaster, 0);
|
||||
sqliteFree(zMaster);
|
||||
return 0;
|
||||
}
|
||||
sqliteFree(zMaster);
|
||||
pNew = sqliteMalloc(sizeof(Dbbe) + strlen(zName) + 1);
|
||||
if( pNew==0 ){
|
||||
sqliteSetString(pzErrMsg, "out of memory", 0);
|
||||
return 0;
|
||||
}
|
||||
pNew->zDir = (char*)&pNew[1];
|
||||
strcpy(pNew->zDir, zName);
|
||||
pNew->write = writeFlag;
|
||||
pNew->pOpen = 0;
|
||||
time(&statbuf.st_ctime);
|
||||
rc4init(&pNew->rc4, (char*)&statbuf, sizeof(statbuf));
|
||||
return pNew;
|
||||
}
|
||||
|
||||
/*
|
||||
** Completely shutdown the given database. Close all files. Free all memory.
|
||||
*/
|
||||
void sqliteDbbeClose(Dbbe *pBe){
|
||||
BeFile *pFile, *pNext;
|
||||
int i;
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pNext){
|
||||
pNext = pFile->pNext;
|
||||
gdbm_close(pFile->dbf);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
for(i=0; i<pBe->nTemp; i++){
|
||||
if( pBe->apTemp[i]!=0 ){
|
||||
unlink(pBe->azTemp[i]);
|
||||
fclose(pBe->apTemp[i]);
|
||||
sqliteFree(pBe->azTemp[i]);
|
||||
pBe->apTemp[i] = 0;
|
||||
pBe->azTemp[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sqliteFree(pBe->azTemp);
|
||||
sqliteFree(pBe->apTemp);
|
||||
memset(pBe, 0, sizeof(*pBe));
|
||||
sqliteFree(pBe);
|
||||
}
|
||||
|
||||
/*
|
||||
** Translate the name of an SQL table (or index) into the name
|
||||
** of a file that holds the key/data pairs for that table or
|
||||
** index. Space to hold the filename is obtained from
|
||||
** sqliteMalloc() and must be freed by the calling function.
|
||||
*/
|
||||
static char *sqliteFileOfTable(Dbbe *pBe, const char *zTable){
|
||||
char *zFile = 0;
|
||||
int i;
|
||||
sqliteSetString(&zFile, pBe->zDir, "/", zTable, ".tbl", 0);
|
||||
if( zFile==0 ) return 0;
|
||||
for(i=strlen(pBe->zDir)+1; zFile[i]; i++){
|
||||
int c = zFile[i];
|
||||
if( isupper(c) ){
|
||||
zFile[i] = tolower(c);
|
||||
}else if( !isalnum(c) && c!='-' && c!='_' && c!='.' ){
|
||||
zFile[i] = '+';
|
||||
}
|
||||
}
|
||||
return zFile;
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate a random filename with the given prefix. The new filename
|
||||
** is written into zBuf[]. The calling function must insure that
|
||||
** zBuf[] is big enough to hold the prefix plus 20 or so extra
|
||||
** characters.
|
||||
**
|
||||
** Very random names are chosen so that the chance of a
|
||||
** collision with an existing filename is very very small.
|
||||
*/
|
||||
static void randomName(struct rc4 *pRc4, char *zBuf, char *zPrefix){
|
||||
int i, j;
|
||||
static const char zRandomChars[] = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
strcpy(zBuf, zPrefix);
|
||||
j = strlen(zBuf);
|
||||
for(i=0; i<15; i++){
|
||||
int c = rc4byte(pRc4) % (sizeof(zRandomChars) - 1);
|
||||
zBuf[j++] = zRandomChars[c];
|
||||
}
|
||||
zBuf[j] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a new table cursor. Write a pointer to the corresponding
|
||||
** DbbeCursor structure into *ppCursr. Return an integer success
|
||||
** code:
|
||||
**
|
||||
** SQLITE_OK It worked!
|
||||
**
|
||||
** SQLITE_NOMEM sqliteMalloc() failed
|
||||
**
|
||||
** SQLITE_PERM Attempt to access a file for which file
|
||||
** access permission is denied
|
||||
**
|
||||
** SQLITE_BUSY Another thread or process is already using
|
||||
** the corresponding file and has that file locked.
|
||||
**
|
||||
** SQLITE_READONLY The current thread already has this file open
|
||||
** readonly but you are trying to open for writing.
|
||||
** (This can happen if a SELECT callback tries to
|
||||
** do an UPDATE or DELETE.)
|
||||
**
|
||||
** If zTable is 0 or "", then a temporary database file is created and
|
||||
** a cursor to that temporary file is opened. The temporary file
|
||||
** will be deleted from the disk when it is closed.
|
||||
*/
|
||||
int sqliteDbbeOpenCursor(
|
||||
Dbbe *pBe, /* The database the table belongs to */
|
||||
const char *zTable, /* The SQL name of the file to be opened */
|
||||
int writeable, /* True to open for writing */
|
||||
DbbeCursor **ppCursr /* Write the resulting table pointer here */
|
||||
){
|
||||
char *zFile; /* Name of the table file */
|
||||
DbbeCursor *pCursr; /* The new table cursor */
|
||||
BeFile *pFile; /* The underlying data file for this table */
|
||||
int rc = SQLITE_OK; /* Return value */
|
||||
int rw_mask; /* Permissions mask for opening a table */
|
||||
int mode; /* Mode for opening a table */
|
||||
|
||||
*ppCursr = 0;
|
||||
pCursr = sqliteMalloc( sizeof(*pCursr) );
|
||||
if( pCursr==0 ) return SQLITE_NOMEM;
|
||||
if( zTable ){
|
||||
zFile = sqliteFileOfTable(pBe, zTable);
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pFile->pNext){
|
||||
if( strcmp(pFile->zName,zFile)==0 ) break;
|
||||
}
|
||||
}else{
|
||||
pFile = 0;
|
||||
zFile = 0;
|
||||
}
|
||||
if( pFile==0 ){
|
||||
if( writeable ){
|
||||
rw_mask = GDBM_WRCREAT | GDBM_FAST;
|
||||
mode = 0640;
|
||||
}else{
|
||||
rw_mask = GDBM_READER;
|
||||
mode = 0640;
|
||||
}
|
||||
pFile = sqliteMalloc( sizeof(*pFile) );
|
||||
if( pFile==0 ){
|
||||
sqliteFree(zFile);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
if( zFile ){
|
||||
if( !writeable || pBe->write ){
|
||||
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
|
||||
}else{
|
||||
pFile->dbf = 0;
|
||||
}
|
||||
}else{
|
||||
int limit;
|
||||
struct rc4 *pRc4;
|
||||
char zRandom[50];
|
||||
pRc4 = &pBe->rc4;
|
||||
zFile = 0;
|
||||
limit = 5;
|
||||
do {
|
||||
randomName(&pBe->rc4, zRandom, "_temp_table_");
|
||||
sqliteFree(zFile);
|
||||
zFile = sqliteFileOfTable(pBe, zRandom);
|
||||
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
|
||||
}while( pFile->dbf==0 && limit-- >= 0);
|
||||
pFile->delOnClose = 1;
|
||||
}
|
||||
pFile->writeable = writeable;
|
||||
pFile->zName = zFile;
|
||||
pFile->nRef = 1;
|
||||
pFile->pPrev = 0;
|
||||
if( pBe->pOpen ){
|
||||
pBe->pOpen->pPrev = pFile;
|
||||
}
|
||||
pFile->pNext = pBe->pOpen;
|
||||
pBe->pOpen = pFile;
|
||||
if( pFile->dbf==0 ){
|
||||
if( !writeable && access(zFile,0) ){
|
||||
/* Trying to read a non-existant file. This is OK. All the
|
||||
** reads will return empty, which is what we want. */
|
||||
rc = SQLITE_OK;
|
||||
}else if( pBe->write==0 ){
|
||||
rc = SQLITE_READONLY;
|
||||
}else if( access(zFile,W_OK|R_OK) ){
|
||||
rc = SQLITE_PERM;
|
||||
}else{
|
||||
rc = SQLITE_BUSY;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
sqliteFree(zFile);
|
||||
pFile->nRef++;
|
||||
if( writeable && !pFile->writeable ){
|
||||
rc = SQLITE_READONLY;
|
||||
}
|
||||
}
|
||||
pCursr->pBe = pBe;
|
||||
pCursr->pFile = pFile;
|
||||
pCursr->readPending = 0;
|
||||
pCursr->needRewind = 1;
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqliteDbbeCloseCursor(pCursr);
|
||||
*ppCursr = 0;
|
||||
}else{
|
||||
*ppCursr = pCursr;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Drop a table from the database. The file on the disk that corresponds
|
||||
** to this table is deleted.
|
||||
*/
|
||||
void sqliteDbbeDropTable(Dbbe *pBe, const char *zTable){
|
||||
char *zFile; /* Name of the table file */
|
||||
|
||||
zFile = sqliteFileOfTable(pBe, zTable);
|
||||
unlink(zFile);
|
||||
sqliteFree(zFile);
|
||||
}
|
||||
|
||||
/*
|
||||
** Reorganize a table to reduce search times and disk usage.
|
||||
*/
|
||||
int sqliteDbbeReorganizeTable(Dbbe *pBe, const char *zTable){
|
||||
DbbeCursor *pCrsr;
|
||||
int rc;
|
||||
|
||||
rc = sqliteDbbeOpenCursor(pBe, zTable, 1, &pCrsr);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
if( pCrsr && pCrsr->pFile && pCrsr->pFile->dbf ){
|
||||
gdbm_reorganize(pCrsr->pFile->dbf);
|
||||
}
|
||||
if( pCrsr ){
|
||||
sqliteDbbeCloseCursor(pCrsr);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a cursor previously opened by sqliteDbbeOpenCursor().
|
||||
**
|
||||
** There can be multiple cursors pointing to the same open file.
|
||||
** The underlying file is not closed until all cursors have been
|
||||
** closed. This routine decrements the BeFile.nref field of the
|
||||
** underlying file and closes the file when nref reaches 0.
|
||||
*/
|
||||
void sqliteDbbeCloseCursor(DbbeCursor *pCursr){
|
||||
BeFile *pFile;
|
||||
Dbbe *pBe;
|
||||
if( pCursr==0 ) return;
|
||||
pFile = pCursr->pFile;
|
||||
pBe = pCursr->pBe;
|
||||
pFile->nRef--;
|
||||
if( pFile->dbf!=NULL ){
|
||||
gdbm_sync(pFile->dbf);
|
||||
}
|
||||
if( pFile->nRef<=0 ){
|
||||
if( pFile->dbf!=NULL ){
|
||||
gdbm_close(pFile->dbf);
|
||||
}
|
||||
if( pFile->pPrev ){
|
||||
pFile->pPrev->pNext = pFile->pNext;
|
||||
}else{
|
||||
pBe->pOpen = pFile->pNext;
|
||||
}
|
||||
if( pFile->pNext ){
|
||||
pFile->pNext->pPrev = pFile->pPrev;
|
||||
}
|
||||
if( pFile->delOnClose ){
|
||||
unlink(pFile->zName);
|
||||
}
|
||||
sqliteFree(pFile->zName);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
if( pCursr->key.dptr ) free(pCursr->key.dptr);
|
||||
if( pCursr->data.dptr ) free(pCursr->data.dptr);
|
||||
memset(pCursr, 0, sizeof(*pCursr));
|
||||
sqliteFree(pCursr);
|
||||
}
|
||||
|
||||
/*
|
||||
** Clear the given datum
|
||||
*/
|
||||
static void datumClear(datum *p){
|
||||
if( p->dptr ) free(p->dptr);
|
||||
p->dptr = 0;
|
||||
p->dsize = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Fetch a single record from an open cursor. Return 1 on success
|
||||
** and 0 on failure.
|
||||
*/
|
||||
int sqliteDbbeFetch(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return pCursr->data.dptr!=0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
int sqliteDbbeTest(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
int result = 0;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
result = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy bytes from the current key or data into a buffer supplied by
|
||||
** the calling function. Return the number of bytes copied.
|
||||
*/
|
||||
int sqliteDbbeCopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( offset>=pCursr->key.dsize ) return 0;
|
||||
if( offset+size>pCursr->key.dsize ){
|
||||
n = pCursr->key.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->key.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
int sqliteDbbeCopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset>=pCursr->data.dsize ) return 0;
|
||||
if( offset+size>pCursr->data.dsize ){
|
||||
n = pCursr->data.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->data.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to bytes from the key or data. The data returned
|
||||
** is ephemeral.
|
||||
*/
|
||||
char *sqliteDbbeReadKey(DbbeCursor *pCursr, int offset){
|
||||
if( offset<0 || offset>=pCursr->key.dsize ) return "";
|
||||
return &pCursr->key.dptr[offset];
|
||||
}
|
||||
char *sqliteDbbeReadData(DbbeCursor *pCursr, int offset){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset<0 || offset>=pCursr->data.dsize ) return "";
|
||||
return &pCursr->data.dptr[offset];
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the total number of bytes in either data or key.
|
||||
*/
|
||||
int sqliteDbbeKeyLength(DbbeCursor *pCursr){
|
||||
return pCursr->key.dsize;
|
||||
}
|
||||
int sqliteDbbeDataLength(DbbeCursor *pCursr){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
return pCursr->data.dsize;
|
||||
}
|
||||
|
||||
/*
|
||||
** Make is so that the next call to sqliteNextKey() finds the first
|
||||
** key of the table.
|
||||
*/
|
||||
int sqliteDbbeRewind(DbbeCursor *pCursr){
|
||||
pCursr->needRewind = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read the next key from the table. Return 1 on success. Return
|
||||
** 0 if there are no more keys.
|
||||
*/
|
||||
int sqliteDbbeNextKey(DbbeCursor *pCursr){
|
||||
datum nextkey;
|
||||
int rc;
|
||||
if( pCursr==0 || pCursr->pFile==0 || pCursr->pFile->dbf==0 ){
|
||||
pCursr->readPending = 0;
|
||||
return 0;
|
||||
}
|
||||
if( pCursr->needRewind ){
|
||||
nextkey = gdbm_firstkey(pCursr->pFile->dbf);
|
||||
pCursr->needRewind = 0;
|
||||
}else{
|
||||
nextkey = gdbm_nextkey(pCursr->pFile->dbf, pCursr->key);
|
||||
}
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
pCursr->key = nextkey;
|
||||
if( pCursr->key.dptr ){
|
||||
pCursr->readPending = 1;
|
||||
rc = 1;
|
||||
}else{
|
||||
pCursr->needRewind = 1;
|
||||
pCursr->readPending = 0;
|
||||
rc = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a new integer key.
|
||||
*/
|
||||
int sqliteDbbeNew(DbbeCursor *pCursr){
|
||||
int iKey;
|
||||
datum key;
|
||||
int go = 1;
|
||||
int i;
|
||||
struct rc4 *pRc4;
|
||||
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return 1;
|
||||
pRc4 = &pCursr->pBe->rc4;
|
||||
while( go ){
|
||||
iKey = 0;
|
||||
for(i=0; i<4; i++){
|
||||
iKey = (iKey<<8) + rc4byte(pRc4);
|
||||
}
|
||||
if( iKey==0 ) continue;
|
||||
key.dptr = (char*)&iKey;
|
||||
key.dsize = 4;
|
||||
go = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return iKey;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write an entry into the table. Overwrite any prior entry with the
|
||||
** same key.
|
||||
*/
|
||||
int sqliteDbbePut(DbbeCursor *pCursr, int nKey,char *pKey,int nData,char *pData){
|
||||
datum data, key;
|
||||
int rc;
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
data.dsize = nData;
|
||||
data.dptr = pData;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_store(pCursr->pFile->dbf, key, data, GDBM_REPLACE);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove an entry from a table, if the entry exists.
|
||||
*/
|
||||
int sqliteDbbeDelete(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
int rc;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_delete(pCursr->pFile->dbf, key);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
return rc;
|
||||
return sqliteGdbmOpen(zName, writeFlag, createFlag, pzErrMsg);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -684,7 +71,7 @@ int sqliteDbbeDelete(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
** and then immediately unlinking the file. That works great
|
||||
** under Unix, but fails when we try to port to Windows.
|
||||
*/
|
||||
int sqliteDbbeOpenTempFile(Dbbe *pBe, FILE **ppFile){
|
||||
int sqliteDbbeOpenTempFile(const char *zDir, Dbbe *pBe, FILE **ppFile){
|
||||
char *zFile; /* Full name of the temporary file */
|
||||
char zBuf[50]; /* Base name of the temporary file */
|
||||
int i; /* Loop counter */
|
||||
@@ -706,10 +93,10 @@ int sqliteDbbeOpenTempFile(Dbbe *pBe, FILE **ppFile){
|
||||
limit = 4;
|
||||
zFile = 0;
|
||||
do{
|
||||
randomName(&pBe->rc4, zBuf, "/_temp_file_");
|
||||
sqliteRandomName(zBuf, "/_temp_file_");
|
||||
sqliteFree(zFile);
|
||||
zFile = 0;
|
||||
sqliteSetString(&zFile, pBe->zDir, zBuf, 0);
|
||||
sqliteSetString(&zFile, zDir, zBuf, 0);
|
||||
}while( access(zFile,0)==0 && limit-- >= 0 );
|
||||
*ppFile = pBe->apTemp[i] = fopen(zFile, "w+");
|
||||
if( pBe->apTemp[i]==0 ){
|
||||
@@ -723,7 +110,7 @@ int sqliteDbbeOpenTempFile(Dbbe *pBe, FILE **ppFile){
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a temporary file opened using sqliteDbbeOpenTempFile()
|
||||
** Close a temporary file opened using sqliteGdbmOpenTempFile()
|
||||
*/
|
||||
void sqliteDbbeCloseTempFile(Dbbe *pBe, FILE *f){
|
||||
int i;
|
||||
@@ -738,3 +125,74 @@ void sqliteDbbeCloseTempFile(Dbbe *pBe, FILE *f){
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close all temporary files that happen to still be open.
|
||||
** This routine is called when the database is being closed.
|
||||
*/
|
||||
void sqliteDbbeCloseAllTempFiles(Dbbe *pBe){
|
||||
int i;
|
||||
for(i=0; i<pBe->nTemp; i++){
|
||||
if( pBe->apTemp[i]!=0 ){
|
||||
unlink(pBe->azTemp[i]);
|
||||
fclose(pBe->apTemp[i]);
|
||||
sqliteFree(pBe->azTemp[i]);
|
||||
pBe->apTemp[i] = 0;
|
||||
pBe->azTemp[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sqliteFree(pBe->azTemp);
|
||||
sqliteFree(pBe->apTemp);
|
||||
}
|
||||
|
||||
/*
|
||||
** Translate the name of an SQL table (or index) into the name
|
||||
** of a file that holds the key/data pairs for that table or
|
||||
** index. Space to hold the filename is obtained from
|
||||
** sqliteMalloc() and must be freed by the calling function.
|
||||
**
|
||||
** zDir is the name of the directory in which the file should
|
||||
** be located. zSuffix is the filename extension to use for
|
||||
** the file.
|
||||
*/
|
||||
char *sqliteDbbeNameToFile(
|
||||
const char *zDir, /* Directory containing the file */
|
||||
const char *zTable, /* Name of the SQL table that the file contains */
|
||||
const char *zSuffix /* Suffix for the file. Includes the "." */
|
||||
){
|
||||
char *zFile = 0;
|
||||
int i, k, c;
|
||||
int nChar = 0;
|
||||
|
||||
for(i=0; (c = zTable[i])!=0; i++){
|
||||
if( !isalnum(c) && c!='_' ){
|
||||
nChar += 3;
|
||||
}else{
|
||||
nChar ++;
|
||||
}
|
||||
}
|
||||
nChar += strlen(zDir) + strlen(zSuffix) + 2;
|
||||
zFile = sqliteMalloc( nChar );
|
||||
if( zFile==0 ) return 0;
|
||||
for(i=0; (c = zDir[i])!=0; i++){
|
||||
zFile[i] = c;
|
||||
}
|
||||
zFile[i++] = '/';
|
||||
for(k=0; (c = zTable[k])!=0; k++){
|
||||
if( isupper(c) ){
|
||||
zFile[i++] = tolower(c);
|
||||
}else if( isalnum(c) || c=='_' ){
|
||||
zFile[i++] = c;
|
||||
}else{
|
||||
zFile[i++] = '~';
|
||||
zFile[i++] = "0123456789abcdef"[c & 0xf];
|
||||
zFile[i++] = "0123456789abcdef"[(c>>8)&0xf];
|
||||
}
|
||||
}
|
||||
for(k=0; (c = zSuffix[k])!=0; k++){
|
||||
zFile[i++] = c;
|
||||
}
|
||||
zFile[i] = 0;
|
||||
return zFile;
|
||||
}
|
||||
|
||||
+114
-79
@@ -28,7 +28,7 @@
|
||||
** This library was originally designed to support the following
|
||||
** backends: GDBM, NDBM, SDBM, Berkeley DB.
|
||||
**
|
||||
** $Id: dbbe.h,v 1.7 2000/08/17 09:50:00 drh Exp $
|
||||
** $Id: dbbe.h,v 1.10 2001/01/15 22:51:10 drh Exp $
|
||||
*/
|
||||
#ifndef _SQLITE_DBBE_H_
|
||||
#define _SQLITE_DBBE_H_
|
||||
@@ -53,91 +53,126 @@
|
||||
*/
|
||||
typedef struct Dbbe Dbbe;
|
||||
typedef struct DbbeCursor DbbeCursor;
|
||||
typedef struct DbbeMethods DbbeMethods;
|
||||
|
||||
/*
|
||||
** The 18 interface routines.
|
||||
** Open a complete database.
|
||||
**
|
||||
** If the database name begins with "gdbm:" the GDBM driver is used.
|
||||
** If the name begins with "memory:" the in-memory driver is used.
|
||||
** The default driver is GDBM.
|
||||
*/
|
||||
|
||||
/* Open a complete database */
|
||||
Dbbe *sqliteDbbeOpen(const char *zName, int write, int create, char **pzErr);
|
||||
|
||||
/* Close the whole database. */
|
||||
void sqliteDbbeClose(Dbbe*);
|
||||
/*
|
||||
** Each of the various SQLite backends defines a set of methods for
|
||||
** accessing the database. Pointers to the methods are contained in
|
||||
** an instance of the following structure. A pointer to a static instance
|
||||
** of this structure is assigned to the Dbbe structure that sqlileDbbeOpen
|
||||
** returns.
|
||||
*/
|
||||
struct DbbeMethods {
|
||||
/* Close the whole database. */
|
||||
void (*Close)(Dbbe*);
|
||||
|
||||
/* Open a cursor into particular file of a previously opened database.
|
||||
** Create the file if it doesn't already exist and writeable!=0. zName
|
||||
** is the base name of the file to be opened. This routine will add
|
||||
** an appropriate path and extension to the filename to locate the
|
||||
** actual file.
|
||||
/* Open a cursor into particular file of a previously opened database.
|
||||
** Create the file if it doesn't already exist and writeable!=0. zName
|
||||
** is the base name of the file to be opened. This routine will add
|
||||
** an appropriate path and extension to the filename to locate the
|
||||
** actual file.
|
||||
**
|
||||
** The keyType parameter is TRUE if this table will only be accessed
|
||||
** using integer keys. This parameter allows the database backend to
|
||||
** use a faster algorithm for the special case of integer keys, if it
|
||||
** wants to.
|
||||
**
|
||||
** If zName is 0 or "", then a temporary file is created that
|
||||
** will be deleted when closed.
|
||||
*/
|
||||
int (*OpenCursor)(Dbbe*, const char *zName, int writeable,
|
||||
int intKeyOnly, DbbeCursor**);
|
||||
|
||||
/* Delete a table from the database */
|
||||
void (*DropTable)(Dbbe*, const char *zTableName);
|
||||
|
||||
/* Reorganize a table to speed access or reduce its disk usage */
|
||||
int (*ReorganizeTable)(Dbbe*, const char *zTableName);
|
||||
|
||||
/* Close a cursor */
|
||||
void (*CloseCursor)(DbbeCursor*);
|
||||
|
||||
/* Fetch an entry from a table with the given key. Return 1 if
|
||||
** successful and 0 if no such entry exists.
|
||||
*/
|
||||
int (*Fetch)(DbbeCursor*, int nKey, char *pKey);
|
||||
|
||||
/* Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
int (*Test)(DbbeCursor*, int nKey, char *pKey);
|
||||
|
||||
/* Retrieve the key or data used for the last fetch. Only size
|
||||
** bytes are read beginning with the offset-th byte. The return
|
||||
** value is the actual number of bytes read.
|
||||
*/
|
||||
int (*CopyKey)(DbbeCursor*, int offset, int size, char *zBuf);
|
||||
int (*CopyData)(DbbeCursor*, int offset, int size, char *zBuf);
|
||||
|
||||
/* Retrieve the key or data. The result is ephemeral. In other words,
|
||||
** the result is stored in a buffer that might be overwritten on the next
|
||||
** call to any DBBE routine. If the results are needed for longer than
|
||||
** that, you must make a copy.
|
||||
*/
|
||||
char *(*ReadKey)(DbbeCursor*, int offset);
|
||||
char *(*ReadData)(DbbeCursor*, int offset);
|
||||
|
||||
/* Return the length of the most recently fetched key or data. */
|
||||
int (*KeyLength)(DbbeCursor*);
|
||||
int (*DataLength)(DbbeCursor*);
|
||||
|
||||
/* Retrieve the next entry in the table. The first key is retrieved
|
||||
** the first time this routine is called, or after a call to
|
||||
** Dbbe.Rewind(). The return value is 1 if there is another
|
||||
** entry, or 0 if there are no more entries. */
|
||||
int (*NextKey)(DbbeCursor*);
|
||||
|
||||
/* Make it so that the next call to Dbbe.NextKey() returns
|
||||
** the first entry of the table. */
|
||||
int (*Rewind)(DbbeCursor*);
|
||||
|
||||
/* Get a new integer key for this table. */
|
||||
int (*New)(DbbeCursor*);
|
||||
|
||||
/* Write an entry into a table. If another entry already exists with
|
||||
** the same key, the old entry is discarded first.
|
||||
*/
|
||||
int (*Put)(DbbeCursor*, int nKey, char *pKey, int nData, char *pData);
|
||||
|
||||
/* Remove an entry from the table */
|
||||
int (*Delete)(DbbeCursor*, int nKey, char *pKey);
|
||||
|
||||
/* Open a file suitable for temporary storage */
|
||||
int (*OpenTempFile)(Dbbe*, FILE**);
|
||||
|
||||
/* Close a temporary file */
|
||||
void (*CloseTempFile)(Dbbe *, FILE *);
|
||||
};
|
||||
|
||||
/*
|
||||
** This is the structure returned by sqliteDbbeOpen(). It contains
|
||||
** information common to all the different backend drivers.
|
||||
**
|
||||
** If zName is 0 or "", then a temporary file is created that
|
||||
** will be deleted when closed.
|
||||
** The information in this structure (with the exception the method
|
||||
** pointers in the Dbbe.x field) is intended to be visible to
|
||||
** the backend drivers only. Users should not access or modify
|
||||
** this structure in any way other than the read the method pointers
|
||||
** in Dbbe.x.
|
||||
*/
|
||||
int sqliteDbbeOpenCursor(Dbbe*, const char *zName, int writeable, DbbeCursor**);
|
||||
|
||||
/* Delete a table from the database */
|
||||
void sqliteDbbeDropTable(Dbbe*, const char *zTableName);
|
||||
|
||||
/* Reorganize a table to speed access or reduce its disk usage */
|
||||
int sqliteDbbeReorganizeTable(Dbbe*, const char *zTableName);
|
||||
|
||||
/* Close a cursor */
|
||||
void sqliteDbbeCloseCursor(DbbeCursor*);
|
||||
|
||||
/* Fetch an entry from a table with the given key. Return 1 if
|
||||
** successful and 0 if no such entry exists.
|
||||
*/
|
||||
int sqliteDbbeFetch(DbbeCursor*, int nKey, char *pKey);
|
||||
|
||||
/* Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
int sqliteDbbeTest(DbbeCursor*, int nKey, char *pKey);
|
||||
|
||||
/* Retrieve the key or data used for the last fetch. Only size
|
||||
** bytes are read beginning with the offset-th byte. The return
|
||||
** value is the actual number of bytes read.
|
||||
*/
|
||||
int sqliteDbbeCopyKey(DbbeCursor*, int offset, int size, char *zBuf);
|
||||
int sqliteDbbeCopyData(DbbeCursor*, int offset, int size, char *zBuf);
|
||||
|
||||
/* Retrieve the key or data. The result is ephemeral. In other words,
|
||||
** the result is stored in a buffer that might be overwritten on the next
|
||||
** call to any DBBE routine. If the results are needed for longer than
|
||||
** that, you must make a copy.
|
||||
*/
|
||||
char *sqliteDbbeReadKey(DbbeCursor*, int offset);
|
||||
char *sqliteDbbeReadData(DbbeCursor*, int offset);
|
||||
|
||||
/* Return the length of the most recently fetched key or data. */
|
||||
int sqliteDbbeKeyLength(DbbeCursor*);
|
||||
int sqliteDbbeDataLength(DbbeCursor*);
|
||||
|
||||
/* Retrieve the next entry in the table. The first key is retrieved
|
||||
** the first time this routine is called, or after a call to
|
||||
** sqliteDbbeRewind(). The return value is 1 if there is another
|
||||
** entry, or 0 if there are no more entries. */
|
||||
int sqliteDbbeNextKey(DbbeCursor*);
|
||||
|
||||
/* Make it so that the next call to sqliteDbbeNextKey() returns
|
||||
** the first entry of the table. */
|
||||
int sqliteDbbeRewind(DbbeCursor*);
|
||||
|
||||
/* Get a new integer key for this table. */
|
||||
int sqliteDbbeNew(DbbeCursor*);
|
||||
|
||||
/* Write an entry into a table. If another entry already exists with
|
||||
** the same key, the old entry is discarded first.
|
||||
*/
|
||||
int sqliteDbbePut(DbbeCursor*, int nKey, char *pKey, int nData, char *pData);
|
||||
|
||||
/* Remove an entry from the table */
|
||||
int sqliteDbbeDelete(DbbeCursor*, int nKey, char *pKey);
|
||||
|
||||
/* Open a file suitable for temporary storage */
|
||||
int sqliteDbbeOpenTempFile(Dbbe*, FILE**);
|
||||
|
||||
/* Close a temporary file */
|
||||
void sqliteDbbeCloseTempFile(Dbbe *, FILE *);
|
||||
struct Dbbe {
|
||||
struct DbbeMethods *x; /* Backend-specific methods for database access */
|
||||
int nTemp; /* Number of temporary files created */
|
||||
FILE **apTemp; /* Space to hold temporary file pointers */
|
||||
char **azTemp; /* Names of the temporary files */
|
||||
};
|
||||
|
||||
#endif /* defined(_SQLITE_DBBE_H_) */
|
||||
|
||||
+638
@@ -0,0 +1,638 @@
|
||||
/*
|
||||
** Copyright (c) 2000 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains code to implement the database backend (DBBE)
|
||||
** for sqlite. The database backend is the interface between
|
||||
** sqlite and the code that does the actually reading and writing
|
||||
** of information to the disk.
|
||||
**
|
||||
** This file uses GDBM as the database backend. It should be
|
||||
** relatively simple to convert to a different database such
|
||||
** as NDBM, SDBM, or BerkeleyDB.
|
||||
**
|
||||
** $Id: dbbegdbm.c,v 1.3 2001/01/15 22:51:10 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <gdbm.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
/*
|
||||
** Information about each open disk file is an instance of this
|
||||
** structure. There will only be one such structure for each
|
||||
** disk file. If the VDBE opens the same file twice (as will happen
|
||||
** for a self-join, for example) then two DbbeCursor structures are
|
||||
** created but there is only a single BeFile structure with an
|
||||
** nRef of 2.
|
||||
*/
|
||||
typedef struct BeFile BeFile;
|
||||
struct BeFile {
|
||||
char *zName; /* Name of the file */
|
||||
GDBM_FILE dbf; /* The file itself */
|
||||
int nRef; /* Number of references */
|
||||
int delOnClose; /* Delete when closing */
|
||||
int writeable; /* Opened for writing */
|
||||
BeFile *pNext, *pPrev; /* Next and previous on list of open files */
|
||||
};
|
||||
|
||||
/*
|
||||
** The following structure contains all information used by GDBM
|
||||
** database driver. This is a subclass of the Dbbe structure.
|
||||
*/
|
||||
typedef struct Dbbex Dbbex;
|
||||
struct Dbbex {
|
||||
Dbbe dbbe; /* The base class */
|
||||
int write; /* True for write permission */
|
||||
BeFile *pOpen; /* List of open files */
|
||||
char *zDir; /* Directory hold the database */
|
||||
};
|
||||
|
||||
/*
|
||||
** An cursor into a database file is an instance of the following structure.
|
||||
** There can only be a single BeFile structure for each disk file, but
|
||||
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
|
||||
** a cursor pointing to a particular part of the open BeFile. The
|
||||
** BeFile.nRef field hold a count of the number of DbbeCursor structures
|
||||
** associated with the same disk file.
|
||||
*/
|
||||
struct DbbeCursor {
|
||||
Dbbex *pBe; /* The database of which this record is a part */
|
||||
BeFile *pFile; /* The database file for this table */
|
||||
datum key; /* Most recently used key */
|
||||
datum data; /* Most recent data */
|
||||
int needRewind; /* Next key should be the first */
|
||||
int readPending; /* The fetch hasn't actually been done yet */
|
||||
};
|
||||
|
||||
/*
|
||||
** The "mkdir()" function only takes one argument under Windows.
|
||||
*/
|
||||
#if OS_WIN
|
||||
# define mkdir(A,B) mkdir(A)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Forward declaration
|
||||
*/
|
||||
static void sqliteGdbmCloseCursor(DbbeCursor *pCursr);
|
||||
|
||||
/*
|
||||
** Completely shutdown the given database. Close all files. Free all memory.
|
||||
*/
|
||||
static void sqliteGdbmClose(Dbbe *pDbbe){
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
BeFile *pFile, *pNext;
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pNext){
|
||||
pNext = pFile->pNext;
|
||||
gdbm_close(pFile->dbf);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
sqliteDbbeCloseAllTempFiles(pDbbe);
|
||||
memset(pBe, 0, sizeof(*pBe));
|
||||
sqliteFree(pBe);
|
||||
}
|
||||
|
||||
/*
|
||||
** Translate the name of an SQL table (or index) into the name
|
||||
** of a file that holds the key/data pairs for that table or
|
||||
** index. Space to hold the filename is obtained from
|
||||
** sqliteMalloc() and must be freed by the calling function.
|
||||
*/
|
||||
static char *sqliteFileOfTable(Dbbex *pBe, const char *zTable){
|
||||
char *zFile = 0;
|
||||
int i;
|
||||
sqliteSetString(&zFile, pBe->zDir, "/", zTable, ".tbl", 0);
|
||||
if( zFile==0 ) return 0;
|
||||
for(i=strlen(pBe->zDir)+1; zFile[i]; i++){
|
||||
int c = zFile[i];
|
||||
if( isupper(c) ){
|
||||
zFile[i] = tolower(c);
|
||||
}else if( !isalnum(c) && c!='-' && c!='_' && c!='.' ){
|
||||
zFile[i] = '+';
|
||||
}
|
||||
}
|
||||
return zFile;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a new table cursor. Write a pointer to the corresponding
|
||||
** DbbeCursor structure into *ppCursr. Return an integer success
|
||||
** code:
|
||||
**
|
||||
** SQLITE_OK It worked!
|
||||
**
|
||||
** SQLITE_NOMEM sqliteMalloc() failed
|
||||
**
|
||||
** SQLITE_PERM Attempt to access a file for which file
|
||||
** access permission is denied
|
||||
**
|
||||
** SQLITE_BUSY Another thread or process is already using
|
||||
** the corresponding file and has that file locked.
|
||||
**
|
||||
** SQLITE_READONLY The current thread already has this file open
|
||||
** readonly but you are trying to open for writing.
|
||||
** (This can happen if a SELECT callback tries to
|
||||
** do an UPDATE or DELETE.)
|
||||
**
|
||||
** If zTable is 0 or "", then a temporary database file is created and
|
||||
** a cursor to that temporary file is opened. The temporary file
|
||||
** will be deleted from the disk when it is closed.
|
||||
*/
|
||||
static int sqliteGdbmOpenCursor(
|
||||
Dbbe *pDbbe, /* The database the table belongs to */
|
||||
const char *zTable, /* The SQL name of the file to be opened */
|
||||
int writeable, /* True to open for writing */
|
||||
int intKeyOnly, /* True if only integer keys are used */
|
||||
DbbeCursor **ppCursr /* Write the resulting table pointer here */
|
||||
){
|
||||
char *zFile; /* Name of the table file */
|
||||
DbbeCursor *pCursr; /* The new table cursor */
|
||||
BeFile *pFile; /* The underlying data file for this table */
|
||||
int rc = SQLITE_OK; /* Return value */
|
||||
int rw_mask; /* Permissions mask for opening a table */
|
||||
int mode; /* Mode for opening a table */
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
|
||||
*ppCursr = 0;
|
||||
pCursr = sqliteMalloc( sizeof(*pCursr) );
|
||||
if( pCursr==0 ) return SQLITE_NOMEM;
|
||||
if( zTable ){
|
||||
zFile = sqliteFileOfTable(pBe, zTable);
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pFile->pNext){
|
||||
if( strcmp(pFile->zName,zFile)==0 ) break;
|
||||
}
|
||||
}else{
|
||||
pFile = 0;
|
||||
zFile = 0;
|
||||
}
|
||||
if( pFile==0 ){
|
||||
if( writeable ){
|
||||
rw_mask = GDBM_WRCREAT | GDBM_FAST;
|
||||
mode = 0640;
|
||||
}else{
|
||||
rw_mask = GDBM_READER;
|
||||
mode = 0640;
|
||||
}
|
||||
pFile = sqliteMalloc( sizeof(*pFile) );
|
||||
if( pFile==0 ){
|
||||
sqliteFree(zFile);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
if( zFile ){
|
||||
if( !writeable || pBe->write ){
|
||||
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
|
||||
}else{
|
||||
pFile->dbf = 0;
|
||||
}
|
||||
}else{
|
||||
int limit;
|
||||
char zRandom[50];
|
||||
zFile = 0;
|
||||
limit = 5;
|
||||
do {
|
||||
sqliteRandomName(zRandom, "_temp_table_");
|
||||
sqliteFree(zFile);
|
||||
zFile = sqliteFileOfTable(pBe, zRandom);
|
||||
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
|
||||
}while( pFile->dbf==0 && limit-- >= 0);
|
||||
pFile->delOnClose = 1;
|
||||
}
|
||||
pFile->writeable = writeable;
|
||||
pFile->zName = zFile;
|
||||
pFile->nRef = 1;
|
||||
pFile->pPrev = 0;
|
||||
if( pBe->pOpen ){
|
||||
pBe->pOpen->pPrev = pFile;
|
||||
}
|
||||
pFile->pNext = pBe->pOpen;
|
||||
pBe->pOpen = pFile;
|
||||
if( pFile->dbf==0 ){
|
||||
if( !writeable && access(zFile,0) ){
|
||||
/* Trying to read a non-existant file. This is OK. All the
|
||||
** reads will return empty, which is what we want. */
|
||||
rc = SQLITE_OK;
|
||||
}else if( pBe->write==0 ){
|
||||
rc = SQLITE_READONLY;
|
||||
}else if( access(zFile,W_OK|R_OK) ){
|
||||
rc = SQLITE_PERM;
|
||||
}else{
|
||||
rc = SQLITE_BUSY;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
sqliteFree(zFile);
|
||||
pFile->nRef++;
|
||||
if( writeable && !pFile->writeable ){
|
||||
rc = SQLITE_READONLY;
|
||||
}
|
||||
}
|
||||
pCursr->pBe = pBe;
|
||||
pCursr->pFile = pFile;
|
||||
pCursr->readPending = 0;
|
||||
pCursr->needRewind = 1;
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqliteGdbmCloseCursor(pCursr);
|
||||
*ppCursr = 0;
|
||||
}else{
|
||||
*ppCursr = pCursr;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Drop a table from the database. The file on the disk that corresponds
|
||||
** to this table is deleted.
|
||||
*/
|
||||
static void sqliteGdbmDropTable(Dbbe *pBe, const char *zTable){
|
||||
char *zFile; /* Name of the table file */
|
||||
|
||||
zFile = sqliteFileOfTable((Dbbex*)pBe, zTable);
|
||||
unlink(zFile);
|
||||
sqliteFree(zFile);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a cursor previously opened by sqliteGdbmOpenCursor().
|
||||
**
|
||||
** There can be multiple cursors pointing to the same open file.
|
||||
** The underlying file is not closed until all cursors have been
|
||||
** closed. This routine decrements the BeFile.nref field of the
|
||||
** underlying file and closes the file when nref reaches 0.
|
||||
*/
|
||||
static void sqliteGdbmCloseCursor(DbbeCursor *pCursr){
|
||||
BeFile *pFile;
|
||||
Dbbex *pBe;
|
||||
if( pCursr==0 ) return;
|
||||
pFile = pCursr->pFile;
|
||||
pBe = pCursr->pBe;
|
||||
pFile->nRef--;
|
||||
if( pFile->dbf!=NULL ){
|
||||
gdbm_sync(pFile->dbf);
|
||||
}
|
||||
if( pFile->nRef<=0 ){
|
||||
if( pFile->dbf!=NULL ){
|
||||
gdbm_close(pFile->dbf);
|
||||
}
|
||||
if( pFile->pPrev ){
|
||||
pFile->pPrev->pNext = pFile->pNext;
|
||||
}else{
|
||||
pBe->pOpen = pFile->pNext;
|
||||
}
|
||||
if( pFile->pNext ){
|
||||
pFile->pNext->pPrev = pFile->pPrev;
|
||||
}
|
||||
if( pFile->delOnClose ){
|
||||
unlink(pFile->zName);
|
||||
}
|
||||
sqliteFree(pFile->zName);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
if( pCursr->key.dptr ) free(pCursr->key.dptr);
|
||||
if( pCursr->data.dptr ) free(pCursr->data.dptr);
|
||||
memset(pCursr, 0, sizeof(*pCursr));
|
||||
sqliteFree(pCursr);
|
||||
}
|
||||
|
||||
/*
|
||||
** Reorganize a table to reduce search times and disk usage.
|
||||
*/
|
||||
static int sqliteGdbmReorganizeTable(Dbbe *pBe, const char *zTable){
|
||||
DbbeCursor *pCrsr;
|
||||
int rc;
|
||||
|
||||
rc = sqliteGdbmOpenCursor(pBe, zTable, 1, 0, &pCrsr);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
if( pCrsr && pCrsr->pFile && pCrsr->pFile->dbf ){
|
||||
gdbm_reorganize(pCrsr->pFile->dbf);
|
||||
}
|
||||
if( pCrsr ){
|
||||
sqliteGdbmCloseCursor(pCrsr);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Clear the given datum
|
||||
*/
|
||||
static void datumClear(datum *p){
|
||||
if( p->dptr ) free(p->dptr);
|
||||
p->dptr = 0;
|
||||
p->dsize = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Fetch a single record from an open cursor. Return 1 on success
|
||||
** and 0 on failure.
|
||||
*/
|
||||
static int sqliteGdbmFetch(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return pCursr->data.dptr!=0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
static int sqliteGdbmTest(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
int result = 0;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
result = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy bytes from the current key or data into a buffer supplied by
|
||||
** the calling function. Return the number of bytes copied.
|
||||
*/
|
||||
static
|
||||
int sqliteGdbmCopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( offset>=pCursr->key.dsize ) return 0;
|
||||
if( offset+size>pCursr->key.dsize ){
|
||||
n = pCursr->key.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->key.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
static
|
||||
int sqliteGdbmCopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset>=pCursr->data.dsize ) return 0;
|
||||
if( offset+size>pCursr->data.dsize ){
|
||||
n = pCursr->data.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->data.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to bytes from the key or data. The data returned
|
||||
** is ephemeral.
|
||||
*/
|
||||
static char *sqliteGdbmReadKey(DbbeCursor *pCursr, int offset){
|
||||
if( offset<0 || offset>=pCursr->key.dsize ) return "";
|
||||
return &pCursr->key.dptr[offset];
|
||||
}
|
||||
static char *sqliteGdbmReadData(DbbeCursor *pCursr, int offset){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset<0 || offset>=pCursr->data.dsize ) return "";
|
||||
return &pCursr->data.dptr[offset];
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the total number of bytes in either data or key.
|
||||
*/
|
||||
static int sqliteGdbmKeyLength(DbbeCursor *pCursr){
|
||||
return pCursr->key.dsize;
|
||||
}
|
||||
static int sqliteGdbmDataLength(DbbeCursor *pCursr){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
return pCursr->data.dsize;
|
||||
}
|
||||
|
||||
/*
|
||||
** Make is so that the next call to sqliteNextKey() finds the first
|
||||
** key of the table.
|
||||
*/
|
||||
static int sqliteGdbmRewind(DbbeCursor *pCursr){
|
||||
pCursr->needRewind = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read the next key from the table. Return 1 on success. Return
|
||||
** 0 if there are no more keys.
|
||||
*/
|
||||
static int sqliteGdbmNextKey(DbbeCursor *pCursr){
|
||||
datum nextkey;
|
||||
int rc;
|
||||
if( pCursr==0 || pCursr->pFile==0 || pCursr->pFile->dbf==0 ){
|
||||
pCursr->readPending = 0;
|
||||
return 0;
|
||||
}
|
||||
if( pCursr->needRewind ){
|
||||
nextkey = gdbm_firstkey(pCursr->pFile->dbf);
|
||||
pCursr->needRewind = 0;
|
||||
}else{
|
||||
nextkey = gdbm_nextkey(pCursr->pFile->dbf, pCursr->key);
|
||||
}
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
pCursr->key = nextkey;
|
||||
if( pCursr->key.dptr ){
|
||||
pCursr->readPending = 1;
|
||||
rc = 1;
|
||||
}else{
|
||||
pCursr->needRewind = 1;
|
||||
pCursr->readPending = 0;
|
||||
rc = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a new integer key.
|
||||
*/
|
||||
static int sqliteGdbmNew(DbbeCursor *pCursr){
|
||||
int iKey;
|
||||
datum key;
|
||||
int go = 1;
|
||||
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return 1;
|
||||
while( go ){
|
||||
iKey = sqliteRandomInteger();
|
||||
if( iKey==0 ) continue;
|
||||
key.dptr = (char*)&iKey;
|
||||
key.dsize = 4;
|
||||
go = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return iKey;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write an entry into the table. Overwrite any prior entry with the
|
||||
** same key.
|
||||
*/
|
||||
static int sqliteGdbmPut(
|
||||
DbbeCursor *pCursr, /* Write to the database associated with this cursor */
|
||||
int nKey, /* Number of bytes in the key */
|
||||
char *pKey, /* The data for the key */
|
||||
int nData, /* Number of bytes of data */
|
||||
char *pData /* The data */
|
||||
){
|
||||
datum data, key;
|
||||
int rc;
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
data.dsize = nData;
|
||||
data.dptr = pData;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_store(pCursr->pFile->dbf, key, data, GDBM_REPLACE);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove an entry from a table, if the entry exists.
|
||||
*/
|
||||
static int sqliteGdbmDelete(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
int rc;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_delete(pCursr->pFile->dbf, key);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a temporary file. The file is located in the same directory
|
||||
** as the rest of the database.
|
||||
*/
|
||||
static int sqliteGdbmOpenTempFile(Dbbe *pDbbe, FILE **ppFile){
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
return sqliteDbbeOpenTempFile(pBe->zDir, pDbbe, ppFile);
|
||||
}
|
||||
|
||||
/*
|
||||
** This variable contains pointers to all of the access methods
|
||||
** used to implement the GDBM backend.
|
||||
*/
|
||||
static struct DbbeMethods gdbmMethods = {
|
||||
/* n Close */ sqliteGdbmClose,
|
||||
/* OpenCursor */ sqliteGdbmOpenCursor,
|
||||
/* DropTable */ sqliteGdbmDropTable,
|
||||
/* ReorganizeTable */ sqliteGdbmReorganizeTable,
|
||||
/* CloseCursor */ sqliteGdbmCloseCursor,
|
||||
/* Fetch */ sqliteGdbmFetch,
|
||||
/* Test */ sqliteGdbmTest,
|
||||
/* CopyKey */ sqliteGdbmCopyKey,
|
||||
/* CopyData */ sqliteGdbmCopyData,
|
||||
/* ReadKey */ sqliteGdbmReadKey,
|
||||
/* ReadData */ sqliteGdbmReadData,
|
||||
/* KeyLength */ sqliteGdbmKeyLength,
|
||||
/* DataLength */ sqliteGdbmDataLength,
|
||||
/* NextKey */ sqliteGdbmNextKey,
|
||||
/* Rewind */ sqliteGdbmRewind,
|
||||
/* New */ sqliteGdbmNew,
|
||||
/* Put */ sqliteGdbmPut,
|
||||
/* Delete */ sqliteGdbmDelete,
|
||||
/* OpenTempFile */ sqliteGdbmOpenTempFile,
|
||||
/* CloseTempFile */ sqliteDbbeCloseTempFile
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
** This routine opens a new database. For the GDBM driver
|
||||
** implemented here, the database name is the name of the directory
|
||||
** containing all the files of the database.
|
||||
**
|
||||
** If successful, a pointer to the Dbbe structure is returned.
|
||||
** If there are errors, an appropriate error message is left
|
||||
** in *pzErrMsg and NULL is returned.
|
||||
*/
|
||||
Dbbe *sqliteGdbmOpen(
|
||||
const char *zName, /* The name of the database */
|
||||
int writeFlag, /* True if we will be writing to the database */
|
||||
int createFlag, /* True to create database if it doesn't exist */
|
||||
char **pzErrMsg /* Write error messages (if any) here */
|
||||
){
|
||||
Dbbex *pNew;
|
||||
struct stat statbuf;
|
||||
char *zMaster;
|
||||
|
||||
if( !writeFlag ) createFlag = 0;
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
if( createFlag ) mkdir(zName, 0750);
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
sqliteSetString(pzErrMsg, createFlag ?
|
||||
"can't find or create directory \"" : "can't find directory \"",
|
||||
zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if( !S_ISDIR(statbuf.st_mode) ){
|
||||
sqliteSetString(pzErrMsg, "not a directory: \"", zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
if( access(zName, writeFlag ? (X_OK|W_OK|R_OK) : (X_OK|R_OK)) ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied", 0);
|
||||
return 0;
|
||||
}
|
||||
zMaster = 0;
|
||||
sqliteSetString(&zMaster, zName, "/" MASTER_NAME ".tbl", 0);
|
||||
if( stat(zMaster, &statbuf)==0
|
||||
&& access(zMaster, writeFlag ? (W_OK|R_OK) : R_OK)!=0 ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied for ", zMaster, 0);
|
||||
sqliteFree(zMaster);
|
||||
return 0;
|
||||
}
|
||||
sqliteFree(zMaster);
|
||||
pNew = sqliteMalloc(sizeof(Dbbex) + strlen(zName) + 1);
|
||||
if( pNew==0 ){
|
||||
sqliteSetString(pzErrMsg, "out of memory", 0);
|
||||
return 0;
|
||||
}
|
||||
pNew->dbbe.x = &gdbmMethods;
|
||||
pNew->zDir = (char*)&pNew[1];
|
||||
strcpy(pNew->zDir, zName);
|
||||
pNew->write = writeFlag;
|
||||
pNew->pOpen = 0;
|
||||
return &pNew->dbbe;
|
||||
}
|
||||
+791
@@ -0,0 +1,791 @@
|
||||
/*
|
||||
** Copyright (c) 2000 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains code to implement the database backend (DBBE)
|
||||
** for sqlite. The database backend is the interface between
|
||||
** sqlite and the code that does the actually reading and writing
|
||||
** of information to the disk.
|
||||
**
|
||||
** This file uses an in-memory hash table as the database backend.
|
||||
**
|
||||
** $Id: dbbemem.c,v 1.7 2001/01/15 22:51:10 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
|
||||
typedef struct Array Array;
|
||||
typedef struct ArrayElem ArrayElem;
|
||||
typedef struct Datum Datum;
|
||||
|
||||
/* A complete associative array is an instance of the following structure.
|
||||
** The internals of this structure are intended to be opaque -- client
|
||||
** code should not attempt to access or modify the fields of this structure
|
||||
** directly. Change this structure only by using the routines below.
|
||||
** However, many of the "procedures" and "functions" for modifying and
|
||||
** accessing this structure are really macros, so we can't really make
|
||||
** this structure opaque.
|
||||
*/
|
||||
struct Array {
|
||||
int count; /* Number of entries in the array */
|
||||
ArrayElem *first; /* The first element of the array */
|
||||
int htsize; /* Number of buckets in the hash table */
|
||||
struct _Array_ht { /* the hash table */
|
||||
int count; /* Number of entries with this hash */
|
||||
ArrayElem *chain; /* Pointer to first entry with this hash */
|
||||
} *ht;
|
||||
};
|
||||
|
||||
/*
|
||||
** An instance of the following structure stores a single key or
|
||||
** data element.
|
||||
*/
|
||||
struct Datum {
|
||||
int n;
|
||||
void *p;
|
||||
};
|
||||
|
||||
/* Each element in the associative array is an instance of the following
|
||||
** structure. All elements are stored on a single doubly-linked list.
|
||||
**
|
||||
** Again, this structure is intended to be opaque, but it can't really
|
||||
** be opaque because it is used by macros.
|
||||
*/
|
||||
struct ArrayElem {
|
||||
ArrayElem *next, *prev; /* Next and previous elements in the array */
|
||||
Datum key, data; /* Key and data for this element */
|
||||
};
|
||||
|
||||
/* Some routines are so simple that they can be implemented as macros
|
||||
** These are given first. */
|
||||
|
||||
/* Return the number of entries in the array */
|
||||
#define ArrayCount(X) ((X)->count)
|
||||
|
||||
/* Return a pointer to the first element of the array */
|
||||
#define ArrayFirst(X) ((X)->first)
|
||||
|
||||
/* Return a pointer to the next (or previous) element of the array */
|
||||
#define ArrayNext(X) ((X)->next)
|
||||
#define ArrayPrev(X) ((X)->prev)
|
||||
|
||||
/* Return TRUE if the element given is the last element in the array */
|
||||
#define ArrayIsLast(X) ((X)->next==0)
|
||||
#define ArrayIsFirst(X) ((X)->prev==0)
|
||||
|
||||
/* Return the data or key for an element of the array */
|
||||
#define ArrayData(X) ((X)->data.p)
|
||||
#define ArrayDataSize(X) ((X)->data.n)
|
||||
#define ArrayKey(X) ((X)->key.p)
|
||||
#define ArrayKeySize(X) ((X)->key.n)
|
||||
|
||||
/* Turn bulk memory into an associative array object by initializing the
|
||||
** fields of the Array structure.
|
||||
*/
|
||||
static void ArrayInit(Array *new){
|
||||
new->first = 0;
|
||||
new->count = 0;
|
||||
new->htsize = 0;
|
||||
new->ht = 0;
|
||||
}
|
||||
|
||||
/* Remove all entries from an associative array. Reclaim all memory.
|
||||
** This is the opposite of ArrayInit().
|
||||
*/
|
||||
static void ArrayClear(Array *array){
|
||||
ArrayElem *elem; /* For looping over all elements of the array */
|
||||
|
||||
elem = array->first;
|
||||
array->first = 0;
|
||||
array->count = 0;
|
||||
if( array->ht ) sqliteFree(array->ht);
|
||||
array->ht = 0;
|
||||
array->htsize = 0;
|
||||
while( elem ){
|
||||
ArrayElem *next_elem = elem->next;
|
||||
sqliteFree(elem);
|
||||
elem = next_elem;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate a hash from an N-byte key
|
||||
*/
|
||||
static int ArrayHash(Datum d){
|
||||
int h = 0;
|
||||
while( d.n-- > 0 ){
|
||||
/* The funky case "*(char**)&d.p" is to work around a bug the
|
||||
** c89 compiler of HPUX. */
|
||||
h = (h<<9) ^ (h<<3) ^ h ^ *((*(char**)&d.p)++);
|
||||
}
|
||||
if( h<0 ) h = -h;
|
||||
return h;
|
||||
}
|
||||
|
||||
/* Resize the hash table for a Array array
|
||||
*/
|
||||
static void ArrayRehash(Array *array, int new_size){
|
||||
struct _Array_ht *new_ht; /* The new hash table */
|
||||
ArrayElem *elem, *next_elem; /* For looping over existing elements */
|
||||
int i; /* Loop counter */
|
||||
ArrayElem *x; /* Element being copied to new hash table */
|
||||
|
||||
new_ht = sqliteMalloc( new_size*sizeof(struct _Array_ht) );
|
||||
if( array->ht ) sqliteFree(array->ht);
|
||||
array->ht = new_ht;
|
||||
array->htsize = new_size;
|
||||
for(i=new_size-1; i>=0; i--){
|
||||
new_ht[i].count = 0;
|
||||
new_ht[i].chain = 0;
|
||||
}
|
||||
for(elem=array->first, array->first=0; elem; elem = next_elem){
|
||||
int h = ArrayHash(elem->key) & (new_size-1);
|
||||
next_elem = elem->next;
|
||||
x = new_ht[h].chain;
|
||||
if( x ){
|
||||
elem->next = x;
|
||||
elem->prev = x->prev;
|
||||
if( x->prev ) x->prev->next = elem;
|
||||
else array->first = elem;
|
||||
x->prev = elem;
|
||||
}else{
|
||||
elem->next = array->first;
|
||||
if( array->first ) array->first->prev = elem;
|
||||
elem->prev = 0;
|
||||
array->first = elem;
|
||||
}
|
||||
new_ht[h].chain = elem;
|
||||
new_ht[h].count++;
|
||||
}
|
||||
}
|
||||
|
||||
/* This function (for internal use only) locates an element in an
|
||||
** array that matches the given key. The hash for this key has
|
||||
** already been computed and is passed as the 3rd parameter.
|
||||
*/
|
||||
static ArrayElem *ArrayFindElementGivenHash(
|
||||
const Array *array, /* The array to be searched */
|
||||
const Datum key, /* The key we are searching for */
|
||||
int h /* The hash for this key. */
|
||||
){
|
||||
ArrayElem *elem; /* Used to loop thru the element list */
|
||||
int count; /* Number of elements left to test */
|
||||
|
||||
if( array->count ){
|
||||
elem = array->ht[h].chain;
|
||||
count = array->ht[h].count;
|
||||
while( count-- && elem ){
|
||||
if( elem->key.n==key.n && memcmp(elem->key.p,key.p,key.n)==0 ){
|
||||
return elem;
|
||||
}
|
||||
elem = elem->next;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Attempt to locate an element of the associative array with a key
|
||||
** that matches "key". Return the ArrayElement if found and NULL if
|
||||
** if no match.
|
||||
*/
|
||||
static ArrayElem *ArrayFindElement(const Array *array, Datum key){
|
||||
int h; /* A hash on key */
|
||||
if( array->count==0 ) return 0;
|
||||
h = ArrayHash(key);
|
||||
return ArrayFindElementGivenHash(array, key, h & (array->htsize-1));
|
||||
}
|
||||
|
||||
/* Remove a single entry from the array given a pointer to that
|
||||
** element and a hash on the element's key.
|
||||
*/
|
||||
static void ArrayRemoveElementGivenHash(
|
||||
Array *array, /* The array containing "elem" */
|
||||
ArrayElem* elem, /* The element to be removed from the array */
|
||||
int h /* Hash value for the element */
|
||||
){
|
||||
if( elem->prev ){
|
||||
elem->prev->next = elem->next;
|
||||
}else{
|
||||
array->first = elem->next;
|
||||
}
|
||||
if( elem->next ){
|
||||
elem->next->prev = elem->prev;
|
||||
}
|
||||
if( array->ht[h].chain==elem ){
|
||||
array->ht[h].chain = elem->next;
|
||||
}
|
||||
array->ht[h].count--;
|
||||
if( array->ht[h].count<=0 ){
|
||||
array->ht[h].chain = 0;
|
||||
}
|
||||
sqliteFree( elem );
|
||||
array->count--;
|
||||
}
|
||||
|
||||
/* Attempt to locate an element of the associative array with a key
|
||||
** that matches "key". Return the data for this element if it is
|
||||
** found, or NULL if no match is found.
|
||||
*/
|
||||
static Datum ArrayFind(const Array *array, Datum key){
|
||||
int h; /* A hash on key */
|
||||
ArrayElem *elem; /* The element that matches key */
|
||||
static Datum nil = {0, 0};
|
||||
|
||||
if( array->count==0 ) return nil;
|
||||
h = ArrayHash(key);
|
||||
elem = ArrayFindElementGivenHash(array, key, h & (array->htsize-1));
|
||||
return elem ? elem->data : nil;
|
||||
}
|
||||
|
||||
/* Insert an element into the array. The key will be "key" and
|
||||
** the data will be "data".
|
||||
**
|
||||
** If no array element exists with a matching key, then a new
|
||||
** array element is created. The key is copied into the new element.
|
||||
** But only a pointer to the data is stored. NULL is returned.
|
||||
**
|
||||
** If another element already exists with the same key, then the
|
||||
** new data replaces the old data and the old data is returned.
|
||||
** The key is not copied in this instance.
|
||||
**
|
||||
** If the "data" parameter to this function is NULL, then the
|
||||
** element corresponding to "key" is removed from the array.
|
||||
*/
|
||||
static Datum ArrayInsert(Array *array, Datum key, Datum data){
|
||||
int hraw; /* Raw hash value of the key */
|
||||
int h; /* the hash of the key modulo hash table size */
|
||||
ArrayElem *elem; /* Used to loop thru the element list */
|
||||
ArrayElem *new_elem; /* New element added to the array */
|
||||
Datum rv; /* Return value */
|
||||
static Datum nil = {0, 0};
|
||||
|
||||
hraw = ArrayHash(key);
|
||||
h = hraw & (array->htsize-1);
|
||||
elem = ArrayFindElementGivenHash(array,key,h);
|
||||
if( elem ){
|
||||
Datum old_data = elem->data;
|
||||
if( data.p==0 ){
|
||||
ArrayRemoveElementGivenHash(array,elem,h);
|
||||
}else{
|
||||
elem->data = data;
|
||||
}
|
||||
return old_data;
|
||||
}
|
||||
if( data.p==0 ) return nil;
|
||||
new_elem = (ArrayElem*)sqliteMalloc( sizeof(ArrayElem) + key.n );
|
||||
if( new_elem==0 ) return nil;
|
||||
new_elem->key.n = key.n;
|
||||
new_elem->key.p = (void*)&new_elem[1];
|
||||
memcpy(new_elem->key.p, key.p, key.n);
|
||||
array->count++;
|
||||
if( array->htsize==0 ) ArrayRehash(array,4);
|
||||
if( array->count > array->htsize ){
|
||||
ArrayRehash(array,array->htsize*2);
|
||||
}
|
||||
h = hraw & (array->htsize-1);
|
||||
elem = array->ht[h].chain;
|
||||
if( elem ){
|
||||
new_elem->next = elem;
|
||||
new_elem->prev = elem->prev;
|
||||
if( elem->prev ){ elem->prev->next = new_elem; }
|
||||
else { array->first = new_elem; }
|
||||
elem->prev = new_elem;
|
||||
}else{
|
||||
new_elem->next = array->first;
|
||||
new_elem->prev = 0;
|
||||
if( array->first ){ array->first->prev = new_elem; }
|
||||
array->first = new_elem;
|
||||
}
|
||||
array->ht[h].count++;
|
||||
array->ht[h].chain = new_elem;
|
||||
new_elem->data = data;
|
||||
rv.p = 0;
|
||||
rv.n = 0;
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
** Information about each open database table is an instance of this
|
||||
** structure. There will only be one such structure for each
|
||||
** table. If the VDBE opens the same table twice (as will happen
|
||||
** for a self-join, for example) then two DbbeCursor structures are
|
||||
** created but there is only a single MTable structure.
|
||||
*/
|
||||
typedef struct MTable MTable;
|
||||
struct MTable {
|
||||
char *zName; /* Name of the table */
|
||||
int delOnClose; /* Delete when closing */
|
||||
int intKeyOnly; /* Use only integer keys on this table */
|
||||
Array data; /* The data in this stable */
|
||||
};
|
||||
|
||||
/*
|
||||
** The following structure contains all information used by GDBM
|
||||
** database driver. This is a subclass of the Dbbe structure.
|
||||
*/
|
||||
typedef struct Dbbex Dbbex;
|
||||
struct Dbbex {
|
||||
Dbbe dbbe; /* The base class */
|
||||
Array tables; /* All tables of the database */
|
||||
};
|
||||
|
||||
/*
|
||||
** An cursor into a database file is an instance of the following structure.
|
||||
** There can only be a single MTable structure for each disk file, but
|
||||
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
|
||||
** a cursor pointing to a particular part of the open MTable. The
|
||||
** MTable.nRef field hold a count of the number of DbbeCursor structures
|
||||
** associated with the same disk file.
|
||||
*/
|
||||
struct DbbeCursor {
|
||||
Dbbex *pBe; /* The database of which this record is a part */
|
||||
MTable *pTble; /* The database file for this table */
|
||||
ArrayElem *elem; /* Most recently accessed record */
|
||||
int needRewind; /* Next key should be the first */
|
||||
};
|
||||
|
||||
/*
|
||||
** Forward declaration
|
||||
*/
|
||||
static void sqliteMemCloseCursor(DbbeCursor *pCursr);
|
||||
|
||||
/*
|
||||
** Erase all the memory of an MTable
|
||||
*/
|
||||
static void deleteMTable(MTable *p){
|
||||
ArrayElem *i;
|
||||
for(i=ArrayFirst(&p->data); i; i=ArrayNext(i)){
|
||||
void *data = ArrayData(i);
|
||||
sqliteFree(data);
|
||||
}
|
||||
ArrayClear(&p->data);
|
||||
sqliteFree(p->zName);
|
||||
sqliteFree(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** Completely shutdown the given database. Close all files. Free all memory.
|
||||
*/
|
||||
static void sqliteMemClose(Dbbe *pDbbe){
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
MTable *pTble;
|
||||
ArrayElem *j;
|
||||
for(j=ArrayFirst(&pBe->tables); j; j=ArrayNext(j)){
|
||||
pTble = ArrayData(j);
|
||||
deleteMTable(pTble);
|
||||
}
|
||||
ArrayClear(&pBe->tables);
|
||||
sqliteDbbeCloseAllTempFiles(pDbbe);
|
||||
memset(pBe, 0, sizeof(*pBe));
|
||||
sqliteFree(pBe);
|
||||
}
|
||||
|
||||
/*
|
||||
** Translate the name of an SQL table (or index) into its
|
||||
** canonical name.
|
||||
**
|
||||
** Space to hold the canonical name is obtained from
|
||||
** sqliteMalloc() and must be freed by the calling function.
|
||||
*/
|
||||
static char *sqliteNameOfTable(const char *zTable){
|
||||
char *zNew = 0;
|
||||
int i, c;
|
||||
sqliteSetString(&zNew, zTable, 0);
|
||||
if( zNew==0 ) return 0;
|
||||
for(i=0; (c = zNew[i])!=0; i++){
|
||||
if( isupper(c) ){
|
||||
zNew[i] = tolower(c);
|
||||
}
|
||||
}
|
||||
return zNew;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a new table cursor. Write a pointer to the corresponding
|
||||
** DbbeCursor structure into *ppCursr. Return an integer success
|
||||
** code:
|
||||
**
|
||||
** SQLITE_OK It worked!
|
||||
**
|
||||
** SQLITE_NOMEM sqliteMalloc() failed
|
||||
**
|
||||
** SQLITE_PERM Attempt to access a file for which file
|
||||
** access permission is denied
|
||||
**
|
||||
** SQLITE_BUSY Another thread or process is already using
|
||||
** the corresponding file and has that file locked.
|
||||
**
|
||||
** SQLITE_READONLY The current thread already has this file open
|
||||
** readonly but you are trying to open for writing.
|
||||
** (This can happen if a SELECT callback tries to
|
||||
** do an UPDATE or DELETE.)
|
||||
**
|
||||
** If zTable is 0 or "", then a temporary database file is created and
|
||||
** a cursor to that temporary file is opened. The temporary file
|
||||
** will be deleted from the disk when it is closed.
|
||||
*/
|
||||
static int sqliteMemOpenCursor(
|
||||
Dbbe *pDbbe, /* The database the table belongs to */
|
||||
const char *zTable, /* The SQL name of the file to be opened */
|
||||
int writeable, /* True to open for writing */
|
||||
int intKeyOnly, /* True if only integer keys are used */
|
||||
DbbeCursor **ppCursr /* Write the resulting table pointer here */
|
||||
){
|
||||
DbbeCursor *pCursr; /* The new table cursor */
|
||||
char *zName; /* Canonical table name */
|
||||
MTable *pTble; /* The underlying data file for this table */
|
||||
int rc = SQLITE_OK; /* Return value */
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
|
||||
*ppCursr = 0;
|
||||
pCursr = sqliteMalloc( sizeof(*pCursr) );
|
||||
if( pCursr==0 ) return SQLITE_NOMEM;
|
||||
if( zTable ){
|
||||
Datum key;
|
||||
zName = sqliteNameOfTable(zTable);
|
||||
key.p = zName;
|
||||
key.n = strlen(zName);
|
||||
pTble = ArrayFind(&pBe->tables, key).p;
|
||||
}else{
|
||||
zName = 0;
|
||||
pTble = 0;
|
||||
}
|
||||
if( pTble==0 ){
|
||||
pTble = sqliteMalloc( sizeof(*pTble) );
|
||||
if( pTble==0 ){
|
||||
sqliteFree(zName);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
if( zName ){
|
||||
Datum ins_key, ins_data;
|
||||
pTble->zName = zName;
|
||||
pTble->delOnClose = 0;
|
||||
ins_data.p = pTble;
|
||||
ins_data.n = sizeof( *pTble );
|
||||
ins_key.p = zName;
|
||||
ins_key.n = strlen(zName);
|
||||
ArrayInsert(&pBe->tables, ins_key, ins_data);
|
||||
}else{
|
||||
pTble->zName = 0;
|
||||
pTble->delOnClose = 1;
|
||||
}
|
||||
pTble->intKeyOnly = intKeyOnly;
|
||||
ArrayInit(&pTble->data);
|
||||
}else{
|
||||
assert( pTble->intKeyOnly==intKeyOnly );
|
||||
sqliteFree(zName);
|
||||
}
|
||||
pCursr->pBe = pBe;
|
||||
pCursr->pTble = pTble;
|
||||
pCursr->needRewind = 1;
|
||||
*ppCursr = pCursr;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Drop a table from the database. The file on the disk that corresponds
|
||||
** to this table is deleted.
|
||||
*/
|
||||
static void sqliteMemDropTable(Dbbe *pDbbe, const char *zTable){
|
||||
char *zName; /* Name of the table file */
|
||||
Datum key, data;
|
||||
MTable *pTble;
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
|
||||
zName = sqliteNameOfTable(zTable);
|
||||
key.p = zName;
|
||||
key.n = strlen(zName);
|
||||
pTble = ArrayFind(&pBe->tables, key).p;
|
||||
if( pTble ){
|
||||
data.p = 0;
|
||||
data.n = 0;
|
||||
ArrayInsert(&pBe->tables, key, data);
|
||||
deleteMTable(pTble);
|
||||
}
|
||||
sqliteFree(zName);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a cursor previously opened by sqliteMemOpenCursor().
|
||||
**
|
||||
** There can be multiple cursors pointing to the same open file.
|
||||
** The underlying file is not closed until all cursors have been
|
||||
** closed. This routine decrements the MTable.nref field of the
|
||||
** underlying file and closes the file when nref reaches 0.
|
||||
*/
|
||||
static void sqliteMemCloseCursor(DbbeCursor *pCursr){
|
||||
MTable *pTble;
|
||||
Dbbex *pBe;
|
||||
if( pCursr==0 ) return;
|
||||
pTble = pCursr->pTble;
|
||||
pBe = pCursr->pBe;
|
||||
if( pTble->delOnClose ){
|
||||
deleteMTable(pTble);
|
||||
}
|
||||
sqliteFree(pCursr);
|
||||
}
|
||||
|
||||
/*
|
||||
** Reorganize a table to reduce search times and disk usage.
|
||||
*/
|
||||
static int sqliteMemReorganizeTable(Dbbe *pBe, const char *zTable){
|
||||
/* Do nothing */
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Fetch a single record from an open cursor. Return 1 on success
|
||||
** and 0 on failure.
|
||||
*/
|
||||
static int sqliteMemFetch(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
Datum key;
|
||||
key.n = nKey;
|
||||
key.p = pKey;
|
||||
assert( nKey==4 || pCursr->pTble->intKeyOnly==0 );
|
||||
pCursr->elem = ArrayFindElement(&pCursr->pTble->data, key);
|
||||
return pCursr->elem!=0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
static int sqliteMemTest(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
return sqliteMemFetch(pCursr, nKey, pKey);
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy bytes from the current key or data into a buffer supplied by
|
||||
** the calling function. Return the number of bytes copied.
|
||||
*/
|
||||
static
|
||||
int sqliteMemCopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( pCursr->elem==0 ) return 0;
|
||||
if( offset>=ArrayKeySize(pCursr->elem) ) return 0;
|
||||
if( offset+size>ArrayKeySize(pCursr->elem) ){
|
||||
n = ArrayKeySize(pCursr->elem) - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &((char*)ArrayKey(pCursr->elem))[offset], n);
|
||||
return n;
|
||||
}
|
||||
static
|
||||
int sqliteMemCopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( pCursr->elem==0 ) return 0;
|
||||
if( offset>=ArrayDataSize(pCursr->elem) ) return 0;
|
||||
if( offset+size>ArrayDataSize(pCursr->elem) ){
|
||||
n = ArrayDataSize(pCursr->elem) - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &((char*)ArrayData(pCursr->elem))[offset], n);
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to bytes from the key or data. The data returned
|
||||
** is ephemeral.
|
||||
*/
|
||||
static char *sqliteMemReadKey(DbbeCursor *pCursr, int offset){
|
||||
if( pCursr->elem==0 || offset<0 || offset>=ArrayKeySize(pCursr->elem) ){
|
||||
return "";
|
||||
}
|
||||
return &((char*)ArrayKey(pCursr->elem))[offset];
|
||||
}
|
||||
static char *sqliteMemReadData(DbbeCursor *pCursr, int offset){
|
||||
if( pCursr->elem==0 || offset<0 || offset>=ArrayDataSize(pCursr->elem) ){
|
||||
return "";
|
||||
}
|
||||
return &((char*)ArrayData(pCursr->elem))[offset];
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the total number of bytes in either data or key.
|
||||
*/
|
||||
static int sqliteMemKeyLength(DbbeCursor *pCursr){
|
||||
return pCursr->elem ? ArrayKeySize(pCursr->elem) : 0;
|
||||
}
|
||||
static int sqliteMemDataLength(DbbeCursor *pCursr){
|
||||
return pCursr->elem ? ArrayDataSize(pCursr->elem) : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Make is so that the next call to sqliteNextKey() finds the first
|
||||
** key of the table.
|
||||
*/
|
||||
static int sqliteMemRewind(DbbeCursor *pCursr){
|
||||
pCursr->needRewind = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read the next key from the table. Return 1 on success. Return
|
||||
** 0 if there are no more keys.
|
||||
*/
|
||||
static int sqliteMemNextKey(DbbeCursor *pCursr){
|
||||
if( pCursr->needRewind || pCursr->elem==0 ){
|
||||
pCursr->elem = ArrayFirst(&pCursr->pTble->data);
|
||||
pCursr->needRewind = 0;
|
||||
}else{
|
||||
pCursr->elem = ArrayNext(pCursr->elem);
|
||||
}
|
||||
return pCursr->elem!=0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a new integer key.
|
||||
*/
|
||||
static int sqliteMemNew(DbbeCursor *pCursr){
|
||||
int iKey;
|
||||
Datum key;
|
||||
int go = 1;
|
||||
|
||||
while( go ){
|
||||
iKey = sqliteRandomInteger();
|
||||
if( iKey==0 ) continue;
|
||||
key.p = (char*)&iKey;
|
||||
key.n = 4;
|
||||
go = ArrayFindElement(&pCursr->pTble->data, key)!=0;
|
||||
}
|
||||
return iKey;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write an entry into the table. Overwrite any prior entry with the
|
||||
** same key.
|
||||
*/
|
||||
static int sqliteMemPut(
|
||||
DbbeCursor *pCursr, /* Write new entry into this database table */
|
||||
int nKey, char *pKey, /* The key of the new entry */
|
||||
int nData, char *pData /* The data of the new entry */
|
||||
){
|
||||
Datum data, key;
|
||||
data.n = nData;
|
||||
data.p = sqliteMalloc( data.n );
|
||||
memcpy(data.p, pData, data.n);
|
||||
key.n = nKey;
|
||||
key.p = pKey;
|
||||
assert( nKey==4 || pCursr->pTble->intKeyOnly==0 );
|
||||
data = ArrayInsert(&pCursr->pTble->data, key, data);
|
||||
if( data.p ){
|
||||
sqliteFree(data.p);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove an entry from a table, if the entry exists.
|
||||
*/
|
||||
static int sqliteMemDelete(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
Datum key, data;
|
||||
key.n = nKey;
|
||||
key.p = pKey;
|
||||
data.p = 0;
|
||||
data.n = 0;
|
||||
data = ArrayInsert(&pCursr->pTble->data, key, data);
|
||||
if( data.p ){
|
||||
sqliteFree(data.p);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a temporary file. The file is located in the current working
|
||||
** directory.
|
||||
*/
|
||||
static int sqliteMemOpenTempFile(Dbbe *pDbbe, FILE **ppFile){
|
||||
const char *zTemps[] = { "/usr/tmp", "/var/tmp", "/tmp", "/temp", 0};
|
||||
const char *zDir;
|
||||
int i;
|
||||
struct stat statbuf;
|
||||
for(i=0; zTemps[i]; i++){
|
||||
zDir = zTemps[i];
|
||||
if( stat("/usr/tmp", &statbuf)==0 && S_ISDIR(statbuf.st_mode)
|
||||
&& access("/usr/tmp", W_OK|X_OK)==0 ){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( zDir==0 ) zDir = ".";
|
||||
return sqliteDbbeOpenTempFile(zDir, pDbbe, ppFile);
|
||||
}
|
||||
|
||||
/*
|
||||
** This variable contains pointers to all of the access methods
|
||||
** used to implement the MEMORY backend.
|
||||
*/
|
||||
static struct DbbeMethods memoryMethods = {
|
||||
/* n Close */ sqliteMemClose,
|
||||
/* OpenCursor */ sqliteMemOpenCursor,
|
||||
/* DropTable */ sqliteMemDropTable,
|
||||
/* ReorganizeTable */ sqliteMemReorganizeTable,
|
||||
/* CloseCursor */ sqliteMemCloseCursor,
|
||||
/* Fetch */ sqliteMemFetch,
|
||||
/* Test */ sqliteMemTest,
|
||||
/* CopyKey */ sqliteMemCopyKey,
|
||||
/* CopyData */ sqliteMemCopyData,
|
||||
/* ReadKey */ sqliteMemReadKey,
|
||||
/* ReadData */ sqliteMemReadData,
|
||||
/* KeyLength */ sqliteMemKeyLength,
|
||||
/* DataLength */ sqliteMemDataLength,
|
||||
/* NextKey */ sqliteMemNextKey,
|
||||
/* Rewind */ sqliteMemRewind,
|
||||
/* New */ sqliteMemNew,
|
||||
/* Put */ sqliteMemPut,
|
||||
/* Delete */ sqliteMemDelete,
|
||||
/* OpenTempFile */ sqliteMemOpenTempFile,
|
||||
/* CloseTempFile */ sqliteDbbeCloseTempFile
|
||||
};
|
||||
|
||||
/*
|
||||
** This routine opens a new database. For the GDBM driver
|
||||
** implemented here, the database name is the name of the directory
|
||||
** containing all the files of the database.
|
||||
**
|
||||
** If successful, a pointer to the Dbbe structure is returned.
|
||||
** If there are errors, an appropriate error message is left
|
||||
** in *pzErrMsg and NULL is returned.
|
||||
*/
|
||||
Dbbe *sqliteMemOpen(
|
||||
const char *zName, /* The name of the database */
|
||||
int writeFlag, /* True if we will be writing to the database */
|
||||
int createFlag, /* True to create database if it doesn't exist */
|
||||
char **pzErrMsg /* Write error messages (if any) here */
|
||||
){
|
||||
Dbbex *pNew;
|
||||
|
||||
pNew = sqliteMalloc( sizeof(*pNew) );
|
||||
if( pNew==0 ){
|
||||
sqliteSetString(pzErrMsg, "out of memory", 0);
|
||||
return 0;
|
||||
}
|
||||
ArrayInit(&pNew->tables);
|
||||
pNew->dbbe.x = &memoryMethods;
|
||||
return &pNew->dbbe;
|
||||
}
|
||||
+3
-3
@@ -24,7 +24,7 @@
|
||||
** This file contains C code routines that are called by the parser
|
||||
** to handle DELETE FROM statements.
|
||||
**
|
||||
** $Id: delete.c,v 1.6 2000/06/21 13:59:11 drh Exp $
|
||||
** $Id: delete.c,v 1.7 2001/01/15 22:51:10 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -105,9 +105,9 @@ void sqliteDeleteFrom(
|
||||
*/
|
||||
base = pParse->nTab;
|
||||
sqliteVdbeAddOp(v, OP_ListRewind, 0, 0, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_Open, base, 1, pTab->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenTbl, base, 1, pTab->zName, 0);
|
||||
for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
|
||||
sqliteVdbeAddOp(v, OP_Open, base+i, 1, pIdx->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, base+i, 1, pIdx->zName, 0);
|
||||
}
|
||||
end = sqliteVdbeMakeLabel(v);
|
||||
addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end, 0, 0);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
This directory is intended to hold "experimental" files.
|
||||
|
||||
The code in this directory does not necessary work. It may
|
||||
or may not be added to future SQLite releases. We just need
|
||||
a place to put ideas and works-in-progress and so this
|
||||
directory was created.
|
||||
|
||||
If you are interested in the production versions of SQLite,
|
||||
you can safely ignore this directory.
|
||||
+1495
File diff suppressed because it is too large
Load Diff
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
** Copyright (c) 2001 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** $Id: db.h,v 1.1 2001/02/11 16:56:24 drh Exp $
|
||||
*/
|
||||
|
||||
typedef struct Db Db;
|
||||
typedef struct DbCursor DbCursor;
|
||||
|
||||
int sqliteDbOpen(const char *filename, Db**);
|
||||
int sqliteDbClose(Db*);
|
||||
int sqliteDbBeginTransaction(Db*);
|
||||
int sqliteDbCommit(Db*);
|
||||
int sqliteDbRollback(Db*);
|
||||
|
||||
int sqliteDbCreateTable(Db*, int *pTblno);
|
||||
int sqliteDbDropTable(Db*, int tblno);
|
||||
|
||||
int sqliteDbCursorOpen(Db*, int tblno, DbCursor**);
|
||||
int sqliteDbCursorClose(DbCursor*);
|
||||
|
||||
int sqliteDbCursorFirst(DbCursor*);
|
||||
int sqliteDbCursorNext(DbCursor*);
|
||||
int sqliteDbCursorDatasize(DbCursor*);
|
||||
int sqliteDbCursorKeysize(DbCursor*);
|
||||
int sqliteDbCursorRead(DbCursor*, int amt, int offset, void *buf);
|
||||
int sqliteDbCursorReadKey(DbCursor*, int amt, int offset, void *buf);
|
||||
int sqliteDbCursorWrite(DbCursor*, int amt, int offset, const void *buf);
|
||||
|
||||
int sqliteDbCursorFind(DbCursor*, int nKey, const void *pKey, int createFlag);
|
||||
int sqliteDbCursorResize(DbCursor*, int nData);
|
||||
@@ -0,0 +1,618 @@
|
||||
/*
|
||||
** Copyright (c) 2000 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains code to implement the database backend (DBBE)
|
||||
** for sqlite. The database backend is the interface between
|
||||
** sqlite and the code that does the actually reading and writing
|
||||
** of information to the disk.
|
||||
**
|
||||
** This file uses Berkeley Database version 1.85 as the database backend.
|
||||
**
|
||||
** $Id: dbbebdb1.c,v 1.1 2001/02/11 16:56:24 drh Exp $
|
||||
*/
|
||||
#ifdef USE_BDB2
|
||||
|
||||
#include "sqliteInt.h"
|
||||
#include <sys/types.h>
|
||||
#include <limits.h>
|
||||
#include <db.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
/*
|
||||
** Information about each open disk file is an instance of this
|
||||
** structure. There will only be one such structure for each
|
||||
** disk file. If the VDBE opens the same file twice (as will happen
|
||||
** for a self-join, for example) then two DbbeCursor structures are
|
||||
** created but there is only a single BeFile structure with an
|
||||
** nRef of 2.
|
||||
*/
|
||||
typedef struct BeFile BeFile;
|
||||
struct BeFile {
|
||||
char *zName; /* Name of the file */
|
||||
DB dbf; /* The file itself */
|
||||
int nRef; /* Number of references */
|
||||
int delOnClose; /* Delete when closing */
|
||||
int writeable; /* Opened for writing */
|
||||
DbbeCursor *pCursor; /* Which of several DbbeCursors has the file cursor */
|
||||
BeFile *pNext, *pPrev; /* Next and previous on list of open files */
|
||||
};
|
||||
|
||||
/*
|
||||
** The following structure contains all information used by BDB2
|
||||
** database driver. This is a subclass of the Dbbe structure.
|
||||
*/
|
||||
typedef struct Dbbex Dbbex;
|
||||
struct Dbbex {
|
||||
Dbbe dbbe; /* The base class */
|
||||
int write; /* True for write permission */
|
||||
BeFile *pOpen; /* List of open files */
|
||||
char *zDir; /* Directory hold the database */
|
||||
};
|
||||
|
||||
/*
|
||||
** An cursor into a database file is an instance of the following structure.
|
||||
** There can only be a single BeFile structure for each disk file, but
|
||||
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
|
||||
** a cursor pointing to a particular part of the open BeFile. The
|
||||
** BeFile.nRef field hold a count of the number of DbbeCursor structures
|
||||
** associated with the same disk file.
|
||||
*/
|
||||
struct DbbeCursor {
|
||||
Dbbex *pBe; /* The database of which this record is a part */
|
||||
BeFile *pFile; /* The database file for this table */
|
||||
DBT key; /* Most recently used key */
|
||||
DBT data; /* Most recent data */
|
||||
int needRewind; /* Next key should be the first */
|
||||
int readPending; /* The fetch hasn't actually been done yet */
|
||||
};
|
||||
|
||||
/*
|
||||
** The "mkdir()" function only takes one argument under Windows.
|
||||
*/
|
||||
#if OS_WIN
|
||||
# define mkdir(A,B) mkdir(A)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Forward declaration
|
||||
*/
|
||||
static void sqliteBdb1CloseCursor(DbbeCursor *pCursr);
|
||||
|
||||
/*
|
||||
** Completely shutdown the given database. Close all files. Free all memory.
|
||||
*/
|
||||
static void sqliteBdb1Close(Dbbe *pDbbe){
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
BeFile *pFile, *pNext;
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pNext){
|
||||
pNext = pFile->pNext;
|
||||
(*pFile->dbf)(pFile->dbf);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
sqliteDbbeCloseAllTempFiles(pDbbe);
|
||||
memset(pBe, 0, sizeof(*pBe));
|
||||
sqliteFree(pBe);
|
||||
}
|
||||
|
||||
/*
|
||||
** Translate the name of an SQL table (or index) into the name
|
||||
** of a file that holds the key/data pairs for that table or
|
||||
** index. Space to hold the filename is obtained from
|
||||
** sqliteMalloc() and must be freed by the calling function.
|
||||
*/
|
||||
static char *sqliteFileOfTable(Dbbex *pBe, const char *zTable){
|
||||
return sqliteDbbeNameToFile(pBe->zDir, zTable, ".tbl");
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a new table cursor. Write a pointer to the corresponding
|
||||
** DbbeCursor structure into *ppCursr. Return an integer success
|
||||
** code:
|
||||
**
|
||||
** SQLITE_OK It worked!
|
||||
**
|
||||
** SQLITE_NOMEM sqliteMalloc() failed
|
||||
**
|
||||
** SQLITE_PERM Attempt to access a file for which file
|
||||
** access permission is denied
|
||||
**
|
||||
** SQLITE_BUSY Another thread or process is already using
|
||||
** the corresponding file and has that file locked.
|
||||
**
|
||||
** SQLITE_READONLY The current thread already has this file open
|
||||
** readonly but you are trying to open for writing.
|
||||
** (This can happen if a SELECT callback tries to
|
||||
** do an UPDATE or DELETE.)
|
||||
**
|
||||
** If zTable is 0 or "", then a temporary database file is created and
|
||||
** a cursor to that temporary file is opened. The temporary file
|
||||
** will be deleted from the disk when it is closed.
|
||||
*/
|
||||
static int sqliteBdb1OpenCursor(
|
||||
Dbbe *pDbbe, /* The database the table belongs to */
|
||||
const char *zTable, /* The SQL name of the file to be opened */
|
||||
int writeable, /* True to open for writing */
|
||||
int intKeyOnly, /* True if only integer keys are used */
|
||||
DbbeCursor **ppCursr /* Write the resulting table pointer here */
|
||||
){
|
||||
char *zFile; /* Name of the table file */
|
||||
DbbeCursor *pCursr; /* The new table cursor */
|
||||
BeFile *pFile; /* The underlying data file for this table */
|
||||
int rc = SQLITE_OK; /* Return value */
|
||||
int open_flags; /* Flags passed to dbopen() */
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
|
||||
*ppCursr = 0;
|
||||
pCursr = sqliteMalloc( sizeof(*pCursr) );
|
||||
if( pCursr==0 ) return SQLITE_NOMEM;
|
||||
if( zTable ){
|
||||
zFile = sqliteFileOfTable(pBe, zTable);
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pFile->pNext){
|
||||
if( strcmp(pFile->zName,zFile)==0 ) break;
|
||||
}
|
||||
}else{
|
||||
pFile = 0;
|
||||
zFile = 0;
|
||||
}
|
||||
if( pFile==0 ){
|
||||
if( writeable ){
|
||||
open_flags = O_RDWR|O_CREAT
|
||||
}else{
|
||||
open_flags = O_RDONLY;
|
||||
}
|
||||
pFile = sqliteMalloc( sizeof(*pFile) );
|
||||
if( pFile==0 ){
|
||||
sqliteFree(zFile);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
if( zFile ){
|
||||
if( !writeable || pBe->write ){
|
||||
pFile->dbf = dbopen(zFile, open_flags, DB_HASH, 0);
|
||||
}else{
|
||||
pFile->dbf = 0;
|
||||
}
|
||||
}else{
|
||||
int limit;
|
||||
char zRandom[50];
|
||||
zFile = 0;
|
||||
limit = 5;
|
||||
do {
|
||||
sqliteRandomName(zRandom, "_temp_table_");
|
||||
sqliteFree(zFile);
|
||||
zFile = sqliteFileOfTable(pBe, zRandom);
|
||||
pFile->dbf = dbopen(zFile, open_flags, DB_HASH, 0);
|
||||
}while( pFile->dbf==0 && limit-- >= 0);
|
||||
pFile->delOnClose = 1;
|
||||
}
|
||||
pFile->writeable = writeable;
|
||||
pFile->zName = zFile;
|
||||
pFile->nRef = 1;
|
||||
pFile->pPrev = 0;
|
||||
if( pBe->pOpen ){
|
||||
pBe->pOpen->pPrev = pFile;
|
||||
}
|
||||
pFile->pCursor = 0;
|
||||
pFile->pNext = pBe->pOpen;
|
||||
pBe->pOpen = pFile;
|
||||
if( pFile->dbf==0 ){
|
||||
if( !writeable && access(zFile,0) ){
|
||||
/* Trying to read a non-existant file. This is OK. All the
|
||||
** reads will return empty, which is what we want. */
|
||||
rc = SQLITE_OK;
|
||||
}else if( pBe->write==0 ){
|
||||
rc = SQLITE_READONLY;
|
||||
}else if( access(zFile,W_OK|R_OK) ){
|
||||
rc = SQLITE_PERM;
|
||||
}else{
|
||||
rc = SQLITE_BUSY;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
sqliteFree(zFile);
|
||||
pFile->nRef++;
|
||||
if( writeable && !pFile->writeable ){
|
||||
rc = SQLITE_READONLY;
|
||||
}
|
||||
}
|
||||
pCursr->pBe = pBe;
|
||||
pCursr->pFile = pFile;
|
||||
pCursr->readPending = 0;
|
||||
pCursr->needRewind = 1;
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqliteBdb1CloseCursor(pCursr);
|
||||
*ppCursr = 0;
|
||||
}else{
|
||||
*ppCursr = pCursr;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Drop a table from the database. The file on the disk that corresponds
|
||||
** to this table is deleted.
|
||||
*/
|
||||
static void sqliteBdb1DropTable(Dbbe *pBe, const char *zTable){
|
||||
char *zFile; /* Name of the table file */
|
||||
|
||||
zFile = sqliteFileOfTable((Dbbex*)pBe, zTable);
|
||||
unlink(zFile);
|
||||
sqliteFree(zFile);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a cursor previously opened by sqliteBdb1OpenCursor().
|
||||
**
|
||||
** There can be multiple cursors pointing to the same open file.
|
||||
** The underlying file is not closed until all cursors have been
|
||||
** closed. This routine decrements the BeFile.nref field of the
|
||||
** underlying file and closes the file when nref reaches 0.
|
||||
*/
|
||||
static void sqliteBdb1CloseCursor(DbbeCursor *pCursr){
|
||||
BeFile *pFile;
|
||||
Dbbex *pBe;
|
||||
if( pCursr==0 ) return;
|
||||
pFile = pCursr->pFile;
|
||||
pBe = pCursr->pBe;
|
||||
if( pFile->pCursor==pCursr ){
|
||||
pFile->pCursor = 0;
|
||||
}
|
||||
pFile->nRef--;
|
||||
if( pFile->dbf!=NULL ){
|
||||
(*pFile->dbf->sync)(pFile->dbf, 0);
|
||||
}
|
||||
if( pFile->nRef<=0 ){
|
||||
if( pFile->dbf!=NULL ){
|
||||
(*pFile->dbf->close)(pFile->dbf);
|
||||
}
|
||||
if( pFile->pPrev ){
|
||||
pFile->pPrev->pNext = pFile->pNext;
|
||||
}else{
|
||||
pBe->pOpen = pFile->pNext;
|
||||
}
|
||||
if( pFile->pNext ){
|
||||
pFile->pNext->pPrev = pFile->pPrev;
|
||||
}
|
||||
if( pFile->delOnClose ){
|
||||
unlink(pFile->zName);
|
||||
}
|
||||
sqliteFree(pFile->zName);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
if( pCursr->key.dptr ) free(pCursr->key.dptr); ######
|
||||
if( pCursr->data.dptr ) free(pCursr->data.dptr); ######
|
||||
memset(pCursr, 0, sizeof(*pCursr));
|
||||
sqliteFree(pCursr);
|
||||
}
|
||||
|
||||
/*
|
||||
** Reorganize a table to reduce search times and disk usage.
|
||||
*/
|
||||
static int sqliteBdb1ReorganizeTable(Dbbe *pBe, const char *zTable){
|
||||
/* No-op */
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Clear the given datum
|
||||
*/
|
||||
static void datumClear(datum *p){
|
||||
if( p->dptr ) free(p->dptr); ########
|
||||
p->data = 0;
|
||||
p->size = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Fetch a single record from an open cursor. Return 1 on success
|
||||
** and 0 on failure.
|
||||
*/
|
||||
static int sqliteBdb1Fetch(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
DBT key;
|
||||
key.size = nKey;
|
||||
key.data = pKey;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return pCursr->data.dptr!=0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
static int sqliteBdb1Test(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
DBT key;
|
||||
int result = 0;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
result = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy bytes from the current key or data into a buffer supplied by
|
||||
** the calling function. Return the number of bytes copied.
|
||||
*/
|
||||
static
|
||||
int sqliteBdb1CopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( offset>=pCursr->key.dsize ) return 0;
|
||||
if( offset+size>pCursr->key.dsize ){
|
||||
n = pCursr->key.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->key.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
static
|
||||
int sqliteBdb1CopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset>=pCursr->data.dsize ) return 0;
|
||||
if( offset+size>pCursr->data.dsize ){
|
||||
n = pCursr->data.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->data.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to bytes from the key or data. The data returned
|
||||
** is ephemeral.
|
||||
*/
|
||||
static char *sqliteBdb1ReadKey(DbbeCursor *pCursr, int offset){
|
||||
if( offset<0 || offset>=pCursr->key.dsize ) return "";
|
||||
return &pCursr->key.dptr[offset];
|
||||
}
|
||||
static char *sqliteBdb1ReadData(DbbeCursor *pCursr, int offset){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset<0 || offset>=pCursr->data.dsize ) return "";
|
||||
return &pCursr->data.dptr[offset];
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the total number of bytes in either data or key.
|
||||
*/
|
||||
static int sqliteBdb1KeyLength(DbbeCursor *pCursr){
|
||||
return pCursr->key.dsize;
|
||||
}
|
||||
static int sqliteBdb1DataLength(DbbeCursor *pCursr){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
return pCursr->data.dsize;
|
||||
}
|
||||
|
||||
/*
|
||||
** Make is so that the next call to sqliteNextKey() finds the first
|
||||
** key of the table.
|
||||
*/
|
||||
static int sqliteBdb1Rewind(DbbeCursor *pCursr){
|
||||
pCursr->needRewind = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read the next key from the table. Return 1 on success. Return
|
||||
** 0 if there are no more keys.
|
||||
*/
|
||||
static int sqliteBdb1NextKey(DbbeCursor *pCursr){
|
||||
DBT nextkey;
|
||||
int rc;
|
||||
if( pCursr==0 || pCursr->pFile==0 || pCursr->pFile->dbf==0 ){
|
||||
pCursr->readPending = 0;
|
||||
return 0;
|
||||
}
|
||||
if( pCursr->needRewind ){
|
||||
nextkey = gdbm_firstkey(pCursr->pFile->dbf);
|
||||
pCursr->needRewind = 0;
|
||||
}else{
|
||||
nextkey = gdbm_nextkey(pCursr->pFile->dbf, pCursr->key);
|
||||
}
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
pCursr->key = nextkey;
|
||||
if( pCursr->key.dptr ){
|
||||
pCursr->readPending = 1;
|
||||
rc = 1;
|
||||
}else{
|
||||
pCursr->needRewind = 1;
|
||||
pCursr->readPending = 0;
|
||||
rc = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a new integer key.
|
||||
*/
|
||||
static int sqliteBdb1New(DbbeCursor *pCursr){
|
||||
int iKey;
|
||||
DBT key;
|
||||
int go = 1;
|
||||
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return 1;
|
||||
while( go ){
|
||||
iKey = sqliteRandomInteger();
|
||||
if( iKey==0 ) continue;
|
||||
key.dptr = (char*)&iKey;
|
||||
key.dsize = 4;
|
||||
go = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return iKey;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write an entry into the table. Overwrite any prior entry with the
|
||||
** same key.
|
||||
*/
|
||||
static int sqliteBdb1Put(
|
||||
DbbeCursor *pCursr, /* Write to the database associated with this cursor */
|
||||
int nKey, /* Number of bytes in the key */
|
||||
char *pKey, /* The data for the key */
|
||||
int nData, /* Number of bytes of data */
|
||||
char *pData /* The data */
|
||||
){
|
||||
DBT data, key;
|
||||
int rc;
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
data.dsize = nData;
|
||||
data.dptr = pData;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_store(pCursr->pFile->dbf, key, data, GDBM_REPLACE);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove an entry from a table, if the entry exists.
|
||||
*/
|
||||
static int sqliteBdb1Delete(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
DBT key;
|
||||
int rc;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_delete(pCursr->pFile->dbf, key);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a temporary file. The file is located in the same directory
|
||||
** as the rest of the database.
|
||||
*/
|
||||
static int sqliteBdb1OpenTempFile(Dbbe *pDbbe, FILE **ppFile){
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
return sqliteDbbeOpenTempFile(pBe->zDir, pDbbe, ppFile);
|
||||
}
|
||||
|
||||
/*
|
||||
** This variable contains pointers to all of the access methods
|
||||
** used to implement the GDBM backend.
|
||||
*/
|
||||
static struct DbbeMethods gdbmMethods = {
|
||||
/* n Close */ sqliteBdb1Close,
|
||||
/* OpenCursor */ sqliteBdb1OpenCursor,
|
||||
/* DropTable */ sqliteBdb1DropTable,
|
||||
/* ReorganizeTable */ sqliteBdb1ReorganizeTable,
|
||||
/* CloseCursor */ sqliteBdb1CloseCursor,
|
||||
/* Fetch */ sqliteBdb1Fetch,
|
||||
/* Test */ sqliteBdb1Test,
|
||||
/* CopyKey */ sqliteBdb1CopyKey,
|
||||
/* CopyData */ sqliteBdb1CopyData,
|
||||
/* ReadKey */ sqliteBdb1ReadKey,
|
||||
/* ReadData */ sqliteBdb1ReadData,
|
||||
/* KeyLength */ sqliteBdb1KeyLength,
|
||||
/* DataLength */ sqliteBdb1DataLength,
|
||||
/* NextKey */ sqliteBdb1NextKey,
|
||||
/* Rewind */ sqliteBdb1Rewind,
|
||||
/* New */ sqliteBdb1New,
|
||||
/* Put */ sqliteBdb1Put,
|
||||
/* Delete */ sqliteBdb1Delete,
|
||||
/* OpenTempFile */ sqliteBdb1OpenTempFile,
|
||||
/* CloseTempFile */ sqliteDbbeCloseTempFile
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
** This routine opens a new database. For the GDBM driver
|
||||
** implemented here, the database name is the name of the directory
|
||||
** containing all the files of the database.
|
||||
**
|
||||
** If successful, a pointer to the Dbbe structure is returned.
|
||||
** If there are errors, an appropriate error message is left
|
||||
** in *pzErrMsg and NULL is returned.
|
||||
*/
|
||||
Dbbe *sqliteBdb1Open(
|
||||
const char *zName, /* The name of the database */
|
||||
int writeFlag, /* True if we will be writing to the database */
|
||||
int createFlag, /* True to create database if it doesn't exist */
|
||||
char **pzErrMsg /* Write error messages (if any) here */
|
||||
){
|
||||
Dbbex *pNew;
|
||||
struct stat statbuf;
|
||||
char *zMaster;
|
||||
|
||||
if( !writeFlag ) createFlag = 0;
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
if( createFlag ) mkdir(zName, 0750);
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
sqliteSetString(pzErrMsg, createFlag ?
|
||||
"can't find or create directory \"" : "can't find directory \"",
|
||||
zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if( !S_ISDIR(statbuf.st_mode) ){
|
||||
sqliteSetString(pzErrMsg, "not a directory: \"", zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
if( access(zName, writeFlag ? (X_OK|W_OK|R_OK) : (X_OK|R_OK)) ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied", 0);
|
||||
return 0;
|
||||
}
|
||||
zMaster = 0;
|
||||
sqliteSetString(&zMaster, zName, "/" MASTER_NAME ".tbl", 0);
|
||||
if( stat(zMaster, &statbuf)==0
|
||||
&& access(zMaster, writeFlag ? (W_OK|R_OK) : R_OK)!=0 ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied for ", zMaster, 0);
|
||||
sqliteFree(zMaster);
|
||||
return 0;
|
||||
}
|
||||
sqliteFree(zMaster);
|
||||
pNew = sqliteMalloc(sizeof(Dbbex) + strlen(zName) + 1);
|
||||
if( pNew==0 ){
|
||||
sqliteSetString(pzErrMsg, "out of memory", 0);
|
||||
return 0;
|
||||
}
|
||||
pNew->dbbe.x = &gdbmMethods;
|
||||
pNew->zDir = (char*)&pNew[1];
|
||||
strcpy(pNew->zDir, zName);
|
||||
pNew->write = writeFlag;
|
||||
pNew->pOpen = 0;
|
||||
return &pNew->dbbe;
|
||||
}
|
||||
@@ -0,0 +1,776 @@
|
||||
/*
|
||||
** Copyright (c) 2000 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains code to implement the database backend (DBBE)
|
||||
** for sqlite. The database backend is the interface between
|
||||
** sqlite and the code that does the actually reading and writing
|
||||
** of information to the disk.
|
||||
**
|
||||
** This file uses GDBM as the database backend. It should be
|
||||
** relatively simple to convert to a different database such
|
||||
** as NDBM, SDBM, or BerkeleyDB.
|
||||
**
|
||||
** $Id: dbbemird.c,v 1.1 2001/02/11 16:56:24 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <gdbm.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
/*
|
||||
** Information about each open disk file is an instance of this
|
||||
** structure. There will only be one such structure for each
|
||||
** disk file. If the VDBE opens the same file twice (as will happen
|
||||
** for a self-join, for example) then two DbbeCursor structures are
|
||||
** created but there is only a single BeFile structure with an
|
||||
** nRef of 2.
|
||||
*/
|
||||
typedef struct BeFile BeFile;
|
||||
struct BeFile {
|
||||
char *zName; /* Name of the file */
|
||||
GDBM_FILE dbf; /* The file itself */
|
||||
int nRef; /* Number of references */
|
||||
int delOnClose; /* Delete when closing */
|
||||
int writeable; /* Opened for writing */
|
||||
BeFile *pNext, *pPrev; /* Next and previous on list of open files */
|
||||
};
|
||||
|
||||
/*
|
||||
** The following structure holds the current state of the RC4 algorithm.
|
||||
** We use RC4 as a random number generator. Each call to RC4 gives
|
||||
** a random 8-bit number.
|
||||
**
|
||||
** Nothing in this file or anywhere else in SQLite does any kind of
|
||||
** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random
|
||||
** number generator) not as an encryption device.
|
||||
*/
|
||||
struct rc4 {
|
||||
int i, j;
|
||||
int s[256];
|
||||
};
|
||||
|
||||
/*
|
||||
** The following structure contains all information used by GDBM
|
||||
** database driver. This is a subclass of the Dbbe structure.
|
||||
*/
|
||||
typedef struct Dbbex Dbbex;
|
||||
struct Dbbex {
|
||||
Dbbe dbbe; /* The base class */
|
||||
char *zDir; /* The directory containing the database */
|
||||
int write; /* True for write permission */
|
||||
BeFile *pOpen; /* List of open files */
|
||||
int nTemp; /* Number of temporary files created */
|
||||
FILE **apTemp; /* Space to hold temporary file pointers */
|
||||
char **azTemp; /* Names of the temporary files */
|
||||
struct rc4 rc4; /* The random number generator */
|
||||
};
|
||||
|
||||
/*
|
||||
** An cursor into a database file is an instance of the following structure.
|
||||
** There can only be a single BeFile structure for each disk file, but
|
||||
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
|
||||
** a cursor pointing to a particular part of the open BeFile. The
|
||||
** BeFile.nRef field hold a count of the number of DbbeCursor structures
|
||||
** associated with the same disk file.
|
||||
*/
|
||||
struct DbbeCursor {
|
||||
Dbbex *pBe; /* The database of which this record is a part */
|
||||
BeFile *pFile; /* The database file for this table */
|
||||
datum key; /* Most recently used key */
|
||||
datum data; /* Most recent data */
|
||||
int needRewind; /* Next key should be the first */
|
||||
int readPending; /* The fetch hasn't actually been done yet */
|
||||
};
|
||||
|
||||
/*
|
||||
** Initialize the RC4 PRNG. "seed" is a pointer to some random
|
||||
** data used to initialize the PRNG.
|
||||
*/
|
||||
static void rc4init(struct rc4 *p, char *seed, int seedlen){
|
||||
int i;
|
||||
char k[256];
|
||||
p->j = 0;
|
||||
p->i = 0;
|
||||
for(i=0; i<256; i++){
|
||||
p->s[i] = i;
|
||||
k[i] = seed[i%seedlen];
|
||||
}
|
||||
for(i=0; i<256; i++){
|
||||
int t;
|
||||
p->j = (p->j + p->s[i] + k[i]) & 0xff;
|
||||
t = p->s[p->j];
|
||||
p->s[p->j] = p->s[i];
|
||||
p->s[i] = t;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a single 8-bit random value from the RC4 PRNG.
|
||||
*/
|
||||
static int rc4byte(struct rc4 *p){
|
||||
int t;
|
||||
p->i = (p->i + 1) & 0xff;
|
||||
p->j = (p->j + p->s[p->i]) & 0xff;
|
||||
t = p->s[p->i];
|
||||
p->s[p->i] = p->s[p->j];
|
||||
p->s[p->j] = t;
|
||||
t = p->s[p->i] + p->s[p->j];
|
||||
return t & 0xff;
|
||||
}
|
||||
|
||||
/*
|
||||
** The "mkdir()" function only takes one argument under Windows.
|
||||
*/
|
||||
#if OS_WIN
|
||||
# define mkdir(A,B) mkdir(A)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Forward declaration
|
||||
*/
|
||||
static void sqliteGdbmCloseCursor(DbbeCursor *pCursr);
|
||||
|
||||
/*
|
||||
** Completely shutdown the given database. Close all files. Free all memory.
|
||||
*/
|
||||
static void sqliteGdbmClose(Dbbe *pDbbe){
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
BeFile *pFile, *pNext;
|
||||
int i;
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pNext){
|
||||
pNext = pFile->pNext;
|
||||
gdbm_close(pFile->dbf);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
for(i=0; i<pBe->nTemp; i++){
|
||||
if( pBe->apTemp[i]!=0 ){
|
||||
unlink(pBe->azTemp[i]);
|
||||
fclose(pBe->apTemp[i]);
|
||||
sqliteFree(pBe->azTemp[i]);
|
||||
pBe->apTemp[i] = 0;
|
||||
pBe->azTemp[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sqliteFree(pBe->azTemp);
|
||||
sqliteFree(pBe->apTemp);
|
||||
memset(pBe, 0, sizeof(*pBe));
|
||||
sqliteFree(pBe);
|
||||
}
|
||||
|
||||
/*
|
||||
** Translate the name of an SQL table (or index) into the name
|
||||
** of a file that holds the key/data pairs for that table or
|
||||
** index. Space to hold the filename is obtained from
|
||||
** sqliteMalloc() and must be freed by the calling function.
|
||||
*/
|
||||
static char *sqliteFileOfTable(Dbbex *pBe, const char *zTable){
|
||||
char *zFile = 0;
|
||||
int i;
|
||||
sqliteSetString(&zFile, pBe->zDir, "/", zTable, ".tbl", 0);
|
||||
if( zFile==0 ) return 0;
|
||||
for(i=strlen(pBe->zDir)+1; zFile[i]; i++){
|
||||
int c = zFile[i];
|
||||
if( isupper(c) ){
|
||||
zFile[i] = tolower(c);
|
||||
}else if( !isalnum(c) && c!='-' && c!='_' && c!='.' ){
|
||||
zFile[i] = '+';
|
||||
}
|
||||
}
|
||||
return zFile;
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate a random filename with the given prefix. The new filename
|
||||
** is written into zBuf[]. The calling function must insure that
|
||||
** zBuf[] is big enough to hold the prefix plus 20 or so extra
|
||||
** characters.
|
||||
**
|
||||
** Very random names are chosen so that the chance of a
|
||||
** collision with an existing filename is very very small.
|
||||
*/
|
||||
static void randomName(struct rc4 *pRc4, char *zBuf, char *zPrefix){
|
||||
int i, j;
|
||||
static const char zRandomChars[] = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
strcpy(zBuf, zPrefix);
|
||||
j = strlen(zBuf);
|
||||
for(i=0; i<15; i++){
|
||||
int c = rc4byte(pRc4) % (sizeof(zRandomChars) - 1);
|
||||
zBuf[j++] = zRandomChars[c];
|
||||
}
|
||||
zBuf[j] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a new table cursor. Write a pointer to the corresponding
|
||||
** DbbeCursor structure into *ppCursr. Return an integer success
|
||||
** code:
|
||||
**
|
||||
** SQLITE_OK It worked!
|
||||
**
|
||||
** SQLITE_NOMEM sqliteMalloc() failed
|
||||
**
|
||||
** SQLITE_PERM Attempt to access a file for which file
|
||||
** access permission is denied
|
||||
**
|
||||
** SQLITE_BUSY Another thread or process is already using
|
||||
** the corresponding file and has that file locked.
|
||||
**
|
||||
** SQLITE_READONLY The current thread already has this file open
|
||||
** readonly but you are trying to open for writing.
|
||||
** (This can happen if a SELECT callback tries to
|
||||
** do an UPDATE or DELETE.)
|
||||
**
|
||||
** If zTable is 0 or "", then a temporary database file is created and
|
||||
** a cursor to that temporary file is opened. The temporary file
|
||||
** will be deleted from the disk when it is closed.
|
||||
*/
|
||||
static int sqliteGdbmOpenCursor(
|
||||
Dbbe *pDbbe, /* The database the table belongs to */
|
||||
const char *zTable, /* The SQL name of the file to be opened */
|
||||
int writeable, /* True to open for writing */
|
||||
DbbeCursor **ppCursr /* Write the resulting table pointer here */
|
||||
){
|
||||
char *zFile; /* Name of the table file */
|
||||
DbbeCursor *pCursr; /* The new table cursor */
|
||||
BeFile *pFile; /* The underlying data file for this table */
|
||||
int rc = SQLITE_OK; /* Return value */
|
||||
int rw_mask; /* Permissions mask for opening a table */
|
||||
int mode; /* Mode for opening a table */
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
|
||||
*ppCursr = 0;
|
||||
pCursr = sqliteMalloc( sizeof(*pCursr) );
|
||||
if( pCursr==0 ) return SQLITE_NOMEM;
|
||||
if( zTable ){
|
||||
zFile = sqliteFileOfTable(pBe, zTable);
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pFile->pNext){
|
||||
if( strcmp(pFile->zName,zFile)==0 ) break;
|
||||
}
|
||||
}else{
|
||||
pFile = 0;
|
||||
zFile = 0;
|
||||
}
|
||||
if( pFile==0 ){
|
||||
if( writeable ){
|
||||
rw_mask = GDBM_WRCREAT | GDBM_FAST;
|
||||
mode = 0640;
|
||||
}else{
|
||||
rw_mask = GDBM_READER;
|
||||
mode = 0640;
|
||||
}
|
||||
pFile = sqliteMalloc( sizeof(*pFile) );
|
||||
if( pFile==0 ){
|
||||
sqliteFree(zFile);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
if( zFile ){
|
||||
if( !writeable || pBe->write ){
|
||||
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
|
||||
}else{
|
||||
pFile->dbf = 0;
|
||||
}
|
||||
}else{
|
||||
int limit;
|
||||
struct rc4 *pRc4;
|
||||
char zRandom[50];
|
||||
pRc4 = &pBe->rc4;
|
||||
zFile = 0;
|
||||
limit = 5;
|
||||
do {
|
||||
randomName(&pBe->rc4, zRandom, "_temp_table_");
|
||||
sqliteFree(zFile);
|
||||
zFile = sqliteFileOfTable(pBe, zRandom);
|
||||
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
|
||||
}while( pFile->dbf==0 && limit-- >= 0);
|
||||
pFile->delOnClose = 1;
|
||||
}
|
||||
pFile->writeable = writeable;
|
||||
pFile->zName = zFile;
|
||||
pFile->nRef = 1;
|
||||
pFile->pPrev = 0;
|
||||
if( pBe->pOpen ){
|
||||
pBe->pOpen->pPrev = pFile;
|
||||
}
|
||||
pFile->pNext = pBe->pOpen;
|
||||
pBe->pOpen = pFile;
|
||||
if( pFile->dbf==0 ){
|
||||
if( !writeable && access(zFile,0) ){
|
||||
/* Trying to read a non-existant file. This is OK. All the
|
||||
** reads will return empty, which is what we want. */
|
||||
rc = SQLITE_OK;
|
||||
}else if( pBe->write==0 ){
|
||||
rc = SQLITE_READONLY;
|
||||
}else if( access(zFile,W_OK|R_OK) ){
|
||||
rc = SQLITE_PERM;
|
||||
}else{
|
||||
rc = SQLITE_BUSY;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
sqliteFree(zFile);
|
||||
pFile->nRef++;
|
||||
if( writeable && !pFile->writeable ){
|
||||
rc = SQLITE_READONLY;
|
||||
}
|
||||
}
|
||||
pCursr->pBe = pBe;
|
||||
pCursr->pFile = pFile;
|
||||
pCursr->readPending = 0;
|
||||
pCursr->needRewind = 1;
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqliteGdbmCloseCursor(pCursr);
|
||||
*ppCursr = 0;
|
||||
}else{
|
||||
*ppCursr = pCursr;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Drop a table from the database. The file on the disk that corresponds
|
||||
** to this table is deleted.
|
||||
*/
|
||||
static void sqliteGdbmDropTable(Dbbe *pBe, const char *zTable){
|
||||
char *zFile; /* Name of the table file */
|
||||
|
||||
zFile = sqliteFileOfTable((Dbbex*)pBe, zTable);
|
||||
unlink(zFile);
|
||||
sqliteFree(zFile);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a cursor previously opened by sqliteGdbmOpenCursor().
|
||||
**
|
||||
** There can be multiple cursors pointing to the same open file.
|
||||
** The underlying file is not closed until all cursors have been
|
||||
** closed. This routine decrements the BeFile.nref field of the
|
||||
** underlying file and closes the file when nref reaches 0.
|
||||
*/
|
||||
static void sqliteGdbmCloseCursor(DbbeCursor *pCursr){
|
||||
BeFile *pFile;
|
||||
Dbbex *pBe;
|
||||
if( pCursr==0 ) return;
|
||||
pFile = pCursr->pFile;
|
||||
pBe = pCursr->pBe;
|
||||
pFile->nRef--;
|
||||
if( pFile->dbf!=NULL ){
|
||||
gdbm_sync(pFile->dbf);
|
||||
}
|
||||
if( pFile->nRef<=0 ){
|
||||
if( pFile->dbf!=NULL ){
|
||||
gdbm_close(pFile->dbf);
|
||||
}
|
||||
if( pFile->pPrev ){
|
||||
pFile->pPrev->pNext = pFile->pNext;
|
||||
}else{
|
||||
pBe->pOpen = pFile->pNext;
|
||||
}
|
||||
if( pFile->pNext ){
|
||||
pFile->pNext->pPrev = pFile->pPrev;
|
||||
}
|
||||
if( pFile->delOnClose ){
|
||||
unlink(pFile->zName);
|
||||
}
|
||||
sqliteFree(pFile->zName);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
if( pCursr->key.dptr ) free(pCursr->key.dptr);
|
||||
if( pCursr->data.dptr ) free(pCursr->data.dptr);
|
||||
memset(pCursr, 0, sizeof(*pCursr));
|
||||
sqliteFree(pCursr);
|
||||
}
|
||||
|
||||
/*
|
||||
** Reorganize a table to reduce search times and disk usage.
|
||||
*/
|
||||
static int sqliteGdbmReorganizeTable(Dbbe *pBe, const char *zTable){
|
||||
DbbeCursor *pCrsr;
|
||||
int rc;
|
||||
|
||||
rc = sqliteGdbmOpenCursor(pBe, zTable, 1, &pCrsr);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
if( pCrsr && pCrsr->pFile && pCrsr->pFile->dbf ){
|
||||
gdbm_reorganize(pCrsr->pFile->dbf);
|
||||
}
|
||||
if( pCrsr ){
|
||||
sqliteGdbmCloseCursor(pCrsr);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Clear the given datum
|
||||
*/
|
||||
static void datumClear(datum *p){
|
||||
if( p->dptr ) free(p->dptr);
|
||||
p->dptr = 0;
|
||||
p->dsize = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Fetch a single record from an open cursor. Return 1 on success
|
||||
** and 0 on failure.
|
||||
*/
|
||||
static int sqliteGdbmFetch(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return pCursr->data.dptr!=0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
static int sqliteGdbmTest(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
int result = 0;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
result = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy bytes from the current key or data into a buffer supplied by
|
||||
** the calling function. Return the number of bytes copied.
|
||||
*/
|
||||
static
|
||||
int sqliteGdbmCopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( offset>=pCursr->key.dsize ) return 0;
|
||||
if( offset+size>pCursr->key.dsize ){
|
||||
n = pCursr->key.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->key.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
static
|
||||
int sqliteGdbmCopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset>=pCursr->data.dsize ) return 0;
|
||||
if( offset+size>pCursr->data.dsize ){
|
||||
n = pCursr->data.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->data.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to bytes from the key or data. The data returned
|
||||
** is ephemeral.
|
||||
*/
|
||||
static char *sqliteGdbmReadKey(DbbeCursor *pCursr, int offset){
|
||||
if( offset<0 || offset>=pCursr->key.dsize ) return "";
|
||||
return &pCursr->key.dptr[offset];
|
||||
}
|
||||
static char *sqliteGdbmReadData(DbbeCursor *pCursr, int offset){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset<0 || offset>=pCursr->data.dsize ) return "";
|
||||
return &pCursr->data.dptr[offset];
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the total number of bytes in either data or key.
|
||||
*/
|
||||
static int sqliteGdbmKeyLength(DbbeCursor *pCursr){
|
||||
return pCursr->key.dsize;
|
||||
}
|
||||
static int sqliteGdbmDataLength(DbbeCursor *pCursr){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
return pCursr->data.dsize;
|
||||
}
|
||||
|
||||
/*
|
||||
** Make is so that the next call to sqliteNextKey() finds the first
|
||||
** key of the table.
|
||||
*/
|
||||
static int sqliteGdbmRewind(DbbeCursor *pCursr){
|
||||
pCursr->needRewind = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read the next key from the table. Return 1 on success. Return
|
||||
** 0 if there are no more keys.
|
||||
*/
|
||||
static int sqliteGdbmNextKey(DbbeCursor *pCursr){
|
||||
datum nextkey;
|
||||
int rc;
|
||||
if( pCursr==0 || pCursr->pFile==0 || pCursr->pFile->dbf==0 ){
|
||||
pCursr->readPending = 0;
|
||||
return 0;
|
||||
}
|
||||
if( pCursr->needRewind ){
|
||||
nextkey = gdbm_firstkey(pCursr->pFile->dbf);
|
||||
pCursr->needRewind = 0;
|
||||
}else{
|
||||
nextkey = gdbm_nextkey(pCursr->pFile->dbf, pCursr->key);
|
||||
}
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
pCursr->key = nextkey;
|
||||
if( pCursr->key.dptr ){
|
||||
pCursr->readPending = 1;
|
||||
rc = 1;
|
||||
}else{
|
||||
pCursr->needRewind = 1;
|
||||
pCursr->readPending = 0;
|
||||
rc = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a new integer key.
|
||||
*/
|
||||
static int sqliteGdbmNew(DbbeCursor *pCursr){
|
||||
int iKey;
|
||||
datum key;
|
||||
int go = 1;
|
||||
int i;
|
||||
struct rc4 *pRc4;
|
||||
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return 1;
|
||||
pRc4 = &pCursr->pBe->rc4;
|
||||
while( go ){
|
||||
iKey = 0;
|
||||
for(i=0; i<4; i++){
|
||||
iKey = (iKey<<8) + rc4byte(pRc4);
|
||||
}
|
||||
if( iKey==0 ) continue;
|
||||
key.dptr = (char*)&iKey;
|
||||
key.dsize = 4;
|
||||
go = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return iKey;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write an entry into the table. Overwrite any prior entry with the
|
||||
** same key.
|
||||
*/
|
||||
static int
|
||||
sqliteGdbmPut(DbbeCursor *pCursr, int nKey,char *pKey,int nData,char *pData){
|
||||
datum data, key;
|
||||
int rc;
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
data.dsize = nData;
|
||||
data.dptr = pData;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_store(pCursr->pFile->dbf, key, data, GDBM_REPLACE);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove an entry from a table, if the entry exists.
|
||||
*/
|
||||
static int sqliteGdbmDelete(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
int rc;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_delete(pCursr->pFile->dbf, key);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a temporary file. The file should be deleted when closed.
|
||||
**
|
||||
** Note that we can't use the old Unix trick of opening the file
|
||||
** and then immediately unlinking the file. That works great
|
||||
** under Unix, but fails when we try to port to Windows.
|
||||
*/
|
||||
static int sqliteGdbmOpenTempFile(Dbbe *pDbbe, FILE **ppFile){
|
||||
char *zFile; /* Full name of the temporary file */
|
||||
char zBuf[50]; /* Base name of the temporary file */
|
||||
int i; /* Loop counter */
|
||||
int limit; /* Prevent an infinite loop */
|
||||
int rc = SQLITE_OK; /* Value returned by this function */
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
|
||||
for(i=0; i<pBe->nTemp; i++){
|
||||
if( pBe->apTemp[i]==0 ) break;
|
||||
}
|
||||
if( i>=pBe->nTemp ){
|
||||
pBe->nTemp++;
|
||||
pBe->apTemp = sqliteRealloc(pBe->apTemp, pBe->nTemp*sizeof(FILE*) );
|
||||
pBe->azTemp = sqliteRealloc(pBe->azTemp, pBe->nTemp*sizeof(char*) );
|
||||
}
|
||||
if( pBe->apTemp==0 ){
|
||||
*ppFile = 0;
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
limit = 4;
|
||||
zFile = 0;
|
||||
do{
|
||||
randomName(&pBe->rc4, zBuf, "/_temp_file_");
|
||||
sqliteFree(zFile);
|
||||
zFile = 0;
|
||||
sqliteSetString(&zFile, pBe->zDir, zBuf, 0);
|
||||
}while( access(zFile,0)==0 && limit-- >= 0 );
|
||||
*ppFile = pBe->apTemp[i] = fopen(zFile, "w+");
|
||||
if( pBe->apTemp[i]==0 ){
|
||||
rc = SQLITE_ERROR;
|
||||
sqliteFree(zFile);
|
||||
pBe->azTemp[i] = 0;
|
||||
}else{
|
||||
pBe->azTemp[i] = zFile;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a temporary file opened using sqliteGdbmOpenTempFile()
|
||||
*/
|
||||
static void sqliteGdbmCloseTempFile(Dbbe *pDbbe, FILE *f){
|
||||
int i;
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
for(i=0; i<pBe->nTemp; i++){
|
||||
if( pBe->apTemp[i]==f ){
|
||||
unlink(pBe->azTemp[i]);
|
||||
sqliteFree(pBe->azTemp[i]);
|
||||
pBe->apTemp[i] = 0;
|
||||
pBe->azTemp[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** This routine opens a new database. For the GDBM driver
|
||||
** implemented here, the database name is the name of the directory
|
||||
** containing all the files of the database.
|
||||
**
|
||||
** If successful, a pointer to the Dbbe structure is returned.
|
||||
** If there are errors, an appropriate error message is left
|
||||
** in *pzErrMsg and NULL is returned.
|
||||
*/
|
||||
Dbbe *sqliteGdbmOpen(
|
||||
const char *zName, /* The name of the database */
|
||||
int writeFlag, /* True if we will be writing to the database */
|
||||
int createFlag, /* True to create database if it doesn't exist */
|
||||
char **pzErrMsg /* Write error messages (if any) here */
|
||||
){
|
||||
Dbbex *pNew;
|
||||
struct stat statbuf;
|
||||
char *zMaster;
|
||||
|
||||
if( !writeFlag ) createFlag = 0;
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
if( createFlag ) mkdir(zName, 0750);
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
sqliteSetString(pzErrMsg, createFlag ?
|
||||
"can't find or create directory \"" : "can't find directory \"",
|
||||
zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if( !S_ISDIR(statbuf.st_mode) ){
|
||||
sqliteSetString(pzErrMsg, "not a directory: \"", zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
if( access(zName, writeFlag ? (X_OK|W_OK|R_OK) : (X_OK|R_OK)) ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied", 0);
|
||||
return 0;
|
||||
}
|
||||
zMaster = 0;
|
||||
sqliteSetString(&zMaster, zName, "/" MASTER_NAME ".tbl", 0);
|
||||
if( stat(zMaster, &statbuf)==0
|
||||
&& access(zMaster, writeFlag ? (W_OK|R_OK) : R_OK)!=0 ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied for ", zMaster, 0);
|
||||
sqliteFree(zMaster);
|
||||
return 0;
|
||||
}
|
||||
sqliteFree(zMaster);
|
||||
pNew = sqliteMalloc(sizeof(Dbbex) + strlen(zName) + 1);
|
||||
if( pNew==0 ){
|
||||
sqliteSetString(pzErrMsg, "out of memory", 0);
|
||||
return 0;
|
||||
}
|
||||
pNew->dbbe.Close = sqliteGdbmClose;
|
||||
pNew->dbbe.OpenCursor = sqliteGdbmOpenCursor;
|
||||
pNew->dbbe.DropTable = sqliteGdbmDropTable;
|
||||
pNew->dbbe.ReorganizeTable = sqliteGdbmReorganizeTable;
|
||||
pNew->dbbe.CloseCursor = sqliteGdbmCloseCursor;
|
||||
pNew->dbbe.Fetch = sqliteGdbmFetch;
|
||||
pNew->dbbe.Test = sqliteGdbmTest;
|
||||
pNew->dbbe.CopyKey = sqliteGdbmCopyKey;
|
||||
pNew->dbbe.CopyData = sqliteGdbmCopyData;
|
||||
pNew->dbbe.ReadKey = sqliteGdbmReadKey;
|
||||
pNew->dbbe.ReadData = sqliteGdbmReadData;
|
||||
pNew->dbbe.KeyLength = sqliteGdbmKeyLength;
|
||||
pNew->dbbe.DataLength = sqliteGdbmDataLength;
|
||||
pNew->dbbe.NextKey = sqliteGdbmNextKey;
|
||||
pNew->dbbe.Rewind = sqliteGdbmRewind;
|
||||
pNew->dbbe.New = sqliteGdbmNew;
|
||||
pNew->dbbe.Put = sqliteGdbmPut;
|
||||
pNew->dbbe.Delete = sqliteGdbmDelete;
|
||||
pNew->dbbe.OpenTempFile = sqliteGdbmOpenTempFile;
|
||||
pNew->dbbe.CloseTempFile = sqliteGdbmCloseTempFile;
|
||||
pNew->zDir = (char*)&pNew[1];
|
||||
strcpy(pNew->zDir, zName);
|
||||
pNew->write = writeFlag;
|
||||
pNew->pOpen = 0;
|
||||
time(&statbuf.st_ctime);
|
||||
rc4init(&pNew->rc4, (char*)&statbuf, sizeof(statbuf));
|
||||
return &pNew->dbbe;
|
||||
}
|
||||
+708
@@ -0,0 +1,708 @@
|
||||
/*
|
||||
** Copyright (c) 2001 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** $Id: pg.c,v 1.1 2001/02/11 16:56:24 drh Exp $
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include "sqliteInt.h"
|
||||
#include "pg.h"
|
||||
|
||||
/*
|
||||
** Uncomment the following for a debug trace
|
||||
*/
|
||||
#if 1
|
||||
# define TRACE(X) printf X; fflush(stdout);
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Hash table sizes
|
||||
*/
|
||||
#define J_HASH_SIZE 127 /* Size of the journal page hash table */
|
||||
#define PG_HASH_SIZE 349 /* Size of the database page hash table */
|
||||
|
||||
/*
|
||||
** Forward declaration of structure
|
||||
*/
|
||||
typedef struct Pghdr Pghdr;
|
||||
|
||||
/*
|
||||
** All information about a single paging file is contained in an
|
||||
** instance of the following structure.
|
||||
*/
|
||||
struct Pgr {
|
||||
int fdMain; /* The main database file */
|
||||
char *zMain; /* Name of the database file */
|
||||
int fdJournal; /* The journal file */
|
||||
char *zJournal; /* Name of the journal file */
|
||||
int nMemPg; /* Number of memory-resident pages */
|
||||
int nJPg; /* Number of pages in the journal */
|
||||
int nDbPg; /* Number of pages in the database */
|
||||
int nRefPg; /* Number of pages currently in use */
|
||||
Pghdr *pLru, *pMru; /* Least and most recently used mem-page */
|
||||
Pghdr *pJidx; /* List of journal index pages */
|
||||
Pghdr *pAll; /* All pages, except journal index pages */
|
||||
u32 aJHash[J_HASH_SIZE]; /* Journal page hash table */
|
||||
Pghdr *aPgHash[PG_HASH_SIZE]; /* Mem-page hash table */
|
||||
};
|
||||
|
||||
/*
|
||||
** Each memory-resident page of the paging file has a header which
|
||||
** is an instance of the following structure.
|
||||
*/
|
||||
struct Pghdr {
|
||||
Pgr *p; /* Pointer back to the Pgr structure */
|
||||
int nRef; /* Number of references to this page */
|
||||
int isDirty; /* TRUE if needs to be written to disk */
|
||||
u32 dbpgno; /* Page number in the database file */
|
||||
u32 jpgno; /* Page number in the journal file */
|
||||
Pghdr *pNx; /* Next page on a list of them all */
|
||||
Pghdr *pLru; /* Less recently used pages */
|
||||
Pghdr *pMru; /* More recently used pages */
|
||||
Pghdr *pNxHash; /* Next with same dbpgno hash */
|
||||
Pghdr *pPvHash; /* Previous with the same dbpgno hash */
|
||||
};
|
||||
|
||||
/*
|
||||
** For a memory-resident page, the page data comes immediately after
|
||||
** the page header. The following macros can be used to change a
|
||||
** pointer to a page header into a pointer to the data, or vice
|
||||
** versa.
|
||||
*/
|
||||
#define PG_TO_DATA(X) ((void*)&(X)[1])
|
||||
#define DATA_TO_PG(X) (&((Pghdr*)(X))[-1])
|
||||
|
||||
/*
|
||||
** The number of in-memory pages that we accumulate before trying
|
||||
** to reuse older pages when new ones are requested.
|
||||
*/
|
||||
#define MX_MEM_PAGE 100
|
||||
|
||||
/*
|
||||
** The number of journal data pages that come between consecutive
|
||||
** journal index pages.
|
||||
*/
|
||||
#define N_J_DATAPAGE (SQLITE_PAGE_SIZE/(2*sizeof(u32)))
|
||||
|
||||
/*
|
||||
** An index page in the journal consists of an array of N_J_DATAPAGE
|
||||
** of the following structures. There is one instance of the following
|
||||
** structure for each of the N_J_DATAPAGE data pages that follow the
|
||||
** index.
|
||||
**
|
||||
** Let the journal page number that a JidxEntry describes be J. Then
|
||||
** the JidxEntry.dbpgno field is the page of the database file that
|
||||
** corresponds to the J page in the journal. The JidxEntry.next_jpgno
|
||||
** field hold the number of another journal page that contains
|
||||
** a database file page with the same hash as JidxEntry.dbpgno.
|
||||
**
|
||||
** All information is written to the journal index in big-endian
|
||||
** notation.
|
||||
*/
|
||||
typedef struct JidxEntry JidxEntry;
|
||||
struct JidxEntry {
|
||||
char dbpgno[sizeof(u32)]; /* Database page number for this entry */
|
||||
char next_jpgno[sizeof(u32)]; /* Next entry with same hash on dbpgno */
|
||||
};
|
||||
|
||||
/*
|
||||
** Read a page from a file into memory. Return SQLITE_OK if successful.
|
||||
** The "pgno" parameter tells where in the file to read the page.
|
||||
** The first page is 1. Files do not contain a page 0 since a page
|
||||
** number of 0 is used to indicate "no such page".
|
||||
*/
|
||||
static int sqlitePgRead(int fd, char *zBuf, u32 pgno){
|
||||
int got = 0;
|
||||
int amt;
|
||||
|
||||
assert( pgno>0 );
|
||||
assert( fd>=0 );
|
||||
lseek(fd, SEEK_SET, (pgno-1)*SQLITE_PAGE_SIZE);
|
||||
while( got<SQLITE_PAGE_SIZE ){
|
||||
amt = read(fd, &zBuf[got], SQLITE_PAGE_SIZE - got);
|
||||
if( amt<=0 ){
|
||||
memset(&zBuf[got], 0, SQLITE_PAGE_SIZE - got);
|
||||
return amt==0 ? SQLITE_OK : SQLITE_IOERR;
|
||||
}
|
||||
got += amt;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read a page from a file into memory. Return SQLITE_OK if successful.
|
||||
** The "pgno" parameter tells where in the file to write the page.
|
||||
** The first page is 1. Files do not contain a page 0 since a page
|
||||
** number of 0 is used to indicate "no such page".
|
||||
*/
|
||||
static int sqlitePgWrite(int fd, char *zBuf, u32 pgno){
|
||||
int done = 0;
|
||||
int amt;
|
||||
|
||||
assert( pgno>0 );
|
||||
assert( fd>=0 );
|
||||
lseek(fd, SEEK_SET, (pgno-1)*SQLITE_PAGE_SIZE);
|
||||
while( done<SQLITE_PAGE_SIZE ){
|
||||
amt = write(fd, &zBuf[done], SQLITE_PAGE_SIZE - done);
|
||||
if( amt<=0 ) return SQLITE_IOERR;
|
||||
done += amt;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Turn four bytes into an integer. The first byte is always the
|
||||
** most significant 8 bits.
|
||||
*/
|
||||
static u32 sqlitePgGetInt(const char *p){
|
||||
return ((p[0]&0xff)<<24) | ((p[1]&0xff)<<16) | ((p[2]&0xff)<<8) | (p[3]&0xff);
|
||||
}
|
||||
|
||||
/*
|
||||
** Turn an integer into 4 bytes. The first byte is always the
|
||||
** most significant 8 bits.
|
||||
*/
|
||||
static void sqlitePgPutInt(u32 v, char *p){
|
||||
p[3] = v & 0xff;
|
||||
v >>= 8;
|
||||
p[2] = v & 0xff;
|
||||
v >>= 8;
|
||||
p[1] = v & 0xff;
|
||||
v >>= 8;
|
||||
p[0] = v & 0xff;
|
||||
}
|
||||
|
||||
/*
|
||||
** Check the hash table for an in-memory page. Return a pointer to
|
||||
** the page header if found. Return NULL if the page is not in memory.
|
||||
*/
|
||||
static Pghdr *sqlitePgFind(Pgr *p, u32 pgno){
|
||||
int h;
|
||||
Pghdr *pPg;
|
||||
|
||||
if( pgno==0 ) return 0;
|
||||
h = pgno % PG_HASH_SIZE;
|
||||
for(pPg = p->aPgHash[h]; pPg; pPg=pPg->pNxHash){
|
||||
if( pPg->dbpgno==pgno ) return pPg;
|
||||
}
|
||||
TRACE(("PG: data page %u is %#x\n", pgno, (u32)pPg));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Locate and return an index page from the journal.
|
||||
**
|
||||
** The first page of a journal is the primary index. Additional
|
||||
** index pages are called secondary indices. Index pages appear
|
||||
** in the journal as often as needed. (If SQLITE_PAGE_SIZE==1024,
|
||||
** then there are 1024/sizeof(int)*2 = 128 database between each
|
||||
** pair of index pages.) Journal index pages are not hashed and
|
||||
** do no appear on the Pgr.pAll list. Index pages are on the
|
||||
** Pgr.pJidx list only. Index pages have Pghdr.dbpgno==0.
|
||||
**
|
||||
** If the requested index page is not already in memory, then a
|
||||
** new memory page is created to hold the index.
|
||||
**
|
||||
** This routine will return a NULL pointer if we run out of memory.
|
||||
*/
|
||||
static Pghdr *sqlitePgFindJidx(Pgr *p, u32 pgno){
|
||||
Pghdr *pPg;
|
||||
|
||||
assert( pgno % (N_J_DATAPAGE+1) == 1 );
|
||||
for(pPg=p->pJidx; pPg; pPg=pPg->pNx){
|
||||
if( pPg->jpgno==pgno ){
|
||||
TRACE(("PG: found j-index %u at %#x\n", pgno, (u32)pPg));
|
||||
return pPg;
|
||||
}
|
||||
}
|
||||
pPg = sqliteMalloc( sizeof(Pghdr)+SQLITE_PAGE_SIZE );
|
||||
if( pPg==0 ) return 0;
|
||||
pPg->jpgno = pgno;
|
||||
pPg->pNx = p->pJidx;
|
||||
p->pJidx = pPg;
|
||||
sqlitePgRead(p->fdJournal, PG_TO_DATA(pPg), pgno);
|
||||
TRACE(("PG: create j-index %u at %#x\n", pgno, (u32)pPg));
|
||||
return pPg;
|
||||
}
|
||||
|
||||
/*
|
||||
** Look in the journal to see if the given database page is stored
|
||||
** in the journal. If it is, return its journal page number. If
|
||||
** not, return 0.
|
||||
*/
|
||||
static u32 sqlitePgJournalPageNumber(Pgr *p, u32 dbpgno){
|
||||
u32 jpgno;
|
||||
|
||||
if( dbpgno==0 ) return 0;
|
||||
jpgno = p->aJHash[dbpgno % J_HASH_SIZE];
|
||||
while( jpgno!=0 ){
|
||||
int idx_num; /* Which journal index describes page jpgno */
|
||||
int ipgno; /* Page number for the journal index */
|
||||
int idx_slot; /* Which entry in index idx_num describes jpgno */
|
||||
Pghdr *pIdxPg; /* The index page for jpgno */
|
||||
JidxEntry *aIdx; /* The data for the index page */
|
||||
|
||||
idx_num = (jpgno - 1)/(N_J_DATAPAGE + 1);
|
||||
idx_slot = (jpgno - 1) % (N_J_DATAPAGE + 1) - 2;
|
||||
ipgno = idx_num * (N_J_DATAPAGE + 1) + 1;
|
||||
if( ipgno>p->nJPg ){
|
||||
jpgno = 0;
|
||||
break;
|
||||
}
|
||||
pIdxPg = sqlitePgFindJidx(p, ipgno);
|
||||
assert( pIdxPg!=0 );
|
||||
aIdx = PG_TO_DATA(pIdxPg);
|
||||
if( dbpgno==sqlitePgGetInt(aIdx[idx_slot].dbpgno) ){
|
||||
break;
|
||||
}
|
||||
jpgno = sqlitePgGetInt(aIdx[idx_slot].next_jpgno);
|
||||
}
|
||||
return jpgno;
|
||||
}
|
||||
|
||||
/*
|
||||
** Make a page not dirty by writing it to the journal.
|
||||
*/
|
||||
static int sqlitePgMakeClean(Pghdr *pPg){
|
||||
Pgr *p = pPg->p;
|
||||
int rc;
|
||||
|
||||
assert( pPg->isDirty );
|
||||
assert( p->fdJournal>=0 );
|
||||
if( pPg->jpgno==0 ){
|
||||
int jpgno; /* A newly allocate page in the journal */
|
||||
int idx_num; /* Which journal index describes page jpgno */
|
||||
int idx_slot; /* Which entry in index idx_num describes jpgno */
|
||||
Pghdr *pIdxPg; /* The index page for jpgno */
|
||||
JidxEntry *aIdx; /* The data for the index page */
|
||||
int h; /* The hash value for pPg->dbpgno */
|
||||
|
||||
jpgno = p->nJPg + 1;
|
||||
if( jpgno % (N_J_DATAPAGE + 1) == 1 ){
|
||||
jpgno++;
|
||||
}
|
||||
idx_num = (jpgno - 1)/(N_J_DATAPAGE + 1);
|
||||
idx_slot = (jpgno - 1) % (N_J_DATAPAGE + 1) - 2;
|
||||
pIdxPg = sqlitePgFindJidx(p, idx_num * (N_J_DATAPAGE + 1) + 1);
|
||||
assert( pIdxPg!=0 );
|
||||
aIdx = PG_TO_DATA(pIdxPg);
|
||||
sqlitePgPutInt(pPg->dbpgno, aIdx[idx_slot].dbpgno);
|
||||
h = pPg->dbpgno % J_HASH_SIZE;
|
||||
sqlitePgPutInt(p->aJHash[h], aIdx[idx_slot].next_jpgno);
|
||||
p->aJHash[h] = jpgno;
|
||||
p->nJPg = jpgno;
|
||||
pPg->jpgno = jpgno;
|
||||
TRACE(("PG: assign d-page %u to j-page %u\n", jpgno, pPg->dbpgno));
|
||||
}
|
||||
rc = sqlitePgWrite(p->fdJournal, PG_TO_DATA(pPg), pPg->jpgno);
|
||||
if( rc==SQLITE_OK ){
|
||||
pPg->isDirty = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Find the number of pages in the given file by measuring the size
|
||||
** of the file. Return 0 if there is any problem.
|
||||
*/
|
||||
static int sqlitePgPageCount(int fd){
|
||||
struct stat statbuf;
|
||||
if( fstat(fd, &statbuf)!=0 ) return 0;
|
||||
return statbuf.st_size/SQLITE_PAGE_SIZE;
|
||||
}
|
||||
|
||||
/*
|
||||
** This routine reads the journal and transfers pages from the
|
||||
** journal to the database.
|
||||
*/
|
||||
static int sqlitePgJournalPlayback(Pgr *p){
|
||||
Pghdr *pPg;
|
||||
JidxEntry *aIdx;
|
||||
int nJpg;
|
||||
int jpgno = 1;
|
||||
int i;
|
||||
int dbpgno;
|
||||
int rc;
|
||||
char idx[SQLITE_PAGE_SIZE];
|
||||
char pgbuf[SQLITE_PAGE_SIZE];
|
||||
|
||||
assert( p->fdJournal>=0 );
|
||||
nJpg = sqlitePgPageCount(p->fdJournal);
|
||||
while( jpgno<=nJpg ){
|
||||
if( !sqlitePgRead(p->fdJournal, idx, jpgno++) ) break;
|
||||
aIdx = (JidxEntry*)idx;
|
||||
for(i=0; i<N_J_DATAPAGE; i++){
|
||||
dbpgno = sqlitePgGetInt(&idx[i]);
|
||||
if( dbpgno==0 ){
|
||||
jpgno = nJpg+1;
|
||||
break;
|
||||
}
|
||||
pPg = sqlitePgFind(p, dbpgno);
|
||||
if( pPg ){
|
||||
rc = sqlitePgWrite(p->fdMain, PG_TO_DATA(pPg), dbpgno);
|
||||
TRACE(("PG: commit j-page %u to d-page %u from memory\n",jpgno,dbpgno));
|
||||
}else{
|
||||
rc = sqlitePgRead(p->fdJournal, pgbuf, jpgno);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
rc = sqlitePgWrite(p->fdMain, pgbuf, dbpgno);
|
||||
TRACE(("PG: commit j-page %u to d-page %u from disk\n",jpgno,dbpgno));
|
||||
}
|
||||
jpgno++;
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
TRACE(("PG: commit complete. deleting the journal.\n"));
|
||||
fsync(p->fdMain);
|
||||
close(p->fdJournal);
|
||||
p->fdJournal = -1;
|
||||
unlink(p->zJournal);
|
||||
for(pPg=p->pAll; pPg; pPg=pPg->pNx){
|
||||
pPg->isDirty = 0;
|
||||
pPg->jpgno = 0;
|
||||
}
|
||||
while( (pPg = p->pJidx)!=0 ){
|
||||
p->pAll = pPg->pNx;
|
||||
sqliteFree(pPg);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove the given page from the LRU list.
|
||||
*/
|
||||
static void sqlitePgUnlinkLru(Pghdr *pPg){
|
||||
Pgr *p = pPg->p;
|
||||
if( pPg->pLru ){
|
||||
pPg->pLru->pMru = pPg->pLru;
|
||||
}
|
||||
if( pPg->pMru ){
|
||||
pPg->pMru->pLru = pPg->pMru;
|
||||
}
|
||||
if( p->pLru==pPg ){
|
||||
p->pLru = pPg->pLru;
|
||||
}
|
||||
if( p->pMru==pPg ){
|
||||
p->pMru = pPg->pMru;
|
||||
}
|
||||
pPg->pLru = pPg->pMru = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open the database file and make *ppPgr pointer to a structure describing it.
|
||||
** Return SQLITE_OK on success or an error code if there is a failure.
|
||||
**
|
||||
** If there was an unfinished commit, complete it before returnning.
|
||||
*/
|
||||
int sqlitePgOpen(const char *zFilename, Pgr **ppPgr){
|
||||
Pgr *p;
|
||||
int n;
|
||||
|
||||
n = strlen(zFilename);
|
||||
p = sqliteMalloc( sizeof(*p) + n*2 + 4 );
|
||||
if( p==0 ){
|
||||
*ppPgr = 0;
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
p->zMain = (char*)&p[1];
|
||||
strcpy(p->zMain, zFilename);
|
||||
p->zJournal = &p->zMain[n+1];
|
||||
strcpy(p->zJournal, p->zMain);
|
||||
p->zJournal[n] = '~';
|
||||
p->zJournal[n+1] = 0;
|
||||
p->fdJournal = -1;
|
||||
p->fdMain = open(p->zMain, O_CREAT|O_RDWR, 0600);
|
||||
if( p->fdMain<0 ){
|
||||
*ppPgr = 0;
|
||||
sqliteFree(p);
|
||||
return SQLITE_PERM;
|
||||
}
|
||||
p->nDbPg = sqlitePgPageCount(p->fdMain);
|
||||
if( access(p->zJournal, R_OK)==0 ){
|
||||
sqlitePgJournalPlayback(p);
|
||||
}
|
||||
*ppPgr = p;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Close the database file. Any outstanding transactions are abandoned.
|
||||
*/
|
||||
int sqlitePgClose(Pgr *p){
|
||||
Pghdr *pPg;
|
||||
|
||||
if( p->fdMain ) close(p->fdMain);
|
||||
if( p->fdJournal ) close(p->fdJournal);
|
||||
unlink(p->zJournal);
|
||||
while( (pPg = p->pAll)!=0 ){
|
||||
p->pAll = pPg->pNx;
|
||||
sqliteFree(pPg);
|
||||
}
|
||||
while( (pPg = p->pJidx)!=0 ){
|
||||
p->pAll = pPg->pNx;
|
||||
sqliteFree(pPg);
|
||||
}
|
||||
sqliteFree(p);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Begin a new transaction. Return SQLITE_OK on success or an error
|
||||
** code if something goes wrong.
|
||||
*/
|
||||
int sqlitePgBeginTransaction(Pgr *p){
|
||||
assert( p->fdJournal<0 );
|
||||
if( p->nRefPg>0 ){
|
||||
/* release the read lock */
|
||||
}
|
||||
/* write lock the database */
|
||||
p->fdJournal = open(p->zJournal, O_CREAT|O_EXCL|O_RDWR, 0600);
|
||||
if( p->fdJournal<0 ){
|
||||
return SQLITE_PERM;
|
||||
}
|
||||
p->nJPg = 0;
|
||||
TRACE(("PG: begin transaction\n"));
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Commit the current transaction. Return SQLITE_OK or an error code.
|
||||
*/
|
||||
int sqlitePgCommit(Pgr *p){
|
||||
Pghdr *pPrimaryIdx = 0;
|
||||
Pghdr *pPg;
|
||||
int rc;
|
||||
|
||||
for(pPg=p->pAll; pPg; pPg=pPg->pNx){
|
||||
if( pPg->isDirty ){
|
||||
rc = sqlitePgMakeClean(pPg);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(pPg=p->pJidx; pPg; pPg=pPg->pNx){
|
||||
if( pPg->jpgno==1 ){
|
||||
pPrimaryIdx = pPg;
|
||||
}else{
|
||||
TRACE(("PG: writing j-index %u\n", pPg->jpgno));
|
||||
rc = sqlitePgMakeClean(pPg);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert( pPrimaryIdx!=0 );
|
||||
fsync(p->fdJournal);
|
||||
TRACE(("PG: writing j-index %u\n", pPrimaryIdx->jpgno));
|
||||
rc = sqlitePgMakeClean(pPrimaryIdx);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
fsync(p->fdJournal);
|
||||
rc = sqlitePgJournalPlayback(p);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
/* remove write lock from database */
|
||||
if( p->nRefPg>0 ){
|
||||
/* acquire read lock on database */
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Abandon the current transaction.
|
||||
*/
|
||||
int sqlitePgRollback(Pgr *p){
|
||||
Pghdr *pPg;
|
||||
|
||||
TRACE(("PG: begin rollback\n"));
|
||||
for(pPg=p->pAll; pPg; pPg=pPg->pNx){
|
||||
if( pPg->isDirty || pPg->jpgno!=0 ){
|
||||
pPg->isDirty = 0;
|
||||
pPg->jpgno = 0;
|
||||
if( pPg->nRef>0 ){
|
||||
TRACE(("PG: reloading d-page %u\n", pPg->dbpgno));
|
||||
sqlitePgRead(p->fdMain, PG_TO_DATA(pPg), pPg->dbpgno);
|
||||
}else{
|
||||
sqlitePgUnlinkLru(pPg);
|
||||
}
|
||||
}
|
||||
}
|
||||
close(p->fdJournal);
|
||||
p->fdJournal = -1;
|
||||
unlink(p->zJournal);
|
||||
while( (pPg = p->pJidx)!=0 ){
|
||||
p->pAll = pPg->pNx;
|
||||
sqliteFree(pPg);
|
||||
}
|
||||
p->nDbPg = sqlitePgPageCount(p->fdMain);
|
||||
/* remove write lock from database */
|
||||
if( p->nRefPg>0 ){
|
||||
/* acquire read lock on database */
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a page from the database. Return a pointer to the data for that
|
||||
** page.
|
||||
**
|
||||
** A NULL pointer will be returned if we run out of memory.
|
||||
*/
|
||||
int sqlitePgGet(Pgr *p, u32 pgno, void **ppData){
|
||||
Pghdr *pPg;
|
||||
int h;
|
||||
|
||||
pPg = sqlitePgFind(p, pgno);
|
||||
if( pPg ){
|
||||
pPg->nRef++;
|
||||
if( pPg->nRef==1 ){
|
||||
sqlitePgUnlinkLru(pPg);
|
||||
TRACE(("PG: d-page %u pulled from cache\n", pgno));
|
||||
}
|
||||
p->nRefPg++;
|
||||
if( p->nRefPg==1 ){
|
||||
/* Acquire a read lock */
|
||||
}
|
||||
*ppData = PG_TO_DATA(pPg);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
if( p->nMemPg<MX_MEM_PAGE || p->pLru==0 ){
|
||||
pPg = sqliteMalloc( sizeof(Pghdr) + SQLITE_PAGE_SIZE );
|
||||
if( pPg==0 ) return SQLITE_NOMEM;
|
||||
p->nMemPg++;
|
||||
pPg->pNx = p->pAll;
|
||||
p->pAll = pPg;
|
||||
pPg->p = p;
|
||||
TRACE(("PG: new page %d created.\n", p->nMemPg));
|
||||
}else{
|
||||
int rc;
|
||||
pPg = p->pLru;
|
||||
if( pPg->isDirty ){
|
||||
rc = sqlitePgMakeClean(pPg);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}
|
||||
sqlitePgUnlinkLru(pPg);
|
||||
h = pPg->dbpgno % PG_HASH_SIZE;
|
||||
if( pPg->pPvHash ){
|
||||
pPg->pPvHash->pNxHash = pPg->pNxHash;
|
||||
}else{
|
||||
assert( p->aPgHash[h]==pPg );
|
||||
p->aPgHash[h] = pPg->pNxHash;
|
||||
}
|
||||
if( pPg->pNxHash ){
|
||||
pPg->pNxHash->pPvHash = pPg->pPvHash;
|
||||
}
|
||||
TRACE(("PG: recycling d-page %u to d-page %u\n", pPg->dbpgno, pgno));
|
||||
}
|
||||
pPg->dbpgno = pgno;
|
||||
if( pgno>p->nDbPg ){
|
||||
p->nDbPg = pgno;
|
||||
}
|
||||
h = pgno % PG_HASH_SIZE;
|
||||
pPg->pPvHash = 0;
|
||||
pPg->pNxHash = p->aPgHash[h];
|
||||
if( pPg->pNxHash ){
|
||||
pPg->pNxHash->pPvHash = pPg;
|
||||
}
|
||||
p->aPgHash[h] = pPg;
|
||||
pPg->jpgno = sqlitePgJournalPageNumber(p, pgno);
|
||||
if( pPg->jpgno!=0 ){
|
||||
TRACE(("PG: reading d-page %u content from j-page %u\n", pgno, pPg->jpgno));
|
||||
sqlitePgRead(p->fdJournal, PG_TO_DATA(pPg), pPg->jpgno);
|
||||
}else if( pPg->dbpgno!=0 ){
|
||||
TRACE(("PG: reading d-page %u from database\n", pgno));
|
||||
sqlitePgRead(p->fdMain, PG_TO_DATA(pPg), pPg->dbpgno);
|
||||
}else{
|
||||
TRACE(("PG: reading zero page\n");
|
||||
memset(PG_TO_DATA(pPg), 0, SQLITE_PAGE_SIZE);
|
||||
}
|
||||
pPg->isDirty = 0;
|
||||
pPg->nRef = 1;
|
||||
p->nRefPg++;
|
||||
if( p->nRefPg==1 ){
|
||||
/* Acquire a read lock */
|
||||
}
|
||||
*ppData = PG_TO_DATA(pPg);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Release a reference to a database data page.
|
||||
*/
|
||||
int sqlitePgUnref(void *pData){
|
||||
Pghdr *pPg = DATA_TO_PG(pData);
|
||||
pPg->nRef--;
|
||||
assert( pPg->nRef>=0 );
|
||||
if( pPg->nRef==0 ){
|
||||
Pgr *p = pPg->p;
|
||||
pPg->pMru = 0;
|
||||
pPg->pLru = p->pLru;
|
||||
p->pLru = pPg;
|
||||
TRACE(("PG: d-page %u is unused\n", pPg->dbpgno));
|
||||
p->nRefPg--;
|
||||
if( p->nRefPg==0 ){
|
||||
/* Release the read lock */
|
||||
}
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** The database page in the argument has been modified. Write it back
|
||||
** to the database file on the next commit.
|
||||
*/
|
||||
int sqlitePgTouch(void *pD){
|
||||
Pghdr *pPg = DATA_TO_PG(pD);
|
||||
assert( pPg->p->fdJournal>=0 );
|
||||
if( pPg->isDirty==0 ){
|
||||
pPg->isDirty = 1;
|
||||
TRACE(("PG: d-page %u is dirty\n", pPg->dbpgno));
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the number of the first unused page at the end of the
|
||||
** database file.
|
||||
*/
|
||||
int sqlitePgCount(Pgr *p, u32 *pPgno){
|
||||
*pPgno = p->nDbPg;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the page number associated with the given page.
|
||||
*/
|
||||
u32 sqlitePgNum(void *pD){
|
||||
Pghdr *pPg = DATA_TO_PG(pD);
|
||||
return pPg->dbpgno;
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
** Copyright (c) 2001 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** $Id: pg.h,v 1.1 2001/02/11 16:56:24 drh Exp $
|
||||
*/
|
||||
|
||||
typedef struct Pgr Pgr;
|
||||
#define SQLITE_PAGE_SIZE 1024
|
||||
|
||||
|
||||
int sqlitePgOpen(const char *filename, Pgr **pp);
|
||||
int sqlitePgClose(Pgr*);
|
||||
int sqlitePgBeginTransaction(Pgr*);
|
||||
int sqlitePgCommit(Pgr*);
|
||||
int sqlitePgRollback(Pgr*);
|
||||
int sqlitePgGet(Pgr*, u32 pgno, void **);
|
||||
int sqlitePgUnref(void*);
|
||||
int sqlitePgTouch(void*);
|
||||
int sqlitePgCount(Pgr*, u32*);
|
||||
u32 sqlitePgNum(void*);
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
set sizes {1024 2048 4096 8192 16384 32768}
|
||||
set fmt { %-8s}
|
||||
|
||||
puts -nonewline "page size: "
|
||||
foreach s $sizes {
|
||||
puts -nonewline [format $fmt $s]
|
||||
}
|
||||
puts ""
|
||||
|
||||
puts -nonewline "on leaf: "
|
||||
foreach s $sizes {
|
||||
set x [expr {$s - 18*4}]
|
||||
set p($s) $x
|
||||
puts -nonewline [format $fmt $x]
|
||||
}
|
||||
puts ""
|
||||
|
||||
puts -nonewline "direct: "
|
||||
foreach s $sizes {
|
||||
set x [expr {$p($s) + 10*$s}]
|
||||
set p($s) $x
|
||||
puts -nonewline [format $fmt $x]
|
||||
}
|
||||
puts ""
|
||||
|
||||
puts -nonewline "indirect: "
|
||||
foreach s $sizes {
|
||||
set x [expr {$p($s) + ($s/4.0)*$s}]
|
||||
set p($s) $x
|
||||
puts -nonewline [format $fmt $x]
|
||||
}
|
||||
puts ""
|
||||
|
||||
puts -nonewline "dbl indir: "
|
||||
foreach s $sizes {
|
||||
set x [expr {$p($s) + ($s/4.0)*($s/4)*$s}]
|
||||
set p($s) $x
|
||||
puts -nonewline [format $fmt $x]
|
||||
}
|
||||
puts ""
|
||||
+47
-12
@@ -24,7 +24,7 @@
|
||||
** This file contains routines used for analyzing expressions and
|
||||
** for generating VDBE code that evaluates expressions.
|
||||
**
|
||||
** $Id: expr.c,v 1.18 2000/06/21 13:59:11 drh Exp $
|
||||
** $Id: expr.c,v 1.21 2001/01/15 22:51:10 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -216,7 +216,7 @@ int sqliteExprResolveIds(Parse *pParse, IdList *pTabList, Expr *pExpr){
|
||||
** table. The cursor number of the temporary table has already
|
||||
** been put in iTable by sqliteExprResolveInSelect().
|
||||
*/
|
||||
sqliteVdbeAddOp(v, OP_Open, pExpr->iTable, 1, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, pExpr->iTable, 1, 0, 0);
|
||||
if( sqliteSelect(pParse, pExpr->pSelect, SRT_Set, pExpr->iTable) );
|
||||
}else if( pExpr->pList ){
|
||||
/* Case 2: expr IN (exprlist)
|
||||
@@ -323,6 +323,8 @@ int sqliteFuncId(Token *pToken){
|
||||
{ "sum", 3, FN_Sum },
|
||||
{ "avg", 3, FN_Avg },
|
||||
{ "fcnt", 4, FN_Fcnt }, /* Used for testing only */
|
||||
{ "length", 6, FN_Length},
|
||||
{ "substr", 6, FN_Substr},
|
||||
};
|
||||
int i;
|
||||
for(i=0; i<ArraySize(aFunc); i++){
|
||||
@@ -381,6 +383,16 @@ int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){
|
||||
is_agg = 1;
|
||||
break;
|
||||
}
|
||||
case FN_Length: {
|
||||
too_few_args = n<1;
|
||||
too_many_args = n>1;
|
||||
break;
|
||||
}
|
||||
case FN_Substr: {
|
||||
too_few_args = n<3;
|
||||
too_many_args = n>3;
|
||||
break;
|
||||
}
|
||||
/* The "fcnt(*)" function always returns the number of fetch
|
||||
** operations that have occurred so far while processing the
|
||||
** SQL statement. This information can be used by test procedures
|
||||
@@ -392,6 +404,7 @@ int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){
|
||||
n = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
if( no_such_func ){
|
||||
@@ -574,15 +587,37 @@ void sqliteExprCode(Parse *pParse, Expr *pExpr){
|
||||
int op;
|
||||
int i;
|
||||
ExprList *pList = pExpr->pList;
|
||||
if( id==FN_Fcnt ){
|
||||
sqliteVdbeAddOp(v, OP_Fcnt, 0, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
op = id==FN_Min ? OP_Min : OP_Max;
|
||||
for(i=0; i<pList->nExpr; i++){
|
||||
sqliteExprCode(pParse, pList->a[i].pExpr);
|
||||
if( i>0 ){
|
||||
sqliteVdbeAddOp(v, op, 0, 0, 0, 0);
|
||||
switch( id ){
|
||||
case FN_Fcnt: {
|
||||
sqliteVdbeAddOp(v, OP_Fcnt, 0, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
case FN_Min:
|
||||
case FN_Max: {
|
||||
op = id==FN_Min ? OP_Min : OP_Max;
|
||||
for(i=0; i<pList->nExpr; i++){
|
||||
sqliteExprCode(pParse, pList->a[i].pExpr);
|
||||
if( i>0 ){
|
||||
sqliteVdbeAddOp(v, op, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FN_Length: {
|
||||
sqliteExprCode(pParse, pList->a[0].pExpr);
|
||||
sqliteVdbeAddOp(v, OP_Strlen, 0, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
case FN_Substr: {
|
||||
for(i=0; i<pList->nExpr; i++){
|
||||
sqliteExprCode(pParse, pList->a[i].pExpr);
|
||||
}
|
||||
sqliteVdbeAddOp(v, OP_Substr, 0, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
/* Can't happen! */
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -703,7 +738,7 @@ void sqliteExprIfTrue(Parse *pParse, Expr *pExpr, int dest){
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate code for boolean expression such that a jump is made
|
||||
** Generate code for a boolean expression such that a jump is made
|
||||
** to the label "dest" if the expression is false but execution
|
||||
** continues straight thru if the expression is true.
|
||||
*/
|
||||
|
||||
+4
-4
@@ -24,7 +24,7 @@
|
||||
** This file contains C code routines that are called by the parser
|
||||
** to handle INSERT statements.
|
||||
**
|
||||
** $Id: insert.c,v 1.11 2000/06/21 13:59:12 drh Exp $
|
||||
** $Id: insert.c,v 1.12 2001/01/15 22:51:11 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -92,7 +92,7 @@ void sqliteInsert(
|
||||
if( pSelect ){
|
||||
int rc;
|
||||
srcTab = pParse->nTab++;
|
||||
sqliteVdbeAddOp(v, OP_Open, srcTab, 1, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenTbl, srcTab, 1, 0, 0);
|
||||
rc = sqliteSelect(pParse, pSelect, SRT_Table, srcTab);
|
||||
if( rc ) goto insert_cleanup;
|
||||
assert( pSelect->pEList );
|
||||
@@ -156,9 +156,9 @@ void sqliteInsert(
|
||||
** all indices of that table.
|
||||
*/
|
||||
base = pParse->nTab;
|
||||
sqliteVdbeAddOp(v, OP_Open, base, 1, pTab->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenTbl, base, 1, pTab->zName, 0);
|
||||
for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
|
||||
sqliteVdbeAddOp(v, OP_Open, idx+base, 1, pIdx->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, idx+base, 1, pIdx->zName, 0);
|
||||
}
|
||||
|
||||
/* If the data source is a SELECT statement, then we have to create
|
||||
|
||||
+59
-28
@@ -26,9 +26,10 @@
|
||||
** other files are for internal use by SQLite and should not be
|
||||
** accessed by users of the library.
|
||||
**
|
||||
** $Id: main.c,v 1.16 2000/08/02 13:47:42 drh Exp $
|
||||
** $Id: main.c,v 1.25 2001/02/11 16:56:24 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <unistd.h>
|
||||
|
||||
/*
|
||||
** This is the callback routine for the code that initializes the
|
||||
@@ -123,7 +124,7 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){
|
||||
** database scheme.
|
||||
*/
|
||||
static VdbeOp initProg[] = {
|
||||
{ OP_Open, 0, 0, MASTER_NAME},
|
||||
{ OP_OpenTbl, 0, 0, MASTER_NAME},
|
||||
{ OP_Next, 0, 9, 0}, /* 1 */
|
||||
{ OP_Field, 0, 0, 0},
|
||||
{ OP_String, 0, 0, "meta"},
|
||||
@@ -154,7 +155,7 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){
|
||||
/* Create a virtual machine to run the initialization program. Run
|
||||
** the program. The delete the virtual machine.
|
||||
*/
|
||||
vdbe = sqliteVdbeCreate(db->pBe);
|
||||
vdbe = sqliteVdbeCreate(db);
|
||||
if( vdbe==0 ){
|
||||
sqliteSetString(pzErrMsg, "out of memory",0);
|
||||
return 1;
|
||||
@@ -184,6 +185,11 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** The version of the library
|
||||
*/
|
||||
const char sqlite_version[] = SQLITE_VERSION;
|
||||
|
||||
/*
|
||||
** Open a new SQLite database. Construct an "sqlite" structure to define
|
||||
** the state of this database and return a pointer to that structure.
|
||||
@@ -222,7 +228,7 @@ sqlite *sqlite_open(const char *zFilename, int mode, char **pzErrMsg){
|
||||
if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){
|
||||
sqlite_close(db);
|
||||
return 0;
|
||||
}else{
|
||||
}else /* if( pzErrMsg ) */{
|
||||
free(*pzErrMsg);
|
||||
*pzErrMsg = 0;
|
||||
}
|
||||
@@ -234,7 +240,7 @@ sqlite *sqlite_open(const char *zFilename, int mode, char **pzErrMsg){
|
||||
*/
|
||||
void sqlite_close(sqlite *db){
|
||||
int i;
|
||||
sqliteDbbeClose(db->pBe);
|
||||
db->pBe->x->Close(db->pBe);
|
||||
for(i=0; i<N_HASH; i++){
|
||||
Table *pNext, *pList = db->apTblHash[i];
|
||||
db->apTblHash[i] = 0;
|
||||
@@ -252,29 +258,50 @@ void sqlite_close(sqlite *db){
|
||||
** Return TRUE if the given SQL string ends in a semicolon.
|
||||
*/
|
||||
int sqlite_complete(const char *zSql){
|
||||
int i;
|
||||
int lastWasSemi = 0;
|
||||
|
||||
i = 0;
|
||||
while( i>=0 && zSql[i]!=0 ){
|
||||
int tokenType;
|
||||
int n;
|
||||
|
||||
n = sqliteGetToken(&zSql[i], &tokenType);
|
||||
switch( tokenType ){
|
||||
case TK_SPACE:
|
||||
case TK_COMMENT:
|
||||
int isComplete = 0;
|
||||
while( *zSql ){
|
||||
switch( *zSql ){
|
||||
case ';': {
|
||||
isComplete = 1;
|
||||
break;
|
||||
case TK_SEMI:
|
||||
lastWasSemi = 1;
|
||||
}
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\f': {
|
||||
break;
|
||||
default:
|
||||
lastWasSemi = 0;
|
||||
}
|
||||
case '\'': {
|
||||
isComplete = 0;
|
||||
zSql++;
|
||||
while( *zSql && *zSql!='\'' ){ zSql++; }
|
||||
if( *zSql==0 ) return 0;
|
||||
break;
|
||||
}
|
||||
case '"': {
|
||||
isComplete = 0;
|
||||
zSql++;
|
||||
while( *zSql && *zSql!='"' ){ zSql++; }
|
||||
if( *zSql==0 ) return 0;
|
||||
break;
|
||||
}
|
||||
case '-': {
|
||||
if( zSql[1]!='-' ){
|
||||
isComplete = 0;
|
||||
break;
|
||||
}
|
||||
while( *zSql && *zSql!='\n' ){ zSql++; }
|
||||
if( *zSql==0 ) return isComplete;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
isComplete = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
i += n;
|
||||
zSql++;
|
||||
}
|
||||
return lastWasSemi;
|
||||
return isComplete;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -295,7 +322,6 @@ int sqlite_exec(
|
||||
char **pzErrMsg /* Write error messages here */
|
||||
){
|
||||
Parse sParse;
|
||||
int rc;
|
||||
|
||||
if( pzErrMsg ) *pzErrMsg = 0;
|
||||
if( (db->flags & SQLITE_Initialized)==0 ){
|
||||
@@ -306,9 +332,8 @@ int sqlite_exec(
|
||||
sParse.db = db;
|
||||
sParse.xCallback = xCallback;
|
||||
sParse.pArg = pArg;
|
||||
rc = sqliteRunParser(&sParse, zSql, pzErrMsg);
|
||||
sqliteStrRealloc(pzErrMsg);
|
||||
return rc;
|
||||
sqliteRunParser(&sParse, zSql, pzErrMsg);
|
||||
return sParse.rc;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -322,7 +347,6 @@ static int sqlite_default_busy_callback(
|
||||
const char *NotUsed, /* The name of the table that is busy */
|
||||
int count /* Number of times table has been busy */
|
||||
){
|
||||
int rc;
|
||||
#if defined(HAVE_USLEEP) && HAVE_USLEEP
|
||||
int delay = 10000;
|
||||
int prior_delay = 0;
|
||||
@@ -378,3 +402,10 @@ void sqlite_busy_timeout(sqlite *db, int ms){
|
||||
sqlite_busy_handler(db, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Cause any pending operation to stop at its earliest opportunity.
|
||||
*/
|
||||
void sqlite_interrupt(sqlite *db){
|
||||
db->flags |= SQLITE_Interrupt;
|
||||
}
|
||||
|
||||
+6
-6
@@ -26,7 +26,7 @@
|
||||
** the parser. Lemon will also generate a header file containing
|
||||
** numeric codes for all of the tokens.
|
||||
**
|
||||
** @(#) $Id: parse.y,v 1.25 2000/08/01 09:56:27 drh Exp $
|
||||
** @(#) $Id: parse.y,v 1.26 2001/01/04 14:20:18 drh Exp $
|
||||
*/
|
||||
%token_prefix TK_
|
||||
%token_type {Token}
|
||||
@@ -114,12 +114,12 @@ ccons ::= CHECK LP expr RP.
|
||||
conslist_opt ::= .
|
||||
conslist_opt ::= COMMA conslist.
|
||||
conslist ::= conslist COMMA tcons.
|
||||
conslist ::= conslist tcons.
|
||||
conslist ::= tcons.
|
||||
tcons ::= CONSTRAINT id tcons2.
|
||||
tcons ::= tcons2.
|
||||
tcons2 ::= PRIMARY KEY LP idxlist(X) RP. {sqliteCreateIndex(pParse,0,0,X,0,0);}
|
||||
tcons2 ::= UNIQUE LP idlist RP.
|
||||
tcons2 ::= CHECK expr.
|
||||
tcons ::= CONSTRAINT id.
|
||||
tcons ::= PRIMARY KEY LP idxlist(X) RP. {sqliteCreateIndex(pParse,0,0,X,0,0);}
|
||||
tcons ::= UNIQUE LP idlist RP.
|
||||
tcons ::= CHECK expr.
|
||||
idlist ::= idlist COMMA id.
|
||||
idlist ::= id.
|
||||
|
||||
|
||||
+789
@@ -0,0 +1,789 @@
|
||||
/*
|
||||
** The "printf" code that follows dates from the 1980's. It is in
|
||||
** the public domain. The original comments are included here for
|
||||
** completeness. They are slightly out-of-date.
|
||||
**
|
||||
** The following modules is an enhanced replacement for the "printf" programs
|
||||
** found in the standard library. The following enhancements are
|
||||
** supported:
|
||||
**
|
||||
** + Additional functions. The standard set of "printf" functions
|
||||
** includes printf, fprintf, sprintf, vprintf, vfprintf, and
|
||||
** vsprintf. This module adds the following:
|
||||
**
|
||||
** * snprintf -- Works like sprintf, but has an extra argument
|
||||
** which is the size of the buffer written to.
|
||||
**
|
||||
** * mprintf -- Similar to sprintf. Writes output to memory
|
||||
** obtained from malloc.
|
||||
**
|
||||
** * xprintf -- Calls a function to dispose of output.
|
||||
**
|
||||
** * nprintf -- No output, but returns the number of characters
|
||||
** that would have been output by printf.
|
||||
**
|
||||
** * A v- version (ex: vsnprintf) of every function is also
|
||||
** supplied.
|
||||
**
|
||||
** + A few extensions to the formatting notation are supported:
|
||||
**
|
||||
** * The "=" flag (similar to "-") causes the output to be
|
||||
** be centered in the appropriately sized field.
|
||||
**
|
||||
** * The %b field outputs an integer in binary notation.
|
||||
**
|
||||
** * The %c field now accepts a precision. The character output
|
||||
** is repeated by the number of times the precision specifies.
|
||||
**
|
||||
** * The %' field works like %c, but takes as its character the
|
||||
** next character of the format string, instead of the next
|
||||
** argument. For example, printf("%.78'-") prints 78 minus
|
||||
** signs, the same as printf("%.78c",'-').
|
||||
**
|
||||
** + When compiled using GCC on a SPARC, this version of printf is
|
||||
** faster than the library printf for SUN OS 4.1.
|
||||
**
|
||||
** + All functions are fully reentrant.
|
||||
**
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
/*
|
||||
** Undefine COMPATIBILITY to make some slight changes in the way things
|
||||
** work. I think the changes are an improvement, but they are not
|
||||
** backwards compatible.
|
||||
*/
|
||||
/* #define COMPATIBILITY / * Compatible with SUN OS 4.1 */
|
||||
|
||||
/*
|
||||
** Conversion types fall into various categories as defined by the
|
||||
** following enumeration.
|
||||
*/
|
||||
enum et_type { /* The type of the format field */
|
||||
etRADIX, /* Integer types. %d, %x, %o, and so forth */
|
||||
etFLOAT, /* Floating point. %f */
|
||||
etEXP, /* Exponentional notation. %e and %E */
|
||||
etGENERIC, /* Floating or exponential, depending on exponent. %g */
|
||||
etSIZE, /* Return number of characters processed so far. %n */
|
||||
etSTRING, /* Strings. %s */
|
||||
etPERCENT, /* Percent symbol. %% */
|
||||
etCHARX, /* Characters. %c */
|
||||
etERROR, /* Used to indicate no such conversion type */
|
||||
/* The rest are extensions, not normally found in printf() */
|
||||
etCHARLIT, /* Literal characters. %' */
|
||||
etSQLESCAPE, /* Strings with '\'' doubled. %q */
|
||||
etORDINAL /* 1st, 2nd, 3rd and so forth */
|
||||
};
|
||||
|
||||
/*
|
||||
** Each builtin conversion character (ex: the 'd' in "%d") is described
|
||||
** by an instance of the following structure
|
||||
*/
|
||||
typedef struct et_info { /* Information about each format field */
|
||||
int fmttype; /* The format field code letter */
|
||||
int base; /* The base for radix conversion */
|
||||
char *charset; /* The character set for conversion */
|
||||
int flag_signed; /* Is the quantity signed? */
|
||||
char *prefix; /* Prefix on non-zero values in alt format */
|
||||
enum et_type type; /* Conversion paradigm */
|
||||
} et_info;
|
||||
|
||||
/*
|
||||
** The following table is searched linearly, so it is good to put the
|
||||
** most frequently used conversion types first.
|
||||
*/
|
||||
static et_info fmtinfo[] = {
|
||||
{ 'd', 10, "0123456789", 1, 0, etRADIX, },
|
||||
{ 's', 0, 0, 0, 0, etSTRING, },
|
||||
{ 'q', 0, 0, 0, 0, etSQLESCAPE, },
|
||||
{ 'c', 0, 0, 0, 0, etCHARX, },
|
||||
{ 'o', 8, "01234567", 0, "0", etRADIX, },
|
||||
{ 'u', 10, "0123456789", 0, 0, etRADIX, },
|
||||
{ 'x', 16, "0123456789abcdef", 0, "x0", etRADIX, },
|
||||
{ 'X', 16, "0123456789ABCDEF", 0, "X0", etRADIX, },
|
||||
{ 'r', 10, "0123456789", 0, 0, etORDINAL, },
|
||||
{ 'f', 0, 0, 1, 0, etFLOAT, },
|
||||
{ 'e', 0, "e", 1, 0, etEXP, },
|
||||
{ 'E', 0, "E", 1, 0, etEXP, },
|
||||
{ 'g', 0, "e", 1, 0, etGENERIC, },
|
||||
{ 'G', 0, "E", 1, 0, etGENERIC, },
|
||||
{ 'i', 10, "0123456789", 1, 0, etRADIX, },
|
||||
{ 'n', 0, 0, 0, 0, etSIZE, },
|
||||
{ '%', 0, 0, 0, 0, etPERCENT, },
|
||||
{ 'b', 2, "01", 0, "b0", etRADIX, }, /* Binary */
|
||||
{ 'p', 10, "0123456789", 0, 0, etRADIX, }, /* Pointers */
|
||||
{ '\'', 0, 0, 0, 0, etCHARLIT, }, /* Literal char */
|
||||
};
|
||||
#define etNINFO (sizeof(fmtinfo)/sizeof(fmtinfo[0]))
|
||||
|
||||
/*
|
||||
** If NOFLOATINGPOINT is defined, then none of the floating point
|
||||
** conversions will work.
|
||||
*/
|
||||
#ifndef etNOFLOATINGPOINT
|
||||
/*
|
||||
** "*val" is a double such that 0.1 <= *val < 10.0
|
||||
** Return the ascii code for the leading digit of *val, then
|
||||
** multiply "*val" by 10.0 to renormalize.
|
||||
**
|
||||
** Example:
|
||||
** input: *val = 3.14159
|
||||
** output: *val = 1.4159 function return = '3'
|
||||
**
|
||||
** The counter *cnt is incremented each time. After counter exceeds
|
||||
** 16 (the number of significant digits in a 64-bit float) '0' is
|
||||
** always returned.
|
||||
*/
|
||||
static int et_getdigit(double *val, int *cnt){
|
||||
int digit;
|
||||
double d;
|
||||
if( (*cnt)++ >= 16 ) return '0';
|
||||
digit = (int)*val;
|
||||
d = digit;
|
||||
digit += '0';
|
||||
*val = (*val - d)*10.0;
|
||||
return digit;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define etBUFSIZE 1000 /* Size of the output buffer */
|
||||
|
||||
/*
|
||||
** The root program. All variations call this core.
|
||||
**
|
||||
** INPUTS:
|
||||
** func This is a pointer to a function taking three arguments
|
||||
** 1. A pointer to anything. Same as the "arg" parameter.
|
||||
** 2. A pointer to the list of characters to be output
|
||||
** (Note, this list is NOT null terminated.)
|
||||
** 3. An integer number of characters to be output.
|
||||
** (Note: This number might be zero.)
|
||||
**
|
||||
** arg This is the pointer to anything which will be passed as the
|
||||
** first argument to "func". Use it for whatever you like.
|
||||
**
|
||||
** fmt This is the format string, as in the usual print.
|
||||
**
|
||||
** ap This is a pointer to a list of arguments. Same as in
|
||||
** vfprint.
|
||||
**
|
||||
** OUTPUTS:
|
||||
** The return value is the total number of characters sent to
|
||||
** the function "func". Returns -1 on a error.
|
||||
**
|
||||
** Note that the order in which automatic variables are declared below
|
||||
** seems to make a big difference in determining how fast this beast
|
||||
** will run.
|
||||
*/
|
||||
static int vxprintf(
|
||||
void (*func)(void*,char*,int),
|
||||
void *arg,
|
||||
const char *format,
|
||||
va_list ap
|
||||
){
|
||||
register const char *fmt; /* The format string. */
|
||||
register int c; /* Next character in the format string */
|
||||
register char *bufpt; /* Pointer to the conversion buffer */
|
||||
register int precision; /* Precision of the current field */
|
||||
register int length; /* Length of the field */
|
||||
register int idx; /* A general purpose loop counter */
|
||||
int count; /* Total number of characters output */
|
||||
int width; /* Width of the current field */
|
||||
int flag_leftjustify; /* True if "-" flag is present */
|
||||
int flag_plussign; /* True if "+" flag is present */
|
||||
int flag_blanksign; /* True if " " flag is present */
|
||||
int flag_alternateform; /* True if "#" flag is present */
|
||||
int flag_zeropad; /* True if field width constant starts with zero */
|
||||
int flag_long; /* True if "l" flag is present */
|
||||
int flag_center; /* True if "=" flag is present */
|
||||
unsigned long longvalue; /* Value for integer types */
|
||||
double realvalue; /* Value for real types */
|
||||
et_info *infop; /* Pointer to the appropriate info structure */
|
||||
char buf[etBUFSIZE]; /* Conversion buffer */
|
||||
char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */
|
||||
int errorflag = 0; /* True if an error is encountered */
|
||||
enum et_type xtype; /* Conversion paradigm */
|
||||
char *zMem; /* String to be freed */
|
||||
char *zExtra; /* Extra memory used for etTCLESCAPE conversions */
|
||||
static char spaces[] = " "
|
||||
" ";
|
||||
#define etSPACESIZE (sizeof(spaces)-1)
|
||||
#ifndef etNOFLOATINGPOINT
|
||||
int exp; /* exponent of real numbers */
|
||||
double rounder; /* Used for rounding floating point values */
|
||||
int flag_dp; /* True if decimal point should be shown */
|
||||
int flag_rtz; /* True if trailing zeros should be removed */
|
||||
int flag_exp; /* True to force display of the exponent */
|
||||
int nsd; /* Number of significant digits returned */
|
||||
#endif
|
||||
|
||||
fmt = format; /* Put in a register for speed */
|
||||
count = length = 0;
|
||||
bufpt = 0;
|
||||
for(; (c=(*fmt))!=0; ++fmt){
|
||||
if( c!='%' ){
|
||||
register int amt;
|
||||
bufpt = (char *)fmt;
|
||||
amt = 1;
|
||||
while( (c=(*++fmt))!='%' && c!=0 ) amt++;
|
||||
(*func)(arg,bufpt,amt);
|
||||
count += amt;
|
||||
if( c==0 ) break;
|
||||
}
|
||||
if( (c=(*++fmt))==0 ){
|
||||
errorflag = 1;
|
||||
(*func)(arg,"%",1);
|
||||
count++;
|
||||
break;
|
||||
}
|
||||
/* Find out what flags are present */
|
||||
flag_leftjustify = flag_plussign = flag_blanksign =
|
||||
flag_alternateform = flag_zeropad = flag_center = 0;
|
||||
do{
|
||||
switch( c ){
|
||||
case '-': flag_leftjustify = 1; c = 0; break;
|
||||
case '+': flag_plussign = 1; c = 0; break;
|
||||
case ' ': flag_blanksign = 1; c = 0; break;
|
||||
case '#': flag_alternateform = 1; c = 0; break;
|
||||
case '0': flag_zeropad = 1; c = 0; break;
|
||||
case '=': flag_center = 1; c = 0; break;
|
||||
default: break;
|
||||
}
|
||||
}while( c==0 && (c=(*++fmt))!=0 );
|
||||
if( flag_center ) flag_leftjustify = 0;
|
||||
/* Get the field width */
|
||||
width = 0;
|
||||
if( c=='*' ){
|
||||
width = va_arg(ap,int);
|
||||
if( width<0 ){
|
||||
flag_leftjustify = 1;
|
||||
width = -width;
|
||||
}
|
||||
c = *++fmt;
|
||||
}else{
|
||||
while( c>='0' && c<='9' ){
|
||||
width = width*10 + c - '0';
|
||||
c = *++fmt;
|
||||
}
|
||||
}
|
||||
if( width > etBUFSIZE-10 ){
|
||||
width = etBUFSIZE-10;
|
||||
}
|
||||
/* Get the precision */
|
||||
if( c=='.' ){
|
||||
precision = 0;
|
||||
c = *++fmt;
|
||||
if( c=='*' ){
|
||||
precision = va_arg(ap,int);
|
||||
#ifndef etCOMPATIBILITY
|
||||
/* This is sensible, but SUN OS 4.1 doesn't do it. */
|
||||
if( precision<0 ) precision = -precision;
|
||||
#endif
|
||||
c = *++fmt;
|
||||
}else{
|
||||
while( c>='0' && c<='9' ){
|
||||
precision = precision*10 + c - '0';
|
||||
c = *++fmt;
|
||||
}
|
||||
}
|
||||
/* Limit the precision to prevent overflowing buf[] during conversion */
|
||||
if( precision>etBUFSIZE-40 ) precision = etBUFSIZE-40;
|
||||
}else{
|
||||
precision = -1;
|
||||
}
|
||||
/* Get the conversion type modifier */
|
||||
if( c=='l' ){
|
||||
flag_long = 1;
|
||||
c = *++fmt;
|
||||
}else{
|
||||
flag_long = 0;
|
||||
}
|
||||
/* Fetch the info entry for the field */
|
||||
infop = 0;
|
||||
for(idx=0; idx<etNINFO; idx++){
|
||||
if( c==fmtinfo[idx].fmttype ){
|
||||
infop = &fmtinfo[idx];
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* No info entry found. It must be an error. */
|
||||
if( infop==0 ){
|
||||
xtype = etERROR;
|
||||
}else{
|
||||
xtype = infop->type;
|
||||
}
|
||||
zExtra = 0;
|
||||
|
||||
/*
|
||||
** At this point, variables are initialized as follows:
|
||||
**
|
||||
** flag_alternateform TRUE if a '#' is present.
|
||||
** flag_plussign TRUE if a '+' is present.
|
||||
** flag_leftjustify TRUE if a '-' is present or if the
|
||||
** field width was negative.
|
||||
** flag_zeropad TRUE if the width began with 0.
|
||||
** flag_long TRUE if the letter 'l' (ell) prefixed
|
||||
** the conversion character.
|
||||
** flag_blanksign TRUE if a ' ' is present.
|
||||
** width The specified field width. This is
|
||||
** always non-negative. Zero is the default.
|
||||
** precision The specified precision. The default
|
||||
** is -1.
|
||||
** xtype The class of the conversion.
|
||||
** infop Pointer to the appropriate info struct.
|
||||
*/
|
||||
switch( xtype ){
|
||||
case etORDINAL:
|
||||
case etRADIX:
|
||||
if( flag_long ) longvalue = va_arg(ap,long);
|
||||
else longvalue = va_arg(ap,int);
|
||||
#ifdef etCOMPATIBILITY
|
||||
/* For the format %#x, the value zero is printed "0" not "0x0".
|
||||
** I think this is stupid. */
|
||||
if( longvalue==0 ) flag_alternateform = 0;
|
||||
#else
|
||||
/* More sensible: turn off the prefix for octal (to prevent "00"),
|
||||
** but leave the prefix for hex. */
|
||||
if( longvalue==0 && infop->base==8 ) flag_alternateform = 0;
|
||||
#endif
|
||||
if( infop->flag_signed ){
|
||||
if( *(long*)&longvalue<0 ){
|
||||
longvalue = -*(long*)&longvalue;
|
||||
prefix = '-';
|
||||
}else if( flag_plussign ) prefix = '+';
|
||||
else if( flag_blanksign ) prefix = ' ';
|
||||
else prefix = 0;
|
||||
}else prefix = 0;
|
||||
if( flag_zeropad && precision<width-(prefix!=0) ){
|
||||
precision = width-(prefix!=0);
|
||||
}
|
||||
bufpt = &buf[etBUFSIZE];
|
||||
if( xtype==etORDINAL ){
|
||||
long a,b;
|
||||
a = longvalue%10;
|
||||
b = longvalue%100;
|
||||
bufpt -= 2;
|
||||
if( a==0 || a>3 || (b>10 && b<14) ){
|
||||
bufpt[0] = 't';
|
||||
bufpt[1] = 'h';
|
||||
}else if( a==1 ){
|
||||
bufpt[0] = 's';
|
||||
bufpt[1] = 't';
|
||||
}else if( a==2 ){
|
||||
bufpt[0] = 'n';
|
||||
bufpt[1] = 'd';
|
||||
}else if( a==3 ){
|
||||
bufpt[0] = 'r';
|
||||
bufpt[1] = 'd';
|
||||
}
|
||||
}
|
||||
{
|
||||
register char *cset; /* Use registers for speed */
|
||||
register int base;
|
||||
cset = infop->charset;
|
||||
base = infop->base;
|
||||
do{ /* Convert to ascii */
|
||||
*(--bufpt) = cset[longvalue%base];
|
||||
longvalue = longvalue/base;
|
||||
}while( longvalue>0 );
|
||||
}
|
||||
length = (long)&buf[etBUFSIZE]-(long)bufpt;
|
||||
for(idx=precision-length; idx>0; idx--){
|
||||
*(--bufpt) = '0'; /* Zero pad */
|
||||
}
|
||||
if( prefix ) *(--bufpt) = prefix; /* Add sign */
|
||||
if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */
|
||||
char *pre, x;
|
||||
pre = infop->prefix;
|
||||
if( *bufpt!=pre[0] ){
|
||||
for(pre=infop->prefix; (x=(*pre))!=0; pre++) *(--bufpt) = x;
|
||||
}
|
||||
}
|
||||
length = (long)&buf[etBUFSIZE]-(long)bufpt;
|
||||
break;
|
||||
case etFLOAT:
|
||||
case etEXP:
|
||||
case etGENERIC:
|
||||
realvalue = va_arg(ap,double);
|
||||
#ifndef etNOFLOATINGPOINT
|
||||
if( precision<0 ) precision = 6; /* Set default precision */
|
||||
if( precision>etBUFSIZE-10 ) precision = etBUFSIZE-10;
|
||||
if( realvalue<0.0 ){
|
||||
realvalue = -realvalue;
|
||||
prefix = '-';
|
||||
}else{
|
||||
if( flag_plussign ) prefix = '+';
|
||||
else if( flag_blanksign ) prefix = ' ';
|
||||
else prefix = 0;
|
||||
}
|
||||
if( infop->type==etGENERIC && precision>0 ) precision--;
|
||||
rounder = 0.0;
|
||||
#ifdef COMPATIBILITY
|
||||
/* Rounding works like BSD when the constant 0.4999 is used. Wierd! */
|
||||
for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1);
|
||||
#else
|
||||
/* It makes more sense to use 0.5 */
|
||||
for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1);
|
||||
#endif
|
||||
if( infop->type==etFLOAT ) realvalue += rounder;
|
||||
/* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
|
||||
exp = 0;
|
||||
if( realvalue>0.0 ){
|
||||
int k = 0;
|
||||
while( realvalue>=1e8 && k++<100 ){ realvalue *= 1e-8; exp+=8; }
|
||||
while( realvalue>=10.0 && k++<100 ){ realvalue *= 0.1; exp++; }
|
||||
while( realvalue<1e-8 && k++<100 ){ realvalue *= 1e8; exp-=8; }
|
||||
while( realvalue<1.0 && k++<100 ){ realvalue *= 10.0; exp--; }
|
||||
if( k>=100 ){
|
||||
bufpt = "NaN";
|
||||
length = 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bufpt = buf;
|
||||
/*
|
||||
** If the field type is etGENERIC, then convert to either etEXP
|
||||
** or etFLOAT, as appropriate.
|
||||
*/
|
||||
flag_exp = xtype==etEXP;
|
||||
if( xtype!=etFLOAT ){
|
||||
realvalue += rounder;
|
||||
if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; }
|
||||
}
|
||||
if( xtype==etGENERIC ){
|
||||
flag_rtz = !flag_alternateform;
|
||||
if( exp<-4 || exp>precision ){
|
||||
xtype = etEXP;
|
||||
}else{
|
||||
precision = precision - exp;
|
||||
xtype = etFLOAT;
|
||||
}
|
||||
}else{
|
||||
flag_rtz = 0;
|
||||
}
|
||||
/*
|
||||
** The "exp+precision" test causes output to be of type etEXP if
|
||||
** the precision is too large to fit in buf[].
|
||||
*/
|
||||
nsd = 0;
|
||||
if( xtype==etFLOAT && exp+precision<etBUFSIZE-30 ){
|
||||
flag_dp = (precision>0 || flag_alternateform);
|
||||
if( prefix ) *(bufpt++) = prefix; /* Sign */
|
||||
if( exp<0 ) *(bufpt++) = '0'; /* Digits before "." */
|
||||
else for(; exp>=0; exp--) *(bufpt++) = et_getdigit(&realvalue,&nsd);
|
||||
if( flag_dp ) *(bufpt++) = '.'; /* The decimal point */
|
||||
for(exp++; exp<0 && precision>0; precision--, exp++){
|
||||
*(bufpt++) = '0';
|
||||
}
|
||||
while( (precision--)>0 ) *(bufpt++) = et_getdigit(&realvalue,&nsd);
|
||||
*(bufpt--) = 0; /* Null terminate */
|
||||
if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */
|
||||
while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0;
|
||||
if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0;
|
||||
}
|
||||
bufpt++; /* point to next free slot */
|
||||
}else{ /* etEXP or etGENERIC */
|
||||
flag_dp = (precision>0 || flag_alternateform);
|
||||
if( prefix ) *(bufpt++) = prefix; /* Sign */
|
||||
*(bufpt++) = et_getdigit(&realvalue,&nsd); /* First digit */
|
||||
if( flag_dp ) *(bufpt++) = '.'; /* Decimal point */
|
||||
while( (precision--)>0 ) *(bufpt++) = et_getdigit(&realvalue,&nsd);
|
||||
bufpt--; /* point to last digit */
|
||||
if( flag_rtz && flag_dp ){ /* Remove tail zeros */
|
||||
while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0;
|
||||
if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0;
|
||||
}
|
||||
bufpt++; /* point to next free slot */
|
||||
if( exp || flag_exp ){
|
||||
*(bufpt++) = infop->charset[0];
|
||||
if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; } /* sign of exp */
|
||||
else { *(bufpt++) = '+'; }
|
||||
if( exp>=100 ){
|
||||
*(bufpt++) = (exp/100)+'0'; /* 100's digit */
|
||||
exp %= 100;
|
||||
}
|
||||
*(bufpt++) = exp/10+'0'; /* 10's digit */
|
||||
*(bufpt++) = exp%10+'0'; /* 1's digit */
|
||||
}
|
||||
}
|
||||
/* The converted number is in buf[] and zero terminated. Output it.
|
||||
** Note that the number is in the usual order, not reversed as with
|
||||
** integer conversions. */
|
||||
length = (long)bufpt-(long)buf;
|
||||
bufpt = buf;
|
||||
|
||||
/* Special case: Add leading zeros if the flag_zeropad flag is
|
||||
** set and we are not left justified */
|
||||
if( flag_zeropad && !flag_leftjustify && length < width){
|
||||
int i;
|
||||
int nPad = width - length;
|
||||
for(i=width; i>=nPad; i--){
|
||||
bufpt[i] = bufpt[i-nPad];
|
||||
}
|
||||
i = prefix!=0;
|
||||
while( nPad-- ) bufpt[i++] = '0';
|
||||
length = width;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case etSIZE:
|
||||
*(va_arg(ap,int*)) = count;
|
||||
length = width = 0;
|
||||
break;
|
||||
case etPERCENT:
|
||||
buf[0] = '%';
|
||||
bufpt = buf;
|
||||
length = 1;
|
||||
break;
|
||||
case etCHARLIT:
|
||||
case etCHARX:
|
||||
c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt);
|
||||
if( precision>=0 ){
|
||||
for(idx=1; idx<precision; idx++) buf[idx] = c;
|
||||
length = precision;
|
||||
}else{
|
||||
length =1;
|
||||
}
|
||||
bufpt = buf;
|
||||
break;
|
||||
case etSTRING:
|
||||
zMem = bufpt = va_arg(ap,char*);
|
||||
if( bufpt==0 ) bufpt = "(null)";
|
||||
length = strlen(bufpt);
|
||||
if( precision>=0 && precision<length ) length = precision;
|
||||
break;
|
||||
case etSQLESCAPE:
|
||||
{
|
||||
int i, j, n, c;
|
||||
char *arg = va_arg(ap,char*);
|
||||
if( arg==0 ) arg = "(NULL)";
|
||||
for(i=n=0; (c=arg[i])!=0; i++){
|
||||
if( c=='\'' ) n++;
|
||||
}
|
||||
n += i + 1;
|
||||
if( n>etBUFSIZE ){
|
||||
bufpt = zExtra = sqliteMalloc( n );
|
||||
}else{
|
||||
bufpt = buf;
|
||||
}
|
||||
for(i=j=0; (c=arg[i])!=0; i++){
|
||||
bufpt[j++] = c;
|
||||
if( c=='\'' ) bufpt[j++] = c;
|
||||
}
|
||||
bufpt[j] = 0;
|
||||
length = j;
|
||||
if( precision>=0 && precision<length ) length = precision;
|
||||
}
|
||||
break;
|
||||
case etERROR:
|
||||
buf[0] = '%';
|
||||
buf[1] = c;
|
||||
errorflag = 0;
|
||||
idx = 1+(c!=0);
|
||||
(*func)(arg,"%",idx);
|
||||
count += idx;
|
||||
if( c==0 ) fmt--;
|
||||
break;
|
||||
}/* End switch over the format type */
|
||||
/*
|
||||
** The text of the conversion is pointed to by "bufpt" and is
|
||||
** "length" characters long. The field width is "width". Do
|
||||
** the output.
|
||||
*/
|
||||
if( !flag_leftjustify ){
|
||||
register int nspace;
|
||||
nspace = width-length;
|
||||
if( nspace>0 ){
|
||||
if( flag_center ){
|
||||
nspace = nspace/2;
|
||||
width -= nspace;
|
||||
flag_leftjustify = 1;
|
||||
}
|
||||
count += nspace;
|
||||
while( nspace>=etSPACESIZE ){
|
||||
(*func)(arg,spaces,etSPACESIZE);
|
||||
nspace -= etSPACESIZE;
|
||||
}
|
||||
if( nspace>0 ) (*func)(arg,spaces,nspace);
|
||||
}
|
||||
}
|
||||
if( length>0 ){
|
||||
(*func)(arg,bufpt,length);
|
||||
count += length;
|
||||
}
|
||||
if( flag_leftjustify ){
|
||||
register int nspace;
|
||||
nspace = width-length;
|
||||
if( nspace>0 ){
|
||||
count += nspace;
|
||||
while( nspace>=etSPACESIZE ){
|
||||
(*func)(arg,spaces,etSPACESIZE);
|
||||
nspace -= etSPACESIZE;
|
||||
}
|
||||
if( nspace>0 ) (*func)(arg,spaces,nspace);
|
||||
}
|
||||
}
|
||||
if( zExtra ){
|
||||
sqliteFree(zExtra);
|
||||
}
|
||||
}/* End for loop over the format string */
|
||||
return errorflag ? -1 : count;
|
||||
} /* End of function */
|
||||
|
||||
|
||||
/* This structure is used to store state information about the
|
||||
** write to memory that is currently in progress.
|
||||
*/
|
||||
struct sgMprintf {
|
||||
char *zBase; /* A base allocation */
|
||||
char *zText; /* The string collected so far */
|
||||
int nChar; /* Length of the string so far */
|
||||
int nAlloc; /* Amount of space allocated in zText */
|
||||
};
|
||||
|
||||
/*
|
||||
** This function implements the callback from vxprintf.
|
||||
**
|
||||
** This routine add nNewChar characters of text in zNewText to
|
||||
** the sgMprintf structure pointed to by "arg".
|
||||
*/
|
||||
static void mout(void *arg, char *zNewText, int nNewChar){
|
||||
struct sgMprintf *pM = (struct sgMprintf*)arg;
|
||||
if( pM->nChar + nNewChar + 1 > pM->nAlloc ){
|
||||
pM->nAlloc = pM->nChar + nNewChar*2 + 1;
|
||||
if( pM->zText==pM->zBase ){
|
||||
pM->zText = sqliteMalloc(pM->nAlloc);
|
||||
if( pM->zText && pM->nChar ) memcpy(pM->zText,pM->zBase,pM->nChar);
|
||||
}else{
|
||||
pM->zText = sqliteRealloc(pM->zText, pM->nAlloc);
|
||||
}
|
||||
}
|
||||
if( pM->zText ){
|
||||
memcpy(&pM->zText[pM->nChar], zNewText, nNewChar);
|
||||
pM->nChar += nNewChar;
|
||||
pM->zText[pM->nChar] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** sqlite_mprintf() works like printf(), but allocations memory to hold the
|
||||
** resulting string and returns a pointer to the allocated memory. Use
|
||||
** sqliteFree() to release the memory allocated.
|
||||
*/
|
||||
char *sqlite_mprintf(const char *zFormat, ...){
|
||||
va_list ap;
|
||||
struct sgMprintf sMprintf;
|
||||
char *zNew;
|
||||
char zBuf[200];
|
||||
|
||||
sMprintf.nChar = 0;
|
||||
sMprintf.nAlloc = sizeof(zBuf);
|
||||
sMprintf.zText = zBuf;
|
||||
sMprintf.zBase = zBuf;
|
||||
va_start(ap,zFormat);
|
||||
vxprintf(mout,&sMprintf,zFormat,ap);
|
||||
va_end(ap);
|
||||
sMprintf.zText[sMprintf.nChar] = 0;
|
||||
if( sMprintf.zText==sMprintf.zBase ){
|
||||
zNew = sqliteMalloc( sMprintf.nChar+1 );
|
||||
if( zNew ) strcpy(zNew,zBuf);
|
||||
}else{
|
||||
zNew = sqliteRealloc(sMprintf.zText,sMprintf.nChar+1);
|
||||
}
|
||||
return zNew;
|
||||
}
|
||||
|
||||
/* This is the varargs version of sqlite_mprintf.
|
||||
*/
|
||||
char *sqlite_vmprintf(const char *zFormat, va_list ap){
|
||||
struct sgMprintf sMprintf;
|
||||
char zBuf[200];
|
||||
sMprintf.nChar = 0;
|
||||
sMprintf.zText = zBuf;
|
||||
sMprintf.nAlloc = sizeof(zBuf);
|
||||
sMprintf.zBase = zBuf;
|
||||
vxprintf(mout,&sMprintf,zFormat,ap);
|
||||
sMprintf.zText[sMprintf.nChar] = 0;
|
||||
if( sMprintf.zText==sMprintf.zBase ){
|
||||
sMprintf.zText = sqliteMalloc( strlen(zBuf)+1 );
|
||||
if( sMprintf.zText ) strcpy(sMprintf.zText,zBuf);
|
||||
}else{
|
||||
sMprintf.zText = sqliteRealloc(sMprintf.zText,sMprintf.nChar+1);
|
||||
}
|
||||
return sMprintf.zText;
|
||||
}
|
||||
|
||||
/*
|
||||
** The following four routines implement the varargs versions of the
|
||||
** sqlite_exec() and sqlite_get_table() interfaces. See the sqlite.h
|
||||
** header files for a more detailed description of how these interfaces
|
||||
** work.
|
||||
**
|
||||
** These routines are all just simple wrappers.
|
||||
*/
|
||||
int sqlite_exec_printf(
|
||||
sqlite *db, /* An open database */
|
||||
char *sqlFormat, /* printf-style format string for the SQL */
|
||||
sqlite_callback xCallback, /* Callback function */
|
||||
void *pArg, /* 1st argument to callback function */
|
||||
char **errmsg, /* Error msg written here */
|
||||
... /* Arguments to the format string. */
|
||||
){
|
||||
va_list ap;
|
||||
int rc;
|
||||
|
||||
va_start(ap, errmsg);
|
||||
rc = sqlite_exec_vprintf(db, sqlFormat, xCallback, pArg, errmsg, ap);
|
||||
va_end(ap);
|
||||
return rc;
|
||||
}
|
||||
int sqlite_exec_vprintf(
|
||||
sqlite *db, /* An open database */
|
||||
char *sqlFormat, /* printf-style format string for the SQL */
|
||||
sqlite_callback xCallback, /* Callback function */
|
||||
void *pArg, /* 1st argument to callback function */
|
||||
char **errmsg, /* Error msg written here */
|
||||
va_list ap /* Arguments to the format string. */
|
||||
){
|
||||
char *zSql;
|
||||
int rc;
|
||||
|
||||
zSql = sqlite_vmprintf(sqlFormat, ap);
|
||||
rc = sqlite_exec(db, zSql, xCallback, pArg, errmsg);
|
||||
sqliteFree(zSql);
|
||||
return rc;
|
||||
}
|
||||
int sqlite_get_table_printf(
|
||||
sqlite *db, /* An open database */
|
||||
char *sqlFormat, /* printf-style format string for the SQL */
|
||||
char ***resultp, /* Result written to a char *[] that this points to */
|
||||
int *nrow, /* Number of result rows written here */
|
||||
int *ncol, /* Number of result columns written here */
|
||||
char **errmsg, /* Error msg written here */
|
||||
... /* Arguments to the format string */
|
||||
){
|
||||
va_list ap;
|
||||
int rc;
|
||||
|
||||
va_start(ap, errmsg);
|
||||
rc = sqlite_get_table_vprintf(db, sqlFormat, resultp, nrow, ncol, errmsg, ap);
|
||||
va_end(ap);
|
||||
return rc;
|
||||
}
|
||||
int sqlite_get_table_vprintf(
|
||||
sqlite *db, /* An open database */
|
||||
char *sqlFormat, /* printf-style format string for the SQL */
|
||||
char ***resultp, /* Result written to a char *[] that this points to */
|
||||
int *nrow, /* Number of result rows written here */
|
||||
int *ncolumn, /* Number of result columns written here */
|
||||
char **errmsg, /* Error msg written here */
|
||||
va_list ap /* Arguments to the format string */
|
||||
){
|
||||
char *zSql;
|
||||
int rc;
|
||||
|
||||
zSql = sqlite_vmprintf(sqlFormat, ap);
|
||||
rc = sqlite_get_table(db, zSql, resultp, nrow, ncolumn, errmsg);
|
||||
sqliteFree(zSql);
|
||||
return rc;
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
** Copyright (c) 2000 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains code to implement a pseudo-random number
|
||||
** generator (PRNG) for SQLite.
|
||||
**
|
||||
** Random numbers are used by some of the database backends in order
|
||||
** to generate random integer keys for tables or random filenames.
|
||||
**
|
||||
** $Id: random.c,v 1.2 2001/01/31 13:28:09 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <time.h>
|
||||
|
||||
/*
|
||||
** Get a single 8-bit random value from the RC4 PRNG.
|
||||
*/
|
||||
int sqliteRandomByte(void){
|
||||
int t;
|
||||
|
||||
/*
|
||||
** The following structure holds the current state of the RC4 algorithm.
|
||||
** We use RC4 as a random number generator. Each call to RC4 gives
|
||||
** a random 8-bit number.
|
||||
**
|
||||
** Nothing in this file or anywhere else in SQLite does any kind of
|
||||
** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random
|
||||
** number generator) not as an encryption device.
|
||||
*/
|
||||
static struct {
|
||||
int isInit;
|
||||
int i, j;
|
||||
int s[256];
|
||||
} prng_state;
|
||||
|
||||
/* Initialize the state of the random number generator once,
|
||||
** the first time this routine is called. The seed value does
|
||||
** not need to contain a lot of randomness since we are not
|
||||
** trying to do secure encryption or anything like that...
|
||||
*/
|
||||
if( !prng_state.isInit ){
|
||||
int i;
|
||||
static char seed[] = " sqlite random seed";
|
||||
char k[256];
|
||||
time((time_t*)seed);
|
||||
prng_state.j = 0;
|
||||
prng_state.i = 0;
|
||||
for(i=0; i<256; i++){
|
||||
prng_state.s[i] = i;
|
||||
k[i] = seed[i%sizeof(seed)];
|
||||
}
|
||||
for(i=0; i<256; i++){
|
||||
int t;
|
||||
prng_state.j = (prng_state.j + prng_state.s[i] + k[i]) & 0xff;
|
||||
t = prng_state.s[prng_state.j];
|
||||
prng_state.s[prng_state.j] = prng_state.s[i];
|
||||
prng_state.s[i] = t;
|
||||
}
|
||||
prng_state.isInit = 1;
|
||||
}
|
||||
|
||||
/* Generate and return single random byte
|
||||
*/
|
||||
prng_state.i = (prng_state.i + 1) & 0xff;
|
||||
prng_state.j = (prng_state.j + prng_state.s[prng_state.i]) & 0xff;
|
||||
t = prng_state.s[prng_state.i];
|
||||
prng_state.s[prng_state.i] = prng_state.s[prng_state.j];
|
||||
prng_state.s[prng_state.j] = t;
|
||||
t = prng_state.s[prng_state.i] + prng_state.s[prng_state.j];
|
||||
return t & 0xff;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a random 32-bit integer. The integer is generated by making
|
||||
** 4 calls to sqliteRandomByte().
|
||||
*/
|
||||
int sqliteRandomInteger(void){
|
||||
int r;
|
||||
int i;
|
||||
r = sqliteRandomByte();
|
||||
for(i=1; i<4; i++){
|
||||
r = (r<<8) + sqliteRandomByte();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Generate a random filename with the given prefix. The new filename
|
||||
** is written into zBuf[]. The calling function must insure that
|
||||
** zBuf[] is big enough to hold the prefix plus 20 or so extra
|
||||
** characters.
|
||||
**
|
||||
** Very random names are chosen so that the chance of a
|
||||
** collision with an existing filename is very very small.
|
||||
*/
|
||||
void sqliteRandomName(char *zBuf, char *zPrefix){
|
||||
int i, j;
|
||||
static const char zRandomChars[] = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
strcpy(zBuf, zPrefix);
|
||||
j = strlen(zBuf);
|
||||
for(i=0; i<15; i++){
|
||||
int c = sqliteRandomByte() % (sizeof(zRandomChars) - 1);
|
||||
zBuf[j++] = zRandomChars[c];
|
||||
}
|
||||
zBuf[j] = 0;
|
||||
}
|
||||
+9
-7
@@ -24,7 +24,7 @@
|
||||
** This file contains C code routines that are called by the parser
|
||||
** to handle SELECT statements.
|
||||
**
|
||||
** $Id: select.c,v 1.26 2000/07/29 13:06:59 drh Exp $
|
||||
** $Id: select.c,v 1.28 2001/01/15 22:51:11 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -424,7 +424,7 @@ static int matchOrderbyToColumn(
|
||||
Vdbe *sqliteGetVdbe(Parse *pParse){
|
||||
Vdbe *v = pParse->pVdbe;
|
||||
if( v==0 ){
|
||||
v = pParse->pVdbe = sqliteVdbeCreate(pParse->db->pBe);
|
||||
v = pParse->pVdbe = sqliteVdbeCreate(pParse->db);
|
||||
}
|
||||
if( v==0 ){
|
||||
sqliteSetString(&pParse->zErrMsg, "out of memory", 0);
|
||||
@@ -489,9 +489,11 @@ static int multiSelect(Parse *pParse, Select *p, int eDest, int iParm){
|
||||
&& matchOrderbyToColumn(pParse, p, p->pOrderBy, unionTab, 1) ){
|
||||
return 1;
|
||||
}
|
||||
sqliteVdbeAddOp(v, OP_Open, unionTab, 1, 0, 0);
|
||||
if( p->op!=TK_ALL ){
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, unionTab, 1, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_KeyAsData, unionTab, 1, 0, 0);
|
||||
}else{
|
||||
sqliteVdbeAddOp(v, OP_OpenTbl, unionTab, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,7 +551,7 @@ static int multiSelect(Parse *pParse, Select *p, int eDest, int iParm){
|
||||
if( p->pOrderBy && matchOrderbyToColumn(pParse,p,p->pOrderBy,tab1,1) ){
|
||||
return 1;
|
||||
}
|
||||
sqliteVdbeAddOp(v, OP_Open, tab1, 1, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, tab1, 1, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_KeyAsData, tab1, 1, 0, 0);
|
||||
|
||||
/* Code the SELECTs to our left into temporary table "tab1".
|
||||
@@ -559,7 +561,7 @@ static int multiSelect(Parse *pParse, Select *p, int eDest, int iParm){
|
||||
|
||||
/* Code the current SELECT into temporary table "tab2"
|
||||
*/
|
||||
sqliteVdbeAddOp(v, OP_Open, tab2, 1, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, tab2, 1, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_KeyAsData, tab2, 1, 0, 0);
|
||||
p->pPrior = 0;
|
||||
rc = sqliteSelect(pParse, p, SRT_Union, tab2);
|
||||
@@ -818,7 +820,7 @@ int sqliteSelect(
|
||||
*/
|
||||
v = pParse->pVdbe;
|
||||
if( v==0 ){
|
||||
v = pParse->pVdbe = sqliteVdbeCreate(pParse->db->pBe);
|
||||
v = pParse->pVdbe = sqliteVdbeCreate(pParse->db);
|
||||
}
|
||||
if( v==0 ){
|
||||
sqliteSetString(&pParse->zErrMsg, "out of memory", 0);
|
||||
@@ -852,7 +854,7 @@ int sqliteSelect(
|
||||
/* Begin the database scan
|
||||
*/
|
||||
if( isDistinct ){
|
||||
sqliteVdbeAddOp(v, OP_Open, distinct, 1, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, distinct, 1, 0, 0);
|
||||
}
|
||||
pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 0);
|
||||
if( pWInfo==0 ) return 1;
|
||||
|
||||
+141
-55
@@ -24,7 +24,7 @@
|
||||
** This file contains code to implement the "sqlite" command line
|
||||
** utility for accessing SQLite databases.
|
||||
**
|
||||
** $Id: shell.c,v 1.21 2000/08/17 09:50:00 drh Exp $
|
||||
** $Id: shell.c,v 1.29 2001/01/04 14:27:08 drh Exp $
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -32,6 +32,9 @@
|
||||
#include "sqlite.h"
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#ifdef OS_UNIX
|
||||
# include <signal.h>
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_READLINE) && HAVE_READLINE==1
|
||||
# include <readline/readline.h>
|
||||
@@ -41,6 +44,13 @@
|
||||
# define add_history(X)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** The following is the open SQLite database. We make a pointer
|
||||
** to this database a static variable so that it can be accessed
|
||||
** by the SIGINT handler to interrupt database processing.
|
||||
*/
|
||||
static sqlite *db = 0;
|
||||
|
||||
/*
|
||||
** This routine reads a line of text from standard input, stores
|
||||
** the text in memory obtained from malloc() and returns a pointer
|
||||
@@ -140,8 +150,9 @@ struct callback_data {
|
||||
#define MODE_Line 0 /* One column per line. Blank line between records */
|
||||
#define MODE_Column 1 /* One record per line in neat columns */
|
||||
#define MODE_List 2 /* One record per line with a separator */
|
||||
#define MODE_Html 3 /* Generate an XHTML table */
|
||||
#define MODE_Insert 4 /* Generate SQL "insert" statements */
|
||||
#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */
|
||||
#define MODE_Html 4 /* Generate an XHTML table */
|
||||
#define MODE_Insert 5 /* Generate SQL "insert" statements */
|
||||
|
||||
/*
|
||||
** Number of elements in an array
|
||||
@@ -228,6 +239,13 @@ static void output_html_string(FILE *out, const char *z){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** This routine runs when the user presses Ctrl-C
|
||||
*/
|
||||
static void interrupt_handler(int NotUsed){
|
||||
if( db ) sqlite_interrupt(db);
|
||||
}
|
||||
|
||||
/*
|
||||
** This is the callback routine that the SQLite library
|
||||
** invokes for each row of a query result.
|
||||
@@ -237,9 +255,14 @@ static int callback(void *pArg, int nArg, char **azArg, char **azCol){
|
||||
struct callback_data *p = (struct callback_data*)pArg;
|
||||
switch( p->mode ){
|
||||
case MODE_Line: {
|
||||
int w = 5;
|
||||
for(i=0; i<nArg; i++){
|
||||
int len = strlen(azCol[i]);
|
||||
if( len>w ) w = len;
|
||||
}
|
||||
if( p->cnt++>0 ) fprintf(p->out,"\n");
|
||||
for(i=0; i<nArg; i++){
|
||||
fprintf(p->out,"%s = %s\n", azCol[i], azArg[i] ? azArg[i] : 0);
|
||||
fprintf(p->out,"%*s = %s\n", w, azCol[i], azArg[i] ? azArg[i] : 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -253,9 +276,9 @@ static int callback(void *pArg, int nArg, char **azArg, char **azCol){
|
||||
w = 0;
|
||||
}
|
||||
if( w<=0 ){
|
||||
w = strlen(azCol[i]);
|
||||
w = strlen(azCol[i] ? azCol[i] : "");
|
||||
if( w<10 ) w = 10;
|
||||
n = strlen(azArg[i]);
|
||||
n = strlen(azArg[i] ? azArg[i] : "");
|
||||
if( w<n ) w = n;
|
||||
}
|
||||
if( i<ArraySize(p->actualWidth) ){
|
||||
@@ -291,6 +314,7 @@ static int callback(void *pArg, int nArg, char **azArg, char **azCol){
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MODE_Semi:
|
||||
case MODE_List: {
|
||||
if( p->cnt++==0 && p->showHeader ){
|
||||
for(i=0; i<nArg; i++){
|
||||
@@ -312,7 +336,13 @@ static int callback(void *pArg, int nArg, char **azArg, char **azCol){
|
||||
}
|
||||
z += j;
|
||||
}
|
||||
fprintf(p->out, "%s", i==nArg-1 ? "\n" : p->separator);
|
||||
if( i<nArg-1 ){
|
||||
fprintf(p->out, "%s", p->separator);
|
||||
}else if( p->mode==MODE_Semi ){
|
||||
fprintf(p->out, ";\n");
|
||||
}else{
|
||||
fprintf(p->out, "\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -364,14 +394,15 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){
|
||||
fprintf(pData->out, "%s;\n", azArg[2]);
|
||||
if( strcmp(azArg[1],"table")==0 ){
|
||||
struct callback_data d2;
|
||||
char zSql[1000];
|
||||
d2 = *pData;
|
||||
d2.mode = MODE_List;
|
||||
d2.escape = '\t';
|
||||
strcpy(d2.separator,"\t");
|
||||
fprintf(pData->out, "COPY '%s' FROM STDIN;\n", azArg[0]);
|
||||
sprintf(zSql, "SELECT * FROM '%s'", azArg[0]);
|
||||
sqlite_exec(pData->db, zSql, callback, &d2, 0);
|
||||
sqlite_exec_printf(pData->db,
|
||||
"SELECT * FROM '%q'",
|
||||
callback, &d2, 0, azArg[0]
|
||||
);
|
||||
fprintf(pData->out, "\\.\n");
|
||||
}
|
||||
fprintf(pData->out, "VACUUM '%s';\n", azArg[0]);
|
||||
@@ -388,8 +419,8 @@ static char zHelp[] =
|
||||
".header ON|OFF Turn display of headers on or off\n"
|
||||
".help Show this message\n"
|
||||
".indices TABLE Show names of all indices on TABLE\n"
|
||||
".mode MODE Set mode to one of \"line\", \"column\", "
|
||||
"\"list\", or \"html\"\n"
|
||||
".mode MODE Set mode to one of \"line\", \"column\", \n"
|
||||
" \"insert\", \"list\", or \"html\"\n"
|
||||
".mode insert TABLE Generate SQL insert statements for TABLE\n"
|
||||
".output FILENAME Send output to FILENAME\n"
|
||||
".output stdout Send output to the screen\n"
|
||||
@@ -433,22 +464,24 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){
|
||||
if( nArg==0 ) return;
|
||||
n = strlen(azArg[0]);
|
||||
c = azArg[0][0];
|
||||
|
||||
if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
|
||||
char *zErrMsg = 0;
|
||||
char zSql[1000];
|
||||
if( nArg==1 ){
|
||||
sprintf(zSql, "SELECT name, type, sql FROM sqlite_master "
|
||||
"WHERE type!='meta' "
|
||||
"ORDER BY tbl_name, type DESC, name");
|
||||
sqlite_exec(db, zSql, dump_callback, p, &zErrMsg);
|
||||
sqlite_exec(db,
|
||||
"SELECT name, type, sql FROM sqlite_master "
|
||||
"WHERE type!='meta' "
|
||||
"ORDER BY tbl_name, type DESC, name",
|
||||
dump_callback, p, &zErrMsg
|
||||
);
|
||||
}else{
|
||||
int i;
|
||||
for(i=1; i<nArg && zErrMsg==0; i++){
|
||||
sprintf(zSql, "SELECT name, type, sql FROM sqlite_master "
|
||||
"WHERE tbl_name LIKE '%.800s' AND type!='meta' "
|
||||
"ORDER BY type DESC, name", azArg[i]);
|
||||
sqlite_exec(db, zSql, dump_callback, p, &zErrMsg);
|
||||
sqlite_exec_printf(db,
|
||||
"SELECT name, type, sql FROM sqlite_master "
|
||||
"WHERE tbl_name LIKE '%q' AND type!='meta' "
|
||||
"ORDER BY type DESC, name",
|
||||
dump_callback, p, &zErrMsg, azArg[i]
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -494,14 +527,15 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){
|
||||
if( c=='i' && strncmp(azArg[0], "indices", n)==0 && nArg>1 ){
|
||||
struct callback_data data;
|
||||
char *zErrMsg = 0;
|
||||
char zSql[1000];
|
||||
memcpy(&data, p, sizeof(data));
|
||||
data.showHeader = 0;
|
||||
data.mode = MODE_List;
|
||||
sprintf(zSql, "SELECT name FROM sqlite_master "
|
||||
"WHERE type='index' AND tbl_name LIKE '%.800s' "
|
||||
"ORDER BY name", azArg[1]);
|
||||
sqlite_exec(db, zSql, callback, &data, &zErrMsg);
|
||||
sqlite_exec_printf(db,
|
||||
"SELECT name FROM sqlite_master "
|
||||
"WHERE type='index' AND tbl_name LIKE '%q' "
|
||||
"ORDER BY name",
|
||||
callback, &data, &zErrMsg, azArg[1]
|
||||
);
|
||||
if( zErrMsg ){
|
||||
fprintf(stderr,"Error: %s\n", zErrMsg);
|
||||
free(zErrMsg);
|
||||
@@ -546,21 +580,37 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){
|
||||
if( c=='s' && strncmp(azArg[0], "schema", n)==0 ){
|
||||
struct callback_data data;
|
||||
char *zErrMsg = 0;
|
||||
char zSql[1000];
|
||||
memcpy(&data, p, sizeof(data));
|
||||
data.showHeader = 0;
|
||||
data.mode = MODE_List;
|
||||
data.mode = MODE_Semi;
|
||||
if( nArg>1 ){
|
||||
sprintf(zSql, "SELECT sql FROM sqlite_master "
|
||||
"WHERE tbl_name LIKE '%.800s' AND type!='meta'"
|
||||
"ORDER BY type DESC, name",
|
||||
azArg[1]);
|
||||
if( sqliteStrICmp(azArg[1],"sqlite_master")==0 ){
|
||||
char *new_argv[2], *new_colv[2];
|
||||
new_argv[0] = "CREATE TABLE sqlite_master (\n"
|
||||
" type text,\n"
|
||||
" name text,\n"
|
||||
" tbl_name text,\n"
|
||||
" sql text\n"
|
||||
")";
|
||||
new_argv[1] = 0;
|
||||
new_colv[0] = "sql";
|
||||
new_colv[1] = 0;
|
||||
callback(&data, 1, new_argv, new_colv);
|
||||
}else{
|
||||
sqlite_exec_printf(db,
|
||||
"SELECT sql FROM sqlite_master "
|
||||
"WHERE tbl_name LIKE '%q' AND type!='meta'"
|
||||
"ORDER BY type DESC, name",
|
||||
callback, &data, &zErrMsg, azArg[1]);
|
||||
}
|
||||
}else{
|
||||
sprintf(zSql, "SELECT sql FROM sqlite_master "
|
||||
sqlite_exec(db,
|
||||
"SELECT sql FROM sqlite_master "
|
||||
"WHERE type!='meta' "
|
||||
"ORDER BY tbl_name, type DESC, name");
|
||||
"ORDER BY tbl_name, type DESC, name",
|
||||
callback, &data, &zErrMsg
|
||||
);
|
||||
}
|
||||
sqlite_exec(db, zSql, callback, &data, &zErrMsg);
|
||||
if( zErrMsg ){
|
||||
fprintf(stderr,"Error: %s\n", zErrMsg);
|
||||
free(zErrMsg);
|
||||
@@ -572,28 +622,49 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){
|
||||
}else
|
||||
|
||||
if( c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0 ){
|
||||
struct callback_data data;
|
||||
char *zErrMsg = 0;
|
||||
char zSql[1000];
|
||||
memcpy(&data, p, sizeof(data));
|
||||
data.showHeader = 0;
|
||||
data.mode = MODE_List;
|
||||
char **azResult;
|
||||
int nRow, rc;
|
||||
char *zErrMsg;
|
||||
if( nArg==1 ){
|
||||
sprintf(zSql,
|
||||
rc = sqlite_get_table(db,
|
||||
"SELECT name FROM sqlite_master "
|
||||
"WHERE type='table' "
|
||||
"ORDER BY name");
|
||||
"ORDER BY name",
|
||||
&azResult, &nRow, 0, &zErrMsg
|
||||
);
|
||||
}else{
|
||||
sprintf(zSql,
|
||||
rc = sqlite_get_table_printf(db,
|
||||
"SELECT name FROM sqlite_master "
|
||||
"WHERE type='table' AND name LIKE '%%%.100s%%' "
|
||||
"ORDER BY name", azArg[1]);
|
||||
"WHERE type='table' AND name LIKE '%%%q%%' "
|
||||
"ORDER BY name",
|
||||
&azResult, &nRow, 0, &zErrMsg, azArg[1]
|
||||
);
|
||||
}
|
||||
sqlite_exec(db, zSql, callback, &data, &zErrMsg);
|
||||
if( zErrMsg ){
|
||||
fprintf(stderr,"Error: %s\n", zErrMsg);
|
||||
free(zErrMsg);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
int len, maxlen = 0;
|
||||
int i, j;
|
||||
int nPrintCol, nPrintRow;
|
||||
for(i=1; i<=nRow; i++){
|
||||
if( azResult[i]==0 ) continue;
|
||||
len = strlen(azResult[i]);
|
||||
if( len>maxlen ) maxlen = len;
|
||||
}
|
||||
nPrintCol = 80/(maxlen+2);
|
||||
if( nPrintCol<1 ) nPrintCol = 1;
|
||||
nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
|
||||
for(i=0; i<nPrintRow; i++){
|
||||
for(j=i+1; j<=nRow; j+=nPrintRow){
|
||||
char *zSp = j<=nPrintRow ? "" : " ";
|
||||
printf("%s%-*s", zSp, maxlen, azResult[j] ? azResult[j] : "");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
sqlite_free_table(azResult);
|
||||
}else
|
||||
|
||||
if( c=='t' && n>1 && strncmp(azArg[0], "timeout", n)==0 && nArg>=2 ){
|
||||
@@ -614,15 +685,18 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){
|
||||
}
|
||||
|
||||
int main(int argc, char **argv){
|
||||
sqlite *db;
|
||||
char *zErrMsg = 0;
|
||||
char *argv0 = argv[0];
|
||||
struct callback_data data;
|
||||
int echo = 0;
|
||||
|
||||
memset(&data, 0, sizeof(data));
|
||||
data.mode = MODE_List;
|
||||
strcpy(data.separator,"|");
|
||||
data.showHeader = 0;
|
||||
#ifdef SIGINT
|
||||
signal(SIGINT, interrupt_handler);
|
||||
#endif
|
||||
while( argc>=2 && argv[1][0]=='-' ){
|
||||
if( strcmp(argv[1],"-html")==0 ){
|
||||
data.mode = MODE_Html;
|
||||
@@ -648,6 +722,10 @@ int main(int argc, char **argv){
|
||||
data.showHeader = 0;
|
||||
argc--;
|
||||
argv++;
|
||||
}else if( strcmp(argv[1],"-echo")==0 ){
|
||||
echo = 1;
|
||||
argc--;
|
||||
argv++;
|
||||
}else{
|
||||
fprintf(stderr,"%s: unknown option: %s\n", argv0, argv[1]);
|
||||
return 1;
|
||||
@@ -668,7 +746,7 @@ int main(int argc, char **argv){
|
||||
}
|
||||
exit(1);
|
||||
}else{
|
||||
printf("Database \"%s\" opened READ ONLY!\n", argv[1]);
|
||||
fprintf(stderr,"Database \"%s\" opened READ ONLY!\n", argv[1]);
|
||||
}
|
||||
}
|
||||
data.out = stdout;
|
||||
@@ -684,19 +762,26 @@ int main(int argc, char **argv){
|
||||
int istty = isatty(0);
|
||||
if( istty ){
|
||||
printf(
|
||||
"Enter \".help\" for instructions\n"
|
||||
"SQLite version %s\n"
|
||||
"Enter \".help\" for instructions\n",
|
||||
sqlite_version
|
||||
);
|
||||
}
|
||||
while( (zLine = one_input_line(zSql, istty))!=0 ){
|
||||
if( echo ) printf("%s\n", zLine);
|
||||
if( zLine && zLine[0]=='.' ){
|
||||
do_meta_command(zLine, db, &data);
|
||||
free(zLine);
|
||||
continue;
|
||||
}
|
||||
if( zSql==0 ){
|
||||
nSql = strlen(zLine);
|
||||
zSql = malloc( nSql+1 );
|
||||
strcpy(zSql, zLine);
|
||||
int i;
|
||||
for(i=0; zLine[i] && isspace(zLine[i]); i++){}
|
||||
if( zLine[i]!=0 ){
|
||||
nSql = strlen(zLine);
|
||||
zSql = malloc( nSql+1 );
|
||||
strcpy(zSql, zLine);
|
||||
}
|
||||
}else{
|
||||
int len = strlen(zLine);
|
||||
zSql = realloc( zSql, nSql + len + 2 );
|
||||
@@ -709,10 +794,11 @@ int main(int argc, char **argv){
|
||||
nSql += len;
|
||||
}
|
||||
free(zLine);
|
||||
if( sqlite_complete(zSql) ){
|
||||
if( zSql && sqlite_complete(zSql) ){
|
||||
data.cnt = 0;
|
||||
if( sqlite_exec(db, zSql, callback, &data, &zErrMsg)!=0
|
||||
&& zErrMsg!=0 ){
|
||||
if( !istty && !echo ) printf("%s\n",zSql);
|
||||
printf("SQL error: %s\n", zErrMsg);
|
||||
free(zErrMsg);
|
||||
zErrMsg = 0;
|
||||
|
||||
+157
-7
@@ -24,16 +24,23 @@
|
||||
** This header file defines the interface that the sqlite library
|
||||
** presents to client programs.
|
||||
**
|
||||
** @(#) $Id: sqlite.h.in,v 1.1 2000/08/17 10:22:34 drh Exp $
|
||||
** @(#) $Id: sqlite.h.in,v 1.9 2001/01/20 19:52:49 drh Exp $
|
||||
*/
|
||||
#ifndef _SQLITE_H_
|
||||
#define _SQLITE_H_
|
||||
#include <stdarg.h> /* Needed for the definition of va_list */
|
||||
|
||||
/*
|
||||
** The version of the SQLite library
|
||||
** The version of the SQLite library.
|
||||
*/
|
||||
#define SQLITE_VERSION --VERS--
|
||||
#define SQLITE_VERSION_STRING "--VERS--"
|
||||
#define SQLITE_VERSION "--VERS--"
|
||||
|
||||
/*
|
||||
** The version string is also compiled into the library so that a program
|
||||
** can check to make sure that the lib*.a file and the *.h file are from
|
||||
** the same version.
|
||||
*/
|
||||
extern const char sqlite_version[];
|
||||
|
||||
/*
|
||||
** Each open sqlite database is represented by an instance of the
|
||||
@@ -120,16 +127,30 @@ int sqlite_exec(
|
||||
);
|
||||
|
||||
/*
|
||||
** Return values fro sqlite_exec()
|
||||
** Return values for sqlite_exec()
|
||||
*/
|
||||
#define SQLITE_OK 0 /* Successful result */
|
||||
#define SQLITE_INTERNAL 1 /* An internal logic error in SQLite */
|
||||
#define SQLITE_ERROR 2 /* SQL error or missing database */
|
||||
#define SQLITE_ERROR 1 /* SQL error or missing database */
|
||||
#define SQLITE_INTERNAL 2 /* An internal logic error in SQLite */
|
||||
#define SQLITE_PERM 3 /* Access permission denied */
|
||||
#define SQLITE_ABORT 4 /* Callback routine requested an abort */
|
||||
#define SQLITE_BUSY 5 /* One or more database files are locked */
|
||||
#define SQLITE_NOMEM 6 /* A malloc() failed */
|
||||
#define SQLITE_READONLY 7 /* Attempt to write a readonly database */
|
||||
#define SQLITE_INTERRUPT 8 /* Operation terminated by sqlite_interrupt() */
|
||||
#define SQLITE_IOERR 9 /* Disk full or other I/O error */
|
||||
#define SQLITE_CORRUPT 10 /* The database disk image is malformed */
|
||||
#define SQLITE_NOTFOUND 11 /* Table or record not found */
|
||||
#define SQLITE_FULL 12 /* Insertion failed because database is full */
|
||||
|
||||
/* This function causes any pending database operation to abort and
|
||||
** return at its earliest opportunity. This routine is typically
|
||||
** called in response to a user action such as pressing "Cancel"
|
||||
** or Ctrl-C where the user wants a long query operation to halt
|
||||
** immediately.
|
||||
*/
|
||||
void sqlite_interrupt(sqlite*);
|
||||
|
||||
|
||||
/* This function returns true if the given input string comprises
|
||||
** one or more complete SQL statements.
|
||||
@@ -176,4 +197,133 @@ void sqlite_busy_handler(sqlite*, int(*)(void*,const char*,int), void*);
|
||||
*/
|
||||
void sqlite_busy_timeout(sqlite*, int ms);
|
||||
|
||||
/*
|
||||
** This next routine is really just a wrapper around sqlite_exec().
|
||||
** Instead of invoking a user-supplied callback for each row of the
|
||||
** result, this routine remembers each row of the result in memory
|
||||
** obtained from malloc(), then returns all of the result after the
|
||||
** query has finished.
|
||||
**
|
||||
** As an example, suppose the query result where this table:
|
||||
**
|
||||
** Name | Age
|
||||
** -----------------------
|
||||
** Alice | 43
|
||||
** Bob | 28
|
||||
** Cindy | 21
|
||||
**
|
||||
** If the 3rd argument were &azResult then after the function returns
|
||||
** azResult will contain the following data:
|
||||
**
|
||||
** azResult[0] = "Name";
|
||||
** azResult[1] = "Age";
|
||||
** azResult[2] = "Alice";
|
||||
** azResult[3] = "43";
|
||||
** azResult[4] = "Bob";
|
||||
** azResult[5] = "28";
|
||||
** azResult[6] = "Cindy";
|
||||
** azResult[7] = "21";
|
||||
**
|
||||
** Notice that there is an extra row of data containing the column
|
||||
** headers. But the *nrow return value is still 3. *ncolumn is
|
||||
** set to 2. In general, the number of values inserted into azResult
|
||||
** will be ((*nrow) + 1)*(*ncolumn).
|
||||
**
|
||||
** After the calling function has finished using the result, it should
|
||||
** pass the result data pointer to sqlite_free_table() in order to
|
||||
** release the memory that was malloc-ed. Because of the way the
|
||||
** malloc() happens, the calling function must not try to call
|
||||
** malloc() directly. Only sqlite_free_table() is able to release
|
||||
** the memory properly and safely.
|
||||
**
|
||||
** The return value of this routine is the same as from sqlite_exec().
|
||||
*/
|
||||
int sqlite_get_table(
|
||||
sqlite*, /* An open database */
|
||||
char *sql, /* SQL to be executed */
|
||||
char ***resultp, /* Result written to a char *[] that this points to */
|
||||
int *nrow, /* Number of result rows written here */
|
||||
int *ncolumn, /* Number of result columns written here */
|
||||
char **errmsg /* Error msg written here */
|
||||
);
|
||||
|
||||
/*
|
||||
** Call this routine to free the memory that sqlite_get_table() allocated.
|
||||
*/
|
||||
void sqlite_free_table(char **result);
|
||||
|
||||
/*
|
||||
** The following routines are wrappers around sqlite_exec() and
|
||||
** sqlite_get_table(). The only difference between the routines that
|
||||
** follow and the originals is that the second argument to the
|
||||
** routines that follow is really a printf()-style format
|
||||
** string describing the SQL to be executed. Arguments to the format
|
||||
** string appear at the end of the argument list.
|
||||
**
|
||||
** All of the usual printf formatting options apply. In addition, there
|
||||
** is a "%q" option. %q works like %s in that it substitutes a null-terminated
|
||||
** string from the argument list. But %q also doubles every '\'' character.
|
||||
** %q is designed for use inside a string literal. By doubling each '\''
|
||||
** character it escapes that character and allows it to be inserted into
|
||||
** the string.
|
||||
**
|
||||
** For example, so some string variable contains text as follows:
|
||||
**
|
||||
** char *zText = "It's a happy day!";
|
||||
**
|
||||
** We can use this text in an SQL statement as follows:
|
||||
**
|
||||
** sqlite_exec_printf(db, "INSERT INTO table VALUES('%q')",
|
||||
** callback1, 0, 0, zText);
|
||||
**
|
||||
** Because the %q format string is used, the '\'' character in zText
|
||||
** is escaped and the SQL generated is as follows:
|
||||
**
|
||||
** INSERT INTO table1 VALUES('It''s a happy day!')
|
||||
**
|
||||
** This is correct. Had we used %s instead of %q, the generated SQL
|
||||
** would have looked like this:
|
||||
**
|
||||
** INSERT INTO table1 VALUES('It's a happy day!');
|
||||
**
|
||||
** This second example is an SQL syntax error. As a general rule you
|
||||
** should always use %q instead of %s when inserting text into a string
|
||||
** literal.
|
||||
*/
|
||||
int sqlite_exec_printf(
|
||||
sqlite*, /* An open database */
|
||||
char *sqlFormat, /* printf-style format string for the SQL */
|
||||
sqlite_callback, /* Callback function */
|
||||
void *, /* 1st argument to callback function */
|
||||
char **errmsg, /* Error msg written here */
|
||||
... /* Arguments to the format string. */
|
||||
);
|
||||
int sqlite_exec_vprintf(
|
||||
sqlite*, /* An open database */
|
||||
char *sqlFormat, /* printf-style format string for the SQL */
|
||||
sqlite_callback, /* Callback function */
|
||||
void *, /* 1st argument to callback function */
|
||||
char **errmsg, /* Error msg written here */
|
||||
va_list ap /* Arguments to the format string. */
|
||||
);
|
||||
int sqlite_get_table_printf(
|
||||
sqlite*, /* An open database */
|
||||
char *sqlFormat, /* printf-style format string for the SQL */
|
||||
char ***resultp, /* Result written to a char *[] that this points to */
|
||||
int *nrow, /* Number of result rows written here */
|
||||
int *ncolumn, /* Number of result columns written here */
|
||||
char **errmsg, /* Error msg written here */
|
||||
... /* Arguments to the format string */
|
||||
);
|
||||
int sqlite_get_table_vprintf(
|
||||
sqlite*, /* An open database */
|
||||
char *sqlFormat, /* printf-style format string for the SQL */
|
||||
char ***resultp, /* Result written to a char *[] that this points to */
|
||||
int *nrow, /* Number of result rows written here */
|
||||
int *ncolumn, /* Number of result columns written here */
|
||||
char **errmsg, /* Error msg written here */
|
||||
va_list ap /* Arguments to the format string */
|
||||
);
|
||||
|
||||
|
||||
#endif /* _SQLITE_H_ */
|
||||
|
||||
+28
-11
@@ -23,7 +23,7 @@
|
||||
*************************************************************************
|
||||
** Internal interface definitions for SQLite.
|
||||
**
|
||||
** @(#) $Id: sqliteInt.h,v 1.29 2000/08/02 13:47:42 drh Exp $
|
||||
** @(#) $Id: sqliteInt.h,v 1.36 2001/01/20 19:52:50 drh Exp $
|
||||
*/
|
||||
#include "sqlite.h"
|
||||
#include "dbbe.h"
|
||||
@@ -35,6 +35,11 @@
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
/*
|
||||
** The paging system deals with 32-bit integers.
|
||||
*/
|
||||
typedef unsigned int u32;
|
||||
|
||||
/*
|
||||
** If memory allocation problems are found, recompile with
|
||||
**
|
||||
@@ -62,17 +67,18 @@
|
||||
|
||||
/*
|
||||
** The following global variables are used for testing and debugging
|
||||
** only. Thy only work if MEMORY_DEBUG is defined.
|
||||
** only. They only work if MEMORY_DEBUG is defined.
|
||||
*/
|
||||
#ifdef MEMORY_DEBUG
|
||||
int sqlite_nMalloc; /* Number of sqliteMalloc() calls */
|
||||
int sqlite_nFree; /* Number of sqliteFree() calls */
|
||||
int sqlite_iMallocFail; /* Fail sqliteMalloc() after this many calls */
|
||||
extern int sqlite_nMalloc; /* Number of sqliteMalloc() calls */
|
||||
extern int sqlite_nFree; /* Number of sqliteFree() calls */
|
||||
extern int sqlite_iMallocFail; /* Fail sqliteMalloc() after this many calls */
|
||||
#endif
|
||||
|
||||
/*
|
||||
** The number of entries in the in-memory hash array holding the
|
||||
** database schema.
|
||||
** database schema. (Collision resolution is by chaining, so the
|
||||
** table will hold more than this many entries.)
|
||||
*/
|
||||
#define N_HASH 51
|
||||
|
||||
@@ -99,6 +105,8 @@ int sqlite_iMallocFail; /* Fail sqliteMalloc() after this many calls */
|
||||
#define FN_Sum 4
|
||||
#define FN_Avg 5
|
||||
#define FN_Fcnt 6
|
||||
#define FN_Length 7
|
||||
#define FN_Substr 8
|
||||
|
||||
/*
|
||||
** Forward references to structures
|
||||
@@ -121,7 +129,7 @@ typedef struct AggExpr AggExpr;
|
||||
*/
|
||||
struct sqlite {
|
||||
Dbbe *pBe; /* The backend driver */
|
||||
int flags; /* Miscellanous flags */
|
||||
int flags; /* Miscellanous flags. See below */
|
||||
int file_format; /* What file format version is this database? */
|
||||
int nTable; /* Number of tables in the database */
|
||||
void *pBusyArg; /* 1st Argument to the busy callback */
|
||||
@@ -133,8 +141,9 @@ struct sqlite {
|
||||
/*
|
||||
** Possible values for the sqlite.flags.
|
||||
*/
|
||||
#define SQLITE_VdbeTrace 0x00000001
|
||||
#define SQLITE_Initialized 0x00000002
|
||||
#define SQLITE_VdbeTrace 0x00000001 /* True to trace VDBE execution */
|
||||
#define SQLITE_Initialized 0x00000002 /* True after initialization */
|
||||
#define SQLITE_Interrupt 0x00000004 /* Cancel current operation */
|
||||
|
||||
/*
|
||||
** Current file format version
|
||||
@@ -165,7 +174,7 @@ struct Table {
|
||||
};
|
||||
|
||||
/*
|
||||
** Each SQL index is represented in memory by and
|
||||
** Each SQL index is represented in memory by an
|
||||
** instance of the following structure.
|
||||
**
|
||||
** The columns of the table that are to be indexed are described
|
||||
@@ -198,7 +207,7 @@ struct Index {
|
||||
** this structure.
|
||||
*/
|
||||
struct Token {
|
||||
char *z; /* Text of the token */
|
||||
char *z; /* Text of the token. Not NULL-terminated! */
|
||||
int n; /* Number of characters in this token */
|
||||
};
|
||||
|
||||
@@ -323,6 +332,7 @@ struct AggExpr {
|
||||
*/
|
||||
struct Parse {
|
||||
sqlite *db; /* The main database structure */
|
||||
int rc; /* Return code from execution */
|
||||
sqlite_callback xCallback; /* The callback function */
|
||||
void *pArg; /* First argument to the callback function */
|
||||
char *zErrMsg; /* An error message */
|
||||
@@ -414,3 +424,10 @@ void sqliteExprResolveInSelect(Parse*, Expr*);
|
||||
int sqliteExprAnalyzeAggregates(Parse*, Expr*);
|
||||
void sqliteParseInfoReset(Parse*);
|
||||
Vdbe *sqliteGetVdbe(Parse*);
|
||||
int sqliteRandomByte(void);
|
||||
int sqliteRandomInteger(void);
|
||||
void sqliteRandomName(char*,char*);
|
||||
int sqliteDbbeOpenTempFile(const char*, Dbbe*, FILE**);
|
||||
void sqliteDbbeCloseTempFile(Dbbe*, FILE*);
|
||||
void sqliteDbbeCloseAllTempFiles(Dbbe*);
|
||||
char *sqliteDbbeNameToFile(const char*,const char*,const char*);
|
||||
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
** Copyright (c) 2000 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains the sqlite_get_table() and sqlite_free_table()
|
||||
** interface routines. These are just wrappers around the main
|
||||
** interface routine of sqlite_exec().
|
||||
**
|
||||
** These routines are in a separate files so that they will not be linked
|
||||
** if they are not used.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include "sqlite.h"
|
||||
|
||||
/*
|
||||
** This structure is used to pass data from sqlite_get_table() through
|
||||
** to the callback function is uses to build the result.
|
||||
*/
|
||||
typedef struct TabResult {
|
||||
char **azResult;
|
||||
int nResult;
|
||||
int nAlloc;
|
||||
int nRow;
|
||||
int nColumn;
|
||||
int nData;
|
||||
int rc;
|
||||
} TabResult;
|
||||
|
||||
/*
|
||||
** This routine is called once for each row in the result table. Its job
|
||||
** is to fill in the TabResult structure appropriately, allocating new
|
||||
** memory as necessary.
|
||||
*/
|
||||
static int sqlite_get_table_cb(void *pArg, int nCol, char **argv, char **colv){
|
||||
TabResult *p = (TabResult*)pArg;
|
||||
int need;
|
||||
int i;
|
||||
char *z;
|
||||
|
||||
/* Make sure there is enough space in p->azResult to hold everything
|
||||
** we need to remember from this invocation of the callback.
|
||||
*/
|
||||
if( p->nRow==0 ){
|
||||
p->nColumn = nCol;
|
||||
need = nCol*2;
|
||||
}else{
|
||||
need = nCol;
|
||||
}
|
||||
if( p->nData + need >= p->nAlloc ){
|
||||
p->nAlloc = p->nAlloc*2 + need + 1;
|
||||
p->azResult = realloc( p->azResult, sizeof(char*)*p->nAlloc );
|
||||
if( p->azResult==0 ){
|
||||
p->rc = SQLITE_NOMEM;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* If this is the first row, then generate an extra row containing
|
||||
** the names of all columns.
|
||||
*/
|
||||
if( p->nRow==0 ){
|
||||
for(i=0; i<nCol; i++){
|
||||
if( colv[i]==0 ){
|
||||
z = 0;
|
||||
}else{
|
||||
z = malloc( strlen(colv[i])+1 );
|
||||
if( z==0 ){
|
||||
p->rc = SQLITE_NOMEM;
|
||||
return 1;
|
||||
}
|
||||
strcpy(z, colv[i]);
|
||||
}
|
||||
p->azResult[p->nData++] = z;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy over the row data
|
||||
*/
|
||||
for(i=0; i<nCol; i++){
|
||||
if( argv[i]==0 ){
|
||||
z = 0;
|
||||
}else{
|
||||
z = malloc( strlen(argv[i])+1 );
|
||||
if( z==0 ){
|
||||
p->rc = SQLITE_NOMEM;
|
||||
return 1;
|
||||
}
|
||||
strcpy(z, argv[i]);
|
||||
}
|
||||
p->azResult[p->nData++] = z;
|
||||
}
|
||||
p->nRow++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Query the database. But instead of invoking a callback for each row,
|
||||
** malloc() for space to hold the result and return the entire results
|
||||
** at the conclusion of the call.
|
||||
**
|
||||
** The result that is written to ***pazResult is held in memory obtained
|
||||
** from malloc(). But the caller cannot free this memory directly.
|
||||
** Instead, the entire table should be passed to sqlite_free_table() when
|
||||
** the calling procedure is finished using it.
|
||||
*/
|
||||
int sqlite_get_table(
|
||||
sqlite *db, /* The database on which the SQL executes */
|
||||
char *zSql, /* The SQL to be executed */
|
||||
char ***pazResult, /* Write the result table here */
|
||||
int *pnRow, /* Write the number of rows in the result here */
|
||||
int *pnColumn, /* Write the number of columns of result here */
|
||||
char **pzErrMsg /* Write error messages here */
|
||||
){
|
||||
int rc;
|
||||
TabResult res;
|
||||
if( pazResult==0 ){ return SQLITE_ERROR; }
|
||||
*pazResult = 0;
|
||||
if( pnColumn ) *pnColumn = 0;
|
||||
if( pnRow ) *pnRow = 0;
|
||||
res.nResult = 0;
|
||||
res.nRow = 0;
|
||||
res.nColumn = 0;
|
||||
res.nData = 1;
|
||||
res.nAlloc = 200;
|
||||
res.rc = SQLITE_OK;
|
||||
res.azResult = malloc( sizeof(char*)*res.nAlloc );
|
||||
if( res.azResult==0 ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
res.azResult[0] = 0;
|
||||
rc = sqlite_exec(db, zSql, sqlite_get_table_cb, &res, pzErrMsg);
|
||||
if( res.azResult ){
|
||||
res.azResult[0] = (char*)res.nData;
|
||||
}
|
||||
if( rc==SQLITE_ABORT ){
|
||||
sqlite_free_table(&res.azResult[1]);
|
||||
return res.rc;
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite_free_table(&res.azResult[1]);
|
||||
return rc;
|
||||
}
|
||||
if( res.nAlloc>res.nData ){
|
||||
res.azResult = realloc( res.azResult, sizeof(char*)*(res.nData+1) );
|
||||
if( res.azResult==0 ) return SQLITE_NOMEM;
|
||||
}
|
||||
*pazResult = &res.azResult[1];
|
||||
if( pnColumn ) *pnColumn = res.nColumn;
|
||||
if( pnRow ) *pnRow = res.nRow;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This routine frees the space the sqlite_get_table() malloced.
|
||||
*/
|
||||
void sqlite_free_table(
|
||||
char **azResult /* Result returned from from sqlite_get_table() */
|
||||
){
|
||||
if( azResult ){
|
||||
int i, n;
|
||||
azResult--;
|
||||
n = (int)azResult[0];
|
||||
for(i=1; i<n; i++){ if( azResult[i] ) free(azResult[i]); }
|
||||
free(azResult);
|
||||
}
|
||||
}
|
||||
+111
-63
@@ -23,10 +23,12 @@
|
||||
*************************************************************************
|
||||
** A TCL Interface to SQLite
|
||||
**
|
||||
** $Id: tclsqlite.c,v 1.8 2000/08/17 09:50:00 drh Exp $
|
||||
** $Id: tclsqlite.c,v 1.13 2001/01/31 13:28:09 drh Exp $
|
||||
*/
|
||||
#ifndef NO_TCL /* Omit this whole file if TCL is unavailable */
|
||||
|
||||
#include "sqlite.h"
|
||||
#include <tcl.h>
|
||||
#include "tcl.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -38,7 +40,7 @@ typedef struct SqliteDb SqliteDb;
|
||||
struct SqliteDb {
|
||||
sqlite *db; /* The "real" database structure */
|
||||
Tcl_Interp *interp; /* The interpreter used for this database */
|
||||
char *zBusy; /* The name of the busy callback routine */
|
||||
char *zBusy; /* The busy callback routine */
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -49,7 +51,7 @@ typedef struct CallbackData CallbackData;
|
||||
struct CallbackData {
|
||||
Tcl_Interp *interp; /* The TCL interpreter */
|
||||
char *zArray; /* The array into which data is written */
|
||||
char *zCode; /* The code to execute for each row */
|
||||
Tcl_Obj *pCode; /* The code to execute for each row */
|
||||
int once; /* Set only for the first invocation of callback */
|
||||
};
|
||||
|
||||
@@ -66,6 +68,7 @@ static int DbEvalCallback(
|
||||
int i, rc;
|
||||
if( cbData->zArray[0] ){
|
||||
if( cbData->once ){
|
||||
Tcl_SetVar2(cbData->interp, cbData->zArray, "*", "", 0);
|
||||
for(i=0; i<nCol; i++){
|
||||
Tcl_SetVar2(cbData->interp, cbData->zArray, "*", azN[i],
|
||||
TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
|
||||
@@ -84,8 +87,34 @@ static int DbEvalCallback(
|
||||
}
|
||||
}
|
||||
cbData->once = 0;
|
||||
rc = Tcl_Eval(cbData->interp, cbData->zCode);
|
||||
return rc;
|
||||
rc = Tcl_EvalObj(cbData->interp, cbData->pCode);
|
||||
return rc!=TCL_OK && rc!=TCL_CONTINUE;
|
||||
}
|
||||
|
||||
/*
|
||||
** This is an alternative callback for database queries. Instead
|
||||
** of invoking a TCL script to handle the result, this callback just
|
||||
** appends each column of the result to a list. After the query
|
||||
** is complete, the list is returned.
|
||||
*/
|
||||
static int DbEvalCallback2(
|
||||
void *clientData, /* An instance of CallbackData */
|
||||
int nCol, /* Number of columns in the result */
|
||||
char ** azCol, /* Data for each column */
|
||||
char ** azN /* Name for each column */
|
||||
){
|
||||
Tcl_Obj *pList = (Tcl_Obj*)clientData;
|
||||
int i;
|
||||
for(i=0; i<nCol; i++){
|
||||
Tcl_Obj *pElem;
|
||||
if( azCol[i] && *azCol[i] ){
|
||||
pElem = Tcl_NewStringObj(azCol[i], -1);
|
||||
}else{
|
||||
pElem = Tcl_NewObj();
|
||||
}
|
||||
Tcl_ListObjAppendElement(0, pList, pElem);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -109,7 +138,6 @@ static int DbBusyHandler(void *cd, const char *zTable, int nTries){
|
||||
int rc;
|
||||
char zVal[30];
|
||||
char *zCmd;
|
||||
char *zResult;
|
||||
Tcl_DString cmd;
|
||||
|
||||
Tcl_DStringInit(&cmd);
|
||||
@@ -139,58 +167,70 @@ static int DbBusyHandler(void *cd, const char *zTable, int nTries){
|
||||
** and calls that connection "db1". The second command causes this
|
||||
** subroutine to be invoked.
|
||||
*/
|
||||
static int DbCmd(void *cd, Tcl_Interp *interp, int argc, char **argv){
|
||||
char *z;
|
||||
int n, c;
|
||||
static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
||||
SqliteDb *pDb = (SqliteDb*)cd;
|
||||
if( argc<2 ){
|
||||
Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
|
||||
" SUBCOMMAND ...\"", 0);
|
||||
int choice;
|
||||
static char *DB_optStrs[] = {
|
||||
"busy", "close", "complete", "eval", "timeout"
|
||||
};
|
||||
enum DB_opts {
|
||||
DB_BUSY, DB_CLOSE, DB_COMPLETE, DB_EVAL, DB_TIMEOUT
|
||||
};
|
||||
|
||||
if( objc<2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
z = argv[1];
|
||||
n = strlen(z);
|
||||
c = z[0];
|
||||
if( Tcl_GetIndexFromObj(interp, objv[1], DB_optStrs, "option", 0, &choice) ){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
switch( (enum DB_opts)choice ){
|
||||
|
||||
/* $db busy ?CALLBACK?
|
||||
**
|
||||
** Invoke the given callback if an SQL statement attempts to open
|
||||
** a locked database file.
|
||||
*/
|
||||
if( c=='b' && strncmp(z,"busy",n)==0 ){
|
||||
if( argc>3 ){
|
||||
Tcl_AppendResult(interp,"wrong # args: should be \"",
|
||||
argv[0], " busy ?CALLBACK?", 0);
|
||||
case DB_BUSY: {
|
||||
if( objc>3 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "CALLBACK");
|
||||
return TCL_ERROR;
|
||||
}else if( argc==2 ){
|
||||
}else if( objc==2 ){
|
||||
if( pDb->zBusy ){
|
||||
Tcl_AppendResult(interp, pDb->zBusy, 0);
|
||||
}
|
||||
}else{
|
||||
char *zBusy;
|
||||
int len;
|
||||
if( pDb->zBusy ){
|
||||
Tcl_Free(pDb->zBusy);
|
||||
pDb->zBusy = 0;
|
||||
}
|
||||
if( argv[2][0] ){
|
||||
pDb->zBusy = Tcl_Alloc( strlen(argv[2]) + 1 );
|
||||
if( pDb->zBusy ){
|
||||
strcpy(pDb->zBusy, argv[2]);
|
||||
}
|
||||
zBusy = Tcl_GetStringFromObj(objv[2], &len);
|
||||
if( zBusy && len>0 ){
|
||||
pDb->zBusy = Tcl_Alloc( len + 1 );
|
||||
strcpy(pDb->zBusy, zBusy);
|
||||
}else{
|
||||
pDb->zBusy = 0;
|
||||
}
|
||||
if( pDb->zBusy ){
|
||||
pDb->interp = interp;
|
||||
sqlite_busy_handler(pDb->db, DbBusyHandler, pDb);
|
||||
}else{
|
||||
sqlite_busy_handler(pDb->db, 0, 0);
|
||||
}
|
||||
}
|
||||
}else
|
||||
break;
|
||||
}
|
||||
|
||||
/* $db close
|
||||
**
|
||||
** Shutdown the database
|
||||
*/
|
||||
if( c=='c' && n>=2 && strncmp(z,"close",n)==0 ){
|
||||
Tcl_DeleteCommand(interp, argv[0]);
|
||||
}else
|
||||
case DB_CLOSE: {
|
||||
Tcl_DeleteCommand(interp, Tcl_GetStringFromObj(objv[0], 0));
|
||||
break;
|
||||
}
|
||||
|
||||
/* $db complete SQL
|
||||
**
|
||||
@@ -198,16 +238,18 @@ static int DbCmd(void *cd, Tcl_Interp *interp, int argc, char **argv){
|
||||
** additional lines of input are needed. This is similar to the
|
||||
** built-in "info complete" command of Tcl.
|
||||
*/
|
||||
if( c=='c' && n>=2 && strncmp(z,"complete",n)==0 ){
|
||||
char *zRes;
|
||||
if( argc!=3 ){
|
||||
Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
|
||||
" complete SQL\"", 0);
|
||||
case DB_COMPLETE: {
|
||||
Tcl_Obj *pResult;
|
||||
int isComplete;
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "SQL");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
zRes = sqlite_complete(argv[2]) ? "1" : "0";
|
||||
Tcl_SetResult(interp, zRes, TCL_VOLATILE);
|
||||
}else
|
||||
isComplete = sqlite_complete( Tcl_GetStringFromObj(objv[2], 0) );
|
||||
pResult = Tcl_GetObjResult(interp);
|
||||
Tcl_SetBooleanObj(pResult, isComplete);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
** $db eval $sql ?array { ...code... }?
|
||||
@@ -218,57 +260,59 @@ static int DbCmd(void *cd, Tcl_Interp *interp, int argc, char **argv){
|
||||
** If "array" is an empty string, then the values are placed in variables
|
||||
** that have the same name as the fields extracted by the query.
|
||||
*/
|
||||
if( c=='e' && strncmp(z,"eval",n)==0 ){
|
||||
case DB_EVAL: {
|
||||
CallbackData cbData;
|
||||
char *zErrMsg;
|
||||
char *zSql;
|
||||
int rc;
|
||||
|
||||
if( argc!=5 && argc!=3 ){
|
||||
Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
|
||||
" eval SQL ?ARRAY-NAME CODE?", 0);
|
||||
if( objc!=5 && objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "SQL ?ARRAY-NAME CODE?");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
pDb->interp = interp;
|
||||
if( argc==5 ){
|
||||
zSql = Tcl_GetStringFromObj(objv[2], 0);
|
||||
Tcl_IncrRefCount(objv[2]);
|
||||
if( objc==5 ){
|
||||
cbData.interp = interp;
|
||||
cbData.once = 1;
|
||||
cbData.zArray = argv[3];
|
||||
cbData.zCode = argv[4];
|
||||
cbData.zArray = Tcl_GetStringFromObj(objv[3], 0);
|
||||
cbData.pCode = objv[4];
|
||||
zErrMsg = 0;
|
||||
rc = sqlite_exec(pDb->db, argv[2], DbEvalCallback, &cbData, &zErrMsg);
|
||||
Tcl_IncrRefCount(objv[3]);
|
||||
Tcl_IncrRefCount(objv[4]);
|
||||
rc = sqlite_exec(pDb->db, zSql, DbEvalCallback, &cbData, &zErrMsg);
|
||||
Tcl_DecrRefCount(objv[4]);
|
||||
Tcl_DecrRefCount(objv[3]);
|
||||
}else{
|
||||
rc = sqlite_exec(pDb->db, argv[2], 0, 0, &zErrMsg);
|
||||
Tcl_Obj *pList = Tcl_NewObj();
|
||||
rc = sqlite_exec(pDb->db, zSql, DbEvalCallback2, pList, &zErrMsg);
|
||||
Tcl_SetObjResult(interp, pList);
|
||||
}
|
||||
if( zErrMsg ){
|
||||
Tcl_SetResult(interp, zErrMsg, TCL_VOLATILE);
|
||||
free(zErrMsg);
|
||||
}
|
||||
Tcl_DecrRefCount(objv[2]);
|
||||
return rc;
|
||||
}else
|
||||
}
|
||||
|
||||
/*
|
||||
** $db timeout MILLESECONDS
|
||||
**
|
||||
** Delay for the number of milliseconds specified when a file is locked.
|
||||
*/
|
||||
if( c=='t' && strncmp(z,"timeout",n)==0 ){
|
||||
case DB_TIMEOUT: {
|
||||
int ms;
|
||||
if( argc!=3 ){
|
||||
Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
|
||||
" timeout MILLISECONDS", 0);
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "MILLISECONDS");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( Tcl_GetInt(interp, argv[2], &ms) ) return TCL_ERROR;
|
||||
if( Tcl_GetIntFromObj(interp, objv[2], &ms) ) return TCL_ERROR;
|
||||
sqlite_busy_timeout(pDb->db, ms);
|
||||
}else
|
||||
|
||||
/* The default
|
||||
*/
|
||||
{
|
||||
Tcl_AppendResult(interp,"unknown subcommand \"", z,
|
||||
"\" - should be one of: close complete eval", 0);
|
||||
return TCL_ERROR;
|
||||
break;
|
||||
}
|
||||
} /* End of the SWITCH statement */
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
@@ -314,7 +358,7 @@ static int DbMain(void *cd, Tcl_Interp *interp, int argc, char **argv){
|
||||
free(zErrMsg);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
Tcl_CreateCommand(interp, argv[1], DbCmd, (char*)p, DbDeleteCmd);
|
||||
Tcl_CreateObjCommand(interp, argv[1], DbObjCmd, (char*)p, DbDeleteCmd);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
@@ -336,6 +380,7 @@ int Sqlite_SafeInit(Tcl_Interp *interp){
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/*
|
||||
** If compiled using mktclapp, this routine runs to initialize
|
||||
** everything.
|
||||
@@ -343,6 +388,7 @@ int Sqlite_SafeInit(Tcl_Interp *interp){
|
||||
int Et_AppInit(Tcl_Interp *interp){
|
||||
return Sqlite_Init(interp);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** If the macro TCLSH is defined and is one, then put in code for the
|
||||
@@ -397,3 +443,5 @@ int TCLSH_MAIN(int argc, char **argv){
|
||||
return 0;
|
||||
}
|
||||
#endif /* TCLSH */
|
||||
|
||||
#endif /* !defined(NO_TCL) */
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
hi
|
||||
+18
-5
@@ -27,7 +27,7 @@
|
||||
** individual tokens and sends those tokens one-by-one over to the
|
||||
** parser for analysis.
|
||||
**
|
||||
** $Id: tokenize.c,v 1.13 2000/08/09 17:17:25 drh Exp $
|
||||
** $Id: tokenize.c,v 1.17 2001/02/11 16:56:24 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <ctype.h>
|
||||
@@ -102,7 +102,7 @@ static Keyword aKeywordTable[] = {
|
||||
/*
|
||||
** This is the hash table
|
||||
*/
|
||||
#define KEY_HASH_SIZE 37
|
||||
#define KEY_HASH_SIZE 69
|
||||
static Keyword *apHashTable[KEY_HASH_SIZE];
|
||||
|
||||
|
||||
@@ -298,7 +298,7 @@ int sqliteGetToken(const char *z, int *tokenType){
|
||||
|
||||
/*
|
||||
** Run the parser on the given SQL string. The parser structure is
|
||||
** passed in. Return the number of errors.
|
||||
** passed in. An SQLITE_ status code.
|
||||
*/
|
||||
int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
|
||||
int nErr = 0;
|
||||
@@ -308,9 +308,11 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
|
||||
static FILE *trace = 0;
|
||||
extern void *sqliteParserAlloc(void*(*)(int));
|
||||
extern void sqliteParserFree(void*, void(*)(void*));
|
||||
extern int sqliteParser(void*, int, ...);
|
||||
extern int sqliteParser(void*, int, Token, Parse*);
|
||||
extern void sqliteParserTrace(FILE*, char *);
|
||||
|
||||
pParse->db->flags &= ~SQLITE_Interrupt;
|
||||
pParse->rc = SQLITE_OK;
|
||||
i = 0;
|
||||
sqliteParseInfoReset(pParse);
|
||||
pEngine = sqliteParserAlloc((void*(*)(int))malloc);
|
||||
@@ -318,10 +320,17 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
|
||||
sqliteSetString(pzErrMsg, "out of memory", 0);
|
||||
return 1;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
sqliteParserTrace(trace, "parser: ");
|
||||
#endif
|
||||
while( nErr==0 && i>=0 && zSql[i]!=0 ){
|
||||
int tokenType;
|
||||
|
||||
if( (pParse->db->flags & SQLITE_Interrupt)!=0 ){
|
||||
pParse->rc = SQLITE_INTERRUPT;
|
||||
sqliteSetString(pzErrMsg, "interrupt", 0);
|
||||
break;
|
||||
}
|
||||
pParse->sLastToken.z = &zSql[i];
|
||||
pParse->sLastToken.n = sqliteGetToken(&zSql[i], &tokenType);
|
||||
i += pParse->sLastToken.n;
|
||||
@@ -391,7 +400,7 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( nErr==0 ){
|
||||
if( nErr==0 && (pParse->db->flags & SQLITE_Interrupt)==0 ){
|
||||
sqliteParser(pEngine, 0, pParse->sLastToken, pParse);
|
||||
if( pParse->zErrMsg && pParse->sErrToken.z ){
|
||||
sqliteSetNString(pzErrMsg, "near \"", -1,
|
||||
@@ -423,5 +432,9 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
|
||||
pParse->pNewTable = 0;
|
||||
}
|
||||
sqliteParseInfoReset(pParse);
|
||||
sqliteStrRealloc(pzErrMsg);
|
||||
if( nErr>0 && pParse->rc==SQLITE_OK ){
|
||||
pParse->rc = SQLITE_ERROR;
|
||||
}
|
||||
return nErr;
|
||||
}
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@
|
||||
** This file contains C code routines that are called by the parser
|
||||
** to handle UPDATE statements.
|
||||
**
|
||||
** $Id: update.c,v 1.8 2000/06/21 13:59:12 drh Exp $
|
||||
** $Id: update.c,v 1.9 2001/01/15 22:51:11 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -159,9 +159,9 @@ void sqliteUpdate(
|
||||
*/
|
||||
sqliteVdbeAddOp(v, OP_ListRewind, 0, 0, 0, 0);
|
||||
base = pParse->nTab;
|
||||
sqliteVdbeAddOp(v, OP_Open, base, 1, pTab->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenTbl, base, 1, pTab->zName, 0);
|
||||
for(i=0; i<nIdx; i++){
|
||||
sqliteVdbeAddOp(v, OP_Open, base+i+1, 1, apIdx[i]->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, base+i+1, 1, apIdx[i]->zName, 0);
|
||||
}
|
||||
|
||||
/* Loop over every record that needs updating. We have to load
|
||||
|
||||
+97
-9
@@ -26,14 +26,26 @@
|
||||
** This file contains functions for allocating memory, comparing
|
||||
** strings, and stuff like that.
|
||||
**
|
||||
** $Id: util.c,v 1.14 2000/07/31 11:57:37 drh Exp $
|
||||
** $Id: util.c,v 1.17 2000/12/10 18:23:51 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/*
|
||||
** If MEMORY_DEBUG is defined, then use versions of malloc() and
|
||||
** free() that track memory usage and check for buffer overruns.
|
||||
*/
|
||||
#ifdef MEMORY_DEBUG
|
||||
|
||||
/*
|
||||
** For keeping track of the number of mallocs and frees. This
|
||||
** is used to check for memory leaks.
|
||||
*/
|
||||
int sqlite_nMalloc; /* Number of sqliteMalloc() calls */
|
||||
int sqlite_nFree; /* Number of sqliteFree() calls */
|
||||
int sqlite_iMallocFail; /* Fail sqliteMalloc() after this many calls */
|
||||
|
||||
|
||||
/*
|
||||
** Allocate new memory and set it to zero. Return NULL if
|
||||
@@ -164,9 +176,14 @@ char *sqliteStrNDup_(const char *z, int n, char *zFile, int line){
|
||||
}
|
||||
return zNew;
|
||||
}
|
||||
#endif /* MEMORY_DEBUG */
|
||||
|
||||
/*
|
||||
** The following versions of malloc() and free() are for use in a
|
||||
** normal build.
|
||||
*/
|
||||
#if !defined(MEMORY_DEBUG)
|
||||
|
||||
#else /* !defined(MEMORY_DEBUG) */
|
||||
/*
|
||||
** Allocate new memory and set it to zero. Return NULL if
|
||||
** no memory is available.
|
||||
@@ -219,7 +236,7 @@ char *sqliteStrNDup(const char *z, int n){
|
||||
}
|
||||
return zNew;
|
||||
}
|
||||
#endif /* MEMORY_DEBUG */
|
||||
#endif /* !defined(MEMORY_DEBUG) */
|
||||
|
||||
/*
|
||||
** Create a string from the 2nd and subsequent arguments (up to the
|
||||
@@ -353,7 +370,7 @@ int sqliteHashNoCase(const char *z, int n){
|
||||
int c;
|
||||
if( n<=0 ) n = strlen(z);
|
||||
while( n-- > 0 && (c = *z++)!=0 ){
|
||||
h = h<<3 ^ h ^ UpperToLower[c];
|
||||
h = (h<<3) ^ h ^ UpperToLower[c];
|
||||
}
|
||||
if( h<0 ) h = -h;
|
||||
return h;
|
||||
@@ -550,6 +567,41 @@ static int privateStrCmp(const char *atext, const char *btext, int useCase){
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
** Do a comparison of pure numerics. If either string is not a pure
|
||||
** numeric, then return 0. Otherwise return 1 and set *pResult to be
|
||||
** negative, zero or positive if the first string are numerially less than
|
||||
** equal to, or greater than the second.
|
||||
*/
|
||||
static int privateCompareNum(const char *a, const char *b, int *pResult){
|
||||
char *endPtr;
|
||||
double rA, rB;
|
||||
int isNumA, isNumB;
|
||||
if( isdigit(*a) || ((*a=='-' || *a=='+') && isdigit(a[1])) ){
|
||||
rA = strtod(a, &endPtr);
|
||||
isNumA = *endPtr==0;
|
||||
}else{
|
||||
isNumA = 0;
|
||||
}
|
||||
if( isdigit(*b) || ((*b=='-' || *b=='+') && isdigit(b[1])) ){
|
||||
rB = strtod(b, &endPtr);
|
||||
isNumB = *endPtr==0;
|
||||
}else{
|
||||
isNumB = 0;
|
||||
}
|
||||
if( isNumB==0 && isNumA==0 ) return 0;
|
||||
if( isNumA!=isNumB ){
|
||||
*pResult = isNumA - isNumB;
|
||||
}else if( rA<rB ){
|
||||
*pResult = -1;
|
||||
}else if( rA>rB ){
|
||||
*pResult = 1;
|
||||
}else{
|
||||
*pResult = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* This comparison routine is what we use for comparison operations
|
||||
** in an SQL expression. (Ex: name<'Hello' or value<5). Compare two
|
||||
** strings. Use case only as a tie-breaker. Numbers compare in
|
||||
@@ -557,8 +609,10 @@ static int privateStrCmp(const char *atext, const char *btext, int useCase){
|
||||
*/
|
||||
int sqliteCompare(const char *atext, const char *btext){
|
||||
int result;
|
||||
result = privateStrCmp(atext, btext, 0);
|
||||
if( result==0 ) result = privateStrCmp(atext, btext, 1);
|
||||
if( !privateCompareNum(atext, btext, &result) || result==0 ){
|
||||
result = privateStrCmp(atext, btext, 0);
|
||||
if( result==0 ) result = privateStrCmp(atext, btext, 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -573,12 +627,13 @@ int sortCmp(const char **a, const char **b){
|
||||
return sqliteCompare(*a, *b);
|
||||
}
|
||||
int main(int argc, char **argv){
|
||||
int i, j, k, n;
|
||||
int i, j, k, n, cnt;
|
||||
static char *azStr[] = {
|
||||
"abc", "aBc", "abcd", "aBcd",
|
||||
"123", "124", "1234", "-123", "-124", "-1234",
|
||||
"123", "124", "1234", "-123", "-124", "-1234", "+124",
|
||||
"123.45", "123.456", "123.46", "-123.45", "-123.46", "-123.456",
|
||||
"x9", "x10", "x-9", "x-10", "X9", "X10",
|
||||
"1.234e+02", "+123", "1.23E2", "1.2345e+2", "-1.2345e2", "+w"
|
||||
};
|
||||
n = sizeof(azStr)/sizeof(azStr[0]);
|
||||
qsort(azStr, n, sizeof(azStr[0]), sortCmp);
|
||||
@@ -587,6 +642,7 @@ int main(int argc, char **argv){
|
||||
}
|
||||
printf("Sanity1...");
|
||||
fflush(stdout);
|
||||
cnt = 0;
|
||||
for(i=0; i<n-1; i++){
|
||||
char *a = azStr[i];
|
||||
for(j=i+1; j<n; j++){
|
||||
@@ -595,10 +651,42 @@ int main(int argc, char **argv){
|
||||
printf("Failed! \"%s\" vs \"%s\"\n", a, b);
|
||||
i = j = n;
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
if( i<n ){
|
||||
printf(" OK\n");
|
||||
printf(" OK (%d)\n", cnt);
|
||||
}
|
||||
printf("Sanity2...");
|
||||
fflush(stdout);
|
||||
cnt = 0;
|
||||
for(i=0; i<n; i++){
|
||||
char *a = azStr[i];
|
||||
for(j=0; j<n; j++){
|
||||
char *b = azStr[j];
|
||||
for(k=0; k<n; k++){
|
||||
char *c = azStr[k];
|
||||
int x1, x2, x3, success;
|
||||
x1 = sqliteCompare(a,b);
|
||||
x2 = sqliteCompare(b,c);
|
||||
x3 = sqliteCompare(a,c);
|
||||
if( x1==0 ){
|
||||
success = x2==x3;
|
||||
}else if( x1<0 ){
|
||||
success = (x2<=0 && x3<=0) || x2>0;
|
||||
}else{
|
||||
success = (x2>=0 && x3>=0) || x2<0;
|
||||
}
|
||||
if( !success ){
|
||||
printf("Failed! \"%s\" vs \"%s\" vs \"%s\"\n", a, b, c);
|
||||
i = j = k = n+1;
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( i<n+1 ){
|
||||
printf(" OK (%d)\n", cnt);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
+581
-402
File diff suppressed because it is too large
Load Diff
+96
-92
@@ -27,7 +27,7 @@
|
||||
** or VDBE. The VDBE implements an abstract machine that runs a
|
||||
** simple program to access and modify the underlying database.
|
||||
**
|
||||
** $Id: vdbe.h,v 1.11 2000/07/28 14:32:50 drh Exp $
|
||||
** $Id: vdbe.h,v 1.15 2001/01/15 22:51:12 drh Exp $
|
||||
*/
|
||||
#ifndef _SQLITE_VDBE_H_
|
||||
#define _SQLITE_VDBE_H_
|
||||
@@ -71,115 +71,119 @@ typedef struct VdbeOp VdbeOp;
|
||||
** The source tree contains an AWK script named renumberOps.awk that
|
||||
** can be used to renumber these opcodes when new opcodes are inserted.
|
||||
*/
|
||||
#define OP_Open 1
|
||||
#define OP_Close 2
|
||||
#define OP_Fetch 3
|
||||
#define OP_Fcnt 4
|
||||
#define OP_New 5
|
||||
#define OP_Put 6
|
||||
#define OP_Distinct 7
|
||||
#define OP_Found 8
|
||||
#define OP_NotFound 9
|
||||
#define OP_Delete 10
|
||||
#define OP_Field 11
|
||||
#define OP_KeyAsData 12
|
||||
#define OP_Key 13
|
||||
#define OP_Rewind 14
|
||||
#define OP_Next 15
|
||||
#define OP_OpenIdx 1
|
||||
#define OP_OpenTbl 2
|
||||
#define OP_Close 3
|
||||
#define OP_Fetch 4
|
||||
#define OP_Fcnt 5
|
||||
#define OP_New 6
|
||||
#define OP_Put 7
|
||||
#define OP_Distinct 8
|
||||
#define OP_Found 9
|
||||
#define OP_NotFound 10
|
||||
#define OP_Delete 11
|
||||
#define OP_Field 12
|
||||
#define OP_KeyAsData 13
|
||||
#define OP_Key 14
|
||||
#define OP_Rewind 15
|
||||
#define OP_Next 16
|
||||
|
||||
#define OP_Destroy 16
|
||||
#define OP_Reorganize 17
|
||||
#define OP_Destroy 17
|
||||
#define OP_Reorganize 18
|
||||
|
||||
#define OP_ResetIdx 18
|
||||
#define OP_NextIdx 19
|
||||
#define OP_PutIdx 20
|
||||
#define OP_DeleteIdx 21
|
||||
#define OP_ResetIdx 19
|
||||
#define OP_NextIdx 20
|
||||
#define OP_PutIdx 21
|
||||
#define OP_DeleteIdx 22
|
||||
|
||||
#define OP_MemLoad 22
|
||||
#define OP_MemStore 23
|
||||
#define OP_MemLoad 23
|
||||
#define OP_MemStore 24
|
||||
|
||||
#define OP_ListOpen 24
|
||||
#define OP_ListWrite 25
|
||||
#define OP_ListRewind 26
|
||||
#define OP_ListRead 27
|
||||
#define OP_ListClose 28
|
||||
#define OP_ListOpen 25
|
||||
#define OP_ListWrite 26
|
||||
#define OP_ListRewind 27
|
||||
#define OP_ListRead 28
|
||||
#define OP_ListClose 29
|
||||
|
||||
#define OP_SortOpen 29
|
||||
#define OP_SortPut 30
|
||||
#define OP_SortMakeRec 31
|
||||
#define OP_SortMakeKey 32
|
||||
#define OP_Sort 33
|
||||
#define OP_SortNext 34
|
||||
#define OP_SortKey 35
|
||||
#define OP_SortCallback 36
|
||||
#define OP_SortClose 37
|
||||
#define OP_SortOpen 30
|
||||
#define OP_SortPut 31
|
||||
#define OP_SortMakeRec 32
|
||||
#define OP_SortMakeKey 33
|
||||
#define OP_Sort 34
|
||||
#define OP_SortNext 35
|
||||
#define OP_SortKey 36
|
||||
#define OP_SortCallback 37
|
||||
#define OP_SortClose 38
|
||||
|
||||
#define OP_FileOpen 38
|
||||
#define OP_FileRead 39
|
||||
#define OP_FileField 40
|
||||
#define OP_FileClose 41
|
||||
#define OP_FileOpen 39
|
||||
#define OP_FileRead 40
|
||||
#define OP_FileField 41
|
||||
#define OP_FileClose 42
|
||||
|
||||
#define OP_AggReset 42
|
||||
#define OP_AggFocus 43
|
||||
#define OP_AggIncr 44
|
||||
#define OP_AggNext 45
|
||||
#define OP_AggSet 46
|
||||
#define OP_AggGet 47
|
||||
#define OP_AggReset 43
|
||||
#define OP_AggFocus 44
|
||||
#define OP_AggIncr 45
|
||||
#define OP_AggNext 46
|
||||
#define OP_AggSet 47
|
||||
#define OP_AggGet 48
|
||||
|
||||
#define OP_SetInsert 48
|
||||
#define OP_SetFound 49
|
||||
#define OP_SetNotFound 50
|
||||
#define OP_SetClear 51
|
||||
#define OP_SetInsert 49
|
||||
#define OP_SetFound 50
|
||||
#define OP_SetNotFound 51
|
||||
#define OP_SetClear 52
|
||||
|
||||
#define OP_MakeRecord 52
|
||||
#define OP_MakeKey 53
|
||||
#define OP_MakeRecord 53
|
||||
#define OP_MakeKey 54
|
||||
|
||||
#define OP_Goto 54
|
||||
#define OP_If 55
|
||||
#define OP_Halt 56
|
||||
#define OP_Goto 55
|
||||
#define OP_If 56
|
||||
#define OP_Halt 57
|
||||
|
||||
#define OP_ColumnCount 57
|
||||
#define OP_ColumnName 58
|
||||
#define OP_Callback 59
|
||||
#define OP_ColumnCount 58
|
||||
#define OP_ColumnName 59
|
||||
#define OP_Callback 60
|
||||
|
||||
#define OP_Integer 60
|
||||
#define OP_String 61
|
||||
#define OP_Null 62
|
||||
#define OP_Pop 63
|
||||
#define OP_Dup 64
|
||||
#define OP_Pull 65
|
||||
#define OP_Integer 61
|
||||
#define OP_String 62
|
||||
#define OP_Null 63
|
||||
#define OP_Pop 64
|
||||
#define OP_Dup 65
|
||||
#define OP_Pull 66
|
||||
|
||||
#define OP_Add 66
|
||||
#define OP_AddImm 67
|
||||
#define OP_Subtract 68
|
||||
#define OP_Multiply 69
|
||||
#define OP_Divide 70
|
||||
#define OP_Min 71
|
||||
#define OP_Max 72
|
||||
#define OP_Like 73
|
||||
#define OP_Glob 74
|
||||
#define OP_Eq 75
|
||||
#define OP_Ne 76
|
||||
#define OP_Lt 77
|
||||
#define OP_Le 78
|
||||
#define OP_Gt 79
|
||||
#define OP_Ge 80
|
||||
#define OP_IsNull 81
|
||||
#define OP_NotNull 82
|
||||
#define OP_Negative 83
|
||||
#define OP_And 84
|
||||
#define OP_Or 85
|
||||
#define OP_Not 86
|
||||
#define OP_Concat 87
|
||||
#define OP_Noop 88
|
||||
#define OP_Add 67
|
||||
#define OP_AddImm 68
|
||||
#define OP_Subtract 69
|
||||
#define OP_Multiply 70
|
||||
#define OP_Divide 71
|
||||
#define OP_Min 72
|
||||
#define OP_Max 73
|
||||
#define OP_Like 74
|
||||
#define OP_Glob 75
|
||||
#define OP_Eq 76
|
||||
#define OP_Ne 77
|
||||
#define OP_Lt 78
|
||||
#define OP_Le 79
|
||||
#define OP_Gt 80
|
||||
#define OP_Ge 81
|
||||
#define OP_IsNull 82
|
||||
#define OP_NotNull 83
|
||||
#define OP_Negative 84
|
||||
#define OP_And 85
|
||||
#define OP_Or 86
|
||||
#define OP_Not 87
|
||||
#define OP_Concat 88
|
||||
#define OP_Noop 89
|
||||
|
||||
#define OP_MAX 88
|
||||
#define OP_Strlen 90
|
||||
#define OP_Substr 91
|
||||
|
||||
#define OP_MAX 91
|
||||
|
||||
/*
|
||||
** Prototypes for the VDBE interface. See comments on the implementation
|
||||
** for a description of what each of these routines does.
|
||||
*/
|
||||
Vdbe *sqliteVdbeCreate(Dbbe*);
|
||||
Vdbe *sqliteVdbeCreate(sqlite*);
|
||||
int sqliteVdbeAddOp(Vdbe*,int,int,int,const char*,int);
|
||||
int sqliteVdbeAddOpList(Vdbe*, int nOp, VdbeOp const *aOp);
|
||||
void sqliteVdbeChangeP3(Vdbe*, int addr, const char *zP1, int N);
|
||||
@@ -193,6 +197,6 @@ int sqliteVdbeList(Vdbe*,sqlite_callback,void*,char**);
|
||||
void sqliteVdbeResolveLabel(Vdbe*, int);
|
||||
int sqliteVdbeCurrentAddr(Vdbe*);
|
||||
void sqliteVdbeTrace(Vdbe*,FILE*);
|
||||
|
||||
void sqliteVdbeCompressSpace(Vdbe*,int);
|
||||
|
||||
#endif
|
||||
|
||||
+9
-5
@@ -25,7 +25,7 @@
|
||||
** the WHERE clause of SQL statements. Also found here are subroutines
|
||||
** to generate VDBE code to evaluate expressions.
|
||||
**
|
||||
** $Id: where.c,v 1.8 2000/06/21 13:59:13 drh Exp $
|
||||
** $Id: where.c,v 1.11 2001/02/19 18:24:22 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -271,9 +271,9 @@ WhereInfo *sqliteWhereBegin(
|
||||
/* Open all tables in the pTabList and all indices in aIdx[].
|
||||
*/
|
||||
for(i=0; i<pTabList->nId; i++){
|
||||
sqliteVdbeAddOp(v, OP_Open, base+i, 0, pTabList->a[i].pTab->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenTbl, base+i, 0, pTabList->a[i].pTab->zName, 0);
|
||||
if( i<ARRAYSIZE(aIdx) && aIdx[i]!=0 ){
|
||||
sqliteVdbeAddOp(v, OP_Open, base+pTabList->nId+i, 0, aIdx[i]->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, base+pTabList->nId+i, 0, aIdx[i]->zName,0);
|
||||
}
|
||||
}
|
||||
memcpy(pWInfo->aIdx, aIdx, sizeof(aIdx));
|
||||
@@ -324,7 +324,7 @@ WhereInfo *sqliteWhereBegin(
|
||||
if( i==pTabList->nId-1 && pushKey ){
|
||||
haveKey = 1;
|
||||
}else{
|
||||
sqliteVdbeAddOp(v, OP_Fetch, idx, 0, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_Fetch, base+idx, 0, 0, 0);
|
||||
haveKey = 0;
|
||||
}
|
||||
}
|
||||
@@ -338,8 +338,12 @@ WhereInfo *sqliteWhereBegin(
|
||||
if( (aExpr[j].prereqRight & loopMask)!=aExpr[j].prereqRight ) continue;
|
||||
if( (aExpr[j].prereqLeft & loopMask)!=aExpr[j].prereqLeft ) continue;
|
||||
if( haveKey ){
|
||||
if( pushKey && i==pTabList->nId-1 ){
|
||||
sqliteVdbeAddOp(v, OP_Dup, 0, 0, 0, 0);
|
||||
}else{
|
||||
haveKey = 0;
|
||||
}
|
||||
sqliteVdbeAddOp(v, OP_Fetch, base+idx, 0, 0, 0);
|
||||
haveKey = 0;
|
||||
}
|
||||
sqliteExprIfFalse(pParse, aExpr[j].p, cont);
|
||||
aExpr[j].p = 0;
|
||||
|
||||
+43
-6
@@ -22,24 +22,61 @@
|
||||
#***********************************************************************
|
||||
# This file runs all tests.
|
||||
#
|
||||
# $Id: all.test,v 1.2 2000/06/02 14:27:23 drh Exp $
|
||||
# $Id: all.test,v 1.5 2000/12/10 18:23:51 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
rename finish_test really_finish_test
|
||||
proc finish_test {} {}
|
||||
proc finish_test {} {memleak_check}
|
||||
|
||||
if {[file exists ./sqlite_test_count]} {
|
||||
set COUNT [exec cat ./sqlite_test_count]
|
||||
} else {
|
||||
set COUNT 1
|
||||
set COUNT 4
|
||||
}
|
||||
|
||||
if {[file exists ./sqlite_test_prefixes]} {
|
||||
set PREFIXES [exec cat ./sqlite_test_prefixes]
|
||||
} else {
|
||||
set PREFIXES {memory: gdbm:}
|
||||
}
|
||||
|
||||
# LeakList will hold a list of the number of unfreed mallocs after
|
||||
# each round of the test. This number should be constant. If it
|
||||
# grows, it may mean there is a memory leak in the library.
|
||||
#
|
||||
set LeakList {}
|
||||
|
||||
|
||||
for {set Counter 0} {$Counter<$COUNT} {incr Counter} {
|
||||
foreach testfile [lsort -dictionary [glob $testdir/*.test]] {
|
||||
if {[file tail $testfile]=="all.test"} continue
|
||||
source $testfile
|
||||
foreach p $PREFIXES {
|
||||
set dbprefix $p
|
||||
foreach testfile [lsort -dictionary [glob $testdir/*.test]] {
|
||||
if {[file tail $testfile]=="all.test"} continue
|
||||
source $testfile
|
||||
}
|
||||
}
|
||||
if {[info exists Leak]} {
|
||||
lappend LeakList $Leak
|
||||
}
|
||||
}
|
||||
|
||||
# Do one last test to look for a memory leak in the library. This will
|
||||
# only work if SQLite is compiled with the -DMEMORY_DEBUG=1 flag.
|
||||
#
|
||||
if {$LeakList!=""} {
|
||||
puts -nonewline memory-leak-test...
|
||||
incr ::nTest
|
||||
foreach x $LeakList {
|
||||
if {$x!=[lindex $LeakList 0]} {
|
||||
puts " failed!"
|
||||
puts "Expected: all values to be the same"
|
||||
puts " Got: $LeakList"
|
||||
incr ::nErr
|
||||
break
|
||||
}
|
||||
}
|
||||
puts " Ok"
|
||||
}
|
||||
|
||||
really_finish_test
|
||||
|
||||
+11
-11
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is exercising the code in dbbe.c.
|
||||
#
|
||||
# $Id: dbbe.test,v 1.3 2000/08/17 09:50:00 drh Exp $
|
||||
# $Id: dbbe.test,v 1.5 2001/01/31 13:28:09 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -32,13 +32,13 @@ source $testdir/tester.tcl
|
||||
#
|
||||
do_test dbbe-1.1 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
set v [catch {sqlite db testdb 0444} msg]
|
||||
lappend v $msg
|
||||
} {1 {can't find directory "testdb"}}
|
||||
do_test dbbe-1.2 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
set v [catch {sqlite db testdb/dummy 0666} msg]
|
||||
lappend v $msg
|
||||
} {1 {can't find or create directory "testdb/dummy"}}
|
||||
@@ -49,7 +49,7 @@ do_test dbbe-1.2 {
|
||||
#
|
||||
do_test dbbe-1.3 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
set v [catch {sqlite db testdb 0666} msg]
|
||||
lappend v $msg
|
||||
} {0 {}}
|
||||
@@ -58,7 +58,7 @@ do_test dbbe-1.3 {
|
||||
#
|
||||
do_test dbbe-1.4 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
set fd [open testdb w]
|
||||
puts $fd hi!
|
||||
close $fd
|
||||
@@ -70,7 +70,7 @@ do_test dbbe-1.4 {
|
||||
#
|
||||
do_test dbbe-1.5 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
file mkdir testdb
|
||||
file attributes testdb -permissions 0
|
||||
set v [catch {sqlite db testdb 0666} msg]
|
||||
@@ -81,7 +81,7 @@ do_test dbbe-1.5 {
|
||||
#
|
||||
do_test dbbe-1.6 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
sqlite db testdb 0666
|
||||
execsql {CREATE TABLE t1(x int)}
|
||||
db close
|
||||
@@ -91,7 +91,7 @@ do_test dbbe-1.6 {
|
||||
} {1 {access permission denied for testdb/sqlite_master.tbl}}
|
||||
do_test dbbe-1.6b {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
sqlite db testdb 0666
|
||||
execsql {CREATE TABLE t1(x int)}
|
||||
db close
|
||||
@@ -105,7 +105,7 @@ do_test dbbe-1.6b {
|
||||
#
|
||||
do_test dbbe-2.1 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
sqlite db testdb 0666
|
||||
execsql {
|
||||
CREATE TABLE t1(x int);
|
||||
@@ -123,14 +123,14 @@ do_test dbbe-2.1 {
|
||||
#
|
||||
do_test dbbe-3.1 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
sqlite db testdb 0666
|
||||
execsql {CREATE TABLE t1(x int)}
|
||||
db close
|
||||
sqlite db testdb 0444
|
||||
set v [catch {execsql {INSERT INTO t1 VALUES(1)}} msg]
|
||||
lappend v $msg
|
||||
} {1 {table t1 is readonly}}
|
||||
} {7 {table t1 is readonly}}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
+3
-3
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing expressions.
|
||||
#
|
||||
# $Id: expr.test,v 1.8 2000/06/16 20:51:26 drh Exp $
|
||||
# $Id: expr.test,v 1.9 2000/09/14 01:21:11 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -80,8 +80,8 @@ test_expr expr-1.37 {i1=1, i2=NULL} {not i2} {1}
|
||||
test_expr expr-2.1 {r1=1.23, r2=2.34} {r1+r2} 3.57
|
||||
test_expr expr-2.2 {r1=1.23, r2=2.34} {r1-r2} -1.11
|
||||
test_expr expr-2.3 {r1=1.23, r2=2.34} {r1*r2} 2.8782
|
||||
test_expr expr-2.4 {r1=1.23, r2=2.34} {r1/r2} 0.525641
|
||||
test_expr expr-2.5 {r1=1.23, r2=2.34} {r2/r1} 1.90244
|
||||
test_expr expr-2.4 {r1=1.23, r2=2.34} {r1/r2} 0.525641025641026
|
||||
test_expr expr-2.5 {r1=1.23, r2=2.34} {r2/r1} 1.90243902439024
|
||||
test_expr expr-2.6 {r1=1.23, r2=2.34} {r2<r1} 0
|
||||
test_expr expr-2.7 {r1=1.23, r2=2.34} {r2<=r1} 0
|
||||
test_expr expr-2.8 {r1=1.23, r2=2.34} {r2>r1} 1
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# Copyright (c) 1999, 2000 D. Richard Hipp
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
# Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# Author contact information:
|
||||
# drh@hwaci.com
|
||||
# http://www.hwaci.com/drh/
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing built-in functions.
|
||||
#
|
||||
# $Id: func.test,v 1.1 2000/08/28 16:22:00 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
# Create a table to work with.
|
||||
#
|
||||
execsql {CREATE TABLE tbl1(t1 text)}
|
||||
foreach word {this program is free software} {
|
||||
execsql "INSERT INTO tbl1 VALUES('$word')"
|
||||
}
|
||||
|
||||
# Make sure the table was created properly.
|
||||
#
|
||||
do_test func-0.0 {
|
||||
execsql {SELECT t1 FROM tbl1 ORDER BY t1}
|
||||
} {free is program software this}
|
||||
|
||||
# Check out the length() function
|
||||
#
|
||||
do_test func-1.0 {
|
||||
execsql {SELECT length(t1) FROM tbl1 ORDER BY t1}
|
||||
} {4 2 7 8 4}
|
||||
do_test func-1.1 {
|
||||
set r [catch {execsql {SELECT length(*) FROM tbl1 ORDER BY t1}} msg]
|
||||
lappend r $msg
|
||||
} {1 {too few arguments to function length()}}
|
||||
do_test func-1.2 {
|
||||
set r [catch {execsql {SELECT length(t1,5) FROM tbl1 ORDER BY t1}} msg]
|
||||
lappend r $msg
|
||||
} {1 {too many arguments to function length()}}
|
||||
do_test func-1.3 {
|
||||
execsql {SELECT length(t1), count(*) FROM tbl1 GROUP BY length(t1)
|
||||
ORDER BY length(t1)}
|
||||
} {2 1 4 2 7 1 8 1}
|
||||
|
||||
# Check out the substr() function
|
||||
#
|
||||
do_test func-2.0 {
|
||||
execsql {SELECT substr(t1,1,2) FROM tbl1 ORDER BY t1}
|
||||
} {fr is pr so th}
|
||||
do_test func-2.1 {
|
||||
execsql {SELECT substr(t1,2,1) FROM tbl1 ORDER BY t1}
|
||||
} {r s r o h}
|
||||
do_test func-2.2 {
|
||||
execsql {SELECT substr(t1,3,3) FROM tbl1 ORDER BY t1}
|
||||
} {ee {} ogr ftw is}
|
||||
do_test func-2.3 {
|
||||
execsql {SELECT substr(t1,-1,1) FROM tbl1 ORDER BY t1}
|
||||
} {e s m e s}
|
||||
do_test func-2.4 {
|
||||
execsql {SELECT substr(t1,-1,2) FROM tbl1 ORDER BY t1}
|
||||
} {e s m e s}
|
||||
do_test func-2.5 {
|
||||
execsql {SELECT substr(t1,-2,1) FROM tbl1 ORDER BY t1}
|
||||
} {e i a r i}
|
||||
do_test func-2.6 {
|
||||
execsql {SELECT substr(t1,-2,2) FROM tbl1 ORDER BY t1}
|
||||
} {ee is am re is}
|
||||
do_test func-2.7 {
|
||||
execsql {SELECT substr(t1,-4,2) FROM tbl1 ORDER BY t1}
|
||||
} {fr {} gr wa th}
|
||||
do_test func-2.8 {
|
||||
execsql {SELECT t1 FROM tbl1 ORDER BY substr(t1,2,20)}
|
||||
} {this software free program is}
|
||||
|
||||
finish_test
|
||||
+8
-1
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the CREATE INDEX statement.
|
||||
#
|
||||
# $Id: index.test,v 1.7 2000/08/02 13:47:42 drh Exp $
|
||||
# $Id: index.test,v 1.8 2000/10/19 14:10:09 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -39,12 +39,14 @@ do_test index-1.1b {
|
||||
execsql {SELECT name, sql, tbl_name, type FROM sqlite_master
|
||||
WHERE name='index1'}
|
||||
} {index1 {CREATE INDEX index1 ON test1(f1)} test1 index}
|
||||
skipif memory:
|
||||
do_test index-1.1c {
|
||||
db close
|
||||
sqlite db testdb
|
||||
execsql {SELECT name, sql, tbl_name, type FROM sqlite_master
|
||||
WHERE name='index1'}
|
||||
} {index1 {CREATE INDEX index1 ON test1(f1)} test1 index}
|
||||
skipif memory:
|
||||
do_test index-1.1d {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -106,6 +108,7 @@ set r {}
|
||||
for {set i 1} {$i<100} {incr i} {
|
||||
lappend r testdb/index$i.tbl
|
||||
}
|
||||
skipif memory:
|
||||
do_test index-3.2 {
|
||||
execsql {INSERT INTO test1 VALUES(1,2,3,4,5)}
|
||||
lsort -dictionary [glob testdb/index*.tbl]
|
||||
@@ -223,6 +226,10 @@ do_test index-7.1 {
|
||||
for {set i 1} {$i<20} {incr i} {
|
||||
execsql "INSERT INTO test1 VALUES($i,[expr {int(pow(2,$i))}])"
|
||||
}
|
||||
execsql {SELECT count(*) FROM test1}
|
||||
} {19}
|
||||
skipif memory:
|
||||
do_test index-7.1b {
|
||||
lsort -dictionary [glob testdb/test1*.tbl]
|
||||
} {testdb/test1.tbl testdb/test1__primary_key.tbl}
|
||||
do_test index-7.2 {
|
||||
|
||||
+8
-4
@@ -23,7 +23,9 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this script is database locks.
|
||||
#
|
||||
# $Id: lock.test,v 1.2 2000/08/04 13:51:11 drh Exp $
|
||||
# $Id: lock.test,v 1.5 2001/01/04 14:20:18 drh Exp $
|
||||
|
||||
if {$dbprefix=="gdbm:"} {
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -59,7 +61,7 @@ do_test lock-1.2 {
|
||||
#
|
||||
set v [catch {execsql {UPDATE big SET f2='xyz' WHERE f1=11}} msg]
|
||||
lappend v $msg
|
||||
} {1 {table big is locked}}
|
||||
} {5 {table big is locked}}
|
||||
|
||||
do_test lock-1.3 {
|
||||
# Try to update the database in a separate process
|
||||
@@ -70,7 +72,7 @@ do_test lock-1.3 {
|
||||
puts $f "SELECT f2 FROM big WHERE f1=11;"
|
||||
close $f
|
||||
exec ./sqlite testdb <update.sql
|
||||
} "SQL error: table big is locked\n22"
|
||||
} "UPDATE big SET f2='xyz' WHERE f1=11;\nSQL error: table big is locked\n22"
|
||||
|
||||
do_test lock-1.4 {
|
||||
# Try to update the database using a timeout
|
||||
@@ -81,7 +83,7 @@ do_test lock-1.4 {
|
||||
puts $f "SELECT f2 FROM big WHERE f1=11;"
|
||||
close $f
|
||||
exec ./sqlite testdb <update.sql
|
||||
} "SQL error: table big is locked\n22"
|
||||
} "UPDATE big SET f2='xyz' WHERE f1=11;\nSQL error: table big is locked\n22"
|
||||
|
||||
do_test lock-1.5 {
|
||||
# Try to update the database using a timeout
|
||||
@@ -99,3 +101,5 @@ catch {exec kill -HUP $::lock_pid}
|
||||
catch {exec kill -9 $::lock_pid}
|
||||
|
||||
finish_test
|
||||
|
||||
}
|
||||
|
||||
+29
-1
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is exercising the code in main.c.
|
||||
#
|
||||
# $Id: main.test,v 1.3 2000/06/16 20:51:26 drh Exp $
|
||||
# $Id: main.test,v 1.4 2000/12/10 18:23:52 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -48,6 +48,34 @@ do_test main-1.4 {
|
||||
;
|
||||
}
|
||||
} {1}
|
||||
do_test main-1.5 {
|
||||
db complete {DROP TABLE 'xyz;}
|
||||
} {0}
|
||||
do_test main-1.6 {
|
||||
db complete {DROP TABLE 'xyz';}
|
||||
} {1}
|
||||
do_test main-1.7 {
|
||||
db complete {DROP TABLE "xyz;}
|
||||
} {0}
|
||||
do_test main-1.8 {
|
||||
db complete {DROP TABLE "xyz';}
|
||||
} {0}
|
||||
do_test main-1.9 {
|
||||
db complete {DROP TABLE "xyz";}
|
||||
} {1}
|
||||
do_test main-1.10 {
|
||||
db complete {DROP TABLE xyz; hi}
|
||||
} {0}
|
||||
do_test main-1.11 {
|
||||
db complete {DROP TABLE xyz; }
|
||||
} {1}
|
||||
do_test main-1.12 {
|
||||
db complete {DROP TABLE xyz; -- hi }
|
||||
} {1}
|
||||
do_test main-1.13 {
|
||||
db complete {DROP TABLE xyz; -- hi
|
||||
}
|
||||
} {1}
|
||||
|
||||
# Try to open a database with a corrupt master file.
|
||||
#
|
||||
|
||||
+12
-2
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the SELECT statement.
|
||||
#
|
||||
# $Id: select2.test,v 1.8 2000/07/29 13:07:00 drh Exp $
|
||||
# $Id: select2.test,v 1.9 2000/10/19 14:10:09 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -104,9 +104,16 @@ do_test select2-3.2c {
|
||||
execsql {SELECT f1 FROM tbl2 WHERE f2=1000}
|
||||
} {500}
|
||||
do_test select2-3.2d {
|
||||
execsql {SELECT fcnt() FROM tbl2 WHERE 1000=f2}
|
||||
} {2}
|
||||
do_test select2-3.2e {
|
||||
execsql {SELECT fcnt() FROM tbl2 WHERE f2=1000}
|
||||
} {2}
|
||||
testif gdbm:
|
||||
do_test select2-3.2f {
|
||||
set t1 [lindex [time {execsql {SELECT f1 FROM tbl2 WHERE 1000=f2}} 1] 0]
|
||||
set t2 [lindex [time {execsql {SELECT f1 FROM tbl2 WHERE f2=1000}} 1] 0]
|
||||
expr {$t1*0.8<$t2 && $t2*0.8<$t1}
|
||||
expr {$t1*0.7<$t2 && $t2*0.7<$t1}
|
||||
} {1}
|
||||
|
||||
# Make sure queries run faster with an index than without
|
||||
@@ -117,5 +124,8 @@ do_test select2-3.3 {
|
||||
set t2 [lindex [time {execsql {SELECT f1 FROM tbl2 WHERE f2==2000}} 1] 0]
|
||||
expr {$t1*10 < $t2}
|
||||
} {1}
|
||||
do_test select2-3.4 {
|
||||
expr {[execsql {SELECT fcnt() FROM tbl2 WHERE f2==2000}]>10}
|
||||
} {1}
|
||||
|
||||
finish_test
|
||||
|
||||
+12
-1
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the CREATE TABLE statement.
|
||||
#
|
||||
# $Id: table.test,v 1.6 2000/08/02 13:47:43 drh Exp $
|
||||
# $Id: table.test,v 1.7 2000/10/19 14:10:09 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -49,6 +49,9 @@ do_test table-1.1 {
|
||||
#
|
||||
do_test table-1.2 {
|
||||
execsql {INSERT INTO test1 VALUES('hi', 'y''all')}
|
||||
} {}
|
||||
testif gdbm:
|
||||
do_test table-1.2b {
|
||||
lsort [glob -nocomplain testdb/*.tbl]
|
||||
} {testdb/sqlite_master.tbl testdb/test1.tbl}
|
||||
|
||||
@@ -61,6 +64,7 @@ do_test table-1.3 {
|
||||
# Close and reopen the database. Verify that everything is
|
||||
# still the same.
|
||||
#
|
||||
skipif memory:
|
||||
do_test table-1.4 {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -76,6 +80,7 @@ do_test table-1.5 {
|
||||
|
||||
# Verify that the file associated with the database is gone.
|
||||
#
|
||||
testif gdbm:
|
||||
do_test table-1.5 {
|
||||
lsort [glob -nocomplain testdb/*.tbl]
|
||||
} {testdb/sqlite_master.tbl}
|
||||
@@ -83,6 +88,7 @@ do_test table-1.5 {
|
||||
# Close and reopen the database. Verify that the table is
|
||||
# still gone.
|
||||
#
|
||||
skipif memory:
|
||||
do_test table-1.6 {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -121,6 +127,7 @@ do_test table-2.1b {
|
||||
set v [catch {execsql {CREATE TABLE sqlite_master(two text)}} msg]
|
||||
lappend v $msg
|
||||
} {1 {table sqlite_master already exists}}
|
||||
skipif memory:
|
||||
do_test table-2.1c {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -138,6 +145,7 @@ do_test table-2.2a {
|
||||
set v [catch {execsql {CREATE TABLE test3(two text)}} msg]
|
||||
lappend v $msg
|
||||
} {1 {there is already an index named test3}}
|
||||
skipif memory:
|
||||
do_test table-2.2b {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -198,6 +206,7 @@ do_test table-3.4 {
|
||||
set v [catch {execsql {CREATE TABLE bIg(xyz foo)}} msg]
|
||||
lappend v $msg
|
||||
} {1 {table bIg already exists}}
|
||||
skipif memory:
|
||||
do_test table-3.5 {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -226,6 +235,7 @@ do_test table-4.1 {
|
||||
}
|
||||
execsql {SELECT name FROM sqlite_master WHERE type!='meta' ORDER BY name}
|
||||
} $r
|
||||
skipif memory:
|
||||
do_test table-4.1b {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -287,6 +297,7 @@ do_test table-5.4 {
|
||||
|
||||
# Create a table with a goofy name
|
||||
#
|
||||
testif gdbm:
|
||||
do_test table-6.1 {
|
||||
execsql {CREATE TABLE 'Spaces In This Name!'(x int)}
|
||||
execsql {INSERT INTO 'spaces in this name!' VALUES(1)}
|
||||
|
||||
+76
-14
@@ -23,13 +23,29 @@
|
||||
# This file implements some common TCL routines used for regression
|
||||
# testing the SQLite library
|
||||
#
|
||||
# $Id: tester.tcl,v 1.5 2000/06/08 13:36:41 drh Exp $
|
||||
# $Id: tester.tcl,v 1.10 2001/01/31 13:28:09 drh Exp $
|
||||
|
||||
# Create a test database
|
||||
#
|
||||
file delete -force testdb
|
||||
file mkdir testdb
|
||||
sqlite db testdb
|
||||
if {![info exists dbprefix]} {
|
||||
if {[info exists env(SQLITE_PREFIX)]} {
|
||||
set dbprefix $env(SQLITE_PREFIX):
|
||||
} else {
|
||||
set dbprefix "gdbm:"
|
||||
}
|
||||
}
|
||||
switch $dbprefix {
|
||||
gdbm: {
|
||||
if {[catch {file delete -force testdb}]} {
|
||||
exec rm -rf testdb
|
||||
}
|
||||
file mkdir testdb
|
||||
}
|
||||
memory: {
|
||||
# do nothing
|
||||
}
|
||||
}
|
||||
sqlite db ${dbprefix}testdb
|
||||
|
||||
# Abort early if this script has been run before.
|
||||
#
|
||||
@@ -39,12 +55,17 @@ if {[info exists nTest]} return
|
||||
#
|
||||
set nErr 0
|
||||
set nTest 0
|
||||
set skip_test 0
|
||||
|
||||
# Invoke the do_test procedure to run a single test
|
||||
#
|
||||
proc do_test {name cmd expected} {
|
||||
global argv nErr nTest
|
||||
if {[llength $argv]==0} {
|
||||
global argv nErr nTest skip_test
|
||||
if {$skip_test} {
|
||||
set skip_test 0
|
||||
return
|
||||
}
|
||||
if {[llength $argv]==0} {
|
||||
set go 1
|
||||
} else {
|
||||
set go 0
|
||||
@@ -57,7 +78,7 @@ proc do_test {name cmd expected} {
|
||||
}
|
||||
if {!$go} return
|
||||
incr nTest
|
||||
puts -nonewline $name...
|
||||
puts -nonewline $::dbprefix$name...
|
||||
flush stdout
|
||||
if {[catch {uplevel #0 "$cmd;\n"} result]} {
|
||||
puts "\nError: $result"
|
||||
@@ -70,10 +91,48 @@ proc do_test {name cmd expected} {
|
||||
}
|
||||
}
|
||||
|
||||
# Skip a test based on the dbprefix
|
||||
#
|
||||
proc skipif {args} {
|
||||
foreach a $args {
|
||||
if {$::dbprefix==$a} {
|
||||
set ::skip_test 1
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Run the next test only if the dbprefix is among the listed arguments
|
||||
#
|
||||
proc testif {args} {
|
||||
foreach a $args {
|
||||
if {$::dbprefix==$a} {
|
||||
set ::skip_test 0
|
||||
return
|
||||
}
|
||||
}
|
||||
set ::skip_test 1
|
||||
}
|
||||
|
||||
# The procedure uses the special "--malloc-stats--" macro of SQLite
|
||||
# (which is only available if SQLite is compiled with -DMEMORY_DEBUG=1)
|
||||
# to see how many malloc()s have not been free()ed. The number
|
||||
# of surplus malloc()s is stored in the global variable $::Leak.
|
||||
# If the value in $::Leak grows, it may mean there is a memory leak
|
||||
# in the library.
|
||||
#
|
||||
proc memleak_check {} {
|
||||
set r [execsql {--malloc-stats--}]
|
||||
if {$r==""} return
|
||||
set ::Leak [expr {[lindex $r 0]-[lindex $r 1]}]
|
||||
# puts "*** $::Leak mallocs have not been freed ***"
|
||||
}
|
||||
|
||||
# Run this routine last
|
||||
#
|
||||
proc finish_test {} {
|
||||
global nTest nErr
|
||||
memleak_check
|
||||
catch {db close}
|
||||
puts "$nErr errors out of $nTest tests"
|
||||
exit $nErr
|
||||
@@ -82,13 +141,8 @@ proc finish_test {} {
|
||||
# A procedure to execute SQL
|
||||
#
|
||||
proc execsql {sql} {
|
||||
set result {}
|
||||
db eval $sql data {
|
||||
foreach f $data(*) {
|
||||
lappend result $data($f)
|
||||
}
|
||||
}
|
||||
return $result
|
||||
# puts "SQL = $sql"
|
||||
return [db eval $sql]
|
||||
}
|
||||
|
||||
# Another procedure to execute SQL. This one includes the field
|
||||
@@ -103,3 +157,11 @@ proc execsql2 {sql} {
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
# Delete a file or directory
|
||||
#
|
||||
proc forcedelete {filename} {
|
||||
if {[catch {file delete -force $filename}]} {
|
||||
exec rm -rf $filename
|
||||
}
|
||||
}
|
||||
|
||||
+277
-23
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the UPDATE statement.
|
||||
#
|
||||
# $Id: update.test,v 1.4 2000/06/21 13:59:14 drh Exp $
|
||||
# $Id: update.test,v 1.5 2001/02/19 18:24:22 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -90,47 +90,301 @@ do_test update-3.8 {
|
||||
execsql {UPDATE test1 SET F2=f1, F1=f2}
|
||||
execsql {SELECT * FROM test1 ORDER BY F1}
|
||||
} {2 1 4 2 8 3 16 4 32 5 64 6 128 7 256 8 512 9 1024 10}
|
||||
|
||||
# Create an index and make sure updating works with an index.
|
||||
#
|
||||
do_test update-3.9 {
|
||||
execsql {CREATE INDEX index1 ON test1(f1)}
|
||||
execsql {CREATE INDEX index2 ON test1(f1)}
|
||||
execsql {UPDATE test1 SET f1=1025 WHERE f2==10}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1}
|
||||
} {2 1 4 2 8 3 16 4 32 5 64 6 128 7 256 8 512 9 1025 10}
|
||||
do_test update-3.10 {
|
||||
execsql {SELECT * FROM test1 WHERE f1=1025}
|
||||
} {1025 10}
|
||||
do_test update-3.11 {
|
||||
execsql {UPDATE test1 SET f2=11 WHERE f1==1025}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1}
|
||||
} {2 1 4 2 8 3 16 4 32 5 64 6 128 7 256 8 512 9 1025 11}
|
||||
do_test update-3.12 {
|
||||
execsql {SELECT * FROM test1 WHERE f1=1025}
|
||||
} {1025 11}
|
||||
execsql {UPDATE test1 SET F2=f1, F1=f2}
|
||||
execsql {SELECT * FROM test1 ORDER BY F1}
|
||||
} {1 2 2 4 3 8 4 16 5 32 6 64 7 128 8 256 9 512 10 1024}
|
||||
|
||||
# Create duplicate entries and make sure updating still
|
||||
# works.
|
||||
#
|
||||
do_test update-4.0 {
|
||||
execsql {
|
||||
DELETE FROM test1 WHERE f1<=5;
|
||||
INSERT INTO test1(f1,f2) VALUES(8,88);
|
||||
INSERT INTO test1(f1,f2) VALUES(8,888);
|
||||
INSERT INTO test1(f1,f2) VALUES(77,128);
|
||||
INSERT INTO test1(f1,f2) VALUES(777,128);
|
||||
}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-4.1 {
|
||||
execsql {UPDATE test1 SET f2=f2+1 WHERE f1==8}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 889 9 512 10 1024 77 128 777 128}
|
||||
do_test update-4.2 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2>800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-4.3 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2<800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-4.4 {
|
||||
execsql {UPDATE test1 SET f1=f1+1 WHERE f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 778 128}
|
||||
do_test update-4.5 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1>100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 777 128}
|
||||
do_test update-4.6 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1<=100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
|
||||
# Repeat the previous sequence of tests with an index.
|
||||
#
|
||||
do_test update-5.0 {
|
||||
execsql {CREATE INDEX idx1 ON test1(f1)}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-5.1 {
|
||||
execsql {UPDATE test1 SET f2=f2+1 WHERE f1==8}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 889 9 512 10 1024 77 128 777 128}
|
||||
do_test update-5.2 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2>800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-5.3 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2<800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-5.4 {
|
||||
execsql {UPDATE test1 SET f1=f1+1 WHERE f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 778 128}
|
||||
do_test update-5.4.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
|
||||
} {78 128}
|
||||
do_test update-5.4.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {778 128}
|
||||
do_test update-5.4.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 128 8 256 8 888}
|
||||
do_test update-5.5 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1>100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 777 128}
|
||||
do_test update-5.5.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
|
||||
} {78 128}
|
||||
do_test update-5.5.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-5.5.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
|
||||
} {777 128}
|
||||
do_test update-5.5.4 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 128 8 256 8 888}
|
||||
do_test update-5.6 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1<=100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-5.6.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==77 ORDER BY f1,f2}
|
||||
} {77 128}
|
||||
do_test update-5.6.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-5.6.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
|
||||
} {777 128}
|
||||
do_test update-5.6.4 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 256 8 888}
|
||||
|
||||
# Repeat the previous sequence of tests with a different index.
|
||||
#
|
||||
do_test update-6.0 {
|
||||
execsql {DROP INDEX idx1}
|
||||
execsql {CREATE INDEX idx1 ON test1(f2)}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-6.1 {
|
||||
execsql {UPDATE test1 SET f2=f2+1 WHERE f1==8}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 889 9 512 10 1024 77 128 777 128}
|
||||
do_test update-6.1.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 89 8 257 8 889}
|
||||
do_test update-6.1.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f2==89 ORDER BY f1,f2}
|
||||
} {8 89}
|
||||
do_test update-6.1.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==88 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-6.2 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2>800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-6.3 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2<800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-6.3.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 256 8 888}
|
||||
do_test update-6.3.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f2==89 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-6.3.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f2==88 ORDER BY f1,f2}
|
||||
} {8 88}
|
||||
do_test update-6.4 {
|
||||
execsql {UPDATE test1 SET f1=f1+1 WHERE f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 778 128}
|
||||
do_test update-6.4.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
|
||||
} {78 128}
|
||||
do_test update-6.4.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {778 128}
|
||||
do_test update-6.4.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 128 8 256 8 888}
|
||||
do_test update-6.5 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1>100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 777 128}
|
||||
do_test update-6.5.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
|
||||
} {78 128}
|
||||
do_test update-6.5.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-6.5.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
|
||||
} {777 128}
|
||||
do_test update-6.5.4 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 128 8 256 8 888}
|
||||
do_test update-6.6 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1<=100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-6.6.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==77 ORDER BY f1,f2}
|
||||
} {77 128}
|
||||
do_test update-6.6.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-6.6.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
|
||||
} {777 128}
|
||||
do_test update-6.6.4 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 256 8 888}
|
||||
|
||||
# Repeat the previous sequence of tests with multiple
|
||||
# indices
|
||||
#
|
||||
do_test update-7.0 {
|
||||
execsql {CREATE INDEX idx2 ON test1(f2)}
|
||||
execsql {CREATE INDEX idx3 ON test1(f1,f2)}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-7.1 {
|
||||
execsql {UPDATE test1 SET f2=f2+1 WHERE f1==8}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 889 9 512 10 1024 77 128 777 128}
|
||||
do_test update-7.1.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 89 8 257 8 889}
|
||||
do_test update-7.1.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f2==89 ORDER BY f1,f2}
|
||||
} {8 89}
|
||||
do_test update-7.1.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==88 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-7.2 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2>800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-7.3 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2<800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-7.3.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 256 8 888}
|
||||
do_test update-7.3.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f2==89 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-7.3.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f2==88 ORDER BY f1,f2}
|
||||
} {8 88}
|
||||
do_test update-7.4 {
|
||||
execsql {UPDATE test1 SET f1=f1+1 WHERE f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 778 128}
|
||||
do_test update-7.4.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
|
||||
} {78 128}
|
||||
do_test update-7.4.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {778 128}
|
||||
do_test update-7.4.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 128 8 256 8 888}
|
||||
do_test update-7.5 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1>100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 777 128}
|
||||
do_test update-7.5.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
|
||||
} {78 128}
|
||||
do_test update-7.5.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-7.5.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
|
||||
} {777 128}
|
||||
do_test update-7.5.4 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 128 8 256 8 888}
|
||||
do_test update-7.6 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1<=100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-7.6.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==77 ORDER BY f1,f2}
|
||||
} {77 128}
|
||||
do_test update-7.6.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-7.6.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
|
||||
} {777 128}
|
||||
do_test update-7.6.4 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 256 8 888}
|
||||
|
||||
# Error messages
|
||||
#
|
||||
do_test update-4.1 {
|
||||
do_test update-9.1 {
|
||||
set v [catch {execsql {
|
||||
UPDATE test1 SET x=11 WHERE f1=1025
|
||||
}} msg]
|
||||
lappend v $msg
|
||||
} {1 {no such column: x}}
|
||||
do_test update-4.2 {
|
||||
do_test update-9.2 {
|
||||
set v [catch {execsql {
|
||||
UPDATE test1 SET f1=x(11) WHERE f1=1025
|
||||
}} msg]
|
||||
lappend v $msg
|
||||
} {1 {no such function: x}}
|
||||
do_test update-4.3 {
|
||||
do_test update-9.3 {
|
||||
set v [catch {execsql {
|
||||
UPDATE test1 SET f1=11 WHERE x=1025
|
||||
}} msg]
|
||||
lappend v $msg
|
||||
} {1 {no such column: x}}
|
||||
do_test update-4.4 {
|
||||
do_test update-9.4 {
|
||||
set v [catch {execsql {
|
||||
UPDATE test1 SET f1=11 WHERE x(f1)=1025
|
||||
}} msg]
|
||||
|
||||
+3
-1
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the VACUUM statement.
|
||||
#
|
||||
# $Id: vacuum.test,v 1.1 2000/06/03 18:06:54 drh Exp $
|
||||
# $Id: vacuum.test,v 1.2 2000/10/19 14:10:10 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -53,6 +53,7 @@ execsql {INSERT INTO test1 VALUES(2)}
|
||||
execsql {INSERT INTO test1 VALUES(3)}
|
||||
execsql {INSERT INTO test2 VALUES(4)}
|
||||
|
||||
testif gdbm:
|
||||
do_test vacuum-1.3 {
|
||||
set b1 [file mtime testdb/test1.tbl]
|
||||
set b2 [file mtime testdb/test2.tbl]
|
||||
@@ -64,6 +65,7 @@ do_test vacuum-1.3 {
|
||||
set a3 [file mtime testdb/index1.tbl]
|
||||
expr {$a1>$b1 && $a2==$b2 && $a3==$b3}
|
||||
} {1}
|
||||
testif gdbm:
|
||||
do_test vacuum-1.4 {
|
||||
set b1 [file mtime testdb/test1.tbl]
|
||||
set b2 [file mtime testdb/test2.tbl]
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
** Copyright (c) 1999, 2000 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** A utility print statistics about the content of a GDBM database.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <gdbm.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int bins[] = {
|
||||
4, 8, 12, 16, 24,
|
||||
32, 40, 48, 56, 64,
|
||||
80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256,
|
||||
288, 320, 352, 384, 416, 448, 480, 512, 1024, 2048, 4096, 8192,
|
||||
16384, 32768, 65536,
|
||||
};
|
||||
#define NBIN (sizeof(bins)/sizeof(bins[0])+1)
|
||||
|
||||
static int gAllSize[NBIN];
|
||||
static int gCount;
|
||||
static int gTotal;
|
||||
static int gMax;
|
||||
static int gKey;
|
||||
static int gMaxKey;
|
||||
|
||||
static int gdbm_stat(char *zFilename){
|
||||
GDBM_FILE p;
|
||||
datum data, key, next;
|
||||
int nEntry = 0;
|
||||
int keyTotal = 0;
|
||||
int dataTotal = 0;
|
||||
int allTotal = 0;
|
||||
int lMax = 0;
|
||||
int keySize[NBIN], dataSize[NBIN], allSize[NBIN];
|
||||
int i, priorSize;
|
||||
|
||||
p = gdbm_open(zFilename, 0, GDBM_READER, 0, 0);
|
||||
if( p==0 ){
|
||||
fprintf(stderr,"can't open file \"%s\"\n", zFilename);
|
||||
return 1;
|
||||
}
|
||||
for(i=0; i<NBIN; i++){
|
||||
keySize[i] = 0;
|
||||
dataSize[i] = 0;
|
||||
allSize[i] = 0;
|
||||
}
|
||||
key = gdbm_firstkey(p);
|
||||
while( key.dptr ){
|
||||
int all;
|
||||
nEntry++;
|
||||
gCount++;
|
||||
keyTotal += key.dsize;
|
||||
for(i=0; i<NBIN-1 && key.dsize>bins[i]; i++){}
|
||||
keySize[i]++;
|
||||
gKey += key.dsize;
|
||||
if( key.dsize>gMaxKey ) gMaxKey = key.dsize;
|
||||
data = gdbm_fetch(p, key);
|
||||
if( data.dptr==0 ) data.dsize = 0;
|
||||
dataTotal += data.dsize;
|
||||
for(i=0; i<NBIN-1 && data.dsize>bins[i]; i++){}
|
||||
dataSize[i]++;
|
||||
all = key.dsize + data.dsize;
|
||||
allTotal += all;
|
||||
gTotal += all;
|
||||
if( all>gMax ) gMax = all;
|
||||
if( all>lMax ) lMax = all;
|
||||
for(i=0; i<NBIN-1 && all>bins[i]; i++){}
|
||||
allSize[i]++;
|
||||
gAllSize[i]++;
|
||||
next = gdbm_nextkey(p, key);
|
||||
free( key.dptr );
|
||||
key = next;
|
||||
}
|
||||
gdbm_close(p);
|
||||
printf("%s:\n", zFilename);
|
||||
printf(" entries: %d\n", nEntry);
|
||||
printf(" keysize: %d (%d per entry)\n",
|
||||
keyTotal, nEntry>0 ? (keyTotal+nEntry-1)/nEntry : 0);
|
||||
printf(" datasize: %d (%d per entry)\n",
|
||||
dataTotal, nEntry>0 ? (dataTotal+nEntry-1)/nEntry : 0);
|
||||
printf(" size: %d (%d per entry)\n",
|
||||
allTotal, nEntry>0 ? (allTotal+nEntry-1)/nEntry : 0);
|
||||
priorSize = 0;
|
||||
for(i=0; i<NBIN-1; i++){
|
||||
if( keySize[i]==0 && dataSize[i]==0 ) continue;
|
||||
printf("%5d..%-5d %7d %7d %7d\n", priorSize, bins[i], keySize[i],
|
||||
dataSize[i], allSize[i]);
|
||||
priorSize = bins[i]+1;
|
||||
}
|
||||
if( keySize[NBIN-1]>0 || dataSize[NBIN-1]>0 ){
|
||||
printf("%5d..%-5d %7d %7d %7d\n", priorSize, lMax,
|
||||
keySize[NBIN-1], dataSize[NBIN-1], allSize[NBIN-1]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv){
|
||||
int i, ps, sum;
|
||||
int nErr = 0;
|
||||
for(i=1; i<argc; i++){
|
||||
nErr += gdbm_stat(argv[i]);
|
||||
}
|
||||
printf("*****************************************************************\n");
|
||||
printf("Entries: %d\n", gCount);
|
||||
printf("Size: %d\n", gTotal);
|
||||
printf("Avg Size: %d\n", gCount>0 ? (gTotal + gCount - 1)/gCount : 0);
|
||||
printf("Key Size: %d\n", gKey);
|
||||
printf("Avg Key Size: %d\n", gCount>0 ? (gKey + gCount - 1)/gCount : 0);
|
||||
printf("Max Key Size: %d\n\n", gMaxKey);
|
||||
ps = 0;
|
||||
sum = 0;
|
||||
for(i=0; i<NBIN-1; i++){
|
||||
if( gAllSize[i]==0 ) continue;
|
||||
sum += gAllSize[i];
|
||||
printf("%5d..%-5d %8d %3d%%\n",
|
||||
ps, bins[i], gAllSize[i], sum*100/gCount);
|
||||
ps = bins[i]+1;
|
||||
}
|
||||
if( gAllSize[NBIN-1]>0 ){
|
||||
printf("%5d..%-5d %8d 100%%\n", ps, gMax, gAllSize[NBIN-1]);
|
||||
}
|
||||
return nErr;
|
||||
}
|
||||
+2
-1
@@ -375,7 +375,8 @@ struct action *ap2;
|
||||
rc = ap1->sp->index - ap2->sp->index;
|
||||
if( rc==0 ) rc = (int)ap1->type - (int)ap2->type;
|
||||
if( rc==0 ){
|
||||
assert( ap1->type==REDUCE && ap2->type==REDUCE );
|
||||
assert( ap1->type==REDUCE || ap1->type==RD_RESOLVED || ap1->type==CONFLICT);
|
||||
assert( ap2->type==REDUCE || ap2->type==RD_RESOLVED || ap2->type==CONFLICT);
|
||||
rc = ap1->x.rp->index - ap2->x.rp->index;
|
||||
}
|
||||
return rc;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
The SQL database used for ACD contains 113 tables and indices implemented
|
||||
in GDBM. The following are statistics on the sizes of keys and data
|
||||
within these tables and indices.
|
||||
|
||||
Entries: 962080
|
||||
Size: 45573853
|
||||
Avg Size: 48
|
||||
Key Size: 11045299
|
||||
Avg Key Size: 12
|
||||
Max Key Size: 99
|
||||
|
||||
|
||||
Size of key Cummulative
|
||||
and data Instances Percentage
|
||||
------------ ---------- -----------
|
||||
0..8 266 0%
|
||||
9..12 5485 0%
|
||||
13..16 73633 8%
|
||||
17..24 180918 27%
|
||||
25..32 209823 48%
|
||||
33..40 148995 64%
|
||||
41..48 76304 72%
|
||||
49..56 14346 73%
|
||||
57..64 15725 75%
|
||||
65..80 44916 80%
|
||||
81..96 127815 93%
|
||||
97..112 34769 96%
|
||||
113..128 13314 98%
|
||||
129..144 8098 99%
|
||||
145..160 3355 99%
|
||||
161..176 1159 99%
|
||||
177..192 629 99%
|
||||
193..208 221 99%
|
||||
209..224 210 99%
|
||||
225..240 129 99%
|
||||
241..256 57 99%
|
||||
257..288 496 99%
|
||||
289..320 60 99%
|
||||
321..352 37 99%
|
||||
353..384 46 99%
|
||||
385..416 22 99%
|
||||
417..448 24 99%
|
||||
449..480 26 99%
|
||||
481..512 27 99%
|
||||
513..1024 471 99%
|
||||
1025..2048 389 99%
|
||||
2049..4096 182 99%
|
||||
4097..8192 74 99%
|
||||
8193..16384 34 99%
|
||||
16385..32768 17 99%
|
||||
32769..65536 5 99%
|
||||
65537..131073 3 100%
|
||||
|
||||
|
||||
This information is gathered to help design the new built-in
|
||||
backend for sqlite 2.0. Note in particular that 99% of all
|
||||
database entries have a combined key and data size of less than
|
||||
144 bytes. So if a leaf node in the new database is able to
|
||||
store 144 bytes of combined key and data, only 1% of the leaves
|
||||
will require overflow pages. Furthermore, note that no key
|
||||
is larger than 99 bytes, so if the key will never be on an
|
||||
overflow page.
|
||||
|
||||
The average combined size of key+data is 48. Add in 16 bytes of
|
||||
overhead for a total of 64. That means that a 1K page will
|
||||
store (on average) about 16 entries.
|
||||
+1
-1
@@ -40,7 +40,6 @@ Single
|
||||
4 1 0 100 0 0 20 0.0000 4 195 1140 3675 2550 Tokenizer\001
|
||||
4 1 0 100 0 0 20 0.0000 4 195 1020 3675 750 Interface\001
|
||||
4 1 0 100 0 0 20 0.0000 4 195 990 3675 9825 Backend\001
|
||||
4 1 0 100 0 0 14 0.0000 4 150 570 3675 1575 main.c\001
|
||||
4 1 0 100 0 0 14 0.0000 4 195 1860 3675 6525 build.c delete.c expr.c\001
|
||||
4 1 0 100 0 0 14 0.0000 4 195 2115 3675 6750 insert.c select.c update.c\001
|
||||
4 1 0 100 0 0 14 0.0000 4 150 705 3675 6975 where.c\001
|
||||
@@ -48,3 +47,4 @@ Single
|
||||
4 1 0 100 0 0 14 0.0000 4 150 870 3675 3375 tokenize.c\001
|
||||
4 1 0 100 0 0 14 0.0000 4 150 570 3675 8775 vdbe.c\001
|
||||
4 1 0 100 0 0 14 0.0000 4 150 570 3675 10500 dbbe.c\001
|
||||
4 1 0 100 0 0 14 0.0000 4 195 2040 3675 1575 main.c table.c tclsqlite.c\001
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.8 KiB |
+5
-3
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Run this Tcl script to generate the sqlite.html file.
|
||||
#
|
||||
set rcsid {$Id: arch.tcl,v 1.2 2000/06/09 14:14:34 drh Exp $}
|
||||
set rcsid {$Id: arch.tcl,v 1.3 2000/09/29 13:30:55 drh Exp $}
|
||||
|
||||
puts {<html>
|
||||
<head>
|
||||
@@ -30,8 +30,10 @@ follows will provide a quick overview of each of these components.
|
||||
|
||||
<h2>Interface</h2>
|
||||
|
||||
<p>The public interface to the SQLite library is implemented by
|
||||
four functions found in the <b>main.c</b> source file. Additional
|
||||
<p>Most of the public interface to the SQLite library is implemented by
|
||||
four functions found in the <b>main.c</b> source file. The
|
||||
<b>sqlite_get_table()</b> routine is implemented in <b>table.c</b>.
|
||||
The Tcl interface is implemented by <b>tclsqlite.c</b>. More
|
||||
information on the C interface to SQLite is
|
||||
<a href="c_interface.html">available separately</a>.<p>
|
||||
|
||||
|
||||
+243
-17
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Run this Tcl script to generate the sqlite.html file.
|
||||
#
|
||||
set rcsid {$Id: c_interface.tcl,v 1.6 2000/07/28 14:32:51 drh Exp $}
|
||||
set rcsid {$Id: c_interface.tcl,v 1.13 2000/11/28 20:46:39 drh Exp $}
|
||||
|
||||
puts {<html>
|
||||
<head>
|
||||
@@ -22,15 +22,14 @@ programming interface.</p>
|
||||
|
||||
<h2>The API</h2>
|
||||
|
||||
<p>The interface to the SQLite library consists of six functions
|
||||
(only three of which are required),
|
||||
one opaque data structure, and some constants used as return
|
||||
values from sqlite_exec():</p>
|
||||
<p>The interface to the SQLite library consists of three core functions,
|
||||
one opaque data structure, and some constants used as return values.
|
||||
The core interface is as follows:</p>
|
||||
|
||||
<blockquote><pre>
|
||||
typedef struct sqlite sqlite;
|
||||
|
||||
sqlite *sqlite_open(const char *filename, int mode, char **errmsg);
|
||||
sqlite *sqlite_open(const char *dbname, int mode, char **errmsg);
|
||||
|
||||
void sqlite_close(sqlite*);
|
||||
|
||||
@@ -42,12 +41,6 @@ int sqlite_exec(
|
||||
char **errmsg
|
||||
);
|
||||
|
||||
int sqlite_complete(const char *sql);
|
||||
|
||||
void sqlite_busy_handler(sqlite*, int (*)(void*,const char*,int), void*);
|
||||
|
||||
void sqlite_busy_timeout(sqlite*, int ms);
|
||||
|
||||
#define SQLITE_OK 0 /* Successful result */
|
||||
#define SQLITE_INTERNAL 1 /* An internal logic error in SQLite */
|
||||
#define SQLITE_ERROR 2 /* SQL error or missing database */
|
||||
@@ -58,6 +51,73 @@ void sqlite_busy_timeout(sqlite*, int ms);
|
||||
#define SQLITE_READONLY 7 /* Attempt to write a readonly database */
|
||||
</pre></blockquote>
|
||||
|
||||
<p>Only the three core routines shown above are required to use
|
||||
SQLite. But there are many other functions that provide
|
||||
useful interfaces. These extended routines are as follows:
|
||||
</p>
|
||||
|
||||
<blockquote><pre>
|
||||
int sqlite_get_table(
|
||||
sqlite*,
|
||||
char *sql,
|
||||
char ***result,
|
||||
int *nrow,
|
||||
int *ncolumn,
|
||||
char **errmsg
|
||||
);
|
||||
|
||||
void sqlite_free_table(char**);
|
||||
|
||||
void sqlite_interrupt(sqlite*);
|
||||
|
||||
int sqlite_complete(const char *sql);
|
||||
|
||||
void sqlite_busy_handler(sqlite*, int (*)(void*,const char*,int), void*);
|
||||
|
||||
void sqlite_busy_timeout(sqlite*, int ms);
|
||||
|
||||
const char sqlite_version[];
|
||||
|
||||
int sqlite_exec_printf(
|
||||
sqlite*,
|
||||
char *sql,
|
||||
int (*)(void*,int,char**,char**),
|
||||
void*,
|
||||
char **errmsg,
|
||||
...
|
||||
);
|
||||
|
||||
int sqlite_exec_vprintf(
|
||||
sqlite*,
|
||||
char *sql,
|
||||
int (*)(void*,int,char**,char**),
|
||||
void*,
|
||||
char **errmsg,
|
||||
va_list
|
||||
);
|
||||
|
||||
int sqlite_get_table_printf(
|
||||
sqlite*,
|
||||
char *sql,
|
||||
char ***result,
|
||||
int *nrow,
|
||||
int *ncolumn,
|
||||
char **errmsg,
|
||||
...
|
||||
);
|
||||
|
||||
int sqlite_get_table_vprintf(
|
||||
sqlite*,
|
||||
char *sql,
|
||||
char ***result,
|
||||
int *nrow,
|
||||
int *ncolumn,
|
||||
char **errmsg,
|
||||
va_list
|
||||
);
|
||||
|
||||
</pre></blockquote>
|
||||
|
||||
<p>All of the above definitions are included in the "sqlite.h"
|
||||
header file that comes in the source tree.</p>
|
||||
|
||||
@@ -74,8 +134,9 @@ written to memory obtained from malloc() and *errmsg will be made
|
||||
to point to this error message. The calling function is responsible
|
||||
for freeing the memory when it has finished with it.</p>
|
||||
|
||||
<p>An SQLite database is just a directory containing a collection of
|
||||
GDBM files. There is one GDBM file for each table and index in the
|
||||
<p>The name of an SQLite database is normally the name of a directory
|
||||
that contains a collection of GDBM files that comprise the database.
|
||||
There is one GDBM file for each table and index in the
|
||||
database. All GDBM files end with the ".tbl" suffix. Every SQLite
|
||||
database also contains a special database table named <b>sqlite_master</b>
|
||||
stored in its own GDBM file. This special table records the database
|
||||
@@ -83,7 +144,20 @@ schema.</p>
|
||||
|
||||
<p>To create a new SQLite database, all you have to do is call
|
||||
<b>sqlite_open()</b> with the first parameter set to the name of
|
||||
an empty directory and the second parameter set to 0666.</p>
|
||||
an empty directory and the second parameter set to 0666. The
|
||||
directory is created automatically if it does not already exist.</p>
|
||||
|
||||
<p>Beginning with SQLite version 1.0.14, SQLite supports database
|
||||
backends other than GDBM. The only backends currently supported
|
||||
are the default GDBM driver and an in-memory hash table database.
|
||||
You may anticipate additional backends in future versions of SQLite.</p>
|
||||
|
||||
<p>An alternative database backend is specified by prepending the
|
||||
backend name and a colon to the database name argument of the
|
||||
<b>sqlite_open()</b> function. For the GDBM backend, you can
|
||||
prepend "<b>gdbm:</b>" to the directory name. To select the in-memory
|
||||
hash table backend, prepend "<b>memory:</b>" to the database name.
|
||||
Future database drivers will be selected by a similar mechanism.</p>
|
||||
|
||||
<p>The return value of the <b>sqlite_open()</b> function is a
|
||||
pointer to an opaque <b>sqlite</b> structure. This pointer will
|
||||
@@ -205,12 +279,75 @@ a database file that was originally opened for reading only. This can
|
||||
happen if the callback from a query attempts to update the table
|
||||
being queried.
|
||||
</p></dd>
|
||||
<dt>SQLITE_INTERRUPT</dt>
|
||||
<dd><p>This value is returned if a call to <b>sqlite_interrupt()</b>
|
||||
interrupts a database operation in progress.
|
||||
</p></dd>
|
||||
</dl>
|
||||
</blockquote>
|
||||
|
||||
<h2>Querying without using a callback function</h2>
|
||||
|
||||
<p>The <b>sqlite_get_table()</b> function is a wrapper around
|
||||
<b>sqlite_exec()</b> that collects all the information from successive
|
||||
callbacks and write it into memory obtained from malloc(). This
|
||||
is a convenience function that allows the application to get the
|
||||
entire result of a database query with a single function call.</p>
|
||||
|
||||
<p>The main result from <b>sqlite_get_table()</b> is an array of pointers
|
||||
to strings. There is one element in this array for each column of
|
||||
each row in the result. NULL results are represented by a NULL
|
||||
pointer. In addition to the regular data, there is an added row at the
|
||||
beginning of the array that contains the names of each column of the
|
||||
result.</p>
|
||||
|
||||
<p>As an example, consider the following query:</p>
|
||||
|
||||
<blockquote>
|
||||
SELECT employee_name, login, host FROM users WHERE logic LIKE 'd%';
|
||||
</blockquote>
|
||||
|
||||
<p>This query will return the name, login and host computer name
|
||||
for every employee whose login begins with the letter "d". If this
|
||||
query is submitted to <b>sqlite_get_table()</b> the result might
|
||||
look like this:</p>
|
||||
|
||||
<blockquote>
|
||||
nrow = 2<br>
|
||||
ncolumn = 3<br>
|
||||
result[0] = "employee_name"<br>
|
||||
result[1] = "login"<br>
|
||||
result[2] = "host"<br>
|
||||
result[3] = "dummy"<br>
|
||||
result[4] = "No such user"<br>
|
||||
result[5] = 0<br>
|
||||
result[6] = "D. Richard Hipp"<br>
|
||||
result[7] = "drh"<br>
|
||||
result[8] = "zadok"
|
||||
</blockquote>
|
||||
|
||||
<p>Notice that the "host" value for the "dummy" record is NULL so
|
||||
the result[] array contains a NULL pointer at that slot.</p>
|
||||
|
||||
<p>Memory to hold the information returned by <b>sqlite_get_table()</b>
|
||||
is obtained from malloc(). But the calling function should not try
|
||||
to free this information directly. Instead, pass the complete table
|
||||
to <b>sqlite_free_table()</b> when the table is no longer needed.</p>
|
||||
|
||||
<p>The <b>sqlite_get_table()</b> routine returns the same integer
|
||||
result code as <b>sqlite_exec()</b>.</p>
|
||||
|
||||
<h2>Interrupting an SQLite operation</h2>
|
||||
|
||||
<p>The <b>sqlite_interrupt()</b> function can be called from a
|
||||
different thread or from a signal handler to the current database
|
||||
operation to exit at its first opportunity. When this happens,
|
||||
the <b>sqlite_exec()</b> routine (or the equivalent) that started
|
||||
the database operation will return SQLITE_INTERRUPT.</p>
|
||||
|
||||
<h2>Testing for a complete SQL statement</h2>
|
||||
|
||||
<p>The last interface routine to SQLite is a convenience function used
|
||||
<p>The next interface routine to SQLite is a convenience function used
|
||||
to test whether or not a string forms a complete SQL statement.
|
||||
If the <b>sqlite_complete()</b> function returns true when its input
|
||||
is a string, then the argument forms a complete SQL statement.
|
||||
@@ -230,7 +367,17 @@ then <b>sqlite_exec()</b> is called and the input buffer is reset. If
|
||||
the continuation prompt and another line of text is read and added to
|
||||
the input buffer.</p>
|
||||
|
||||
<h2>Changing the libraries reponse to locked files</h2>
|
||||
<h2>Library version string</h2>
|
||||
|
||||
<p>The SQLite library exports the string constant named
|
||||
<b>sqlite_version</b> which contains the version number of the
|
||||
library. The header file contains a macro SQLITE_VERSION
|
||||
with the same information. If desired, a program can compare
|
||||
the SQLITE_VERSION macro against the <b>sqlite_version</b>
|
||||
string constant to verify that the version number of the
|
||||
header file and the library match.</p>
|
||||
|
||||
<h2>Changing the libraries response to locked files</h2>
|
||||
|
||||
<p>The GDBM library supports database locks at the file level.
|
||||
If a GDBM database file is opened for reading, then that same
|
||||
@@ -271,6 +418,85 @@ will wait for the lock to clear for at least the number of milliseconds
|
||||
specified before it returns SQLITE_BUSY. Specifying zero milliseconds for
|
||||
the timeout restores the default behavior.</p>
|
||||
|
||||
<h2>Using the <tt>_printf()</tt> wrapper functions</h2>
|
||||
|
||||
<p>The four utility functions</p>
|
||||
|
||||
<p>
|
||||
<ul>
|
||||
<li><b>sqlite_exec_printf()</b></li>
|
||||
<li><b>sqlite_exec_vprintf()</b></li>
|
||||
<li><b>sqlite_get_table_printf()</b></li>
|
||||
<li><b>sqlite_get_table_vprintf()</b></li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>implement the same query functionality as <b>sqlite_exec()</b>
|
||||
and <b>sqlite_get_table()</b>. But instead of taking a complete
|
||||
SQL statement as their second argument, the four <b>_printf</b>
|
||||
routines take a printf-style format string. The SQL statement to
|
||||
be executed is generated from this format string and from whatever
|
||||
additional arguments are attached to the end of the function call.</p>
|
||||
|
||||
<p>There are two advantages to using the SQLite printf
|
||||
functions instead of <b>sprintf()</b>. First of all, with the
|
||||
SQLite printf routines, there is never a danger of overflowing a
|
||||
static buffer as there is with <b>sprintf()</b>. The SQLite
|
||||
printf routines automatically allocate (and later free)
|
||||
as much memory as is
|
||||
necessary to hold the SQL statements generated.</p>
|
||||
|
||||
<p>The second advantage the SQLite printf routines have over
|
||||
<b>sprintf()</b> is a new formatting option specifically designed
|
||||
to support string literals in SQL. Within the format string,
|
||||
the %q formatting option works very much like %s in that it
|
||||
reads a null-terminated string from the argument list and inserts
|
||||
it into the result. But %q translates the inserted string by
|
||||
making two copies of every single-quote (') character in the
|
||||
substituted string. This has the effect of escaping the end-of-string
|
||||
meaning of single-quote within a string literal.
|
||||
</p>
|
||||
|
||||
<p>Consider an example. Suppose you are trying to insert a string
|
||||
value into a database table where the string value was obtained from
|
||||
user input. Suppose the string to be inserted is stored in a variable
|
||||
named zString. The code to do the insertion might look like this:</p>
|
||||
|
||||
<blockquote><pre>
|
||||
sqlite_exec_printf(db,
|
||||
"INSERT INTO table1 VALUES('%s')",
|
||||
0, 0, 0, zString);
|
||||
</pre></blockquote>
|
||||
|
||||
<p>If the zString variable holds text like "Hello", then this statement
|
||||
will work just fine. But suppose the user enters a string like
|
||||
"Hi y'all!". The SQL statement generated reads as follows:
|
||||
|
||||
<blockquote><pre>
|
||||
INSERT INTO table1 VALUES('Hi y'all')
|
||||
</pre></blockquote>
|
||||
|
||||
<p>This is not valid SQL because of the apostrophy in the word "y'all".
|
||||
But if the %q formatting option is used instead of %s, like this:</p>
|
||||
|
||||
<blockquote><pre>
|
||||
sqlite_exec_printf(db,
|
||||
"INSERT INTO table1 VALUES('%q')",
|
||||
0, 0, 0, zString);
|
||||
</pre></blockquote>
|
||||
|
||||
<p>Then the generated SQL will look like the following:</p>
|
||||
|
||||
<blockquote><pre>
|
||||
INSERT INTO table1 VALUES('Hi y''all')
|
||||
</pre></blockquote>
|
||||
|
||||
<p>Here the apostrophy has been escaped and the SQL statement is well-formed.
|
||||
When generating SQL on-the-fly from data that might contain a
|
||||
single-quote character ('), it is always a good idea to use the
|
||||
SQLite printf routines and the %q formatting option instead of <b>sprintf</b>.
|
||||
</p>
|
||||
|
||||
<h2>Usage Examples</h2>
|
||||
|
||||
<p>For examples of how the SQLite C/C++ interface can be used,
|
||||
|
||||
+128
@@ -17,6 +17,134 @@ proc chng {date desc} {
|
||||
puts "<DD><P><UL>$desc</UL></P></DD>"
|
||||
}
|
||||
|
||||
chng {2001 Feb 19 (1.0.21)} {
|
||||
<li>The UPDATE statement was not working when the WHERE clause contained
|
||||
some terms that could be satisfied using indices and other terms that
|
||||
could not. Fixed.</li>
|
||||
}
|
||||
|
||||
chng {2001 Feb 11 (1.0.20)} {
|
||||
<li>Merge development changes into the main trunk. Future work toward
|
||||
using a BTree file structure will use a separate CVS source tree. This
|
||||
CVS tree will continue to support the GDBM version of SQLite only.</li>
|
||||
}
|
||||
|
||||
chng {2001 Feb 6 (1.0.19)} {
|
||||
<li>Fix a strange (but valid) C declaration that was causing problems
|
||||
for QNX. No logical changes.</li>
|
||||
}
|
||||
|
||||
chng {2001 Jan 4 (1.0.18)} {
|
||||
<li>Print the offending SQL statement when an error occurs.</li>
|
||||
<li>Do not require commas between constraints in CREATE TABLE statements.</li>
|
||||
<li>Added the "-echo" option to the shell.</li>
|
||||
<li>Changes to comments.</li>
|
||||
}
|
||||
|
||||
chng {2000 Dec 10 (1.0.17)} {
|
||||
<li>Rewrote <b>sqlite_complete()</b> to make it faster.</li>
|
||||
<li>Minor tweaks to other code to make it run a little faster.</li>
|
||||
<li>Added new tests for <b>sqlite_complete()</b> and for memory leaks.</li>
|
||||
}
|
||||
|
||||
chng {2000 Dec 4 (1.0.16)} {
|
||||
<li>Documentation updates. Mostly fixing of typos and spelling errors.</li>
|
||||
}
|
||||
|
||||
chng {2000 Oct 23 (1.0.15)} {
|
||||
<li>Documentation updates</li>
|
||||
<li>Some sanity checking code was removed from the inner loop of vdbe.c
|
||||
to help the library to run a little faster. The code is only
|
||||
removed if you compile with -DNDEBUG.</li>
|
||||
}
|
||||
|
||||
chng {2000 Oct 19 (1.0.14)} {
|
||||
<li>Added a "memory:" backend driver that stores its database in an
|
||||
in-memory hash table.</li>
|
||||
}
|
||||
|
||||
chng {2000 Oct 18 (1.0.13)} {
|
||||
<li>Break out the GDBM driver into a separate file in anticipation
|
||||
to added new drivers.</li>
|
||||
<li>Allow the name of a database to be prefixed by the driver type.
|
||||
For now, the only driver type is "gdbm:".</li>
|
||||
}
|
||||
|
||||
chng {2000 Oct 16 (1.0.12)} {
|
||||
<li>Fixed an off-by-one error that was causing a coredump in
|
||||
the '%q' format directive of the new
|
||||
<b>sqlite_..._printf()</b> routines.</li>
|
||||
<li>Added the <b>sqlite_interrupt()</b> interface.</li>
|
||||
<li>In the shell, <b>sqlite_interrupt()</b> is invoked when the
|
||||
user presses Control-C</li>
|
||||
<li>Fixed some instances where <b>sqlite_exec()</b> was
|
||||
returning the wrong error code.</li>
|
||||
}
|
||||
|
||||
chng {2000 Oct 11 (1.0.10)} {
|
||||
<li>Added notes on how to compile for Windows95/98.</li>
|
||||
<li>Removed a few variables that were not being used. Etc.</li>
|
||||
}
|
||||
|
||||
chng {2000 Oct 8 (1.0.9)} {
|
||||
<li>Added the <b>sqlite_..._printf()</b> interface routines.</li>
|
||||
<li>Modified the <b>sqlite</b> shell program to use the new interface
|
||||
routines.</li>
|
||||
<li>Modified the <b>sqlite</b> shell program to print the schema for
|
||||
the built-in SQLITE_MASTER table, if explicitly requested.</li>
|
||||
}
|
||||
|
||||
chng {2000 Sep 30 (1.0.8)} {
|
||||
<li>Begin writing documentation on the TCL interface.</li>
|
||||
}
|
||||
|
||||
chng {2000 Sep 29 (Not Released)} {
|
||||
<li>Added the <b>sqlite_get_table()</b> API</li>
|
||||
<li>Updated the documentation for due to the above change.</li>
|
||||
<li>Modified the <b>sqlite</b> shell to make use of the new
|
||||
sqlite_get_table() API in order to print a list of tables
|
||||
in multiple columns, similar to the way "ls" prints filenames.</li>
|
||||
<li>Modified the <b>sqlite</b> shell to print a semicolon at the
|
||||
end of each CREATE statement in the output of the ".schema" command.</li>
|
||||
}
|
||||
|
||||
chng {2000 Sep 21 (Not Released)} {
|
||||
<li>Change the tclsqlite "eval" method to return a list of results if
|
||||
no callback script is specified.</li>
|
||||
<li>Change tclsqlite.c to use the Tcl_Obj interface</li>
|
||||
<li>Add tclsqlite.c to the libsqlite.a library</li>
|
||||
}
|
||||
|
||||
chng {2000 Sep 13 (Version 1.0.5)} {
|
||||
<li>Changed the print format for floating point values from "%g" to "%.15g".
|
||||
</li>
|
||||
<li>Changed the comparison function so that numbers in exponential notation
|
||||
(ex: 1.234e+05) sort in numerical order.</li>
|
||||
}
|
||||
|
||||
chng {2000 Aug 28 (Version 1.0.4)} {
|
||||
<li>Added functions <b>length()</b> and <b>substr()</b>.</li>
|
||||
<li>Fix a bug in the <b>sqlite</b> shell program that was causing
|
||||
a coredump when the output mode was "column" and the first row
|
||||
of data contained a NULL.</li>
|
||||
}
|
||||
|
||||
chng {2000 Aug 22 (Version 1.0.3)} {
|
||||
<li>In the sqlite shell, print the "Database opened READ ONLY" message
|
||||
to stderr instead of stdout.</li>
|
||||
<li>In the sqlite shell, now print the version number on initial startup.</li>
|
||||
<li>Add the <b>sqlite_version[]</b> string constant to the library</li>
|
||||
<li>Makefile updates</li>
|
||||
<li>Bug fix: incorrect VDBE code was being generated for the following
|
||||
circumstance: a query on an indexed table containing a WHERE clause with
|
||||
an IN operator that had a subquery on its right-hand side.</li>
|
||||
}
|
||||
|
||||
chng {2000 Aug 18 (Version 1.0.1)} {
|
||||
<li>Fix a bug in the configure script.</li>
|
||||
<li>Minor revisions to the website.</li>
|
||||
}
|
||||
|
||||
chng {2000 Aug 17 (Version 1.0)} {
|
||||
<li>Change the <b>sqlite</b> program so that it can read
|
||||
databases for which it lacks write permission. (It used to
|
||||
|
||||
+62
-2
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Run this Tcl script to generate the crosscompile.html file.
|
||||
#
|
||||
set rcsid {$Id: crosscompile.tcl,v 1.2 2000/07/31 19:16:32 drh Exp $}
|
||||
set rcsid {$Id: crosscompile.tcl,v 1.4 2001/01/31 13:28:09 drh Exp $}
|
||||
|
||||
puts {<html>
|
||||
<head>
|
||||
@@ -23,7 +23,9 @@ binaries. See the website for details.</p>
|
||||
|
||||
<p>This page describes how you can use MinGW configured as a
|
||||
cross-compiler running under RedHat 6.0 Linux to generate a
|
||||
binary for SQLite that runs under WinNT.</p>
|
||||
binary for SQLite that runs under WinNT.
|
||||
Some additional steps (described <a href="#win95">below</a>)
|
||||
are needed to target Win95/98.</p>
|
||||
}
|
||||
|
||||
proc Code {body} {
|
||||
@@ -123,6 +125,64 @@ make
|
||||
</pre></blockquote>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<a name="win95">
|
||||
<h2>Targetting Windows95/98 instead of WindowsNT</h2>
|
||||
|
||||
<p>A small amount of additional work is needed to get SQLite running
|
||||
under Windows95/98. The first problem is that the LockFile() and
|
||||
UnlockFile() API that the Roth GDBM port uses does not work under
|
||||
Windows95. The easiest workaround is to just disable file locking
|
||||
in the GDBM library. You can do so by appending a few lines of code
|
||||
to the end of one of the GDBM source files and compiling the library
|
||||
using a special command-line option. Replace step (4) above as
|
||||
follows:</p>
|
||||
|
||||
<ol>
|
||||
<li value="4"><p>
|
||||
Append text to the <b>systems.h</b> source file as follows:</p>
|
||||
|
||||
<blockquote><pre>
|
||||
cat >>systems.h <<\END
|
||||
#ifdef NO_LOCKS
|
||||
#undef UNLOCK_FILE
|
||||
#define UNLOCK_FILE(x)
|
||||
#undef READLOCK_FILE
|
||||
#define READLOCK_FILE(x) lock_val=0;
|
||||
#undef WRITELOCK_FILE
|
||||
#define WRITELOCK_FILE(x) lock_val=0;
|
||||
#endif
|
||||
END
|
||||
</pre></blockquote>
|
||||
|
||||
<p>Then manually build the GDBM library with the extra
|
||||
"NO_LOCKS" define as follows:</p>
|
||||
<blockquote><pre>
|
||||
i386-mingw32-gcc -DWIN32=1 -DNO_LOCKS -O2 -c *.c
|
||||
i386-mingw32-ar cr libgdbm.a *.o
|
||||
i386-mingw32-ranlib libgdbm.a
|
||||
cd ..
|
||||
</pre></blockquote>
|
||||
</p></li>
|
||||
</ol>
|
||||
|
||||
<p>Note that the locking problem has been reported and may actually
|
||||
be fixed in the Roth GDBM distribution by the time you read this.
|
||||
You should probably check before you make the above changes.</p>
|
||||
|
||||
<p>The above is all you have to do to get SQLite to work on Windows95.
|
||||
But one more step is required to get it to work <i>well</i>. It
|
||||
turns out that SQLite make heavy use of <b>malloc()</b> and
|
||||
<b>free()</b> and the implementation of this functions
|
||||
on Windows95 is particular poor. Large database queries will run
|
||||
more than 10 times faster if you substitute a better memory allocator
|
||||
such as the one by
|
||||
<a href="http://g.oswego.edu/dl/html/malloc.html">Doug Lea</a>.
|
||||
A copy of Doug's allocator is included in the <b>contrib</b>
|
||||
directory of the source tree. Speed improvements are also reported
|
||||
on WindowsNT when alternative memory allocators are used, though
|
||||
the speedup is not as dramatic as it is with WIndows95.</p>
|
||||
|
||||
}
|
||||
puts {
|
||||
<p><hr /></p>
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#
|
||||
# Run this Tcl script to generate the dynload.html file.
|
||||
#
|
||||
set rcsid {$Id: dynload.tcl,v 1.1 2001/02/11 16:58:22 drh Exp $}
|
||||
|
||||
puts {<html>
|
||||
<head>
|
||||
<title>How to build a dynamically loaded Tcl extension for SQLite</title>
|
||||
</head>
|
||||
<body bgcolor=white>
|
||||
<h1 align=center>
|
||||
How To Build A Dynamically Loaded Tcl Extension
|
||||
</h1>}
|
||||
puts {<p>
|
||||
<i>This note was contributed by
|
||||
<a href="bsaunder@tampabay.rr.com.nospam">Bill Saunders</a>. Thanks, Bill!</i>
|
||||
|
||||
<p>
|
||||
To compile the SQLite Tcl extension into a dynamically loaded module
|
||||
I did the following:
|
||||
</p>
|
||||
|
||||
<ol>
|
||||
<li><p>Do a standard compile
|
||||
(I had a dir called bld at the same level as sqlite ie
|
||||
/root/bld
|
||||
/root/sqlite
|
||||
I followed the directions and did a standard build in the bld
|
||||
directory)</p></li>
|
||||
|
||||
<li><p>
|
||||
Now do the following in the bld directory
|
||||
<blockquote><pre>
|
||||
gcc -shared -I. -lgdbm ../sqlite/src/tclsqlite.c libsqlite.a -o sqlite.so
|
||||
</pre></blockquote></p></li>
|
||||
|
||||
<li><p>
|
||||
This should produce the file sqlite.so in the bld directory</p></li>
|
||||
|
||||
<li><p>
|
||||
Create a pkgIndex.tcl file that contains this line
|
||||
|
||||
<blockquote><pre>
|
||||
package ifneeded sqlite 1.0 [list load [file join $dir sqlite.so]]
|
||||
</pre></blockquote></p></li>
|
||||
|
||||
<li><p>
|
||||
To use this put sqlite.so and pkgIndex.tcl in the same directory</p></li>
|
||||
|
||||
<li><p>
|
||||
From that directory start wish</p></li>
|
||||
|
||||
<li><p>
|
||||
Execute the following tcl command (tells tcl where to fine loadable
|
||||
modules)
|
||||
<blockquote><pre>
|
||||
lappend auto_path [exec pwd]
|
||||
</pre></blockquote></p></li>
|
||||
|
||||
<li><p>
|
||||
Load the package
|
||||
<blockquote><pre>
|
||||
package require sqlite
|
||||
</pre></blockquote></p></li>
|
||||
|
||||
<li><p>
|
||||
Have fun....</p></li>
|
||||
</ul>
|
||||
|
||||
</body></html>}
|
||||
+41
-30
@@ -1,12 +1,12 @@
|
||||
#
|
||||
# Run this TCL script to generate HTML for the index.html file.
|
||||
#
|
||||
set rcsid {$Id: index.tcl,v 1.27 2000/08/17 10:22:34 drh Exp $}
|
||||
set rcsid {$Id: index.tcl,v 1.30 2001/02/19 18:24:22 drh Exp $}
|
||||
|
||||
puts {<html>
|
||||
<head><title>SQLite: An SQL Database Engine Built Atop GDBM</title></head>
|
||||
<head><title>SQLite: An SQL Database Library Built Atop GDBM</title></head>
|
||||
<body bgcolor=white>
|
||||
<h1 align=center>SQLite: An SQL Database Engine Built Atop
|
||||
<h1 align=center>SQLite: An SQL Database Library Built Atop
|
||||
<a href="http://www.gnu.org/software/gdbm/gdbm.html">GDBM</a></h1>
|
||||
<p align=center>}
|
||||
puts "This page was last modified on [lrange $rcsid 3 4] GMT<br>"
|
||||
@@ -17,33 +17,17 @@ puts {</p>}
|
||||
|
||||
puts {<h2>Introduction</h2>
|
||||
|
||||
<p>SQLite is an SQL database engine built on top of the
|
||||
<a href="http://www.gnu.org/software/gdbm/gdbm.html">GDBM library</a>.
|
||||
SQLite includes a standalone command-line
|
||||
access program (<a href="sqlite.html">sqlite</a>)
|
||||
and a C library (<a href="c_interface.html">libsqlite.a</a>)
|
||||
that can be linked
|
||||
with a C/C++ program to provide SQL database access without
|
||||
a separate RDBMS.</p>
|
||||
<p>SQLite is an SQL database library
|
||||
(<a href="c_interface.html">libsqlite.a</a>) that uses
|
||||
<a href="http://www.gnu.org/software/gdbm/gdbm.html">GDBM</a>
|
||||
as its underlying file storage mechanism.
|
||||
Programs that link the SQLite library can have SQL database
|
||||
access without running a separate RDBMS process.
|
||||
The distribution comes with a standalone command-line
|
||||
access program (<a href="sqlite.html">sqlite</a>) that can
|
||||
be used to administer an SQLite database and which serves as
|
||||
an example of how to use the SQLite library.</p>
|
||||
|
||||
<h2>Important News Flash!</h2>
|
||||
<p>
|
||||
The SQLite file format was changed in an incompatible way on
|
||||
Aug 2, 2000. If you are updated the library and have databases
|
||||
built using the old version of the library, you should save your
|
||||
old databases into an ASCII file then reimport those
|
||||
database using the new library. For example, if you change the
|
||||
name of the old <b>sqlite</b> utility to "old-sqlite" and
|
||||
change the name of the old database directory to "old-db", then
|
||||
you can reconstruct the database as follows:</p>
|
||||
|
||||
<blockquote><pre>
|
||||
echo .dump | old-sqlite old-db | sqlite db
|
||||
</pre></blockquote>
|
||||
|
||||
<p>This file format change was made to work around a potential
|
||||
inefficiency in GDBM that comes up when large indices are created
|
||||
on tables where many entries in the table have the same index key.</p>
|
||||
|
||||
<h2>Features</h2>
|
||||
|
||||
@@ -56,7 +40,7 @@ on tables where many entries in the table have the same index key.</p>
|
||||
<li>Very simple
|
||||
<a href="c_interface.html">C/C++ interface</a> requires the use of only
|
||||
three functions and one opaque structure.</li>
|
||||
<li>A <a href="http://dev.scriptics.com/">Tcl</a> interface is
|
||||
<li>A <a href="tclsqlite.html">Tcl</a> interface is
|
||||
included.</li>
|
||||
<li>Command-line access program <a href="sqlite.html">sqlite</a> uses
|
||||
the <a href="http://www.google.com/search?q=gnu+readline+library">GNU
|
||||
@@ -77,6 +61,12 @@ all code except for a few areas which are unreachable or which are
|
||||
only reached when <tt>malloc()</tt> fails. The code has been tested
|
||||
for memory leaks and is found to be clean.</p>
|
||||
|
||||
<p><b>Important Note:</b> A bug was found in the processing of UPDATE
|
||||
statements when the WHERE clause contained some terms that could be
|
||||
satisfied using indices and other terms which could not. The problem
|
||||
was fixed in version 1.0.21. Users of prior versions of SQLite should
|
||||
consider upgrading.</p>
|
||||
|
||||
<p>
|
||||
Among the SQL features that SQLite does not currently implement are:</p>
|
||||
|
||||
@@ -88,6 +78,26 @@ Among the SQL features that SQLite does not currently implement are:</p>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<h2>Important News Flash!</h2>
|
||||
<p>
|
||||
The SQLite file format was changed in an incompatible way on
|
||||
Aug 2, 2000 (prior to version 1.0, when SQLite was still in Beta).
|
||||
If you are updated the library and have databases
|
||||
built using the old version of the library, you should save your
|
||||
old databases into an ASCII file then reimport the
|
||||
database using the new library. For example, if you change the
|
||||
name of the old <b>sqlite</b> utility to "old-sqlite" and
|
||||
change the name of the old database directory to "old-db", then
|
||||
you can reconstruct the database as follows:</p>
|
||||
|
||||
<blockquote><pre>
|
||||
echo .dump | old-sqlite old-db | sqlite db
|
||||
</pre></blockquote>
|
||||
|
||||
<p>This file format change was made to work around a potential
|
||||
inefficiency in GDBM that comes up when large indices are created
|
||||
on tables where many entries in the table have the same index key.</p>
|
||||
|
||||
<h2>Documentation</h2>
|
||||
|
||||
<p>The following documentation is currently available:</p>
|
||||
@@ -97,6 +107,7 @@ Among the SQL features that SQLite does not currently implement are:</p>
|
||||
command-line utility.</li>
|
||||
<li>The <a href="lang.html">SQL Language</a> subset understood by SQLite.</li>
|
||||
<li>The <a href="c_interface.html">C/C++ Interface</a>.</li>
|
||||
<li>The <a href="tclsqlite.html">Tcl Interface</a>.</li>
|
||||
<li>The <a href="fileformat.html">file format</a> used by SQLite databases.</li>
|
||||
<li>The <a href="arch.html">Architecture of the SQLite Library</a> describes
|
||||
how the library is put together.</li>
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
#
|
||||
# Run this Tcl script to generate the tclsqlite.html file.
|
||||
#
|
||||
set rcsid {$Id: tclsqlite.tcl,v 1.3 2000/10/23 13:16:33 drh Exp $}
|
||||
|
||||
puts {<html>
|
||||
<head>
|
||||
<title>The Tcl interface to the SQLite library</title>
|
||||
</head>
|
||||
<body bgcolor=white>
|
||||
<h1 align=center>
|
||||
The Tcl interface to the SQLite library
|
||||
</h1>}
|
||||
puts "<p align=center>
|
||||
(This page was last modified on [lrange $rcsid 3 4] GMT)
|
||||
</p>"
|
||||
|
||||
puts {
|
||||
<p>The SQLite library is designed to be very easy to use from
|
||||
a Tcl or Tcl/Tk script. This document gives an overview of the Tcl
|
||||
programming interface.</p>
|
||||
|
||||
<h2>The API</h2>
|
||||
|
||||
<p>The interface to the SQLite library consists of single
|
||||
tcl command named <b>sqlite</b>. Because there is only this
|
||||
one interface command, the interface is not placed in a separate
|
||||
namespace.</p>
|
||||
|
||||
<p>The <b>sqlite</b> command is used as follows:</p>
|
||||
|
||||
<blockquote>
|
||||
<b>sqlite</b> <i>dbcmd database-name</i>
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
The <b>sqlite</b> command opens the database named in the second
|
||||
argument. If the database does not already exist, it is
|
||||
automatically created.
|
||||
The <b>sqlite</b> command also creates a new Tcl
|
||||
command to control the database. The name of the new Tcl command
|
||||
is given by the first argument. This approach is similar to the
|
||||
way widgets are created in Tk.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The name of the database is usually either the name of a directory
|
||||
that will contain the GDBM files that comprise the database, or it is the
|
||||
name of the directory prefaced by "<b>gdbm:</b>". The second form
|
||||
of the name is a new feature beginning in SQLite version 1.0.14 that
|
||||
allows you to select alternative database backends. The default
|
||||
backend is GDBM. But you can also select to store the database in
|
||||
a hash table in memory by using the prefix "<b>memory:</b>".
|
||||
Other backends may be added in the future.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Every time you open an SQLite database with the <b>memory:</b> prefix
|
||||
on the database name, you get a new in-memory database. This is true
|
||||
even if you open two databases with the same name. Furthermore,
|
||||
an in-memory database is automatically deleted when the database is
|
||||
closed and so is not useful for persistant storage like a normal
|
||||
database. But the use of an in-memory SQL database does give Tcl/Tk
|
||||
a powerful new data storage mechanism that can do things that are
|
||||
difficult to do with only Tcl array variables. In fact, the
|
||||
hash-table backend for SQLite was created for the sole purpose of
|
||||
providing better data structure support to the Tcl language.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Once an SQLite database is open, it can be controlled using
|
||||
methods of the <i>dbcmd</i>. There are currently 5 methods
|
||||
defined:</p>
|
||||
|
||||
<p>
|
||||
<ul>
|
||||
<li> busy
|
||||
<li> close
|
||||
<li> complete
|
||||
<li> eval
|
||||
<li> timeout
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>We will explain all of these methods, though not in that order.
|
||||
We will be begin with the "close" method.</p>
|
||||
|
||||
<h2>The "close" method</h2>
|
||||
|
||||
<p>
|
||||
As its name suggests, the "close" method to an SQLite database just
|
||||
closes the database. This has the side-effect of deleting the
|
||||
<i>dbcmd</i> Tcl command. Here is an example of opening and then
|
||||
immediately closing a database:
|
||||
</p>
|
||||
|
||||
<blockquote>
|
||||
<b>sqlite db1 ./testdb<br>
|
||||
db1 close</b>
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
If you delete the <i>dbcmd</i> directly, that has the same effect
|
||||
as invoking the "close" method. So the following code is equivalent
|
||||
to the previous:</p>
|
||||
|
||||
<blockquote>
|
||||
<b>sqlite db1 ./testdb<br>
|
||||
rename db1 {}</b>
|
||||
</blockquote>
|
||||
|
||||
<h2>The "eval" method</h2>
|
||||
|
||||
<p>
|
||||
The most useful <i>dbcmd</i> method is "eval". The eval method is used
|
||||
to execute SQL on the database. The syntax of the eval method looks
|
||||
like this:</p>
|
||||
|
||||
<blockquote>
|
||||
<i>dbcmd</i> <b>eval</b> <i>sql</i>
|
||||
?<i>array-name script</i>?
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
The job of the eval method is to execute the SQL statement or statements
|
||||
given in the second argument. For example, to create a new table in
|
||||
a database, you can do this:</p>
|
||||
|
||||
<blockquote>
|
||||
<b>sqlite db1 ./testdb<br>
|
||||
db1 eval {CREATE TABLE t1(a int, b text)}</b>
|
||||
</blockquote>
|
||||
|
||||
<p>The above code creates a new table named <b>t1</b> with columns
|
||||
<b>a</b> and <b>b</b>. What could be simpler?</p>
|
||||
|
||||
<p>Query results are returned as a list of column values. If a
|
||||
query requests 2 columns and there are 3 rows matching the query,
|
||||
then the returned list will contain 6 elements. For example:</p>
|
||||
|
||||
<blockquote>
|
||||
<b>db1 eval {INSERT INTO t1 VALUES(1,'hello')}<br>
|
||||
db1 eval {INSERT INTO t1 VALUES(2,'goodbye')}<br>
|
||||
db1 eval {INSERT INTO t1 VALUES(3,'howdy!')}<br>
|
||||
set x [db1 eval {SELECT * FROM t1 ORDER BY a}]</b>
|
||||
</blockquote>
|
||||
|
||||
<p>The variable <b>$x</b> is set by the above code to</p>
|
||||
|
||||
<blockquote>
|
||||
<b>1 hello 2 goodbye 3 howdy!</b>
|
||||
</blockquote>
|
||||
|
||||
<p>You can also process the results of a query one row at a time
|
||||
by specifying the name of an array variable and a script following
|
||||
the SQL code. For each row of the query result, the value of each
|
||||
column will be inserted into the array variable and the script will
|
||||
be executed. For instance:</p>
|
||||
|
||||
<blockquote>
|
||||
<b>db1 eval {SELECT * FROM t1 ORDER BY a} values {<br>
|
||||
parray values<br>
|
||||
puts ""<br>
|
||||
}</b>
|
||||
</blockquote>
|
||||
|
||||
<p>This last code will give the following output:</p>
|
||||
|
||||
<blockquote><b>
|
||||
values(*) = a b<br>
|
||||
values(a) = 1<br>
|
||||
values(b) = hello<p>
|
||||
|
||||
values(*) = a b<br>
|
||||
values(a) = 2<br>
|
||||
values(b) = goodbye<p>
|
||||
|
||||
values(*) = a b<br>
|
||||
values(a) = 3<br>
|
||||
values(b) = howdy!</b>
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
For each column in a row of the result, the name of that column
|
||||
is used as an index in to array. The value of the column is stored
|
||||
in the corresponding array entry. The special array index * is
|
||||
used to store a list of column names in the order that they appear.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If the array variable name is the empty string, then the value of
|
||||
each column is stored in a variable with the same name as the column
|
||||
itself. For example:
|
||||
</p>
|
||||
|
||||
<blockquote>
|
||||
<b>db1 eval {SELECT * FROM t1 ORDER BY a} {} {<br>
|
||||
puts "a=$a b=$b"<br>
|
||||
}</b>
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
From this we get the following output
|
||||
</p>
|
||||
|
||||
<blockquote><b>
|
||||
a=1 b=hello<br>
|
||||
a=2 b=goodbye<br>
|
||||
a=3 b=howdy!</b>
|
||||
</blockquote>
|
||||
|
||||
<h2>The "complete" method</h2>
|
||||
|
||||
<p>
|
||||
The "complete" method takes a string of supposed SQL as its only argument.
|
||||
It returns TRUE if the string is a complete statement of SQL and FALSE if
|
||||
there is more to be entered.</p>
|
||||
|
||||
<p>The "complete" method is useful when building interactive applications
|
||||
in order to know when the user has finished entering a line of SQL code.
|
||||
This is really just an interface to the <b>sqlite_complete()</b> C
|
||||
function. Refer to the <a href="c_interface.html">C/C++ interface</a>
|
||||
specification for additional information.</p>
|
||||
|
||||
<h2>The "timeout" method</h2>
|
||||
|
||||
<p>The "timeout" method is used to control how long the SQLite library
|
||||
will wait for locks to clear before giving up on a database transaction.
|
||||
The default timeout is 0 millisecond. (In other words, the default behavior
|
||||
is not to wait at all.)</p>
|
||||
|
||||
<p>The GDBM backend allows multiple simultaneous
|
||||
readers or a single writer but not both. If any process is writing to
|
||||
the database no other process is allows to read or write. If any process
|
||||
is reading the database other processes are allowed to read but not write.
|
||||
Each GDBM file is locked separately. Because each SQL table is stored as
|
||||
a separate file, it is possible for different processes to write to different
|
||||
database tables at the same time, just not the same table.</p>
|
||||
|
||||
<p>When SQLite tries to open a GDBM file and finds that it is locked, it
|
||||
can optionally delay for a short while and try to open the file again.
|
||||
This process repeats until the query times out and SQLite returns a
|
||||
failure. The timeout is adjustable. It is set to 0 by default so that
|
||||
if a GDBM file is locked, the SQL statement fails immediately. But you
|
||||
can use the "timeout" method to change the timeout value to a positive
|
||||
number. For example:</p>
|
||||
|
||||
<blockquote><b>db1 timeout 2000</b></blockquote>
|
||||
|
||||
<p>The argument to the timeout method is the maximum number of milliseconds
|
||||
to wait for the lock to clear. So in the example above, the maximum delay
|
||||
would be 2 seconds.</p>
|
||||
|
||||
<h2>The "busy" method</h2>
|
||||
|
||||
<p>The "busy" method, like "timeout", only comes into play when a GDBM
|
||||
file is locked. But the "busy" method gives the programmer much more
|
||||
control over what action to take. The "busy" method specifies a callback
|
||||
Tcl procedure that is invoked whenever SQLite tries to open a locked
|
||||
GDBM file. This callback can do whatever is desired. Presumably, the
|
||||
callback will do some other useful work for a short while then return
|
||||
so that the lock can be tried again. The callback procedure should
|
||||
return "0" if it wants SQLite to try again to open the GDBM file and
|
||||
should return "1" if it wants SQLite to abandon the current operation.
|
||||
|
||||
}
|
||||
|
||||
puts {
|
||||
<p><hr /></p>
|
||||
<p><a href="index.html"><img src="/goback.jpg" border=0 />
|
||||
Back to the SQLite Home Page</a>
|
||||
</p>
|
||||
|
||||
</body></html>}
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Run this Tcl script to generate the vdbe.html file.
|
||||
#
|
||||
set rcsid {$Id: vdbe.tcl,v 1.5 2000/07/30 20:04:43 drh Exp $}
|
||||
set rcsid {$Id: vdbe.tcl,v 1.6 2000/11/28 20:46:41 drh Exp $}
|
||||
|
||||
puts {<html>
|
||||
<head>
|
||||
@@ -220,7 +220,7 @@ cursor to the "examp" file.</p>
|
||||
preprocessor macro, then
|
||||
there is a special SQL comment that will cause the
|
||||
the VDBE to traces the execution of programs.
|
||||
Though this features was originally intended for testing
|
||||
Though this feature was originally intended for testing
|
||||
and debugging, it might also be useful in learning about
|
||||
how the VDBE operates.
|
||||
Use the "<tt>--vdbe-trace-on--</tt>" comment to
|
||||
@@ -306,7 +306,7 @@ int Callback(void *pUserData, int nColumn, char *azData[], char *azColumnName[])
|
||||
|
||||
<p>The SQLite library supplies the VDBE with a pointer to the callback function
|
||||
and the <b>pUserData</b> pointer. (Both the callback and the user data were
|
||||
originally passed in as argument to the <b>sqlite_exec()</b> API function.)
|
||||
originally passed in as arguments to the <b>sqlite_exec()</b> API function.)
|
||||
The job of the VDBE is to
|
||||
come up with values for <b>nColumn</b>, <b>azData[]</b>,
|
||||
and <b>azColumnName[]</b>.
|
||||
@@ -769,7 +769,7 @@ a small percentage of the rows end up in the result. This can
|
||||
take a long time on a big table. To speed things up, SQLite
|
||||
can use an index.</p>
|
||||
|
||||
<p>An GDBM file associates a key with some data. For a SQLite
|
||||
<p>A GDBM file associates a key with some data. For a SQLite
|
||||
table, the GDBM file is set up so that the key is a integer
|
||||
and the data is the information for one row of the table.
|
||||
Indices in SQLite reverse this arrangement. The GDBM key
|
||||
@@ -1172,7 +1172,7 @@ with that key is brought into focus. A new bucket is created with
|
||||
the key if one did not previously exist. Once the bucket is in
|
||||
focus, the memory locations of the bucket are used to accumulate
|
||||
the values of the various aggregate functions. After the query
|
||||
loop terminates, the each bucket is visited once to generate a
|
||||
loop terminates, each bucket is visited once to generate a
|
||||
single row of the results.</p>
|
||||
|
||||
<p>An example will help to clarify this concept. Consider the
|
||||
@@ -1476,7 +1476,7 @@ VDBE halts.</p>
|
||||
All this code does is make an entry in the temporary table for each
|
||||
row of the examp2 table. The key for each temporary table entry
|
||||
is the "three" column of examp2 and the data
|
||||
entries is an empty string since it is never used.</p>
|
||||
is an empty string since it is never used.</p>
|
||||
|
||||
<p>The outer SELECT is implemented by instructions 8 through 19. In
|
||||
particular, the WHERE clause containing the IN operator is implemented
|
||||
|
||||
Reference in New Issue
Block a user