Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
+35
-6
@@ -47,25 +47,30 @@ 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 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/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 +120,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 +138,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
|
||||
@@ -154,9 +165,18 @@ insert.o: $(TOP)/src/insert.c $(HDR)
|
||||
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 +226,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 +245,8 @@ PUBLISH = \
|
||||
vdbe.html \
|
||||
c_interface.html \
|
||||
crosscompile.html \
|
||||
mingw.html
|
||||
mingw.html \
|
||||
tclsqlite.html
|
||||
|
||||
website: $(PUBLISH)
|
||||
|
||||
@@ -230,6 +254,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)
|
||||
|
||||
##########
|
||||
|
||||
@@ -1,76 +1,82 @@
|
||||
C Version\s1.0\s(CVS\s499)
|
||||
D 2000-08-17T10:25:00
|
||||
C Version\s1.0.17\s(CVS\s486)
|
||||
D 2000-12-10T18:40:00
|
||||
F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4
|
||||
F Makefile.in 8a4cc1bdcbb870f76d0c769136cf973af07586a4
|
||||
F Makefile.in 0b1fdafa55e1bf4d3a4f5213544130e66ef32052
|
||||
F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958
|
||||
F VERSION 61652cd1568dcf2614df833eba241755eee34e89
|
||||
F configure 51063d594190fa085f909cefc9427241088bec4f x
|
||||
F configure.in a04f02ba61ed09a00e862b4f78b91b06a559e0b5
|
||||
F VERSION 2436af0010b224c49639a6ce276551a862a08115
|
||||
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/build.c e2ceba852dc45ca899e68a042b29c3daab011575
|
||||
F src/dbbe.c 7e01384320075bf1d3e7fb54984df73435908809
|
||||
F src/dbbe.h d175a04b35ea75078274e059dcbcbf7c1262d42a
|
||||
F src/dbbegdbm.c 4ac7222afff0cf91014803f8791740b6da825a2b
|
||||
F src/dbbemem.c b55faed2e4719db47ade3483e85f7cb4b5fad39a
|
||||
F src/delete.c 4d491eaf61b515516749c7ed68fa3b2ee8a09065
|
||||
F src/expr.c 2fa63f086707176d09092e71832f9bbdc6a8ac85
|
||||
F src/expr.c 7d7646afd52d1448237e5f517454cfb2d3d394d6
|
||||
F src/insert.c f146f149ad2422a1dc3bfa7a1651a25940f98958
|
||||
F src/main.c ba16b81890d962821bb90f0a4de9a29b0e495eb2
|
||||
F src/main.c 52355458131977c1ac40a006ce2fe2035f39069c
|
||||
F src/parse.y 5d199034de5d29ebedb42c1c51f34db4df40cbe5
|
||||
F src/select.c d382e96c2221d08367cc87976f2b574537c9de97
|
||||
F src/shell.c 0aabf9e5e89fc8ac7a56de7f05255ed6864170f2
|
||||
F src/printf.c 1efb6b3e7f28a93be57132de3f8f400d2ac1460e
|
||||
F src/select.c c1de8ac34131324fa05664b06b0ae1ee9c02905d
|
||||
F src/shell.c 146149928c7dca23ceebd6aa53026e9b02d3b4e4
|
||||
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/sqlite.h.in fd86903777f1ee7bd0465243224a0fd2100eedc8
|
||||
F src/sqliteInt.h ed06214fc7054e43182ac351ebde913ede708639
|
||||
F src/table.c 5be76051a8ed6f6bfa641f4adc52529efa34fbf9
|
||||
F src/tclsqlite.c 178adf318eab2ff480c288a87541d4ab1c37d985
|
||||
F src/tokenize.c 6843f1d7a5d2ee08ceb10bdecfcc8684131ffcf7
|
||||
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/util.c 0298100e6427a4b644f767ede12276fa7170fbb6
|
||||
F src/vdbe.c 3ddd7e3d17c954fc50afd5e73898371da09ad1c6
|
||||
F src/vdbe.h 140cdec3c56f70483e169f8ae657bd90f9fd6e98
|
||||
F src/where.c 3dfad2ffd0aa994d5eceac88852f7189c8d1d3c8
|
||||
F test/all.test 15cac2f6b2d4c55bf896212aff3cc9d6597b0490
|
||||
F test/copy.test b77a1214bd7756f2849d5c4fa6e715c0ff0c34eb
|
||||
F test/dbbe.test c6079572516aeb2739a35fd272b105c45c30cc76
|
||||
F test/dbbe.test bd2cd9fe84c6d69b6ae42ac5f55b1e940bdca886
|
||||
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 f56cf41d29d2c4cbaa6239424b5b0ee844c273a0
|
||||
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/table.test eaa25951c0f18615763cd3dc248ea4bc38739c05
|
||||
F test/tester.tcl 446b88283b12efb12691479a403cde15d64fbb82
|
||||
F test/update.test 62f6ce99ff31756aab0ca832ff6d34c5a87b6250
|
||||
F test/vacuum.test 8becf5cfeb897108b35cdd996793e7f1df2f28fd
|
||||
F test/vacuum.test 2127748ff4ddb409212efbb6d9fb9c469ea1b49c
|
||||
F test/where.test bbab5a308055fb6087dc23d600b4ad2b72797397
|
||||
F tool/gdbmdump.c 529e67c78d920606ba196326ea55b57b75fcc82b
|
||||
F tool/lemon.c 1f0e96515c12e9e413f5b8fdebc79ddcf18ddc9d
|
||||
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 www/arch.fig 4f246003b7da23bd63b8b0af0618afb4ee3055c8
|
||||
F www/arch.png 8dae0766d42ed3de9ed013c1341a5792bcf633e6
|
||||
F www/arch.tcl a40380c1fe0080c43e6cc5c20ed70731511b06be
|
||||
F www/c_interface.tcl 11be2d5826eb7d6efd629751d3b483c1ed78ba14
|
||||
F www/changes.tcl 3b0abdf91bef39602876788bbf0e1c4585784363
|
||||
F www/crosscompile.tcl bee79c34f6c3f162ec1c6f5294e79f73651d27ee
|
||||
F www/fileformat.tcl cfb7fba80b7275555281ba2f256c00734bcdd1c9
|
||||
F www/index.tcl 6dae7e07d110ce06c327223365dc1b475b4fb6ee
|
||||
F www/index.tcl b19418d506f90968deef972bf1b427d98bdf13e0
|
||||
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 b46f9a317b06bd490bda987e11e24f2c27162370
|
||||
R c7a71bdb40c0db1def459d2ec0493e1b
|
||||
U drh
|
||||
Z f346b6602fac1a26cb3e2b4625824e0f
|
||||
Z 76f9c173ea2160a2a9011f55704b6843
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
f37dd18e3fc6314eda2af786d74e0a445b9b2287
|
||||
bee0c818590ccdbb96e0a4cb2998bc077564b2ae
|
||||
+8
-6
@@ -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.24 2000/10/16 22:06:42 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-690
@@ -30,124 +30,17 @@
|
||||
** 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.21 2000/10/19 14:10:09 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,582 +52,13 @@ 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;
|
||||
}
|
||||
|
||||
/*
|
||||
** 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.
|
||||
*/
|
||||
int sqliteDbbeOpenTempFile(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 */
|
||||
int limit; /* Prevent an infinite loop */
|
||||
int rc = SQLITE_OK; /* Value returned by this function */
|
||||
|
||||
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 sqliteDbbeOpenTempFile()
|
||||
*/
|
||||
void sqliteDbbeCloseTempFile(Dbbe *pBe, FILE *f){
|
||||
int i;
|
||||
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);
|
||||
return sqliteGdbmOpen(zName, writeFlag, createFlag, pzErrMsg);
|
||||
}
|
||||
|
||||
+76
-67
@@ -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.8 2000/10/19 01:49:02 drh Exp $
|
||||
*/
|
||||
#ifndef _SQLITE_DBBE_H_
|
||||
#define _SQLITE_DBBE_H_
|
||||
@@ -54,90 +54,99 @@
|
||||
typedef struct Dbbe Dbbe;
|
||||
typedef struct DbbeCursor DbbeCursor;
|
||||
|
||||
/*
|
||||
** The 18 interface routines.
|
||||
*/
|
||||
|
||||
/* Open a complete database */
|
||||
/*
|
||||
** 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.
|
||||
*/
|
||||
Dbbe *sqliteDbbeOpen(const char *zName, int write, int create, char **pzErr);
|
||||
|
||||
/* Close the whole database. */
|
||||
void sqliteDbbeClose(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.
|
||||
**
|
||||
** If zName is 0 or "", then a temporary file is created that
|
||||
** will be deleted when closed.
|
||||
/*
|
||||
** This is the structure returned by sqliteDbbeOpen(). It contains pointers
|
||||
** to all access routines for the database backend.
|
||||
*/
|
||||
int sqliteDbbeOpenCursor(Dbbe*, const char *zName, int writeable, DbbeCursor**);
|
||||
struct Dbbe {
|
||||
/* Close the whole database. */
|
||||
void (*Close)(Dbbe*);
|
||||
|
||||
/* Delete a table from the database */
|
||||
void sqliteDbbeDropTable(Dbbe*, const char *zTableName);
|
||||
/* 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.
|
||||
**
|
||||
** 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, DbbeCursor**);
|
||||
|
||||
/* Reorganize a table to speed access or reduce its disk usage */
|
||||
int sqliteDbbeReorganizeTable(Dbbe*, const char *zTableName);
|
||||
/* Delete a table from the database */
|
||||
void (*DropTable)(Dbbe*, const char *zTableName);
|
||||
|
||||
/* Close a cursor */
|
||||
void sqliteDbbeCloseCursor(DbbeCursor*);
|
||||
/* Reorganize a table to speed access or reduce its disk usage */
|
||||
int (*ReorganizeTable)(Dbbe*, const char *zTableName);
|
||||
|
||||
/* 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);
|
||||
/* Close a cursor */
|
||||
void (*CloseCursor)(DbbeCursor*);
|
||||
|
||||
/* Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
int sqliteDbbeTest(DbbeCursor*, int nKey, char *pKey);
|
||||
/* 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);
|
||||
|
||||
/* 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);
|
||||
/* 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. 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);
|
||||
/* 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);
|
||||
|
||||
/* Return the length of the most recently fetched key or data. */
|
||||
int sqliteDbbeKeyLength(DbbeCursor*);
|
||||
int sqliteDbbeDataLength(DbbeCursor*);
|
||||
/* 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);
|
||||
|
||||
/* 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*);
|
||||
/* Return the length of the most recently fetched key or data. */
|
||||
int (*KeyLength)(DbbeCursor*);
|
||||
int (*DataLength)(DbbeCursor*);
|
||||
|
||||
/* Make it so that the next call to sqliteDbbeNextKey() returns
|
||||
** the first entry of the table. */
|
||||
int sqliteDbbeRewind(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*);
|
||||
|
||||
/* Get a new integer key for this table. */
|
||||
int sqliteDbbeNew(DbbeCursor*);
|
||||
/* Make it so that the next call to Dbbe.NextKey() returns
|
||||
** the first entry of the table. */
|
||||
int (*Rewind)(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);
|
||||
/* Get a new integer key for this table. */
|
||||
int (*New)(DbbeCursor*);
|
||||
|
||||
/* Remove an entry from the table */
|
||||
int sqliteDbbeDelete(DbbeCursor*, int nKey, char *pKey);
|
||||
/* 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);
|
||||
|
||||
/* Open a file suitable for temporary storage */
|
||||
int sqliteDbbeOpenTempFile(Dbbe*, FILE**);
|
||||
/* Remove an entry from the table */
|
||||
int (*Delete)(DbbeCursor*, int nKey, char *pKey);
|
||||
|
||||
/* Close a temporary file */
|
||||
void sqliteDbbeCloseTempFile(Dbbe *, FILE *);
|
||||
/* Open a file suitable for temporary storage */
|
||||
int (*OpenTempFile)(Dbbe*, FILE**);
|
||||
|
||||
/* Close a temporary file */
|
||||
void (*CloseTempFile)(Dbbe *, FILE *);
|
||||
};
|
||||
|
||||
#endif /* defined(_SQLITE_DBBE_H_) */
|
||||
|
||||
+776
@@ -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: dbbegdbm.c,v 1.1 2000/10/19 01:49:02 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;
|
||||
}
|
||||
+918
@@ -0,0 +1,918 @@
|
||||
/*
|
||||
** 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.5 2000/12/10 18:23:50 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 */
|
||||
Array data; /* The data in this stable */
|
||||
};
|
||||
|
||||
/*
|
||||
** 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 */
|
||||
Array tables; /* All tables of the database */
|
||||
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 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 */
|
||||
};
|
||||
|
||||
/*
|
||||
** 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;
|
||||
}
|
||||
|
||||
/*
|
||||
** 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, *pNext;
|
||||
int i;
|
||||
ArrayElem *j, *k;
|
||||
for(j=ArrayFirst(&pBe->tables); j; j=ArrayNext(j)){
|
||||
pTble = ArrayData(j);
|
||||
deleteMTable(pTble);
|
||||
}
|
||||
ArrayClear(&pBe->tables);
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
** 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;
|
||||
}
|
||||
|
||||
/*
|
||||
** 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 */
|
||||
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 */
|
||||
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 ){
|
||||
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;
|
||||
}
|
||||
ArrayInit(&pTble->data);
|
||||
}else{
|
||||
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;
|
||||
ArrayElem *i;
|
||||
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;
|
||||
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;
|
||||
int i;
|
||||
struct rc4 *pRc4;
|
||||
|
||||
pRc4 = &pCursr->pBe->rc4;
|
||||
while( go ){
|
||||
iKey = 0;
|
||||
for(i=0; i<4; i++){
|
||||
iKey = (iKey<<8) + rc4byte(pRc4);
|
||||
}
|
||||
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, int nKey,char *pKey, int nData, char *pData){
|
||||
Datum data, key;
|
||||
data.n = nData;
|
||||
data.p = sqliteMalloc( data.n );
|
||||
memcpy(data.p, pData, data.n);
|
||||
key.n = nKey;
|
||||
key.p = pKey;
|
||||
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 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 sqliteMemOpenTempFile(Dbbe *pDbbe, FILE **ppTble){
|
||||
char *zName; /* 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 ){
|
||||
*ppTble = 0;
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
limit = 4;
|
||||
zName = 0;
|
||||
do{
|
||||
randomName(&pBe->rc4, zBuf, "/tmp/_temp_file_");
|
||||
sqliteFree(zName);
|
||||
zName = 0;
|
||||
sqliteSetString(&zName, zBuf, 0);
|
||||
}while( access(zName,0)==0 && limit-- >= 0 );
|
||||
*ppTble = pBe->apTemp[i] = fopen(zName, "w+");
|
||||
if( pBe->apTemp[i]==0 ){
|
||||
rc = SQLITE_ERROR;
|
||||
sqliteFree(zName);
|
||||
pBe->azTemp[i] = 0;
|
||||
}else{
|
||||
pBe->azTemp[i] = zName;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a temporary file opened using sqliteMemOpenTempFile()
|
||||
*/
|
||||
static void sqliteMemCloseTempFile(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 *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;
|
||||
long now;
|
||||
|
||||
pNew = sqliteMalloc( sizeof(*pNew) );
|
||||
if( pNew==0 ){
|
||||
sqliteSetString(pzErrMsg, "out of memory", 0);
|
||||
return 0;
|
||||
}
|
||||
ArrayInit(&pNew->tables);
|
||||
pNew->dbbe.Close = sqliteMemClose;
|
||||
pNew->dbbe.OpenCursor = sqliteMemOpenCursor;
|
||||
pNew->dbbe.DropTable = sqliteMemDropTable;
|
||||
pNew->dbbe.ReorganizeTable = sqliteMemReorganizeTable;
|
||||
pNew->dbbe.CloseCursor = sqliteMemCloseCursor;
|
||||
pNew->dbbe.Fetch = sqliteMemFetch;
|
||||
pNew->dbbe.Test = sqliteMemTest;
|
||||
pNew->dbbe.CopyKey = sqliteMemCopyKey;
|
||||
pNew->dbbe.CopyData = sqliteMemCopyData;
|
||||
pNew->dbbe.ReadKey = sqliteMemReadKey;
|
||||
pNew->dbbe.ReadData = sqliteMemReadData;
|
||||
pNew->dbbe.KeyLength = sqliteMemKeyLength;
|
||||
pNew->dbbe.DataLength = sqliteMemDataLength;
|
||||
pNew->dbbe.NextKey = sqliteMemNextKey;
|
||||
pNew->dbbe.Rewind = sqliteMemRewind;
|
||||
pNew->dbbe.New = sqliteMemNew;
|
||||
pNew->dbbe.Put = sqliteMemPut;
|
||||
pNew->dbbe.Delete = sqliteMemDelete;
|
||||
pNew->dbbe.OpenTempFile = sqliteMemOpenTempFile;
|
||||
pNew->dbbe.CloseTempFile = sqliteMemCloseTempFile;
|
||||
time(&now);
|
||||
rc4init(&pNew->rc4, (char*)&now, sizeof(now));
|
||||
return &pNew->dbbe;
|
||||
}
|
||||
+46
-11
@@ -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.20 2000/11/28 20:47:18 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
+57
-26
@@ -26,7 +26,7 @@
|
||||
** 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.22 2000/12/10 18:23:50 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -154,7 +154,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 +184,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 +227,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 +239,7 @@ sqlite *sqlite_open(const char *zFilename, int mode, char **pzErrMsg){
|
||||
*/
|
||||
void sqlite_close(sqlite *db){
|
||||
int i;
|
||||
sqliteDbbeClose(db->pBe);
|
||||
db->pBe->Close(db->pBe);
|
||||
for(i=0; i<N_HASH; i++){
|
||||
Table *pNext, *pList = db->apTblHash[i];
|
||||
db->apTblHash[i] = 0;
|
||||
@@ -252,29 +257,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;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+790
@@ -0,0 +1,790 @@
|
||||
/*
|
||||
** 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 <ctype.h>
|
||||
#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( isdigit(c) ){
|
||||
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( isdigit(c) ){
|
||||
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;
|
||||
}
|
||||
+3
-3
@@ -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.27 2000/10/16 22:06:42 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);
|
||||
@@ -818,7 +818,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);
|
||||
|
||||
+126
-51
@@ -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.27 2000/10/16 22:06:42 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,7 +685,6 @@ 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;
|
||||
@@ -623,6 +693,9 @@ int main(int argc, char **argv){
|
||||
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;
|
||||
@@ -668,7 +741,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,7 +757,9 @@ 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 ){
|
||||
|
||||
+153
-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.7 2000/11/28 20:47:20 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,26 @@ 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() */
|
||||
|
||||
/* 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 +193,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_ */
|
||||
|
||||
+16
-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.33 2000/12/10 18:23:51 drh Exp $
|
||||
*/
|
||||
#include "sqlite.h"
|
||||
#include "dbbe.h"
|
||||
@@ -62,17 +62,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 +100,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 +124,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 +136,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 +169,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 +202,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 +327,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 */
|
||||
|
||||
+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);
|
||||
}
|
||||
}
|
||||
+110
-62
@@ -23,8 +23,10 @@
|
||||
*************************************************************************
|
||||
** A TCL Interface to SQLite
|
||||
**
|
||||
** $Id: tclsqlite.c,v 1.8 2000/08/17 09:50:00 drh Exp $
|
||||
** $Id: tclsqlite.c,v 1.12 2000/10/19 14:59:27 drh Exp $
|
||||
*/
|
||||
#ifndef NO_TCL /* Omit this whole file if TCL is unavailable */
|
||||
|
||||
#include "sqlite.h"
|
||||
#include <tcl.h>
|
||||
#include <stdlib.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) */
|
||||
|
||||
+17
-4
@@ -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.16 2000/12/10 18:23:51 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;
|
||||
@@ -311,6 +311,8 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
|
||||
extern int sqliteParser(void*, int, ...);
|
||||
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;
|
||||
}
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
+535
-378
File diff suppressed because it is too large
Load Diff
+7
-4
@@ -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.14 2000/10/16 22:06:43 drh Exp $
|
||||
*/
|
||||
#ifndef _SQLITE_VDBE_H_
|
||||
#define _SQLITE_VDBE_H_
|
||||
@@ -173,13 +173,16 @@ typedef struct VdbeOp VdbeOp;
|
||||
#define OP_Concat 87
|
||||
#define OP_Noop 88
|
||||
|
||||
#define OP_MAX 88
|
||||
#define OP_Strlen 89
|
||||
#define OP_Substr 90
|
||||
|
||||
#define OP_MAX 90
|
||||
|
||||
/*
|
||||
** 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 +196,6 @@ int sqliteVdbeList(Vdbe*,sqlite_callback,void*,char**);
|
||||
void sqliteVdbeResolveLabel(Vdbe*, int);
|
||||
int sqliteVdbeCurrentAddr(Vdbe*);
|
||||
void sqliteVdbeTrace(Vdbe*,FILE*);
|
||||
|
||||
void sqliteVdbeCompressSpace(Vdbe*,int);
|
||||
|
||||
#endif
|
||||
|
||||
+2
-2
@@ -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.9 2000/08/22 18:29:34 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+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
|
||||
|
||||
+2
-2
@@ -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.4 2000/10/16 22:06:43 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -130,7 +130,7 @@ do_test dbbe-3.1 {
|
||||
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 {
|
||||
|
||||
+6
-2
@@ -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.4 2000/10/19 14:10:09 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
|
||||
@@ -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)}
|
||||
|
||||
+65
-14
@@ -23,13 +23,27 @@
|
||||
# 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.8 2000/12/10 18:23:52 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: {
|
||||
file delete -force testdb
|
||||
file mkdir testdb
|
||||
}
|
||||
memory: {
|
||||
# do nothing
|
||||
}
|
||||
}
|
||||
sqlite db ${dbprefix}testdb
|
||||
|
||||
# Abort early if this script has been run before.
|
||||
#
|
||||
@@ -39,12 +53,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 +76,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 +89,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 +139,7 @@ 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
|
||||
return [db eval $sql]
|
||||
}
|
||||
|
||||
# Another procedure to execute SQL. This one includes the field
|
||||
|
||||
+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]
|
||||
|
||||
+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;
|
||||
|
||||
+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,
|
||||
|
||||
+104
@@ -17,6 +17,110 @@ proc chng {date desc} {
|
||||
puts "<DD><P><UL>$desc</UL></P></DD>"
|
||||
}
|
||||
|
||||
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.3 2000/10/11 19:28:53 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)
|
||||
#undef WRITELOCK_FILE
|
||||
#define WRITELOCK_FILE(x)
|
||||
#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>
|
||||
|
||||
+34
-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.29 2000/09/30 22:46:07 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
|
||||
@@ -88,6 +72,25 @@ 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. 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 +100,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