Compare commits

...

83 Commits

Author SHA1 Message Date
drh 827e656177 Version 1.0.21 (CVS 482)
FossilOrigin-Name: 7a1147ff52df5f78b1a2e3502c1b78e4d3906968
2001-02-19 18:30:00 +00:00
drh 4f3c63e475 fix the UPDATE bug (CVS 185)
FossilOrigin-Name: 99aae9aaa9f53d1be6600f8598f66594692aae39
2001-02-19 18:24:21 +00:00
drh 4e0dec47d7 Version 1.0.20 (CVS 484)
FossilOrigin-Name: eb0a523c49df65ef3454b8d7e05a6a852b24fc8b
2001-02-11 17:00:00 +00:00
drh 03e7e9516f Perpare to fork SQLite2.0 develop into a separate tree (CVS 184)
FossilOrigin-Name: 4f00e27f17a15504543c5bbb8765c88bacd7257e
2001-02-11 16:58:21 +00:00
drh 338ea13c0d Perpare to fork SQLite2.0 develop into a separate tree (CVS 183)
FossilOrigin-Name: 6adb6078871114ba19ab601bb94d43ff9e03e43f
2001-02-11 16:56:24 +00:00
drh 2e7051b3ec hi (CVS 1716)
FossilOrigin-Name: 5128135c7e12f763722981ad39ecb7835db49142
2001-02-06 14:10:26 +00:00
drh 98a0b0834d :-) (CVS 1715)
FossilOrigin-Name: 29305c3c9eb805a1f5beb82ad50cba1d3bbd3465
2001-02-06 14:01:23 +00:00
drh c58011e862 Version 1.0.19 (CVS 483)
FossilOrigin-Name: 08e179b076b53a2792aba7541f614ad39fa45194
2001-02-06 14:00:00 +00:00
drh b5730347ad Working better with Win95.
Continued work on the new db.c backend. (CVS 181)

FossilOrigin-Name: f9c55a900032ed6babd86170dcb5257e33da950f
2001-01-31 13:28:08 +00:00
drh 17a6893463 Working better with Win95.
Continued work on the new db.c backend. (CVS 1714)

FossilOrigin-Name: df0ff304855bd18c7a3517c500e891b6d006be6a
2001-01-31 13:28:08 +00:00
drh d1881e6165 :-) (CVS 1713)
FossilOrigin-Name: 3259a53111bd19417e78160174bd67c69098494a
2001-01-29 01:27:19 +00:00
drh b5b452cb58 :-) (CVS 1712)
FossilOrigin-Name: edb01b1275c3de7c398522b5826c898917811247
2001-01-25 01:45:40 +00:00
drh acbcdc49c6 :-) (CVS 180)
FossilOrigin-Name: 98da825312fd4bb8a20ff33293131c02beb3ae63
2001-01-22 00:31:53 +00:00
drh d9e5075ff6 :-) (CVS 1711)
FossilOrigin-Name: 0529c979fd17995aff82e21b91b5cc833f23d8ef
2001-01-21 22:03:30 +00:00
drh 2702114089 :-) (CVS 179)
FossilOrigin-Name: d5f2a668978c0d108045237f19b0a7efa07678f2
2001-01-21 00:58:07 +00:00
drh 41a2b48bd0 :-) (CVS 178)
FossilOrigin-Name: 1662063dfb0925bd439e3e2e49bff82705e20fd0
2001-01-20 19:52:49 +00:00
drh e5080e4e9e continued progress toward version 2.0 (CVS 1710)
FossilOrigin-Name: a60af40a3e2e2903d0caa8b81b7e5a4cdc67cd52
2001-01-15 22:51:09 +00:00
drh 345fda3ef5 continued progress toward version 2.0 (CVS 177)
FossilOrigin-Name: c6ffb7ec6acb596907ead8992dfad94e18e83866
2001-01-15 22:51:08 +00:00
drh ae85dc8b0b Changes to the DBBE. Moving toward having many more
backend driver choices. (CVS 176)

FossilOrigin-Name: c0730217a04323a1a73d125e3e7da32bcc8d58fc
2001-01-13 14:34:05 +00:00
drh 993c2a7c8f Version 1.0.18 (CVS 485)
FossilOrigin-Name: 46b86abb1cc8e550acddba24e510d36eaf8ac6b9
2001-01-04 14:29:59 +00:00
drh 2fad28e310 branches: 1.55.2;
:-) (CVS 175)

FossilOrigin-Name: 81815a9c30ffc803cc03aad98936cba641674cd8
2001-01-04 14:27:07 +00:00
drh 660f68d6a2 :-) (CVS 174)
FossilOrigin-Name: 5ddeeb477148295b66fdd78d102827386852fed3
2001-01-04 14:27:07 +00:00
drh 4b2d64a9c9 :-) (CVS 173)
FossilOrigin-Name: e54b0c5ea845111d09529b57b2372ea2e797651a
2001-01-04 14:20:18 +00:00
drh 41adf6c327 branches: 1.17.2;
:-) (CVS 172)

FossilOrigin-Name: 64900e42cdb92b44e0fb3a17f0b94fc62ce733d5
2001-01-04 14:20:18 +00:00
drh a2e1bb5a58 :-) (CVS 171)
FossilOrigin-Name: d49e10fb5e29318f1f777664562b68dd054f7535
2001-01-04 14:20:18 +00:00
drh 68b8f0a1ef Version 1.0.17 (CVS 486)
FossilOrigin-Name: bee0c818590ccdbb96e0a4cb2998bc077564b2ae
2000-12-10 18:40:00 +00:00
drh acf4575752 speed tweaks and documentation updates (CVS 170)
FossilOrigin-Name: b46f9a317b06bd490bda987e11e24f2c27162370
2000-12-10 18:35:20 +00:00
drh 144f1d64b5 speed tweaks and documentation updates (CVS 169)
FossilOrigin-Name: 00c3dfd025f5e66587e23170eddb33112b5e1133
2000-12-10 18:23:51 +00:00
drh 78e6f71fcc branches: 1.16.2;
speed tweaks and documentation updates (CVS 168)

FossilOrigin-Name: af9673d1bff613b93b25fb95dc4cc409314da9f2
2000-12-10 18:23:51 +00:00
drh 8c82b3506e speed tweaks and documentation updates (CVS 167)
FossilOrigin-Name: ced90c3f93e5e0cf73ea77b62a1308cd8dd5f3b3
2000-12-10 18:23:50 +00:00
drh af90b19472 Version 1.0.16 (CVS 487)
FossilOrigin-Name: 8c36b248fd57dfaf3233a365f13f150a07affb3c
2000-11-28 21:00:00 +00:00
drh 66b89c8f68 Comment changes only (CVS 166)
FossilOrigin-Name: 5518e012bf35821f65fe2ca7cbafca6a97e7a4ac
2000-11-28 20:47:17 +00:00
drh d9a4698e5f Comment changes only (CVS 165)
FossilOrigin-Name: 97339efdf3f3b02a6a4fe57d2cb01d19cec3c749
2000-11-28 20:46:38 +00:00
drh 52d17876d1 Version 1.0.15 (CVS 488)
FossilOrigin-Name: d2ad3d2b4ee868621fb1155c64216536d576e456
2000-10-23 13:19:59 +00:00
drh 8c3052c49b documentation and speed updates (CVS 164)
FossilOrigin-Name: 356cdd64860b714f52529159fada799dca7bb1c0
2000-10-23 13:16:31 +00:00
drh 76634487b6 remove unnecessary code when NDEBUG is defined (CVS 163)
FossilOrigin-Name: 738e3e49f6d45e4393e35f7f5f65a41d3c2080c9
2000-10-23 01:07:59 +00:00
drh ccb2a3daf6 fix a debugging issue (CVS 162)
FossilOrigin-Name: f0a5255d2657ddcba24353d6d94b0d9c579d8dec
2000-10-22 20:39:58 +00:00
drh ed5423d5ae fix an HPUX bug (CVS 1709)
FossilOrigin-Name: 9e91b729f0a708b08060979a4998be890b6d268d
2000-10-19 15:28:40 +00:00
drh 549cfc6c80 Version 1.0.14 (CVS 489)
FossilOrigin-Name: 4788dc32a5c9fc3fcd08cbc43b13671c132f0c3e
2000-10-19 15:00:00 +00:00
drh 3cebbde37e remove the Et_AppInit() function from tclsqlite (CVS 161)
FossilOrigin-Name: 8eee153078b27b56c6da7d0b7a3df25985ed051f
2000-10-19 14:59:27 +00:00
drh efa4e17240 make it a little faster (CVS 160)
FossilOrigin-Name: 757668bd641134a6f7479c8df1c8b06a24d51ee4
2000-10-19 14:42:04 +00:00
drh 01d01eeee0 add the sqlite_test_prefixes control file (CVS 159)
FossilOrigin-Name: 4ccd9103c3e7236084283a7311b6e746037d12aa
2000-10-19 14:21:42 +00:00
drh 42f04222c0 fix a memory leadk (CVS 1708)
FossilOrigin-Name: 948f5a749fdc7f40f8a8e5a4e5be8139f9e4c66d
2000-10-19 14:18:34 +00:00
drh 99e86e6b0b Added the "memory:" driver (CVS 1707)
FossilOrigin-Name: e9236833d95060509bdcba008d0e15e61fd7b6d7
2000-10-19 14:10:09 +00:00
drh 767c2001c6 Added the "memory:" driver (CVS 158)
FossilOrigin-Name: 54d60c68dc83410e911b828a680772541c44e9df
2000-10-19 14:10:08 +00:00
drh e907bfc2ac Version 1.0.13 (CVS 490)
FossilOrigin-Name: b9c84fa57912d7d777b4ebefc12627d80a2fc378
2000-10-19 02:00:00 +00:00
drh bb0b679c02 Break out DBBE drivers into separate files. (CVS 157)
FossilOrigin-Name: 979ef5d5d64b8e38cc15fef0d2d507ca2fe6842c
2000-10-19 01:49:02 +00:00
drh 58c2976fa9 Version 1.0.12 (CVS 491)
FossilOrigin-Name: 7330218a912ea788b0c83fa7c7350fd6df88b18a
2000-10-17 01:34:59 +00:00
drh 9b1eb48b7d Fix an off-by-one error in printf.c (CVS 156)
FossilOrigin-Name: 17fcd3b01568e95224425b982fb77abd8b12191a
2000-10-17 01:31:22 +00:00
drh 1afad7c98a :-) (CVS 155)
FossilOrigin-Name: 3dc7913dfc4751a00cc5d14f3aa4a3a25d7531bf
2000-10-16 22:11:57 +00:00
drh 611ad21d8b :-) (CVS 154)
FossilOrigin-Name: bae2050f3f63ff0b56ac38fabe220f150cc2d35a
2000-10-16 22:09:38 +00:00
drh 773ab7ae4c Added an interrupt capability (CVS 1706)
FossilOrigin-Name: fb015479b1d0d7c8074e92907044449d56dd6ad8
2000-10-16 22:06:43 +00:00
drh 4c5043910b Added an interrupt capability (CVS 153)
FossilOrigin-Name: f7ea08b931b9b40831bfe73cd7afea17f92112cf
2000-10-16 22:06:40 +00:00
drh b2b415b97f Removed dlmalloc.c (CVS 1705)
FossilOrigin-Name: d67884f6278a130c81bc75f472170eb6c799ddfa
2000-10-12 13:29:49 +00:00
drh 3e85251e81 Version 1.0.10 (CVS 492)
FossilOrigin-Name: e0c9e80bdbaf672c72ead7827946bb9ac6b7ec32
2000-10-11 19:30:00 +00:00
drh fe79b5325e Misc cleanup. Notes on compiling for Win95. (CVS 1704)
FossilOrigin-Name: 217a8af6d0f4396b755b7d21f94ae6fe002728a1
2000-10-11 19:28:52 +00:00
drh 7c68d60b6f Misc cleanup. Notes on compiling for Win95. (CVS 152)
FossilOrigin-Name: 3f0f1fa1fce794d1661c845f1a63a8d744892c25
2000-10-11 19:28:51 +00:00
drh d42b00f654 Version 1.0.9 (CVS 493)
FossilOrigin-Name: ebbb9e4a660e833efddb7ffea737135fff3bf2f5
2000-10-09 22:30:00 +00:00
drh 98699b54bb Documentation changes only (CVS 151)
FossilOrigin-Name: 15340d2bb34c4d7ab629f9fa2231c7f34a248db9
2000-10-09 12:57:00 +00:00
drh a18c5681e9 Added the _printf() interface. (CVS 150)
FossilOrigin-Name: f9372072a6d6caa15fa14ec722523944470fe155
2000-10-08 22:20:57 +00:00
drh 35d5d9e212 Version 1.0.8 (CVS 494)
FossilOrigin-Name: 384909e50f4ae2e84534f9a6505cfb5cc9f4d6ca
2000-09-30 23:00:00 +00:00
drh 9b0d0a8bc5 Documenting the TCL interface (CVS 149)
FossilOrigin-Name: 7e0bacedf928095b29e7166eacd2356e8169d6dd
2000-09-30 22:46:05 +00:00
drh 05a1c8d08c Fix a problem with the configure script (CVS 1703)
FossilOrigin-Name: ed5f5404ad804aec21ad971d59af834929381b5b
2000-09-29 15:15:52 +00:00
drh fdc99e9235 Fix a problem with the configure script (CVS 148)
FossilOrigin-Name: 2aeb82db3ef6fc13eafa4fcf614e8f1dfd247abf
2000-09-29 15:15:51 +00:00
drh e371033ddf :-) (CVS 147)
FossilOrigin-Name: e11f7527f9187e8902db84069baad0d3b7e17ad0
2000-09-29 13:30:53 +00:00
drh 6d31316cf3 Obj-ify tclsqlite (CVS 146)
FossilOrigin-Name: 85a4254ef0998fac06ddc285decd79345968fee1
2000-09-21 13:01:35 +00:00
drh 6562e5af1f Version 1.0.5 (CVS 495)
FossilOrigin-Name: 84839d8764ecdfac4d8c56618d9c107c8bbf65ab
2000-09-14 01:25:00 +00:00
drh a5c2ad06e6 better handling of exponential notation (CVS 145)
FossilOrigin-Name: ea0e32828f5643eeb00b216a287d14f4826ddf2d
2000-09-14 01:21:10 +00:00
drh e09efa30c4 Version 1.0.4 (CVS 496)
FossilOrigin-Name: 92346e003eba759251d23c296f97a7ad6299f5de
2000-08-28 16:25:00 +00:00
drh ff6e9116e5 adding length() and substr() tests. fix shell.c bug (CVS 144)
FossilOrigin-Name: b8cec9b938b4be1ccf230588d8e1564fb3ac4316
2000-08-28 16:21:58 +00:00
drh 6ec2733b44 Added length() and substr() functions (CVS 143)
FossilOrigin-Name: 0eef538f3de66fede7c88f8be8c3458d84107c3f
2000-08-28 15:51:43 +00:00
drh 3d441f8082 Version 1.0.3 (CVS 497)
FossilOrigin-Name: d35a1f8b37333e2df86c22c2dbd766132b4931b2
2000-08-22 18:30:00 +00:00
drh 63e2741e97 bug fix (CVS 142)
FossilOrigin-Name: f255ea6d4bb085a869485c14b4c96af13ca826fa
2000-08-22 18:29:33 +00:00
drh 64bde80a16 :-) (CVS 141)
FossilOrigin-Name: 7ecc9c447bd9259c52e82c8a75830c7fc3381af7
2000-08-22 13:47:56 +00:00
drh 4b1fb13436 :-) (CVS 140)
FossilOrigin-Name: 9d72ceb44d325963ffdf5d247961519dc2cb72ad
2000-08-22 13:43:28 +00:00
drh 3d0b5598c0 :-) (CVS 139)
FossilOrigin-Name: 67e2da38c30ca73707ad48064a23794cfcff7888
2000-08-22 13:40:51 +00:00
drh b217a57eb9 :-) (CVS 138)
FossilOrigin-Name: c6b71d9b4b09243401072eaa278fd1b9757a9c3d
2000-08-22 13:40:18 +00:00
drh 80afdca0d8 :-) (CVS 137)
FossilOrigin-Name: 5f6559ee7b82eb18078ddbb14d815d0ae0c7ff50
2000-08-22 13:27:22 +00:00
drh 61bc272814 bug fix in lemon (CVS 136)
FossilOrigin-Name: 6f4c080739333dbbc3556b5cf96f341f62167646
2000-08-20 11:42:46 +00:00
drh f4b283d086 Version 1.0.1 (CVS 498)
FossilOrigin-Name: e8521fc10dcfa02fc4ae35fa83f5bae4864a3d28
2000-08-18 09:59:59 +00:00
drh ea1fea5a50 :-) (CVS 135)
FossilOrigin-Name: 0a0576e2f9e5404dff68683ef6fdf04d3a350b1a
2000-08-18 09:58:51 +00:00
drh d317676578 configure script bug (CVS 134)
FossilOrigin-Name: 862b649204f27b4d5e8fa16b1140b945c17d0e14
2000-08-18 09:34:19 +00:00
drh bbd4a757a7 configure script bug (CVS 133)
FossilOrigin-Name: c773a449b1f152a83c93a26e169f60c4f30d50a2
2000-08-18 09:33:39 +00:00
66 changed files with 9702 additions and 1649 deletions
+39 -6
View File
@@ -47,25 +47,31 @@ LIBREADLINE = @TARGET_READLINE_LIBS@
# Object files for the SQLite library.
#
LIBOBJ = build.o dbbe.o delete.o expr.o insert.o \
main.o parse.o select.o tokenize.o update.o \
util.o vdbe.o where.o
LIBOBJ = build.o dbbe.o dbbegdbm.o dbbemem.o delete.o expr.o insert.o \
main.o parse.o printf.o random.o select.o table.o tokenize.o \
update.o util.o vdbe.o where.o tclsqlite.o
# All of the source code files.
#
SRC = \
$(TOP)/src/build.c \
$(TOP)/src/dbbe.c \
$(TOP)/src/dbbegdbm.c \
$(TOP)/src/dbbemem.c \
$(TOP)/src/dbbe.h \
$(TOP)/src/dbbemem.c \
$(TOP)/src/delete.c \
$(TOP)/src/expr.c \
$(TOP)/src/insert.c \
$(TOP)/src/main.c \
$(TOP)/src/parse.y \
$(TOP)/src/printf.c \
$(TOP)/src/random.c \
$(TOP)/src/select.c \
$(TOP)/src/shell.c \
$(TOP)/src/sqlite.h.in \
$(TOP)/src/sqliteInt.h \
$(TOP)/src/table.c \
$(TOP)/src/tclsqlite.c \
$(TOP)/src/tokenize.c \
$(TOP)/src/update.c \
@@ -115,6 +121,12 @@ build.o: $(TOP)/src/build.c $(HDR)
dbbe.o: $(TOP)/src/dbbe.c $(HDR)
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/dbbe.c
dbbegdbm.o: $(TOP)/src/dbbegdbm.c $(HDR)
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/dbbegdbm.c
dbbemem.o: $(TOP)/src/dbbemem.c $(HDR)
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/dbbemem.c
main.o: $(TOP)/src/main.c $(HDR)
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/main.c
@@ -127,8 +139,8 @@ parse.c: $(TOP)/src/parse.y lemon
cp $(TOP)/src/parse.y .
./lemon parse.y
sqlite.h: $(TOP)/src/sqlite.h.in
sed s/--VERS--/`cat ${TOP}/VERSION`/ $(TOP)/src/sqlite.h.in >sqlite.h
sqlite.h: $(TOP)/src/sqlite.h.in
sed -e s/--VERS--/`cat ${TOP}/VERSION`/ $(TOP)/src/sqlite.h.in >sqlite.h
tokenize.o: $(TOP)/src/tokenize.c $(HDR)
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/tokenize.c
@@ -151,12 +163,24 @@ expr.o: $(TOP)/src/expr.c $(HDR)
insert.o: $(TOP)/src/insert.c $(HDR)
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/insert.c
random.o: $(TOP)/src/random.c $(HDR)
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/random.c
select.o: $(TOP)/src/select.c $(HDR)
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/select.c
table.o: $(TOP)/src/table.c $(HDR)
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/table.c
update.o: $(TOP)/src/update.c $(HDR)
$(TCC) $(GDBM_FLAGS) -c $(TOP)/src/update.c
tclsqlite.o: $(TOP)/src/tclsqlite.c $(HDR)
$(TCC) $(GDBM_FLAGS) $(TCL_FLAGS) -c $(TOP)/src/tclsqlite.c
printf.o: $(TOP)/src/printf.c $(HDR)
$(TCC) $(GDBM_FLAGS) $(TCL_FLAGS) -c $(TOP)/src/printf.c
gdbmdump: $(TOP)/tool/gdbmdump.c
$(TCC) $(GDBM_FLAGS) -o gdbmdump $(TOP)/tool/gdbmdump.c $(LIBGDBM)
@@ -206,6 +230,9 @@ crosscompile.html: $(TOP)/www/crosscompile.tcl
mingw.html: $(TOP)/www/mingw.tcl
tclsh $(TOP)/www/mingw.tcl >mingw.html
tclsqlite.html: $(TOP)/www/tclsqlite.tcl
tclsh $(TOP)/www/tclsqlite.tcl >tclsqlite.html
# Files to be published on the website.
#
@@ -222,7 +249,8 @@ PUBLISH = \
vdbe.html \
c_interface.html \
crosscompile.html \
mingw.html
mingw.html \
tclsqlite.html
website: $(PUBLISH)
@@ -230,6 +258,11 @@ publish: $(PUBLISH)
chmod 0644 $(PUBLISH)
scp $(PUBLISH) hwaci@oak.he.net:public_html/sw/sqlite
install: sqlite libsqlite.a sqlite.h
mv sqlite /usr/bin
mv libsqlite.a /usr/lib
mv sqlite.h /usr/include
clean:
rm -f *.o sqlite libsqlite.a sqlite.h
rm -f lemon lempar.c parse.* sqlite.tar.gz
+1 -1
View File
@@ -1 +1 @@
1.0
1.0.21
Vendored
+30 -27
View File
@@ -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
View File
@@ -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)
##########
+48
View File
@@ -0,0 +1,48 @@
The SQL database used for ACD contains 113 tables and indices implemented
in GDBM. The following are statistics on the sizes of keys and data
within these tables and indices.
Entries: 962080
Size: 45573853
Avg Size: 48
Key Size: 11045299
Avg Key Size: 12
Max Key Size: 99
0..8 266 0%
9..12 5485 0%
13..16 73633 8%
17..24 180918 27%
25..32 209823 48%
33..40 148995 64%
41..48 76304 72%
49..56 14346 73%
57..64 15725 75%
65..80 44916 80%
81..96 127815 93%
97..112 34769 96%
113..128 13314 98%
129..144 8098 99%
145..160 3355 99%
161..176 1159 99%
177..192 629 99%
193..208 221 99%
209..224 210 99%
225..240 129 99%
241..256 57 99%
257..288 496 99%
289..320 60 99%
321..352 37 99%
353..384 46 99%
385..416 22 99%
417..448 24 99%
449..480 26 99%
481..512 27 99%
513..1024 471 99%
1025..2048 389 99%
2049..4096 182 99%
4097..8192 74 99%
8193..16384 34 99%
16385..32768 17 99%
32769..65536 5 99%
65537..131073 3 100%
+69 -48
View File
@@ -1,76 +1,97 @@
C Version\s1.0\s(CVS\s499)
D 2000-08-17T10:25:00
C Version\s1.0.21\s(CVS\s482)
D 2001-02-19T18:30:00
F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4
F Makefile.in 8a4cc1bdcbb870f76d0c769136cf973af07586a4
F Makefile.in 7efa81e2985b45ba73db27d55b70cc927f5abfd7
F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958
F VERSION 61652cd1568dcf2614df833eba241755eee34e89
F configure 51063d594190fa085f909cefc9427241088bec4f x
F configure.in a04f02ba61ed09a00e862b4f78b91b06a559e0b5
F VERSION 4686794d487da05ffcbf92801fd0754f6dfa0c39
F configure 3dc1edb9dcf60215e31ff72b447935ab62211442 x
F configure.in d892ca33db7e88a055519ce2f36dcb11020e8fff
F doc/lemon.html e233a3e97a779c7a87e1bc4528c664a58e49dd47
F src/build.c 4d90e9e94750ca80249fc7958c617021d8bb7a50
F src/dbbe.c 226daaf8c095ceb4aff48cad188dad90643f9867
F src/dbbe.h 6337132f904e72ecb28b07390021c241397e4cbf
F src/delete.c 4d491eaf61b515516749c7ed68fa3b2ee8a09065
F src/expr.c 2fa63f086707176d09092e71832f9bbdc6a8ac85
F src/insert.c f146f149ad2422a1dc3bfa7a1651a25940f98958
F src/main.c ba16b81890d962821bb90f0a4de9a29b0e495eb2
F src/parse.y 5d199034de5d29ebedb42c1c51f34db4df40cbe5
F src/select.c d382e96c2221d08367cc87976f2b574537c9de97
F src/shell.c 0aabf9e5e89fc8ac7a56de7f05255ed6864170f2
F doc/report1.txt ad0a41513479f1be0355d1f3f074e66779ff2282
F src/TODO 38a68a489e56e9fd4a96263e0ff9404a47368ad4
F src/build.c 7aa5879bf58ea6bbff22c26c59d1130021fa6ca4
F src/dbbe.c 162d29b09ac379f160892c5795efc14099dcc8eb
F src/dbbe.h 0435a36906a839cce062608f51bd9d3e79878fec
F src/dbbegdbm.c 5bfcb1b4ee47a98c5eae83041e9716cd3233fd0e
F src/dbbemem.c 215e107830ddf0d5a565bb7c20dd7200a925ef75
F src/delete.c b83dc815f83220a82df13f1f1f479187d305fe6a
F src/ex/README b745b00acce2d892f60c40111dacdfc48e0c1c7a
F src/ex/db.c f1419ae6c93e40b5ac6e39fe7efd95d868e6f9d7
F src/ex/db.h 3f2933ee20c147fe494835786e4c6f3a562def4e
F src/ex/dbbebdb1.c 61ed414307f829478614def33de640bbe5b2f770
F src/ex/dbbemird.c b00aef85656fa0a101dac2c32e12922ad106715a
F src/ex/pg.c 2bbf6a94f37226d06337868b6bf4d7affc60197f
F src/ex/pg.h 23a4ac807b0546ec2bb6239ec8bd3e06926572cd
F src/ex/sizes.tcl f54bad4a2ac567624be59131a6ee42d71b41a3d7
F src/expr.c 49bc261fdc4f4fb91c74cd668a9a952c00e85931
F src/insert.c 4bc1cab84f7805d560a1417734a532843e30b762
F src/main.c 5afe29c425b875acede20f609485866eb5b276f6
F src/parse.y 25ee4d8efccc4b247c32fe4ab194e3dd8fd5a4ee
F src/printf.c af0dc65c293427272e1949c7807b1d88f10004fd
F src/random.c b36c3f57dc80c8f354e6bfbf39cf1e1de021d54a
F src/select.c 0cadab95c8011ddbffe804de94f12f3c0e317863
F src/shell.c 441e20913cde0bb71281f4027623c623530241cd
F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e
F src/sqlite.h.in fe04454ae51f9f3717179ffe5a10a25874643ced
F src/sqliteInt.h f6d1e139b3bfa4ceff2136684e19d76b53178ec0
F src/tclsqlite.c 89dc4ba2b521f3e919d6d7aaa4cc1c2aba8e16f3
F src/tokenize.c 097bec5843d4a0fb4509e036fee93bac080c5e73
F src/update.c 51b9ef7434b15e31096155da920302e9db0d27fc
F src/util.c b75b33e6bd5d47898bb7ed9fdd0dea4fe7c19b00
F src/vdbe.c bdedf21230581f0cf73a2dcd8fe23f30cf30ebe6
F src/vdbe.h 6c5653241633c583549c2d8097394ab52550eb63
F src/where.c 420f666a38b405cd58bd7af832ed99f1dbc7d336
F test/all.test 0950c135cab7e60c07bd745ccfad1476211e5bd7
F src/sqlite.h.in 7c1a53f020418d89d13ed2fe9c477ff54540755d
F src/sqliteInt.h fd513fa6b7ac94919f85ebfa183aaa194284ce16
F src/table.c 5be76051a8ed6f6bfa641f4adc52529efa34fbf9
F src/tclsqlite.c 2a925e1835c348f07dd17c87d95ae9a577833407
F src/test.file 55ca6286e3e4f4fba5d0448333fa99fc5a404a73
F src/tokenize.c c7ad428f38e56342eb2025320480b5ae9ece1b90
F src/update.c 9692fbac8e95fdbc5318d39db576aa6c57b9c8ab
F src/util.c 0298100e6427a4b644f767ede12276fa7170fbb6
F src/vdbe.c 6e613f25b0fe1c81b097f46a8fe68c68c39a6abf
F src/vdbe.h d94224ad39c8e9de83dda8d8f960059eb71c0780
F src/where.c e0865f52398662b541bea29ded6aa9687d9f3645
F test/all.test 15cac2f6b2d4c55bf896212aff3cc9d6597b0490
F test/copy.test b77a1214bd7756f2849d5c4fa6e715c0ff0c34eb
F test/dbbe.test c6079572516aeb2739a35fd272b105c45c30cc76
F test/dbbe.test 27deeebf2a01da97cabaab8dc7f34ca3b51a0123
F test/delete.test 402ee3ccb6e544582d24c573ef70b34d09583ae7
F test/expr.test 09b55ccf81cb8cc2f9cd83d592a2ba187ee48ba8
F test/expr.test 48273bf48a15d226c35829f702af4254c0ff6795
F test/func.test 02aed8845b98bde1043dda97455de1d37238ebb3
F test/in.test 2c560c0f55fb777029fd9bb5378f2997582aa603
F test/index.test 950be6116122c6e2db7c2c345eabcdb854ced1d0
F test/index.test ee060ef8912be47ba616e50cce7985259a68d58a
F test/insert.test 66f4c3bd600fec8eb1e733b928cbe6fa885eff0c
F test/insert2.test 732405e30331635af8d159fccabe835eea5cd0c6
F test/lock.test 72f3b11be44f29739cb270b14faea2687dedde7d
F test/main.test b7366cc6f3690915a11834bc1090deeff08acaf9
F test/lock.test bf6d3f8a0a91c6673ff5e357b7a1397e241f7f14
F test/main.test 83cfa58b0b9b69924cc915d5c50b12f0ac9e40e7
F test/select1.test 68ff778c24fc8982e63dda37acb5b0396913adf7
F test/select2.test 45c28211702b5c82b06dd624a112ea17417f343c
F test/select2.test 0c24b9bb8825ebb96e6cc65f1eb61bace0e02aa0
F test/select3.test a9234b8424b6c6d71de534f43b91ade9be68e9cc
F test/select4.test cb5374d7c87680e294ac749307459a5cc547609d
F test/select5.test e2b9d51d88cbd6c307c2c05b0ef55fe7ba811ac2
F test/sort.test d582086c4bb7df3fbf50aa72e69d7e235e9f8e31
F test/subselect.test bf8b251a92fb091973c1c469ce499dc9648a41d5
F test/table.test 620cd72a6c29da3b9153d15c9e94abbbb282373b
F test/tester.tcl 95b286791e6256bb6db0165f9342c70fff549a62
F test/update.test 62f6ce99ff31756aab0ca832ff6d34c5a87b6250
F test/vacuum.test 8becf5cfeb897108b35cdd996793e7f1df2f28fd
F test/table.test eaa25951c0f18615763cd3dc248ea4bc38739c05
F test/tester.tcl e24caef6d07c58c16b24e7afc967464b288a4065
F test/update.test 72c0c93310483b86dc904a992220c5b84c7ce100
F test/vacuum.test 2127748ff4ddb409212efbb6d9fb9c469ea1b49c
F test/where.test bbab5a308055fb6087dc23d600b4ad2b72797397
F tool/gdbmdump.c 529e67c78d920606ba196326ea55b57b75fcc82b
F tool/lemon.c 1f0e96515c12e9e413f5b8fdebc79ddcf18ddc9d
F tool/gdbmstat.c 56a9033531e5f5a48413f6ec436d5fb0341632c1
F tool/lemon.c b13a31798574af881753d38f4da7d505929259c3
F tool/lempar.c a1eec94d6eacc12332368660ec65f3b248853833
F tool/memleak.awk a0a11dd84bf4582acc81c3c61271021ae49b3f15
F tool/opNames.awk 2bd9071a138e4e2be13dc98fe066398a61219e1e
F tool/opcodeDoc.awk b3a2a3d5d3075b8bd90b7afe24283efdd586659c
F tool/renumberOps.awk 6d067177ad5f8d711b79577b462da9b3634bd0a9
F www/arch.fig 4e26e9dca3c49724fc8f554c695ddea9f2413156
F www/arch.png c4d908b79065a72e7dcf19317f36d1324c550e87
F www/arch.tcl 4f6a9afecc099a27bba17b4f8cc9561abc15dc40
F www/c_interface.tcl 29593cf77025bab137b7ba64b9459eb5eb6b4873
F www/changes.tcl 32a1a09297095ffa3e49473732e4b60fc6cdc7c1
F www/crosscompile.tcl 19734ce7f18b16ff2ed8479412abf8aca56e1dcc
F tool/report1.txt 9eae07f26a8fc53889b45fc833a66a33daa22816
F www/arch.fig 4f246003b7da23bd63b8b0af0618afb4ee3055c8
F www/arch.png 8dae0766d42ed3de9ed013c1341a5792bcf633e6
F www/arch.tcl a40380c1fe0080c43e6cc5c20ed70731511b06be
F www/c_interface.tcl 11be2d5826eb7d6efd629751d3b483c1ed78ba14
F www/changes.tcl 50b0890caa8e2b9622237aef27bdf92c90255b35
F www/crosscompile.tcl c99efacb3aefaa550c6e80d91b240f55eb9fd33e
F www/dynload.tcl 02eb8273aa78cfa9070dd4501dca937fb22b466c
F www/fileformat.tcl cfb7fba80b7275555281ba2f256c00734bcdd1c9
F www/index.tcl 6dae7e07d110ce06c327223365dc1b475b4fb6ee
F www/index.tcl 02fe82f3a50d5f5b831afeafb62fc64ea5e63b32
F www/lang.tcl 9192e114b19987e630a41e879585b87006eb84a1
F www/mingw.tcl fc5f4ba9d336b6e8c97347cc6496d6162461ef60
F www/opcode.tcl cb3a1abf8b7b9be9f3a228d097d6bf8b742c2b6f
F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f
F www/vdbe.tcl bcbfc33bcdd0ebad95eab31286adb9e1bc289520
P 5ec2b094781bb3c32b0ef0284aa4dc2b26d9e873
R 55d9f580b0fd85533f8ccc00b91b305d
F www/tclsqlite.tcl 06f81c401f79a04f2c5ebfb97e7c176225c0aef2
F www/vdbe.tcl 0c8aaa529dd216ccbf7daaabd80985e413d5f9ad
P 99aae9aaa9f53d1be6600f8598f66594692aae39
R 59db407686a1360be2fb4d74bb42df04
U drh
Z f346b6602fac1a26cb3e2b4625824e0f
Z 7f2f78067b526c415a8e7024c34c1b61
+1 -1
View File
@@ -1 +1 @@
f37dd18e3fc6314eda2af786d74e0a445b9b2287
7a1147ff52df5f78b1a2e3502c1b78e4d3906968
+35
View File
@@ -0,0 +1,35 @@
* Finish fleshing out the db.c file.
- sqliteDbReadOvfl
- sqliteDbSpreadLoad
- sqliteDbSplit
- sqliteDbNextIndexLevel
- fix sqliteDbCursorNext to work right after sqliteDbCursorDelete
* Compile db.c with -Wall and get no errors.
* Make a pass over pg.c and db.c looking for errors.
- correct handling of I/O errors, malloc failures, etc.
- page leaks (not calling sqlitePgUnref)
* Write a test interface for db.c.
* Compile and link against the db test interface.
* Generate tests for the db interface.
* Add read/write locks to pg.c
* Add an sqliteDbReorganize() function.
* Integrate db into vdbe.
* Modify code generation to take advantage of the new db interface.
- Able to delete without disturbing scan order
- Now keeps a count of number of table entries
+ Special processing for count(*)
+ Better selection of indices on a select
- Transactions
* Modify sqlite_master to store the table number.
* Add a cache in DbCursor to speed up the sqliteDbReadOvfl() routine.
* Add cache information to speed up sqliteDbCursorMoveTo().
Longer term:
* Document all the changes and release Sqlite 2.0.
* Techniques for optimizing querys by grouping data with similar
indices.
* "OPTIMIZE select" statement to automatically create and/or tune
indices.
* "CREATE INDEX FOR select" to automatically generate needed indices.
* "VACUUM table USING index".
* Parse and use constraints.
+16 -14
View File
@@ -33,7 +33,7 @@
** COPY
** VACUUM
**
** $Id: build.c,v 1.23 2000/08/03 15:09:20 drh Exp $
** $Id: build.c,v 1.25 2001/01/15 22:51:09 drh Exp $
*/
#include "sqliteInt.h"
@@ -48,20 +48,22 @@
** no VDBE code was generated.
*/
void sqliteExec(Parse *pParse){
int rc = SQLITE_OK;
if( pParse->pVdbe ){
if( pParse->explain ){
sqliteVdbeList(pParse->pVdbe, pParse->xCallback, pParse->pArg,
&pParse->zErrMsg);
rc = sqliteVdbeList(pParse->pVdbe, pParse->xCallback, pParse->pArg,
&pParse->zErrMsg);
}else{
FILE *trace = (pParse->db->flags & SQLITE_VdbeTrace)!=0 ? stderr : 0;
sqliteVdbeTrace(pParse->pVdbe, trace);
sqliteVdbeExec(pParse->pVdbe, pParse->xCallback, pParse->pArg,
&pParse->zErrMsg, pParse->db->pBusyArg,
pParse->db->xBusyCallback);
rc = sqliteVdbeExec(pParse->pVdbe, pParse->xCallback, pParse->pArg,
&pParse->zErrMsg, pParse->db->pBusyArg,
pParse->db->xBusyCallback);
}
sqliteVdbeDelete(pParse->pVdbe);
pParse->pVdbe = 0;
pParse->colNamesSet = 0;
pParse->rc = rc;
}
}
@@ -339,7 +341,7 @@ void sqliteEndTable(Parse *pParse, Token *pEnd){
*/
if( !pParse->initFlag ){
static VdbeOp addTable[] = {
{ OP_Open, 0, 1, MASTER_NAME },
{ OP_OpenTbl, 0, 1, MASTER_NAME },
{ OP_New, 0, 0, 0},
{ OP_String, 0, 0, "table" },
{ OP_String, 0, 0, 0}, /* 3 */
@@ -414,7 +416,7 @@ void sqliteDropTable(Parse *pParse, Token *pName){
v = sqliteGetVdbe(pParse);
if( v ){
static VdbeOp dropTable[] = {
{ OP_Open, 0, 1, MASTER_NAME },
{ OP_OpenTbl, 0, 1, MASTER_NAME },
{ OP_ListOpen, 0, 0, 0},
{ OP_String, 0, 0, 0}, /* 2 */
{ OP_Next, 0, ADDR(10), 0}, /* 3 */
@@ -591,7 +593,7 @@ void sqliteCreateIndex(
*/
if( pParse->initFlag==0 ){
static VdbeOp addTable[] = {
{ OP_Open, 2, 1, MASTER_NAME},
{ OP_OpenTbl, 2, 1, MASTER_NAME},
{ OP_New, 2, 0, 0},
{ OP_String, 0, 0, "index"},
{ OP_String, 0, 0, 0}, /* 3 */
@@ -608,8 +610,8 @@ void sqliteCreateIndex(
v = sqliteGetVdbe(pParse);
if( v==0 ) goto exit_create_index;
sqliteVdbeAddOp(v, OP_Open, 0, 0, pTab->zName, 0);
sqliteVdbeAddOp(v, OP_Open, 1, 1, pIndex->zName, 0);
sqliteVdbeAddOp(v, OP_OpenTbl, 0, 0, pTab->zName, 0);
sqliteVdbeAddOp(v, OP_OpenIdx, 1, 1, pIndex->zName, 0);
if( pStart && pEnd ){
int base;
n = (int)pEnd->z - (int)pStart->z + 1;
@@ -668,7 +670,7 @@ void sqliteDropIndex(Parse *pParse, Token *pName){
v = sqliteGetVdbe(pParse);
if( v ){
static VdbeOp dropIndex[] = {
{ OP_Open, 0, 1, MASTER_NAME},
{ OP_OpenTbl, 0, 1, MASTER_NAME},
{ OP_ListOpen, 0, 0, 0},
{ OP_String, 0, 0, 0}, /* 2 */
{ OP_Next, 0, ADDR(9), 0}, /* 3 */
@@ -841,9 +843,9 @@ void sqliteCopy(
addr = sqliteVdbeAddOp(v, OP_FileOpen, 0, 0, 0, 0);
sqliteVdbeChangeP3(v, addr, pFilename->z, pFilename->n);
sqliteVdbeDequoteP3(v, addr);
sqliteVdbeAddOp(v, OP_Open, 0, 1, pTab->zName, 0);
sqliteVdbeAddOp(v, OP_OpenTbl, 0, 1, pTab->zName, 0);
for(i=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
sqliteVdbeAddOp(v, OP_Open, i, 1, pIdx->zName, 0);
sqliteVdbeAddOp(v, OP_OpenIdx, i, 1, pIdx->zName, 0);
}
end = sqliteVdbeMakeLabel(v);
addr = sqliteVdbeAddOp(v, OP_FileRead, pTab->nCol, end, 0, 0);
+89 -631
View File
@@ -30,124 +30,18 @@
** relatively simple to convert to a different database such
** as NDBM, SDBM, or BerkeleyDB.
**
** $Id: dbbe.c,v 1.19 2000/08/17 09:50:00 drh Exp $
** $Id: dbbe.c,v 1.23 2001/01/15 22:51:10 drh Exp $
*/
#include "sqliteInt.h"
#include <gdbm.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
/*
** Information about each open disk file is an instance of this
** structure. There will only be one such structure for each
** disk file. If the VDBE opens the same file twice (as will happen
** for a self-join, for example) then two DbbeCursor structures are
** created but there is only a single BeFile structure with an
** nRef of 2.
*/
typedef struct BeFile BeFile;
struct BeFile {
char *zName; /* Name of the file */
GDBM_FILE dbf; /* The file itself */
int nRef; /* Number of references */
int delOnClose; /* Delete when closing */
int writeable; /* Opened for writing */
BeFile *pNext, *pPrev; /* Next and previous on list of open files */
};
/*
** The following structure holds the current state of the RC4 algorithm.
** We use RC4 as a random number generator. Each call to RC4 gives
** a random 8-bit number.
**
** Nothing in this file or anywhere else in SQLite does any kind of
** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random
** number generator) not as an encryption device.
*/
struct rc4 {
int i, j;
int s[256];
};
/*
** The complete database is an instance of the following structure.
*/
struct Dbbe {
char *zDir; /* The directory containing the database */
int write; /* True for write permission */
BeFile *pOpen; /* List of open files */
int nTemp; /* Number of temporary files created */
FILE **apTemp; /* Space to hold temporary file pointers */
char **azTemp; /* Names of the temporary files */
struct rc4 rc4; /* The random number generator */
};
/*
** An cursor into a database file is an instance of the following structure.
** There can only be a single BeFile structure for each disk file, but
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
** a cursor pointing to a particular part of the open BeFile. The
** BeFile.nRef field hold a count of the number of DbbeCursor structures
** associated with the same disk file.
*/
struct DbbeCursor {
Dbbe *pBe; /* The database of which this record is a part */
BeFile *pFile; /* The database file for this table */
datum key; /* Most recently used key */
datum data; /* Most recent data */
int needRewind; /* Next key should be the first */
int readPending; /* The fetch hasn't actually been done yet */
};
/*
** Initialize the RC4 PRNG. "seed" is a pointer to some random
** data used to initialize the PRNG.
*/
static void rc4init(struct rc4 *p, char *seed, int seedlen){
int i;
char k[256];
p->j = 0;
p->i = 0;
for(i=0; i<256; i++){
p->s[i] = i;
k[i] = seed[i%seedlen];
}
for(i=0; i<256; i++){
int t;
p->j = (p->j + p->s[i] + k[i]) & 0xff;
t = p->s[p->j];
p->s[p->j] = p->s[i];
p->s[i] = t;
}
}
/*
** Get a single 8-bit random value from the RC4 PRNG.
*/
static int rc4byte(struct rc4 *p){
int t;
p->i = (p->i + 1) & 0xff;
p->j = (p->j + p->s[p->i]) & 0xff;
t = p->s[p->i];
p->s[p->i] = p->s[p->j];
p->s[p->j] = t;
t = p->s[p->i] + p->s[p->j];
return t & 0xff;
}
/*
** The "mkdir()" function only takes one argument under Windows.
*/
#if OS_WIN
# define mkdir(A,B) mkdir(A)
#endif
/*
** This routine opens a new database. For the GDBM driver
** implemented here, the database name is the name of the directory
** containing all the files of the database.
** This routine opens a new database. It looks at the first
** few characters of the database name to try to determine what
** kind of database to open. If the first characters are "gdbm:",
** then it uses the GDBM driver. If the first few characters are
** "memory:" then it uses the in-memory driver. If there is no
** match, the default to the GDBM driver.
**
** If successful, a pointer to the Dbbe structure is returned.
** If there are errors, an appropriate error message is left
@@ -159,522 +53,15 @@ Dbbe *sqliteDbbeOpen(
int createFlag, /* True to create database if it doesn't exist */
char **pzErrMsg /* Write error messages (if any) here */
){
Dbbe *pNew;
struct stat statbuf;
char *zMaster;
if( !writeFlag ) createFlag = 0;
if( stat(zName, &statbuf)!=0 ){
if( createFlag ) mkdir(zName, 0750);
if( stat(zName, &statbuf)!=0 ){
sqliteSetString(pzErrMsg, createFlag ?
"can't find or create directory \"" : "can't find directory \"",
zName, "\"", 0);
return 0;
}
extern Dbbe *sqliteGdbmOpen(const char*,int,int,char**);
if( strncmp(zName, "gdbm:", 5)==0 ){
return sqliteGdbmOpen(&zName[5], writeFlag, createFlag, pzErrMsg);
}
if( !S_ISDIR(statbuf.st_mode) ){
sqliteSetString(pzErrMsg, "not a directory: \"", zName, "\"", 0);
return 0;
if( strncmp(zName, "memory:", 7)==0 ){
extern Dbbe *sqliteMemOpen(const char*,int,int,char**);
return sqliteMemOpen(&zName[7], writeFlag, createFlag, pzErrMsg);
}
if( access(zName, writeFlag ? (X_OK|W_OK|R_OK) : (X_OK|R_OK)) ){
sqliteSetString(pzErrMsg, "access permission denied", 0);
return 0;
}
zMaster = 0;
sqliteSetString(&zMaster, zName, "/" MASTER_NAME ".tbl", 0);
if( stat(zMaster, &statbuf)==0
&& access(zMaster, writeFlag ? (W_OK|R_OK) : R_OK)!=0 ){
sqliteSetString(pzErrMsg, "access permission denied for ", zMaster, 0);
sqliteFree(zMaster);
return 0;
}
sqliteFree(zMaster);
pNew = sqliteMalloc(sizeof(Dbbe) + strlen(zName) + 1);
if( pNew==0 ){
sqliteSetString(pzErrMsg, "out of memory", 0);
return 0;
}
pNew->zDir = (char*)&pNew[1];
strcpy(pNew->zDir, zName);
pNew->write = writeFlag;
pNew->pOpen = 0;
time(&statbuf.st_ctime);
rc4init(&pNew->rc4, (char*)&statbuf, sizeof(statbuf));
return pNew;
}
/*
** Completely shutdown the given database. Close all files. Free all memory.
*/
void sqliteDbbeClose(Dbbe *pBe){
BeFile *pFile, *pNext;
int i;
for(pFile=pBe->pOpen; pFile; pFile=pNext){
pNext = pFile->pNext;
gdbm_close(pFile->dbf);
memset(pFile, 0, sizeof(*pFile));
sqliteFree(pFile);
}
for(i=0; i<pBe->nTemp; i++){
if( pBe->apTemp[i]!=0 ){
unlink(pBe->azTemp[i]);
fclose(pBe->apTemp[i]);
sqliteFree(pBe->azTemp[i]);
pBe->apTemp[i] = 0;
pBe->azTemp[i] = 0;
break;
}
}
sqliteFree(pBe->azTemp);
sqliteFree(pBe->apTemp);
memset(pBe, 0, sizeof(*pBe));
sqliteFree(pBe);
}
/*
** Translate the name of an SQL table (or index) into the name
** of a file that holds the key/data pairs for that table or
** index. Space to hold the filename is obtained from
** sqliteMalloc() and must be freed by the calling function.
*/
static char *sqliteFileOfTable(Dbbe *pBe, const char *zTable){
char *zFile = 0;
int i;
sqliteSetString(&zFile, pBe->zDir, "/", zTable, ".tbl", 0);
if( zFile==0 ) return 0;
for(i=strlen(pBe->zDir)+1; zFile[i]; i++){
int c = zFile[i];
if( isupper(c) ){
zFile[i] = tolower(c);
}else if( !isalnum(c) && c!='-' && c!='_' && c!='.' ){
zFile[i] = '+';
}
}
return zFile;
}
/*
** Generate a random filename with the given prefix. The new filename
** is written into zBuf[]. The calling function must insure that
** zBuf[] is big enough to hold the prefix plus 20 or so extra
** characters.
**
** Very random names are chosen so that the chance of a
** collision with an existing filename is very very small.
*/
static void randomName(struct rc4 *pRc4, char *zBuf, char *zPrefix){
int i, j;
static const char zRandomChars[] = "abcdefghijklmnopqrstuvwxyz0123456789";
strcpy(zBuf, zPrefix);
j = strlen(zBuf);
for(i=0; i<15; i++){
int c = rc4byte(pRc4) % (sizeof(zRandomChars) - 1);
zBuf[j++] = zRandomChars[c];
}
zBuf[j] = 0;
}
/*
** Open a new table cursor. Write a pointer to the corresponding
** DbbeCursor structure into *ppCursr. Return an integer success
** code:
**
** SQLITE_OK It worked!
**
** SQLITE_NOMEM sqliteMalloc() failed
**
** SQLITE_PERM Attempt to access a file for which file
** access permission is denied
**
** SQLITE_BUSY Another thread or process is already using
** the corresponding file and has that file locked.
**
** SQLITE_READONLY The current thread already has this file open
** readonly but you are trying to open for writing.
** (This can happen if a SELECT callback tries to
** do an UPDATE or DELETE.)
**
** If zTable is 0 or "", then a temporary database file is created and
** a cursor to that temporary file is opened. The temporary file
** will be deleted from the disk when it is closed.
*/
int sqliteDbbeOpenCursor(
Dbbe *pBe, /* The database the table belongs to */
const char *zTable, /* The SQL name of the file to be opened */
int writeable, /* True to open for writing */
DbbeCursor **ppCursr /* Write the resulting table pointer here */
){
char *zFile; /* Name of the table file */
DbbeCursor *pCursr; /* The new table cursor */
BeFile *pFile; /* The underlying data file for this table */
int rc = SQLITE_OK; /* Return value */
int rw_mask; /* Permissions mask for opening a table */
int mode; /* Mode for opening a table */
*ppCursr = 0;
pCursr = sqliteMalloc( sizeof(*pCursr) );
if( pCursr==0 ) return SQLITE_NOMEM;
if( zTable ){
zFile = sqliteFileOfTable(pBe, zTable);
for(pFile=pBe->pOpen; pFile; pFile=pFile->pNext){
if( strcmp(pFile->zName,zFile)==0 ) break;
}
}else{
pFile = 0;
zFile = 0;
}
if( pFile==0 ){
if( writeable ){
rw_mask = GDBM_WRCREAT | GDBM_FAST;
mode = 0640;
}else{
rw_mask = GDBM_READER;
mode = 0640;
}
pFile = sqliteMalloc( sizeof(*pFile) );
if( pFile==0 ){
sqliteFree(zFile);
return SQLITE_NOMEM;
}
if( zFile ){
if( !writeable || pBe->write ){
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
}else{
pFile->dbf = 0;
}
}else{
int limit;
struct rc4 *pRc4;
char zRandom[50];
pRc4 = &pBe->rc4;
zFile = 0;
limit = 5;
do {
randomName(&pBe->rc4, zRandom, "_temp_table_");
sqliteFree(zFile);
zFile = sqliteFileOfTable(pBe, zRandom);
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
}while( pFile->dbf==0 && limit-- >= 0);
pFile->delOnClose = 1;
}
pFile->writeable = writeable;
pFile->zName = zFile;
pFile->nRef = 1;
pFile->pPrev = 0;
if( pBe->pOpen ){
pBe->pOpen->pPrev = pFile;
}
pFile->pNext = pBe->pOpen;
pBe->pOpen = pFile;
if( pFile->dbf==0 ){
if( !writeable && access(zFile,0) ){
/* Trying to read a non-existant file. This is OK. All the
** reads will return empty, which is what we want. */
rc = SQLITE_OK;
}else if( pBe->write==0 ){
rc = SQLITE_READONLY;
}else if( access(zFile,W_OK|R_OK) ){
rc = SQLITE_PERM;
}else{
rc = SQLITE_BUSY;
}
}
}else{
sqliteFree(zFile);
pFile->nRef++;
if( writeable && !pFile->writeable ){
rc = SQLITE_READONLY;
}
}
pCursr->pBe = pBe;
pCursr->pFile = pFile;
pCursr->readPending = 0;
pCursr->needRewind = 1;
if( rc!=SQLITE_OK ){
sqliteDbbeCloseCursor(pCursr);
*ppCursr = 0;
}else{
*ppCursr = pCursr;
}
return rc;
}
/*
** Drop a table from the database. The file on the disk that corresponds
** to this table is deleted.
*/
void sqliteDbbeDropTable(Dbbe *pBe, const char *zTable){
char *zFile; /* Name of the table file */
zFile = sqliteFileOfTable(pBe, zTable);
unlink(zFile);
sqliteFree(zFile);
}
/*
** Reorganize a table to reduce search times and disk usage.
*/
int sqliteDbbeReorganizeTable(Dbbe *pBe, const char *zTable){
DbbeCursor *pCrsr;
int rc;
rc = sqliteDbbeOpenCursor(pBe, zTable, 1, &pCrsr);
if( rc!=SQLITE_OK ){
return rc;
}
if( pCrsr && pCrsr->pFile && pCrsr->pFile->dbf ){
gdbm_reorganize(pCrsr->pFile->dbf);
}
if( pCrsr ){
sqliteDbbeCloseCursor(pCrsr);
}
return SQLITE_OK;
}
/*
** Close a cursor previously opened by sqliteDbbeOpenCursor().
**
** There can be multiple cursors pointing to the same open file.
** The underlying file is not closed until all cursors have been
** closed. This routine decrements the BeFile.nref field of the
** underlying file and closes the file when nref reaches 0.
*/
void sqliteDbbeCloseCursor(DbbeCursor *pCursr){
BeFile *pFile;
Dbbe *pBe;
if( pCursr==0 ) return;
pFile = pCursr->pFile;
pBe = pCursr->pBe;
pFile->nRef--;
if( pFile->dbf!=NULL ){
gdbm_sync(pFile->dbf);
}
if( pFile->nRef<=0 ){
if( pFile->dbf!=NULL ){
gdbm_close(pFile->dbf);
}
if( pFile->pPrev ){
pFile->pPrev->pNext = pFile->pNext;
}else{
pBe->pOpen = pFile->pNext;
}
if( pFile->pNext ){
pFile->pNext->pPrev = pFile->pPrev;
}
if( pFile->delOnClose ){
unlink(pFile->zName);
}
sqliteFree(pFile->zName);
memset(pFile, 0, sizeof(*pFile));
sqliteFree(pFile);
}
if( pCursr->key.dptr ) free(pCursr->key.dptr);
if( pCursr->data.dptr ) free(pCursr->data.dptr);
memset(pCursr, 0, sizeof(*pCursr));
sqliteFree(pCursr);
}
/*
** Clear the given datum
*/
static void datumClear(datum *p){
if( p->dptr ) free(p->dptr);
p->dptr = 0;
p->dsize = 0;
}
/*
** Fetch a single record from an open cursor. Return 1 on success
** and 0 on failure.
*/
int sqliteDbbeFetch(DbbeCursor *pCursr, int nKey, char *pKey){
datum key;
key.dsize = nKey;
key.dptr = pKey;
datumClear(&pCursr->key);
datumClear(&pCursr->data);
if( pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, key);
}
return pCursr->data.dptr!=0;
}
/*
** Return 1 if the given key is already in the table. Return 0
** if it is not.
*/
int sqliteDbbeTest(DbbeCursor *pCursr, int nKey, char *pKey){
datum key;
int result = 0;
key.dsize = nKey;
key.dptr = pKey;
if( pCursr->pFile && pCursr->pFile->dbf ){
result = gdbm_exists(pCursr->pFile->dbf, key);
}
return result;
}
/*
** Copy bytes from the current key or data into a buffer supplied by
** the calling function. Return the number of bytes copied.
*/
int sqliteDbbeCopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
int n;
if( offset>=pCursr->key.dsize ) return 0;
if( offset+size>pCursr->key.dsize ){
n = pCursr->key.dsize - offset;
}else{
n = size;
}
memcpy(zBuf, &pCursr->key.dptr[offset], n);
return n;
}
int sqliteDbbeCopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
int n;
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
pCursr->readPending = 0;
}
if( offset>=pCursr->data.dsize ) return 0;
if( offset+size>pCursr->data.dsize ){
n = pCursr->data.dsize - offset;
}else{
n = size;
}
memcpy(zBuf, &pCursr->data.dptr[offset], n);
return n;
}
/*
** Return a pointer to bytes from the key or data. The data returned
** is ephemeral.
*/
char *sqliteDbbeReadKey(DbbeCursor *pCursr, int offset){
if( offset<0 || offset>=pCursr->key.dsize ) return "";
return &pCursr->key.dptr[offset];
}
char *sqliteDbbeReadData(DbbeCursor *pCursr, int offset){
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
pCursr->readPending = 0;
}
if( offset<0 || offset>=pCursr->data.dsize ) return "";
return &pCursr->data.dptr[offset];
}
/*
** Return the total number of bytes in either data or key.
*/
int sqliteDbbeKeyLength(DbbeCursor *pCursr){
return pCursr->key.dsize;
}
int sqliteDbbeDataLength(DbbeCursor *pCursr){
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
pCursr->readPending = 0;
}
return pCursr->data.dsize;
}
/*
** Make is so that the next call to sqliteNextKey() finds the first
** key of the table.
*/
int sqliteDbbeRewind(DbbeCursor *pCursr){
pCursr->needRewind = 1;
return SQLITE_OK;
}
/*
** Read the next key from the table. Return 1 on success. Return
** 0 if there are no more keys.
*/
int sqliteDbbeNextKey(DbbeCursor *pCursr){
datum nextkey;
int rc;
if( pCursr==0 || pCursr->pFile==0 || pCursr->pFile->dbf==0 ){
pCursr->readPending = 0;
return 0;
}
if( pCursr->needRewind ){
nextkey = gdbm_firstkey(pCursr->pFile->dbf);
pCursr->needRewind = 0;
}else{
nextkey = gdbm_nextkey(pCursr->pFile->dbf, pCursr->key);
}
datumClear(&pCursr->key);
datumClear(&pCursr->data);
pCursr->key = nextkey;
if( pCursr->key.dptr ){
pCursr->readPending = 1;
rc = 1;
}else{
pCursr->needRewind = 1;
pCursr->readPending = 0;
rc = 0;
}
return rc;
}
/*
** Get a new integer key.
*/
int sqliteDbbeNew(DbbeCursor *pCursr){
int iKey;
datum key;
int go = 1;
int i;
struct rc4 *pRc4;
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return 1;
pRc4 = &pCursr->pBe->rc4;
while( go ){
iKey = 0;
for(i=0; i<4; i++){
iKey = (iKey<<8) + rc4byte(pRc4);
}
if( iKey==0 ) continue;
key.dptr = (char*)&iKey;
key.dsize = 4;
go = gdbm_exists(pCursr->pFile->dbf, key);
}
return iKey;
}
/*
** Write an entry into the table. Overwrite any prior entry with the
** same key.
*/
int sqliteDbbePut(DbbeCursor *pCursr, int nKey,char *pKey,int nData,char *pData){
datum data, key;
int rc;
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
data.dsize = nData;
data.dptr = pData;
key.dsize = nKey;
key.dptr = pKey;
rc = gdbm_store(pCursr->pFile->dbf, key, data, GDBM_REPLACE);
if( rc ) rc = SQLITE_ERROR;
datumClear(&pCursr->key);
datumClear(&pCursr->data);
return rc;
}
/*
** Remove an entry from a table, if the entry exists.
*/
int sqliteDbbeDelete(DbbeCursor *pCursr, int nKey, char *pKey){
datum key;
int rc;
datumClear(&pCursr->key);
datumClear(&pCursr->data);
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
key.dsize = nKey;
key.dptr = pKey;
rc = gdbm_delete(pCursr->pFile->dbf, key);
if( rc ) rc = SQLITE_ERROR;
return rc;
return sqliteGdbmOpen(zName, writeFlag, createFlag, pzErrMsg);
}
/*
@@ -684,7 +71,7 @@ int sqliteDbbeDelete(DbbeCursor *pCursr, int nKey, char *pKey){
** and then immediately unlinking the file. That works great
** under Unix, but fails when we try to port to Windows.
*/
int sqliteDbbeOpenTempFile(Dbbe *pBe, FILE **ppFile){
int sqliteDbbeOpenTempFile(const char *zDir, Dbbe *pBe, FILE **ppFile){
char *zFile; /* Full name of the temporary file */
char zBuf[50]; /* Base name of the temporary file */
int i; /* Loop counter */
@@ -706,10 +93,10 @@ int sqliteDbbeOpenTempFile(Dbbe *pBe, FILE **ppFile){
limit = 4;
zFile = 0;
do{
randomName(&pBe->rc4, zBuf, "/_temp_file_");
sqliteRandomName(zBuf, "/_temp_file_");
sqliteFree(zFile);
zFile = 0;
sqliteSetString(&zFile, pBe->zDir, zBuf, 0);
sqliteSetString(&zFile, zDir, zBuf, 0);
}while( access(zFile,0)==0 && limit-- >= 0 );
*ppFile = pBe->apTemp[i] = fopen(zFile, "w+");
if( pBe->apTemp[i]==0 ){
@@ -723,7 +110,7 @@ int sqliteDbbeOpenTempFile(Dbbe *pBe, FILE **ppFile){
}
/*
** Close a temporary file opened using sqliteDbbeOpenTempFile()
** Close a temporary file opened using sqliteGdbmOpenTempFile()
*/
void sqliteDbbeCloseTempFile(Dbbe *pBe, FILE *f){
int i;
@@ -738,3 +125,74 @@ void sqliteDbbeCloseTempFile(Dbbe *pBe, FILE *f){
}
fclose(f);
}
/*
** Close all temporary files that happen to still be open.
** This routine is called when the database is being closed.
*/
void sqliteDbbeCloseAllTempFiles(Dbbe *pBe){
int i;
for(i=0; i<pBe->nTemp; i++){
if( pBe->apTemp[i]!=0 ){
unlink(pBe->azTemp[i]);
fclose(pBe->apTemp[i]);
sqliteFree(pBe->azTemp[i]);
pBe->apTemp[i] = 0;
pBe->azTemp[i] = 0;
break;
}
}
sqliteFree(pBe->azTemp);
sqliteFree(pBe->apTemp);
}
/*
** Translate the name of an SQL table (or index) into the name
** of a file that holds the key/data pairs for that table or
** index. Space to hold the filename is obtained from
** sqliteMalloc() and must be freed by the calling function.
**
** zDir is the name of the directory in which the file should
** be located. zSuffix is the filename extension to use for
** the file.
*/
char *sqliteDbbeNameToFile(
const char *zDir, /* Directory containing the file */
const char *zTable, /* Name of the SQL table that the file contains */
const char *zSuffix /* Suffix for the file. Includes the "." */
){
char *zFile = 0;
int i, k, c;
int nChar = 0;
for(i=0; (c = zTable[i])!=0; i++){
if( !isalnum(c) && c!='_' ){
nChar += 3;
}else{
nChar ++;
}
}
nChar += strlen(zDir) + strlen(zSuffix) + 2;
zFile = sqliteMalloc( nChar );
if( zFile==0 ) return 0;
for(i=0; (c = zDir[i])!=0; i++){
zFile[i] = c;
}
zFile[i++] = '/';
for(k=0; (c = zTable[k])!=0; k++){
if( isupper(c) ){
zFile[i++] = tolower(c);
}else if( isalnum(c) || c=='_' ){
zFile[i++] = c;
}else{
zFile[i++] = '~';
zFile[i++] = "0123456789abcdef"[c & 0xf];
zFile[i++] = "0123456789abcdef"[(c>>8)&0xf];
}
}
for(k=0; (c = zSuffix[k])!=0; k++){
zFile[i++] = c;
}
zFile[i] = 0;
return zFile;
}
+114 -79
View File
@@ -28,7 +28,7 @@
** This library was originally designed to support the following
** backends: GDBM, NDBM, SDBM, Berkeley DB.
**
** $Id: dbbe.h,v 1.7 2000/08/17 09:50:00 drh Exp $
** $Id: dbbe.h,v 1.10 2001/01/15 22:51:10 drh Exp $
*/
#ifndef _SQLITE_DBBE_H_
#define _SQLITE_DBBE_H_
@@ -53,91 +53,126 @@
*/
typedef struct Dbbe Dbbe;
typedef struct DbbeCursor DbbeCursor;
typedef struct DbbeMethods DbbeMethods;
/*
** The 18 interface routines.
** Open a complete database.
**
** If the database name begins with "gdbm:" the GDBM driver is used.
** If the name begins with "memory:" the in-memory driver is used.
** The default driver is GDBM.
*/
/* Open a complete database */
Dbbe *sqliteDbbeOpen(const char *zName, int write, int create, char **pzErr);
/* Close the whole database. */
void sqliteDbbeClose(Dbbe*);
/*
** Each of the various SQLite backends defines a set of methods for
** accessing the database. Pointers to the methods are contained in
** an instance of the following structure. A pointer to a static instance
** of this structure is assigned to the Dbbe structure that sqlileDbbeOpen
** returns.
*/
struct DbbeMethods {
/* Close the whole database. */
void (*Close)(Dbbe*);
/* Open a cursor into particular file of a previously opened database.
** Create the file if it doesn't already exist and writeable!=0. zName
** is the base name of the file to be opened. This routine will add
** an appropriate path and extension to the filename to locate the
** actual file.
/* Open a cursor into particular file of a previously opened database.
** Create the file if it doesn't already exist and writeable!=0. zName
** is the base name of the file to be opened. This routine will add
** an appropriate path and extension to the filename to locate the
** actual file.
**
** The keyType parameter is TRUE if this table will only be accessed
** using integer keys. This parameter allows the database backend to
** use a faster algorithm for the special case of integer keys, if it
** wants to.
**
** If zName is 0 or "", then a temporary file is created that
** will be deleted when closed.
*/
int (*OpenCursor)(Dbbe*, const char *zName, int writeable,
int intKeyOnly, DbbeCursor**);
/* Delete a table from the database */
void (*DropTable)(Dbbe*, const char *zTableName);
/* Reorganize a table to speed access or reduce its disk usage */
int (*ReorganizeTable)(Dbbe*, const char *zTableName);
/* Close a cursor */
void (*CloseCursor)(DbbeCursor*);
/* Fetch an entry from a table with the given key. Return 1 if
** successful and 0 if no such entry exists.
*/
int (*Fetch)(DbbeCursor*, int nKey, char *pKey);
/* Return 1 if the given key is already in the table. Return 0
** if it is not.
*/
int (*Test)(DbbeCursor*, int nKey, char *pKey);
/* Retrieve the key or data used for the last fetch. Only size
** bytes are read beginning with the offset-th byte. The return
** value is the actual number of bytes read.
*/
int (*CopyKey)(DbbeCursor*, int offset, int size, char *zBuf);
int (*CopyData)(DbbeCursor*, int offset, int size, char *zBuf);
/* Retrieve the key or data. The result is ephemeral. In other words,
** the result is stored in a buffer that might be overwritten on the next
** call to any DBBE routine. If the results are needed for longer than
** that, you must make a copy.
*/
char *(*ReadKey)(DbbeCursor*, int offset);
char *(*ReadData)(DbbeCursor*, int offset);
/* Return the length of the most recently fetched key or data. */
int (*KeyLength)(DbbeCursor*);
int (*DataLength)(DbbeCursor*);
/* Retrieve the next entry in the table. The first key is retrieved
** the first time this routine is called, or after a call to
** Dbbe.Rewind(). The return value is 1 if there is another
** entry, or 0 if there are no more entries. */
int (*NextKey)(DbbeCursor*);
/* Make it so that the next call to Dbbe.NextKey() returns
** the first entry of the table. */
int (*Rewind)(DbbeCursor*);
/* Get a new integer key for this table. */
int (*New)(DbbeCursor*);
/* Write an entry into a table. If another entry already exists with
** the same key, the old entry is discarded first.
*/
int (*Put)(DbbeCursor*, int nKey, char *pKey, int nData, char *pData);
/* Remove an entry from the table */
int (*Delete)(DbbeCursor*, int nKey, char *pKey);
/* Open a file suitable for temporary storage */
int (*OpenTempFile)(Dbbe*, FILE**);
/* Close a temporary file */
void (*CloseTempFile)(Dbbe *, FILE *);
};
/*
** This is the structure returned by sqliteDbbeOpen(). It contains
** information common to all the different backend drivers.
**
** If zName is 0 or "", then a temporary file is created that
** will be deleted when closed.
** The information in this structure (with the exception the method
** pointers in the Dbbe.x field) is intended to be visible to
** the backend drivers only. Users should not access or modify
** this structure in any way other than the read the method pointers
** in Dbbe.x.
*/
int sqliteDbbeOpenCursor(Dbbe*, const char *zName, int writeable, DbbeCursor**);
/* Delete a table from the database */
void sqliteDbbeDropTable(Dbbe*, const char *zTableName);
/* Reorganize a table to speed access or reduce its disk usage */
int sqliteDbbeReorganizeTable(Dbbe*, const char *zTableName);
/* Close a cursor */
void sqliteDbbeCloseCursor(DbbeCursor*);
/* Fetch an entry from a table with the given key. Return 1 if
** successful and 0 if no such entry exists.
*/
int sqliteDbbeFetch(DbbeCursor*, int nKey, char *pKey);
/* Return 1 if the given key is already in the table. Return 0
** if it is not.
*/
int sqliteDbbeTest(DbbeCursor*, int nKey, char *pKey);
/* Retrieve the key or data used for the last fetch. Only size
** bytes are read beginning with the offset-th byte. The return
** value is the actual number of bytes read.
*/
int sqliteDbbeCopyKey(DbbeCursor*, int offset, int size, char *zBuf);
int sqliteDbbeCopyData(DbbeCursor*, int offset, int size, char *zBuf);
/* Retrieve the key or data. The result is ephemeral. In other words,
** the result is stored in a buffer that might be overwritten on the next
** call to any DBBE routine. If the results are needed for longer than
** that, you must make a copy.
*/
char *sqliteDbbeReadKey(DbbeCursor*, int offset);
char *sqliteDbbeReadData(DbbeCursor*, int offset);
/* Return the length of the most recently fetched key or data. */
int sqliteDbbeKeyLength(DbbeCursor*);
int sqliteDbbeDataLength(DbbeCursor*);
/* Retrieve the next entry in the table. The first key is retrieved
** the first time this routine is called, or after a call to
** sqliteDbbeRewind(). The return value is 1 if there is another
** entry, or 0 if there are no more entries. */
int sqliteDbbeNextKey(DbbeCursor*);
/* Make it so that the next call to sqliteDbbeNextKey() returns
** the first entry of the table. */
int sqliteDbbeRewind(DbbeCursor*);
/* Get a new integer key for this table. */
int sqliteDbbeNew(DbbeCursor*);
/* Write an entry into a table. If another entry already exists with
** the same key, the old entry is discarded first.
*/
int sqliteDbbePut(DbbeCursor*, int nKey, char *pKey, int nData, char *pData);
/* Remove an entry from the table */
int sqliteDbbeDelete(DbbeCursor*, int nKey, char *pKey);
/* Open a file suitable for temporary storage */
int sqliteDbbeOpenTempFile(Dbbe*, FILE**);
/* Close a temporary file */
void sqliteDbbeCloseTempFile(Dbbe *, FILE *);
struct Dbbe {
struct DbbeMethods *x; /* Backend-specific methods for database access */
int nTemp; /* Number of temporary files created */
FILE **apTemp; /* Space to hold temporary file pointers */
char **azTemp; /* Names of the temporary files */
};
#endif /* defined(_SQLITE_DBBE_H_) */
+638
View File
@@ -0,0 +1,638 @@
/*
** Copyright (c) 2000 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*************************************************************************
** This file contains code to implement the database backend (DBBE)
** for sqlite. The database backend is the interface between
** sqlite and the code that does the actually reading and writing
** of information to the disk.
**
** This file uses GDBM as the database backend. It should be
** relatively simple to convert to a different database such
** as NDBM, SDBM, or BerkeleyDB.
**
** $Id: dbbegdbm.c,v 1.3 2001/01/15 22:51:10 drh Exp $
*/
#include "sqliteInt.h"
#include <gdbm.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
/*
** Information about each open disk file is an instance of this
** structure. There will only be one such structure for each
** disk file. If the VDBE opens the same file twice (as will happen
** for a self-join, for example) then two DbbeCursor structures are
** created but there is only a single BeFile structure with an
** nRef of 2.
*/
typedef struct BeFile BeFile;
struct BeFile {
char *zName; /* Name of the file */
GDBM_FILE dbf; /* The file itself */
int nRef; /* Number of references */
int delOnClose; /* Delete when closing */
int writeable; /* Opened for writing */
BeFile *pNext, *pPrev; /* Next and previous on list of open files */
};
/*
** The following structure contains all information used by GDBM
** database driver. This is a subclass of the Dbbe structure.
*/
typedef struct Dbbex Dbbex;
struct Dbbex {
Dbbe dbbe; /* The base class */
int write; /* True for write permission */
BeFile *pOpen; /* List of open files */
char *zDir; /* Directory hold the database */
};
/*
** An cursor into a database file is an instance of the following structure.
** There can only be a single BeFile structure for each disk file, but
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
** a cursor pointing to a particular part of the open BeFile. The
** BeFile.nRef field hold a count of the number of DbbeCursor structures
** associated with the same disk file.
*/
struct DbbeCursor {
Dbbex *pBe; /* The database of which this record is a part */
BeFile *pFile; /* The database file for this table */
datum key; /* Most recently used key */
datum data; /* Most recent data */
int needRewind; /* Next key should be the first */
int readPending; /* The fetch hasn't actually been done yet */
};
/*
** The "mkdir()" function only takes one argument under Windows.
*/
#if OS_WIN
# define mkdir(A,B) mkdir(A)
#endif
/*
** Forward declaration
*/
static void sqliteGdbmCloseCursor(DbbeCursor *pCursr);
/*
** Completely shutdown the given database. Close all files. Free all memory.
*/
static void sqliteGdbmClose(Dbbe *pDbbe){
Dbbex *pBe = (Dbbex*)pDbbe;
BeFile *pFile, *pNext;
for(pFile=pBe->pOpen; pFile; pFile=pNext){
pNext = pFile->pNext;
gdbm_close(pFile->dbf);
memset(pFile, 0, sizeof(*pFile));
sqliteFree(pFile);
}
sqliteDbbeCloseAllTempFiles(pDbbe);
memset(pBe, 0, sizeof(*pBe));
sqliteFree(pBe);
}
/*
** Translate the name of an SQL table (or index) into the name
** of a file that holds the key/data pairs for that table or
** index. Space to hold the filename is obtained from
** sqliteMalloc() and must be freed by the calling function.
*/
static char *sqliteFileOfTable(Dbbex *pBe, const char *zTable){
char *zFile = 0;
int i;
sqliteSetString(&zFile, pBe->zDir, "/", zTable, ".tbl", 0);
if( zFile==0 ) return 0;
for(i=strlen(pBe->zDir)+1; zFile[i]; i++){
int c = zFile[i];
if( isupper(c) ){
zFile[i] = tolower(c);
}else if( !isalnum(c) && c!='-' && c!='_' && c!='.' ){
zFile[i] = '+';
}
}
return zFile;
}
/*
** Open a new table cursor. Write a pointer to the corresponding
** DbbeCursor structure into *ppCursr. Return an integer success
** code:
**
** SQLITE_OK It worked!
**
** SQLITE_NOMEM sqliteMalloc() failed
**
** SQLITE_PERM Attempt to access a file for which file
** access permission is denied
**
** SQLITE_BUSY Another thread or process is already using
** the corresponding file and has that file locked.
**
** SQLITE_READONLY The current thread already has this file open
** readonly but you are trying to open for writing.
** (This can happen if a SELECT callback tries to
** do an UPDATE or DELETE.)
**
** If zTable is 0 or "", then a temporary database file is created and
** a cursor to that temporary file is opened. The temporary file
** will be deleted from the disk when it is closed.
*/
static int sqliteGdbmOpenCursor(
Dbbe *pDbbe, /* The database the table belongs to */
const char *zTable, /* The SQL name of the file to be opened */
int writeable, /* True to open for writing */
int intKeyOnly, /* True if only integer keys are used */
DbbeCursor **ppCursr /* Write the resulting table pointer here */
){
char *zFile; /* Name of the table file */
DbbeCursor *pCursr; /* The new table cursor */
BeFile *pFile; /* The underlying data file for this table */
int rc = SQLITE_OK; /* Return value */
int rw_mask; /* Permissions mask for opening a table */
int mode; /* Mode for opening a table */
Dbbex *pBe = (Dbbex*)pDbbe;
*ppCursr = 0;
pCursr = sqliteMalloc( sizeof(*pCursr) );
if( pCursr==0 ) return SQLITE_NOMEM;
if( zTable ){
zFile = sqliteFileOfTable(pBe, zTable);
for(pFile=pBe->pOpen; pFile; pFile=pFile->pNext){
if( strcmp(pFile->zName,zFile)==0 ) break;
}
}else{
pFile = 0;
zFile = 0;
}
if( pFile==0 ){
if( writeable ){
rw_mask = GDBM_WRCREAT | GDBM_FAST;
mode = 0640;
}else{
rw_mask = GDBM_READER;
mode = 0640;
}
pFile = sqliteMalloc( sizeof(*pFile) );
if( pFile==0 ){
sqliteFree(zFile);
return SQLITE_NOMEM;
}
if( zFile ){
if( !writeable || pBe->write ){
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
}else{
pFile->dbf = 0;
}
}else{
int limit;
char zRandom[50];
zFile = 0;
limit = 5;
do {
sqliteRandomName(zRandom, "_temp_table_");
sqliteFree(zFile);
zFile = sqliteFileOfTable(pBe, zRandom);
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
}while( pFile->dbf==0 && limit-- >= 0);
pFile->delOnClose = 1;
}
pFile->writeable = writeable;
pFile->zName = zFile;
pFile->nRef = 1;
pFile->pPrev = 0;
if( pBe->pOpen ){
pBe->pOpen->pPrev = pFile;
}
pFile->pNext = pBe->pOpen;
pBe->pOpen = pFile;
if( pFile->dbf==0 ){
if( !writeable && access(zFile,0) ){
/* Trying to read a non-existant file. This is OK. All the
** reads will return empty, which is what we want. */
rc = SQLITE_OK;
}else if( pBe->write==0 ){
rc = SQLITE_READONLY;
}else if( access(zFile,W_OK|R_OK) ){
rc = SQLITE_PERM;
}else{
rc = SQLITE_BUSY;
}
}
}else{
sqliteFree(zFile);
pFile->nRef++;
if( writeable && !pFile->writeable ){
rc = SQLITE_READONLY;
}
}
pCursr->pBe = pBe;
pCursr->pFile = pFile;
pCursr->readPending = 0;
pCursr->needRewind = 1;
if( rc!=SQLITE_OK ){
sqliteGdbmCloseCursor(pCursr);
*ppCursr = 0;
}else{
*ppCursr = pCursr;
}
return rc;
}
/*
** Drop a table from the database. The file on the disk that corresponds
** to this table is deleted.
*/
static void sqliteGdbmDropTable(Dbbe *pBe, const char *zTable){
char *zFile; /* Name of the table file */
zFile = sqliteFileOfTable((Dbbex*)pBe, zTable);
unlink(zFile);
sqliteFree(zFile);
}
/*
** Close a cursor previously opened by sqliteGdbmOpenCursor().
**
** There can be multiple cursors pointing to the same open file.
** The underlying file is not closed until all cursors have been
** closed. This routine decrements the BeFile.nref field of the
** underlying file and closes the file when nref reaches 0.
*/
static void sqliteGdbmCloseCursor(DbbeCursor *pCursr){
BeFile *pFile;
Dbbex *pBe;
if( pCursr==0 ) return;
pFile = pCursr->pFile;
pBe = pCursr->pBe;
pFile->nRef--;
if( pFile->dbf!=NULL ){
gdbm_sync(pFile->dbf);
}
if( pFile->nRef<=0 ){
if( pFile->dbf!=NULL ){
gdbm_close(pFile->dbf);
}
if( pFile->pPrev ){
pFile->pPrev->pNext = pFile->pNext;
}else{
pBe->pOpen = pFile->pNext;
}
if( pFile->pNext ){
pFile->pNext->pPrev = pFile->pPrev;
}
if( pFile->delOnClose ){
unlink(pFile->zName);
}
sqliteFree(pFile->zName);
memset(pFile, 0, sizeof(*pFile));
sqliteFree(pFile);
}
if( pCursr->key.dptr ) free(pCursr->key.dptr);
if( pCursr->data.dptr ) free(pCursr->data.dptr);
memset(pCursr, 0, sizeof(*pCursr));
sqliteFree(pCursr);
}
/*
** Reorganize a table to reduce search times and disk usage.
*/
static int sqliteGdbmReorganizeTable(Dbbe *pBe, const char *zTable){
DbbeCursor *pCrsr;
int rc;
rc = sqliteGdbmOpenCursor(pBe, zTable, 1, 0, &pCrsr);
if( rc!=SQLITE_OK ){
return rc;
}
if( pCrsr && pCrsr->pFile && pCrsr->pFile->dbf ){
gdbm_reorganize(pCrsr->pFile->dbf);
}
if( pCrsr ){
sqliteGdbmCloseCursor(pCrsr);
}
return SQLITE_OK;
}
/*
** Clear the given datum
*/
static void datumClear(datum *p){
if( p->dptr ) free(p->dptr);
p->dptr = 0;
p->dsize = 0;
}
/*
** Fetch a single record from an open cursor. Return 1 on success
** and 0 on failure.
*/
static int sqliteGdbmFetch(DbbeCursor *pCursr, int nKey, char *pKey){
datum key;
key.dsize = nKey;
key.dptr = pKey;
datumClear(&pCursr->key);
datumClear(&pCursr->data);
if( pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, key);
}
return pCursr->data.dptr!=0;
}
/*
** Return 1 if the given key is already in the table. Return 0
** if it is not.
*/
static int sqliteGdbmTest(DbbeCursor *pCursr, int nKey, char *pKey){
datum key;
int result = 0;
key.dsize = nKey;
key.dptr = pKey;
if( pCursr->pFile && pCursr->pFile->dbf ){
result = gdbm_exists(pCursr->pFile->dbf, key);
}
return result;
}
/*
** Copy bytes from the current key or data into a buffer supplied by
** the calling function. Return the number of bytes copied.
*/
static
int sqliteGdbmCopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
int n;
if( offset>=pCursr->key.dsize ) return 0;
if( offset+size>pCursr->key.dsize ){
n = pCursr->key.dsize - offset;
}else{
n = size;
}
memcpy(zBuf, &pCursr->key.dptr[offset], n);
return n;
}
static
int sqliteGdbmCopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
int n;
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
pCursr->readPending = 0;
}
if( offset>=pCursr->data.dsize ) return 0;
if( offset+size>pCursr->data.dsize ){
n = pCursr->data.dsize - offset;
}else{
n = size;
}
memcpy(zBuf, &pCursr->data.dptr[offset], n);
return n;
}
/*
** Return a pointer to bytes from the key or data. The data returned
** is ephemeral.
*/
static char *sqliteGdbmReadKey(DbbeCursor *pCursr, int offset){
if( offset<0 || offset>=pCursr->key.dsize ) return "";
return &pCursr->key.dptr[offset];
}
static char *sqliteGdbmReadData(DbbeCursor *pCursr, int offset){
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
pCursr->readPending = 0;
}
if( offset<0 || offset>=pCursr->data.dsize ) return "";
return &pCursr->data.dptr[offset];
}
/*
** Return the total number of bytes in either data or key.
*/
static int sqliteGdbmKeyLength(DbbeCursor *pCursr){
return pCursr->key.dsize;
}
static int sqliteGdbmDataLength(DbbeCursor *pCursr){
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
pCursr->readPending = 0;
}
return pCursr->data.dsize;
}
/*
** Make is so that the next call to sqliteNextKey() finds the first
** key of the table.
*/
static int sqliteGdbmRewind(DbbeCursor *pCursr){
pCursr->needRewind = 1;
return SQLITE_OK;
}
/*
** Read the next key from the table. Return 1 on success. Return
** 0 if there are no more keys.
*/
static int sqliteGdbmNextKey(DbbeCursor *pCursr){
datum nextkey;
int rc;
if( pCursr==0 || pCursr->pFile==0 || pCursr->pFile->dbf==0 ){
pCursr->readPending = 0;
return 0;
}
if( pCursr->needRewind ){
nextkey = gdbm_firstkey(pCursr->pFile->dbf);
pCursr->needRewind = 0;
}else{
nextkey = gdbm_nextkey(pCursr->pFile->dbf, pCursr->key);
}
datumClear(&pCursr->key);
datumClear(&pCursr->data);
pCursr->key = nextkey;
if( pCursr->key.dptr ){
pCursr->readPending = 1;
rc = 1;
}else{
pCursr->needRewind = 1;
pCursr->readPending = 0;
rc = 0;
}
return rc;
}
/*
** Get a new integer key.
*/
static int sqliteGdbmNew(DbbeCursor *pCursr){
int iKey;
datum key;
int go = 1;
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return 1;
while( go ){
iKey = sqliteRandomInteger();
if( iKey==0 ) continue;
key.dptr = (char*)&iKey;
key.dsize = 4;
go = gdbm_exists(pCursr->pFile->dbf, key);
}
return iKey;
}
/*
** Write an entry into the table. Overwrite any prior entry with the
** same key.
*/
static int sqliteGdbmPut(
DbbeCursor *pCursr, /* Write to the database associated with this cursor */
int nKey, /* Number of bytes in the key */
char *pKey, /* The data for the key */
int nData, /* Number of bytes of data */
char *pData /* The data */
){
datum data, key;
int rc;
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
data.dsize = nData;
data.dptr = pData;
key.dsize = nKey;
key.dptr = pKey;
rc = gdbm_store(pCursr->pFile->dbf, key, data, GDBM_REPLACE);
if( rc ) rc = SQLITE_ERROR;
datumClear(&pCursr->key);
datumClear(&pCursr->data);
return rc;
}
/*
** Remove an entry from a table, if the entry exists.
*/
static int sqliteGdbmDelete(DbbeCursor *pCursr, int nKey, char *pKey){
datum key;
int rc;
datumClear(&pCursr->key);
datumClear(&pCursr->data);
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
key.dsize = nKey;
key.dptr = pKey;
rc = gdbm_delete(pCursr->pFile->dbf, key);
if( rc ) rc = SQLITE_ERROR;
return rc;
}
/*
** Open a temporary file. The file is located in the same directory
** as the rest of the database.
*/
static int sqliteGdbmOpenTempFile(Dbbe *pDbbe, FILE **ppFile){
Dbbex *pBe = (Dbbex*)pDbbe;
return sqliteDbbeOpenTempFile(pBe->zDir, pDbbe, ppFile);
}
/*
** This variable contains pointers to all of the access methods
** used to implement the GDBM backend.
*/
static struct DbbeMethods gdbmMethods = {
/* n Close */ sqliteGdbmClose,
/* OpenCursor */ sqliteGdbmOpenCursor,
/* DropTable */ sqliteGdbmDropTable,
/* ReorganizeTable */ sqliteGdbmReorganizeTable,
/* CloseCursor */ sqliteGdbmCloseCursor,
/* Fetch */ sqliteGdbmFetch,
/* Test */ sqliteGdbmTest,
/* CopyKey */ sqliteGdbmCopyKey,
/* CopyData */ sqliteGdbmCopyData,
/* ReadKey */ sqliteGdbmReadKey,
/* ReadData */ sqliteGdbmReadData,
/* KeyLength */ sqliteGdbmKeyLength,
/* DataLength */ sqliteGdbmDataLength,
/* NextKey */ sqliteGdbmNextKey,
/* Rewind */ sqliteGdbmRewind,
/* New */ sqliteGdbmNew,
/* Put */ sqliteGdbmPut,
/* Delete */ sqliteGdbmDelete,
/* OpenTempFile */ sqliteGdbmOpenTempFile,
/* CloseTempFile */ sqliteDbbeCloseTempFile
};
/*
** This routine opens a new database. For the GDBM driver
** implemented here, the database name is the name of the directory
** containing all the files of the database.
**
** If successful, a pointer to the Dbbe structure is returned.
** If there are errors, an appropriate error message is left
** in *pzErrMsg and NULL is returned.
*/
Dbbe *sqliteGdbmOpen(
const char *zName, /* The name of the database */
int writeFlag, /* True if we will be writing to the database */
int createFlag, /* True to create database if it doesn't exist */
char **pzErrMsg /* Write error messages (if any) here */
){
Dbbex *pNew;
struct stat statbuf;
char *zMaster;
if( !writeFlag ) createFlag = 0;
if( stat(zName, &statbuf)!=0 ){
if( createFlag ) mkdir(zName, 0750);
if( stat(zName, &statbuf)!=0 ){
sqliteSetString(pzErrMsg, createFlag ?
"can't find or create directory \"" : "can't find directory \"",
zName, "\"", 0);
return 0;
}
}
if( !S_ISDIR(statbuf.st_mode) ){
sqliteSetString(pzErrMsg, "not a directory: \"", zName, "\"", 0);
return 0;
}
if( access(zName, writeFlag ? (X_OK|W_OK|R_OK) : (X_OK|R_OK)) ){
sqliteSetString(pzErrMsg, "access permission denied", 0);
return 0;
}
zMaster = 0;
sqliteSetString(&zMaster, zName, "/" MASTER_NAME ".tbl", 0);
if( stat(zMaster, &statbuf)==0
&& access(zMaster, writeFlag ? (W_OK|R_OK) : R_OK)!=0 ){
sqliteSetString(pzErrMsg, "access permission denied for ", zMaster, 0);
sqliteFree(zMaster);
return 0;
}
sqliteFree(zMaster);
pNew = sqliteMalloc(sizeof(Dbbex) + strlen(zName) + 1);
if( pNew==0 ){
sqliteSetString(pzErrMsg, "out of memory", 0);
return 0;
}
pNew->dbbe.x = &gdbmMethods;
pNew->zDir = (char*)&pNew[1];
strcpy(pNew->zDir, zName);
pNew->write = writeFlag;
pNew->pOpen = 0;
return &pNew->dbbe;
}
+791
View File
@@ -0,0 +1,791 @@
/*
** Copyright (c) 2000 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*************************************************************************
** This file contains code to implement the database backend (DBBE)
** for sqlite. The database backend is the interface between
** sqlite and the code that does the actually reading and writing
** of information to the disk.
**
** This file uses an in-memory hash table as the database backend.
**
** $Id: dbbemem.c,v 1.7 2001/01/15 22:51:10 drh Exp $
*/
#include "sqliteInt.h"
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
typedef struct Array Array;
typedef struct ArrayElem ArrayElem;
typedef struct Datum Datum;
/* A complete associative array is an instance of the following structure.
** The internals of this structure are intended to be opaque -- client
** code should not attempt to access or modify the fields of this structure
** directly. Change this structure only by using the routines below.
** However, many of the "procedures" and "functions" for modifying and
** accessing this structure are really macros, so we can't really make
** this structure opaque.
*/
struct Array {
int count; /* Number of entries in the array */
ArrayElem *first; /* The first element of the array */
int htsize; /* Number of buckets in the hash table */
struct _Array_ht { /* the hash table */
int count; /* Number of entries with this hash */
ArrayElem *chain; /* Pointer to first entry with this hash */
} *ht;
};
/*
** An instance of the following structure stores a single key or
** data element.
*/
struct Datum {
int n;
void *p;
};
/* Each element in the associative array is an instance of the following
** structure. All elements are stored on a single doubly-linked list.
**
** Again, this structure is intended to be opaque, but it can't really
** be opaque because it is used by macros.
*/
struct ArrayElem {
ArrayElem *next, *prev; /* Next and previous elements in the array */
Datum key, data; /* Key and data for this element */
};
/* Some routines are so simple that they can be implemented as macros
** These are given first. */
/* Return the number of entries in the array */
#define ArrayCount(X) ((X)->count)
/* Return a pointer to the first element of the array */
#define ArrayFirst(X) ((X)->first)
/* Return a pointer to the next (or previous) element of the array */
#define ArrayNext(X) ((X)->next)
#define ArrayPrev(X) ((X)->prev)
/* Return TRUE if the element given is the last element in the array */
#define ArrayIsLast(X) ((X)->next==0)
#define ArrayIsFirst(X) ((X)->prev==0)
/* Return the data or key for an element of the array */
#define ArrayData(X) ((X)->data.p)
#define ArrayDataSize(X) ((X)->data.n)
#define ArrayKey(X) ((X)->key.p)
#define ArrayKeySize(X) ((X)->key.n)
/* Turn bulk memory into an associative array object by initializing the
** fields of the Array structure.
*/
static void ArrayInit(Array *new){
new->first = 0;
new->count = 0;
new->htsize = 0;
new->ht = 0;
}
/* Remove all entries from an associative array. Reclaim all memory.
** This is the opposite of ArrayInit().
*/
static void ArrayClear(Array *array){
ArrayElem *elem; /* For looping over all elements of the array */
elem = array->first;
array->first = 0;
array->count = 0;
if( array->ht ) sqliteFree(array->ht);
array->ht = 0;
array->htsize = 0;
while( elem ){
ArrayElem *next_elem = elem->next;
sqliteFree(elem);
elem = next_elem;
}
}
/*
** Generate a hash from an N-byte key
*/
static int ArrayHash(Datum d){
int h = 0;
while( d.n-- > 0 ){
/* The funky case "*(char**)&d.p" is to work around a bug the
** c89 compiler of HPUX. */
h = (h<<9) ^ (h<<3) ^ h ^ *((*(char**)&d.p)++);
}
if( h<0 ) h = -h;
return h;
}
/* Resize the hash table for a Array array
*/
static void ArrayRehash(Array *array, int new_size){
struct _Array_ht *new_ht; /* The new hash table */
ArrayElem *elem, *next_elem; /* For looping over existing elements */
int i; /* Loop counter */
ArrayElem *x; /* Element being copied to new hash table */
new_ht = sqliteMalloc( new_size*sizeof(struct _Array_ht) );
if( array->ht ) sqliteFree(array->ht);
array->ht = new_ht;
array->htsize = new_size;
for(i=new_size-1; i>=0; i--){
new_ht[i].count = 0;
new_ht[i].chain = 0;
}
for(elem=array->first, array->first=0; elem; elem = next_elem){
int h = ArrayHash(elem->key) & (new_size-1);
next_elem = elem->next;
x = new_ht[h].chain;
if( x ){
elem->next = x;
elem->prev = x->prev;
if( x->prev ) x->prev->next = elem;
else array->first = elem;
x->prev = elem;
}else{
elem->next = array->first;
if( array->first ) array->first->prev = elem;
elem->prev = 0;
array->first = elem;
}
new_ht[h].chain = elem;
new_ht[h].count++;
}
}
/* This function (for internal use only) locates an element in an
** array that matches the given key. The hash for this key has
** already been computed and is passed as the 3rd parameter.
*/
static ArrayElem *ArrayFindElementGivenHash(
const Array *array, /* The array to be searched */
const Datum key, /* The key we are searching for */
int h /* The hash for this key. */
){
ArrayElem *elem; /* Used to loop thru the element list */
int count; /* Number of elements left to test */
if( array->count ){
elem = array->ht[h].chain;
count = array->ht[h].count;
while( count-- && elem ){
if( elem->key.n==key.n && memcmp(elem->key.p,key.p,key.n)==0 ){
return elem;
}
elem = elem->next;
}
}
return 0;
}
/* Attempt to locate an element of the associative array with a key
** that matches "key". Return the ArrayElement if found and NULL if
** if no match.
*/
static ArrayElem *ArrayFindElement(const Array *array, Datum key){
int h; /* A hash on key */
if( array->count==0 ) return 0;
h = ArrayHash(key);
return ArrayFindElementGivenHash(array, key, h & (array->htsize-1));
}
/* Remove a single entry from the array given a pointer to that
** element and a hash on the element's key.
*/
static void ArrayRemoveElementGivenHash(
Array *array, /* The array containing "elem" */
ArrayElem* elem, /* The element to be removed from the array */
int h /* Hash value for the element */
){
if( elem->prev ){
elem->prev->next = elem->next;
}else{
array->first = elem->next;
}
if( elem->next ){
elem->next->prev = elem->prev;
}
if( array->ht[h].chain==elem ){
array->ht[h].chain = elem->next;
}
array->ht[h].count--;
if( array->ht[h].count<=0 ){
array->ht[h].chain = 0;
}
sqliteFree( elem );
array->count--;
}
/* Attempt to locate an element of the associative array with a key
** that matches "key". Return the data for this element if it is
** found, or NULL if no match is found.
*/
static Datum ArrayFind(const Array *array, Datum key){
int h; /* A hash on key */
ArrayElem *elem; /* The element that matches key */
static Datum nil = {0, 0};
if( array->count==0 ) return nil;
h = ArrayHash(key);
elem = ArrayFindElementGivenHash(array, key, h & (array->htsize-1));
return elem ? elem->data : nil;
}
/* Insert an element into the array. The key will be "key" and
** the data will be "data".
**
** If no array element exists with a matching key, then a new
** array element is created. The key is copied into the new element.
** But only a pointer to the data is stored. NULL is returned.
**
** If another element already exists with the same key, then the
** new data replaces the old data and the old data is returned.
** The key is not copied in this instance.
**
** If the "data" parameter to this function is NULL, then the
** element corresponding to "key" is removed from the array.
*/
static Datum ArrayInsert(Array *array, Datum key, Datum data){
int hraw; /* Raw hash value of the key */
int h; /* the hash of the key modulo hash table size */
ArrayElem *elem; /* Used to loop thru the element list */
ArrayElem *new_elem; /* New element added to the array */
Datum rv; /* Return value */
static Datum nil = {0, 0};
hraw = ArrayHash(key);
h = hraw & (array->htsize-1);
elem = ArrayFindElementGivenHash(array,key,h);
if( elem ){
Datum old_data = elem->data;
if( data.p==0 ){
ArrayRemoveElementGivenHash(array,elem,h);
}else{
elem->data = data;
}
return old_data;
}
if( data.p==0 ) return nil;
new_elem = (ArrayElem*)sqliteMalloc( sizeof(ArrayElem) + key.n );
if( new_elem==0 ) return nil;
new_elem->key.n = key.n;
new_elem->key.p = (void*)&new_elem[1];
memcpy(new_elem->key.p, key.p, key.n);
array->count++;
if( array->htsize==0 ) ArrayRehash(array,4);
if( array->count > array->htsize ){
ArrayRehash(array,array->htsize*2);
}
h = hraw & (array->htsize-1);
elem = array->ht[h].chain;
if( elem ){
new_elem->next = elem;
new_elem->prev = elem->prev;
if( elem->prev ){ elem->prev->next = new_elem; }
else { array->first = new_elem; }
elem->prev = new_elem;
}else{
new_elem->next = array->first;
new_elem->prev = 0;
if( array->first ){ array->first->prev = new_elem; }
array->first = new_elem;
}
array->ht[h].count++;
array->ht[h].chain = new_elem;
new_elem->data = data;
rv.p = 0;
rv.n = 0;
return rv;
}
/*
** Information about each open database table is an instance of this
** structure. There will only be one such structure for each
** table. If the VDBE opens the same table twice (as will happen
** for a self-join, for example) then two DbbeCursor structures are
** created but there is only a single MTable structure.
*/
typedef struct MTable MTable;
struct MTable {
char *zName; /* Name of the table */
int delOnClose; /* Delete when closing */
int intKeyOnly; /* Use only integer keys on this table */
Array data; /* The data in this stable */
};
/*
** The following structure contains all information used by GDBM
** database driver. This is a subclass of the Dbbe structure.
*/
typedef struct Dbbex Dbbex;
struct Dbbex {
Dbbe dbbe; /* The base class */
Array tables; /* All tables of the database */
};
/*
** An cursor into a database file is an instance of the following structure.
** There can only be a single MTable structure for each disk file, but
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
** a cursor pointing to a particular part of the open MTable. The
** MTable.nRef field hold a count of the number of DbbeCursor structures
** associated with the same disk file.
*/
struct DbbeCursor {
Dbbex *pBe; /* The database of which this record is a part */
MTable *pTble; /* The database file for this table */
ArrayElem *elem; /* Most recently accessed record */
int needRewind; /* Next key should be the first */
};
/*
** Forward declaration
*/
static void sqliteMemCloseCursor(DbbeCursor *pCursr);
/*
** Erase all the memory of an MTable
*/
static void deleteMTable(MTable *p){
ArrayElem *i;
for(i=ArrayFirst(&p->data); i; i=ArrayNext(i)){
void *data = ArrayData(i);
sqliteFree(data);
}
ArrayClear(&p->data);
sqliteFree(p->zName);
sqliteFree(p);
}
/*
** Completely shutdown the given database. Close all files. Free all memory.
*/
static void sqliteMemClose(Dbbe *pDbbe){
Dbbex *pBe = (Dbbex*)pDbbe;
MTable *pTble;
ArrayElem *j;
for(j=ArrayFirst(&pBe->tables); j; j=ArrayNext(j)){
pTble = ArrayData(j);
deleteMTable(pTble);
}
ArrayClear(&pBe->tables);
sqliteDbbeCloseAllTempFiles(pDbbe);
memset(pBe, 0, sizeof(*pBe));
sqliteFree(pBe);
}
/*
** Translate the name of an SQL table (or index) into its
** canonical name.
**
** Space to hold the canonical name is obtained from
** sqliteMalloc() and must be freed by the calling function.
*/
static char *sqliteNameOfTable(const char *zTable){
char *zNew = 0;
int i, c;
sqliteSetString(&zNew, zTable, 0);
if( zNew==0 ) return 0;
for(i=0; (c = zNew[i])!=0; i++){
if( isupper(c) ){
zNew[i] = tolower(c);
}
}
return zNew;
}
/*
** Open a new table cursor. Write a pointer to the corresponding
** DbbeCursor structure into *ppCursr. Return an integer success
** code:
**
** SQLITE_OK It worked!
**
** SQLITE_NOMEM sqliteMalloc() failed
**
** SQLITE_PERM Attempt to access a file for which file
** access permission is denied
**
** SQLITE_BUSY Another thread or process is already using
** the corresponding file and has that file locked.
**
** SQLITE_READONLY The current thread already has this file open
** readonly but you are trying to open for writing.
** (This can happen if a SELECT callback tries to
** do an UPDATE or DELETE.)
**
** If zTable is 0 or "", then a temporary database file is created and
** a cursor to that temporary file is opened. The temporary file
** will be deleted from the disk when it is closed.
*/
static int sqliteMemOpenCursor(
Dbbe *pDbbe, /* The database the table belongs to */
const char *zTable, /* The SQL name of the file to be opened */
int writeable, /* True to open for writing */
int intKeyOnly, /* True if only integer keys are used */
DbbeCursor **ppCursr /* Write the resulting table pointer here */
){
DbbeCursor *pCursr; /* The new table cursor */
char *zName; /* Canonical table name */
MTable *pTble; /* The underlying data file for this table */
int rc = SQLITE_OK; /* Return value */
Dbbex *pBe = (Dbbex*)pDbbe;
*ppCursr = 0;
pCursr = sqliteMalloc( sizeof(*pCursr) );
if( pCursr==0 ) return SQLITE_NOMEM;
if( zTable ){
Datum key;
zName = sqliteNameOfTable(zTable);
key.p = zName;
key.n = strlen(zName);
pTble = ArrayFind(&pBe->tables, key).p;
}else{
zName = 0;
pTble = 0;
}
if( pTble==0 ){
pTble = sqliteMalloc( sizeof(*pTble) );
if( pTble==0 ){
sqliteFree(zName);
return SQLITE_NOMEM;
}
if( zName ){
Datum ins_key, ins_data;
pTble->zName = zName;
pTble->delOnClose = 0;
ins_data.p = pTble;
ins_data.n = sizeof( *pTble );
ins_key.p = zName;
ins_key.n = strlen(zName);
ArrayInsert(&pBe->tables, ins_key, ins_data);
}else{
pTble->zName = 0;
pTble->delOnClose = 1;
}
pTble->intKeyOnly = intKeyOnly;
ArrayInit(&pTble->data);
}else{
assert( pTble->intKeyOnly==intKeyOnly );
sqliteFree(zName);
}
pCursr->pBe = pBe;
pCursr->pTble = pTble;
pCursr->needRewind = 1;
*ppCursr = pCursr;
return rc;
}
/*
** Drop a table from the database. The file on the disk that corresponds
** to this table is deleted.
*/
static void sqliteMemDropTable(Dbbe *pDbbe, const char *zTable){
char *zName; /* Name of the table file */
Datum key, data;
MTable *pTble;
Dbbex *pBe = (Dbbex*)pDbbe;
zName = sqliteNameOfTable(zTable);
key.p = zName;
key.n = strlen(zName);
pTble = ArrayFind(&pBe->tables, key).p;
if( pTble ){
data.p = 0;
data.n = 0;
ArrayInsert(&pBe->tables, key, data);
deleteMTable(pTble);
}
sqliteFree(zName);
}
/*
** Close a cursor previously opened by sqliteMemOpenCursor().
**
** There can be multiple cursors pointing to the same open file.
** The underlying file is not closed until all cursors have been
** closed. This routine decrements the MTable.nref field of the
** underlying file and closes the file when nref reaches 0.
*/
static void sqliteMemCloseCursor(DbbeCursor *pCursr){
MTable *pTble;
Dbbex *pBe;
if( pCursr==0 ) return;
pTble = pCursr->pTble;
pBe = pCursr->pBe;
if( pTble->delOnClose ){
deleteMTable(pTble);
}
sqliteFree(pCursr);
}
/*
** Reorganize a table to reduce search times and disk usage.
*/
static int sqliteMemReorganizeTable(Dbbe *pBe, const char *zTable){
/* Do nothing */
return SQLITE_OK;
}
/*
** Fetch a single record from an open cursor. Return 1 on success
** and 0 on failure.
*/
static int sqliteMemFetch(DbbeCursor *pCursr, int nKey, char *pKey){
Datum key;
key.n = nKey;
key.p = pKey;
assert( nKey==4 || pCursr->pTble->intKeyOnly==0 );
pCursr->elem = ArrayFindElement(&pCursr->pTble->data, key);
return pCursr->elem!=0;
}
/*
** Return 1 if the given key is already in the table. Return 0
** if it is not.
*/
static int sqliteMemTest(DbbeCursor *pCursr, int nKey, char *pKey){
return sqliteMemFetch(pCursr, nKey, pKey);
}
/*
** Copy bytes from the current key or data into a buffer supplied by
** the calling function. Return the number of bytes copied.
*/
static
int sqliteMemCopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
int n;
if( pCursr->elem==0 ) return 0;
if( offset>=ArrayKeySize(pCursr->elem) ) return 0;
if( offset+size>ArrayKeySize(pCursr->elem) ){
n = ArrayKeySize(pCursr->elem) - offset;
}else{
n = size;
}
memcpy(zBuf, &((char*)ArrayKey(pCursr->elem))[offset], n);
return n;
}
static
int sqliteMemCopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
int n;
if( pCursr->elem==0 ) return 0;
if( offset>=ArrayDataSize(pCursr->elem) ) return 0;
if( offset+size>ArrayDataSize(pCursr->elem) ){
n = ArrayDataSize(pCursr->elem) - offset;
}else{
n = size;
}
memcpy(zBuf, &((char*)ArrayData(pCursr->elem))[offset], n);
return n;
}
/*
** Return a pointer to bytes from the key or data. The data returned
** is ephemeral.
*/
static char *sqliteMemReadKey(DbbeCursor *pCursr, int offset){
if( pCursr->elem==0 || offset<0 || offset>=ArrayKeySize(pCursr->elem) ){
return "";
}
return &((char*)ArrayKey(pCursr->elem))[offset];
}
static char *sqliteMemReadData(DbbeCursor *pCursr, int offset){
if( pCursr->elem==0 || offset<0 || offset>=ArrayDataSize(pCursr->elem) ){
return "";
}
return &((char*)ArrayData(pCursr->elem))[offset];
}
/*
** Return the total number of bytes in either data or key.
*/
static int sqliteMemKeyLength(DbbeCursor *pCursr){
return pCursr->elem ? ArrayKeySize(pCursr->elem) : 0;
}
static int sqliteMemDataLength(DbbeCursor *pCursr){
return pCursr->elem ? ArrayDataSize(pCursr->elem) : 0;
}
/*
** Make is so that the next call to sqliteNextKey() finds the first
** key of the table.
*/
static int sqliteMemRewind(DbbeCursor *pCursr){
pCursr->needRewind = 1;
return SQLITE_OK;
}
/*
** Read the next key from the table. Return 1 on success. Return
** 0 if there are no more keys.
*/
static int sqliteMemNextKey(DbbeCursor *pCursr){
if( pCursr->needRewind || pCursr->elem==0 ){
pCursr->elem = ArrayFirst(&pCursr->pTble->data);
pCursr->needRewind = 0;
}else{
pCursr->elem = ArrayNext(pCursr->elem);
}
return pCursr->elem!=0;
}
/*
** Get a new integer key.
*/
static int sqliteMemNew(DbbeCursor *pCursr){
int iKey;
Datum key;
int go = 1;
while( go ){
iKey = sqliteRandomInteger();
if( iKey==0 ) continue;
key.p = (char*)&iKey;
key.n = 4;
go = ArrayFindElement(&pCursr->pTble->data, key)!=0;
}
return iKey;
}
/*
** Write an entry into the table. Overwrite any prior entry with the
** same key.
*/
static int sqliteMemPut(
DbbeCursor *pCursr, /* Write new entry into this database table */
int nKey, char *pKey, /* The key of the new entry */
int nData, char *pData /* The data of the new entry */
){
Datum data, key;
data.n = nData;
data.p = sqliteMalloc( data.n );
memcpy(data.p, pData, data.n);
key.n = nKey;
key.p = pKey;
assert( nKey==4 || pCursr->pTble->intKeyOnly==0 );
data = ArrayInsert(&pCursr->pTble->data, key, data);
if( data.p ){
sqliteFree(data.p);
}
return SQLITE_OK;
}
/*
** Remove an entry from a table, if the entry exists.
*/
static int sqliteMemDelete(DbbeCursor *pCursr, int nKey, char *pKey){
Datum key, data;
key.n = nKey;
key.p = pKey;
data.p = 0;
data.n = 0;
data = ArrayInsert(&pCursr->pTble->data, key, data);
if( data.p ){
sqliteFree(data.p);
}
return SQLITE_OK;
}
/*
** Open a temporary file. The file is located in the current working
** directory.
*/
static int sqliteMemOpenTempFile(Dbbe *pDbbe, FILE **ppFile){
const char *zTemps[] = { "/usr/tmp", "/var/tmp", "/tmp", "/temp", 0};
const char *zDir;
int i;
struct stat statbuf;
for(i=0; zTemps[i]; i++){
zDir = zTemps[i];
if( stat("/usr/tmp", &statbuf)==0 && S_ISDIR(statbuf.st_mode)
&& access("/usr/tmp", W_OK|X_OK)==0 ){
break;
}
}
if( zDir==0 ) zDir = ".";
return sqliteDbbeOpenTempFile(zDir, pDbbe, ppFile);
}
/*
** This variable contains pointers to all of the access methods
** used to implement the MEMORY backend.
*/
static struct DbbeMethods memoryMethods = {
/* n Close */ sqliteMemClose,
/* OpenCursor */ sqliteMemOpenCursor,
/* DropTable */ sqliteMemDropTable,
/* ReorganizeTable */ sqliteMemReorganizeTable,
/* CloseCursor */ sqliteMemCloseCursor,
/* Fetch */ sqliteMemFetch,
/* Test */ sqliteMemTest,
/* CopyKey */ sqliteMemCopyKey,
/* CopyData */ sqliteMemCopyData,
/* ReadKey */ sqliteMemReadKey,
/* ReadData */ sqliteMemReadData,
/* KeyLength */ sqliteMemKeyLength,
/* DataLength */ sqliteMemDataLength,
/* NextKey */ sqliteMemNextKey,
/* Rewind */ sqliteMemRewind,
/* New */ sqliteMemNew,
/* Put */ sqliteMemPut,
/* Delete */ sqliteMemDelete,
/* OpenTempFile */ sqliteMemOpenTempFile,
/* CloseTempFile */ sqliteDbbeCloseTempFile
};
/*
** This routine opens a new database. For the GDBM driver
** implemented here, the database name is the name of the directory
** containing all the files of the database.
**
** If successful, a pointer to the Dbbe structure is returned.
** If there are errors, an appropriate error message is left
** in *pzErrMsg and NULL is returned.
*/
Dbbe *sqliteMemOpen(
const char *zName, /* The name of the database */
int writeFlag, /* True if we will be writing to the database */
int createFlag, /* True to create database if it doesn't exist */
char **pzErrMsg /* Write error messages (if any) here */
){
Dbbex *pNew;
pNew = sqliteMalloc( sizeof(*pNew) );
if( pNew==0 ){
sqliteSetString(pzErrMsg, "out of memory", 0);
return 0;
}
ArrayInit(&pNew->tables);
pNew->dbbe.x = &memoryMethods;
return &pNew->dbbe;
}
+3 -3
View File
@@ -24,7 +24,7 @@
** This file contains C code routines that are called by the parser
** to handle DELETE FROM statements.
**
** $Id: delete.c,v 1.6 2000/06/21 13:59:11 drh Exp $
** $Id: delete.c,v 1.7 2001/01/15 22:51:10 drh Exp $
*/
#include "sqliteInt.h"
@@ -105,9 +105,9 @@ void sqliteDeleteFrom(
*/
base = pParse->nTab;
sqliteVdbeAddOp(v, OP_ListRewind, 0, 0, 0, 0);
sqliteVdbeAddOp(v, OP_Open, base, 1, pTab->zName, 0);
sqliteVdbeAddOp(v, OP_OpenTbl, base, 1, pTab->zName, 0);
for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
sqliteVdbeAddOp(v, OP_Open, base+i, 1, pIdx->zName, 0);
sqliteVdbeAddOp(v, OP_OpenIdx, base+i, 1, pIdx->zName, 0);
}
end = sqliteVdbeMakeLabel(v);
addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end, 0, 0);
+9
View File
@@ -0,0 +1,9 @@
This directory is intended to hold "experimental" files.
The code in this directory does not necessary work. It may
or may not be added to future SQLite releases. We just need
a place to put ideas and works-in-progress and so this
directory was created.
If you are interested in the production versions of SQLite,
you can safely ignore this directory.
+1495
View File
File diff suppressed because it is too large Load Diff
+51
View File
@@ -0,0 +1,51 @@
/*
** Copyright (c) 2001 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*************************************************************************
** $Id: db.h,v 1.1 2001/02/11 16:56:24 drh Exp $
*/
typedef struct Db Db;
typedef struct DbCursor DbCursor;
int sqliteDbOpen(const char *filename, Db**);
int sqliteDbClose(Db*);
int sqliteDbBeginTransaction(Db*);
int sqliteDbCommit(Db*);
int sqliteDbRollback(Db*);
int sqliteDbCreateTable(Db*, int *pTblno);
int sqliteDbDropTable(Db*, int tblno);
int sqliteDbCursorOpen(Db*, int tblno, DbCursor**);
int sqliteDbCursorClose(DbCursor*);
int sqliteDbCursorFirst(DbCursor*);
int sqliteDbCursorNext(DbCursor*);
int sqliteDbCursorDatasize(DbCursor*);
int sqliteDbCursorKeysize(DbCursor*);
int sqliteDbCursorRead(DbCursor*, int amt, int offset, void *buf);
int sqliteDbCursorReadKey(DbCursor*, int amt, int offset, void *buf);
int sqliteDbCursorWrite(DbCursor*, int amt, int offset, const void *buf);
int sqliteDbCursorFind(DbCursor*, int nKey, const void *pKey, int createFlag);
int sqliteDbCursorResize(DbCursor*, int nData);
+618
View File
@@ -0,0 +1,618 @@
/*
** Copyright (c) 2000 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*************************************************************************
** This file contains code to implement the database backend (DBBE)
** for sqlite. The database backend is the interface between
** sqlite and the code that does the actually reading and writing
** of information to the disk.
**
** This file uses Berkeley Database version 1.85 as the database backend.
**
** $Id: dbbebdb1.c,v 1.1 2001/02/11 16:56:24 drh Exp $
*/
#ifdef USE_BDB2
#include "sqliteInt.h"
#include <sys/types.h>
#include <limits.h>
#include <db.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
/*
** Information about each open disk file is an instance of this
** structure. There will only be one such structure for each
** disk file. If the VDBE opens the same file twice (as will happen
** for a self-join, for example) then two DbbeCursor structures are
** created but there is only a single BeFile structure with an
** nRef of 2.
*/
typedef struct BeFile BeFile;
struct BeFile {
char *zName; /* Name of the file */
DB dbf; /* The file itself */
int nRef; /* Number of references */
int delOnClose; /* Delete when closing */
int writeable; /* Opened for writing */
DbbeCursor *pCursor; /* Which of several DbbeCursors has the file cursor */
BeFile *pNext, *pPrev; /* Next and previous on list of open files */
};
/*
** The following structure contains all information used by BDB2
** database driver. This is a subclass of the Dbbe structure.
*/
typedef struct Dbbex Dbbex;
struct Dbbex {
Dbbe dbbe; /* The base class */
int write; /* True for write permission */
BeFile *pOpen; /* List of open files */
char *zDir; /* Directory hold the database */
};
/*
** An cursor into a database file is an instance of the following structure.
** There can only be a single BeFile structure for each disk file, but
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
** a cursor pointing to a particular part of the open BeFile. The
** BeFile.nRef field hold a count of the number of DbbeCursor structures
** associated with the same disk file.
*/
struct DbbeCursor {
Dbbex *pBe; /* The database of which this record is a part */
BeFile *pFile; /* The database file for this table */
DBT key; /* Most recently used key */
DBT data; /* Most recent data */
int needRewind; /* Next key should be the first */
int readPending; /* The fetch hasn't actually been done yet */
};
/*
** The "mkdir()" function only takes one argument under Windows.
*/
#if OS_WIN
# define mkdir(A,B) mkdir(A)
#endif
/*
** Forward declaration
*/
static void sqliteBdb1CloseCursor(DbbeCursor *pCursr);
/*
** Completely shutdown the given database. Close all files. Free all memory.
*/
static void sqliteBdb1Close(Dbbe *pDbbe){
Dbbex *pBe = (Dbbex*)pDbbe;
BeFile *pFile, *pNext;
for(pFile=pBe->pOpen; pFile; pFile=pNext){
pNext = pFile->pNext;
(*pFile->dbf)(pFile->dbf);
memset(pFile, 0, sizeof(*pFile));
sqliteFree(pFile);
}
sqliteDbbeCloseAllTempFiles(pDbbe);
memset(pBe, 0, sizeof(*pBe));
sqliteFree(pBe);
}
/*
** Translate the name of an SQL table (or index) into the name
** of a file that holds the key/data pairs for that table or
** index. Space to hold the filename is obtained from
** sqliteMalloc() and must be freed by the calling function.
*/
static char *sqliteFileOfTable(Dbbex *pBe, const char *zTable){
return sqliteDbbeNameToFile(pBe->zDir, zTable, ".tbl");
}
/*
** Open a new table cursor. Write a pointer to the corresponding
** DbbeCursor structure into *ppCursr. Return an integer success
** code:
**
** SQLITE_OK It worked!
**
** SQLITE_NOMEM sqliteMalloc() failed
**
** SQLITE_PERM Attempt to access a file for which file
** access permission is denied
**
** SQLITE_BUSY Another thread or process is already using
** the corresponding file and has that file locked.
**
** SQLITE_READONLY The current thread already has this file open
** readonly but you are trying to open for writing.
** (This can happen if a SELECT callback tries to
** do an UPDATE or DELETE.)
**
** If zTable is 0 or "", then a temporary database file is created and
** a cursor to that temporary file is opened. The temporary file
** will be deleted from the disk when it is closed.
*/
static int sqliteBdb1OpenCursor(
Dbbe *pDbbe, /* The database the table belongs to */
const char *zTable, /* The SQL name of the file to be opened */
int writeable, /* True to open for writing */
int intKeyOnly, /* True if only integer keys are used */
DbbeCursor **ppCursr /* Write the resulting table pointer here */
){
char *zFile; /* Name of the table file */
DbbeCursor *pCursr; /* The new table cursor */
BeFile *pFile; /* The underlying data file for this table */
int rc = SQLITE_OK; /* Return value */
int open_flags; /* Flags passed to dbopen() */
Dbbex *pBe = (Dbbex*)pDbbe;
*ppCursr = 0;
pCursr = sqliteMalloc( sizeof(*pCursr) );
if( pCursr==0 ) return SQLITE_NOMEM;
if( zTable ){
zFile = sqliteFileOfTable(pBe, zTable);
for(pFile=pBe->pOpen; pFile; pFile=pFile->pNext){
if( strcmp(pFile->zName,zFile)==0 ) break;
}
}else{
pFile = 0;
zFile = 0;
}
if( pFile==0 ){
if( writeable ){
open_flags = O_RDWR|O_CREAT
}else{
open_flags = O_RDONLY;
}
pFile = sqliteMalloc( sizeof(*pFile) );
if( pFile==0 ){
sqliteFree(zFile);
return SQLITE_NOMEM;
}
if( zFile ){
if( !writeable || pBe->write ){
pFile->dbf = dbopen(zFile, open_flags, DB_HASH, 0);
}else{
pFile->dbf = 0;
}
}else{
int limit;
char zRandom[50];
zFile = 0;
limit = 5;
do {
sqliteRandomName(zRandom, "_temp_table_");
sqliteFree(zFile);
zFile = sqliteFileOfTable(pBe, zRandom);
pFile->dbf = dbopen(zFile, open_flags, DB_HASH, 0);
}while( pFile->dbf==0 && limit-- >= 0);
pFile->delOnClose = 1;
}
pFile->writeable = writeable;
pFile->zName = zFile;
pFile->nRef = 1;
pFile->pPrev = 0;
if( pBe->pOpen ){
pBe->pOpen->pPrev = pFile;
}
pFile->pCursor = 0;
pFile->pNext = pBe->pOpen;
pBe->pOpen = pFile;
if( pFile->dbf==0 ){
if( !writeable && access(zFile,0) ){
/* Trying to read a non-existant file. This is OK. All the
** reads will return empty, which is what we want. */
rc = SQLITE_OK;
}else if( pBe->write==0 ){
rc = SQLITE_READONLY;
}else if( access(zFile,W_OK|R_OK) ){
rc = SQLITE_PERM;
}else{
rc = SQLITE_BUSY;
}
}
}else{
sqliteFree(zFile);
pFile->nRef++;
if( writeable && !pFile->writeable ){
rc = SQLITE_READONLY;
}
}
pCursr->pBe = pBe;
pCursr->pFile = pFile;
pCursr->readPending = 0;
pCursr->needRewind = 1;
if( rc!=SQLITE_OK ){
sqliteBdb1CloseCursor(pCursr);
*ppCursr = 0;
}else{
*ppCursr = pCursr;
}
return rc;
}
/*
** Drop a table from the database. The file on the disk that corresponds
** to this table is deleted.
*/
static void sqliteBdb1DropTable(Dbbe *pBe, const char *zTable){
char *zFile; /* Name of the table file */
zFile = sqliteFileOfTable((Dbbex*)pBe, zTable);
unlink(zFile);
sqliteFree(zFile);
}
/*
** Close a cursor previously opened by sqliteBdb1OpenCursor().
**
** There can be multiple cursors pointing to the same open file.
** The underlying file is not closed until all cursors have been
** closed. This routine decrements the BeFile.nref field of the
** underlying file and closes the file when nref reaches 0.
*/
static void sqliteBdb1CloseCursor(DbbeCursor *pCursr){
BeFile *pFile;
Dbbex *pBe;
if( pCursr==0 ) return;
pFile = pCursr->pFile;
pBe = pCursr->pBe;
if( pFile->pCursor==pCursr ){
pFile->pCursor = 0;
}
pFile->nRef--;
if( pFile->dbf!=NULL ){
(*pFile->dbf->sync)(pFile->dbf, 0);
}
if( pFile->nRef<=0 ){
if( pFile->dbf!=NULL ){
(*pFile->dbf->close)(pFile->dbf);
}
if( pFile->pPrev ){
pFile->pPrev->pNext = pFile->pNext;
}else{
pBe->pOpen = pFile->pNext;
}
if( pFile->pNext ){
pFile->pNext->pPrev = pFile->pPrev;
}
if( pFile->delOnClose ){
unlink(pFile->zName);
}
sqliteFree(pFile->zName);
memset(pFile, 0, sizeof(*pFile));
sqliteFree(pFile);
}
if( pCursr->key.dptr ) free(pCursr->key.dptr); ######
if( pCursr->data.dptr ) free(pCursr->data.dptr); ######
memset(pCursr, 0, sizeof(*pCursr));
sqliteFree(pCursr);
}
/*
** Reorganize a table to reduce search times and disk usage.
*/
static int sqliteBdb1ReorganizeTable(Dbbe *pBe, const char *zTable){
/* No-op */
return SQLITE_OK;
}
/*
** Clear the given datum
*/
static void datumClear(datum *p){
if( p->dptr ) free(p->dptr); ########
p->data = 0;
p->size = 0;
}
/*
** Fetch a single record from an open cursor. Return 1 on success
** and 0 on failure.
*/
static int sqliteBdb1Fetch(DbbeCursor *pCursr, int nKey, char *pKey){
DBT key;
key.size = nKey;
key.data = pKey;
datumClear(&pCursr->key);
datumClear(&pCursr->data);
if( pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, key);
}
return pCursr->data.dptr!=0;
}
/*
** Return 1 if the given key is already in the table. Return 0
** if it is not.
*/
static int sqliteBdb1Test(DbbeCursor *pCursr, int nKey, char *pKey){
DBT key;
int result = 0;
key.dsize = nKey;
key.dptr = pKey;
if( pCursr->pFile && pCursr->pFile->dbf ){
result = gdbm_exists(pCursr->pFile->dbf, key);
}
return result;
}
/*
** Copy bytes from the current key or data into a buffer supplied by
** the calling function. Return the number of bytes copied.
*/
static
int sqliteBdb1CopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
int n;
if( offset>=pCursr->key.dsize ) return 0;
if( offset+size>pCursr->key.dsize ){
n = pCursr->key.dsize - offset;
}else{
n = size;
}
memcpy(zBuf, &pCursr->key.dptr[offset], n);
return n;
}
static
int sqliteBdb1CopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
int n;
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
pCursr->readPending = 0;
}
if( offset>=pCursr->data.dsize ) return 0;
if( offset+size>pCursr->data.dsize ){
n = pCursr->data.dsize - offset;
}else{
n = size;
}
memcpy(zBuf, &pCursr->data.dptr[offset], n);
return n;
}
/*
** Return a pointer to bytes from the key or data. The data returned
** is ephemeral.
*/
static char *sqliteBdb1ReadKey(DbbeCursor *pCursr, int offset){
if( offset<0 || offset>=pCursr->key.dsize ) return "";
return &pCursr->key.dptr[offset];
}
static char *sqliteBdb1ReadData(DbbeCursor *pCursr, int offset){
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
pCursr->readPending = 0;
}
if( offset<0 || offset>=pCursr->data.dsize ) return "";
return &pCursr->data.dptr[offset];
}
/*
** Return the total number of bytes in either data or key.
*/
static int sqliteBdb1KeyLength(DbbeCursor *pCursr){
return pCursr->key.dsize;
}
static int sqliteBdb1DataLength(DbbeCursor *pCursr){
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
pCursr->readPending = 0;
}
return pCursr->data.dsize;
}
/*
** Make is so that the next call to sqliteNextKey() finds the first
** key of the table.
*/
static int sqliteBdb1Rewind(DbbeCursor *pCursr){
pCursr->needRewind = 1;
return SQLITE_OK;
}
/*
** Read the next key from the table. Return 1 on success. Return
** 0 if there are no more keys.
*/
static int sqliteBdb1NextKey(DbbeCursor *pCursr){
DBT nextkey;
int rc;
if( pCursr==0 || pCursr->pFile==0 || pCursr->pFile->dbf==0 ){
pCursr->readPending = 0;
return 0;
}
if( pCursr->needRewind ){
nextkey = gdbm_firstkey(pCursr->pFile->dbf);
pCursr->needRewind = 0;
}else{
nextkey = gdbm_nextkey(pCursr->pFile->dbf, pCursr->key);
}
datumClear(&pCursr->key);
datumClear(&pCursr->data);
pCursr->key = nextkey;
if( pCursr->key.dptr ){
pCursr->readPending = 1;
rc = 1;
}else{
pCursr->needRewind = 1;
pCursr->readPending = 0;
rc = 0;
}
return rc;
}
/*
** Get a new integer key.
*/
static int sqliteBdb1New(DbbeCursor *pCursr){
int iKey;
DBT key;
int go = 1;
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return 1;
while( go ){
iKey = sqliteRandomInteger();
if( iKey==0 ) continue;
key.dptr = (char*)&iKey;
key.dsize = 4;
go = gdbm_exists(pCursr->pFile->dbf, key);
}
return iKey;
}
/*
** Write an entry into the table. Overwrite any prior entry with the
** same key.
*/
static int sqliteBdb1Put(
DbbeCursor *pCursr, /* Write to the database associated with this cursor */
int nKey, /* Number of bytes in the key */
char *pKey, /* The data for the key */
int nData, /* Number of bytes of data */
char *pData /* The data */
){
DBT data, key;
int rc;
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
data.dsize = nData;
data.dptr = pData;
key.dsize = nKey;
key.dptr = pKey;
rc = gdbm_store(pCursr->pFile->dbf, key, data, GDBM_REPLACE);
if( rc ) rc = SQLITE_ERROR;
datumClear(&pCursr->key);
datumClear(&pCursr->data);
return rc;
}
/*
** Remove an entry from a table, if the entry exists.
*/
static int sqliteBdb1Delete(DbbeCursor *pCursr, int nKey, char *pKey){
DBT key;
int rc;
datumClear(&pCursr->key);
datumClear(&pCursr->data);
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
key.dsize = nKey;
key.dptr = pKey;
rc = gdbm_delete(pCursr->pFile->dbf, key);
if( rc ) rc = SQLITE_ERROR;
return rc;
}
/*
** Open a temporary file. The file is located in the same directory
** as the rest of the database.
*/
static int sqliteBdb1OpenTempFile(Dbbe *pDbbe, FILE **ppFile){
Dbbex *pBe = (Dbbex*)pDbbe;
return sqliteDbbeOpenTempFile(pBe->zDir, pDbbe, ppFile);
}
/*
** This variable contains pointers to all of the access methods
** used to implement the GDBM backend.
*/
static struct DbbeMethods gdbmMethods = {
/* n Close */ sqliteBdb1Close,
/* OpenCursor */ sqliteBdb1OpenCursor,
/* DropTable */ sqliteBdb1DropTable,
/* ReorganizeTable */ sqliteBdb1ReorganizeTable,
/* CloseCursor */ sqliteBdb1CloseCursor,
/* Fetch */ sqliteBdb1Fetch,
/* Test */ sqliteBdb1Test,
/* CopyKey */ sqliteBdb1CopyKey,
/* CopyData */ sqliteBdb1CopyData,
/* ReadKey */ sqliteBdb1ReadKey,
/* ReadData */ sqliteBdb1ReadData,
/* KeyLength */ sqliteBdb1KeyLength,
/* DataLength */ sqliteBdb1DataLength,
/* NextKey */ sqliteBdb1NextKey,
/* Rewind */ sqliteBdb1Rewind,
/* New */ sqliteBdb1New,
/* Put */ sqliteBdb1Put,
/* Delete */ sqliteBdb1Delete,
/* OpenTempFile */ sqliteBdb1OpenTempFile,
/* CloseTempFile */ sqliteDbbeCloseTempFile
};
/*
** This routine opens a new database. For the GDBM driver
** implemented here, the database name is the name of the directory
** containing all the files of the database.
**
** If successful, a pointer to the Dbbe structure is returned.
** If there are errors, an appropriate error message is left
** in *pzErrMsg and NULL is returned.
*/
Dbbe *sqliteBdb1Open(
const char *zName, /* The name of the database */
int writeFlag, /* True if we will be writing to the database */
int createFlag, /* True to create database if it doesn't exist */
char **pzErrMsg /* Write error messages (if any) here */
){
Dbbex *pNew;
struct stat statbuf;
char *zMaster;
if( !writeFlag ) createFlag = 0;
if( stat(zName, &statbuf)!=0 ){
if( createFlag ) mkdir(zName, 0750);
if( stat(zName, &statbuf)!=0 ){
sqliteSetString(pzErrMsg, createFlag ?
"can't find or create directory \"" : "can't find directory \"",
zName, "\"", 0);
return 0;
}
}
if( !S_ISDIR(statbuf.st_mode) ){
sqliteSetString(pzErrMsg, "not a directory: \"", zName, "\"", 0);
return 0;
}
if( access(zName, writeFlag ? (X_OK|W_OK|R_OK) : (X_OK|R_OK)) ){
sqliteSetString(pzErrMsg, "access permission denied", 0);
return 0;
}
zMaster = 0;
sqliteSetString(&zMaster, zName, "/" MASTER_NAME ".tbl", 0);
if( stat(zMaster, &statbuf)==0
&& access(zMaster, writeFlag ? (W_OK|R_OK) : R_OK)!=0 ){
sqliteSetString(pzErrMsg, "access permission denied for ", zMaster, 0);
sqliteFree(zMaster);
return 0;
}
sqliteFree(zMaster);
pNew = sqliteMalloc(sizeof(Dbbex) + strlen(zName) + 1);
if( pNew==0 ){
sqliteSetString(pzErrMsg, "out of memory", 0);
return 0;
}
pNew->dbbe.x = &gdbmMethods;
pNew->zDir = (char*)&pNew[1];
strcpy(pNew->zDir, zName);
pNew->write = writeFlag;
pNew->pOpen = 0;
return &pNew->dbbe;
}
+776
View File
@@ -0,0 +1,776 @@
/*
** Copyright (c) 2000 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*************************************************************************
** This file contains code to implement the database backend (DBBE)
** for sqlite. The database backend is the interface between
** sqlite and the code that does the actually reading and writing
** of information to the disk.
**
** This file uses GDBM as the database backend. It should be
** relatively simple to convert to a different database such
** as NDBM, SDBM, or BerkeleyDB.
**
** $Id: dbbemird.c,v 1.1 2001/02/11 16:56:24 drh Exp $
*/
#include "sqliteInt.h"
#include <gdbm.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
/*
** Information about each open disk file is an instance of this
** structure. There will only be one such structure for each
** disk file. If the VDBE opens the same file twice (as will happen
** for a self-join, for example) then two DbbeCursor structures are
** created but there is only a single BeFile structure with an
** nRef of 2.
*/
typedef struct BeFile BeFile;
struct BeFile {
char *zName; /* Name of the file */
GDBM_FILE dbf; /* The file itself */
int nRef; /* Number of references */
int delOnClose; /* Delete when closing */
int writeable; /* Opened for writing */
BeFile *pNext, *pPrev; /* Next and previous on list of open files */
};
/*
** The following structure holds the current state of the RC4 algorithm.
** We use RC4 as a random number generator. Each call to RC4 gives
** a random 8-bit number.
**
** Nothing in this file or anywhere else in SQLite does any kind of
** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random
** number generator) not as an encryption device.
*/
struct rc4 {
int i, j;
int s[256];
};
/*
** The following structure contains all information used by GDBM
** database driver. This is a subclass of the Dbbe structure.
*/
typedef struct Dbbex Dbbex;
struct Dbbex {
Dbbe dbbe; /* The base class */
char *zDir; /* The directory containing the database */
int write; /* True for write permission */
BeFile *pOpen; /* List of open files */
int nTemp; /* Number of temporary files created */
FILE **apTemp; /* Space to hold temporary file pointers */
char **azTemp; /* Names of the temporary files */
struct rc4 rc4; /* The random number generator */
};
/*
** An cursor into a database file is an instance of the following structure.
** There can only be a single BeFile structure for each disk file, but
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
** a cursor pointing to a particular part of the open BeFile. The
** BeFile.nRef field hold a count of the number of DbbeCursor structures
** associated with the same disk file.
*/
struct DbbeCursor {
Dbbex *pBe; /* The database of which this record is a part */
BeFile *pFile; /* The database file for this table */
datum key; /* Most recently used key */
datum data; /* Most recent data */
int needRewind; /* Next key should be the first */
int readPending; /* The fetch hasn't actually been done yet */
};
/*
** Initialize the RC4 PRNG. "seed" is a pointer to some random
** data used to initialize the PRNG.
*/
static void rc4init(struct rc4 *p, char *seed, int seedlen){
int i;
char k[256];
p->j = 0;
p->i = 0;
for(i=0; i<256; i++){
p->s[i] = i;
k[i] = seed[i%seedlen];
}
for(i=0; i<256; i++){
int t;
p->j = (p->j + p->s[i] + k[i]) & 0xff;
t = p->s[p->j];
p->s[p->j] = p->s[i];
p->s[i] = t;
}
}
/*
** Get a single 8-bit random value from the RC4 PRNG.
*/
static int rc4byte(struct rc4 *p){
int t;
p->i = (p->i + 1) & 0xff;
p->j = (p->j + p->s[p->i]) & 0xff;
t = p->s[p->i];
p->s[p->i] = p->s[p->j];
p->s[p->j] = t;
t = p->s[p->i] + p->s[p->j];
return t & 0xff;
}
/*
** The "mkdir()" function only takes one argument under Windows.
*/
#if OS_WIN
# define mkdir(A,B) mkdir(A)
#endif
/*
** Forward declaration
*/
static void sqliteGdbmCloseCursor(DbbeCursor *pCursr);
/*
** Completely shutdown the given database. Close all files. Free all memory.
*/
static void sqliteGdbmClose(Dbbe *pDbbe){
Dbbex *pBe = (Dbbex*)pDbbe;
BeFile *pFile, *pNext;
int i;
for(pFile=pBe->pOpen; pFile; pFile=pNext){
pNext = pFile->pNext;
gdbm_close(pFile->dbf);
memset(pFile, 0, sizeof(*pFile));
sqliteFree(pFile);
}
for(i=0; i<pBe->nTemp; i++){
if( pBe->apTemp[i]!=0 ){
unlink(pBe->azTemp[i]);
fclose(pBe->apTemp[i]);
sqliteFree(pBe->azTemp[i]);
pBe->apTemp[i] = 0;
pBe->azTemp[i] = 0;
break;
}
}
sqliteFree(pBe->azTemp);
sqliteFree(pBe->apTemp);
memset(pBe, 0, sizeof(*pBe));
sqliteFree(pBe);
}
/*
** Translate the name of an SQL table (or index) into the name
** of a file that holds the key/data pairs for that table or
** index. Space to hold the filename is obtained from
** sqliteMalloc() and must be freed by the calling function.
*/
static char *sqliteFileOfTable(Dbbex *pBe, const char *zTable){
char *zFile = 0;
int i;
sqliteSetString(&zFile, pBe->zDir, "/", zTable, ".tbl", 0);
if( zFile==0 ) return 0;
for(i=strlen(pBe->zDir)+1; zFile[i]; i++){
int c = zFile[i];
if( isupper(c) ){
zFile[i] = tolower(c);
}else if( !isalnum(c) && c!='-' && c!='_' && c!='.' ){
zFile[i] = '+';
}
}
return zFile;
}
/*
** Generate a random filename with the given prefix. The new filename
** is written into zBuf[]. The calling function must insure that
** zBuf[] is big enough to hold the prefix plus 20 or so extra
** characters.
**
** Very random names are chosen so that the chance of a
** collision with an existing filename is very very small.
*/
static void randomName(struct rc4 *pRc4, char *zBuf, char *zPrefix){
int i, j;
static const char zRandomChars[] = "abcdefghijklmnopqrstuvwxyz0123456789";
strcpy(zBuf, zPrefix);
j = strlen(zBuf);
for(i=0; i<15; i++){
int c = rc4byte(pRc4) % (sizeof(zRandomChars) - 1);
zBuf[j++] = zRandomChars[c];
}
zBuf[j] = 0;
}
/*
** Open a new table cursor. Write a pointer to the corresponding
** DbbeCursor structure into *ppCursr. Return an integer success
** code:
**
** SQLITE_OK It worked!
**
** SQLITE_NOMEM sqliteMalloc() failed
**
** SQLITE_PERM Attempt to access a file for which file
** access permission is denied
**
** SQLITE_BUSY Another thread or process is already using
** the corresponding file and has that file locked.
**
** SQLITE_READONLY The current thread already has this file open
** readonly but you are trying to open for writing.
** (This can happen if a SELECT callback tries to
** do an UPDATE or DELETE.)
**
** If zTable is 0 or "", then a temporary database file is created and
** a cursor to that temporary file is opened. The temporary file
** will be deleted from the disk when it is closed.
*/
static int sqliteGdbmOpenCursor(
Dbbe *pDbbe, /* The database the table belongs to */
const char *zTable, /* The SQL name of the file to be opened */
int writeable, /* True to open for writing */
DbbeCursor **ppCursr /* Write the resulting table pointer here */
){
char *zFile; /* Name of the table file */
DbbeCursor *pCursr; /* The new table cursor */
BeFile *pFile; /* The underlying data file for this table */
int rc = SQLITE_OK; /* Return value */
int rw_mask; /* Permissions mask for opening a table */
int mode; /* Mode for opening a table */
Dbbex *pBe = (Dbbex*)pDbbe;
*ppCursr = 0;
pCursr = sqliteMalloc( sizeof(*pCursr) );
if( pCursr==0 ) return SQLITE_NOMEM;
if( zTable ){
zFile = sqliteFileOfTable(pBe, zTable);
for(pFile=pBe->pOpen; pFile; pFile=pFile->pNext){
if( strcmp(pFile->zName,zFile)==0 ) break;
}
}else{
pFile = 0;
zFile = 0;
}
if( pFile==0 ){
if( writeable ){
rw_mask = GDBM_WRCREAT | GDBM_FAST;
mode = 0640;
}else{
rw_mask = GDBM_READER;
mode = 0640;
}
pFile = sqliteMalloc( sizeof(*pFile) );
if( pFile==0 ){
sqliteFree(zFile);
return SQLITE_NOMEM;
}
if( zFile ){
if( !writeable || pBe->write ){
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
}else{
pFile->dbf = 0;
}
}else{
int limit;
struct rc4 *pRc4;
char zRandom[50];
pRc4 = &pBe->rc4;
zFile = 0;
limit = 5;
do {
randomName(&pBe->rc4, zRandom, "_temp_table_");
sqliteFree(zFile);
zFile = sqliteFileOfTable(pBe, zRandom);
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
}while( pFile->dbf==0 && limit-- >= 0);
pFile->delOnClose = 1;
}
pFile->writeable = writeable;
pFile->zName = zFile;
pFile->nRef = 1;
pFile->pPrev = 0;
if( pBe->pOpen ){
pBe->pOpen->pPrev = pFile;
}
pFile->pNext = pBe->pOpen;
pBe->pOpen = pFile;
if( pFile->dbf==0 ){
if( !writeable && access(zFile,0) ){
/* Trying to read a non-existant file. This is OK. All the
** reads will return empty, which is what we want. */
rc = SQLITE_OK;
}else if( pBe->write==0 ){
rc = SQLITE_READONLY;
}else if( access(zFile,W_OK|R_OK) ){
rc = SQLITE_PERM;
}else{
rc = SQLITE_BUSY;
}
}
}else{
sqliteFree(zFile);
pFile->nRef++;
if( writeable && !pFile->writeable ){
rc = SQLITE_READONLY;
}
}
pCursr->pBe = pBe;
pCursr->pFile = pFile;
pCursr->readPending = 0;
pCursr->needRewind = 1;
if( rc!=SQLITE_OK ){
sqliteGdbmCloseCursor(pCursr);
*ppCursr = 0;
}else{
*ppCursr = pCursr;
}
return rc;
}
/*
** Drop a table from the database. The file on the disk that corresponds
** to this table is deleted.
*/
static void sqliteGdbmDropTable(Dbbe *pBe, const char *zTable){
char *zFile; /* Name of the table file */
zFile = sqliteFileOfTable((Dbbex*)pBe, zTable);
unlink(zFile);
sqliteFree(zFile);
}
/*
** Close a cursor previously opened by sqliteGdbmOpenCursor().
**
** There can be multiple cursors pointing to the same open file.
** The underlying file is not closed until all cursors have been
** closed. This routine decrements the BeFile.nref field of the
** underlying file and closes the file when nref reaches 0.
*/
static void sqliteGdbmCloseCursor(DbbeCursor *pCursr){
BeFile *pFile;
Dbbex *pBe;
if( pCursr==0 ) return;
pFile = pCursr->pFile;
pBe = pCursr->pBe;
pFile->nRef--;
if( pFile->dbf!=NULL ){
gdbm_sync(pFile->dbf);
}
if( pFile->nRef<=0 ){
if( pFile->dbf!=NULL ){
gdbm_close(pFile->dbf);
}
if( pFile->pPrev ){
pFile->pPrev->pNext = pFile->pNext;
}else{
pBe->pOpen = pFile->pNext;
}
if( pFile->pNext ){
pFile->pNext->pPrev = pFile->pPrev;
}
if( pFile->delOnClose ){
unlink(pFile->zName);
}
sqliteFree(pFile->zName);
memset(pFile, 0, sizeof(*pFile));
sqliteFree(pFile);
}
if( pCursr->key.dptr ) free(pCursr->key.dptr);
if( pCursr->data.dptr ) free(pCursr->data.dptr);
memset(pCursr, 0, sizeof(*pCursr));
sqliteFree(pCursr);
}
/*
** Reorganize a table to reduce search times and disk usage.
*/
static int sqliteGdbmReorganizeTable(Dbbe *pBe, const char *zTable){
DbbeCursor *pCrsr;
int rc;
rc = sqliteGdbmOpenCursor(pBe, zTable, 1, &pCrsr);
if( rc!=SQLITE_OK ){
return rc;
}
if( pCrsr && pCrsr->pFile && pCrsr->pFile->dbf ){
gdbm_reorganize(pCrsr->pFile->dbf);
}
if( pCrsr ){
sqliteGdbmCloseCursor(pCrsr);
}
return SQLITE_OK;
}
/*
** Clear the given datum
*/
static void datumClear(datum *p){
if( p->dptr ) free(p->dptr);
p->dptr = 0;
p->dsize = 0;
}
/*
** Fetch a single record from an open cursor. Return 1 on success
** and 0 on failure.
*/
static int sqliteGdbmFetch(DbbeCursor *pCursr, int nKey, char *pKey){
datum key;
key.dsize = nKey;
key.dptr = pKey;
datumClear(&pCursr->key);
datumClear(&pCursr->data);
if( pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, key);
}
return pCursr->data.dptr!=0;
}
/*
** Return 1 if the given key is already in the table. Return 0
** if it is not.
*/
static int sqliteGdbmTest(DbbeCursor *pCursr, int nKey, char *pKey){
datum key;
int result = 0;
key.dsize = nKey;
key.dptr = pKey;
if( pCursr->pFile && pCursr->pFile->dbf ){
result = gdbm_exists(pCursr->pFile->dbf, key);
}
return result;
}
/*
** Copy bytes from the current key or data into a buffer supplied by
** the calling function. Return the number of bytes copied.
*/
static
int sqliteGdbmCopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
int n;
if( offset>=pCursr->key.dsize ) return 0;
if( offset+size>pCursr->key.dsize ){
n = pCursr->key.dsize - offset;
}else{
n = size;
}
memcpy(zBuf, &pCursr->key.dptr[offset], n);
return n;
}
static
int sqliteGdbmCopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
int n;
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
pCursr->readPending = 0;
}
if( offset>=pCursr->data.dsize ) return 0;
if( offset+size>pCursr->data.dsize ){
n = pCursr->data.dsize - offset;
}else{
n = size;
}
memcpy(zBuf, &pCursr->data.dptr[offset], n);
return n;
}
/*
** Return a pointer to bytes from the key or data. The data returned
** is ephemeral.
*/
static char *sqliteGdbmReadKey(DbbeCursor *pCursr, int offset){
if( offset<0 || offset>=pCursr->key.dsize ) return "";
return &pCursr->key.dptr[offset];
}
static char *sqliteGdbmReadData(DbbeCursor *pCursr, int offset){
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
pCursr->readPending = 0;
}
if( offset<0 || offset>=pCursr->data.dsize ) return "";
return &pCursr->data.dptr[offset];
}
/*
** Return the total number of bytes in either data or key.
*/
static int sqliteGdbmKeyLength(DbbeCursor *pCursr){
return pCursr->key.dsize;
}
static int sqliteGdbmDataLength(DbbeCursor *pCursr){
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
pCursr->readPending = 0;
}
return pCursr->data.dsize;
}
/*
** Make is so that the next call to sqliteNextKey() finds the first
** key of the table.
*/
static int sqliteGdbmRewind(DbbeCursor *pCursr){
pCursr->needRewind = 1;
return SQLITE_OK;
}
/*
** Read the next key from the table. Return 1 on success. Return
** 0 if there are no more keys.
*/
static int sqliteGdbmNextKey(DbbeCursor *pCursr){
datum nextkey;
int rc;
if( pCursr==0 || pCursr->pFile==0 || pCursr->pFile->dbf==0 ){
pCursr->readPending = 0;
return 0;
}
if( pCursr->needRewind ){
nextkey = gdbm_firstkey(pCursr->pFile->dbf);
pCursr->needRewind = 0;
}else{
nextkey = gdbm_nextkey(pCursr->pFile->dbf, pCursr->key);
}
datumClear(&pCursr->key);
datumClear(&pCursr->data);
pCursr->key = nextkey;
if( pCursr->key.dptr ){
pCursr->readPending = 1;
rc = 1;
}else{
pCursr->needRewind = 1;
pCursr->readPending = 0;
rc = 0;
}
return rc;
}
/*
** Get a new integer key.
*/
static int sqliteGdbmNew(DbbeCursor *pCursr){
int iKey;
datum key;
int go = 1;
int i;
struct rc4 *pRc4;
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return 1;
pRc4 = &pCursr->pBe->rc4;
while( go ){
iKey = 0;
for(i=0; i<4; i++){
iKey = (iKey<<8) + rc4byte(pRc4);
}
if( iKey==0 ) continue;
key.dptr = (char*)&iKey;
key.dsize = 4;
go = gdbm_exists(pCursr->pFile->dbf, key);
}
return iKey;
}
/*
** Write an entry into the table. Overwrite any prior entry with the
** same key.
*/
static int
sqliteGdbmPut(DbbeCursor *pCursr, int nKey,char *pKey,int nData,char *pData){
datum data, key;
int rc;
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
data.dsize = nData;
data.dptr = pData;
key.dsize = nKey;
key.dptr = pKey;
rc = gdbm_store(pCursr->pFile->dbf, key, data, GDBM_REPLACE);
if( rc ) rc = SQLITE_ERROR;
datumClear(&pCursr->key);
datumClear(&pCursr->data);
return rc;
}
/*
** Remove an entry from a table, if the entry exists.
*/
static int sqliteGdbmDelete(DbbeCursor *pCursr, int nKey, char *pKey){
datum key;
int rc;
datumClear(&pCursr->key);
datumClear(&pCursr->data);
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
key.dsize = nKey;
key.dptr = pKey;
rc = gdbm_delete(pCursr->pFile->dbf, key);
if( rc ) rc = SQLITE_ERROR;
return rc;
}
/*
** Open a temporary file. The file should be deleted when closed.
**
** Note that we can't use the old Unix trick of opening the file
** and then immediately unlinking the file. That works great
** under Unix, but fails when we try to port to Windows.
*/
static int sqliteGdbmOpenTempFile(Dbbe *pDbbe, FILE **ppFile){
char *zFile; /* Full name of the temporary file */
char zBuf[50]; /* Base name of the temporary file */
int i; /* Loop counter */
int limit; /* Prevent an infinite loop */
int rc = SQLITE_OK; /* Value returned by this function */
Dbbex *pBe = (Dbbex*)pDbbe;
for(i=0; i<pBe->nTemp; i++){
if( pBe->apTemp[i]==0 ) break;
}
if( i>=pBe->nTemp ){
pBe->nTemp++;
pBe->apTemp = sqliteRealloc(pBe->apTemp, pBe->nTemp*sizeof(FILE*) );
pBe->azTemp = sqliteRealloc(pBe->azTemp, pBe->nTemp*sizeof(char*) );
}
if( pBe->apTemp==0 ){
*ppFile = 0;
return SQLITE_NOMEM;
}
limit = 4;
zFile = 0;
do{
randomName(&pBe->rc4, zBuf, "/_temp_file_");
sqliteFree(zFile);
zFile = 0;
sqliteSetString(&zFile, pBe->zDir, zBuf, 0);
}while( access(zFile,0)==0 && limit-- >= 0 );
*ppFile = pBe->apTemp[i] = fopen(zFile, "w+");
if( pBe->apTemp[i]==0 ){
rc = SQLITE_ERROR;
sqliteFree(zFile);
pBe->azTemp[i] = 0;
}else{
pBe->azTemp[i] = zFile;
}
return rc;
}
/*
** Close a temporary file opened using sqliteGdbmOpenTempFile()
*/
static void sqliteGdbmCloseTempFile(Dbbe *pDbbe, FILE *f){
int i;
Dbbex *pBe = (Dbbex*)pDbbe;
for(i=0; i<pBe->nTemp; i++){
if( pBe->apTemp[i]==f ){
unlink(pBe->azTemp[i]);
sqliteFree(pBe->azTemp[i]);
pBe->apTemp[i] = 0;
pBe->azTemp[i] = 0;
break;
}
}
fclose(f);
}
/*
** This routine opens a new database. For the GDBM driver
** implemented here, the database name is the name of the directory
** containing all the files of the database.
**
** If successful, a pointer to the Dbbe structure is returned.
** If there are errors, an appropriate error message is left
** in *pzErrMsg and NULL is returned.
*/
Dbbe *sqliteGdbmOpen(
const char *zName, /* The name of the database */
int writeFlag, /* True if we will be writing to the database */
int createFlag, /* True to create database if it doesn't exist */
char **pzErrMsg /* Write error messages (if any) here */
){
Dbbex *pNew;
struct stat statbuf;
char *zMaster;
if( !writeFlag ) createFlag = 0;
if( stat(zName, &statbuf)!=0 ){
if( createFlag ) mkdir(zName, 0750);
if( stat(zName, &statbuf)!=0 ){
sqliteSetString(pzErrMsg, createFlag ?
"can't find or create directory \"" : "can't find directory \"",
zName, "\"", 0);
return 0;
}
}
if( !S_ISDIR(statbuf.st_mode) ){
sqliteSetString(pzErrMsg, "not a directory: \"", zName, "\"", 0);
return 0;
}
if( access(zName, writeFlag ? (X_OK|W_OK|R_OK) : (X_OK|R_OK)) ){
sqliteSetString(pzErrMsg, "access permission denied", 0);
return 0;
}
zMaster = 0;
sqliteSetString(&zMaster, zName, "/" MASTER_NAME ".tbl", 0);
if( stat(zMaster, &statbuf)==0
&& access(zMaster, writeFlag ? (W_OK|R_OK) : R_OK)!=0 ){
sqliteSetString(pzErrMsg, "access permission denied for ", zMaster, 0);
sqliteFree(zMaster);
return 0;
}
sqliteFree(zMaster);
pNew = sqliteMalloc(sizeof(Dbbex) + strlen(zName) + 1);
if( pNew==0 ){
sqliteSetString(pzErrMsg, "out of memory", 0);
return 0;
}
pNew->dbbe.Close = sqliteGdbmClose;
pNew->dbbe.OpenCursor = sqliteGdbmOpenCursor;
pNew->dbbe.DropTable = sqliteGdbmDropTable;
pNew->dbbe.ReorganizeTable = sqliteGdbmReorganizeTable;
pNew->dbbe.CloseCursor = sqliteGdbmCloseCursor;
pNew->dbbe.Fetch = sqliteGdbmFetch;
pNew->dbbe.Test = sqliteGdbmTest;
pNew->dbbe.CopyKey = sqliteGdbmCopyKey;
pNew->dbbe.CopyData = sqliteGdbmCopyData;
pNew->dbbe.ReadKey = sqliteGdbmReadKey;
pNew->dbbe.ReadData = sqliteGdbmReadData;
pNew->dbbe.KeyLength = sqliteGdbmKeyLength;
pNew->dbbe.DataLength = sqliteGdbmDataLength;
pNew->dbbe.NextKey = sqliteGdbmNextKey;
pNew->dbbe.Rewind = sqliteGdbmRewind;
pNew->dbbe.New = sqliteGdbmNew;
pNew->dbbe.Put = sqliteGdbmPut;
pNew->dbbe.Delete = sqliteGdbmDelete;
pNew->dbbe.OpenTempFile = sqliteGdbmOpenTempFile;
pNew->dbbe.CloseTempFile = sqliteGdbmCloseTempFile;
pNew->zDir = (char*)&pNew[1];
strcpy(pNew->zDir, zName);
pNew->write = writeFlag;
pNew->pOpen = 0;
time(&statbuf.st_ctime);
rc4init(&pNew->rc4, (char*)&statbuf, sizeof(statbuf));
return &pNew->dbbe;
}
+708
View File
@@ -0,0 +1,708 @@
/*
** Copyright (c) 2001 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*************************************************************************
** $Id: pg.c,v 1.1 2001/02/11 16:56:24 drh Exp $
*/
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "sqliteInt.h"
#include "pg.h"
/*
** Uncomment the following for a debug trace
*/
#if 1
# define TRACE(X) printf X; fflush(stdout);
#endif
/*
** Hash table sizes
*/
#define J_HASH_SIZE 127 /* Size of the journal page hash table */
#define PG_HASH_SIZE 349 /* Size of the database page hash table */
/*
** Forward declaration of structure
*/
typedef struct Pghdr Pghdr;
/*
** All information about a single paging file is contained in an
** instance of the following structure.
*/
struct Pgr {
int fdMain; /* The main database file */
char *zMain; /* Name of the database file */
int fdJournal; /* The journal file */
char *zJournal; /* Name of the journal file */
int nMemPg; /* Number of memory-resident pages */
int nJPg; /* Number of pages in the journal */
int nDbPg; /* Number of pages in the database */
int nRefPg; /* Number of pages currently in use */
Pghdr *pLru, *pMru; /* Least and most recently used mem-page */
Pghdr *pJidx; /* List of journal index pages */
Pghdr *pAll; /* All pages, except journal index pages */
u32 aJHash[J_HASH_SIZE]; /* Journal page hash table */
Pghdr *aPgHash[PG_HASH_SIZE]; /* Mem-page hash table */
};
/*
** Each memory-resident page of the paging file has a header which
** is an instance of the following structure.
*/
struct Pghdr {
Pgr *p; /* Pointer back to the Pgr structure */
int nRef; /* Number of references to this page */
int isDirty; /* TRUE if needs to be written to disk */
u32 dbpgno; /* Page number in the database file */
u32 jpgno; /* Page number in the journal file */
Pghdr *pNx; /* Next page on a list of them all */
Pghdr *pLru; /* Less recently used pages */
Pghdr *pMru; /* More recently used pages */
Pghdr *pNxHash; /* Next with same dbpgno hash */
Pghdr *pPvHash; /* Previous with the same dbpgno hash */
};
/*
** For a memory-resident page, the page data comes immediately after
** the page header. The following macros can be used to change a
** pointer to a page header into a pointer to the data, or vice
** versa.
*/
#define PG_TO_DATA(X) ((void*)&(X)[1])
#define DATA_TO_PG(X) (&((Pghdr*)(X))[-1])
/*
** The number of in-memory pages that we accumulate before trying
** to reuse older pages when new ones are requested.
*/
#define MX_MEM_PAGE 100
/*
** The number of journal data pages that come between consecutive
** journal index pages.
*/
#define N_J_DATAPAGE (SQLITE_PAGE_SIZE/(2*sizeof(u32)))
/*
** An index page in the journal consists of an array of N_J_DATAPAGE
** of the following structures. There is one instance of the following
** structure for each of the N_J_DATAPAGE data pages that follow the
** index.
**
** Let the journal page number that a JidxEntry describes be J. Then
** the JidxEntry.dbpgno field is the page of the database file that
** corresponds to the J page in the journal. The JidxEntry.next_jpgno
** field hold the number of another journal page that contains
** a database file page with the same hash as JidxEntry.dbpgno.
**
** All information is written to the journal index in big-endian
** notation.
*/
typedef struct JidxEntry JidxEntry;
struct JidxEntry {
char dbpgno[sizeof(u32)]; /* Database page number for this entry */
char next_jpgno[sizeof(u32)]; /* Next entry with same hash on dbpgno */
};
/*
** Read a page from a file into memory. Return SQLITE_OK if successful.
** The "pgno" parameter tells where in the file to read the page.
** The first page is 1. Files do not contain a page 0 since a page
** number of 0 is used to indicate "no such page".
*/
static int sqlitePgRead(int fd, char *zBuf, u32 pgno){
int got = 0;
int amt;
assert( pgno>0 );
assert( fd>=0 );
lseek(fd, SEEK_SET, (pgno-1)*SQLITE_PAGE_SIZE);
while( got<SQLITE_PAGE_SIZE ){
amt = read(fd, &zBuf[got], SQLITE_PAGE_SIZE - got);
if( amt<=0 ){
memset(&zBuf[got], 0, SQLITE_PAGE_SIZE - got);
return amt==0 ? SQLITE_OK : SQLITE_IOERR;
}
got += amt;
}
return SQLITE_OK;
}
/*
** Read a page from a file into memory. Return SQLITE_OK if successful.
** The "pgno" parameter tells where in the file to write the page.
** The first page is 1. Files do not contain a page 0 since a page
** number of 0 is used to indicate "no such page".
*/
static int sqlitePgWrite(int fd, char *zBuf, u32 pgno){
int done = 0;
int amt;
assert( pgno>0 );
assert( fd>=0 );
lseek(fd, SEEK_SET, (pgno-1)*SQLITE_PAGE_SIZE);
while( done<SQLITE_PAGE_SIZE ){
amt = write(fd, &zBuf[done], SQLITE_PAGE_SIZE - done);
if( amt<=0 ) return SQLITE_IOERR;
done += amt;
}
return SQLITE_OK;
}
/*
** Turn four bytes into an integer. The first byte is always the
** most significant 8 bits.
*/
static u32 sqlitePgGetInt(const char *p){
return ((p[0]&0xff)<<24) | ((p[1]&0xff)<<16) | ((p[2]&0xff)<<8) | (p[3]&0xff);
}
/*
** Turn an integer into 4 bytes. The first byte is always the
** most significant 8 bits.
*/
static void sqlitePgPutInt(u32 v, char *p){
p[3] = v & 0xff;
v >>= 8;
p[2] = v & 0xff;
v >>= 8;
p[1] = v & 0xff;
v >>= 8;
p[0] = v & 0xff;
}
/*
** Check the hash table for an in-memory page. Return a pointer to
** the page header if found. Return NULL if the page is not in memory.
*/
static Pghdr *sqlitePgFind(Pgr *p, u32 pgno){
int h;
Pghdr *pPg;
if( pgno==0 ) return 0;
h = pgno % PG_HASH_SIZE;
for(pPg = p->aPgHash[h]; pPg; pPg=pPg->pNxHash){
if( pPg->dbpgno==pgno ) return pPg;
}
TRACE(("PG: data page %u is %#x\n", pgno, (u32)pPg));
return 0;
}
/*
** Locate and return an index page from the journal.
**
** The first page of a journal is the primary index. Additional
** index pages are called secondary indices. Index pages appear
** in the journal as often as needed. (If SQLITE_PAGE_SIZE==1024,
** then there are 1024/sizeof(int)*2 = 128 database between each
** pair of index pages.) Journal index pages are not hashed and
** do no appear on the Pgr.pAll list. Index pages are on the
** Pgr.pJidx list only. Index pages have Pghdr.dbpgno==0.
**
** If the requested index page is not already in memory, then a
** new memory page is created to hold the index.
**
** This routine will return a NULL pointer if we run out of memory.
*/
static Pghdr *sqlitePgFindJidx(Pgr *p, u32 pgno){
Pghdr *pPg;
assert( pgno % (N_J_DATAPAGE+1) == 1 );
for(pPg=p->pJidx; pPg; pPg=pPg->pNx){
if( pPg->jpgno==pgno ){
TRACE(("PG: found j-index %u at %#x\n", pgno, (u32)pPg));
return pPg;
}
}
pPg = sqliteMalloc( sizeof(Pghdr)+SQLITE_PAGE_SIZE );
if( pPg==0 ) return 0;
pPg->jpgno = pgno;
pPg->pNx = p->pJidx;
p->pJidx = pPg;
sqlitePgRead(p->fdJournal, PG_TO_DATA(pPg), pgno);
TRACE(("PG: create j-index %u at %#x\n", pgno, (u32)pPg));
return pPg;
}
/*
** Look in the journal to see if the given database page is stored
** in the journal. If it is, return its journal page number. If
** not, return 0.
*/
static u32 sqlitePgJournalPageNumber(Pgr *p, u32 dbpgno){
u32 jpgno;
if( dbpgno==0 ) return 0;
jpgno = p->aJHash[dbpgno % J_HASH_SIZE];
while( jpgno!=0 ){
int idx_num; /* Which journal index describes page jpgno */
int ipgno; /* Page number for the journal index */
int idx_slot; /* Which entry in index idx_num describes jpgno */
Pghdr *pIdxPg; /* The index page for jpgno */
JidxEntry *aIdx; /* The data for the index page */
idx_num = (jpgno - 1)/(N_J_DATAPAGE + 1);
idx_slot = (jpgno - 1) % (N_J_DATAPAGE + 1) - 2;
ipgno = idx_num * (N_J_DATAPAGE + 1) + 1;
if( ipgno>p->nJPg ){
jpgno = 0;
break;
}
pIdxPg = sqlitePgFindJidx(p, ipgno);
assert( pIdxPg!=0 );
aIdx = PG_TO_DATA(pIdxPg);
if( dbpgno==sqlitePgGetInt(aIdx[idx_slot].dbpgno) ){
break;
}
jpgno = sqlitePgGetInt(aIdx[idx_slot].next_jpgno);
}
return jpgno;
}
/*
** Make a page not dirty by writing it to the journal.
*/
static int sqlitePgMakeClean(Pghdr *pPg){
Pgr *p = pPg->p;
int rc;
assert( pPg->isDirty );
assert( p->fdJournal>=0 );
if( pPg->jpgno==0 ){
int jpgno; /* A newly allocate page in the journal */
int idx_num; /* Which journal index describes page jpgno */
int idx_slot; /* Which entry in index idx_num describes jpgno */
Pghdr *pIdxPg; /* The index page for jpgno */
JidxEntry *aIdx; /* The data for the index page */
int h; /* The hash value for pPg->dbpgno */
jpgno = p->nJPg + 1;
if( jpgno % (N_J_DATAPAGE + 1) == 1 ){
jpgno++;
}
idx_num = (jpgno - 1)/(N_J_DATAPAGE + 1);
idx_slot = (jpgno - 1) % (N_J_DATAPAGE + 1) - 2;
pIdxPg = sqlitePgFindJidx(p, idx_num * (N_J_DATAPAGE + 1) + 1);
assert( pIdxPg!=0 );
aIdx = PG_TO_DATA(pIdxPg);
sqlitePgPutInt(pPg->dbpgno, aIdx[idx_slot].dbpgno);
h = pPg->dbpgno % J_HASH_SIZE;
sqlitePgPutInt(p->aJHash[h], aIdx[idx_slot].next_jpgno);
p->aJHash[h] = jpgno;
p->nJPg = jpgno;
pPg->jpgno = jpgno;
TRACE(("PG: assign d-page %u to j-page %u\n", jpgno, pPg->dbpgno));
}
rc = sqlitePgWrite(p->fdJournal, PG_TO_DATA(pPg), pPg->jpgno);
if( rc==SQLITE_OK ){
pPg->isDirty = 0;
}
return rc;
}
/*
** Find the number of pages in the given file by measuring the size
** of the file. Return 0 if there is any problem.
*/
static int sqlitePgPageCount(int fd){
struct stat statbuf;
if( fstat(fd, &statbuf)!=0 ) return 0;
return statbuf.st_size/SQLITE_PAGE_SIZE;
}
/*
** This routine reads the journal and transfers pages from the
** journal to the database.
*/
static int sqlitePgJournalPlayback(Pgr *p){
Pghdr *pPg;
JidxEntry *aIdx;
int nJpg;
int jpgno = 1;
int i;
int dbpgno;
int rc;
char idx[SQLITE_PAGE_SIZE];
char pgbuf[SQLITE_PAGE_SIZE];
assert( p->fdJournal>=0 );
nJpg = sqlitePgPageCount(p->fdJournal);
while( jpgno<=nJpg ){
if( !sqlitePgRead(p->fdJournal, idx, jpgno++) ) break;
aIdx = (JidxEntry*)idx;
for(i=0; i<N_J_DATAPAGE; i++){
dbpgno = sqlitePgGetInt(&idx[i]);
if( dbpgno==0 ){
jpgno = nJpg+1;
break;
}
pPg = sqlitePgFind(p, dbpgno);
if( pPg ){
rc = sqlitePgWrite(p->fdMain, PG_TO_DATA(pPg), dbpgno);
TRACE(("PG: commit j-page %u to d-page %u from memory\n",jpgno,dbpgno));
}else{
rc = sqlitePgRead(p->fdJournal, pgbuf, jpgno);
if( rc!=SQLITE_OK ){
return rc;
}
rc = sqlitePgWrite(p->fdMain, pgbuf, dbpgno);
TRACE(("PG: commit j-page %u to d-page %u from disk\n",jpgno,dbpgno));
}
jpgno++;
if( rc!=SQLITE_OK ){
return rc;
}
}
}
TRACE(("PG: commit complete. deleting the journal.\n"));
fsync(p->fdMain);
close(p->fdJournal);
p->fdJournal = -1;
unlink(p->zJournal);
for(pPg=p->pAll; pPg; pPg=pPg->pNx){
pPg->isDirty = 0;
pPg->jpgno = 0;
}
while( (pPg = p->pJidx)!=0 ){
p->pAll = pPg->pNx;
sqliteFree(pPg);
}
return SQLITE_OK;
}
/*
** Remove the given page from the LRU list.
*/
static void sqlitePgUnlinkLru(Pghdr *pPg){
Pgr *p = pPg->p;
if( pPg->pLru ){
pPg->pLru->pMru = pPg->pLru;
}
if( pPg->pMru ){
pPg->pMru->pLru = pPg->pMru;
}
if( p->pLru==pPg ){
p->pLru = pPg->pLru;
}
if( p->pMru==pPg ){
p->pMru = pPg->pMru;
}
pPg->pLru = pPg->pMru = 0;
}
/*
** Open the database file and make *ppPgr pointer to a structure describing it.
** Return SQLITE_OK on success or an error code if there is a failure.
**
** If there was an unfinished commit, complete it before returnning.
*/
int sqlitePgOpen(const char *zFilename, Pgr **ppPgr){
Pgr *p;
int n;
n = strlen(zFilename);
p = sqliteMalloc( sizeof(*p) + n*2 + 4 );
if( p==0 ){
*ppPgr = 0;
return SQLITE_NOMEM;
}
p->zMain = (char*)&p[1];
strcpy(p->zMain, zFilename);
p->zJournal = &p->zMain[n+1];
strcpy(p->zJournal, p->zMain);
p->zJournal[n] = '~';
p->zJournal[n+1] = 0;
p->fdJournal = -1;
p->fdMain = open(p->zMain, O_CREAT|O_RDWR, 0600);
if( p->fdMain<0 ){
*ppPgr = 0;
sqliteFree(p);
return SQLITE_PERM;
}
p->nDbPg = sqlitePgPageCount(p->fdMain);
if( access(p->zJournal, R_OK)==0 ){
sqlitePgJournalPlayback(p);
}
*ppPgr = p;
return SQLITE_OK;
}
/*
** Close the database file. Any outstanding transactions are abandoned.
*/
int sqlitePgClose(Pgr *p){
Pghdr *pPg;
if( p->fdMain ) close(p->fdMain);
if( p->fdJournal ) close(p->fdJournal);
unlink(p->zJournal);
while( (pPg = p->pAll)!=0 ){
p->pAll = pPg->pNx;
sqliteFree(pPg);
}
while( (pPg = p->pJidx)!=0 ){
p->pAll = pPg->pNx;
sqliteFree(pPg);
}
sqliteFree(p);
return SQLITE_OK;
}
/*
** Begin a new transaction. Return SQLITE_OK on success or an error
** code if something goes wrong.
*/
int sqlitePgBeginTransaction(Pgr *p){
assert( p->fdJournal<0 );
if( p->nRefPg>0 ){
/* release the read lock */
}
/* write lock the database */
p->fdJournal = open(p->zJournal, O_CREAT|O_EXCL|O_RDWR, 0600);
if( p->fdJournal<0 ){
return SQLITE_PERM;
}
p->nJPg = 0;
TRACE(("PG: begin transaction\n"));
return SQLITE_OK;
}
/*
** Commit the current transaction. Return SQLITE_OK or an error code.
*/
int sqlitePgCommit(Pgr *p){
Pghdr *pPrimaryIdx = 0;
Pghdr *pPg;
int rc;
for(pPg=p->pAll; pPg; pPg=pPg->pNx){
if( pPg->isDirty ){
rc = sqlitePgMakeClean(pPg);
if( rc!=SQLITE_OK ){
return rc;
}
}
}
for(pPg=p->pJidx; pPg; pPg=pPg->pNx){
if( pPg->jpgno==1 ){
pPrimaryIdx = pPg;
}else{
TRACE(("PG: writing j-index %u\n", pPg->jpgno));
rc = sqlitePgMakeClean(pPg);
if( rc!=SQLITE_OK ){
return rc;
}
}
}
assert( pPrimaryIdx!=0 );
fsync(p->fdJournal);
TRACE(("PG: writing j-index %u\n", pPrimaryIdx->jpgno));
rc = sqlitePgMakeClean(pPrimaryIdx);
if( rc!=SQLITE_OK ){
return rc;
}
fsync(p->fdJournal);
rc = sqlitePgJournalPlayback(p);
if( rc!=SQLITE_OK ){
return rc;
}
/* remove write lock from database */
if( p->nRefPg>0 ){
/* acquire read lock on database */
}
return SQLITE_OK;
}
/*
** Abandon the current transaction.
*/
int sqlitePgRollback(Pgr *p){
Pghdr *pPg;
TRACE(("PG: begin rollback\n"));
for(pPg=p->pAll; pPg; pPg=pPg->pNx){
if( pPg->isDirty || pPg->jpgno!=0 ){
pPg->isDirty = 0;
pPg->jpgno = 0;
if( pPg->nRef>0 ){
TRACE(("PG: reloading d-page %u\n", pPg->dbpgno));
sqlitePgRead(p->fdMain, PG_TO_DATA(pPg), pPg->dbpgno);
}else{
sqlitePgUnlinkLru(pPg);
}
}
}
close(p->fdJournal);
p->fdJournal = -1;
unlink(p->zJournal);
while( (pPg = p->pJidx)!=0 ){
p->pAll = pPg->pNx;
sqliteFree(pPg);
}
p->nDbPg = sqlitePgPageCount(p->fdMain);
/* remove write lock from database */
if( p->nRefPg>0 ){
/* acquire read lock on database */
}
return SQLITE_OK;
}
/*
** Get a page from the database. Return a pointer to the data for that
** page.
**
** A NULL pointer will be returned if we run out of memory.
*/
int sqlitePgGet(Pgr *p, u32 pgno, void **ppData){
Pghdr *pPg;
int h;
pPg = sqlitePgFind(p, pgno);
if( pPg ){
pPg->nRef++;
if( pPg->nRef==1 ){
sqlitePgUnlinkLru(pPg);
TRACE(("PG: d-page %u pulled from cache\n", pgno));
}
p->nRefPg++;
if( p->nRefPg==1 ){
/* Acquire a read lock */
}
*ppData = PG_TO_DATA(pPg);
return SQLITE_OK;
}
if( p->nMemPg<MX_MEM_PAGE || p->pLru==0 ){
pPg = sqliteMalloc( sizeof(Pghdr) + SQLITE_PAGE_SIZE );
if( pPg==0 ) return SQLITE_NOMEM;
p->nMemPg++;
pPg->pNx = p->pAll;
p->pAll = pPg;
pPg->p = p;
TRACE(("PG: new page %d created.\n", p->nMemPg));
}else{
int rc;
pPg = p->pLru;
if( pPg->isDirty ){
rc = sqlitePgMakeClean(pPg);
if( rc!=SQLITE_OK ) return rc;
}
sqlitePgUnlinkLru(pPg);
h = pPg->dbpgno % PG_HASH_SIZE;
if( pPg->pPvHash ){
pPg->pPvHash->pNxHash = pPg->pNxHash;
}else{
assert( p->aPgHash[h]==pPg );
p->aPgHash[h] = pPg->pNxHash;
}
if( pPg->pNxHash ){
pPg->pNxHash->pPvHash = pPg->pPvHash;
}
TRACE(("PG: recycling d-page %u to d-page %u\n", pPg->dbpgno, pgno));
}
pPg->dbpgno = pgno;
if( pgno>p->nDbPg ){
p->nDbPg = pgno;
}
h = pgno % PG_HASH_SIZE;
pPg->pPvHash = 0;
pPg->pNxHash = p->aPgHash[h];
if( pPg->pNxHash ){
pPg->pNxHash->pPvHash = pPg;
}
p->aPgHash[h] = pPg;
pPg->jpgno = sqlitePgJournalPageNumber(p, pgno);
if( pPg->jpgno!=0 ){
TRACE(("PG: reading d-page %u content from j-page %u\n", pgno, pPg->jpgno));
sqlitePgRead(p->fdJournal, PG_TO_DATA(pPg), pPg->jpgno);
}else if( pPg->dbpgno!=0 ){
TRACE(("PG: reading d-page %u from database\n", pgno));
sqlitePgRead(p->fdMain, PG_TO_DATA(pPg), pPg->dbpgno);
}else{
TRACE(("PG: reading zero page\n");
memset(PG_TO_DATA(pPg), 0, SQLITE_PAGE_SIZE);
}
pPg->isDirty = 0;
pPg->nRef = 1;
p->nRefPg++;
if( p->nRefPg==1 ){
/* Acquire a read lock */
}
*ppData = PG_TO_DATA(pPg);
return SQLITE_OK;
}
/*
** Release a reference to a database data page.
*/
int sqlitePgUnref(void *pData){
Pghdr *pPg = DATA_TO_PG(pData);
pPg->nRef--;
assert( pPg->nRef>=0 );
if( pPg->nRef==0 ){
Pgr *p = pPg->p;
pPg->pMru = 0;
pPg->pLru = p->pLru;
p->pLru = pPg;
TRACE(("PG: d-page %u is unused\n", pPg->dbpgno));
p->nRefPg--;
if( p->nRefPg==0 ){
/* Release the read lock */
}
}
return SQLITE_OK;
}
/*
** The database page in the argument has been modified. Write it back
** to the database file on the next commit.
*/
int sqlitePgTouch(void *pD){
Pghdr *pPg = DATA_TO_PG(pD);
assert( pPg->p->fdJournal>=0 );
if( pPg->isDirty==0 ){
pPg->isDirty = 1;
TRACE(("PG: d-page %u is dirty\n", pPg->dbpgno));
}
return SQLITE_OK;
}
/*
** Return the number of the first unused page at the end of the
** database file.
*/
int sqlitePgCount(Pgr *p, u32 *pPgno){
*pPgno = p->nDbPg;
return SQLITE_OK;
}
/*
** Return the page number associated with the given page.
*/
u32 sqlitePgNum(void *pD){
Pghdr *pPg = DATA_TO_PG(pD);
return pPg->dbpgno;
}
+40
View File
@@ -0,0 +1,40 @@
/*
** Copyright (c) 2001 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*************************************************************************
** $Id: pg.h,v 1.1 2001/02/11 16:56:24 drh Exp $
*/
typedef struct Pgr Pgr;
#define SQLITE_PAGE_SIZE 1024
int sqlitePgOpen(const char *filename, Pgr **pp);
int sqlitePgClose(Pgr*);
int sqlitePgBeginTransaction(Pgr*);
int sqlitePgCommit(Pgr*);
int sqlitePgRollback(Pgr*);
int sqlitePgGet(Pgr*, u32 pgno, void **);
int sqlitePgUnref(void*);
int sqlitePgTouch(void*);
int sqlitePgCount(Pgr*, u32*);
u32 sqlitePgNum(void*);
+41
View File
@@ -0,0 +1,41 @@
set sizes {1024 2048 4096 8192 16384 32768}
set fmt { %-8s}
puts -nonewline "page size: "
foreach s $sizes {
puts -nonewline [format $fmt $s]
}
puts ""
puts -nonewline "on leaf: "
foreach s $sizes {
set x [expr {$s - 18*4}]
set p($s) $x
puts -nonewline [format $fmt $x]
}
puts ""
puts -nonewline "direct: "
foreach s $sizes {
set x [expr {$p($s) + 10*$s}]
set p($s) $x
puts -nonewline [format $fmt $x]
}
puts ""
puts -nonewline "indirect: "
foreach s $sizes {
set x [expr {$p($s) + ($s/4.0)*$s}]
set p($s) $x
puts -nonewline [format $fmt $x]
}
puts ""
puts -nonewline "dbl indir: "
foreach s $sizes {
set x [expr {$p($s) + ($s/4.0)*($s/4)*$s}]
set p($s) $x
puts -nonewline [format $fmt $x]
}
puts ""
+47 -12
View File
@@ -24,7 +24,7 @@
** This file contains routines used for analyzing expressions and
** for generating VDBE code that evaluates expressions.
**
** $Id: expr.c,v 1.18 2000/06/21 13:59:11 drh Exp $
** $Id: expr.c,v 1.21 2001/01/15 22:51:10 drh Exp $
*/
#include "sqliteInt.h"
@@ -216,7 +216,7 @@ int sqliteExprResolveIds(Parse *pParse, IdList *pTabList, Expr *pExpr){
** table. The cursor number of the temporary table has already
** been put in iTable by sqliteExprResolveInSelect().
*/
sqliteVdbeAddOp(v, OP_Open, pExpr->iTable, 1, 0, 0);
sqliteVdbeAddOp(v, OP_OpenIdx, pExpr->iTable, 1, 0, 0);
if( sqliteSelect(pParse, pExpr->pSelect, SRT_Set, pExpr->iTable) );
}else if( pExpr->pList ){
/* Case 2: expr IN (exprlist)
@@ -323,6 +323,8 @@ int sqliteFuncId(Token *pToken){
{ "sum", 3, FN_Sum },
{ "avg", 3, FN_Avg },
{ "fcnt", 4, FN_Fcnt }, /* Used for testing only */
{ "length", 6, FN_Length},
{ "substr", 6, FN_Substr},
};
int i;
for(i=0; i<ArraySize(aFunc); i++){
@@ -381,6 +383,16 @@ int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){
is_agg = 1;
break;
}
case FN_Length: {
too_few_args = n<1;
too_many_args = n>1;
break;
}
case FN_Substr: {
too_few_args = n<3;
too_many_args = n>3;
break;
}
/* The "fcnt(*)" function always returns the number of fetch
** operations that have occurred so far while processing the
** SQL statement. This information can be used by test procedures
@@ -392,6 +404,7 @@ int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){
n = 0;
break;
}
default: break;
}
if( no_such_func ){
@@ -574,15 +587,37 @@ void sqliteExprCode(Parse *pParse, Expr *pExpr){
int op;
int i;
ExprList *pList = pExpr->pList;
if( id==FN_Fcnt ){
sqliteVdbeAddOp(v, OP_Fcnt, 0, 0, 0, 0);
break;
}
op = id==FN_Min ? OP_Min : OP_Max;
for(i=0; i<pList->nExpr; i++){
sqliteExprCode(pParse, pList->a[i].pExpr);
if( i>0 ){
sqliteVdbeAddOp(v, op, 0, 0, 0, 0);
switch( id ){
case FN_Fcnt: {
sqliteVdbeAddOp(v, OP_Fcnt, 0, 0, 0, 0);
break;
}
case FN_Min:
case FN_Max: {
op = id==FN_Min ? OP_Min : OP_Max;
for(i=0; i<pList->nExpr; i++){
sqliteExprCode(pParse, pList->a[i].pExpr);
if( i>0 ){
sqliteVdbeAddOp(v, op, 0, 0, 0, 0);
}
}
break;
}
case FN_Length: {
sqliteExprCode(pParse, pList->a[0].pExpr);
sqliteVdbeAddOp(v, OP_Strlen, 0, 0, 0, 0);
break;
}
case FN_Substr: {
for(i=0; i<pList->nExpr; i++){
sqliteExprCode(pParse, pList->a[i].pExpr);
}
sqliteVdbeAddOp(v, OP_Substr, 0, 0, 0, 0);
break;
}
default: {
/* Can't happen! */
break;
}
}
break;
@@ -703,7 +738,7 @@ void sqliteExprIfTrue(Parse *pParse, Expr *pExpr, int dest){
}
/*
** Generate code for boolean expression such that a jump is made
** Generate code for a boolean expression such that a jump is made
** to the label "dest" if the expression is false but execution
** continues straight thru if the expression is true.
*/
+4 -4
View File
@@ -24,7 +24,7 @@
** This file contains C code routines that are called by the parser
** to handle INSERT statements.
**
** $Id: insert.c,v 1.11 2000/06/21 13:59:12 drh Exp $
** $Id: insert.c,v 1.12 2001/01/15 22:51:11 drh Exp $
*/
#include "sqliteInt.h"
@@ -92,7 +92,7 @@ void sqliteInsert(
if( pSelect ){
int rc;
srcTab = pParse->nTab++;
sqliteVdbeAddOp(v, OP_Open, srcTab, 1, 0, 0);
sqliteVdbeAddOp(v, OP_OpenTbl, srcTab, 1, 0, 0);
rc = sqliteSelect(pParse, pSelect, SRT_Table, srcTab);
if( rc ) goto insert_cleanup;
assert( pSelect->pEList );
@@ -156,9 +156,9 @@ void sqliteInsert(
** all indices of that table.
*/
base = pParse->nTab;
sqliteVdbeAddOp(v, OP_Open, base, 1, pTab->zName, 0);
sqliteVdbeAddOp(v, OP_OpenTbl, base, 1, pTab->zName, 0);
for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
sqliteVdbeAddOp(v, OP_Open, idx+base, 1, pIdx->zName, 0);
sqliteVdbeAddOp(v, OP_OpenIdx, idx+base, 1, pIdx->zName, 0);
}
/* If the data source is a SELECT statement, then we have to create
+59 -28
View File
@@ -26,9 +26,10 @@
** other files are for internal use by SQLite and should not be
** accessed by users of the library.
**
** $Id: main.c,v 1.16 2000/08/02 13:47:42 drh Exp $
** $Id: main.c,v 1.25 2001/02/11 16:56:24 drh Exp $
*/
#include "sqliteInt.h"
#include <unistd.h>
/*
** This is the callback routine for the code that initializes the
@@ -123,7 +124,7 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){
** database scheme.
*/
static VdbeOp initProg[] = {
{ OP_Open, 0, 0, MASTER_NAME},
{ OP_OpenTbl, 0, 0, MASTER_NAME},
{ OP_Next, 0, 9, 0}, /* 1 */
{ OP_Field, 0, 0, 0},
{ OP_String, 0, 0, "meta"},
@@ -154,7 +155,7 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){
/* Create a virtual machine to run the initialization program. Run
** the program. The delete the virtual machine.
*/
vdbe = sqliteVdbeCreate(db->pBe);
vdbe = sqliteVdbeCreate(db);
if( vdbe==0 ){
sqliteSetString(pzErrMsg, "out of memory",0);
return 1;
@@ -184,6 +185,11 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){
return rc;
}
/*
** The version of the library
*/
const char sqlite_version[] = SQLITE_VERSION;
/*
** Open a new SQLite database. Construct an "sqlite" structure to define
** the state of this database and return a pointer to that structure.
@@ -222,7 +228,7 @@ sqlite *sqlite_open(const char *zFilename, int mode, char **pzErrMsg){
if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){
sqlite_close(db);
return 0;
}else{
}else /* if( pzErrMsg ) */{
free(*pzErrMsg);
*pzErrMsg = 0;
}
@@ -234,7 +240,7 @@ sqlite *sqlite_open(const char *zFilename, int mode, char **pzErrMsg){
*/
void sqlite_close(sqlite *db){
int i;
sqliteDbbeClose(db->pBe);
db->pBe->x->Close(db->pBe);
for(i=0; i<N_HASH; i++){
Table *pNext, *pList = db->apTblHash[i];
db->apTblHash[i] = 0;
@@ -252,29 +258,50 @@ void sqlite_close(sqlite *db){
** Return TRUE if the given SQL string ends in a semicolon.
*/
int sqlite_complete(const char *zSql){
int i;
int lastWasSemi = 0;
i = 0;
while( i>=0 && zSql[i]!=0 ){
int tokenType;
int n;
n = sqliteGetToken(&zSql[i], &tokenType);
switch( tokenType ){
case TK_SPACE:
case TK_COMMENT:
int isComplete = 0;
while( *zSql ){
switch( *zSql ){
case ';': {
isComplete = 1;
break;
case TK_SEMI:
lastWasSemi = 1;
}
case ' ':
case '\t':
case '\n':
case '\f': {
break;
default:
lastWasSemi = 0;
}
case '\'': {
isComplete = 0;
zSql++;
while( *zSql && *zSql!='\'' ){ zSql++; }
if( *zSql==0 ) return 0;
break;
}
case '"': {
isComplete = 0;
zSql++;
while( *zSql && *zSql!='"' ){ zSql++; }
if( *zSql==0 ) return 0;
break;
}
case '-': {
if( zSql[1]!='-' ){
isComplete = 0;
break;
}
while( *zSql && *zSql!='\n' ){ zSql++; }
if( *zSql==0 ) return isComplete;
break;
}
default: {
isComplete = 0;
break;
}
}
i += n;
zSql++;
}
return lastWasSemi;
return isComplete;
}
/*
@@ -295,7 +322,6 @@ int sqlite_exec(
char **pzErrMsg /* Write error messages here */
){
Parse sParse;
int rc;
if( pzErrMsg ) *pzErrMsg = 0;
if( (db->flags & SQLITE_Initialized)==0 ){
@@ -306,9 +332,8 @@ int sqlite_exec(
sParse.db = db;
sParse.xCallback = xCallback;
sParse.pArg = pArg;
rc = sqliteRunParser(&sParse, zSql, pzErrMsg);
sqliteStrRealloc(pzErrMsg);
return rc;
sqliteRunParser(&sParse, zSql, pzErrMsg);
return sParse.rc;
}
/*
@@ -322,7 +347,6 @@ static int sqlite_default_busy_callback(
const char *NotUsed, /* The name of the table that is busy */
int count /* Number of times table has been busy */
){
int rc;
#if defined(HAVE_USLEEP) && HAVE_USLEEP
int delay = 10000;
int prior_delay = 0;
@@ -378,3 +402,10 @@ void sqlite_busy_timeout(sqlite *db, int ms){
sqlite_busy_handler(db, 0, 0);
}
}
/*
** Cause any pending operation to stop at its earliest opportunity.
*/
void sqlite_interrupt(sqlite *db){
db->flags |= SQLITE_Interrupt;
}
+6 -6
View File
@@ -26,7 +26,7 @@
** the parser. Lemon will also generate a header file containing
** numeric codes for all of the tokens.
**
** @(#) $Id: parse.y,v 1.25 2000/08/01 09:56:27 drh Exp $
** @(#) $Id: parse.y,v 1.26 2001/01/04 14:20:18 drh Exp $
*/
%token_prefix TK_
%token_type {Token}
@@ -114,12 +114,12 @@ ccons ::= CHECK LP expr RP.
conslist_opt ::= .
conslist_opt ::= COMMA conslist.
conslist ::= conslist COMMA tcons.
conslist ::= conslist tcons.
conslist ::= tcons.
tcons ::= CONSTRAINT id tcons2.
tcons ::= tcons2.
tcons2 ::= PRIMARY KEY LP idxlist(X) RP. {sqliteCreateIndex(pParse,0,0,X,0,0);}
tcons2 ::= UNIQUE LP idlist RP.
tcons2 ::= CHECK expr.
tcons ::= CONSTRAINT id.
tcons ::= PRIMARY KEY LP idxlist(X) RP. {sqliteCreateIndex(pParse,0,0,X,0,0);}
tcons ::= UNIQUE LP idlist RP.
tcons ::= CHECK expr.
idlist ::= idlist COMMA id.
idlist ::= id.
+789
View File
@@ -0,0 +1,789 @@
/*
** The "printf" code that follows dates from the 1980's. It is in
** the public domain. The original comments are included here for
** completeness. They are slightly out-of-date.
**
** The following modules is an enhanced replacement for the "printf" programs
** found in the standard library. The following enhancements are
** supported:
**
** + Additional functions. The standard set of "printf" functions
** includes printf, fprintf, sprintf, vprintf, vfprintf, and
** vsprintf. This module adds the following:
**
** * snprintf -- Works like sprintf, but has an extra argument
** which is the size of the buffer written to.
**
** * mprintf -- Similar to sprintf. Writes output to memory
** obtained from malloc.
**
** * xprintf -- Calls a function to dispose of output.
**
** * nprintf -- No output, but returns the number of characters
** that would have been output by printf.
**
** * A v- version (ex: vsnprintf) of every function is also
** supplied.
**
** + A few extensions to the formatting notation are supported:
**
** * The "=" flag (similar to "-") causes the output to be
** be centered in the appropriately sized field.
**
** * The %b field outputs an integer in binary notation.
**
** * The %c field now accepts a precision. The character output
** is repeated by the number of times the precision specifies.
**
** * The %' field works like %c, but takes as its character the
** next character of the format string, instead of the next
** argument. For example, printf("%.78'-") prints 78 minus
** signs, the same as printf("%.78c",'-').
**
** + When compiled using GCC on a SPARC, this version of printf is
** faster than the library printf for SUN OS 4.1.
**
** + All functions are fully reentrant.
**
*/
#include "sqliteInt.h"
/*
** Undefine COMPATIBILITY to make some slight changes in the way things
** work. I think the changes are an improvement, but they are not
** backwards compatible.
*/
/* #define COMPATIBILITY / * Compatible with SUN OS 4.1 */
/*
** Conversion types fall into various categories as defined by the
** following enumeration.
*/
enum et_type { /* The type of the format field */
etRADIX, /* Integer types. %d, %x, %o, and so forth */
etFLOAT, /* Floating point. %f */
etEXP, /* Exponentional notation. %e and %E */
etGENERIC, /* Floating or exponential, depending on exponent. %g */
etSIZE, /* Return number of characters processed so far. %n */
etSTRING, /* Strings. %s */
etPERCENT, /* Percent symbol. %% */
etCHARX, /* Characters. %c */
etERROR, /* Used to indicate no such conversion type */
/* The rest are extensions, not normally found in printf() */
etCHARLIT, /* Literal characters. %' */
etSQLESCAPE, /* Strings with '\'' doubled. %q */
etORDINAL /* 1st, 2nd, 3rd and so forth */
};
/*
** Each builtin conversion character (ex: the 'd' in "%d") is described
** by an instance of the following structure
*/
typedef struct et_info { /* Information about each format field */
int fmttype; /* The format field code letter */
int base; /* The base for radix conversion */
char *charset; /* The character set for conversion */
int flag_signed; /* Is the quantity signed? */
char *prefix; /* Prefix on non-zero values in alt format */
enum et_type type; /* Conversion paradigm */
} et_info;
/*
** The following table is searched linearly, so it is good to put the
** most frequently used conversion types first.
*/
static et_info fmtinfo[] = {
{ 'd', 10, "0123456789", 1, 0, etRADIX, },
{ 's', 0, 0, 0, 0, etSTRING, },
{ 'q', 0, 0, 0, 0, etSQLESCAPE, },
{ 'c', 0, 0, 0, 0, etCHARX, },
{ 'o', 8, "01234567", 0, "0", etRADIX, },
{ 'u', 10, "0123456789", 0, 0, etRADIX, },
{ 'x', 16, "0123456789abcdef", 0, "x0", etRADIX, },
{ 'X', 16, "0123456789ABCDEF", 0, "X0", etRADIX, },
{ 'r', 10, "0123456789", 0, 0, etORDINAL, },
{ 'f', 0, 0, 1, 0, etFLOAT, },
{ 'e', 0, "e", 1, 0, etEXP, },
{ 'E', 0, "E", 1, 0, etEXP, },
{ 'g', 0, "e", 1, 0, etGENERIC, },
{ 'G', 0, "E", 1, 0, etGENERIC, },
{ 'i', 10, "0123456789", 1, 0, etRADIX, },
{ 'n', 0, 0, 0, 0, etSIZE, },
{ '%', 0, 0, 0, 0, etPERCENT, },
{ 'b', 2, "01", 0, "b0", etRADIX, }, /* Binary */
{ 'p', 10, "0123456789", 0, 0, etRADIX, }, /* Pointers */
{ '\'', 0, 0, 0, 0, etCHARLIT, }, /* Literal char */
};
#define etNINFO (sizeof(fmtinfo)/sizeof(fmtinfo[0]))
/*
** If NOFLOATINGPOINT is defined, then none of the floating point
** conversions will work.
*/
#ifndef etNOFLOATINGPOINT
/*
** "*val" is a double such that 0.1 <= *val < 10.0
** Return the ascii code for the leading digit of *val, then
** multiply "*val" by 10.0 to renormalize.
**
** Example:
** input: *val = 3.14159
** output: *val = 1.4159 function return = '3'
**
** The counter *cnt is incremented each time. After counter exceeds
** 16 (the number of significant digits in a 64-bit float) '0' is
** always returned.
*/
static int et_getdigit(double *val, int *cnt){
int digit;
double d;
if( (*cnt)++ >= 16 ) return '0';
digit = (int)*val;
d = digit;
digit += '0';
*val = (*val - d)*10.0;
return digit;
}
#endif
#define etBUFSIZE 1000 /* Size of the output buffer */
/*
** The root program. All variations call this core.
**
** INPUTS:
** func This is a pointer to a function taking three arguments
** 1. A pointer to anything. Same as the "arg" parameter.
** 2. A pointer to the list of characters to be output
** (Note, this list is NOT null terminated.)
** 3. An integer number of characters to be output.
** (Note: This number might be zero.)
**
** arg This is the pointer to anything which will be passed as the
** first argument to "func". Use it for whatever you like.
**
** fmt This is the format string, as in the usual print.
**
** ap This is a pointer to a list of arguments. Same as in
** vfprint.
**
** OUTPUTS:
** The return value is the total number of characters sent to
** the function "func". Returns -1 on a error.
**
** Note that the order in which automatic variables are declared below
** seems to make a big difference in determining how fast this beast
** will run.
*/
static int vxprintf(
void (*func)(void*,char*,int),
void *arg,
const char *format,
va_list ap
){
register const char *fmt; /* The format string. */
register int c; /* Next character in the format string */
register char *bufpt; /* Pointer to the conversion buffer */
register int precision; /* Precision of the current field */
register int length; /* Length of the field */
register int idx; /* A general purpose loop counter */
int count; /* Total number of characters output */
int width; /* Width of the current field */
int flag_leftjustify; /* True if "-" flag is present */
int flag_plussign; /* True if "+" flag is present */
int flag_blanksign; /* True if " " flag is present */
int flag_alternateform; /* True if "#" flag is present */
int flag_zeropad; /* True if field width constant starts with zero */
int flag_long; /* True if "l" flag is present */
int flag_center; /* True if "=" flag is present */
unsigned long longvalue; /* Value for integer types */
double realvalue; /* Value for real types */
et_info *infop; /* Pointer to the appropriate info structure */
char buf[etBUFSIZE]; /* Conversion buffer */
char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */
int errorflag = 0; /* True if an error is encountered */
enum et_type xtype; /* Conversion paradigm */
char *zMem; /* String to be freed */
char *zExtra; /* Extra memory used for etTCLESCAPE conversions */
static char spaces[] = " "
" ";
#define etSPACESIZE (sizeof(spaces)-1)
#ifndef etNOFLOATINGPOINT
int exp; /* exponent of real numbers */
double rounder; /* Used for rounding floating point values */
int flag_dp; /* True if decimal point should be shown */
int flag_rtz; /* True if trailing zeros should be removed */
int flag_exp; /* True to force display of the exponent */
int nsd; /* Number of significant digits returned */
#endif
fmt = format; /* Put in a register for speed */
count = length = 0;
bufpt = 0;
for(; (c=(*fmt))!=0; ++fmt){
if( c!='%' ){
register int amt;
bufpt = (char *)fmt;
amt = 1;
while( (c=(*++fmt))!='%' && c!=0 ) amt++;
(*func)(arg,bufpt,amt);
count += amt;
if( c==0 ) break;
}
if( (c=(*++fmt))==0 ){
errorflag = 1;
(*func)(arg,"%",1);
count++;
break;
}
/* Find out what flags are present */
flag_leftjustify = flag_plussign = flag_blanksign =
flag_alternateform = flag_zeropad = flag_center = 0;
do{
switch( c ){
case '-': flag_leftjustify = 1; c = 0; break;
case '+': flag_plussign = 1; c = 0; break;
case ' ': flag_blanksign = 1; c = 0; break;
case '#': flag_alternateform = 1; c = 0; break;
case '0': flag_zeropad = 1; c = 0; break;
case '=': flag_center = 1; c = 0; break;
default: break;
}
}while( c==0 && (c=(*++fmt))!=0 );
if( flag_center ) flag_leftjustify = 0;
/* Get the field width */
width = 0;
if( c=='*' ){
width = va_arg(ap,int);
if( width<0 ){
flag_leftjustify = 1;
width = -width;
}
c = *++fmt;
}else{
while( c>='0' && c<='9' ){
width = width*10 + c - '0';
c = *++fmt;
}
}
if( width > etBUFSIZE-10 ){
width = etBUFSIZE-10;
}
/* Get the precision */
if( c=='.' ){
precision = 0;
c = *++fmt;
if( c=='*' ){
precision = va_arg(ap,int);
#ifndef etCOMPATIBILITY
/* This is sensible, but SUN OS 4.1 doesn't do it. */
if( precision<0 ) precision = -precision;
#endif
c = *++fmt;
}else{
while( c>='0' && c<='9' ){
precision = precision*10 + c - '0';
c = *++fmt;
}
}
/* Limit the precision to prevent overflowing buf[] during conversion */
if( precision>etBUFSIZE-40 ) precision = etBUFSIZE-40;
}else{
precision = -1;
}
/* Get the conversion type modifier */
if( c=='l' ){
flag_long = 1;
c = *++fmt;
}else{
flag_long = 0;
}
/* Fetch the info entry for the field */
infop = 0;
for(idx=0; idx<etNINFO; idx++){
if( c==fmtinfo[idx].fmttype ){
infop = &fmtinfo[idx];
break;
}
}
/* No info entry found. It must be an error. */
if( infop==0 ){
xtype = etERROR;
}else{
xtype = infop->type;
}
zExtra = 0;
/*
** At this point, variables are initialized as follows:
**
** flag_alternateform TRUE if a '#' is present.
** flag_plussign TRUE if a '+' is present.
** flag_leftjustify TRUE if a '-' is present or if the
** field width was negative.
** flag_zeropad TRUE if the width began with 0.
** flag_long TRUE if the letter 'l' (ell) prefixed
** the conversion character.
** flag_blanksign TRUE if a ' ' is present.
** width The specified field width. This is
** always non-negative. Zero is the default.
** precision The specified precision. The default
** is -1.
** xtype The class of the conversion.
** infop Pointer to the appropriate info struct.
*/
switch( xtype ){
case etORDINAL:
case etRADIX:
if( flag_long ) longvalue = va_arg(ap,long);
else longvalue = va_arg(ap,int);
#ifdef etCOMPATIBILITY
/* For the format %#x, the value zero is printed "0" not "0x0".
** I think this is stupid. */
if( longvalue==0 ) flag_alternateform = 0;
#else
/* More sensible: turn off the prefix for octal (to prevent "00"),
** but leave the prefix for hex. */
if( longvalue==0 && infop->base==8 ) flag_alternateform = 0;
#endif
if( infop->flag_signed ){
if( *(long*)&longvalue<0 ){
longvalue = -*(long*)&longvalue;
prefix = '-';
}else if( flag_plussign ) prefix = '+';
else if( flag_blanksign ) prefix = ' ';
else prefix = 0;
}else prefix = 0;
if( flag_zeropad && precision<width-(prefix!=0) ){
precision = width-(prefix!=0);
}
bufpt = &buf[etBUFSIZE];
if( xtype==etORDINAL ){
long a,b;
a = longvalue%10;
b = longvalue%100;
bufpt -= 2;
if( a==0 || a>3 || (b>10 && b<14) ){
bufpt[0] = 't';
bufpt[1] = 'h';
}else if( a==1 ){
bufpt[0] = 's';
bufpt[1] = 't';
}else if( a==2 ){
bufpt[0] = 'n';
bufpt[1] = 'd';
}else if( a==3 ){
bufpt[0] = 'r';
bufpt[1] = 'd';
}
}
{
register char *cset; /* Use registers for speed */
register int base;
cset = infop->charset;
base = infop->base;
do{ /* Convert to ascii */
*(--bufpt) = cset[longvalue%base];
longvalue = longvalue/base;
}while( longvalue>0 );
}
length = (long)&buf[etBUFSIZE]-(long)bufpt;
for(idx=precision-length; idx>0; idx--){
*(--bufpt) = '0'; /* Zero pad */
}
if( prefix ) *(--bufpt) = prefix; /* Add sign */
if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */
char *pre, x;
pre = infop->prefix;
if( *bufpt!=pre[0] ){
for(pre=infop->prefix; (x=(*pre))!=0; pre++) *(--bufpt) = x;
}
}
length = (long)&buf[etBUFSIZE]-(long)bufpt;
break;
case etFLOAT:
case etEXP:
case etGENERIC:
realvalue = va_arg(ap,double);
#ifndef etNOFLOATINGPOINT
if( precision<0 ) precision = 6; /* Set default precision */
if( precision>etBUFSIZE-10 ) precision = etBUFSIZE-10;
if( realvalue<0.0 ){
realvalue = -realvalue;
prefix = '-';
}else{
if( flag_plussign ) prefix = '+';
else if( flag_blanksign ) prefix = ' ';
else prefix = 0;
}
if( infop->type==etGENERIC && precision>0 ) precision--;
rounder = 0.0;
#ifdef COMPATIBILITY
/* Rounding works like BSD when the constant 0.4999 is used. Wierd! */
for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1);
#else
/* It makes more sense to use 0.5 */
for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1);
#endif
if( infop->type==etFLOAT ) realvalue += rounder;
/* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
exp = 0;
if( realvalue>0.0 ){
int k = 0;
while( realvalue>=1e8 && k++<100 ){ realvalue *= 1e-8; exp+=8; }
while( realvalue>=10.0 && k++<100 ){ realvalue *= 0.1; exp++; }
while( realvalue<1e-8 && k++<100 ){ realvalue *= 1e8; exp-=8; }
while( realvalue<1.0 && k++<100 ){ realvalue *= 10.0; exp--; }
if( k>=100 ){
bufpt = "NaN";
length = 3;
break;
}
}
bufpt = buf;
/*
** If the field type is etGENERIC, then convert to either etEXP
** or etFLOAT, as appropriate.
*/
flag_exp = xtype==etEXP;
if( xtype!=etFLOAT ){
realvalue += rounder;
if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; }
}
if( xtype==etGENERIC ){
flag_rtz = !flag_alternateform;
if( exp<-4 || exp>precision ){
xtype = etEXP;
}else{
precision = precision - exp;
xtype = etFLOAT;
}
}else{
flag_rtz = 0;
}
/*
** The "exp+precision" test causes output to be of type etEXP if
** the precision is too large to fit in buf[].
*/
nsd = 0;
if( xtype==etFLOAT && exp+precision<etBUFSIZE-30 ){
flag_dp = (precision>0 || flag_alternateform);
if( prefix ) *(bufpt++) = prefix; /* Sign */
if( exp<0 ) *(bufpt++) = '0'; /* Digits before "." */
else for(; exp>=0; exp--) *(bufpt++) = et_getdigit(&realvalue,&nsd);
if( flag_dp ) *(bufpt++) = '.'; /* The decimal point */
for(exp++; exp<0 && precision>0; precision--, exp++){
*(bufpt++) = '0';
}
while( (precision--)>0 ) *(bufpt++) = et_getdigit(&realvalue,&nsd);
*(bufpt--) = 0; /* Null terminate */
if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */
while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0;
if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0;
}
bufpt++; /* point to next free slot */
}else{ /* etEXP or etGENERIC */
flag_dp = (precision>0 || flag_alternateform);
if( prefix ) *(bufpt++) = prefix; /* Sign */
*(bufpt++) = et_getdigit(&realvalue,&nsd); /* First digit */
if( flag_dp ) *(bufpt++) = '.'; /* Decimal point */
while( (precision--)>0 ) *(bufpt++) = et_getdigit(&realvalue,&nsd);
bufpt--; /* point to last digit */
if( flag_rtz && flag_dp ){ /* Remove tail zeros */
while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0;
if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0;
}
bufpt++; /* point to next free slot */
if( exp || flag_exp ){
*(bufpt++) = infop->charset[0];
if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; } /* sign of exp */
else { *(bufpt++) = '+'; }
if( exp>=100 ){
*(bufpt++) = (exp/100)+'0'; /* 100's digit */
exp %= 100;
}
*(bufpt++) = exp/10+'0'; /* 10's digit */
*(bufpt++) = exp%10+'0'; /* 1's digit */
}
}
/* The converted number is in buf[] and zero terminated. Output it.
** Note that the number is in the usual order, not reversed as with
** integer conversions. */
length = (long)bufpt-(long)buf;
bufpt = buf;
/* Special case: Add leading zeros if the flag_zeropad flag is
** set and we are not left justified */
if( flag_zeropad && !flag_leftjustify && length < width){
int i;
int nPad = width - length;
for(i=width; i>=nPad; i--){
bufpt[i] = bufpt[i-nPad];
}
i = prefix!=0;
while( nPad-- ) bufpt[i++] = '0';
length = width;
}
#endif
break;
case etSIZE:
*(va_arg(ap,int*)) = count;
length = width = 0;
break;
case etPERCENT:
buf[0] = '%';
bufpt = buf;
length = 1;
break;
case etCHARLIT:
case etCHARX:
c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt);
if( precision>=0 ){
for(idx=1; idx<precision; idx++) buf[idx] = c;
length = precision;
}else{
length =1;
}
bufpt = buf;
break;
case etSTRING:
zMem = bufpt = va_arg(ap,char*);
if( bufpt==0 ) bufpt = "(null)";
length = strlen(bufpt);
if( precision>=0 && precision<length ) length = precision;
break;
case etSQLESCAPE:
{
int i, j, n, c;
char *arg = va_arg(ap,char*);
if( arg==0 ) arg = "(NULL)";
for(i=n=0; (c=arg[i])!=0; i++){
if( c=='\'' ) n++;
}
n += i + 1;
if( n>etBUFSIZE ){
bufpt = zExtra = sqliteMalloc( n );
}else{
bufpt = buf;
}
for(i=j=0; (c=arg[i])!=0; i++){
bufpt[j++] = c;
if( c=='\'' ) bufpt[j++] = c;
}
bufpt[j] = 0;
length = j;
if( precision>=0 && precision<length ) length = precision;
}
break;
case etERROR:
buf[0] = '%';
buf[1] = c;
errorflag = 0;
idx = 1+(c!=0);
(*func)(arg,"%",idx);
count += idx;
if( c==0 ) fmt--;
break;
}/* End switch over the format type */
/*
** The text of the conversion is pointed to by "bufpt" and is
** "length" characters long. The field width is "width". Do
** the output.
*/
if( !flag_leftjustify ){
register int nspace;
nspace = width-length;
if( nspace>0 ){
if( flag_center ){
nspace = nspace/2;
width -= nspace;
flag_leftjustify = 1;
}
count += nspace;
while( nspace>=etSPACESIZE ){
(*func)(arg,spaces,etSPACESIZE);
nspace -= etSPACESIZE;
}
if( nspace>0 ) (*func)(arg,spaces,nspace);
}
}
if( length>0 ){
(*func)(arg,bufpt,length);
count += length;
}
if( flag_leftjustify ){
register int nspace;
nspace = width-length;
if( nspace>0 ){
count += nspace;
while( nspace>=etSPACESIZE ){
(*func)(arg,spaces,etSPACESIZE);
nspace -= etSPACESIZE;
}
if( nspace>0 ) (*func)(arg,spaces,nspace);
}
}
if( zExtra ){
sqliteFree(zExtra);
}
}/* End for loop over the format string */
return errorflag ? -1 : count;
} /* End of function */
/* This structure is used to store state information about the
** write to memory that is currently in progress.
*/
struct sgMprintf {
char *zBase; /* A base allocation */
char *zText; /* The string collected so far */
int nChar; /* Length of the string so far */
int nAlloc; /* Amount of space allocated in zText */
};
/*
** This function implements the callback from vxprintf.
**
** This routine add nNewChar characters of text in zNewText to
** the sgMprintf structure pointed to by "arg".
*/
static void mout(void *arg, char *zNewText, int nNewChar){
struct sgMprintf *pM = (struct sgMprintf*)arg;
if( pM->nChar + nNewChar + 1 > pM->nAlloc ){
pM->nAlloc = pM->nChar + nNewChar*2 + 1;
if( pM->zText==pM->zBase ){
pM->zText = sqliteMalloc(pM->nAlloc);
if( pM->zText && pM->nChar ) memcpy(pM->zText,pM->zBase,pM->nChar);
}else{
pM->zText = sqliteRealloc(pM->zText, pM->nAlloc);
}
}
if( pM->zText ){
memcpy(&pM->zText[pM->nChar], zNewText, nNewChar);
pM->nChar += nNewChar;
pM->zText[pM->nChar] = 0;
}
}
/*
** sqlite_mprintf() works like printf(), but allocations memory to hold the
** resulting string and returns a pointer to the allocated memory. Use
** sqliteFree() to release the memory allocated.
*/
char *sqlite_mprintf(const char *zFormat, ...){
va_list ap;
struct sgMprintf sMprintf;
char *zNew;
char zBuf[200];
sMprintf.nChar = 0;
sMprintf.nAlloc = sizeof(zBuf);
sMprintf.zText = zBuf;
sMprintf.zBase = zBuf;
va_start(ap,zFormat);
vxprintf(mout,&sMprintf,zFormat,ap);
va_end(ap);
sMprintf.zText[sMprintf.nChar] = 0;
if( sMprintf.zText==sMprintf.zBase ){
zNew = sqliteMalloc( sMprintf.nChar+1 );
if( zNew ) strcpy(zNew,zBuf);
}else{
zNew = sqliteRealloc(sMprintf.zText,sMprintf.nChar+1);
}
return zNew;
}
/* This is the varargs version of sqlite_mprintf.
*/
char *sqlite_vmprintf(const char *zFormat, va_list ap){
struct sgMprintf sMprintf;
char zBuf[200];
sMprintf.nChar = 0;
sMprintf.zText = zBuf;
sMprintf.nAlloc = sizeof(zBuf);
sMprintf.zBase = zBuf;
vxprintf(mout,&sMprintf,zFormat,ap);
sMprintf.zText[sMprintf.nChar] = 0;
if( sMprintf.zText==sMprintf.zBase ){
sMprintf.zText = sqliteMalloc( strlen(zBuf)+1 );
if( sMprintf.zText ) strcpy(sMprintf.zText,zBuf);
}else{
sMprintf.zText = sqliteRealloc(sMprintf.zText,sMprintf.nChar+1);
}
return sMprintf.zText;
}
/*
** The following four routines implement the varargs versions of the
** sqlite_exec() and sqlite_get_table() interfaces. See the sqlite.h
** header files for a more detailed description of how these interfaces
** work.
**
** These routines are all just simple wrappers.
*/
int sqlite_exec_printf(
sqlite *db, /* An open database */
char *sqlFormat, /* printf-style format string for the SQL */
sqlite_callback xCallback, /* Callback function */
void *pArg, /* 1st argument to callback function */
char **errmsg, /* Error msg written here */
... /* Arguments to the format string. */
){
va_list ap;
int rc;
va_start(ap, errmsg);
rc = sqlite_exec_vprintf(db, sqlFormat, xCallback, pArg, errmsg, ap);
va_end(ap);
return rc;
}
int sqlite_exec_vprintf(
sqlite *db, /* An open database */
char *sqlFormat, /* printf-style format string for the SQL */
sqlite_callback xCallback, /* Callback function */
void *pArg, /* 1st argument to callback function */
char **errmsg, /* Error msg written here */
va_list ap /* Arguments to the format string. */
){
char *zSql;
int rc;
zSql = sqlite_vmprintf(sqlFormat, ap);
rc = sqlite_exec(db, zSql, xCallback, pArg, errmsg);
sqliteFree(zSql);
return rc;
}
int sqlite_get_table_printf(
sqlite *db, /* An open database */
char *sqlFormat, /* printf-style format string for the SQL */
char ***resultp, /* Result written to a char *[] that this points to */
int *nrow, /* Number of result rows written here */
int *ncol, /* Number of result columns written here */
char **errmsg, /* Error msg written here */
... /* Arguments to the format string */
){
va_list ap;
int rc;
va_start(ap, errmsg);
rc = sqlite_get_table_vprintf(db, sqlFormat, resultp, nrow, ncol, errmsg, ap);
va_end(ap);
return rc;
}
int sqlite_get_table_vprintf(
sqlite *db, /* An open database */
char *sqlFormat, /* printf-style format string for the SQL */
char ***resultp, /* Result written to a char *[] that this points to */
int *nrow, /* Number of result rows written here */
int *ncolumn, /* Number of result columns written here */
char **errmsg, /* Error msg written here */
va_list ap /* Arguments to the format string */
){
char *zSql;
int rc;
zSql = sqlite_vmprintf(sqlFormat, ap);
rc = sqlite_get_table(db, zSql, resultp, nrow, ncolumn, errmsg);
sqliteFree(zSql);
return rc;
}
+127
View File
@@ -0,0 +1,127 @@
/*
** Copyright (c) 2000 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*************************************************************************
** This file contains code to implement a pseudo-random number
** generator (PRNG) for SQLite.
**
** Random numbers are used by some of the database backends in order
** to generate random integer keys for tables or random filenames.
**
** $Id: random.c,v 1.2 2001/01/31 13:28:09 drh Exp $
*/
#include "sqliteInt.h"
#include <time.h>
/*
** Get a single 8-bit random value from the RC4 PRNG.
*/
int sqliteRandomByte(void){
int t;
/*
** The following structure holds the current state of the RC4 algorithm.
** We use RC4 as a random number generator. Each call to RC4 gives
** a random 8-bit number.
**
** Nothing in this file or anywhere else in SQLite does any kind of
** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random
** number generator) not as an encryption device.
*/
static struct {
int isInit;
int i, j;
int s[256];
} prng_state;
/* Initialize the state of the random number generator once,
** the first time this routine is called. The seed value does
** not need to contain a lot of randomness since we are not
** trying to do secure encryption or anything like that...
*/
if( !prng_state.isInit ){
int i;
static char seed[] = " sqlite random seed";
char k[256];
time((time_t*)seed);
prng_state.j = 0;
prng_state.i = 0;
for(i=0; i<256; i++){
prng_state.s[i] = i;
k[i] = seed[i%sizeof(seed)];
}
for(i=0; i<256; i++){
int t;
prng_state.j = (prng_state.j + prng_state.s[i] + k[i]) & 0xff;
t = prng_state.s[prng_state.j];
prng_state.s[prng_state.j] = prng_state.s[i];
prng_state.s[i] = t;
}
prng_state.isInit = 1;
}
/* Generate and return single random byte
*/
prng_state.i = (prng_state.i + 1) & 0xff;
prng_state.j = (prng_state.j + prng_state.s[prng_state.i]) & 0xff;
t = prng_state.s[prng_state.i];
prng_state.s[prng_state.i] = prng_state.s[prng_state.j];
prng_state.s[prng_state.j] = t;
t = prng_state.s[prng_state.i] + prng_state.s[prng_state.j];
return t & 0xff;
}
/*
** Return a random 32-bit integer. The integer is generated by making
** 4 calls to sqliteRandomByte().
*/
int sqliteRandomInteger(void){
int r;
int i;
r = sqliteRandomByte();
for(i=1; i<4; i++){
r = (r<<8) + sqliteRandomByte();
}
return r;
}
/*
** Generate a random filename with the given prefix. The new filename
** is written into zBuf[]. The calling function must insure that
** zBuf[] is big enough to hold the prefix plus 20 or so extra
** characters.
**
** Very random names are chosen so that the chance of a
** collision with an existing filename is very very small.
*/
void sqliteRandomName(char *zBuf, char *zPrefix){
int i, j;
static const char zRandomChars[] = "abcdefghijklmnopqrstuvwxyz0123456789";
strcpy(zBuf, zPrefix);
j = strlen(zBuf);
for(i=0; i<15; i++){
int c = sqliteRandomByte() % (sizeof(zRandomChars) - 1);
zBuf[j++] = zRandomChars[c];
}
zBuf[j] = 0;
}
+9 -7
View File
@@ -24,7 +24,7 @@
** This file contains C code routines that are called by the parser
** to handle SELECT statements.
**
** $Id: select.c,v 1.26 2000/07/29 13:06:59 drh Exp $
** $Id: select.c,v 1.28 2001/01/15 22:51:11 drh Exp $
*/
#include "sqliteInt.h"
@@ -424,7 +424,7 @@ static int matchOrderbyToColumn(
Vdbe *sqliteGetVdbe(Parse *pParse){
Vdbe *v = pParse->pVdbe;
if( v==0 ){
v = pParse->pVdbe = sqliteVdbeCreate(pParse->db->pBe);
v = pParse->pVdbe = sqliteVdbeCreate(pParse->db);
}
if( v==0 ){
sqliteSetString(&pParse->zErrMsg, "out of memory", 0);
@@ -489,9 +489,11 @@ static int multiSelect(Parse *pParse, Select *p, int eDest, int iParm){
&& matchOrderbyToColumn(pParse, p, p->pOrderBy, unionTab, 1) ){
return 1;
}
sqliteVdbeAddOp(v, OP_Open, unionTab, 1, 0, 0);
if( p->op!=TK_ALL ){
sqliteVdbeAddOp(v, OP_OpenIdx, unionTab, 1, 0, 0);
sqliteVdbeAddOp(v, OP_KeyAsData, unionTab, 1, 0, 0);
}else{
sqliteVdbeAddOp(v, OP_OpenTbl, unionTab, 1, 0, 0);
}
}
@@ -549,7 +551,7 @@ static int multiSelect(Parse *pParse, Select *p, int eDest, int iParm){
if( p->pOrderBy && matchOrderbyToColumn(pParse,p,p->pOrderBy,tab1,1) ){
return 1;
}
sqliteVdbeAddOp(v, OP_Open, tab1, 1, 0, 0);
sqliteVdbeAddOp(v, OP_OpenIdx, tab1, 1, 0, 0);
sqliteVdbeAddOp(v, OP_KeyAsData, tab1, 1, 0, 0);
/* Code the SELECTs to our left into temporary table "tab1".
@@ -559,7 +561,7 @@ static int multiSelect(Parse *pParse, Select *p, int eDest, int iParm){
/* Code the current SELECT into temporary table "tab2"
*/
sqliteVdbeAddOp(v, OP_Open, tab2, 1, 0, 0);
sqliteVdbeAddOp(v, OP_OpenIdx, tab2, 1, 0, 0);
sqliteVdbeAddOp(v, OP_KeyAsData, tab2, 1, 0, 0);
p->pPrior = 0;
rc = sqliteSelect(pParse, p, SRT_Union, tab2);
@@ -818,7 +820,7 @@ int sqliteSelect(
*/
v = pParse->pVdbe;
if( v==0 ){
v = pParse->pVdbe = sqliteVdbeCreate(pParse->db->pBe);
v = pParse->pVdbe = sqliteVdbeCreate(pParse->db);
}
if( v==0 ){
sqliteSetString(&pParse->zErrMsg, "out of memory", 0);
@@ -852,7 +854,7 @@ int sqliteSelect(
/* Begin the database scan
*/
if( isDistinct ){
sqliteVdbeAddOp(v, OP_Open, distinct, 1, 0, 0);
sqliteVdbeAddOp(v, OP_OpenIdx, distinct, 1, 0, 0);
}
pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 0);
if( pWInfo==0 ) return 1;
+141 -55
View File
@@ -24,7 +24,7 @@
** This file contains code to implement the "sqlite" command line
** utility for accessing SQLite databases.
**
** $Id: shell.c,v 1.21 2000/08/17 09:50:00 drh Exp $
** $Id: shell.c,v 1.29 2001/01/04 14:27:08 drh Exp $
*/
#include <stdlib.h>
#include <string.h>
@@ -32,6 +32,9 @@
#include "sqlite.h"
#include <unistd.h>
#include <ctype.h>
#ifdef OS_UNIX
# include <signal.h>
#endif
#if defined(HAVE_READLINE) && HAVE_READLINE==1
# include <readline/readline.h>
@@ -41,6 +44,13 @@
# define add_history(X)
#endif
/*
** The following is the open SQLite database. We make a pointer
** to this database a static variable so that it can be accessed
** by the SIGINT handler to interrupt database processing.
*/
static sqlite *db = 0;
/*
** This routine reads a line of text from standard input, stores
** the text in memory obtained from malloc() and returns a pointer
@@ -140,8 +150,9 @@ struct callback_data {
#define MODE_Line 0 /* One column per line. Blank line between records */
#define MODE_Column 1 /* One record per line in neat columns */
#define MODE_List 2 /* One record per line with a separator */
#define MODE_Html 3 /* Generate an XHTML table */
#define MODE_Insert 4 /* Generate SQL "insert" statements */
#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */
#define MODE_Html 4 /* Generate an XHTML table */
#define MODE_Insert 5 /* Generate SQL "insert" statements */
/*
** Number of elements in an array
@@ -228,6 +239,13 @@ static void output_html_string(FILE *out, const char *z){
}
}
/*
** This routine runs when the user presses Ctrl-C
*/
static void interrupt_handler(int NotUsed){
if( db ) sqlite_interrupt(db);
}
/*
** This is the callback routine that the SQLite library
** invokes for each row of a query result.
@@ -237,9 +255,14 @@ static int callback(void *pArg, int nArg, char **azArg, char **azCol){
struct callback_data *p = (struct callback_data*)pArg;
switch( p->mode ){
case MODE_Line: {
int w = 5;
for(i=0; i<nArg; i++){
int len = strlen(azCol[i]);
if( len>w ) w = len;
}
if( p->cnt++>0 ) fprintf(p->out,"\n");
for(i=0; i<nArg; i++){
fprintf(p->out,"%s = %s\n", azCol[i], azArg[i] ? azArg[i] : 0);
fprintf(p->out,"%*s = %s\n", w, azCol[i], azArg[i] ? azArg[i] : 0);
}
break;
}
@@ -253,9 +276,9 @@ static int callback(void *pArg, int nArg, char **azArg, char **azCol){
w = 0;
}
if( w<=0 ){
w = strlen(azCol[i]);
w = strlen(azCol[i] ? azCol[i] : "");
if( w<10 ) w = 10;
n = strlen(azArg[i]);
n = strlen(azArg[i] ? azArg[i] : "");
if( w<n ) w = n;
}
if( i<ArraySize(p->actualWidth) ){
@@ -291,6 +314,7 @@ static int callback(void *pArg, int nArg, char **azArg, char **azCol){
}
break;
}
case MODE_Semi:
case MODE_List: {
if( p->cnt++==0 && p->showHeader ){
for(i=0; i<nArg; i++){
@@ -312,7 +336,13 @@ static int callback(void *pArg, int nArg, char **azArg, char **azCol){
}
z += j;
}
fprintf(p->out, "%s", i==nArg-1 ? "\n" : p->separator);
if( i<nArg-1 ){
fprintf(p->out, "%s", p->separator);
}else if( p->mode==MODE_Semi ){
fprintf(p->out, ";\n");
}else{
fprintf(p->out, "\n");
}
}
break;
}
@@ -364,14 +394,15 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){
fprintf(pData->out, "%s;\n", azArg[2]);
if( strcmp(azArg[1],"table")==0 ){
struct callback_data d2;
char zSql[1000];
d2 = *pData;
d2.mode = MODE_List;
d2.escape = '\t';
strcpy(d2.separator,"\t");
fprintf(pData->out, "COPY '%s' FROM STDIN;\n", azArg[0]);
sprintf(zSql, "SELECT * FROM '%s'", azArg[0]);
sqlite_exec(pData->db, zSql, callback, &d2, 0);
sqlite_exec_printf(pData->db,
"SELECT * FROM '%q'",
callback, &d2, 0, azArg[0]
);
fprintf(pData->out, "\\.\n");
}
fprintf(pData->out, "VACUUM '%s';\n", azArg[0]);
@@ -388,8 +419,8 @@ static char zHelp[] =
".header ON|OFF Turn display of headers on or off\n"
".help Show this message\n"
".indices TABLE Show names of all indices on TABLE\n"
".mode MODE Set mode to one of \"line\", \"column\", "
"\"list\", or \"html\"\n"
".mode MODE Set mode to one of \"line\", \"column\", \n"
" \"insert\", \"list\", or \"html\"\n"
".mode insert TABLE Generate SQL insert statements for TABLE\n"
".output FILENAME Send output to FILENAME\n"
".output stdout Send output to the screen\n"
@@ -433,22 +464,24 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){
if( nArg==0 ) return;
n = strlen(azArg[0]);
c = azArg[0][0];
if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
char *zErrMsg = 0;
char zSql[1000];
if( nArg==1 ){
sprintf(zSql, "SELECT name, type, sql FROM sqlite_master "
"WHERE type!='meta' "
"ORDER BY tbl_name, type DESC, name");
sqlite_exec(db, zSql, dump_callback, p, &zErrMsg);
sqlite_exec(db,
"SELECT name, type, sql FROM sqlite_master "
"WHERE type!='meta' "
"ORDER BY tbl_name, type DESC, name",
dump_callback, p, &zErrMsg
);
}else{
int i;
for(i=1; i<nArg && zErrMsg==0; i++){
sprintf(zSql, "SELECT name, type, sql FROM sqlite_master "
"WHERE tbl_name LIKE '%.800s' AND type!='meta' "
"ORDER BY type DESC, name", azArg[i]);
sqlite_exec(db, zSql, dump_callback, p, &zErrMsg);
sqlite_exec_printf(db,
"SELECT name, type, sql FROM sqlite_master "
"WHERE tbl_name LIKE '%q' AND type!='meta' "
"ORDER BY type DESC, name",
dump_callback, p, &zErrMsg, azArg[i]
);
}
}
@@ -494,14 +527,15 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){
if( c=='i' && strncmp(azArg[0], "indices", n)==0 && nArg>1 ){
struct callback_data data;
char *zErrMsg = 0;
char zSql[1000];
memcpy(&data, p, sizeof(data));
data.showHeader = 0;
data.mode = MODE_List;
sprintf(zSql, "SELECT name FROM sqlite_master "
"WHERE type='index' AND tbl_name LIKE '%.800s' "
"ORDER BY name", azArg[1]);
sqlite_exec(db, zSql, callback, &data, &zErrMsg);
sqlite_exec_printf(db,
"SELECT name FROM sqlite_master "
"WHERE type='index' AND tbl_name LIKE '%q' "
"ORDER BY name",
callback, &data, &zErrMsg, azArg[1]
);
if( zErrMsg ){
fprintf(stderr,"Error: %s\n", zErrMsg);
free(zErrMsg);
@@ -546,21 +580,37 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){
if( c=='s' && strncmp(azArg[0], "schema", n)==0 ){
struct callback_data data;
char *zErrMsg = 0;
char zSql[1000];
memcpy(&data, p, sizeof(data));
data.showHeader = 0;
data.mode = MODE_List;
data.mode = MODE_Semi;
if( nArg>1 ){
sprintf(zSql, "SELECT sql FROM sqlite_master "
"WHERE tbl_name LIKE '%.800s' AND type!='meta'"
"ORDER BY type DESC, name",
azArg[1]);
if( sqliteStrICmp(azArg[1],"sqlite_master")==0 ){
char *new_argv[2], *new_colv[2];
new_argv[0] = "CREATE TABLE sqlite_master (\n"
" type text,\n"
" name text,\n"
" tbl_name text,\n"
" sql text\n"
")";
new_argv[1] = 0;
new_colv[0] = "sql";
new_colv[1] = 0;
callback(&data, 1, new_argv, new_colv);
}else{
sqlite_exec_printf(db,
"SELECT sql FROM sqlite_master "
"WHERE tbl_name LIKE '%q' AND type!='meta'"
"ORDER BY type DESC, name",
callback, &data, &zErrMsg, azArg[1]);
}
}else{
sprintf(zSql, "SELECT sql FROM sqlite_master "
sqlite_exec(db,
"SELECT sql FROM sqlite_master "
"WHERE type!='meta' "
"ORDER BY tbl_name, type DESC, name");
"ORDER BY tbl_name, type DESC, name",
callback, &data, &zErrMsg
);
}
sqlite_exec(db, zSql, callback, &data, &zErrMsg);
if( zErrMsg ){
fprintf(stderr,"Error: %s\n", zErrMsg);
free(zErrMsg);
@@ -572,28 +622,49 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){
}else
if( c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0 ){
struct callback_data data;
char *zErrMsg = 0;
char zSql[1000];
memcpy(&data, p, sizeof(data));
data.showHeader = 0;
data.mode = MODE_List;
char **azResult;
int nRow, rc;
char *zErrMsg;
if( nArg==1 ){
sprintf(zSql,
rc = sqlite_get_table(db,
"SELECT name FROM sqlite_master "
"WHERE type='table' "
"ORDER BY name");
"ORDER BY name",
&azResult, &nRow, 0, &zErrMsg
);
}else{
sprintf(zSql,
rc = sqlite_get_table_printf(db,
"SELECT name FROM sqlite_master "
"WHERE type='table' AND name LIKE '%%%.100s%%' "
"ORDER BY name", azArg[1]);
"WHERE type='table' AND name LIKE '%%%q%%' "
"ORDER BY name",
&azResult, &nRow, 0, &zErrMsg, azArg[1]
);
}
sqlite_exec(db, zSql, callback, &data, &zErrMsg);
if( zErrMsg ){
fprintf(stderr,"Error: %s\n", zErrMsg);
free(zErrMsg);
}
if( rc==SQLITE_OK ){
int len, maxlen = 0;
int i, j;
int nPrintCol, nPrintRow;
for(i=1; i<=nRow; i++){
if( azResult[i]==0 ) continue;
len = strlen(azResult[i]);
if( len>maxlen ) maxlen = len;
}
nPrintCol = 80/(maxlen+2);
if( nPrintCol<1 ) nPrintCol = 1;
nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
for(i=0; i<nPrintRow; i++){
for(j=i+1; j<=nRow; j+=nPrintRow){
char *zSp = j<=nPrintRow ? "" : " ";
printf("%s%-*s", zSp, maxlen, azResult[j] ? azResult[j] : "");
}
printf("\n");
}
}
sqlite_free_table(azResult);
}else
if( c=='t' && n>1 && strncmp(azArg[0], "timeout", n)==0 && nArg>=2 ){
@@ -614,15 +685,18 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){
}
int main(int argc, char **argv){
sqlite *db;
char *zErrMsg = 0;
char *argv0 = argv[0];
struct callback_data data;
int echo = 0;
memset(&data, 0, sizeof(data));
data.mode = MODE_List;
strcpy(data.separator,"|");
data.showHeader = 0;
#ifdef SIGINT
signal(SIGINT, interrupt_handler);
#endif
while( argc>=2 && argv[1][0]=='-' ){
if( strcmp(argv[1],"-html")==0 ){
data.mode = MODE_Html;
@@ -648,6 +722,10 @@ int main(int argc, char **argv){
data.showHeader = 0;
argc--;
argv++;
}else if( strcmp(argv[1],"-echo")==0 ){
echo = 1;
argc--;
argv++;
}else{
fprintf(stderr,"%s: unknown option: %s\n", argv0, argv[1]);
return 1;
@@ -668,7 +746,7 @@ int main(int argc, char **argv){
}
exit(1);
}else{
printf("Database \"%s\" opened READ ONLY!\n", argv[1]);
fprintf(stderr,"Database \"%s\" opened READ ONLY!\n", argv[1]);
}
}
data.out = stdout;
@@ -684,19 +762,26 @@ int main(int argc, char **argv){
int istty = isatty(0);
if( istty ){
printf(
"Enter \".help\" for instructions\n"
"SQLite version %s\n"
"Enter \".help\" for instructions\n",
sqlite_version
);
}
while( (zLine = one_input_line(zSql, istty))!=0 ){
if( echo ) printf("%s\n", zLine);
if( zLine && zLine[0]=='.' ){
do_meta_command(zLine, db, &data);
free(zLine);
continue;
}
if( zSql==0 ){
nSql = strlen(zLine);
zSql = malloc( nSql+1 );
strcpy(zSql, zLine);
int i;
for(i=0; zLine[i] && isspace(zLine[i]); i++){}
if( zLine[i]!=0 ){
nSql = strlen(zLine);
zSql = malloc( nSql+1 );
strcpy(zSql, zLine);
}
}else{
int len = strlen(zLine);
zSql = realloc( zSql, nSql + len + 2 );
@@ -709,10 +794,11 @@ int main(int argc, char **argv){
nSql += len;
}
free(zLine);
if( sqlite_complete(zSql) ){
if( zSql && sqlite_complete(zSql) ){
data.cnt = 0;
if( sqlite_exec(db, zSql, callback, &data, &zErrMsg)!=0
&& zErrMsg!=0 ){
if( !istty && !echo ) printf("%s\n",zSql);
printf("SQL error: %s\n", zErrMsg);
free(zErrMsg);
zErrMsg = 0;
+157 -7
View File
@@ -24,16 +24,23 @@
** This header file defines the interface that the sqlite library
** presents to client programs.
**
** @(#) $Id: sqlite.h.in,v 1.1 2000/08/17 10:22:34 drh Exp $
** @(#) $Id: sqlite.h.in,v 1.9 2001/01/20 19:52:49 drh Exp $
*/
#ifndef _SQLITE_H_
#define _SQLITE_H_
#include <stdarg.h> /* Needed for the definition of va_list */
/*
** The version of the SQLite library
** The version of the SQLite library.
*/
#define SQLITE_VERSION --VERS--
#define SQLITE_VERSION_STRING "--VERS--"
#define SQLITE_VERSION "--VERS--"
/*
** The version string is also compiled into the library so that a program
** can check to make sure that the lib*.a file and the *.h file are from
** the same version.
*/
extern const char sqlite_version[];
/*
** Each open sqlite database is represented by an instance of the
@@ -120,16 +127,30 @@ int sqlite_exec(
);
/*
** Return values fro sqlite_exec()
** Return values for sqlite_exec()
*/
#define SQLITE_OK 0 /* Successful result */
#define SQLITE_INTERNAL 1 /* An internal logic error in SQLite */
#define SQLITE_ERROR 2 /* SQL error or missing database */
#define SQLITE_ERROR 1 /* SQL error or missing database */
#define SQLITE_INTERNAL 2 /* An internal logic error in SQLite */
#define SQLITE_PERM 3 /* Access permission denied */
#define SQLITE_ABORT 4 /* Callback routine requested an abort */
#define SQLITE_BUSY 5 /* One or more database files are locked */
#define SQLITE_NOMEM 6 /* A malloc() failed */
#define SQLITE_READONLY 7 /* Attempt to write a readonly database */
#define SQLITE_INTERRUPT 8 /* Operation terminated by sqlite_interrupt() */
#define SQLITE_IOERR 9 /* Disk full or other I/O error */
#define SQLITE_CORRUPT 10 /* The database disk image is malformed */
#define SQLITE_NOTFOUND 11 /* Table or record not found */
#define SQLITE_FULL 12 /* Insertion failed because database is full */
/* This function causes any pending database operation to abort and
** return at its earliest opportunity. This routine is typically
** called in response to a user action such as pressing "Cancel"
** or Ctrl-C where the user wants a long query operation to halt
** immediately.
*/
void sqlite_interrupt(sqlite*);
/* This function returns true if the given input string comprises
** one or more complete SQL statements.
@@ -176,4 +197,133 @@ void sqlite_busy_handler(sqlite*, int(*)(void*,const char*,int), void*);
*/
void sqlite_busy_timeout(sqlite*, int ms);
/*
** This next routine is really just a wrapper around sqlite_exec().
** Instead of invoking a user-supplied callback for each row of the
** result, this routine remembers each row of the result in memory
** obtained from malloc(), then returns all of the result after the
** query has finished.
**
** As an example, suppose the query result where this table:
**
** Name | Age
** -----------------------
** Alice | 43
** Bob | 28
** Cindy | 21
**
** If the 3rd argument were &azResult then after the function returns
** azResult will contain the following data:
**
** azResult[0] = "Name";
** azResult[1] = "Age";
** azResult[2] = "Alice";
** azResult[3] = "43";
** azResult[4] = "Bob";
** azResult[5] = "28";
** azResult[6] = "Cindy";
** azResult[7] = "21";
**
** Notice that there is an extra row of data containing the column
** headers. But the *nrow return value is still 3. *ncolumn is
** set to 2. In general, the number of values inserted into azResult
** will be ((*nrow) + 1)*(*ncolumn).
**
** After the calling function has finished using the result, it should
** pass the result data pointer to sqlite_free_table() in order to
** release the memory that was malloc-ed. Because of the way the
** malloc() happens, the calling function must not try to call
** malloc() directly. Only sqlite_free_table() is able to release
** the memory properly and safely.
**
** The return value of this routine is the same as from sqlite_exec().
*/
int sqlite_get_table(
sqlite*, /* An open database */
char *sql, /* SQL to be executed */
char ***resultp, /* Result written to a char *[] that this points to */
int *nrow, /* Number of result rows written here */
int *ncolumn, /* Number of result columns written here */
char **errmsg /* Error msg written here */
);
/*
** Call this routine to free the memory that sqlite_get_table() allocated.
*/
void sqlite_free_table(char **result);
/*
** The following routines are wrappers around sqlite_exec() and
** sqlite_get_table(). The only difference between the routines that
** follow and the originals is that the second argument to the
** routines that follow is really a printf()-style format
** string describing the SQL to be executed. Arguments to the format
** string appear at the end of the argument list.
**
** All of the usual printf formatting options apply. In addition, there
** is a "%q" option. %q works like %s in that it substitutes a null-terminated
** string from the argument list. But %q also doubles every '\'' character.
** %q is designed for use inside a string literal. By doubling each '\''
** character it escapes that character and allows it to be inserted into
** the string.
**
** For example, so some string variable contains text as follows:
**
** char *zText = "It's a happy day!";
**
** We can use this text in an SQL statement as follows:
**
** sqlite_exec_printf(db, "INSERT INTO table VALUES('%q')",
** callback1, 0, 0, zText);
**
** Because the %q format string is used, the '\'' character in zText
** is escaped and the SQL generated is as follows:
**
** INSERT INTO table1 VALUES('It''s a happy day!')
**
** This is correct. Had we used %s instead of %q, the generated SQL
** would have looked like this:
**
** INSERT INTO table1 VALUES('It's a happy day!');
**
** This second example is an SQL syntax error. As a general rule you
** should always use %q instead of %s when inserting text into a string
** literal.
*/
int sqlite_exec_printf(
sqlite*, /* An open database */
char *sqlFormat, /* printf-style format string for the SQL */
sqlite_callback, /* Callback function */
void *, /* 1st argument to callback function */
char **errmsg, /* Error msg written here */
... /* Arguments to the format string. */
);
int sqlite_exec_vprintf(
sqlite*, /* An open database */
char *sqlFormat, /* printf-style format string for the SQL */
sqlite_callback, /* Callback function */
void *, /* 1st argument to callback function */
char **errmsg, /* Error msg written here */
va_list ap /* Arguments to the format string. */
);
int sqlite_get_table_printf(
sqlite*, /* An open database */
char *sqlFormat, /* printf-style format string for the SQL */
char ***resultp, /* Result written to a char *[] that this points to */
int *nrow, /* Number of result rows written here */
int *ncolumn, /* Number of result columns written here */
char **errmsg, /* Error msg written here */
... /* Arguments to the format string */
);
int sqlite_get_table_vprintf(
sqlite*, /* An open database */
char *sqlFormat, /* printf-style format string for the SQL */
char ***resultp, /* Result written to a char *[] that this points to */
int *nrow, /* Number of result rows written here */
int *ncolumn, /* Number of result columns written here */
char **errmsg, /* Error msg written here */
va_list ap /* Arguments to the format string */
);
#endif /* _SQLITE_H_ */
+28 -11
View File
@@ -23,7 +23,7 @@
*************************************************************************
** Internal interface definitions for SQLite.
**
** @(#) $Id: sqliteInt.h,v 1.29 2000/08/02 13:47:42 drh Exp $
** @(#) $Id: sqliteInt.h,v 1.36 2001/01/20 19:52:50 drh Exp $
*/
#include "sqlite.h"
#include "dbbe.h"
@@ -35,6 +35,11 @@
#include <string.h>
#include <assert.h>
/*
** The paging system deals with 32-bit integers.
*/
typedef unsigned int u32;
/*
** If memory allocation problems are found, recompile with
**
@@ -62,17 +67,18 @@
/*
** The following global variables are used for testing and debugging
** only. Thy only work if MEMORY_DEBUG is defined.
** only. They only work if MEMORY_DEBUG is defined.
*/
#ifdef MEMORY_DEBUG
int sqlite_nMalloc; /* Number of sqliteMalloc() calls */
int sqlite_nFree; /* Number of sqliteFree() calls */
int sqlite_iMallocFail; /* Fail sqliteMalloc() after this many calls */
extern int sqlite_nMalloc; /* Number of sqliteMalloc() calls */
extern int sqlite_nFree; /* Number of sqliteFree() calls */
extern int sqlite_iMallocFail; /* Fail sqliteMalloc() after this many calls */
#endif
/*
** The number of entries in the in-memory hash array holding the
** database schema.
** database schema. (Collision resolution is by chaining, so the
** table will hold more than this many entries.)
*/
#define N_HASH 51
@@ -99,6 +105,8 @@ int sqlite_iMallocFail; /* Fail sqliteMalloc() after this many calls */
#define FN_Sum 4
#define FN_Avg 5
#define FN_Fcnt 6
#define FN_Length 7
#define FN_Substr 8
/*
** Forward references to structures
@@ -121,7 +129,7 @@ typedef struct AggExpr AggExpr;
*/
struct sqlite {
Dbbe *pBe; /* The backend driver */
int flags; /* Miscellanous flags */
int flags; /* Miscellanous flags. See below */
int file_format; /* What file format version is this database? */
int nTable; /* Number of tables in the database */
void *pBusyArg; /* 1st Argument to the busy callback */
@@ -133,8 +141,9 @@ struct sqlite {
/*
** Possible values for the sqlite.flags.
*/
#define SQLITE_VdbeTrace 0x00000001
#define SQLITE_Initialized 0x00000002
#define SQLITE_VdbeTrace 0x00000001 /* True to trace VDBE execution */
#define SQLITE_Initialized 0x00000002 /* True after initialization */
#define SQLITE_Interrupt 0x00000004 /* Cancel current operation */
/*
** Current file format version
@@ -165,7 +174,7 @@ struct Table {
};
/*
** Each SQL index is represented in memory by and
** Each SQL index is represented in memory by an
** instance of the following structure.
**
** The columns of the table that are to be indexed are described
@@ -198,7 +207,7 @@ struct Index {
** this structure.
*/
struct Token {
char *z; /* Text of the token */
char *z; /* Text of the token. Not NULL-terminated! */
int n; /* Number of characters in this token */
};
@@ -323,6 +332,7 @@ struct AggExpr {
*/
struct Parse {
sqlite *db; /* The main database structure */
int rc; /* Return code from execution */
sqlite_callback xCallback; /* The callback function */
void *pArg; /* First argument to the callback function */
char *zErrMsg; /* An error message */
@@ -414,3 +424,10 @@ void sqliteExprResolveInSelect(Parse*, Expr*);
int sqliteExprAnalyzeAggregates(Parse*, Expr*);
void sqliteParseInfoReset(Parse*);
Vdbe *sqliteGetVdbe(Parse*);
int sqliteRandomByte(void);
int sqliteRandomInteger(void);
void sqliteRandomName(char*,char*);
int sqliteDbbeOpenTempFile(const char*, Dbbe*, FILE**);
void sqliteDbbeCloseTempFile(Dbbe*, FILE*);
void sqliteDbbeCloseAllTempFiles(Dbbe*);
char *sqliteDbbeNameToFile(const char*,const char*,const char*);
+185
View File
@@ -0,0 +1,185 @@
/*
** Copyright (c) 2000 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*************************************************************************
** This file contains the sqlite_get_table() and sqlite_free_table()
** interface routines. These are just wrappers around the main
** interface routine of sqlite_exec().
**
** These routines are in a separate files so that they will not be linked
** if they are not used.
*/
#include <stdlib.h>
#include "sqlite.h"
/*
** This structure is used to pass data from sqlite_get_table() through
** to the callback function is uses to build the result.
*/
typedef struct TabResult {
char **azResult;
int nResult;
int nAlloc;
int nRow;
int nColumn;
int nData;
int rc;
} TabResult;
/*
** This routine is called once for each row in the result table. Its job
** is to fill in the TabResult structure appropriately, allocating new
** memory as necessary.
*/
static int sqlite_get_table_cb(void *pArg, int nCol, char **argv, char **colv){
TabResult *p = (TabResult*)pArg;
int need;
int i;
char *z;
/* Make sure there is enough space in p->azResult to hold everything
** we need to remember from this invocation of the callback.
*/
if( p->nRow==0 ){
p->nColumn = nCol;
need = nCol*2;
}else{
need = nCol;
}
if( p->nData + need >= p->nAlloc ){
p->nAlloc = p->nAlloc*2 + need + 1;
p->azResult = realloc( p->azResult, sizeof(char*)*p->nAlloc );
if( p->azResult==0 ){
p->rc = SQLITE_NOMEM;
return 1;
}
}
/* If this is the first row, then generate an extra row containing
** the names of all columns.
*/
if( p->nRow==0 ){
for(i=0; i<nCol; i++){
if( colv[i]==0 ){
z = 0;
}else{
z = malloc( strlen(colv[i])+1 );
if( z==0 ){
p->rc = SQLITE_NOMEM;
return 1;
}
strcpy(z, colv[i]);
}
p->azResult[p->nData++] = z;
}
}
/* Copy over the row data
*/
for(i=0; i<nCol; i++){
if( argv[i]==0 ){
z = 0;
}else{
z = malloc( strlen(argv[i])+1 );
if( z==0 ){
p->rc = SQLITE_NOMEM;
return 1;
}
strcpy(z, argv[i]);
}
p->azResult[p->nData++] = z;
}
p->nRow++;
return 0;
}
/*
** Query the database. But instead of invoking a callback for each row,
** malloc() for space to hold the result and return the entire results
** at the conclusion of the call.
**
** The result that is written to ***pazResult is held in memory obtained
** from malloc(). But the caller cannot free this memory directly.
** Instead, the entire table should be passed to sqlite_free_table() when
** the calling procedure is finished using it.
*/
int sqlite_get_table(
sqlite *db, /* The database on which the SQL executes */
char *zSql, /* The SQL to be executed */
char ***pazResult, /* Write the result table here */
int *pnRow, /* Write the number of rows in the result here */
int *pnColumn, /* Write the number of columns of result here */
char **pzErrMsg /* Write error messages here */
){
int rc;
TabResult res;
if( pazResult==0 ){ return SQLITE_ERROR; }
*pazResult = 0;
if( pnColumn ) *pnColumn = 0;
if( pnRow ) *pnRow = 0;
res.nResult = 0;
res.nRow = 0;
res.nColumn = 0;
res.nData = 1;
res.nAlloc = 200;
res.rc = SQLITE_OK;
res.azResult = malloc( sizeof(char*)*res.nAlloc );
if( res.azResult==0 ){
return SQLITE_NOMEM;
}
res.azResult[0] = 0;
rc = sqlite_exec(db, zSql, sqlite_get_table_cb, &res, pzErrMsg);
if( res.azResult ){
res.azResult[0] = (char*)res.nData;
}
if( rc==SQLITE_ABORT ){
sqlite_free_table(&res.azResult[1]);
return res.rc;
}
if( rc!=SQLITE_OK ){
sqlite_free_table(&res.azResult[1]);
return rc;
}
if( res.nAlloc>res.nData ){
res.azResult = realloc( res.azResult, sizeof(char*)*(res.nData+1) );
if( res.azResult==0 ) return SQLITE_NOMEM;
}
*pazResult = &res.azResult[1];
if( pnColumn ) *pnColumn = res.nColumn;
if( pnRow ) *pnRow = res.nRow;
return rc;
}
/*
** This routine frees the space the sqlite_get_table() malloced.
*/
void sqlite_free_table(
char **azResult /* Result returned from from sqlite_get_table() */
){
if( azResult ){
int i, n;
azResult--;
n = (int)azResult[0];
for(i=1; i<n; i++){ if( azResult[i] ) free(azResult[i]); }
free(azResult);
}
}
+111 -63
View File
@@ -23,10 +23,12 @@
*************************************************************************
** A TCL Interface to SQLite
**
** $Id: tclsqlite.c,v 1.8 2000/08/17 09:50:00 drh Exp $
** $Id: tclsqlite.c,v 1.13 2001/01/31 13:28:09 drh Exp $
*/
#ifndef NO_TCL /* Omit this whole file if TCL is unavailable */
#include "sqlite.h"
#include <tcl.h>
#include "tcl.h"
#include <stdlib.h>
#include <string.h>
@@ -38,7 +40,7 @@ typedef struct SqliteDb SqliteDb;
struct SqliteDb {
sqlite *db; /* The "real" database structure */
Tcl_Interp *interp; /* The interpreter used for this database */
char *zBusy; /* The name of the busy callback routine */
char *zBusy; /* The busy callback routine */
};
/*
@@ -49,7 +51,7 @@ typedef struct CallbackData CallbackData;
struct CallbackData {
Tcl_Interp *interp; /* The TCL interpreter */
char *zArray; /* The array into which data is written */
char *zCode; /* The code to execute for each row */
Tcl_Obj *pCode; /* The code to execute for each row */
int once; /* Set only for the first invocation of callback */
};
@@ -66,6 +68,7 @@ static int DbEvalCallback(
int i, rc;
if( cbData->zArray[0] ){
if( cbData->once ){
Tcl_SetVar2(cbData->interp, cbData->zArray, "*", "", 0);
for(i=0; i<nCol; i++){
Tcl_SetVar2(cbData->interp, cbData->zArray, "*", azN[i],
TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
@@ -84,8 +87,34 @@ static int DbEvalCallback(
}
}
cbData->once = 0;
rc = Tcl_Eval(cbData->interp, cbData->zCode);
return rc;
rc = Tcl_EvalObj(cbData->interp, cbData->pCode);
return rc!=TCL_OK && rc!=TCL_CONTINUE;
}
/*
** This is an alternative callback for database queries. Instead
** of invoking a TCL script to handle the result, this callback just
** appends each column of the result to a list. After the query
** is complete, the list is returned.
*/
static int DbEvalCallback2(
void *clientData, /* An instance of CallbackData */
int nCol, /* Number of columns in the result */
char ** azCol, /* Data for each column */
char ** azN /* Name for each column */
){
Tcl_Obj *pList = (Tcl_Obj*)clientData;
int i;
for(i=0; i<nCol; i++){
Tcl_Obj *pElem;
if( azCol[i] && *azCol[i] ){
pElem = Tcl_NewStringObj(azCol[i], -1);
}else{
pElem = Tcl_NewObj();
}
Tcl_ListObjAppendElement(0, pList, pElem);
}
return 0;
}
/*
@@ -109,7 +138,6 @@ static int DbBusyHandler(void *cd, const char *zTable, int nTries){
int rc;
char zVal[30];
char *zCmd;
char *zResult;
Tcl_DString cmd;
Tcl_DStringInit(&cmd);
@@ -139,58 +167,70 @@ static int DbBusyHandler(void *cd, const char *zTable, int nTries){
** and calls that connection "db1". The second command causes this
** subroutine to be invoked.
*/
static int DbCmd(void *cd, Tcl_Interp *interp, int argc, char **argv){
char *z;
int n, c;
static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
SqliteDb *pDb = (SqliteDb*)cd;
if( argc<2 ){
Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
" SUBCOMMAND ...\"", 0);
int choice;
static char *DB_optStrs[] = {
"busy", "close", "complete", "eval", "timeout"
};
enum DB_opts {
DB_BUSY, DB_CLOSE, DB_COMPLETE, DB_EVAL, DB_TIMEOUT
};
if( objc<2 ){
Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
return TCL_ERROR;
}
z = argv[1];
n = strlen(z);
c = z[0];
if( Tcl_GetIndexFromObj(interp, objv[1], DB_optStrs, "option", 0, &choice) ){
return TCL_ERROR;
}
switch( (enum DB_opts)choice ){
/* $db busy ?CALLBACK?
**
** Invoke the given callback if an SQL statement attempts to open
** a locked database file.
*/
if( c=='b' && strncmp(z,"busy",n)==0 ){
if( argc>3 ){
Tcl_AppendResult(interp,"wrong # args: should be \"",
argv[0], " busy ?CALLBACK?", 0);
case DB_BUSY: {
if( objc>3 ){
Tcl_WrongNumArgs(interp, 2, objv, "CALLBACK");
return TCL_ERROR;
}else if( argc==2 ){
}else if( objc==2 ){
if( pDb->zBusy ){
Tcl_AppendResult(interp, pDb->zBusy, 0);
}
}else{
char *zBusy;
int len;
if( pDb->zBusy ){
Tcl_Free(pDb->zBusy);
pDb->zBusy = 0;
}
if( argv[2][0] ){
pDb->zBusy = Tcl_Alloc( strlen(argv[2]) + 1 );
if( pDb->zBusy ){
strcpy(pDb->zBusy, argv[2]);
}
zBusy = Tcl_GetStringFromObj(objv[2], &len);
if( zBusy && len>0 ){
pDb->zBusy = Tcl_Alloc( len + 1 );
strcpy(pDb->zBusy, zBusy);
}else{
pDb->zBusy = 0;
}
if( pDb->zBusy ){
pDb->interp = interp;
sqlite_busy_handler(pDb->db, DbBusyHandler, pDb);
}else{
sqlite_busy_handler(pDb->db, 0, 0);
}
}
}else
break;
}
/* $db close
**
** Shutdown the database
*/
if( c=='c' && n>=2 && strncmp(z,"close",n)==0 ){
Tcl_DeleteCommand(interp, argv[0]);
}else
case DB_CLOSE: {
Tcl_DeleteCommand(interp, Tcl_GetStringFromObj(objv[0], 0));
break;
}
/* $db complete SQL
**
@@ -198,16 +238,18 @@ static int DbCmd(void *cd, Tcl_Interp *interp, int argc, char **argv){
** additional lines of input are needed. This is similar to the
** built-in "info complete" command of Tcl.
*/
if( c=='c' && n>=2 && strncmp(z,"complete",n)==0 ){
char *zRes;
if( argc!=3 ){
Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
" complete SQL\"", 0);
case DB_COMPLETE: {
Tcl_Obj *pResult;
int isComplete;
if( objc!=3 ){
Tcl_WrongNumArgs(interp, 2, objv, "SQL");
return TCL_ERROR;
}
zRes = sqlite_complete(argv[2]) ? "1" : "0";
Tcl_SetResult(interp, zRes, TCL_VOLATILE);
}else
isComplete = sqlite_complete( Tcl_GetStringFromObj(objv[2], 0) );
pResult = Tcl_GetObjResult(interp);
Tcl_SetBooleanObj(pResult, isComplete);
break;
}
/*
** $db eval $sql ?array { ...code... }?
@@ -218,57 +260,59 @@ static int DbCmd(void *cd, Tcl_Interp *interp, int argc, char **argv){
** If "array" is an empty string, then the values are placed in variables
** that have the same name as the fields extracted by the query.
*/
if( c=='e' && strncmp(z,"eval",n)==0 ){
case DB_EVAL: {
CallbackData cbData;
char *zErrMsg;
char *zSql;
int rc;
if( argc!=5 && argc!=3 ){
Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
" eval SQL ?ARRAY-NAME CODE?", 0);
if( objc!=5 && objc!=3 ){
Tcl_WrongNumArgs(interp, 2, objv, "SQL ?ARRAY-NAME CODE?");
return TCL_ERROR;
}
pDb->interp = interp;
if( argc==5 ){
zSql = Tcl_GetStringFromObj(objv[2], 0);
Tcl_IncrRefCount(objv[2]);
if( objc==5 ){
cbData.interp = interp;
cbData.once = 1;
cbData.zArray = argv[3];
cbData.zCode = argv[4];
cbData.zArray = Tcl_GetStringFromObj(objv[3], 0);
cbData.pCode = objv[4];
zErrMsg = 0;
rc = sqlite_exec(pDb->db, argv[2], DbEvalCallback, &cbData, &zErrMsg);
Tcl_IncrRefCount(objv[3]);
Tcl_IncrRefCount(objv[4]);
rc = sqlite_exec(pDb->db, zSql, DbEvalCallback, &cbData, &zErrMsg);
Tcl_DecrRefCount(objv[4]);
Tcl_DecrRefCount(objv[3]);
}else{
rc = sqlite_exec(pDb->db, argv[2], 0, 0, &zErrMsg);
Tcl_Obj *pList = Tcl_NewObj();
rc = sqlite_exec(pDb->db, zSql, DbEvalCallback2, pList, &zErrMsg);
Tcl_SetObjResult(interp, pList);
}
if( zErrMsg ){
Tcl_SetResult(interp, zErrMsg, TCL_VOLATILE);
free(zErrMsg);
}
Tcl_DecrRefCount(objv[2]);
return rc;
}else
}
/*
** $db timeout MILLESECONDS
**
** Delay for the number of milliseconds specified when a file is locked.
*/
if( c=='t' && strncmp(z,"timeout",n)==0 ){
case DB_TIMEOUT: {
int ms;
if( argc!=3 ){
Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
" timeout MILLISECONDS", 0);
if( objc!=3 ){
Tcl_WrongNumArgs(interp, 2, objv, "MILLISECONDS");
return TCL_ERROR;
}
if( Tcl_GetInt(interp, argv[2], &ms) ) return TCL_ERROR;
if( Tcl_GetIntFromObj(interp, objv[2], &ms) ) return TCL_ERROR;
sqlite_busy_timeout(pDb->db, ms);
}else
/* The default
*/
{
Tcl_AppendResult(interp,"unknown subcommand \"", z,
"\" - should be one of: close complete eval", 0);
return TCL_ERROR;
break;
}
} /* End of the SWITCH statement */
return TCL_OK;
}
@@ -314,7 +358,7 @@ static int DbMain(void *cd, Tcl_Interp *interp, int argc, char **argv){
free(zErrMsg);
return TCL_ERROR;
}
Tcl_CreateCommand(interp, argv[1], DbCmd, (char*)p, DbDeleteCmd);
Tcl_CreateObjCommand(interp, argv[1], DbObjCmd, (char*)p, DbDeleteCmd);
return TCL_OK;
}
@@ -336,6 +380,7 @@ int Sqlite_SafeInit(Tcl_Interp *interp){
return TCL_OK;
}
#if 0
/*
** If compiled using mktclapp, this routine runs to initialize
** everything.
@@ -343,6 +388,7 @@ int Sqlite_SafeInit(Tcl_Interp *interp){
int Et_AppInit(Tcl_Interp *interp){
return Sqlite_Init(interp);
}
#endif
/*
** If the macro TCLSH is defined and is one, then put in code for the
@@ -397,3 +443,5 @@ int TCLSH_MAIN(int argc, char **argv){
return 0;
}
#endif /* TCLSH */
#endif /* !defined(NO_TCL) */
+1
View File
@@ -0,0 +1 @@
hi
+18 -5
View File
@@ -27,7 +27,7 @@
** individual tokens and sends those tokens one-by-one over to the
** parser for analysis.
**
** $Id: tokenize.c,v 1.13 2000/08/09 17:17:25 drh Exp $
** $Id: tokenize.c,v 1.17 2001/02/11 16:56:24 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
@@ -102,7 +102,7 @@ static Keyword aKeywordTable[] = {
/*
** This is the hash table
*/
#define KEY_HASH_SIZE 37
#define KEY_HASH_SIZE 69
static Keyword *apHashTable[KEY_HASH_SIZE];
@@ -298,7 +298,7 @@ int sqliteGetToken(const char *z, int *tokenType){
/*
** Run the parser on the given SQL string. The parser structure is
** passed in. Return the number of errors.
** passed in. An SQLITE_ status code.
*/
int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
int nErr = 0;
@@ -308,9 +308,11 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
static FILE *trace = 0;
extern void *sqliteParserAlloc(void*(*)(int));
extern void sqliteParserFree(void*, void(*)(void*));
extern int sqliteParser(void*, int, ...);
extern int sqliteParser(void*, int, Token, Parse*);
extern void sqliteParserTrace(FILE*, char *);
pParse->db->flags &= ~SQLITE_Interrupt;
pParse->rc = SQLITE_OK;
i = 0;
sqliteParseInfoReset(pParse);
pEngine = sqliteParserAlloc((void*(*)(int))malloc);
@@ -318,10 +320,17 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
sqliteSetString(pzErrMsg, "out of memory", 0);
return 1;
}
#ifndef NDEBUG
sqliteParserTrace(trace, "parser: ");
#endif
while( nErr==0 && i>=0 && zSql[i]!=0 ){
int tokenType;
if( (pParse->db->flags & SQLITE_Interrupt)!=0 ){
pParse->rc = SQLITE_INTERRUPT;
sqliteSetString(pzErrMsg, "interrupt", 0);
break;
}
pParse->sLastToken.z = &zSql[i];
pParse->sLastToken.n = sqliteGetToken(&zSql[i], &tokenType);
i += pParse->sLastToken.n;
@@ -391,7 +400,7 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
break;
}
}
if( nErr==0 ){
if( nErr==0 && (pParse->db->flags & SQLITE_Interrupt)==0 ){
sqliteParser(pEngine, 0, pParse->sLastToken, pParse);
if( pParse->zErrMsg && pParse->sErrToken.z ){
sqliteSetNString(pzErrMsg, "near \"", -1,
@@ -423,5 +432,9 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
pParse->pNewTable = 0;
}
sqliteParseInfoReset(pParse);
sqliteStrRealloc(pzErrMsg);
if( nErr>0 && pParse->rc==SQLITE_OK ){
pParse->rc = SQLITE_ERROR;
}
return nErr;
}
+3 -3
View File
@@ -24,7 +24,7 @@
** This file contains C code routines that are called by the parser
** to handle UPDATE statements.
**
** $Id: update.c,v 1.8 2000/06/21 13:59:12 drh Exp $
** $Id: update.c,v 1.9 2001/01/15 22:51:11 drh Exp $
*/
#include "sqliteInt.h"
@@ -159,9 +159,9 @@ void sqliteUpdate(
*/
sqliteVdbeAddOp(v, OP_ListRewind, 0, 0, 0, 0);
base = pParse->nTab;
sqliteVdbeAddOp(v, OP_Open, base, 1, pTab->zName, 0);
sqliteVdbeAddOp(v, OP_OpenTbl, base, 1, pTab->zName, 0);
for(i=0; i<nIdx; i++){
sqliteVdbeAddOp(v, OP_Open, base+i+1, 1, apIdx[i]->zName, 0);
sqliteVdbeAddOp(v, OP_OpenIdx, base+i+1, 1, apIdx[i]->zName, 0);
}
/* Loop over every record that needs updating. We have to load
+97 -9
View File
@@ -26,14 +26,26 @@
** This file contains functions for allocating memory, comparing
** strings, and stuff like that.
**
** $Id: util.c,v 1.14 2000/07/31 11:57:37 drh Exp $
** $Id: util.c,v 1.17 2000/12/10 18:23:51 drh Exp $
*/
#include "sqliteInt.h"
#include <stdarg.h>
#include <ctype.h>
/*
** If MEMORY_DEBUG is defined, then use versions of malloc() and
** free() that track memory usage and check for buffer overruns.
*/
#ifdef MEMORY_DEBUG
/*
** For keeping track of the number of mallocs and frees. This
** is used to check for memory leaks.
*/
int sqlite_nMalloc; /* Number of sqliteMalloc() calls */
int sqlite_nFree; /* Number of sqliteFree() calls */
int sqlite_iMallocFail; /* Fail sqliteMalloc() after this many calls */
/*
** Allocate new memory and set it to zero. Return NULL if
@@ -164,9 +176,14 @@ char *sqliteStrNDup_(const char *z, int n, char *zFile, int line){
}
return zNew;
}
#endif /* MEMORY_DEBUG */
/*
** The following versions of malloc() and free() are for use in a
** normal build.
*/
#if !defined(MEMORY_DEBUG)
#else /* !defined(MEMORY_DEBUG) */
/*
** Allocate new memory and set it to zero. Return NULL if
** no memory is available.
@@ -219,7 +236,7 @@ char *sqliteStrNDup(const char *z, int n){
}
return zNew;
}
#endif /* MEMORY_DEBUG */
#endif /* !defined(MEMORY_DEBUG) */
/*
** Create a string from the 2nd and subsequent arguments (up to the
@@ -353,7 +370,7 @@ int sqliteHashNoCase(const char *z, int n){
int c;
if( n<=0 ) n = strlen(z);
while( n-- > 0 && (c = *z++)!=0 ){
h = h<<3 ^ h ^ UpperToLower[c];
h = (h<<3) ^ h ^ UpperToLower[c];
}
if( h<0 ) h = -h;
return h;
@@ -550,6 +567,41 @@ static int privateStrCmp(const char *atext, const char *btext, int useCase){
return result;
}
/*
** Do a comparison of pure numerics. If either string is not a pure
** numeric, then return 0. Otherwise return 1 and set *pResult to be
** negative, zero or positive if the first string are numerially less than
** equal to, or greater than the second.
*/
static int privateCompareNum(const char *a, const char *b, int *pResult){
char *endPtr;
double rA, rB;
int isNumA, isNumB;
if( isdigit(*a) || ((*a=='-' || *a=='+') && isdigit(a[1])) ){
rA = strtod(a, &endPtr);
isNumA = *endPtr==0;
}else{
isNumA = 0;
}
if( isdigit(*b) || ((*b=='-' || *b=='+') && isdigit(b[1])) ){
rB = strtod(b, &endPtr);
isNumB = *endPtr==0;
}else{
isNumB = 0;
}
if( isNumB==0 && isNumA==0 ) return 0;
if( isNumA!=isNumB ){
*pResult = isNumA - isNumB;
}else if( rA<rB ){
*pResult = -1;
}else if( rA>rB ){
*pResult = 1;
}else{
*pResult = 0;
}
return 1;
}
/* This comparison routine is what we use for comparison operations
** in an SQL expression. (Ex: name<'Hello' or value<5). Compare two
** strings. Use case only as a tie-breaker. Numbers compare in
@@ -557,8 +609,10 @@ static int privateStrCmp(const char *atext, const char *btext, int useCase){
*/
int sqliteCompare(const char *atext, const char *btext){
int result;
result = privateStrCmp(atext, btext, 0);
if( result==0 ) result = privateStrCmp(atext, btext, 1);
if( !privateCompareNum(atext, btext, &result) || result==0 ){
result = privateStrCmp(atext, btext, 0);
if( result==0 ) result = privateStrCmp(atext, btext, 1);
}
return result;
}
@@ -573,12 +627,13 @@ int sortCmp(const char **a, const char **b){
return sqliteCompare(*a, *b);
}
int main(int argc, char **argv){
int i, j, k, n;
int i, j, k, n, cnt;
static char *azStr[] = {
"abc", "aBc", "abcd", "aBcd",
"123", "124", "1234", "-123", "-124", "-1234",
"123", "124", "1234", "-123", "-124", "-1234", "+124",
"123.45", "123.456", "123.46", "-123.45", "-123.46", "-123.456",
"x9", "x10", "x-9", "x-10", "X9", "X10",
"1.234e+02", "+123", "1.23E2", "1.2345e+2", "-1.2345e2", "+w"
};
n = sizeof(azStr)/sizeof(azStr[0]);
qsort(azStr, n, sizeof(azStr[0]), sortCmp);
@@ -587,6 +642,7 @@ int main(int argc, char **argv){
}
printf("Sanity1...");
fflush(stdout);
cnt = 0;
for(i=0; i<n-1; i++){
char *a = azStr[i];
for(j=i+1; j<n; j++){
@@ -595,10 +651,42 @@ int main(int argc, char **argv){
printf("Failed! \"%s\" vs \"%s\"\n", a, b);
i = j = n;
}
cnt++;
}
}
if( i<n ){
printf(" OK\n");
printf(" OK (%d)\n", cnt);
}
printf("Sanity2...");
fflush(stdout);
cnt = 0;
for(i=0; i<n; i++){
char *a = azStr[i];
for(j=0; j<n; j++){
char *b = azStr[j];
for(k=0; k<n; k++){
char *c = azStr[k];
int x1, x2, x3, success;
x1 = sqliteCompare(a,b);
x2 = sqliteCompare(b,c);
x3 = sqliteCompare(a,c);
if( x1==0 ){
success = x2==x3;
}else if( x1<0 ){
success = (x2<=0 && x3<=0) || x2>0;
}else{
success = (x2>=0 && x3>=0) || x2<0;
}
if( !success ){
printf("Failed! \"%s\" vs \"%s\" vs \"%s\"\n", a, b, c);
i = j = k = n+1;
}
cnt++;
}
}
}
if( i<n+1 ){
printf(" OK (%d)\n", cnt);
}
return 0;
}
+581 -402
View File
File diff suppressed because it is too large Load Diff
+96 -92
View File
@@ -27,7 +27,7 @@
** or VDBE. The VDBE implements an abstract machine that runs a
** simple program to access and modify the underlying database.
**
** $Id: vdbe.h,v 1.11 2000/07/28 14:32:50 drh Exp $
** $Id: vdbe.h,v 1.15 2001/01/15 22:51:12 drh Exp $
*/
#ifndef _SQLITE_VDBE_H_
#define _SQLITE_VDBE_H_
@@ -71,115 +71,119 @@ typedef struct VdbeOp VdbeOp;
** The source tree contains an AWK script named renumberOps.awk that
** can be used to renumber these opcodes when new opcodes are inserted.
*/
#define OP_Open 1
#define OP_Close 2
#define OP_Fetch 3
#define OP_Fcnt 4
#define OP_New 5
#define OP_Put 6
#define OP_Distinct 7
#define OP_Found 8
#define OP_NotFound 9
#define OP_Delete 10
#define OP_Field 11
#define OP_KeyAsData 12
#define OP_Key 13
#define OP_Rewind 14
#define OP_Next 15
#define OP_OpenIdx 1
#define OP_OpenTbl 2
#define OP_Close 3
#define OP_Fetch 4
#define OP_Fcnt 5
#define OP_New 6
#define OP_Put 7
#define OP_Distinct 8
#define OP_Found 9
#define OP_NotFound 10
#define OP_Delete 11
#define OP_Field 12
#define OP_KeyAsData 13
#define OP_Key 14
#define OP_Rewind 15
#define OP_Next 16
#define OP_Destroy 16
#define OP_Reorganize 17
#define OP_Destroy 17
#define OP_Reorganize 18
#define OP_ResetIdx 18
#define OP_NextIdx 19
#define OP_PutIdx 20
#define OP_DeleteIdx 21
#define OP_ResetIdx 19
#define OP_NextIdx 20
#define OP_PutIdx 21
#define OP_DeleteIdx 22
#define OP_MemLoad 22
#define OP_MemStore 23
#define OP_MemLoad 23
#define OP_MemStore 24
#define OP_ListOpen 24
#define OP_ListWrite 25
#define OP_ListRewind 26
#define OP_ListRead 27
#define OP_ListClose 28
#define OP_ListOpen 25
#define OP_ListWrite 26
#define OP_ListRewind 27
#define OP_ListRead 28
#define OP_ListClose 29
#define OP_SortOpen 29
#define OP_SortPut 30
#define OP_SortMakeRec 31
#define OP_SortMakeKey 32
#define OP_Sort 33
#define OP_SortNext 34
#define OP_SortKey 35
#define OP_SortCallback 36
#define OP_SortClose 37
#define OP_SortOpen 30
#define OP_SortPut 31
#define OP_SortMakeRec 32
#define OP_SortMakeKey 33
#define OP_Sort 34
#define OP_SortNext 35
#define OP_SortKey 36
#define OP_SortCallback 37
#define OP_SortClose 38
#define OP_FileOpen 38
#define OP_FileRead 39
#define OP_FileField 40
#define OP_FileClose 41
#define OP_FileOpen 39
#define OP_FileRead 40
#define OP_FileField 41
#define OP_FileClose 42
#define OP_AggReset 42
#define OP_AggFocus 43
#define OP_AggIncr 44
#define OP_AggNext 45
#define OP_AggSet 46
#define OP_AggGet 47
#define OP_AggReset 43
#define OP_AggFocus 44
#define OP_AggIncr 45
#define OP_AggNext 46
#define OP_AggSet 47
#define OP_AggGet 48
#define OP_SetInsert 48
#define OP_SetFound 49
#define OP_SetNotFound 50
#define OP_SetClear 51
#define OP_SetInsert 49
#define OP_SetFound 50
#define OP_SetNotFound 51
#define OP_SetClear 52
#define OP_MakeRecord 52
#define OP_MakeKey 53
#define OP_MakeRecord 53
#define OP_MakeKey 54
#define OP_Goto 54
#define OP_If 55
#define OP_Halt 56
#define OP_Goto 55
#define OP_If 56
#define OP_Halt 57
#define OP_ColumnCount 57
#define OP_ColumnName 58
#define OP_Callback 59
#define OP_ColumnCount 58
#define OP_ColumnName 59
#define OP_Callback 60
#define OP_Integer 60
#define OP_String 61
#define OP_Null 62
#define OP_Pop 63
#define OP_Dup 64
#define OP_Pull 65
#define OP_Integer 61
#define OP_String 62
#define OP_Null 63
#define OP_Pop 64
#define OP_Dup 65
#define OP_Pull 66
#define OP_Add 66
#define OP_AddImm 67
#define OP_Subtract 68
#define OP_Multiply 69
#define OP_Divide 70
#define OP_Min 71
#define OP_Max 72
#define OP_Like 73
#define OP_Glob 74
#define OP_Eq 75
#define OP_Ne 76
#define OP_Lt 77
#define OP_Le 78
#define OP_Gt 79
#define OP_Ge 80
#define OP_IsNull 81
#define OP_NotNull 82
#define OP_Negative 83
#define OP_And 84
#define OP_Or 85
#define OP_Not 86
#define OP_Concat 87
#define OP_Noop 88
#define OP_Add 67
#define OP_AddImm 68
#define OP_Subtract 69
#define OP_Multiply 70
#define OP_Divide 71
#define OP_Min 72
#define OP_Max 73
#define OP_Like 74
#define OP_Glob 75
#define OP_Eq 76
#define OP_Ne 77
#define OP_Lt 78
#define OP_Le 79
#define OP_Gt 80
#define OP_Ge 81
#define OP_IsNull 82
#define OP_NotNull 83
#define OP_Negative 84
#define OP_And 85
#define OP_Or 86
#define OP_Not 87
#define OP_Concat 88
#define OP_Noop 89
#define OP_MAX 88
#define OP_Strlen 90
#define OP_Substr 91
#define OP_MAX 91
/*
** Prototypes for the VDBE interface. See comments on the implementation
** for a description of what each of these routines does.
*/
Vdbe *sqliteVdbeCreate(Dbbe*);
Vdbe *sqliteVdbeCreate(sqlite*);
int sqliteVdbeAddOp(Vdbe*,int,int,int,const char*,int);
int sqliteVdbeAddOpList(Vdbe*, int nOp, VdbeOp const *aOp);
void sqliteVdbeChangeP3(Vdbe*, int addr, const char *zP1, int N);
@@ -193,6 +197,6 @@ int sqliteVdbeList(Vdbe*,sqlite_callback,void*,char**);
void sqliteVdbeResolveLabel(Vdbe*, int);
int sqliteVdbeCurrentAddr(Vdbe*);
void sqliteVdbeTrace(Vdbe*,FILE*);
void sqliteVdbeCompressSpace(Vdbe*,int);
#endif
+9 -5
View File
@@ -25,7 +25,7 @@
** the WHERE clause of SQL statements. Also found here are subroutines
** to generate VDBE code to evaluate expressions.
**
** $Id: where.c,v 1.8 2000/06/21 13:59:13 drh Exp $
** $Id: where.c,v 1.11 2001/02/19 18:24:22 drh Exp $
*/
#include "sqliteInt.h"
@@ -271,9 +271,9 @@ WhereInfo *sqliteWhereBegin(
/* Open all tables in the pTabList and all indices in aIdx[].
*/
for(i=0; i<pTabList->nId; i++){
sqliteVdbeAddOp(v, OP_Open, base+i, 0, pTabList->a[i].pTab->zName, 0);
sqliteVdbeAddOp(v, OP_OpenTbl, base+i, 0, pTabList->a[i].pTab->zName, 0);
if( i<ARRAYSIZE(aIdx) && aIdx[i]!=0 ){
sqliteVdbeAddOp(v, OP_Open, base+pTabList->nId+i, 0, aIdx[i]->zName, 0);
sqliteVdbeAddOp(v, OP_OpenIdx, base+pTabList->nId+i, 0, aIdx[i]->zName,0);
}
}
memcpy(pWInfo->aIdx, aIdx, sizeof(aIdx));
@@ -324,7 +324,7 @@ WhereInfo *sqliteWhereBegin(
if( i==pTabList->nId-1 && pushKey ){
haveKey = 1;
}else{
sqliteVdbeAddOp(v, OP_Fetch, idx, 0, 0, 0);
sqliteVdbeAddOp(v, OP_Fetch, base+idx, 0, 0, 0);
haveKey = 0;
}
}
@@ -338,8 +338,12 @@ WhereInfo *sqliteWhereBegin(
if( (aExpr[j].prereqRight & loopMask)!=aExpr[j].prereqRight ) continue;
if( (aExpr[j].prereqLeft & loopMask)!=aExpr[j].prereqLeft ) continue;
if( haveKey ){
if( pushKey && i==pTabList->nId-1 ){
sqliteVdbeAddOp(v, OP_Dup, 0, 0, 0, 0);
}else{
haveKey = 0;
}
sqliteVdbeAddOp(v, OP_Fetch, base+idx, 0, 0, 0);
haveKey = 0;
}
sqliteExprIfFalse(pParse, aExpr[j].p, cont);
aExpr[j].p = 0;
+43 -6
View File
@@ -22,24 +22,61 @@
#***********************************************************************
# This file runs all tests.
#
# $Id: all.test,v 1.2 2000/06/02 14:27:23 drh Exp $
# $Id: all.test,v 1.5 2000/12/10 18:23:51 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
rename finish_test really_finish_test
proc finish_test {} {}
proc finish_test {} {memleak_check}
if {[file exists ./sqlite_test_count]} {
set COUNT [exec cat ./sqlite_test_count]
} else {
set COUNT 1
set COUNT 4
}
if {[file exists ./sqlite_test_prefixes]} {
set PREFIXES [exec cat ./sqlite_test_prefixes]
} else {
set PREFIXES {memory: gdbm:}
}
# LeakList will hold a list of the number of unfreed mallocs after
# each round of the test. This number should be constant. If it
# grows, it may mean there is a memory leak in the library.
#
set LeakList {}
for {set Counter 0} {$Counter<$COUNT} {incr Counter} {
foreach testfile [lsort -dictionary [glob $testdir/*.test]] {
if {[file tail $testfile]=="all.test"} continue
source $testfile
foreach p $PREFIXES {
set dbprefix $p
foreach testfile [lsort -dictionary [glob $testdir/*.test]] {
if {[file tail $testfile]=="all.test"} continue
source $testfile
}
}
if {[info exists Leak]} {
lappend LeakList $Leak
}
}
# Do one last test to look for a memory leak in the library. This will
# only work if SQLite is compiled with the -DMEMORY_DEBUG=1 flag.
#
if {$LeakList!=""} {
puts -nonewline memory-leak-test...
incr ::nTest
foreach x $LeakList {
if {$x!=[lindex $LeakList 0]} {
puts " failed!"
puts "Expected: all values to be the same"
puts " Got: $LeakList"
incr ::nErr
break
}
}
puts " Ok"
}
really_finish_test
+11 -11
View File
@@ -23,7 +23,7 @@
# This file implements regression tests for SQLite library. The
# focus of this file is exercising the code in dbbe.c.
#
# $Id: dbbe.test,v 1.3 2000/08/17 09:50:00 drh Exp $
# $Id: dbbe.test,v 1.5 2001/01/31 13:28:09 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
@@ -32,13 +32,13 @@ source $testdir/tester.tcl
#
do_test dbbe-1.1 {
catch {db close}
file delete -force testdb
forcedelete testdb
set v [catch {sqlite db testdb 0444} msg]
lappend v $msg
} {1 {can't find directory "testdb"}}
do_test dbbe-1.2 {
catch {db close}
file delete -force testdb
forcedelete testdb
set v [catch {sqlite db testdb/dummy 0666} msg]
lappend v $msg
} {1 {can't find or create directory "testdb/dummy"}}
@@ -49,7 +49,7 @@ do_test dbbe-1.2 {
#
do_test dbbe-1.3 {
catch {db close}
file delete -force testdb
forcedelete testdb
set v [catch {sqlite db testdb 0666} msg]
lappend v $msg
} {0 {}}
@@ -58,7 +58,7 @@ do_test dbbe-1.3 {
#
do_test dbbe-1.4 {
catch {db close}
file delete -force testdb
forcedelete testdb
set fd [open testdb w]
puts $fd hi!
close $fd
@@ -70,7 +70,7 @@ do_test dbbe-1.4 {
#
do_test dbbe-1.5 {
catch {db close}
file delete -force testdb
forcedelete testdb
file mkdir testdb
file attributes testdb -permissions 0
set v [catch {sqlite db testdb 0666} msg]
@@ -81,7 +81,7 @@ do_test dbbe-1.5 {
#
do_test dbbe-1.6 {
catch {db close}
file delete -force testdb
forcedelete testdb
sqlite db testdb 0666
execsql {CREATE TABLE t1(x int)}
db close
@@ -91,7 +91,7 @@ do_test dbbe-1.6 {
} {1 {access permission denied for testdb/sqlite_master.tbl}}
do_test dbbe-1.6b {
catch {db close}
file delete -force testdb
forcedelete testdb
sqlite db testdb 0666
execsql {CREATE TABLE t1(x int)}
db close
@@ -105,7 +105,7 @@ do_test dbbe-1.6b {
#
do_test dbbe-2.1 {
catch {db close}
file delete -force testdb
forcedelete testdb
sqlite db testdb 0666
execsql {
CREATE TABLE t1(x int);
@@ -123,14 +123,14 @@ do_test dbbe-2.1 {
#
do_test dbbe-3.1 {
catch {db close}
file delete -force testdb
forcedelete testdb
sqlite db testdb 0666
execsql {CREATE TABLE t1(x int)}
db close
sqlite db testdb 0444
set v [catch {execsql {INSERT INTO t1 VALUES(1)}} msg]
lappend v $msg
} {1 {table t1 is readonly}}
} {7 {table t1 is readonly}}
finish_test
+3 -3
View File
@@ -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
+92
View File
@@ -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
View File
@@ -23,7 +23,7 @@
# This file implements regression tests for SQLite library. The
# focus of this file is testing the CREATE INDEX statement.
#
# $Id: index.test,v 1.7 2000/08/02 13:47:42 drh Exp $
# $Id: index.test,v 1.8 2000/10/19 14:10:09 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
@@ -39,12 +39,14 @@ do_test index-1.1b {
execsql {SELECT name, sql, tbl_name, type FROM sqlite_master
WHERE name='index1'}
} {index1 {CREATE INDEX index1 ON test1(f1)} test1 index}
skipif memory:
do_test index-1.1c {
db close
sqlite db testdb
execsql {SELECT name, sql, tbl_name, type FROM sqlite_master
WHERE name='index1'}
} {index1 {CREATE INDEX index1 ON test1(f1)} test1 index}
skipif memory:
do_test index-1.1d {
db close
sqlite db testdb
@@ -106,6 +108,7 @@ set r {}
for {set i 1} {$i<100} {incr i} {
lappend r testdb/index$i.tbl
}
skipif memory:
do_test index-3.2 {
execsql {INSERT INTO test1 VALUES(1,2,3,4,5)}
lsort -dictionary [glob testdb/index*.tbl]
@@ -223,6 +226,10 @@ do_test index-7.1 {
for {set i 1} {$i<20} {incr i} {
execsql "INSERT INTO test1 VALUES($i,[expr {int(pow(2,$i))}])"
}
execsql {SELECT count(*) FROM test1}
} {19}
skipif memory:
do_test index-7.1b {
lsort -dictionary [glob testdb/test1*.tbl]
} {testdb/test1.tbl testdb/test1__primary_key.tbl}
do_test index-7.2 {
+8 -4
View File
@@ -23,7 +23,9 @@
# This file implements regression tests for SQLite library. The
# focus of this script is database locks.
#
# $Id: lock.test,v 1.2 2000/08/04 13:51:11 drh Exp $
# $Id: lock.test,v 1.5 2001/01/04 14:20:18 drh Exp $
if {$dbprefix=="gdbm:"} {
set testdir [file dirname $argv0]
source $testdir/tester.tcl
@@ -59,7 +61,7 @@ do_test lock-1.2 {
#
set v [catch {execsql {UPDATE big SET f2='xyz' WHERE f1=11}} msg]
lappend v $msg
} {1 {table big is locked}}
} {5 {table big is locked}}
do_test lock-1.3 {
# Try to update the database in a separate process
@@ -70,7 +72,7 @@ do_test lock-1.3 {
puts $f "SELECT f2 FROM big WHERE f1=11;"
close $f
exec ./sqlite testdb <update.sql
} "SQL error: table big is locked\n22"
} "UPDATE big SET f2='xyz' WHERE f1=11;\nSQL error: table big is locked\n22"
do_test lock-1.4 {
# Try to update the database using a timeout
@@ -81,7 +83,7 @@ do_test lock-1.4 {
puts $f "SELECT f2 FROM big WHERE f1=11;"
close $f
exec ./sqlite testdb <update.sql
} "SQL error: table big is locked\n22"
} "UPDATE big SET f2='xyz' WHERE f1=11;\nSQL error: table big is locked\n22"
do_test lock-1.5 {
# Try to update the database using a timeout
@@ -99,3 +101,5 @@ catch {exec kill -HUP $::lock_pid}
catch {exec kill -9 $::lock_pid}
finish_test
}
+29 -1
View File
@@ -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
View File
@@ -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
View File
@@ -23,7 +23,7 @@
# This file implements regression tests for SQLite library. The
# focus of this file is testing the CREATE TABLE statement.
#
# $Id: table.test,v 1.6 2000/08/02 13:47:43 drh Exp $
# $Id: table.test,v 1.7 2000/10/19 14:10:09 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
@@ -49,6 +49,9 @@ do_test table-1.1 {
#
do_test table-1.2 {
execsql {INSERT INTO test1 VALUES('hi', 'y''all')}
} {}
testif gdbm:
do_test table-1.2b {
lsort [glob -nocomplain testdb/*.tbl]
} {testdb/sqlite_master.tbl testdb/test1.tbl}
@@ -61,6 +64,7 @@ do_test table-1.3 {
# Close and reopen the database. Verify that everything is
# still the same.
#
skipif memory:
do_test table-1.4 {
db close
sqlite db testdb
@@ -76,6 +80,7 @@ do_test table-1.5 {
# Verify that the file associated with the database is gone.
#
testif gdbm:
do_test table-1.5 {
lsort [glob -nocomplain testdb/*.tbl]
} {testdb/sqlite_master.tbl}
@@ -83,6 +88,7 @@ do_test table-1.5 {
# Close and reopen the database. Verify that the table is
# still gone.
#
skipif memory:
do_test table-1.6 {
db close
sqlite db testdb
@@ -121,6 +127,7 @@ do_test table-2.1b {
set v [catch {execsql {CREATE TABLE sqlite_master(two text)}} msg]
lappend v $msg
} {1 {table sqlite_master already exists}}
skipif memory:
do_test table-2.1c {
db close
sqlite db testdb
@@ -138,6 +145,7 @@ do_test table-2.2a {
set v [catch {execsql {CREATE TABLE test3(two text)}} msg]
lappend v $msg
} {1 {there is already an index named test3}}
skipif memory:
do_test table-2.2b {
db close
sqlite db testdb
@@ -198,6 +206,7 @@ do_test table-3.4 {
set v [catch {execsql {CREATE TABLE bIg(xyz foo)}} msg]
lappend v $msg
} {1 {table bIg already exists}}
skipif memory:
do_test table-3.5 {
db close
sqlite db testdb
@@ -226,6 +235,7 @@ do_test table-4.1 {
}
execsql {SELECT name FROM sqlite_master WHERE type!='meta' ORDER BY name}
} $r
skipif memory:
do_test table-4.1b {
db close
sqlite db testdb
@@ -287,6 +297,7 @@ do_test table-5.4 {
# Create a table with a goofy name
#
testif gdbm:
do_test table-6.1 {
execsql {CREATE TABLE 'Spaces In This Name!'(x int)}
execsql {INSERT INTO 'spaces in this name!' VALUES(1)}
+76 -14
View File
@@ -23,13 +23,29 @@
# This file implements some common TCL routines used for regression
# testing the SQLite library
#
# $Id: tester.tcl,v 1.5 2000/06/08 13:36:41 drh Exp $
# $Id: tester.tcl,v 1.10 2001/01/31 13:28:09 drh Exp $
# Create a test database
#
file delete -force testdb
file mkdir testdb
sqlite db testdb
if {![info exists dbprefix]} {
if {[info exists env(SQLITE_PREFIX)]} {
set dbprefix $env(SQLITE_PREFIX):
} else {
set dbprefix "gdbm:"
}
}
switch $dbprefix {
gdbm: {
if {[catch {file delete -force testdb}]} {
exec rm -rf testdb
}
file mkdir testdb
}
memory: {
# do nothing
}
}
sqlite db ${dbprefix}testdb
# Abort early if this script has been run before.
#
@@ -39,12 +55,17 @@ if {[info exists nTest]} return
#
set nErr 0
set nTest 0
set skip_test 0
# Invoke the do_test procedure to run a single test
#
proc do_test {name cmd expected} {
global argv nErr nTest
if {[llength $argv]==0} {
global argv nErr nTest skip_test
if {$skip_test} {
set skip_test 0
return
}
if {[llength $argv]==0} {
set go 1
} else {
set go 0
@@ -57,7 +78,7 @@ proc do_test {name cmd expected} {
}
if {!$go} return
incr nTest
puts -nonewline $name...
puts -nonewline $::dbprefix$name...
flush stdout
if {[catch {uplevel #0 "$cmd;\n"} result]} {
puts "\nError: $result"
@@ -70,10 +91,48 @@ proc do_test {name cmd expected} {
}
}
# Skip a test based on the dbprefix
#
proc skipif {args} {
foreach a $args {
if {$::dbprefix==$a} {
set ::skip_test 1
return
}
}
}
# Run the next test only if the dbprefix is among the listed arguments
#
proc testif {args} {
foreach a $args {
if {$::dbprefix==$a} {
set ::skip_test 0
return
}
}
set ::skip_test 1
}
# The procedure uses the special "--malloc-stats--" macro of SQLite
# (which is only available if SQLite is compiled with -DMEMORY_DEBUG=1)
# to see how many malloc()s have not been free()ed. The number
# of surplus malloc()s is stored in the global variable $::Leak.
# If the value in $::Leak grows, it may mean there is a memory leak
# in the library.
#
proc memleak_check {} {
set r [execsql {--malloc-stats--}]
if {$r==""} return
set ::Leak [expr {[lindex $r 0]-[lindex $r 1]}]
# puts "*** $::Leak mallocs have not been freed ***"
}
# Run this routine last
#
proc finish_test {} {
global nTest nErr
memleak_check
catch {db close}
puts "$nErr errors out of $nTest tests"
exit $nErr
@@ -82,13 +141,8 @@ proc finish_test {} {
# A procedure to execute SQL
#
proc execsql {sql} {
set result {}
db eval $sql data {
foreach f $data(*) {
lappend result $data($f)
}
}
return $result
# puts "SQL = $sql"
return [db eval $sql]
}
# Another procedure to execute SQL. This one includes the field
@@ -103,3 +157,11 @@ proc execsql2 {sql} {
}
return $result
}
# Delete a file or directory
#
proc forcedelete {filename} {
if {[catch {file delete -force $filename}]} {
exec rm -rf $filename
}
}
+277 -23
View File
@@ -23,7 +23,7 @@
# This file implements regression tests for SQLite library. The
# focus of this file is testing the UPDATE statement.
#
# $Id: update.test,v 1.4 2000/06/21 13:59:14 drh Exp $
# $Id: update.test,v 1.5 2001/02/19 18:24:22 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
@@ -90,47 +90,301 @@ do_test update-3.8 {
execsql {UPDATE test1 SET F2=f1, F1=f2}
execsql {SELECT * FROM test1 ORDER BY F1}
} {2 1 4 2 8 3 16 4 32 5 64 6 128 7 256 8 512 9 1024 10}
# Create an index and make sure updating works with an index.
#
do_test update-3.9 {
execsql {CREATE INDEX index1 ON test1(f1)}
execsql {CREATE INDEX index2 ON test1(f1)}
execsql {UPDATE test1 SET f1=1025 WHERE f2==10}
execsql {SELECT * FROM test1 ORDER BY f1}
} {2 1 4 2 8 3 16 4 32 5 64 6 128 7 256 8 512 9 1025 10}
do_test update-3.10 {
execsql {SELECT * FROM test1 WHERE f1=1025}
} {1025 10}
do_test update-3.11 {
execsql {UPDATE test1 SET f2=11 WHERE f1==1025}
execsql {SELECT * FROM test1 ORDER BY f1}
} {2 1 4 2 8 3 16 4 32 5 64 6 128 7 256 8 512 9 1025 11}
do_test update-3.12 {
execsql {SELECT * FROM test1 WHERE f1=1025}
} {1025 11}
execsql {UPDATE test1 SET F2=f1, F1=f2}
execsql {SELECT * FROM test1 ORDER BY F1}
} {1 2 2 4 3 8 4 16 5 32 6 64 7 128 8 256 9 512 10 1024}
# Create duplicate entries and make sure updating still
# works.
#
do_test update-4.0 {
execsql {
DELETE FROM test1 WHERE f1<=5;
INSERT INTO test1(f1,f2) VALUES(8,88);
INSERT INTO test1(f1,f2) VALUES(8,888);
INSERT INTO test1(f1,f2) VALUES(77,128);
INSERT INTO test1(f1,f2) VALUES(777,128);
}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
do_test update-4.1 {
execsql {UPDATE test1 SET f2=f2+1 WHERE f1==8}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 89 8 257 8 889 9 512 10 1024 77 128 777 128}
do_test update-4.2 {
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2>800}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 89 8 257 8 888 9 512 10 1024 77 128 777 128}
do_test update-4.3 {
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2<800}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
do_test update-4.4 {
execsql {UPDATE test1 SET f1=f1+1 WHERE f2==128}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 778 128}
do_test update-4.5 {
execsql {UPDATE test1 SET f1=f1-1 WHERE f1>100 and f2==128}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 777 128}
do_test update-4.6 {
execsql {UPDATE test1 SET f1=f1-1 WHERE f1<=100 and f2==128}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
# Repeat the previous sequence of tests with an index.
#
do_test update-5.0 {
execsql {CREATE INDEX idx1 ON test1(f1)}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
do_test update-5.1 {
execsql {UPDATE test1 SET f2=f2+1 WHERE f1==8}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 89 8 257 8 889 9 512 10 1024 77 128 777 128}
do_test update-5.2 {
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2>800}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 89 8 257 8 888 9 512 10 1024 77 128 777 128}
do_test update-5.3 {
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2<800}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
do_test update-5.4 {
execsql {UPDATE test1 SET f1=f1+1 WHERE f2==128}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 778 128}
do_test update-5.4.1 {
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
} {78 128}
do_test update-5.4.2 {
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
} {778 128}
do_test update-5.4.3 {
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
} {8 88 8 128 8 256 8 888}
do_test update-5.5 {
execsql {UPDATE test1 SET f1=f1-1 WHERE f1>100 and f2==128}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 777 128}
do_test update-5.5.1 {
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
} {78 128}
do_test update-5.5.2 {
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
} {}
do_test update-5.5.3 {
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
} {777 128}
do_test update-5.5.4 {
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
} {8 88 8 128 8 256 8 888}
do_test update-5.6 {
execsql {UPDATE test1 SET f1=f1-1 WHERE f1<=100 and f2==128}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
do_test update-5.6.1 {
execsql {SELECT * FROM test1 WHERE f1==77 ORDER BY f1,f2}
} {77 128}
do_test update-5.6.2 {
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
} {}
do_test update-5.6.3 {
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
} {777 128}
do_test update-5.6.4 {
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
} {8 88 8 256 8 888}
# Repeat the previous sequence of tests with a different index.
#
do_test update-6.0 {
execsql {DROP INDEX idx1}
execsql {CREATE INDEX idx1 ON test1(f2)}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
do_test update-6.1 {
execsql {UPDATE test1 SET f2=f2+1 WHERE f1==8}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 89 8 257 8 889 9 512 10 1024 77 128 777 128}
do_test update-6.1.1 {
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
} {8 89 8 257 8 889}
do_test update-6.1.2 {
execsql {SELECT * FROM test1 WHERE f2==89 ORDER BY f1,f2}
} {8 89}
do_test update-6.1.3 {
execsql {SELECT * FROM test1 WHERE f1==88 ORDER BY f1,f2}
} {}
do_test update-6.2 {
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2>800}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 89 8 257 8 888 9 512 10 1024 77 128 777 128}
do_test update-6.3 {
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2<800}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
do_test update-6.3.1 {
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
} {8 88 8 256 8 888}
do_test update-6.3.2 {
execsql {SELECT * FROM test1 WHERE f2==89 ORDER BY f1,f2}
} {}
do_test update-6.3.3 {
execsql {SELECT * FROM test1 WHERE f2==88 ORDER BY f1,f2}
} {8 88}
do_test update-6.4 {
execsql {UPDATE test1 SET f1=f1+1 WHERE f2==128}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 778 128}
do_test update-6.4.1 {
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
} {78 128}
do_test update-6.4.2 {
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
} {778 128}
do_test update-6.4.3 {
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
} {8 88 8 128 8 256 8 888}
do_test update-6.5 {
execsql {UPDATE test1 SET f1=f1-1 WHERE f1>100 and f2==128}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 777 128}
do_test update-6.5.1 {
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
} {78 128}
do_test update-6.5.2 {
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
} {}
do_test update-6.5.3 {
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
} {777 128}
do_test update-6.5.4 {
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
} {8 88 8 128 8 256 8 888}
do_test update-6.6 {
execsql {UPDATE test1 SET f1=f1-1 WHERE f1<=100 and f2==128}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
do_test update-6.6.1 {
execsql {SELECT * FROM test1 WHERE f1==77 ORDER BY f1,f2}
} {77 128}
do_test update-6.6.2 {
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
} {}
do_test update-6.6.3 {
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
} {777 128}
do_test update-6.6.4 {
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
} {8 88 8 256 8 888}
# Repeat the previous sequence of tests with multiple
# indices
#
do_test update-7.0 {
execsql {CREATE INDEX idx2 ON test1(f2)}
execsql {CREATE INDEX idx3 ON test1(f1,f2)}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
do_test update-7.1 {
execsql {UPDATE test1 SET f2=f2+1 WHERE f1==8}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 89 8 257 8 889 9 512 10 1024 77 128 777 128}
do_test update-7.1.1 {
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
} {8 89 8 257 8 889}
do_test update-7.1.2 {
execsql {SELECT * FROM test1 WHERE f2==89 ORDER BY f1,f2}
} {8 89}
do_test update-7.1.3 {
execsql {SELECT * FROM test1 WHERE f1==88 ORDER BY f1,f2}
} {}
do_test update-7.2 {
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2>800}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 89 8 257 8 888 9 512 10 1024 77 128 777 128}
do_test update-7.3 {
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2<800}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
do_test update-7.3.1 {
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
} {8 88 8 256 8 888}
do_test update-7.3.2 {
execsql {SELECT * FROM test1 WHERE f2==89 ORDER BY f1,f2}
} {}
do_test update-7.3.3 {
execsql {SELECT * FROM test1 WHERE f2==88 ORDER BY f1,f2}
} {8 88}
do_test update-7.4 {
execsql {UPDATE test1 SET f1=f1+1 WHERE f2==128}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 778 128}
do_test update-7.4.1 {
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
} {78 128}
do_test update-7.4.2 {
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
} {778 128}
do_test update-7.4.3 {
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
} {8 88 8 128 8 256 8 888}
do_test update-7.5 {
execsql {UPDATE test1 SET f1=f1-1 WHERE f1>100 and f2==128}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 777 128}
do_test update-7.5.1 {
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
} {78 128}
do_test update-7.5.2 {
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
} {}
do_test update-7.5.3 {
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
} {777 128}
do_test update-7.5.4 {
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
} {8 88 8 128 8 256 8 888}
do_test update-7.6 {
execsql {UPDATE test1 SET f1=f1-1 WHERE f1<=100 and f2==128}
execsql {SELECT * FROM test1 ORDER BY f1,f2}
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
do_test update-7.6.1 {
execsql {SELECT * FROM test1 WHERE f1==77 ORDER BY f1,f2}
} {77 128}
do_test update-7.6.2 {
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
} {}
do_test update-7.6.3 {
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
} {777 128}
do_test update-7.6.4 {
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
} {8 88 8 256 8 888}
# Error messages
#
do_test update-4.1 {
do_test update-9.1 {
set v [catch {execsql {
UPDATE test1 SET x=11 WHERE f1=1025
}} msg]
lappend v $msg
} {1 {no such column: x}}
do_test update-4.2 {
do_test update-9.2 {
set v [catch {execsql {
UPDATE test1 SET f1=x(11) WHERE f1=1025
}} msg]
lappend v $msg
} {1 {no such function: x}}
do_test update-4.3 {
do_test update-9.3 {
set v [catch {execsql {
UPDATE test1 SET f1=11 WHERE x=1025
}} msg]
lappend v $msg
} {1 {no such column: x}}
do_test update-4.4 {
do_test update-9.4 {
set v [catch {execsql {
UPDATE test1 SET f1=11 WHERE x(f1)=1025
}} msg]
+3 -1
View File
@@ -23,7 +23,7 @@
# This file implements regression tests for SQLite library. The
# focus of this file is testing the VACUUM statement.
#
# $Id: vacuum.test,v 1.1 2000/06/03 18:06:54 drh Exp $
# $Id: vacuum.test,v 1.2 2000/10/19 14:10:10 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
@@ -53,6 +53,7 @@ execsql {INSERT INTO test1 VALUES(2)}
execsql {INSERT INTO test1 VALUES(3)}
execsql {INSERT INTO test2 VALUES(4)}
testif gdbm:
do_test vacuum-1.3 {
set b1 [file mtime testdb/test1.tbl]
set b2 [file mtime testdb/test2.tbl]
@@ -64,6 +65,7 @@ do_test vacuum-1.3 {
set a3 [file mtime testdb/index1.tbl]
expr {$a1>$b1 && $a2==$b2 && $a3==$b3}
} {1}
testif gdbm:
do_test vacuum-1.4 {
set b1 [file mtime testdb/test1.tbl]
set b2 [file mtime testdb/test2.tbl]
+144
View File
@@ -0,0 +1,144 @@
/*
** Copyright (c) 1999, 2000 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*************************************************************************
** A utility print statistics about the content of a GDBM database.
*/
#include <stdio.h>
#include <ctype.h>
#include <gdbm.h>
#include <stdlib.h>
static int bins[] = {
4, 8, 12, 16, 24,
32, 40, 48, 56, 64,
80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256,
288, 320, 352, 384, 416, 448, 480, 512, 1024, 2048, 4096, 8192,
16384, 32768, 65536,
};
#define NBIN (sizeof(bins)/sizeof(bins[0])+1)
static int gAllSize[NBIN];
static int gCount;
static int gTotal;
static int gMax;
static int gKey;
static int gMaxKey;
static int gdbm_stat(char *zFilename){
GDBM_FILE p;
datum data, key, next;
int nEntry = 0;
int keyTotal = 0;
int dataTotal = 0;
int allTotal = 0;
int lMax = 0;
int keySize[NBIN], dataSize[NBIN], allSize[NBIN];
int i, priorSize;
p = gdbm_open(zFilename, 0, GDBM_READER, 0, 0);
if( p==0 ){
fprintf(stderr,"can't open file \"%s\"\n", zFilename);
return 1;
}
for(i=0; i<NBIN; i++){
keySize[i] = 0;
dataSize[i] = 0;
allSize[i] = 0;
}
key = gdbm_firstkey(p);
while( key.dptr ){
int all;
nEntry++;
gCount++;
keyTotal += key.dsize;
for(i=0; i<NBIN-1 && key.dsize>bins[i]; i++){}
keySize[i]++;
gKey += key.dsize;
if( key.dsize>gMaxKey ) gMaxKey = key.dsize;
data = gdbm_fetch(p, key);
if( data.dptr==0 ) data.dsize = 0;
dataTotal += data.dsize;
for(i=0; i<NBIN-1 && data.dsize>bins[i]; i++){}
dataSize[i]++;
all = key.dsize + data.dsize;
allTotal += all;
gTotal += all;
if( all>gMax ) gMax = all;
if( all>lMax ) lMax = all;
for(i=0; i<NBIN-1 && all>bins[i]; i++){}
allSize[i]++;
gAllSize[i]++;
next = gdbm_nextkey(p, key);
free( key.dptr );
key = next;
}
gdbm_close(p);
printf("%s:\n", zFilename);
printf(" entries: %d\n", nEntry);
printf(" keysize: %d (%d per entry)\n",
keyTotal, nEntry>0 ? (keyTotal+nEntry-1)/nEntry : 0);
printf(" datasize: %d (%d per entry)\n",
dataTotal, nEntry>0 ? (dataTotal+nEntry-1)/nEntry : 0);
printf(" size: %d (%d per entry)\n",
allTotal, nEntry>0 ? (allTotal+nEntry-1)/nEntry : 0);
priorSize = 0;
for(i=0; i<NBIN-1; i++){
if( keySize[i]==0 && dataSize[i]==0 ) continue;
printf("%5d..%-5d %7d %7d %7d\n", priorSize, bins[i], keySize[i],
dataSize[i], allSize[i]);
priorSize = bins[i]+1;
}
if( keySize[NBIN-1]>0 || dataSize[NBIN-1]>0 ){
printf("%5d..%-5d %7d %7d %7d\n", priorSize, lMax,
keySize[NBIN-1], dataSize[NBIN-1], allSize[NBIN-1]);
}
return 0;
}
int main(int argc, char **argv){
int i, ps, sum;
int nErr = 0;
for(i=1; i<argc; i++){
nErr += gdbm_stat(argv[i]);
}
printf("*****************************************************************\n");
printf("Entries: %d\n", gCount);
printf("Size: %d\n", gTotal);
printf("Avg Size: %d\n", gCount>0 ? (gTotal + gCount - 1)/gCount : 0);
printf("Key Size: %d\n", gKey);
printf("Avg Key Size: %d\n", gCount>0 ? (gKey + gCount - 1)/gCount : 0);
printf("Max Key Size: %d\n\n", gMaxKey);
ps = 0;
sum = 0;
for(i=0; i<NBIN-1; i++){
if( gAllSize[i]==0 ) continue;
sum += gAllSize[i];
printf("%5d..%-5d %8d %3d%%\n",
ps, bins[i], gAllSize[i], sum*100/gCount);
ps = bins[i]+1;
}
if( gAllSize[NBIN-1]>0 ){
printf("%5d..%-5d %8d 100%%\n", ps, gMax, gAllSize[NBIN-1]);
}
return nErr;
}
+2 -1
View File
@@ -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;
+66
View File
@@ -0,0 +1,66 @@
The SQL database used for ACD contains 113 tables and indices implemented
in GDBM. The following are statistics on the sizes of keys and data
within these tables and indices.
Entries: 962080
Size: 45573853
Avg Size: 48
Key Size: 11045299
Avg Key Size: 12
Max Key Size: 99
Size of key Cummulative
and data Instances Percentage
------------ ---------- -----------
0..8 266 0%
9..12 5485 0%
13..16 73633 8%
17..24 180918 27%
25..32 209823 48%
33..40 148995 64%
41..48 76304 72%
49..56 14346 73%
57..64 15725 75%
65..80 44916 80%
81..96 127815 93%
97..112 34769 96%
113..128 13314 98%
129..144 8098 99%
145..160 3355 99%
161..176 1159 99%
177..192 629 99%
193..208 221 99%
209..224 210 99%
225..240 129 99%
241..256 57 99%
257..288 496 99%
289..320 60 99%
321..352 37 99%
353..384 46 99%
385..416 22 99%
417..448 24 99%
449..480 26 99%
481..512 27 99%
513..1024 471 99%
1025..2048 389 99%
2049..4096 182 99%
4097..8192 74 99%
8193..16384 34 99%
16385..32768 17 99%
32769..65536 5 99%
65537..131073 3 100%
This information is gathered to help design the new built-in
backend for sqlite 2.0. Note in particular that 99% of all
database entries have a combined key and data size of less than
144 bytes. So if a leaf node in the new database is able to
store 144 bytes of combined key and data, only 1% of the leaves
will require overflow pages. Furthermore, note that no key
is larger than 99 bytes, so if the key will never be on an
overflow page.
The average combined size of key+data is 48. Add in 16 bytes of
overhead for a total of 64. That means that a 1K page will
store (on average) about 16 entries.
+1 -1
View File
@@ -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
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

+5 -3
View File
@@ -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
View File
@@ -1,7 +1,7 @@
#
# Run this Tcl script to generate the sqlite.html file.
#
set rcsid {$Id: c_interface.tcl,v 1.6 2000/07/28 14:32:51 drh Exp $}
set rcsid {$Id: c_interface.tcl,v 1.13 2000/11/28 20:46:39 drh Exp $}
puts {<html>
<head>
@@ -22,15 +22,14 @@ programming interface.</p>
<h2>The API</h2>
<p>The interface to the SQLite library consists of six functions
(only three of which are required),
one opaque data structure, and some constants used as return
values from sqlite_exec():</p>
<p>The interface to the SQLite library consists of three core functions,
one opaque data structure, and some constants used as return values.
The core interface is as follows:</p>
<blockquote><pre>
typedef struct sqlite sqlite;
sqlite *sqlite_open(const char *filename, int mode, char **errmsg);
sqlite *sqlite_open(const char *dbname, int mode, char **errmsg);
void sqlite_close(sqlite*);
@@ -42,12 +41,6 @@ int sqlite_exec(
char **errmsg
);
int sqlite_complete(const char *sql);
void sqlite_busy_handler(sqlite*, int (*)(void*,const char*,int), void*);
void sqlite_busy_timeout(sqlite*, int ms);
#define SQLITE_OK 0 /* Successful result */
#define SQLITE_INTERNAL 1 /* An internal logic error in SQLite */
#define SQLITE_ERROR 2 /* SQL error or missing database */
@@ -58,6 +51,73 @@ void sqlite_busy_timeout(sqlite*, int ms);
#define SQLITE_READONLY 7 /* Attempt to write a readonly database */
</pre></blockquote>
<p>Only the three core routines shown above are required to use
SQLite. But there are many other functions that provide
useful interfaces. These extended routines are as follows:
</p>
<blockquote><pre>
int sqlite_get_table(
sqlite*,
char *sql,
char ***result,
int *nrow,
int *ncolumn,
char **errmsg
);
void sqlite_free_table(char**);
void sqlite_interrupt(sqlite*);
int sqlite_complete(const char *sql);
void sqlite_busy_handler(sqlite*, int (*)(void*,const char*,int), void*);
void sqlite_busy_timeout(sqlite*, int ms);
const char sqlite_version[];
int sqlite_exec_printf(
sqlite*,
char *sql,
int (*)(void*,int,char**,char**),
void*,
char **errmsg,
...
);
int sqlite_exec_vprintf(
sqlite*,
char *sql,
int (*)(void*,int,char**,char**),
void*,
char **errmsg,
va_list
);
int sqlite_get_table_printf(
sqlite*,
char *sql,
char ***result,
int *nrow,
int *ncolumn,
char **errmsg,
...
);
int sqlite_get_table_vprintf(
sqlite*,
char *sql,
char ***result,
int *nrow,
int *ncolumn,
char **errmsg,
va_list
);
</pre></blockquote>
<p>All of the above definitions are included in the "sqlite.h"
header file that comes in the source tree.</p>
@@ -74,8 +134,9 @@ written to memory obtained from malloc() and *errmsg will be made
to point to this error message. The calling function is responsible
for freeing the memory when it has finished with it.</p>
<p>An SQLite database is just a directory containing a collection of
GDBM files. There is one GDBM file for each table and index in the
<p>The name of an SQLite database is normally the name of a directory
that contains a collection of GDBM files that comprise the database.
There is one GDBM file for each table and index in the
database. All GDBM files end with the ".tbl" suffix. Every SQLite
database also contains a special database table named <b>sqlite_master</b>
stored in its own GDBM file. This special table records the database
@@ -83,7 +144,20 @@ schema.</p>
<p>To create a new SQLite database, all you have to do is call
<b>sqlite_open()</b> with the first parameter set to the name of
an empty directory and the second parameter set to 0666.</p>
an empty directory and the second parameter set to 0666. The
directory is created automatically if it does not already exist.</p>
<p>Beginning with SQLite version 1.0.14, SQLite supports database
backends other than GDBM. The only backends currently supported
are the default GDBM driver and an in-memory hash table database.
You may anticipate additional backends in future versions of SQLite.</p>
<p>An alternative database backend is specified by prepending the
backend name and a colon to the database name argument of the
<b>sqlite_open()</b> function. For the GDBM backend, you can
prepend "<b>gdbm:</b>" to the directory name. To select the in-memory
hash table backend, prepend "<b>memory:</b>" to the database name.
Future database drivers will be selected by a similar mechanism.</p>
<p>The return value of the <b>sqlite_open()</b> function is a
pointer to an opaque <b>sqlite</b> structure. This pointer will
@@ -205,12 +279,75 @@ a database file that was originally opened for reading only. This can
happen if the callback from a query attempts to update the table
being queried.
</p></dd>
<dt>SQLITE_INTERRUPT</dt>
<dd><p>This value is returned if a call to <b>sqlite_interrupt()</b>
interrupts a database operation in progress.
</p></dd>
</dl>
</blockquote>
<h2>Querying without using a callback function</h2>
<p>The <b>sqlite_get_table()</b> function is a wrapper around
<b>sqlite_exec()</b> that collects all the information from successive
callbacks and write it into memory obtained from malloc(). This
is a convenience function that allows the application to get the
entire result of a database query with a single function call.</p>
<p>The main result from <b>sqlite_get_table()</b> is an array of pointers
to strings. There is one element in this array for each column of
each row in the result. NULL results are represented by a NULL
pointer. In addition to the regular data, there is an added row at the
beginning of the array that contains the names of each column of the
result.</p>
<p>As an example, consider the following query:</p>
<blockquote>
SELECT employee_name, login, host FROM users WHERE logic LIKE 'd%';
</blockquote>
<p>This query will return the name, login and host computer name
for every employee whose login begins with the letter "d". If this
query is submitted to <b>sqlite_get_table()</b> the result might
look like this:</p>
<blockquote>
nrow = 2<br>
ncolumn = 3<br>
result[0] = "employee_name"<br>
result[1] = "login"<br>
result[2] = "host"<br>
result[3] = "dummy"<br>
result[4] = "No such user"<br>
result[5] = 0<br>
result[6] = "D. Richard Hipp"<br>
result[7] = "drh"<br>
result[8] = "zadok"
</blockquote>
<p>Notice that the "host" value for the "dummy" record is NULL so
the result[] array contains a NULL pointer at that slot.</p>
<p>Memory to hold the information returned by <b>sqlite_get_table()</b>
is obtained from malloc(). But the calling function should not try
to free this information directly. Instead, pass the complete table
to <b>sqlite_free_table()</b> when the table is no longer needed.</p>
<p>The <b>sqlite_get_table()</b> routine returns the same integer
result code as <b>sqlite_exec()</b>.</p>
<h2>Interrupting an SQLite operation</h2>
<p>The <b>sqlite_interrupt()</b> function can be called from a
different thread or from a signal handler to the current database
operation to exit at its first opportunity. When this happens,
the <b>sqlite_exec()</b> routine (or the equivalent) that started
the database operation will return SQLITE_INTERRUPT.</p>
<h2>Testing for a complete SQL statement</h2>
<p>The last interface routine to SQLite is a convenience function used
<p>The next interface routine to SQLite is a convenience function used
to test whether or not a string forms a complete SQL statement.
If the <b>sqlite_complete()</b> function returns true when its input
is a string, then the argument forms a complete SQL statement.
@@ -230,7 +367,17 @@ then <b>sqlite_exec()</b> is called and the input buffer is reset. If
the continuation prompt and another line of text is read and added to
the input buffer.</p>
<h2>Changing the libraries reponse to locked files</h2>
<h2>Library version string</h2>
<p>The SQLite library exports the string constant named
<b>sqlite_version</b> which contains the version number of the
library. The header file contains a macro SQLITE_VERSION
with the same information. If desired, a program can compare
the SQLITE_VERSION macro against the <b>sqlite_version</b>
string constant to verify that the version number of the
header file and the library match.</p>
<h2>Changing the libraries response to locked files</h2>
<p>The GDBM library supports database locks at the file level.
If a GDBM database file is opened for reading, then that same
@@ -271,6 +418,85 @@ will wait for the lock to clear for at least the number of milliseconds
specified before it returns SQLITE_BUSY. Specifying zero milliseconds for
the timeout restores the default behavior.</p>
<h2>Using the <tt>_printf()</tt> wrapper functions</h2>
<p>The four utility functions</p>
<p>
<ul>
<li><b>sqlite_exec_printf()</b></li>
<li><b>sqlite_exec_vprintf()</b></li>
<li><b>sqlite_get_table_printf()</b></li>
<li><b>sqlite_get_table_vprintf()</b></li>
</ul>
</p>
<p>implement the same query functionality as <b>sqlite_exec()</b>
and <b>sqlite_get_table()</b>. But instead of taking a complete
SQL statement as their second argument, the four <b>_printf</b>
routines take a printf-style format string. The SQL statement to
be executed is generated from this format string and from whatever
additional arguments are attached to the end of the function call.</p>
<p>There are two advantages to using the SQLite printf
functions instead of <b>sprintf()</b>. First of all, with the
SQLite printf routines, there is never a danger of overflowing a
static buffer as there is with <b>sprintf()</b>. The SQLite
printf routines automatically allocate (and later free)
as much memory as is
necessary to hold the SQL statements generated.</p>
<p>The second advantage the SQLite printf routines have over
<b>sprintf()</b> is a new formatting option specifically designed
to support string literals in SQL. Within the format string,
the %q formatting option works very much like %s in that it
reads a null-terminated string from the argument list and inserts
it into the result. But %q translates the inserted string by
making two copies of every single-quote (') character in the
substituted string. This has the effect of escaping the end-of-string
meaning of single-quote within a string literal.
</p>
<p>Consider an example. Suppose you are trying to insert a string
value into a database table where the string value was obtained from
user input. Suppose the string to be inserted is stored in a variable
named zString. The code to do the insertion might look like this:</p>
<blockquote><pre>
sqlite_exec_printf(db,
"INSERT INTO table1 VALUES('%s')",
0, 0, 0, zString);
</pre></blockquote>
<p>If the zString variable holds text like "Hello", then this statement
will work just fine. But suppose the user enters a string like
"Hi y'all!". The SQL statement generated reads as follows:
<blockquote><pre>
INSERT INTO table1 VALUES('Hi y'all')
</pre></blockquote>
<p>This is not valid SQL because of the apostrophy in the word "y'all".
But if the %q formatting option is used instead of %s, like this:</p>
<blockquote><pre>
sqlite_exec_printf(db,
"INSERT INTO table1 VALUES('%q')",
0, 0, 0, zString);
</pre></blockquote>
<p>Then the generated SQL will look like the following:</p>
<blockquote><pre>
INSERT INTO table1 VALUES('Hi y''all')
</pre></blockquote>
<p>Here the apostrophy has been escaped and the SQL statement is well-formed.
When generating SQL on-the-fly from data that might contain a
single-quote character ('), it is always a good idea to use the
SQLite printf routines and the %q formatting option instead of <b>sprintf</b>.
</p>
<h2>Usage Examples</h2>
<p>For examples of how the SQLite C/C++ interface can be used,
+128
View File
@@ -17,6 +17,134 @@ proc chng {date desc} {
puts "<DD><P><UL>$desc</UL></P></DD>"
}
chng {2001 Feb 19 (1.0.21)} {
<li>The UPDATE statement was not working when the WHERE clause contained
some terms that could be satisfied using indices and other terms that
could not. Fixed.</li>
}
chng {2001 Feb 11 (1.0.20)} {
<li>Merge development changes into the main trunk. Future work toward
using a BTree file structure will use a separate CVS source tree. This
CVS tree will continue to support the GDBM version of SQLite only.</li>
}
chng {2001 Feb 6 (1.0.19)} {
<li>Fix a strange (but valid) C declaration that was causing problems
for QNX. No logical changes.</li>
}
chng {2001 Jan 4 (1.0.18)} {
<li>Print the offending SQL statement when an error occurs.</li>
<li>Do not require commas between constraints in CREATE TABLE statements.</li>
<li>Added the "-echo" option to the shell.</li>
<li>Changes to comments.</li>
}
chng {2000 Dec 10 (1.0.17)} {
<li>Rewrote <b>sqlite_complete()</b> to make it faster.</li>
<li>Minor tweaks to other code to make it run a little faster.</li>
<li>Added new tests for <b>sqlite_complete()</b> and for memory leaks.</li>
}
chng {2000 Dec 4 (1.0.16)} {
<li>Documentation updates. Mostly fixing of typos and spelling errors.</li>
}
chng {2000 Oct 23 (1.0.15)} {
<li>Documentation updates</li>
<li>Some sanity checking code was removed from the inner loop of vdbe.c
to help the library to run a little faster. The code is only
removed if you compile with -DNDEBUG.</li>
}
chng {2000 Oct 19 (1.0.14)} {
<li>Added a "memory:" backend driver that stores its database in an
in-memory hash table.</li>
}
chng {2000 Oct 18 (1.0.13)} {
<li>Break out the GDBM driver into a separate file in anticipation
to added new drivers.</li>
<li>Allow the name of a database to be prefixed by the driver type.
For now, the only driver type is "gdbm:".</li>
}
chng {2000 Oct 16 (1.0.12)} {
<li>Fixed an off-by-one error that was causing a coredump in
the '%q' format directive of the new
<b>sqlite_..._printf()</b> routines.</li>
<li>Added the <b>sqlite_interrupt()</b> interface.</li>
<li>In the shell, <b>sqlite_interrupt()</b> is invoked when the
user presses Control-C</li>
<li>Fixed some instances where <b>sqlite_exec()</b> was
returning the wrong error code.</li>
}
chng {2000 Oct 11 (1.0.10)} {
<li>Added notes on how to compile for Windows95/98.</li>
<li>Removed a few variables that were not being used. Etc.</li>
}
chng {2000 Oct 8 (1.0.9)} {
<li>Added the <b>sqlite_..._printf()</b> interface routines.</li>
<li>Modified the <b>sqlite</b> shell program to use the new interface
routines.</li>
<li>Modified the <b>sqlite</b> shell program to print the schema for
the built-in SQLITE_MASTER table, if explicitly requested.</li>
}
chng {2000 Sep 30 (1.0.8)} {
<li>Begin writing documentation on the TCL interface.</li>
}
chng {2000 Sep 29 (Not Released)} {
<li>Added the <b>sqlite_get_table()</b> API</li>
<li>Updated the documentation for due to the above change.</li>
<li>Modified the <b>sqlite</b> shell to make use of the new
sqlite_get_table() API in order to print a list of tables
in multiple columns, similar to the way "ls" prints filenames.</li>
<li>Modified the <b>sqlite</b> shell to print a semicolon at the
end of each CREATE statement in the output of the ".schema" command.</li>
}
chng {2000 Sep 21 (Not Released)} {
<li>Change the tclsqlite "eval" method to return a list of results if
no callback script is specified.</li>
<li>Change tclsqlite.c to use the Tcl_Obj interface</li>
<li>Add tclsqlite.c to the libsqlite.a library</li>
}
chng {2000 Sep 13 (Version 1.0.5)} {
<li>Changed the print format for floating point values from "%g" to "%.15g".
</li>
<li>Changed the comparison function so that numbers in exponential notation
(ex: 1.234e+05) sort in numerical order.</li>
}
chng {2000 Aug 28 (Version 1.0.4)} {
<li>Added functions <b>length()</b> and <b>substr()</b>.</li>
<li>Fix a bug in the <b>sqlite</b> shell program that was causing
a coredump when the output mode was "column" and the first row
of data contained a NULL.</li>
}
chng {2000 Aug 22 (Version 1.0.3)} {
<li>In the sqlite shell, print the "Database opened READ ONLY" message
to stderr instead of stdout.</li>
<li>In the sqlite shell, now print the version number on initial startup.</li>
<li>Add the <b>sqlite_version[]</b> string constant to the library</li>
<li>Makefile updates</li>
<li>Bug fix: incorrect VDBE code was being generated for the following
circumstance: a query on an indexed table containing a WHERE clause with
an IN operator that had a subquery on its right-hand side.</li>
}
chng {2000 Aug 18 (Version 1.0.1)} {
<li>Fix a bug in the configure script.</li>
<li>Minor revisions to the website.</li>
}
chng {2000 Aug 17 (Version 1.0)} {
<li>Change the <b>sqlite</b> program so that it can read
databases for which it lacks write permission. (It used to
+62 -2
View File
@@ -1,7 +1,7 @@
#
# Run this Tcl script to generate the crosscompile.html file.
#
set rcsid {$Id: crosscompile.tcl,v 1.2 2000/07/31 19:16:32 drh Exp $}
set rcsid {$Id: crosscompile.tcl,v 1.4 2001/01/31 13:28:09 drh Exp $}
puts {<html>
<head>
@@ -23,7 +23,9 @@ binaries. See the website for details.</p>
<p>This page describes how you can use MinGW configured as a
cross-compiler running under RedHat 6.0 Linux to generate a
binary for SQLite that runs under WinNT.</p>
binary for SQLite that runs under WinNT.
Some additional steps (described <a href="#win95">below</a>)
are needed to target Win95/98.</p>
}
proc Code {body} {
@@ -123,6 +125,64 @@ make
</pre></blockquote>
</li>
</ol>
<a name="win95">
<h2>Targetting Windows95/98 instead of WindowsNT</h2>
<p>A small amount of additional work is needed to get SQLite running
under Windows95/98. The first problem is that the LockFile() and
UnlockFile() API that the Roth GDBM port uses does not work under
Windows95. The easiest workaround is to just disable file locking
in the GDBM library. You can do so by appending a few lines of code
to the end of one of the GDBM source files and compiling the library
using a special command-line option. Replace step (4) above as
follows:</p>
<ol>
<li value="4"><p>
Append text to the <b>systems.h</b> source file as follows:</p>
<blockquote><pre>
cat &gt;&gt;systems.h &lt;&lt;\END
#ifdef NO_LOCKS
#undef UNLOCK_FILE
#define UNLOCK_FILE(x)
#undef READLOCK_FILE
#define READLOCK_FILE(x) lock_val=0;
#undef WRITELOCK_FILE
#define WRITELOCK_FILE(x) lock_val=0;
#endif
END
</pre></blockquote>
<p>Then manually build the GDBM library with the extra
"NO_LOCKS" define as follows:</p>
<blockquote><pre>
i386-mingw32-gcc -DWIN32=1 -DNO_LOCKS -O2 -c *.c
i386-mingw32-ar cr libgdbm.a *.o
i386-mingw32-ranlib libgdbm.a
cd ..
</pre></blockquote>
</p></li>
</ol>
<p>Note that the locking problem has been reported and may actually
be fixed in the Roth GDBM distribution by the time you read this.
You should probably check before you make the above changes.</p>
<p>The above is all you have to do to get SQLite to work on Windows95.
But one more step is required to get it to work <i>well</i>. It
turns out that SQLite make heavy use of <b>malloc()</b> and
<b>free()</b> and the implementation of this functions
on Windows95 is particular poor. Large database queries will run
more than 10 times faster if you substitute a better memory allocator
such as the one by
<a href="http://g.oswego.edu/dl/html/malloc.html">Doug Lea</a>.
A copy of Doug's allocator is included in the <b>contrib</b>
directory of the source tree. Speed improvements are also reported
on WindowsNT when alternative memory allocators are used, though
the speedup is not as dramatic as it is with WIndows95.</p>
}
puts {
<p><hr /></p>
+70
View File
@@ -0,0 +1,70 @@
#
# Run this Tcl script to generate the dynload.html file.
#
set rcsid {$Id: dynload.tcl,v 1.1 2001/02/11 16:58:22 drh Exp $}
puts {<html>
<head>
<title>How to build a dynamically loaded Tcl extension for SQLite</title>
</head>
<body bgcolor=white>
<h1 align=center>
How To Build A Dynamically Loaded Tcl Extension
</h1>}
puts {<p>
<i>This note was contributed by
<a href="bsaunder@tampabay.rr.com.nospam">Bill Saunders</a>. Thanks, Bill!</i>
<p>
To compile the SQLite Tcl extension into a dynamically loaded module
I did the following:
</p>
<ol>
<li><p>Do a standard compile
(I had a dir called bld at the same level as sqlite ie
/root/bld
/root/sqlite
I followed the directions and did a standard build in the bld
directory)</p></li>
<li><p>
Now do the following in the bld directory
<blockquote><pre>
gcc -shared -I. -lgdbm ../sqlite/src/tclsqlite.c libsqlite.a -o sqlite.so
</pre></blockquote></p></li>
<li><p>
This should produce the file sqlite.so in the bld directory</p></li>
<li><p>
Create a pkgIndex.tcl file that contains this line
<blockquote><pre>
package ifneeded sqlite 1.0 [list load [file join $dir sqlite.so]]
</pre></blockquote></p></li>
<li><p>
To use this put sqlite.so and pkgIndex.tcl in the same directory</p></li>
<li><p>
From that directory start wish</p></li>
<li><p>
Execute the following tcl command (tells tcl where to fine loadable
modules)
<blockquote><pre>
lappend auto_path [exec pwd]
</pre></blockquote></p></li>
<li><p>
Load the package
<blockquote><pre>
package require sqlite
</pre></blockquote></p></li>
<li><p>
Have fun....</p></li>
</ul>
</body></html>}
+41 -30
View File
@@ -1,12 +1,12 @@
#
# Run this TCL script to generate HTML for the index.html file.
#
set rcsid {$Id: index.tcl,v 1.27 2000/08/17 10:22:34 drh Exp $}
set rcsid {$Id: index.tcl,v 1.30 2001/02/19 18:24:22 drh Exp $}
puts {<html>
<head><title>SQLite: An SQL Database Engine Built Atop GDBM</title></head>
<head><title>SQLite: An SQL Database Library Built Atop GDBM</title></head>
<body bgcolor=white>
<h1 align=center>SQLite: An SQL Database Engine Built Atop
<h1 align=center>SQLite: An SQL Database Library Built Atop
<a href="http://www.gnu.org/software/gdbm/gdbm.html">GDBM</a></h1>
<p align=center>}
puts "This page was last modified on [lrange $rcsid 3 4] GMT<br>"
@@ -17,33 +17,17 @@ puts {</p>}
puts {<h2>Introduction</h2>
<p>SQLite is an SQL database engine built on top of the
<a href="http://www.gnu.org/software/gdbm/gdbm.html">GDBM library</a>.
SQLite includes a standalone command-line
access program (<a href="sqlite.html">sqlite</a>)
and a C library (<a href="c_interface.html">libsqlite.a</a>)
that can be linked
with a C/C++ program to provide SQL database access without
a separate RDBMS.</p>
<p>SQLite is an SQL database library
(<a href="c_interface.html">libsqlite.a</a>) that uses
<a href="http://www.gnu.org/software/gdbm/gdbm.html">GDBM</a>
as its underlying file storage mechanism.
Programs that link the SQLite library can have SQL database
access without running a separate RDBMS process.
The distribution comes with a standalone command-line
access program (<a href="sqlite.html">sqlite</a>) that can
be used to administer an SQLite database and which serves as
an example of how to use the SQLite library.</p>
<h2>Important News Flash!</h2>
<p>
The SQLite file format was changed in an incompatible way on
Aug 2, 2000. If you are updated the library and have databases
built using the old version of the library, you should save your
old databases into an ASCII file then reimport those
database using the new library. For example, if you change the
name of the old <b>sqlite</b> utility to "old-sqlite" and
change the name of the old database directory to "old-db", then
you can reconstruct the database as follows:</p>
<blockquote><pre>
echo .dump | old-sqlite old-db | sqlite db
</pre></blockquote>
<p>This file format change was made to work around a potential
inefficiency in GDBM that comes up when large indices are created
on tables where many entries in the table have the same index key.</p>
<h2>Features</h2>
@@ -56,7 +40,7 @@ on tables where many entries in the table have the same index key.</p>
<li>Very simple
<a href="c_interface.html">C/C++ interface</a> requires the use of only
three functions and one opaque structure.</li>
<li>A <a href="http://dev.scriptics.com/">Tcl</a> interface is
<li>A <a href="tclsqlite.html">Tcl</a> interface is
included.</li>
<li>Command-line access program <a href="sqlite.html">sqlite</a> uses
the <a href="http://www.google.com/search?q=gnu+readline+library">GNU
@@ -77,6 +61,12 @@ all code except for a few areas which are unreachable or which are
only reached when <tt>malloc()</tt> fails. The code has been tested
for memory leaks and is found to be clean.</p>
<p><b>Important Note:</b> A bug was found in the processing of UPDATE
statements when the WHERE clause contained some terms that could be
satisfied using indices and other terms which could not. The problem
was fixed in version 1.0.21. Users of prior versions of SQLite should
consider upgrading.</p>
<p>
Among the SQL features that SQLite does not currently implement are:</p>
@@ -88,6 +78,26 @@ Among the SQL features that SQLite does not currently implement are:</p>
</ul>
</p>
<h2>Important News Flash!</h2>
<p>
The SQLite file format was changed in an incompatible way on
Aug 2, 2000 (prior to version 1.0, when SQLite was still in Beta).
If you are updated the library and have databases
built using the old version of the library, you should save your
old databases into an ASCII file then reimport the
database using the new library. For example, if you change the
name of the old <b>sqlite</b> utility to "old-sqlite" and
change the name of the old database directory to "old-db", then
you can reconstruct the database as follows:</p>
<blockquote><pre>
echo .dump | old-sqlite old-db | sqlite db
</pre></blockquote>
<p>This file format change was made to work around a potential
inefficiency in GDBM that comes up when large indices are created
on tables where many entries in the table have the same index key.</p>
<h2>Documentation</h2>
<p>The following documentation is currently available:</p>
@@ -97,6 +107,7 @@ Among the SQL features that SQLite does not currently implement are:</p>
command-line utility.</li>
<li>The <a href="lang.html">SQL Language</a> subset understood by SQLite.</li>
<li>The <a href="c_interface.html">C/C++ Interface</a>.</li>
<li>The <a href="tclsqlite.html">Tcl Interface</a>.</li>
<li>The <a href="fileformat.html">file format</a> used by SQLite databases.</li>
<li>The <a href="arch.html">Architecture of the SQLite Library</a> describes
how the library is put together.</li>
+274
View File
@@ -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>&nbsp;&nbsp;<i>dbcmd&nbsp;&nbsp;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>&nbsp;&nbsp;<b>eval</b>&nbsp;&nbsp;<i>sql</i>
&nbsp;&nbsp;?<i>array-name&nbsp;&nbsp;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>
&nbsp;&nbsp;&nbsp;&nbsp;parray values<br>
&nbsp;&nbsp;&nbsp;&nbsp;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>
&nbsp;&nbsp;&nbsp;&nbsp;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
View File
@@ -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