Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d6b1cf5a7e | |||
| b8525d9dcf | |||
| b072950e24 | |||
| 65cd7c02e7 | |||
| 3c9ebf144e | |||
| e53dea15d1 | |||
| 7d822f292b | |||
| a6e982a256 | |||
| 573bd2754f | |||
| 827e656177 | |||
| 4f3c63e475 | |||
| 4e0dec47d7 | |||
| 03e7e9516f | |||
| 338ea13c0d | |||
| 2e7051b3ec | |||
| 98a0b0834d | |||
| c58011e862 | |||
| b5730347ad | |||
| 17a6893463 | |||
| d1881e6165 | |||
| b5b452cb58 | |||
| acbcdc49c6 | |||
| d9e5075ff6 | |||
| 2702114089 | |||
| 41a2b48bd0 | |||
| e5080e4e9e | |||
| 345fda3ef5 | |||
| ae85dc8b0b | |||
| 993c2a7c8f | |||
| 2fad28e310 | |||
| 660f68d6a2 | |||
| 4b2d64a9c9 | |||
| 41adf6c327 | |||
| a2e1bb5a58 | |||
| 68b8f0a1ef | |||
| acf4575752 | |||
| 144f1d64b5 | |||
| 78e6f71fcc | |||
| 8c82b3506e | |||
| af90b19472 | |||
| 66b89c8f68 | |||
| d9a4698e5f | |||
| 52d17876d1 | |||
| 8c3052c49b | |||
| 76634487b6 | |||
| ccb2a3daf6 | |||
| ed5423d5ae | |||
| 549cfc6c80 | |||
| 3cebbde37e | |||
| efa4e17240 | |||
| 01d01eeee0 | |||
| 42f04222c0 | |||
| 99e86e6b0b | |||
| 767c2001c6 | |||
| e907bfc2ac | |||
| bb0b679c02 | |||
| 58c2976fa9 | |||
| 9b1eb48b7d | |||
| 1afad7c98a | |||
| 611ad21d8b | |||
| 773ab7ae4c | |||
| 4c5043910b | |||
| b2b415b97f |
+15
-3
@@ -47,15 +47,17 @@ 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 printf.o select.o table.o tokenize.o update.o \
|
||||
util.o vdbe.o where.o tclsqlite.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 \
|
||||
@@ -64,6 +66,7 @@ SRC = \
|
||||
$(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 \
|
||||
@@ -118,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
|
||||
|
||||
@@ -154,6 +163,9 @@ 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
|
||||
|
||||
|
||||
-3276
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
The SQL database used for ACD contains 113 tables and indices implemented
|
||||
in GDBM. The following are statistics on the sizes of keys and data
|
||||
within these tables and indices.
|
||||
|
||||
Entries: 962080
|
||||
Size: 45573853
|
||||
Avg Size: 48
|
||||
Key Size: 11045299
|
||||
Avg Key Size: 12
|
||||
Max Key Size: 99
|
||||
|
||||
0..8 266 0%
|
||||
9..12 5485 0%
|
||||
13..16 73633 8%
|
||||
17..24 180918 27%
|
||||
25..32 209823 48%
|
||||
33..40 148995 64%
|
||||
41..48 76304 72%
|
||||
49..56 14346 73%
|
||||
57..64 15725 75%
|
||||
65..80 44916 80%
|
||||
81..96 127815 93%
|
||||
97..112 34769 96%
|
||||
113..128 13314 98%
|
||||
129..144 8098 99%
|
||||
145..160 3355 99%
|
||||
161..176 1159 99%
|
||||
177..192 629 99%
|
||||
193..208 221 99%
|
||||
209..224 210 99%
|
||||
225..240 129 99%
|
||||
241..256 57 99%
|
||||
257..288 496 99%
|
||||
289..320 60 99%
|
||||
321..352 37 99%
|
||||
353..384 46 99%
|
||||
385..416 22 99%
|
||||
417..448 24 99%
|
||||
449..480 26 99%
|
||||
481..512 27 99%
|
||||
513..1024 471 99%
|
||||
1025..2048 389 99%
|
||||
2049..4096 182 99%
|
||||
4097..8192 74 99%
|
||||
8193..16384 34 99%
|
||||
16385..32768 17 99%
|
||||
32769..65536 5 99%
|
||||
65537..131073 3 100%
|
||||
@@ -1,82 +1,97 @@
|
||||
C Version\s1.0.10\s(CVS\s492)
|
||||
D 2000-10-11T19:30:00
|
||||
C Version\s1.0.24\s(CVS\s479)
|
||||
D 2001-03-14T13:00:00
|
||||
F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4
|
||||
F Makefile.in faecea9b6419cec25030b4818c9b3f7f4163b3c1
|
||||
F Makefile.in 7efa81e2985b45ba73db27d55b70cc927f5abfd7
|
||||
F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958
|
||||
F VERSION 0302755bd219af2c39c53cc097caccb858798223
|
||||
F VERSION 404ea684e576d2b596e185903cea48b3e984c7fd
|
||||
F configure 3dc1edb9dcf60215e31ff72b447935ab62211442 x
|
||||
F configure.in d892ca33db7e88a055519ce2f36dcb11020e8fff
|
||||
F contrib/dlmalloc.c 754f43cd3b225728017b2516a58103f4bc3f05ee
|
||||
F doc/lemon.html e233a3e97a779c7a87e1bc4528c664a58e49dd47
|
||||
F src/build.c 4d90e9e94750ca80249fc7958c617021d8bb7a50
|
||||
F src/dbbe.c 226daaf8c095ceb4aff48cad188dad90643f9867
|
||||
F src/dbbe.h 6337132f904e72ecb28b07390021c241397e4cbf
|
||||
F src/dbbemem.c eb3d79be7105bd80069815ee499c8e8682876378
|
||||
F src/delete.c 4d491eaf61b515516749c7ed68fa3b2ee8a09065
|
||||
F src/expr.c e8e350d7baa33bd9ed8701c159eaba5e912e0adb
|
||||
F src/insert.c f146f149ad2422a1dc3bfa7a1651a25940f98958
|
||||
F src/main.c 03ca6d7601b40a3c6de95d7f8c6c817e8cca9fdf
|
||||
F src/parse.y 5d199034de5d29ebedb42c1c51f34db4df40cbe5
|
||||
F src/printf.c 6fc343f0c3537e2530a11ac164ce64ba171aebf8
|
||||
F src/select.c d382e96c2221d08367cc87976f2b574537c9de97
|
||||
F src/shell.c ef5d12129c824cb98238763e9e86ca1847e0c7bd
|
||||
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 576a0a0ad675063a1dd2c4aae68233232bd2deac
|
||||
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 faac634ef0c717bc82ca112a4531a257886f2c7a
|
||||
F src/shell.c 441e20913cde0bb71281f4027623c623530241cd
|
||||
F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e
|
||||
F src/sqlite.h.in 7a11f9d131e9657d11dee5c23dfe458da91de8e2
|
||||
F src/sqliteInt.h b65fdecac7281aafb4c9ff3e79ea1b5546478385
|
||||
F src/table.c f08189678c806d8a74a70c156d7c27083760e028
|
||||
F src/tclsqlite.c 44b08b47612a668caaf7c4ec32133b69d73ff78e
|
||||
F src/tokenize.c 097bec5843d4a0fb4509e036fee93bac080c5e73
|
||||
F src/update.c 51b9ef7434b15e31096155da920302e9db0d27fc
|
||||
F src/util.c 811e0ad47f842c16555aaf361b26dab7221c1a6c
|
||||
F src/vdbe.c b8b0bc26d6543d5d33820809122bf0e7659e4663
|
||||
F src/vdbe.h 8ca755f77eeaa93886d25188fd66129febfa16cf
|
||||
F src/where.c 3dfad2ffd0aa994d5eceac88852f7189c8d1d3c8
|
||||
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 8365b3922ea098330d1e20862d6e64911e4e03d0
|
||||
F src/util.c f4573201fc2b581dbf601c53787349310b7da150
|
||||
F src/vdbe.c e8254ae39d2f42a3f85b168b67a493ae206f4bf4
|
||||
F src/vdbe.h 031b7dd7d6f94c51dc37cdf26efe43d1619bb672
|
||||
F src/where.c 478fde7c930969ca428de2d80b137959d25ee2fb
|
||||
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 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 ad57d7e8114b0691eb5e943b9dabbd68119b8e2c
|
||||
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/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 tool/report1.txt 9eae07f26a8fc53889b45fc833a66a33daa22816
|
||||
F www/arch.fig 4f246003b7da23bd63b8b0af0618afb4ee3055c8
|
||||
F www/arch.png 8dae0766d42ed3de9ed013c1341a5792bcf633e6
|
||||
F www/arch.tcl a40380c1fe0080c43e6cc5c20ed70731511b06be
|
||||
F www/c_interface.tcl 03f5fe8f2da9713aa1748f7e4ce34a0c35926ac9
|
||||
F www/changes.tcl fcd35239062ac03d6169506ae8ef19189979c8f1
|
||||
F www/crosscompile.tcl bee79c34f6c3f162ec1c6f5294e79f73651d27ee
|
||||
F www/c_interface.tcl 11be2d5826eb7d6efd629751d3b483c1ed78ba14
|
||||
F www/changes.tcl a8cbb3496ae35184fabc5f7b47242bf33a5637d1
|
||||
F www/crosscompile.tcl c99efacb3aefaa550c6e80d91b240f55eb9fd33e
|
||||
F www/dynload.tcl 02eb8273aa78cfa9070dd4501dca937fb22b466c
|
||||
F www/fileformat.tcl cfb7fba80b7275555281ba2f256c00734bcdd1c9
|
||||
F www/index.tcl b19418d506f90968deef972bf1b427d98bdf13e0
|
||||
F www/lang.tcl 9192e114b19987e630a41e879585b87006eb84a1
|
||||
F www/index.tcl 0ca6421e6e82b17ed0c1779d46463211498f9d12
|
||||
F www/lang.tcl e3905bec9f0d0fd47d9838e991cab7d6f7aff47d
|
||||
F www/mingw.tcl fc5f4ba9d336b6e8c97347cc6496d6162461ef60
|
||||
F www/opcode.tcl cb3a1abf8b7b9be9f3a228d097d6bf8b742c2b6f
|
||||
F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f
|
||||
F www/tclsqlite.tcl ae101d5f7c07dcc59770e2a84aae09025fab2dad
|
||||
F www/vdbe.tcl bcbfc33bcdd0ebad95eab31286adb9e1bc289520
|
||||
P 217a8af6d0f4396b755b7d21f94ae6fe002728a1
|
||||
R 0e3d2b823401c2dde691e21fb25d6652
|
||||
F www/tclsqlite.tcl 06f81c401f79a04f2c5ebfb97e7c176225c0aef2
|
||||
F www/vdbe.tcl 0c8aaa529dd216ccbf7daaabd80985e413d5f9ad
|
||||
P 8f79ca2acaf765ecb3274c353284c55b6790f657
|
||||
R 360be56ced6760c85770c34d6c105ac8
|
||||
U drh
|
||||
Z eae7458cfefbc493be1d4a350c873f88
|
||||
Z 7f6063d380c9dc73dddd8b75f41104c4
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
e0c9e80bdbaf672c72ead7827946bb9ac6b7ec32
|
||||
34b17e6ce15dd24dc2ca609ef9c136fe4dc1b4b5
|
||||
@@ -0,0 +1,35 @@
|
||||
* Finish fleshing out the db.c file.
|
||||
- sqliteDbReadOvfl
|
||||
- sqliteDbSpreadLoad
|
||||
- sqliteDbSplit
|
||||
- sqliteDbNextIndexLevel
|
||||
- fix sqliteDbCursorNext to work right after sqliteDbCursorDelete
|
||||
* Compile db.c with -Wall and get no errors.
|
||||
* Make a pass over pg.c and db.c looking for errors.
|
||||
- correct handling of I/O errors, malloc failures, etc.
|
||||
- page leaks (not calling sqlitePgUnref)
|
||||
* Write a test interface for db.c.
|
||||
* Compile and link against the db test interface.
|
||||
* Generate tests for the db interface.
|
||||
* Add read/write locks to pg.c
|
||||
* Add an sqliteDbReorganize() function.
|
||||
* Integrate db into vdbe.
|
||||
* Modify code generation to take advantage of the new db interface.
|
||||
- Able to delete without disturbing scan order
|
||||
- Now keeps a count of number of table entries
|
||||
+ Special processing for count(*)
|
||||
+ Better selection of indices on a select
|
||||
- Transactions
|
||||
* Modify sqlite_master to store the table number.
|
||||
* Add a cache in DbCursor to speed up the sqliteDbReadOvfl() routine.
|
||||
* Add cache information to speed up sqliteDbCursorMoveTo().
|
||||
|
||||
Longer term:
|
||||
* Document all the changes and release Sqlite 2.0.
|
||||
* Techniques for optimizing querys by grouping data with similar
|
||||
indices.
|
||||
* "OPTIMIZE select" statement to automatically create and/or tune
|
||||
indices.
|
||||
* "CREATE INDEX FOR select" to automatically generate needed indices.
|
||||
* "VACUUM table USING index".
|
||||
* Parse and use constraints.
|
||||
+16
-14
@@ -33,7 +33,7 @@
|
||||
** COPY
|
||||
** VACUUM
|
||||
**
|
||||
** $Id: build.c,v 1.23 2000/08/03 15:09:20 drh Exp $
|
||||
** $Id: build.c,v 1.25 2001/01/15 22:51:09 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -48,20 +48,22 @@
|
||||
** no VDBE code was generated.
|
||||
*/
|
||||
void sqliteExec(Parse *pParse){
|
||||
int rc = SQLITE_OK;
|
||||
if( pParse->pVdbe ){
|
||||
if( pParse->explain ){
|
||||
sqliteVdbeList(pParse->pVdbe, pParse->xCallback, pParse->pArg,
|
||||
&pParse->zErrMsg);
|
||||
rc = sqliteVdbeList(pParse->pVdbe, pParse->xCallback, pParse->pArg,
|
||||
&pParse->zErrMsg);
|
||||
}else{
|
||||
FILE *trace = (pParse->db->flags & SQLITE_VdbeTrace)!=0 ? stderr : 0;
|
||||
sqliteVdbeTrace(pParse->pVdbe, trace);
|
||||
sqliteVdbeExec(pParse->pVdbe, pParse->xCallback, pParse->pArg,
|
||||
&pParse->zErrMsg, pParse->db->pBusyArg,
|
||||
pParse->db->xBusyCallback);
|
||||
rc = sqliteVdbeExec(pParse->pVdbe, pParse->xCallback, pParse->pArg,
|
||||
&pParse->zErrMsg, pParse->db->pBusyArg,
|
||||
pParse->db->xBusyCallback);
|
||||
}
|
||||
sqliteVdbeDelete(pParse->pVdbe);
|
||||
pParse->pVdbe = 0;
|
||||
pParse->colNamesSet = 0;
|
||||
pParse->rc = rc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +341,7 @@ void sqliteEndTable(Parse *pParse, Token *pEnd){
|
||||
*/
|
||||
if( !pParse->initFlag ){
|
||||
static VdbeOp addTable[] = {
|
||||
{ OP_Open, 0, 1, MASTER_NAME },
|
||||
{ OP_OpenTbl, 0, 1, MASTER_NAME },
|
||||
{ OP_New, 0, 0, 0},
|
||||
{ OP_String, 0, 0, "table" },
|
||||
{ OP_String, 0, 0, 0}, /* 3 */
|
||||
@@ -414,7 +416,7 @@ void sqliteDropTable(Parse *pParse, Token *pName){
|
||||
v = sqliteGetVdbe(pParse);
|
||||
if( v ){
|
||||
static VdbeOp dropTable[] = {
|
||||
{ OP_Open, 0, 1, MASTER_NAME },
|
||||
{ OP_OpenTbl, 0, 1, MASTER_NAME },
|
||||
{ OP_ListOpen, 0, 0, 0},
|
||||
{ OP_String, 0, 0, 0}, /* 2 */
|
||||
{ OP_Next, 0, ADDR(10), 0}, /* 3 */
|
||||
@@ -591,7 +593,7 @@ void sqliteCreateIndex(
|
||||
*/
|
||||
if( pParse->initFlag==0 ){
|
||||
static VdbeOp addTable[] = {
|
||||
{ OP_Open, 2, 1, MASTER_NAME},
|
||||
{ OP_OpenTbl, 2, 1, MASTER_NAME},
|
||||
{ OP_New, 2, 0, 0},
|
||||
{ OP_String, 0, 0, "index"},
|
||||
{ OP_String, 0, 0, 0}, /* 3 */
|
||||
@@ -608,8 +610,8 @@ void sqliteCreateIndex(
|
||||
|
||||
v = sqliteGetVdbe(pParse);
|
||||
if( v==0 ) goto exit_create_index;
|
||||
sqliteVdbeAddOp(v, OP_Open, 0, 0, pTab->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_Open, 1, 1, pIndex->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenTbl, 0, 0, pTab->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, 1, 1, pIndex->zName, 0);
|
||||
if( pStart && pEnd ){
|
||||
int base;
|
||||
n = (int)pEnd->z - (int)pStart->z + 1;
|
||||
@@ -668,7 +670,7 @@ void sqliteDropIndex(Parse *pParse, Token *pName){
|
||||
v = sqliteGetVdbe(pParse);
|
||||
if( v ){
|
||||
static VdbeOp dropIndex[] = {
|
||||
{ OP_Open, 0, 1, MASTER_NAME},
|
||||
{ OP_OpenTbl, 0, 1, MASTER_NAME},
|
||||
{ OP_ListOpen, 0, 0, 0},
|
||||
{ OP_String, 0, 0, 0}, /* 2 */
|
||||
{ OP_Next, 0, ADDR(9), 0}, /* 3 */
|
||||
@@ -841,9 +843,9 @@ void sqliteCopy(
|
||||
addr = sqliteVdbeAddOp(v, OP_FileOpen, 0, 0, 0, 0);
|
||||
sqliteVdbeChangeP3(v, addr, pFilename->z, pFilename->n);
|
||||
sqliteVdbeDequoteP3(v, addr);
|
||||
sqliteVdbeAddOp(v, OP_Open, 0, 1, pTab->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenTbl, 0, 1, pTab->zName, 0);
|
||||
for(i=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
|
||||
sqliteVdbeAddOp(v, OP_Open, i, 1, pIdx->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, i, 1, pIdx->zName, 0);
|
||||
}
|
||||
end = sqliteVdbeMakeLabel(v);
|
||||
addr = sqliteVdbeAddOp(v, OP_FileRead, pTab->nCol, end, 0, 0);
|
||||
|
||||
+89
-631
@@ -30,124 +30,18 @@
|
||||
** relatively simple to convert to a different database such
|
||||
** as NDBM, SDBM, or BerkeleyDB.
|
||||
**
|
||||
** $Id: dbbe.c,v 1.19 2000/08/17 09:50:00 drh Exp $
|
||||
** $Id: dbbe.c,v 1.23 2001/01/15 22:51:10 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <gdbm.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
/*
|
||||
** Information about each open disk file is an instance of this
|
||||
** structure. There will only be one such structure for each
|
||||
** disk file. If the VDBE opens the same file twice (as will happen
|
||||
** for a self-join, for example) then two DbbeCursor structures are
|
||||
** created but there is only a single BeFile structure with an
|
||||
** nRef of 2.
|
||||
*/
|
||||
typedef struct BeFile BeFile;
|
||||
struct BeFile {
|
||||
char *zName; /* Name of the file */
|
||||
GDBM_FILE dbf; /* The file itself */
|
||||
int nRef; /* Number of references */
|
||||
int delOnClose; /* Delete when closing */
|
||||
int writeable; /* Opened for writing */
|
||||
BeFile *pNext, *pPrev; /* Next and previous on list of open files */
|
||||
};
|
||||
|
||||
/*
|
||||
** The following structure holds the current state of the RC4 algorithm.
|
||||
** We use RC4 as a random number generator. Each call to RC4 gives
|
||||
** a random 8-bit number.
|
||||
**
|
||||
** Nothing in this file or anywhere else in SQLite does any kind of
|
||||
** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random
|
||||
** number generator) not as an encryption device.
|
||||
*/
|
||||
struct rc4 {
|
||||
int i, j;
|
||||
int s[256];
|
||||
};
|
||||
|
||||
/*
|
||||
** The complete database is an instance of the following structure.
|
||||
*/
|
||||
struct Dbbe {
|
||||
char *zDir; /* The directory containing the database */
|
||||
int write; /* True for write permission */
|
||||
BeFile *pOpen; /* List of open files */
|
||||
int nTemp; /* Number of temporary files created */
|
||||
FILE **apTemp; /* Space to hold temporary file pointers */
|
||||
char **azTemp; /* Names of the temporary files */
|
||||
struct rc4 rc4; /* The random number generator */
|
||||
};
|
||||
|
||||
/*
|
||||
** An cursor into a database file is an instance of the following structure.
|
||||
** There can only be a single BeFile structure for each disk file, but
|
||||
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
|
||||
** a cursor pointing to a particular part of the open BeFile. The
|
||||
** BeFile.nRef field hold a count of the number of DbbeCursor structures
|
||||
** associated with the same disk file.
|
||||
*/
|
||||
struct DbbeCursor {
|
||||
Dbbe *pBe; /* The database of which this record is a part */
|
||||
BeFile *pFile; /* The database file for this table */
|
||||
datum key; /* Most recently used key */
|
||||
datum data; /* Most recent data */
|
||||
int needRewind; /* Next key should be the first */
|
||||
int readPending; /* The fetch hasn't actually been done yet */
|
||||
};
|
||||
|
||||
/*
|
||||
** Initialize the RC4 PRNG. "seed" is a pointer to some random
|
||||
** data used to initialize the PRNG.
|
||||
*/
|
||||
static void rc4init(struct rc4 *p, char *seed, int seedlen){
|
||||
int i;
|
||||
char k[256];
|
||||
p->j = 0;
|
||||
p->i = 0;
|
||||
for(i=0; i<256; i++){
|
||||
p->s[i] = i;
|
||||
k[i] = seed[i%seedlen];
|
||||
}
|
||||
for(i=0; i<256; i++){
|
||||
int t;
|
||||
p->j = (p->j + p->s[i] + k[i]) & 0xff;
|
||||
t = p->s[p->j];
|
||||
p->s[p->j] = p->s[i];
|
||||
p->s[i] = t;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a single 8-bit random value from the RC4 PRNG.
|
||||
*/
|
||||
static int rc4byte(struct rc4 *p){
|
||||
int t;
|
||||
p->i = (p->i + 1) & 0xff;
|
||||
p->j = (p->j + p->s[p->i]) & 0xff;
|
||||
t = p->s[p->i];
|
||||
p->s[p->i] = p->s[p->j];
|
||||
p->s[p->j] = t;
|
||||
t = p->s[p->i] + p->s[p->j];
|
||||
return t & 0xff;
|
||||
}
|
||||
|
||||
/*
|
||||
** The "mkdir()" function only takes one argument under Windows.
|
||||
*/
|
||||
#if OS_WIN
|
||||
# define mkdir(A,B) mkdir(A)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** This routine opens a new database. For the GDBM driver
|
||||
** implemented here, the database name is the name of the directory
|
||||
** containing all the files of the database.
|
||||
** This routine opens a new database. It looks at the first
|
||||
** few characters of the database name to try to determine what
|
||||
** kind of database to open. If the first characters are "gdbm:",
|
||||
** then it uses the GDBM driver. If the first few characters are
|
||||
** "memory:" then it uses the in-memory driver. If there is no
|
||||
** match, the default to the GDBM driver.
|
||||
**
|
||||
** If successful, a pointer to the Dbbe structure is returned.
|
||||
** If there are errors, an appropriate error message is left
|
||||
@@ -159,522 +53,15 @@ Dbbe *sqliteDbbeOpen(
|
||||
int createFlag, /* True to create database if it doesn't exist */
|
||||
char **pzErrMsg /* Write error messages (if any) here */
|
||||
){
|
||||
Dbbe *pNew;
|
||||
struct stat statbuf;
|
||||
char *zMaster;
|
||||
|
||||
if( !writeFlag ) createFlag = 0;
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
if( createFlag ) mkdir(zName, 0750);
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
sqliteSetString(pzErrMsg, createFlag ?
|
||||
"can't find or create directory \"" : "can't find directory \"",
|
||||
zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
extern Dbbe *sqliteGdbmOpen(const char*,int,int,char**);
|
||||
if( strncmp(zName, "gdbm:", 5)==0 ){
|
||||
return sqliteGdbmOpen(&zName[5], writeFlag, createFlag, pzErrMsg);
|
||||
}
|
||||
if( !S_ISDIR(statbuf.st_mode) ){
|
||||
sqliteSetString(pzErrMsg, "not a directory: \"", zName, "\"", 0);
|
||||
return 0;
|
||||
if( strncmp(zName, "memory:", 7)==0 ){
|
||||
extern Dbbe *sqliteMemOpen(const char*,int,int,char**);
|
||||
return sqliteMemOpen(&zName[7], writeFlag, createFlag, pzErrMsg);
|
||||
}
|
||||
if( access(zName, writeFlag ? (X_OK|W_OK|R_OK) : (X_OK|R_OK)) ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied", 0);
|
||||
return 0;
|
||||
}
|
||||
zMaster = 0;
|
||||
sqliteSetString(&zMaster, zName, "/" MASTER_NAME ".tbl", 0);
|
||||
if( stat(zMaster, &statbuf)==0
|
||||
&& access(zMaster, writeFlag ? (W_OK|R_OK) : R_OK)!=0 ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied for ", zMaster, 0);
|
||||
sqliteFree(zMaster);
|
||||
return 0;
|
||||
}
|
||||
sqliteFree(zMaster);
|
||||
pNew = sqliteMalloc(sizeof(Dbbe) + strlen(zName) + 1);
|
||||
if( pNew==0 ){
|
||||
sqliteSetString(pzErrMsg, "out of memory", 0);
|
||||
return 0;
|
||||
}
|
||||
pNew->zDir = (char*)&pNew[1];
|
||||
strcpy(pNew->zDir, zName);
|
||||
pNew->write = writeFlag;
|
||||
pNew->pOpen = 0;
|
||||
time(&statbuf.st_ctime);
|
||||
rc4init(&pNew->rc4, (char*)&statbuf, sizeof(statbuf));
|
||||
return pNew;
|
||||
}
|
||||
|
||||
/*
|
||||
** Completely shutdown the given database. Close all files. Free all memory.
|
||||
*/
|
||||
void sqliteDbbeClose(Dbbe *pBe){
|
||||
BeFile *pFile, *pNext;
|
||||
int i;
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pNext){
|
||||
pNext = pFile->pNext;
|
||||
gdbm_close(pFile->dbf);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
for(i=0; i<pBe->nTemp; i++){
|
||||
if( pBe->apTemp[i]!=0 ){
|
||||
unlink(pBe->azTemp[i]);
|
||||
fclose(pBe->apTemp[i]);
|
||||
sqliteFree(pBe->azTemp[i]);
|
||||
pBe->apTemp[i] = 0;
|
||||
pBe->azTemp[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sqliteFree(pBe->azTemp);
|
||||
sqliteFree(pBe->apTemp);
|
||||
memset(pBe, 0, sizeof(*pBe));
|
||||
sqliteFree(pBe);
|
||||
}
|
||||
|
||||
/*
|
||||
** Translate the name of an SQL table (or index) into the name
|
||||
** of a file that holds the key/data pairs for that table or
|
||||
** index. Space to hold the filename is obtained from
|
||||
** sqliteMalloc() and must be freed by the calling function.
|
||||
*/
|
||||
static char *sqliteFileOfTable(Dbbe *pBe, const char *zTable){
|
||||
char *zFile = 0;
|
||||
int i;
|
||||
sqliteSetString(&zFile, pBe->zDir, "/", zTable, ".tbl", 0);
|
||||
if( zFile==0 ) return 0;
|
||||
for(i=strlen(pBe->zDir)+1; zFile[i]; i++){
|
||||
int c = zFile[i];
|
||||
if( isupper(c) ){
|
||||
zFile[i] = tolower(c);
|
||||
}else if( !isalnum(c) && c!='-' && c!='_' && c!='.' ){
|
||||
zFile[i] = '+';
|
||||
}
|
||||
}
|
||||
return zFile;
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate a random filename with the given prefix. The new filename
|
||||
** is written into zBuf[]. The calling function must insure that
|
||||
** zBuf[] is big enough to hold the prefix plus 20 or so extra
|
||||
** characters.
|
||||
**
|
||||
** Very random names are chosen so that the chance of a
|
||||
** collision with an existing filename is very very small.
|
||||
*/
|
||||
static void randomName(struct rc4 *pRc4, char *zBuf, char *zPrefix){
|
||||
int i, j;
|
||||
static const char zRandomChars[] = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
strcpy(zBuf, zPrefix);
|
||||
j = strlen(zBuf);
|
||||
for(i=0; i<15; i++){
|
||||
int c = rc4byte(pRc4) % (sizeof(zRandomChars) - 1);
|
||||
zBuf[j++] = zRandomChars[c];
|
||||
}
|
||||
zBuf[j] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a new table cursor. Write a pointer to the corresponding
|
||||
** DbbeCursor structure into *ppCursr. Return an integer success
|
||||
** code:
|
||||
**
|
||||
** SQLITE_OK It worked!
|
||||
**
|
||||
** SQLITE_NOMEM sqliteMalloc() failed
|
||||
**
|
||||
** SQLITE_PERM Attempt to access a file for which file
|
||||
** access permission is denied
|
||||
**
|
||||
** SQLITE_BUSY Another thread or process is already using
|
||||
** the corresponding file and has that file locked.
|
||||
**
|
||||
** SQLITE_READONLY The current thread already has this file open
|
||||
** readonly but you are trying to open for writing.
|
||||
** (This can happen if a SELECT callback tries to
|
||||
** do an UPDATE or DELETE.)
|
||||
**
|
||||
** If zTable is 0 or "", then a temporary database file is created and
|
||||
** a cursor to that temporary file is opened. The temporary file
|
||||
** will be deleted from the disk when it is closed.
|
||||
*/
|
||||
int sqliteDbbeOpenCursor(
|
||||
Dbbe *pBe, /* The database the table belongs to */
|
||||
const char *zTable, /* The SQL name of the file to be opened */
|
||||
int writeable, /* True to open for writing */
|
||||
DbbeCursor **ppCursr /* Write the resulting table pointer here */
|
||||
){
|
||||
char *zFile; /* Name of the table file */
|
||||
DbbeCursor *pCursr; /* The new table cursor */
|
||||
BeFile *pFile; /* The underlying data file for this table */
|
||||
int rc = SQLITE_OK; /* Return value */
|
||||
int rw_mask; /* Permissions mask for opening a table */
|
||||
int mode; /* Mode for opening a table */
|
||||
|
||||
*ppCursr = 0;
|
||||
pCursr = sqliteMalloc( sizeof(*pCursr) );
|
||||
if( pCursr==0 ) return SQLITE_NOMEM;
|
||||
if( zTable ){
|
||||
zFile = sqliteFileOfTable(pBe, zTable);
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pFile->pNext){
|
||||
if( strcmp(pFile->zName,zFile)==0 ) break;
|
||||
}
|
||||
}else{
|
||||
pFile = 0;
|
||||
zFile = 0;
|
||||
}
|
||||
if( pFile==0 ){
|
||||
if( writeable ){
|
||||
rw_mask = GDBM_WRCREAT | GDBM_FAST;
|
||||
mode = 0640;
|
||||
}else{
|
||||
rw_mask = GDBM_READER;
|
||||
mode = 0640;
|
||||
}
|
||||
pFile = sqliteMalloc( sizeof(*pFile) );
|
||||
if( pFile==0 ){
|
||||
sqliteFree(zFile);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
if( zFile ){
|
||||
if( !writeable || pBe->write ){
|
||||
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
|
||||
}else{
|
||||
pFile->dbf = 0;
|
||||
}
|
||||
}else{
|
||||
int limit;
|
||||
struct rc4 *pRc4;
|
||||
char zRandom[50];
|
||||
pRc4 = &pBe->rc4;
|
||||
zFile = 0;
|
||||
limit = 5;
|
||||
do {
|
||||
randomName(&pBe->rc4, zRandom, "_temp_table_");
|
||||
sqliteFree(zFile);
|
||||
zFile = sqliteFileOfTable(pBe, zRandom);
|
||||
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
|
||||
}while( pFile->dbf==0 && limit-- >= 0);
|
||||
pFile->delOnClose = 1;
|
||||
}
|
||||
pFile->writeable = writeable;
|
||||
pFile->zName = zFile;
|
||||
pFile->nRef = 1;
|
||||
pFile->pPrev = 0;
|
||||
if( pBe->pOpen ){
|
||||
pBe->pOpen->pPrev = pFile;
|
||||
}
|
||||
pFile->pNext = pBe->pOpen;
|
||||
pBe->pOpen = pFile;
|
||||
if( pFile->dbf==0 ){
|
||||
if( !writeable && access(zFile,0) ){
|
||||
/* Trying to read a non-existant file. This is OK. All the
|
||||
** reads will return empty, which is what we want. */
|
||||
rc = SQLITE_OK;
|
||||
}else if( pBe->write==0 ){
|
||||
rc = SQLITE_READONLY;
|
||||
}else if( access(zFile,W_OK|R_OK) ){
|
||||
rc = SQLITE_PERM;
|
||||
}else{
|
||||
rc = SQLITE_BUSY;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
sqliteFree(zFile);
|
||||
pFile->nRef++;
|
||||
if( writeable && !pFile->writeable ){
|
||||
rc = SQLITE_READONLY;
|
||||
}
|
||||
}
|
||||
pCursr->pBe = pBe;
|
||||
pCursr->pFile = pFile;
|
||||
pCursr->readPending = 0;
|
||||
pCursr->needRewind = 1;
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqliteDbbeCloseCursor(pCursr);
|
||||
*ppCursr = 0;
|
||||
}else{
|
||||
*ppCursr = pCursr;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Drop a table from the database. The file on the disk that corresponds
|
||||
** to this table is deleted.
|
||||
*/
|
||||
void sqliteDbbeDropTable(Dbbe *pBe, const char *zTable){
|
||||
char *zFile; /* Name of the table file */
|
||||
|
||||
zFile = sqliteFileOfTable(pBe, zTable);
|
||||
unlink(zFile);
|
||||
sqliteFree(zFile);
|
||||
}
|
||||
|
||||
/*
|
||||
** Reorganize a table to reduce search times and disk usage.
|
||||
*/
|
||||
int sqliteDbbeReorganizeTable(Dbbe *pBe, const char *zTable){
|
||||
DbbeCursor *pCrsr;
|
||||
int rc;
|
||||
|
||||
rc = sqliteDbbeOpenCursor(pBe, zTable, 1, &pCrsr);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
if( pCrsr && pCrsr->pFile && pCrsr->pFile->dbf ){
|
||||
gdbm_reorganize(pCrsr->pFile->dbf);
|
||||
}
|
||||
if( pCrsr ){
|
||||
sqliteDbbeCloseCursor(pCrsr);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a cursor previously opened by sqliteDbbeOpenCursor().
|
||||
**
|
||||
** There can be multiple cursors pointing to the same open file.
|
||||
** The underlying file is not closed until all cursors have been
|
||||
** closed. This routine decrements the BeFile.nref field of the
|
||||
** underlying file and closes the file when nref reaches 0.
|
||||
*/
|
||||
void sqliteDbbeCloseCursor(DbbeCursor *pCursr){
|
||||
BeFile *pFile;
|
||||
Dbbe *pBe;
|
||||
if( pCursr==0 ) return;
|
||||
pFile = pCursr->pFile;
|
||||
pBe = pCursr->pBe;
|
||||
pFile->nRef--;
|
||||
if( pFile->dbf!=NULL ){
|
||||
gdbm_sync(pFile->dbf);
|
||||
}
|
||||
if( pFile->nRef<=0 ){
|
||||
if( pFile->dbf!=NULL ){
|
||||
gdbm_close(pFile->dbf);
|
||||
}
|
||||
if( pFile->pPrev ){
|
||||
pFile->pPrev->pNext = pFile->pNext;
|
||||
}else{
|
||||
pBe->pOpen = pFile->pNext;
|
||||
}
|
||||
if( pFile->pNext ){
|
||||
pFile->pNext->pPrev = pFile->pPrev;
|
||||
}
|
||||
if( pFile->delOnClose ){
|
||||
unlink(pFile->zName);
|
||||
}
|
||||
sqliteFree(pFile->zName);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
if( pCursr->key.dptr ) free(pCursr->key.dptr);
|
||||
if( pCursr->data.dptr ) free(pCursr->data.dptr);
|
||||
memset(pCursr, 0, sizeof(*pCursr));
|
||||
sqliteFree(pCursr);
|
||||
}
|
||||
|
||||
/*
|
||||
** Clear the given datum
|
||||
*/
|
||||
static void datumClear(datum *p){
|
||||
if( p->dptr ) free(p->dptr);
|
||||
p->dptr = 0;
|
||||
p->dsize = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Fetch a single record from an open cursor. Return 1 on success
|
||||
** and 0 on failure.
|
||||
*/
|
||||
int sqliteDbbeFetch(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return pCursr->data.dptr!=0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
int sqliteDbbeTest(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
int result = 0;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
result = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy bytes from the current key or data into a buffer supplied by
|
||||
** the calling function. Return the number of bytes copied.
|
||||
*/
|
||||
int sqliteDbbeCopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( offset>=pCursr->key.dsize ) return 0;
|
||||
if( offset+size>pCursr->key.dsize ){
|
||||
n = pCursr->key.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->key.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
int sqliteDbbeCopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset>=pCursr->data.dsize ) return 0;
|
||||
if( offset+size>pCursr->data.dsize ){
|
||||
n = pCursr->data.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->data.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to bytes from the key or data. The data returned
|
||||
** is ephemeral.
|
||||
*/
|
||||
char *sqliteDbbeReadKey(DbbeCursor *pCursr, int offset){
|
||||
if( offset<0 || offset>=pCursr->key.dsize ) return "";
|
||||
return &pCursr->key.dptr[offset];
|
||||
}
|
||||
char *sqliteDbbeReadData(DbbeCursor *pCursr, int offset){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset<0 || offset>=pCursr->data.dsize ) return "";
|
||||
return &pCursr->data.dptr[offset];
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the total number of bytes in either data or key.
|
||||
*/
|
||||
int sqliteDbbeKeyLength(DbbeCursor *pCursr){
|
||||
return pCursr->key.dsize;
|
||||
}
|
||||
int sqliteDbbeDataLength(DbbeCursor *pCursr){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
return pCursr->data.dsize;
|
||||
}
|
||||
|
||||
/*
|
||||
** Make is so that the next call to sqliteNextKey() finds the first
|
||||
** key of the table.
|
||||
*/
|
||||
int sqliteDbbeRewind(DbbeCursor *pCursr){
|
||||
pCursr->needRewind = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read the next key from the table. Return 1 on success. Return
|
||||
** 0 if there are no more keys.
|
||||
*/
|
||||
int sqliteDbbeNextKey(DbbeCursor *pCursr){
|
||||
datum nextkey;
|
||||
int rc;
|
||||
if( pCursr==0 || pCursr->pFile==0 || pCursr->pFile->dbf==0 ){
|
||||
pCursr->readPending = 0;
|
||||
return 0;
|
||||
}
|
||||
if( pCursr->needRewind ){
|
||||
nextkey = gdbm_firstkey(pCursr->pFile->dbf);
|
||||
pCursr->needRewind = 0;
|
||||
}else{
|
||||
nextkey = gdbm_nextkey(pCursr->pFile->dbf, pCursr->key);
|
||||
}
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
pCursr->key = nextkey;
|
||||
if( pCursr->key.dptr ){
|
||||
pCursr->readPending = 1;
|
||||
rc = 1;
|
||||
}else{
|
||||
pCursr->needRewind = 1;
|
||||
pCursr->readPending = 0;
|
||||
rc = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a new integer key.
|
||||
*/
|
||||
int sqliteDbbeNew(DbbeCursor *pCursr){
|
||||
int iKey;
|
||||
datum key;
|
||||
int go = 1;
|
||||
int i;
|
||||
struct rc4 *pRc4;
|
||||
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return 1;
|
||||
pRc4 = &pCursr->pBe->rc4;
|
||||
while( go ){
|
||||
iKey = 0;
|
||||
for(i=0; i<4; i++){
|
||||
iKey = (iKey<<8) + rc4byte(pRc4);
|
||||
}
|
||||
if( iKey==0 ) continue;
|
||||
key.dptr = (char*)&iKey;
|
||||
key.dsize = 4;
|
||||
go = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return iKey;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write an entry into the table. Overwrite any prior entry with the
|
||||
** same key.
|
||||
*/
|
||||
int sqliteDbbePut(DbbeCursor *pCursr, int nKey,char *pKey,int nData,char *pData){
|
||||
datum data, key;
|
||||
int rc;
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
data.dsize = nData;
|
||||
data.dptr = pData;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_store(pCursr->pFile->dbf, key, data, GDBM_REPLACE);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove an entry from a table, if the entry exists.
|
||||
*/
|
||||
int sqliteDbbeDelete(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
int rc;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_delete(pCursr->pFile->dbf, key);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
return rc;
|
||||
return sqliteGdbmOpen(zName, writeFlag, createFlag, pzErrMsg);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -684,7 +71,7 @@ int sqliteDbbeDelete(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
** and then immediately unlinking the file. That works great
|
||||
** under Unix, but fails when we try to port to Windows.
|
||||
*/
|
||||
int sqliteDbbeOpenTempFile(Dbbe *pBe, FILE **ppFile){
|
||||
int sqliteDbbeOpenTempFile(const char *zDir, Dbbe *pBe, FILE **ppFile){
|
||||
char *zFile; /* Full name of the temporary file */
|
||||
char zBuf[50]; /* Base name of the temporary file */
|
||||
int i; /* Loop counter */
|
||||
@@ -706,10 +93,10 @@ int sqliteDbbeOpenTempFile(Dbbe *pBe, FILE **ppFile){
|
||||
limit = 4;
|
||||
zFile = 0;
|
||||
do{
|
||||
randomName(&pBe->rc4, zBuf, "/_temp_file_");
|
||||
sqliteRandomName(zBuf, "/_temp_file_");
|
||||
sqliteFree(zFile);
|
||||
zFile = 0;
|
||||
sqliteSetString(&zFile, pBe->zDir, zBuf, 0);
|
||||
sqliteSetString(&zFile, zDir, zBuf, 0);
|
||||
}while( access(zFile,0)==0 && limit-- >= 0 );
|
||||
*ppFile = pBe->apTemp[i] = fopen(zFile, "w+");
|
||||
if( pBe->apTemp[i]==0 ){
|
||||
@@ -723,7 +110,7 @@ int sqliteDbbeOpenTempFile(Dbbe *pBe, FILE **ppFile){
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a temporary file opened using sqliteDbbeOpenTempFile()
|
||||
** Close a temporary file opened using sqliteGdbmOpenTempFile()
|
||||
*/
|
||||
void sqliteDbbeCloseTempFile(Dbbe *pBe, FILE *f){
|
||||
int i;
|
||||
@@ -738,3 +125,74 @@ void sqliteDbbeCloseTempFile(Dbbe *pBe, FILE *f){
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close all temporary files that happen to still be open.
|
||||
** This routine is called when the database is being closed.
|
||||
*/
|
||||
void sqliteDbbeCloseAllTempFiles(Dbbe *pBe){
|
||||
int i;
|
||||
for(i=0; i<pBe->nTemp; i++){
|
||||
if( pBe->apTemp[i]!=0 ){
|
||||
unlink(pBe->azTemp[i]);
|
||||
fclose(pBe->apTemp[i]);
|
||||
sqliteFree(pBe->azTemp[i]);
|
||||
pBe->apTemp[i] = 0;
|
||||
pBe->azTemp[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sqliteFree(pBe->azTemp);
|
||||
sqliteFree(pBe->apTemp);
|
||||
}
|
||||
|
||||
/*
|
||||
** Translate the name of an SQL table (or index) into the name
|
||||
** of a file that holds the key/data pairs for that table or
|
||||
** index. Space to hold the filename is obtained from
|
||||
** sqliteMalloc() and must be freed by the calling function.
|
||||
**
|
||||
** zDir is the name of the directory in which the file should
|
||||
** be located. zSuffix is the filename extension to use for
|
||||
** the file.
|
||||
*/
|
||||
char *sqliteDbbeNameToFile(
|
||||
const char *zDir, /* Directory containing the file */
|
||||
const char *zTable, /* Name of the SQL table that the file contains */
|
||||
const char *zSuffix /* Suffix for the file. Includes the "." */
|
||||
){
|
||||
char *zFile = 0;
|
||||
int i, k, c;
|
||||
int nChar = 0;
|
||||
|
||||
for(i=0; (c = zTable[i])!=0; i++){
|
||||
if( !isalnum(c) && c!='_' ){
|
||||
nChar += 3;
|
||||
}else{
|
||||
nChar ++;
|
||||
}
|
||||
}
|
||||
nChar += strlen(zDir) + strlen(zSuffix) + 2;
|
||||
zFile = sqliteMalloc( nChar );
|
||||
if( zFile==0 ) return 0;
|
||||
for(i=0; (c = zDir[i])!=0; i++){
|
||||
zFile[i] = c;
|
||||
}
|
||||
zFile[i++] = '/';
|
||||
for(k=0; (c = zTable[k])!=0; k++){
|
||||
if( isupper(c) ){
|
||||
zFile[i++] = tolower(c);
|
||||
}else if( isalnum(c) || c=='_' ){
|
||||
zFile[i++] = c;
|
||||
}else{
|
||||
zFile[i++] = '~';
|
||||
zFile[i++] = "0123456789abcdef"[c & 0xf];
|
||||
zFile[i++] = "0123456789abcdef"[(c>>8)&0xf];
|
||||
}
|
||||
}
|
||||
for(k=0; (c = zSuffix[k])!=0; k++){
|
||||
zFile[i++] = c;
|
||||
}
|
||||
zFile[i] = 0;
|
||||
return zFile;
|
||||
}
|
||||
|
||||
+114
-79
@@ -28,7 +28,7 @@
|
||||
** This library was originally designed to support the following
|
||||
** backends: GDBM, NDBM, SDBM, Berkeley DB.
|
||||
**
|
||||
** $Id: dbbe.h,v 1.7 2000/08/17 09:50:00 drh Exp $
|
||||
** $Id: dbbe.h,v 1.10 2001/01/15 22:51:10 drh Exp $
|
||||
*/
|
||||
#ifndef _SQLITE_DBBE_H_
|
||||
#define _SQLITE_DBBE_H_
|
||||
@@ -53,91 +53,126 @@
|
||||
*/
|
||||
typedef struct Dbbe Dbbe;
|
||||
typedef struct DbbeCursor DbbeCursor;
|
||||
typedef struct DbbeMethods DbbeMethods;
|
||||
|
||||
/*
|
||||
** The 18 interface routines.
|
||||
** Open a complete database.
|
||||
**
|
||||
** If the database name begins with "gdbm:" the GDBM driver is used.
|
||||
** If the name begins with "memory:" the in-memory driver is used.
|
||||
** The default driver is GDBM.
|
||||
*/
|
||||
|
||||
/* Open a complete database */
|
||||
Dbbe *sqliteDbbeOpen(const char *zName, int write, int create, char **pzErr);
|
||||
|
||||
/* Close the whole database. */
|
||||
void sqliteDbbeClose(Dbbe*);
|
||||
/*
|
||||
** Each of the various SQLite backends defines a set of methods for
|
||||
** accessing the database. Pointers to the methods are contained in
|
||||
** an instance of the following structure. A pointer to a static instance
|
||||
** of this structure is assigned to the Dbbe structure that sqlileDbbeOpen
|
||||
** returns.
|
||||
*/
|
||||
struct DbbeMethods {
|
||||
/* Close the whole database. */
|
||||
void (*Close)(Dbbe*);
|
||||
|
||||
/* Open a cursor into particular file of a previously opened database.
|
||||
** Create the file if it doesn't already exist and writeable!=0. zName
|
||||
** is the base name of the file to be opened. This routine will add
|
||||
** an appropriate path and extension to the filename to locate the
|
||||
** actual file.
|
||||
/* Open a cursor into particular file of a previously opened database.
|
||||
** Create the file if it doesn't already exist and writeable!=0. zName
|
||||
** is the base name of the file to be opened. This routine will add
|
||||
** an appropriate path and extension to the filename to locate the
|
||||
** actual file.
|
||||
**
|
||||
** The keyType parameter is TRUE if this table will only be accessed
|
||||
** using integer keys. This parameter allows the database backend to
|
||||
** use a faster algorithm for the special case of integer keys, if it
|
||||
** wants to.
|
||||
**
|
||||
** If zName is 0 or "", then a temporary file is created that
|
||||
** will be deleted when closed.
|
||||
*/
|
||||
int (*OpenCursor)(Dbbe*, const char *zName, int writeable,
|
||||
int intKeyOnly, DbbeCursor**);
|
||||
|
||||
/* Delete a table from the database */
|
||||
void (*DropTable)(Dbbe*, const char *zTableName);
|
||||
|
||||
/* Reorganize a table to speed access or reduce its disk usage */
|
||||
int (*ReorganizeTable)(Dbbe*, const char *zTableName);
|
||||
|
||||
/* Close a cursor */
|
||||
void (*CloseCursor)(DbbeCursor*);
|
||||
|
||||
/* Fetch an entry from a table with the given key. Return 1 if
|
||||
** successful and 0 if no such entry exists.
|
||||
*/
|
||||
int (*Fetch)(DbbeCursor*, int nKey, char *pKey);
|
||||
|
||||
/* Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
int (*Test)(DbbeCursor*, int nKey, char *pKey);
|
||||
|
||||
/* Retrieve the key or data used for the last fetch. Only size
|
||||
** bytes are read beginning with the offset-th byte. The return
|
||||
** value is the actual number of bytes read.
|
||||
*/
|
||||
int (*CopyKey)(DbbeCursor*, int offset, int size, char *zBuf);
|
||||
int (*CopyData)(DbbeCursor*, int offset, int size, char *zBuf);
|
||||
|
||||
/* Retrieve the key or data. The result is ephemeral. In other words,
|
||||
** the result is stored in a buffer that might be overwritten on the next
|
||||
** call to any DBBE routine. If the results are needed for longer than
|
||||
** that, you must make a copy.
|
||||
*/
|
||||
char *(*ReadKey)(DbbeCursor*, int offset);
|
||||
char *(*ReadData)(DbbeCursor*, int offset);
|
||||
|
||||
/* Return the length of the most recently fetched key or data. */
|
||||
int (*KeyLength)(DbbeCursor*);
|
||||
int (*DataLength)(DbbeCursor*);
|
||||
|
||||
/* Retrieve the next entry in the table. The first key is retrieved
|
||||
** the first time this routine is called, or after a call to
|
||||
** Dbbe.Rewind(). The return value is 1 if there is another
|
||||
** entry, or 0 if there are no more entries. */
|
||||
int (*NextKey)(DbbeCursor*);
|
||||
|
||||
/* Make it so that the next call to Dbbe.NextKey() returns
|
||||
** the first entry of the table. */
|
||||
int (*Rewind)(DbbeCursor*);
|
||||
|
||||
/* Get a new integer key for this table. */
|
||||
int (*New)(DbbeCursor*);
|
||||
|
||||
/* Write an entry into a table. If another entry already exists with
|
||||
** the same key, the old entry is discarded first.
|
||||
*/
|
||||
int (*Put)(DbbeCursor*, int nKey, char *pKey, int nData, char *pData);
|
||||
|
||||
/* Remove an entry from the table */
|
||||
int (*Delete)(DbbeCursor*, int nKey, char *pKey);
|
||||
|
||||
/* Open a file suitable for temporary storage */
|
||||
int (*OpenTempFile)(Dbbe*, FILE**);
|
||||
|
||||
/* Close a temporary file */
|
||||
void (*CloseTempFile)(Dbbe *, FILE *);
|
||||
};
|
||||
|
||||
/*
|
||||
** This is the structure returned by sqliteDbbeOpen(). It contains
|
||||
** information common to all the different backend drivers.
|
||||
**
|
||||
** If zName is 0 or "", then a temporary file is created that
|
||||
** will be deleted when closed.
|
||||
** The information in this structure (with the exception the method
|
||||
** pointers in the Dbbe.x field) is intended to be visible to
|
||||
** the backend drivers only. Users should not access or modify
|
||||
** this structure in any way other than the read the method pointers
|
||||
** in Dbbe.x.
|
||||
*/
|
||||
int sqliteDbbeOpenCursor(Dbbe*, const char *zName, int writeable, DbbeCursor**);
|
||||
|
||||
/* Delete a table from the database */
|
||||
void sqliteDbbeDropTable(Dbbe*, const char *zTableName);
|
||||
|
||||
/* Reorganize a table to speed access or reduce its disk usage */
|
||||
int sqliteDbbeReorganizeTable(Dbbe*, const char *zTableName);
|
||||
|
||||
/* Close a cursor */
|
||||
void sqliteDbbeCloseCursor(DbbeCursor*);
|
||||
|
||||
/* Fetch an entry from a table with the given key. Return 1 if
|
||||
** successful and 0 if no such entry exists.
|
||||
*/
|
||||
int sqliteDbbeFetch(DbbeCursor*, int nKey, char *pKey);
|
||||
|
||||
/* Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
int sqliteDbbeTest(DbbeCursor*, int nKey, char *pKey);
|
||||
|
||||
/* Retrieve the key or data used for the last fetch. Only size
|
||||
** bytes are read beginning with the offset-th byte. The return
|
||||
** value is the actual number of bytes read.
|
||||
*/
|
||||
int sqliteDbbeCopyKey(DbbeCursor*, int offset, int size, char *zBuf);
|
||||
int sqliteDbbeCopyData(DbbeCursor*, int offset, int size, char *zBuf);
|
||||
|
||||
/* Retrieve the key or data. The result is ephemeral. In other words,
|
||||
** the result is stored in a buffer that might be overwritten on the next
|
||||
** call to any DBBE routine. If the results are needed for longer than
|
||||
** that, you must make a copy.
|
||||
*/
|
||||
char *sqliteDbbeReadKey(DbbeCursor*, int offset);
|
||||
char *sqliteDbbeReadData(DbbeCursor*, int offset);
|
||||
|
||||
/* Return the length of the most recently fetched key or data. */
|
||||
int sqliteDbbeKeyLength(DbbeCursor*);
|
||||
int sqliteDbbeDataLength(DbbeCursor*);
|
||||
|
||||
/* Retrieve the next entry in the table. The first key is retrieved
|
||||
** the first time this routine is called, or after a call to
|
||||
** sqliteDbbeRewind(). The return value is 1 if there is another
|
||||
** entry, or 0 if there are no more entries. */
|
||||
int sqliteDbbeNextKey(DbbeCursor*);
|
||||
|
||||
/* Make it so that the next call to sqliteDbbeNextKey() returns
|
||||
** the first entry of the table. */
|
||||
int sqliteDbbeRewind(DbbeCursor*);
|
||||
|
||||
/* Get a new integer key for this table. */
|
||||
int sqliteDbbeNew(DbbeCursor*);
|
||||
|
||||
/* Write an entry into a table. If another entry already exists with
|
||||
** the same key, the old entry is discarded first.
|
||||
*/
|
||||
int sqliteDbbePut(DbbeCursor*, int nKey, char *pKey, int nData, char *pData);
|
||||
|
||||
/* Remove an entry from the table */
|
||||
int sqliteDbbeDelete(DbbeCursor*, int nKey, char *pKey);
|
||||
|
||||
/* Open a file suitable for temporary storage */
|
||||
int sqliteDbbeOpenTempFile(Dbbe*, FILE**);
|
||||
|
||||
/* Close a temporary file */
|
||||
void sqliteDbbeCloseTempFile(Dbbe *, FILE *);
|
||||
struct Dbbe {
|
||||
struct DbbeMethods *x; /* Backend-specific methods for database access */
|
||||
int nTemp; /* Number of temporary files created */
|
||||
FILE **apTemp; /* Space to hold temporary file pointers */
|
||||
char **azTemp; /* Names of the temporary files */
|
||||
};
|
||||
|
||||
#endif /* defined(_SQLITE_DBBE_H_) */
|
||||
|
||||
+638
@@ -0,0 +1,638 @@
|
||||
/*
|
||||
** Copyright (c) 2000 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains code to implement the database backend (DBBE)
|
||||
** for sqlite. The database backend is the interface between
|
||||
** sqlite and the code that does the actually reading and writing
|
||||
** of information to the disk.
|
||||
**
|
||||
** This file uses GDBM as the database backend. It should be
|
||||
** relatively simple to convert to a different database such
|
||||
** as NDBM, SDBM, or BerkeleyDB.
|
||||
**
|
||||
** $Id: dbbegdbm.c,v 1.3 2001/01/15 22:51:10 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <gdbm.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
/*
|
||||
** Information about each open disk file is an instance of this
|
||||
** structure. There will only be one such structure for each
|
||||
** disk file. If the VDBE opens the same file twice (as will happen
|
||||
** for a self-join, for example) then two DbbeCursor structures are
|
||||
** created but there is only a single BeFile structure with an
|
||||
** nRef of 2.
|
||||
*/
|
||||
typedef struct BeFile BeFile;
|
||||
struct BeFile {
|
||||
char *zName; /* Name of the file */
|
||||
GDBM_FILE dbf; /* The file itself */
|
||||
int nRef; /* Number of references */
|
||||
int delOnClose; /* Delete when closing */
|
||||
int writeable; /* Opened for writing */
|
||||
BeFile *pNext, *pPrev; /* Next and previous on list of open files */
|
||||
};
|
||||
|
||||
/*
|
||||
** The following structure contains all information used by GDBM
|
||||
** database driver. This is a subclass of the Dbbe structure.
|
||||
*/
|
||||
typedef struct Dbbex Dbbex;
|
||||
struct Dbbex {
|
||||
Dbbe dbbe; /* The base class */
|
||||
int write; /* True for write permission */
|
||||
BeFile *pOpen; /* List of open files */
|
||||
char *zDir; /* Directory hold the database */
|
||||
};
|
||||
|
||||
/*
|
||||
** An cursor into a database file is an instance of the following structure.
|
||||
** There can only be a single BeFile structure for each disk file, but
|
||||
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
|
||||
** a cursor pointing to a particular part of the open BeFile. The
|
||||
** BeFile.nRef field hold a count of the number of DbbeCursor structures
|
||||
** associated with the same disk file.
|
||||
*/
|
||||
struct DbbeCursor {
|
||||
Dbbex *pBe; /* The database of which this record is a part */
|
||||
BeFile *pFile; /* The database file for this table */
|
||||
datum key; /* Most recently used key */
|
||||
datum data; /* Most recent data */
|
||||
int needRewind; /* Next key should be the first */
|
||||
int readPending; /* The fetch hasn't actually been done yet */
|
||||
};
|
||||
|
||||
/*
|
||||
** The "mkdir()" function only takes one argument under Windows.
|
||||
*/
|
||||
#if OS_WIN
|
||||
# define mkdir(A,B) mkdir(A)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Forward declaration
|
||||
*/
|
||||
static void sqliteGdbmCloseCursor(DbbeCursor *pCursr);
|
||||
|
||||
/*
|
||||
** Completely shutdown the given database. Close all files. Free all memory.
|
||||
*/
|
||||
static void sqliteGdbmClose(Dbbe *pDbbe){
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
BeFile *pFile, *pNext;
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pNext){
|
||||
pNext = pFile->pNext;
|
||||
gdbm_close(pFile->dbf);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
sqliteDbbeCloseAllTempFiles(pDbbe);
|
||||
memset(pBe, 0, sizeof(*pBe));
|
||||
sqliteFree(pBe);
|
||||
}
|
||||
|
||||
/*
|
||||
** Translate the name of an SQL table (or index) into the name
|
||||
** of a file that holds the key/data pairs for that table or
|
||||
** index. Space to hold the filename is obtained from
|
||||
** sqliteMalloc() and must be freed by the calling function.
|
||||
*/
|
||||
static char *sqliteFileOfTable(Dbbex *pBe, const char *zTable){
|
||||
char *zFile = 0;
|
||||
int i;
|
||||
sqliteSetString(&zFile, pBe->zDir, "/", zTable, ".tbl", 0);
|
||||
if( zFile==0 ) return 0;
|
||||
for(i=strlen(pBe->zDir)+1; zFile[i]; i++){
|
||||
int c = zFile[i];
|
||||
if( isupper(c) ){
|
||||
zFile[i] = tolower(c);
|
||||
}else if( !isalnum(c) && c!='-' && c!='_' && c!='.' ){
|
||||
zFile[i] = '+';
|
||||
}
|
||||
}
|
||||
return zFile;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a new table cursor. Write a pointer to the corresponding
|
||||
** DbbeCursor structure into *ppCursr. Return an integer success
|
||||
** code:
|
||||
**
|
||||
** SQLITE_OK It worked!
|
||||
**
|
||||
** SQLITE_NOMEM sqliteMalloc() failed
|
||||
**
|
||||
** SQLITE_PERM Attempt to access a file for which file
|
||||
** access permission is denied
|
||||
**
|
||||
** SQLITE_BUSY Another thread or process is already using
|
||||
** the corresponding file and has that file locked.
|
||||
**
|
||||
** SQLITE_READONLY The current thread already has this file open
|
||||
** readonly but you are trying to open for writing.
|
||||
** (This can happen if a SELECT callback tries to
|
||||
** do an UPDATE or DELETE.)
|
||||
**
|
||||
** If zTable is 0 or "", then a temporary database file is created and
|
||||
** a cursor to that temporary file is opened. The temporary file
|
||||
** will be deleted from the disk when it is closed.
|
||||
*/
|
||||
static int sqliteGdbmOpenCursor(
|
||||
Dbbe *pDbbe, /* The database the table belongs to */
|
||||
const char *zTable, /* The SQL name of the file to be opened */
|
||||
int writeable, /* True to open for writing */
|
||||
int intKeyOnly, /* True if only integer keys are used */
|
||||
DbbeCursor **ppCursr /* Write the resulting table pointer here */
|
||||
){
|
||||
char *zFile; /* Name of the table file */
|
||||
DbbeCursor *pCursr; /* The new table cursor */
|
||||
BeFile *pFile; /* The underlying data file for this table */
|
||||
int rc = SQLITE_OK; /* Return value */
|
||||
int rw_mask; /* Permissions mask for opening a table */
|
||||
int mode; /* Mode for opening a table */
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
|
||||
*ppCursr = 0;
|
||||
pCursr = sqliteMalloc( sizeof(*pCursr) );
|
||||
if( pCursr==0 ) return SQLITE_NOMEM;
|
||||
if( zTable ){
|
||||
zFile = sqliteFileOfTable(pBe, zTable);
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pFile->pNext){
|
||||
if( strcmp(pFile->zName,zFile)==0 ) break;
|
||||
}
|
||||
}else{
|
||||
pFile = 0;
|
||||
zFile = 0;
|
||||
}
|
||||
if( pFile==0 ){
|
||||
if( writeable ){
|
||||
rw_mask = GDBM_WRCREAT | GDBM_FAST;
|
||||
mode = 0640;
|
||||
}else{
|
||||
rw_mask = GDBM_READER;
|
||||
mode = 0640;
|
||||
}
|
||||
pFile = sqliteMalloc( sizeof(*pFile) );
|
||||
if( pFile==0 ){
|
||||
sqliteFree(zFile);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
if( zFile ){
|
||||
if( !writeable || pBe->write ){
|
||||
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
|
||||
}else{
|
||||
pFile->dbf = 0;
|
||||
}
|
||||
}else{
|
||||
int limit;
|
||||
char zRandom[50];
|
||||
zFile = 0;
|
||||
limit = 5;
|
||||
do {
|
||||
sqliteRandomName(zRandom, "_temp_table_");
|
||||
sqliteFree(zFile);
|
||||
zFile = sqliteFileOfTable(pBe, zRandom);
|
||||
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
|
||||
}while( pFile->dbf==0 && limit-- >= 0);
|
||||
pFile->delOnClose = 1;
|
||||
}
|
||||
pFile->writeable = writeable;
|
||||
pFile->zName = zFile;
|
||||
pFile->nRef = 1;
|
||||
pFile->pPrev = 0;
|
||||
if( pBe->pOpen ){
|
||||
pBe->pOpen->pPrev = pFile;
|
||||
}
|
||||
pFile->pNext = pBe->pOpen;
|
||||
pBe->pOpen = pFile;
|
||||
if( pFile->dbf==0 ){
|
||||
if( !writeable && access(zFile,0) ){
|
||||
/* Trying to read a non-existant file. This is OK. All the
|
||||
** reads will return empty, which is what we want. */
|
||||
rc = SQLITE_OK;
|
||||
}else if( pBe->write==0 ){
|
||||
rc = SQLITE_READONLY;
|
||||
}else if( access(zFile,W_OK|R_OK) ){
|
||||
rc = SQLITE_PERM;
|
||||
}else{
|
||||
rc = SQLITE_BUSY;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
sqliteFree(zFile);
|
||||
pFile->nRef++;
|
||||
if( writeable && !pFile->writeable ){
|
||||
rc = SQLITE_READONLY;
|
||||
}
|
||||
}
|
||||
pCursr->pBe = pBe;
|
||||
pCursr->pFile = pFile;
|
||||
pCursr->readPending = 0;
|
||||
pCursr->needRewind = 1;
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqliteGdbmCloseCursor(pCursr);
|
||||
*ppCursr = 0;
|
||||
}else{
|
||||
*ppCursr = pCursr;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Drop a table from the database. The file on the disk that corresponds
|
||||
** to this table is deleted.
|
||||
*/
|
||||
static void sqliteGdbmDropTable(Dbbe *pBe, const char *zTable){
|
||||
char *zFile; /* Name of the table file */
|
||||
|
||||
zFile = sqliteFileOfTable((Dbbex*)pBe, zTable);
|
||||
unlink(zFile);
|
||||
sqliteFree(zFile);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a cursor previously opened by sqliteGdbmOpenCursor().
|
||||
**
|
||||
** There can be multiple cursors pointing to the same open file.
|
||||
** The underlying file is not closed until all cursors have been
|
||||
** closed. This routine decrements the BeFile.nref field of the
|
||||
** underlying file and closes the file when nref reaches 0.
|
||||
*/
|
||||
static void sqliteGdbmCloseCursor(DbbeCursor *pCursr){
|
||||
BeFile *pFile;
|
||||
Dbbex *pBe;
|
||||
if( pCursr==0 ) return;
|
||||
pFile = pCursr->pFile;
|
||||
pBe = pCursr->pBe;
|
||||
pFile->nRef--;
|
||||
if( pFile->dbf!=NULL ){
|
||||
gdbm_sync(pFile->dbf);
|
||||
}
|
||||
if( pFile->nRef<=0 ){
|
||||
if( pFile->dbf!=NULL ){
|
||||
gdbm_close(pFile->dbf);
|
||||
}
|
||||
if( pFile->pPrev ){
|
||||
pFile->pPrev->pNext = pFile->pNext;
|
||||
}else{
|
||||
pBe->pOpen = pFile->pNext;
|
||||
}
|
||||
if( pFile->pNext ){
|
||||
pFile->pNext->pPrev = pFile->pPrev;
|
||||
}
|
||||
if( pFile->delOnClose ){
|
||||
unlink(pFile->zName);
|
||||
}
|
||||
sqliteFree(pFile->zName);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
if( pCursr->key.dptr ) free(pCursr->key.dptr);
|
||||
if( pCursr->data.dptr ) free(pCursr->data.dptr);
|
||||
memset(pCursr, 0, sizeof(*pCursr));
|
||||
sqliteFree(pCursr);
|
||||
}
|
||||
|
||||
/*
|
||||
** Reorganize a table to reduce search times and disk usage.
|
||||
*/
|
||||
static int sqliteGdbmReorganizeTable(Dbbe *pBe, const char *zTable){
|
||||
DbbeCursor *pCrsr;
|
||||
int rc;
|
||||
|
||||
rc = sqliteGdbmOpenCursor(pBe, zTable, 1, 0, &pCrsr);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
if( pCrsr && pCrsr->pFile && pCrsr->pFile->dbf ){
|
||||
gdbm_reorganize(pCrsr->pFile->dbf);
|
||||
}
|
||||
if( pCrsr ){
|
||||
sqliteGdbmCloseCursor(pCrsr);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Clear the given datum
|
||||
*/
|
||||
static void datumClear(datum *p){
|
||||
if( p->dptr ) free(p->dptr);
|
||||
p->dptr = 0;
|
||||
p->dsize = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Fetch a single record from an open cursor. Return 1 on success
|
||||
** and 0 on failure.
|
||||
*/
|
||||
static int sqliteGdbmFetch(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return pCursr->data.dptr!=0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
static int sqliteGdbmTest(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
int result = 0;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
result = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy bytes from the current key or data into a buffer supplied by
|
||||
** the calling function. Return the number of bytes copied.
|
||||
*/
|
||||
static
|
||||
int sqliteGdbmCopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( offset>=pCursr->key.dsize ) return 0;
|
||||
if( offset+size>pCursr->key.dsize ){
|
||||
n = pCursr->key.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->key.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
static
|
||||
int sqliteGdbmCopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset>=pCursr->data.dsize ) return 0;
|
||||
if( offset+size>pCursr->data.dsize ){
|
||||
n = pCursr->data.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->data.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to bytes from the key or data. The data returned
|
||||
** is ephemeral.
|
||||
*/
|
||||
static char *sqliteGdbmReadKey(DbbeCursor *pCursr, int offset){
|
||||
if( offset<0 || offset>=pCursr->key.dsize ) return "";
|
||||
return &pCursr->key.dptr[offset];
|
||||
}
|
||||
static char *sqliteGdbmReadData(DbbeCursor *pCursr, int offset){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset<0 || offset>=pCursr->data.dsize ) return "";
|
||||
return &pCursr->data.dptr[offset];
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the total number of bytes in either data or key.
|
||||
*/
|
||||
static int sqliteGdbmKeyLength(DbbeCursor *pCursr){
|
||||
return pCursr->key.dsize;
|
||||
}
|
||||
static int sqliteGdbmDataLength(DbbeCursor *pCursr){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
return pCursr->data.dsize;
|
||||
}
|
||||
|
||||
/*
|
||||
** Make is so that the next call to sqliteNextKey() finds the first
|
||||
** key of the table.
|
||||
*/
|
||||
static int sqliteGdbmRewind(DbbeCursor *pCursr){
|
||||
pCursr->needRewind = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read the next key from the table. Return 1 on success. Return
|
||||
** 0 if there are no more keys.
|
||||
*/
|
||||
static int sqliteGdbmNextKey(DbbeCursor *pCursr){
|
||||
datum nextkey;
|
||||
int rc;
|
||||
if( pCursr==0 || pCursr->pFile==0 || pCursr->pFile->dbf==0 ){
|
||||
pCursr->readPending = 0;
|
||||
return 0;
|
||||
}
|
||||
if( pCursr->needRewind ){
|
||||
nextkey = gdbm_firstkey(pCursr->pFile->dbf);
|
||||
pCursr->needRewind = 0;
|
||||
}else{
|
||||
nextkey = gdbm_nextkey(pCursr->pFile->dbf, pCursr->key);
|
||||
}
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
pCursr->key = nextkey;
|
||||
if( pCursr->key.dptr ){
|
||||
pCursr->readPending = 1;
|
||||
rc = 1;
|
||||
}else{
|
||||
pCursr->needRewind = 1;
|
||||
pCursr->readPending = 0;
|
||||
rc = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a new integer key.
|
||||
*/
|
||||
static int sqliteGdbmNew(DbbeCursor *pCursr){
|
||||
int iKey;
|
||||
datum key;
|
||||
int go = 1;
|
||||
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return 1;
|
||||
while( go ){
|
||||
iKey = sqliteRandomInteger();
|
||||
if( iKey==0 ) continue;
|
||||
key.dptr = (char*)&iKey;
|
||||
key.dsize = 4;
|
||||
go = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return iKey;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write an entry into the table. Overwrite any prior entry with the
|
||||
** same key.
|
||||
*/
|
||||
static int sqliteGdbmPut(
|
||||
DbbeCursor *pCursr, /* Write to the database associated with this cursor */
|
||||
int nKey, /* Number of bytes in the key */
|
||||
char *pKey, /* The data for the key */
|
||||
int nData, /* Number of bytes of data */
|
||||
char *pData /* The data */
|
||||
){
|
||||
datum data, key;
|
||||
int rc;
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
data.dsize = nData;
|
||||
data.dptr = pData;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_store(pCursr->pFile->dbf, key, data, GDBM_REPLACE);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove an entry from a table, if the entry exists.
|
||||
*/
|
||||
static int sqliteGdbmDelete(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
int rc;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_delete(pCursr->pFile->dbf, key);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a temporary file. The file is located in the same directory
|
||||
** as the rest of the database.
|
||||
*/
|
||||
static int sqliteGdbmOpenTempFile(Dbbe *pDbbe, FILE **ppFile){
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
return sqliteDbbeOpenTempFile(pBe->zDir, pDbbe, ppFile);
|
||||
}
|
||||
|
||||
/*
|
||||
** This variable contains pointers to all of the access methods
|
||||
** used to implement the GDBM backend.
|
||||
*/
|
||||
static struct DbbeMethods gdbmMethods = {
|
||||
/* n Close */ sqliteGdbmClose,
|
||||
/* OpenCursor */ sqliteGdbmOpenCursor,
|
||||
/* DropTable */ sqliteGdbmDropTable,
|
||||
/* ReorganizeTable */ sqliteGdbmReorganizeTable,
|
||||
/* CloseCursor */ sqliteGdbmCloseCursor,
|
||||
/* Fetch */ sqliteGdbmFetch,
|
||||
/* Test */ sqliteGdbmTest,
|
||||
/* CopyKey */ sqliteGdbmCopyKey,
|
||||
/* CopyData */ sqliteGdbmCopyData,
|
||||
/* ReadKey */ sqliteGdbmReadKey,
|
||||
/* ReadData */ sqliteGdbmReadData,
|
||||
/* KeyLength */ sqliteGdbmKeyLength,
|
||||
/* DataLength */ sqliteGdbmDataLength,
|
||||
/* NextKey */ sqliteGdbmNextKey,
|
||||
/* Rewind */ sqliteGdbmRewind,
|
||||
/* New */ sqliteGdbmNew,
|
||||
/* Put */ sqliteGdbmPut,
|
||||
/* Delete */ sqliteGdbmDelete,
|
||||
/* OpenTempFile */ sqliteGdbmOpenTempFile,
|
||||
/* CloseTempFile */ sqliteDbbeCloseTempFile
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
** This routine opens a new database. For the GDBM driver
|
||||
** implemented here, the database name is the name of the directory
|
||||
** containing all the files of the database.
|
||||
**
|
||||
** If successful, a pointer to the Dbbe structure is returned.
|
||||
** If there are errors, an appropriate error message is left
|
||||
** in *pzErrMsg and NULL is returned.
|
||||
*/
|
||||
Dbbe *sqliteGdbmOpen(
|
||||
const char *zName, /* The name of the database */
|
||||
int writeFlag, /* True if we will be writing to the database */
|
||||
int createFlag, /* True to create database if it doesn't exist */
|
||||
char **pzErrMsg /* Write error messages (if any) here */
|
||||
){
|
||||
Dbbex *pNew;
|
||||
struct stat statbuf;
|
||||
char *zMaster;
|
||||
|
||||
if( !writeFlag ) createFlag = 0;
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
if( createFlag ) mkdir(zName, 0750);
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
sqliteSetString(pzErrMsg, createFlag ?
|
||||
"can't find or create directory \"" : "can't find directory \"",
|
||||
zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if( !S_ISDIR(statbuf.st_mode) ){
|
||||
sqliteSetString(pzErrMsg, "not a directory: \"", zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
if( access(zName, writeFlag ? (X_OK|W_OK|R_OK) : (X_OK|R_OK)) ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied", 0);
|
||||
return 0;
|
||||
}
|
||||
zMaster = 0;
|
||||
sqliteSetString(&zMaster, zName, "/" MASTER_NAME ".tbl", 0);
|
||||
if( stat(zMaster, &statbuf)==0
|
||||
&& access(zMaster, writeFlag ? (W_OK|R_OK) : R_OK)!=0 ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied for ", zMaster, 0);
|
||||
sqliteFree(zMaster);
|
||||
return 0;
|
||||
}
|
||||
sqliteFree(zMaster);
|
||||
pNew = sqliteMalloc(sizeof(Dbbex) + strlen(zName) + 1);
|
||||
if( pNew==0 ){
|
||||
sqliteSetString(pzErrMsg, "out of memory", 0);
|
||||
return 0;
|
||||
}
|
||||
pNew->dbbe.x = &gdbmMethods;
|
||||
pNew->zDir = (char*)&pNew[1];
|
||||
strcpy(pNew->zDir, zName);
|
||||
pNew->write = writeFlag;
|
||||
pNew->pOpen = 0;
|
||||
return &pNew->dbbe;
|
||||
}
|
||||
+553
-528
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -24,7 +24,7 @@
|
||||
** This file contains C code routines that are called by the parser
|
||||
** to handle DELETE FROM statements.
|
||||
**
|
||||
** $Id: delete.c,v 1.6 2000/06/21 13:59:11 drh Exp $
|
||||
** $Id: delete.c,v 1.7 2001/01/15 22:51:10 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -105,9 +105,9 @@ void sqliteDeleteFrom(
|
||||
*/
|
||||
base = pParse->nTab;
|
||||
sqliteVdbeAddOp(v, OP_ListRewind, 0, 0, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_Open, base, 1, pTab->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenTbl, base, 1, pTab->zName, 0);
|
||||
for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
|
||||
sqliteVdbeAddOp(v, OP_Open, base+i, 1, pIdx->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, base+i, 1, pIdx->zName, 0);
|
||||
}
|
||||
end = sqliteVdbeMakeLabel(v);
|
||||
addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end, 0, 0);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
This directory is intended to hold "experimental" files.
|
||||
|
||||
The code in this directory does not necessary work. It may
|
||||
or may not be added to future SQLite releases. We just need
|
||||
a place to put ideas and works-in-progress and so this
|
||||
directory was created.
|
||||
|
||||
If you are interested in the production versions of SQLite,
|
||||
you can safely ignore this directory.
|
||||
+1495
File diff suppressed because it is too large
Load Diff
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
** Copyright (c) 2001 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** $Id: db.h,v 1.1 2001/02/11 16:56:24 drh Exp $
|
||||
*/
|
||||
|
||||
typedef struct Db Db;
|
||||
typedef struct DbCursor DbCursor;
|
||||
|
||||
int sqliteDbOpen(const char *filename, Db**);
|
||||
int sqliteDbClose(Db*);
|
||||
int sqliteDbBeginTransaction(Db*);
|
||||
int sqliteDbCommit(Db*);
|
||||
int sqliteDbRollback(Db*);
|
||||
|
||||
int sqliteDbCreateTable(Db*, int *pTblno);
|
||||
int sqliteDbDropTable(Db*, int tblno);
|
||||
|
||||
int sqliteDbCursorOpen(Db*, int tblno, DbCursor**);
|
||||
int sqliteDbCursorClose(DbCursor*);
|
||||
|
||||
int sqliteDbCursorFirst(DbCursor*);
|
||||
int sqliteDbCursorNext(DbCursor*);
|
||||
int sqliteDbCursorDatasize(DbCursor*);
|
||||
int sqliteDbCursorKeysize(DbCursor*);
|
||||
int sqliteDbCursorRead(DbCursor*, int amt, int offset, void *buf);
|
||||
int sqliteDbCursorReadKey(DbCursor*, int amt, int offset, void *buf);
|
||||
int sqliteDbCursorWrite(DbCursor*, int amt, int offset, const void *buf);
|
||||
|
||||
int sqliteDbCursorFind(DbCursor*, int nKey, const void *pKey, int createFlag);
|
||||
int sqliteDbCursorResize(DbCursor*, int nData);
|
||||
@@ -0,0 +1,618 @@
|
||||
/*
|
||||
** Copyright (c) 2000 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains code to implement the database backend (DBBE)
|
||||
** for sqlite. The database backend is the interface between
|
||||
** sqlite and the code that does the actually reading and writing
|
||||
** of information to the disk.
|
||||
**
|
||||
** This file uses Berkeley Database version 1.85 as the database backend.
|
||||
**
|
||||
** $Id: dbbebdb1.c,v 1.1 2001/02/11 16:56:24 drh Exp $
|
||||
*/
|
||||
#ifdef USE_BDB2
|
||||
|
||||
#include "sqliteInt.h"
|
||||
#include <sys/types.h>
|
||||
#include <limits.h>
|
||||
#include <db.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
/*
|
||||
** Information about each open disk file is an instance of this
|
||||
** structure. There will only be one such structure for each
|
||||
** disk file. If the VDBE opens the same file twice (as will happen
|
||||
** for a self-join, for example) then two DbbeCursor structures are
|
||||
** created but there is only a single BeFile structure with an
|
||||
** nRef of 2.
|
||||
*/
|
||||
typedef struct BeFile BeFile;
|
||||
struct BeFile {
|
||||
char *zName; /* Name of the file */
|
||||
DB dbf; /* The file itself */
|
||||
int nRef; /* Number of references */
|
||||
int delOnClose; /* Delete when closing */
|
||||
int writeable; /* Opened for writing */
|
||||
DbbeCursor *pCursor; /* Which of several DbbeCursors has the file cursor */
|
||||
BeFile *pNext, *pPrev; /* Next and previous on list of open files */
|
||||
};
|
||||
|
||||
/*
|
||||
** The following structure contains all information used by BDB2
|
||||
** database driver. This is a subclass of the Dbbe structure.
|
||||
*/
|
||||
typedef struct Dbbex Dbbex;
|
||||
struct Dbbex {
|
||||
Dbbe dbbe; /* The base class */
|
||||
int write; /* True for write permission */
|
||||
BeFile *pOpen; /* List of open files */
|
||||
char *zDir; /* Directory hold the database */
|
||||
};
|
||||
|
||||
/*
|
||||
** An cursor into a database file is an instance of the following structure.
|
||||
** There can only be a single BeFile structure for each disk file, but
|
||||
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
|
||||
** a cursor pointing to a particular part of the open BeFile. The
|
||||
** BeFile.nRef field hold a count of the number of DbbeCursor structures
|
||||
** associated with the same disk file.
|
||||
*/
|
||||
struct DbbeCursor {
|
||||
Dbbex *pBe; /* The database of which this record is a part */
|
||||
BeFile *pFile; /* The database file for this table */
|
||||
DBT key; /* Most recently used key */
|
||||
DBT data; /* Most recent data */
|
||||
int needRewind; /* Next key should be the first */
|
||||
int readPending; /* The fetch hasn't actually been done yet */
|
||||
};
|
||||
|
||||
/*
|
||||
** The "mkdir()" function only takes one argument under Windows.
|
||||
*/
|
||||
#if OS_WIN
|
||||
# define mkdir(A,B) mkdir(A)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Forward declaration
|
||||
*/
|
||||
static void sqliteBdb1CloseCursor(DbbeCursor *pCursr);
|
||||
|
||||
/*
|
||||
** Completely shutdown the given database. Close all files. Free all memory.
|
||||
*/
|
||||
static void sqliteBdb1Close(Dbbe *pDbbe){
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
BeFile *pFile, *pNext;
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pNext){
|
||||
pNext = pFile->pNext;
|
||||
(*pFile->dbf)(pFile->dbf);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
sqliteDbbeCloseAllTempFiles(pDbbe);
|
||||
memset(pBe, 0, sizeof(*pBe));
|
||||
sqliteFree(pBe);
|
||||
}
|
||||
|
||||
/*
|
||||
** Translate the name of an SQL table (or index) into the name
|
||||
** of a file that holds the key/data pairs for that table or
|
||||
** index. Space to hold the filename is obtained from
|
||||
** sqliteMalloc() and must be freed by the calling function.
|
||||
*/
|
||||
static char *sqliteFileOfTable(Dbbex *pBe, const char *zTable){
|
||||
return sqliteDbbeNameToFile(pBe->zDir, zTable, ".tbl");
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a new table cursor. Write a pointer to the corresponding
|
||||
** DbbeCursor structure into *ppCursr. Return an integer success
|
||||
** code:
|
||||
**
|
||||
** SQLITE_OK It worked!
|
||||
**
|
||||
** SQLITE_NOMEM sqliteMalloc() failed
|
||||
**
|
||||
** SQLITE_PERM Attempt to access a file for which file
|
||||
** access permission is denied
|
||||
**
|
||||
** SQLITE_BUSY Another thread or process is already using
|
||||
** the corresponding file and has that file locked.
|
||||
**
|
||||
** SQLITE_READONLY The current thread already has this file open
|
||||
** readonly but you are trying to open for writing.
|
||||
** (This can happen if a SELECT callback tries to
|
||||
** do an UPDATE or DELETE.)
|
||||
**
|
||||
** If zTable is 0 or "", then a temporary database file is created and
|
||||
** a cursor to that temporary file is opened. The temporary file
|
||||
** will be deleted from the disk when it is closed.
|
||||
*/
|
||||
static int sqliteBdb1OpenCursor(
|
||||
Dbbe *pDbbe, /* The database the table belongs to */
|
||||
const char *zTable, /* The SQL name of the file to be opened */
|
||||
int writeable, /* True to open for writing */
|
||||
int intKeyOnly, /* True if only integer keys are used */
|
||||
DbbeCursor **ppCursr /* Write the resulting table pointer here */
|
||||
){
|
||||
char *zFile; /* Name of the table file */
|
||||
DbbeCursor *pCursr; /* The new table cursor */
|
||||
BeFile *pFile; /* The underlying data file for this table */
|
||||
int rc = SQLITE_OK; /* Return value */
|
||||
int open_flags; /* Flags passed to dbopen() */
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
|
||||
*ppCursr = 0;
|
||||
pCursr = sqliteMalloc( sizeof(*pCursr) );
|
||||
if( pCursr==0 ) return SQLITE_NOMEM;
|
||||
if( zTable ){
|
||||
zFile = sqliteFileOfTable(pBe, zTable);
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pFile->pNext){
|
||||
if( strcmp(pFile->zName,zFile)==0 ) break;
|
||||
}
|
||||
}else{
|
||||
pFile = 0;
|
||||
zFile = 0;
|
||||
}
|
||||
if( pFile==0 ){
|
||||
if( writeable ){
|
||||
open_flags = O_RDWR|O_CREAT
|
||||
}else{
|
||||
open_flags = O_RDONLY;
|
||||
}
|
||||
pFile = sqliteMalloc( sizeof(*pFile) );
|
||||
if( pFile==0 ){
|
||||
sqliteFree(zFile);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
if( zFile ){
|
||||
if( !writeable || pBe->write ){
|
||||
pFile->dbf = dbopen(zFile, open_flags, DB_HASH, 0);
|
||||
}else{
|
||||
pFile->dbf = 0;
|
||||
}
|
||||
}else{
|
||||
int limit;
|
||||
char zRandom[50];
|
||||
zFile = 0;
|
||||
limit = 5;
|
||||
do {
|
||||
sqliteRandomName(zRandom, "_temp_table_");
|
||||
sqliteFree(zFile);
|
||||
zFile = sqliteFileOfTable(pBe, zRandom);
|
||||
pFile->dbf = dbopen(zFile, open_flags, DB_HASH, 0);
|
||||
}while( pFile->dbf==0 && limit-- >= 0);
|
||||
pFile->delOnClose = 1;
|
||||
}
|
||||
pFile->writeable = writeable;
|
||||
pFile->zName = zFile;
|
||||
pFile->nRef = 1;
|
||||
pFile->pPrev = 0;
|
||||
if( pBe->pOpen ){
|
||||
pBe->pOpen->pPrev = pFile;
|
||||
}
|
||||
pFile->pCursor = 0;
|
||||
pFile->pNext = pBe->pOpen;
|
||||
pBe->pOpen = pFile;
|
||||
if( pFile->dbf==0 ){
|
||||
if( !writeable && access(zFile,0) ){
|
||||
/* Trying to read a non-existant file. This is OK. All the
|
||||
** reads will return empty, which is what we want. */
|
||||
rc = SQLITE_OK;
|
||||
}else if( pBe->write==0 ){
|
||||
rc = SQLITE_READONLY;
|
||||
}else if( access(zFile,W_OK|R_OK) ){
|
||||
rc = SQLITE_PERM;
|
||||
}else{
|
||||
rc = SQLITE_BUSY;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
sqliteFree(zFile);
|
||||
pFile->nRef++;
|
||||
if( writeable && !pFile->writeable ){
|
||||
rc = SQLITE_READONLY;
|
||||
}
|
||||
}
|
||||
pCursr->pBe = pBe;
|
||||
pCursr->pFile = pFile;
|
||||
pCursr->readPending = 0;
|
||||
pCursr->needRewind = 1;
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqliteBdb1CloseCursor(pCursr);
|
||||
*ppCursr = 0;
|
||||
}else{
|
||||
*ppCursr = pCursr;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Drop a table from the database. The file on the disk that corresponds
|
||||
** to this table is deleted.
|
||||
*/
|
||||
static void sqliteBdb1DropTable(Dbbe *pBe, const char *zTable){
|
||||
char *zFile; /* Name of the table file */
|
||||
|
||||
zFile = sqliteFileOfTable((Dbbex*)pBe, zTable);
|
||||
unlink(zFile);
|
||||
sqliteFree(zFile);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a cursor previously opened by sqliteBdb1OpenCursor().
|
||||
**
|
||||
** There can be multiple cursors pointing to the same open file.
|
||||
** The underlying file is not closed until all cursors have been
|
||||
** closed. This routine decrements the BeFile.nref field of the
|
||||
** underlying file and closes the file when nref reaches 0.
|
||||
*/
|
||||
static void sqliteBdb1CloseCursor(DbbeCursor *pCursr){
|
||||
BeFile *pFile;
|
||||
Dbbex *pBe;
|
||||
if( pCursr==0 ) return;
|
||||
pFile = pCursr->pFile;
|
||||
pBe = pCursr->pBe;
|
||||
if( pFile->pCursor==pCursr ){
|
||||
pFile->pCursor = 0;
|
||||
}
|
||||
pFile->nRef--;
|
||||
if( pFile->dbf!=NULL ){
|
||||
(*pFile->dbf->sync)(pFile->dbf, 0);
|
||||
}
|
||||
if( pFile->nRef<=0 ){
|
||||
if( pFile->dbf!=NULL ){
|
||||
(*pFile->dbf->close)(pFile->dbf);
|
||||
}
|
||||
if( pFile->pPrev ){
|
||||
pFile->pPrev->pNext = pFile->pNext;
|
||||
}else{
|
||||
pBe->pOpen = pFile->pNext;
|
||||
}
|
||||
if( pFile->pNext ){
|
||||
pFile->pNext->pPrev = pFile->pPrev;
|
||||
}
|
||||
if( pFile->delOnClose ){
|
||||
unlink(pFile->zName);
|
||||
}
|
||||
sqliteFree(pFile->zName);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
if( pCursr->key.dptr ) free(pCursr->key.dptr); ######
|
||||
if( pCursr->data.dptr ) free(pCursr->data.dptr); ######
|
||||
memset(pCursr, 0, sizeof(*pCursr));
|
||||
sqliteFree(pCursr);
|
||||
}
|
||||
|
||||
/*
|
||||
** Reorganize a table to reduce search times and disk usage.
|
||||
*/
|
||||
static int sqliteBdb1ReorganizeTable(Dbbe *pBe, const char *zTable){
|
||||
/* No-op */
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Clear the given datum
|
||||
*/
|
||||
static void datumClear(datum *p){
|
||||
if( p->dptr ) free(p->dptr); ########
|
||||
p->data = 0;
|
||||
p->size = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Fetch a single record from an open cursor. Return 1 on success
|
||||
** and 0 on failure.
|
||||
*/
|
||||
static int sqliteBdb1Fetch(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
DBT key;
|
||||
key.size = nKey;
|
||||
key.data = pKey;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return pCursr->data.dptr!=0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
static int sqliteBdb1Test(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
DBT key;
|
||||
int result = 0;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
result = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy bytes from the current key or data into a buffer supplied by
|
||||
** the calling function. Return the number of bytes copied.
|
||||
*/
|
||||
static
|
||||
int sqliteBdb1CopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( offset>=pCursr->key.dsize ) return 0;
|
||||
if( offset+size>pCursr->key.dsize ){
|
||||
n = pCursr->key.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->key.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
static
|
||||
int sqliteBdb1CopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset>=pCursr->data.dsize ) return 0;
|
||||
if( offset+size>pCursr->data.dsize ){
|
||||
n = pCursr->data.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->data.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to bytes from the key or data. The data returned
|
||||
** is ephemeral.
|
||||
*/
|
||||
static char *sqliteBdb1ReadKey(DbbeCursor *pCursr, int offset){
|
||||
if( offset<0 || offset>=pCursr->key.dsize ) return "";
|
||||
return &pCursr->key.dptr[offset];
|
||||
}
|
||||
static char *sqliteBdb1ReadData(DbbeCursor *pCursr, int offset){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset<0 || offset>=pCursr->data.dsize ) return "";
|
||||
return &pCursr->data.dptr[offset];
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the total number of bytes in either data or key.
|
||||
*/
|
||||
static int sqliteBdb1KeyLength(DbbeCursor *pCursr){
|
||||
return pCursr->key.dsize;
|
||||
}
|
||||
static int sqliteBdb1DataLength(DbbeCursor *pCursr){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
return pCursr->data.dsize;
|
||||
}
|
||||
|
||||
/*
|
||||
** Make is so that the next call to sqliteNextKey() finds the first
|
||||
** key of the table.
|
||||
*/
|
||||
static int sqliteBdb1Rewind(DbbeCursor *pCursr){
|
||||
pCursr->needRewind = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read the next key from the table. Return 1 on success. Return
|
||||
** 0 if there are no more keys.
|
||||
*/
|
||||
static int sqliteBdb1NextKey(DbbeCursor *pCursr){
|
||||
DBT nextkey;
|
||||
int rc;
|
||||
if( pCursr==0 || pCursr->pFile==0 || pCursr->pFile->dbf==0 ){
|
||||
pCursr->readPending = 0;
|
||||
return 0;
|
||||
}
|
||||
if( pCursr->needRewind ){
|
||||
nextkey = gdbm_firstkey(pCursr->pFile->dbf);
|
||||
pCursr->needRewind = 0;
|
||||
}else{
|
||||
nextkey = gdbm_nextkey(pCursr->pFile->dbf, pCursr->key);
|
||||
}
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
pCursr->key = nextkey;
|
||||
if( pCursr->key.dptr ){
|
||||
pCursr->readPending = 1;
|
||||
rc = 1;
|
||||
}else{
|
||||
pCursr->needRewind = 1;
|
||||
pCursr->readPending = 0;
|
||||
rc = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a new integer key.
|
||||
*/
|
||||
static int sqliteBdb1New(DbbeCursor *pCursr){
|
||||
int iKey;
|
||||
DBT key;
|
||||
int go = 1;
|
||||
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return 1;
|
||||
while( go ){
|
||||
iKey = sqliteRandomInteger();
|
||||
if( iKey==0 ) continue;
|
||||
key.dptr = (char*)&iKey;
|
||||
key.dsize = 4;
|
||||
go = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return iKey;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write an entry into the table. Overwrite any prior entry with the
|
||||
** same key.
|
||||
*/
|
||||
static int sqliteBdb1Put(
|
||||
DbbeCursor *pCursr, /* Write to the database associated with this cursor */
|
||||
int nKey, /* Number of bytes in the key */
|
||||
char *pKey, /* The data for the key */
|
||||
int nData, /* Number of bytes of data */
|
||||
char *pData /* The data */
|
||||
){
|
||||
DBT data, key;
|
||||
int rc;
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
data.dsize = nData;
|
||||
data.dptr = pData;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_store(pCursr->pFile->dbf, key, data, GDBM_REPLACE);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove an entry from a table, if the entry exists.
|
||||
*/
|
||||
static int sqliteBdb1Delete(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
DBT key;
|
||||
int rc;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_delete(pCursr->pFile->dbf, key);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a temporary file. The file is located in the same directory
|
||||
** as the rest of the database.
|
||||
*/
|
||||
static int sqliteBdb1OpenTempFile(Dbbe *pDbbe, FILE **ppFile){
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
return sqliteDbbeOpenTempFile(pBe->zDir, pDbbe, ppFile);
|
||||
}
|
||||
|
||||
/*
|
||||
** This variable contains pointers to all of the access methods
|
||||
** used to implement the GDBM backend.
|
||||
*/
|
||||
static struct DbbeMethods gdbmMethods = {
|
||||
/* n Close */ sqliteBdb1Close,
|
||||
/* OpenCursor */ sqliteBdb1OpenCursor,
|
||||
/* DropTable */ sqliteBdb1DropTable,
|
||||
/* ReorganizeTable */ sqliteBdb1ReorganizeTable,
|
||||
/* CloseCursor */ sqliteBdb1CloseCursor,
|
||||
/* Fetch */ sqliteBdb1Fetch,
|
||||
/* Test */ sqliteBdb1Test,
|
||||
/* CopyKey */ sqliteBdb1CopyKey,
|
||||
/* CopyData */ sqliteBdb1CopyData,
|
||||
/* ReadKey */ sqliteBdb1ReadKey,
|
||||
/* ReadData */ sqliteBdb1ReadData,
|
||||
/* KeyLength */ sqliteBdb1KeyLength,
|
||||
/* DataLength */ sqliteBdb1DataLength,
|
||||
/* NextKey */ sqliteBdb1NextKey,
|
||||
/* Rewind */ sqliteBdb1Rewind,
|
||||
/* New */ sqliteBdb1New,
|
||||
/* Put */ sqliteBdb1Put,
|
||||
/* Delete */ sqliteBdb1Delete,
|
||||
/* OpenTempFile */ sqliteBdb1OpenTempFile,
|
||||
/* CloseTempFile */ sqliteDbbeCloseTempFile
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
** This routine opens a new database. For the GDBM driver
|
||||
** implemented here, the database name is the name of the directory
|
||||
** containing all the files of the database.
|
||||
**
|
||||
** If successful, a pointer to the Dbbe structure is returned.
|
||||
** If there are errors, an appropriate error message is left
|
||||
** in *pzErrMsg and NULL is returned.
|
||||
*/
|
||||
Dbbe *sqliteBdb1Open(
|
||||
const char *zName, /* The name of the database */
|
||||
int writeFlag, /* True if we will be writing to the database */
|
||||
int createFlag, /* True to create database if it doesn't exist */
|
||||
char **pzErrMsg /* Write error messages (if any) here */
|
||||
){
|
||||
Dbbex *pNew;
|
||||
struct stat statbuf;
|
||||
char *zMaster;
|
||||
|
||||
if( !writeFlag ) createFlag = 0;
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
if( createFlag ) mkdir(zName, 0750);
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
sqliteSetString(pzErrMsg, createFlag ?
|
||||
"can't find or create directory \"" : "can't find directory \"",
|
||||
zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if( !S_ISDIR(statbuf.st_mode) ){
|
||||
sqliteSetString(pzErrMsg, "not a directory: \"", zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
if( access(zName, writeFlag ? (X_OK|W_OK|R_OK) : (X_OK|R_OK)) ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied", 0);
|
||||
return 0;
|
||||
}
|
||||
zMaster = 0;
|
||||
sqliteSetString(&zMaster, zName, "/" MASTER_NAME ".tbl", 0);
|
||||
if( stat(zMaster, &statbuf)==0
|
||||
&& access(zMaster, writeFlag ? (W_OK|R_OK) : R_OK)!=0 ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied for ", zMaster, 0);
|
||||
sqliteFree(zMaster);
|
||||
return 0;
|
||||
}
|
||||
sqliteFree(zMaster);
|
||||
pNew = sqliteMalloc(sizeof(Dbbex) + strlen(zName) + 1);
|
||||
if( pNew==0 ){
|
||||
sqliteSetString(pzErrMsg, "out of memory", 0);
|
||||
return 0;
|
||||
}
|
||||
pNew->dbbe.x = &gdbmMethods;
|
||||
pNew->zDir = (char*)&pNew[1];
|
||||
strcpy(pNew->zDir, zName);
|
||||
pNew->write = writeFlag;
|
||||
pNew->pOpen = 0;
|
||||
return &pNew->dbbe;
|
||||
}
|
||||
@@ -0,0 +1,776 @@
|
||||
/*
|
||||
** Copyright (c) 2000 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains code to implement the database backend (DBBE)
|
||||
** for sqlite. The database backend is the interface between
|
||||
** sqlite and the code that does the actually reading and writing
|
||||
** of information to the disk.
|
||||
**
|
||||
** This file uses GDBM as the database backend. It should be
|
||||
** relatively simple to convert to a different database such
|
||||
** as NDBM, SDBM, or BerkeleyDB.
|
||||
**
|
||||
** $Id: dbbemird.c,v 1.1 2001/02/11 16:56:24 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <gdbm.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
/*
|
||||
** Information about each open disk file is an instance of this
|
||||
** structure. There will only be one such structure for each
|
||||
** disk file. If the VDBE opens the same file twice (as will happen
|
||||
** for a self-join, for example) then two DbbeCursor structures are
|
||||
** created but there is only a single BeFile structure with an
|
||||
** nRef of 2.
|
||||
*/
|
||||
typedef struct BeFile BeFile;
|
||||
struct BeFile {
|
||||
char *zName; /* Name of the file */
|
||||
GDBM_FILE dbf; /* The file itself */
|
||||
int nRef; /* Number of references */
|
||||
int delOnClose; /* Delete when closing */
|
||||
int writeable; /* Opened for writing */
|
||||
BeFile *pNext, *pPrev; /* Next and previous on list of open files */
|
||||
};
|
||||
|
||||
/*
|
||||
** The following structure holds the current state of the RC4 algorithm.
|
||||
** We use RC4 as a random number generator. Each call to RC4 gives
|
||||
** a random 8-bit number.
|
||||
**
|
||||
** Nothing in this file or anywhere else in SQLite does any kind of
|
||||
** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random
|
||||
** number generator) not as an encryption device.
|
||||
*/
|
||||
struct rc4 {
|
||||
int i, j;
|
||||
int s[256];
|
||||
};
|
||||
|
||||
/*
|
||||
** The following structure contains all information used by GDBM
|
||||
** database driver. This is a subclass of the Dbbe structure.
|
||||
*/
|
||||
typedef struct Dbbex Dbbex;
|
||||
struct Dbbex {
|
||||
Dbbe dbbe; /* The base class */
|
||||
char *zDir; /* The directory containing the database */
|
||||
int write; /* True for write permission */
|
||||
BeFile *pOpen; /* List of open files */
|
||||
int nTemp; /* Number of temporary files created */
|
||||
FILE **apTemp; /* Space to hold temporary file pointers */
|
||||
char **azTemp; /* Names of the temporary files */
|
||||
struct rc4 rc4; /* The random number generator */
|
||||
};
|
||||
|
||||
/*
|
||||
** An cursor into a database file is an instance of the following structure.
|
||||
** There can only be a single BeFile structure for each disk file, but
|
||||
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
|
||||
** a cursor pointing to a particular part of the open BeFile. The
|
||||
** BeFile.nRef field hold a count of the number of DbbeCursor structures
|
||||
** associated with the same disk file.
|
||||
*/
|
||||
struct DbbeCursor {
|
||||
Dbbex *pBe; /* The database of which this record is a part */
|
||||
BeFile *pFile; /* The database file for this table */
|
||||
datum key; /* Most recently used key */
|
||||
datum data; /* Most recent data */
|
||||
int needRewind; /* Next key should be the first */
|
||||
int readPending; /* The fetch hasn't actually been done yet */
|
||||
};
|
||||
|
||||
/*
|
||||
** Initialize the RC4 PRNG. "seed" is a pointer to some random
|
||||
** data used to initialize the PRNG.
|
||||
*/
|
||||
static void rc4init(struct rc4 *p, char *seed, int seedlen){
|
||||
int i;
|
||||
char k[256];
|
||||
p->j = 0;
|
||||
p->i = 0;
|
||||
for(i=0; i<256; i++){
|
||||
p->s[i] = i;
|
||||
k[i] = seed[i%seedlen];
|
||||
}
|
||||
for(i=0; i<256; i++){
|
||||
int t;
|
||||
p->j = (p->j + p->s[i] + k[i]) & 0xff;
|
||||
t = p->s[p->j];
|
||||
p->s[p->j] = p->s[i];
|
||||
p->s[i] = t;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a single 8-bit random value from the RC4 PRNG.
|
||||
*/
|
||||
static int rc4byte(struct rc4 *p){
|
||||
int t;
|
||||
p->i = (p->i + 1) & 0xff;
|
||||
p->j = (p->j + p->s[p->i]) & 0xff;
|
||||
t = p->s[p->i];
|
||||
p->s[p->i] = p->s[p->j];
|
||||
p->s[p->j] = t;
|
||||
t = p->s[p->i] + p->s[p->j];
|
||||
return t & 0xff;
|
||||
}
|
||||
|
||||
/*
|
||||
** The "mkdir()" function only takes one argument under Windows.
|
||||
*/
|
||||
#if OS_WIN
|
||||
# define mkdir(A,B) mkdir(A)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Forward declaration
|
||||
*/
|
||||
static void sqliteGdbmCloseCursor(DbbeCursor *pCursr);
|
||||
|
||||
/*
|
||||
** Completely shutdown the given database. Close all files. Free all memory.
|
||||
*/
|
||||
static void sqliteGdbmClose(Dbbe *pDbbe){
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
BeFile *pFile, *pNext;
|
||||
int i;
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pNext){
|
||||
pNext = pFile->pNext;
|
||||
gdbm_close(pFile->dbf);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
for(i=0; i<pBe->nTemp; i++){
|
||||
if( pBe->apTemp[i]!=0 ){
|
||||
unlink(pBe->azTemp[i]);
|
||||
fclose(pBe->apTemp[i]);
|
||||
sqliteFree(pBe->azTemp[i]);
|
||||
pBe->apTemp[i] = 0;
|
||||
pBe->azTemp[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sqliteFree(pBe->azTemp);
|
||||
sqliteFree(pBe->apTemp);
|
||||
memset(pBe, 0, sizeof(*pBe));
|
||||
sqliteFree(pBe);
|
||||
}
|
||||
|
||||
/*
|
||||
** Translate the name of an SQL table (or index) into the name
|
||||
** of a file that holds the key/data pairs for that table or
|
||||
** index. Space to hold the filename is obtained from
|
||||
** sqliteMalloc() and must be freed by the calling function.
|
||||
*/
|
||||
static char *sqliteFileOfTable(Dbbex *pBe, const char *zTable){
|
||||
char *zFile = 0;
|
||||
int i;
|
||||
sqliteSetString(&zFile, pBe->zDir, "/", zTable, ".tbl", 0);
|
||||
if( zFile==0 ) return 0;
|
||||
for(i=strlen(pBe->zDir)+1; zFile[i]; i++){
|
||||
int c = zFile[i];
|
||||
if( isupper(c) ){
|
||||
zFile[i] = tolower(c);
|
||||
}else if( !isalnum(c) && c!='-' && c!='_' && c!='.' ){
|
||||
zFile[i] = '+';
|
||||
}
|
||||
}
|
||||
return zFile;
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate a random filename with the given prefix. The new filename
|
||||
** is written into zBuf[]. The calling function must insure that
|
||||
** zBuf[] is big enough to hold the prefix plus 20 or so extra
|
||||
** characters.
|
||||
**
|
||||
** Very random names are chosen so that the chance of a
|
||||
** collision with an existing filename is very very small.
|
||||
*/
|
||||
static void randomName(struct rc4 *pRc4, char *zBuf, char *zPrefix){
|
||||
int i, j;
|
||||
static const char zRandomChars[] = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
strcpy(zBuf, zPrefix);
|
||||
j = strlen(zBuf);
|
||||
for(i=0; i<15; i++){
|
||||
int c = rc4byte(pRc4) % (sizeof(zRandomChars) - 1);
|
||||
zBuf[j++] = zRandomChars[c];
|
||||
}
|
||||
zBuf[j] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a new table cursor. Write a pointer to the corresponding
|
||||
** DbbeCursor structure into *ppCursr. Return an integer success
|
||||
** code:
|
||||
**
|
||||
** SQLITE_OK It worked!
|
||||
**
|
||||
** SQLITE_NOMEM sqliteMalloc() failed
|
||||
**
|
||||
** SQLITE_PERM Attempt to access a file for which file
|
||||
** access permission is denied
|
||||
**
|
||||
** SQLITE_BUSY Another thread or process is already using
|
||||
** the corresponding file and has that file locked.
|
||||
**
|
||||
** SQLITE_READONLY The current thread already has this file open
|
||||
** readonly but you are trying to open for writing.
|
||||
** (This can happen if a SELECT callback tries to
|
||||
** do an UPDATE or DELETE.)
|
||||
**
|
||||
** If zTable is 0 or "", then a temporary database file is created and
|
||||
** a cursor to that temporary file is opened. The temporary file
|
||||
** will be deleted from the disk when it is closed.
|
||||
*/
|
||||
static int sqliteGdbmOpenCursor(
|
||||
Dbbe *pDbbe, /* The database the table belongs to */
|
||||
const char *zTable, /* The SQL name of the file to be opened */
|
||||
int writeable, /* True to open for writing */
|
||||
DbbeCursor **ppCursr /* Write the resulting table pointer here */
|
||||
){
|
||||
char *zFile; /* Name of the table file */
|
||||
DbbeCursor *pCursr; /* The new table cursor */
|
||||
BeFile *pFile; /* The underlying data file for this table */
|
||||
int rc = SQLITE_OK; /* Return value */
|
||||
int rw_mask; /* Permissions mask for opening a table */
|
||||
int mode; /* Mode for opening a table */
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
|
||||
*ppCursr = 0;
|
||||
pCursr = sqliteMalloc( sizeof(*pCursr) );
|
||||
if( pCursr==0 ) return SQLITE_NOMEM;
|
||||
if( zTable ){
|
||||
zFile = sqliteFileOfTable(pBe, zTable);
|
||||
for(pFile=pBe->pOpen; pFile; pFile=pFile->pNext){
|
||||
if( strcmp(pFile->zName,zFile)==0 ) break;
|
||||
}
|
||||
}else{
|
||||
pFile = 0;
|
||||
zFile = 0;
|
||||
}
|
||||
if( pFile==0 ){
|
||||
if( writeable ){
|
||||
rw_mask = GDBM_WRCREAT | GDBM_FAST;
|
||||
mode = 0640;
|
||||
}else{
|
||||
rw_mask = GDBM_READER;
|
||||
mode = 0640;
|
||||
}
|
||||
pFile = sqliteMalloc( sizeof(*pFile) );
|
||||
if( pFile==0 ){
|
||||
sqliteFree(zFile);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
if( zFile ){
|
||||
if( !writeable || pBe->write ){
|
||||
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
|
||||
}else{
|
||||
pFile->dbf = 0;
|
||||
}
|
||||
}else{
|
||||
int limit;
|
||||
struct rc4 *pRc4;
|
||||
char zRandom[50];
|
||||
pRc4 = &pBe->rc4;
|
||||
zFile = 0;
|
||||
limit = 5;
|
||||
do {
|
||||
randomName(&pBe->rc4, zRandom, "_temp_table_");
|
||||
sqliteFree(zFile);
|
||||
zFile = sqliteFileOfTable(pBe, zRandom);
|
||||
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
|
||||
}while( pFile->dbf==0 && limit-- >= 0);
|
||||
pFile->delOnClose = 1;
|
||||
}
|
||||
pFile->writeable = writeable;
|
||||
pFile->zName = zFile;
|
||||
pFile->nRef = 1;
|
||||
pFile->pPrev = 0;
|
||||
if( pBe->pOpen ){
|
||||
pBe->pOpen->pPrev = pFile;
|
||||
}
|
||||
pFile->pNext = pBe->pOpen;
|
||||
pBe->pOpen = pFile;
|
||||
if( pFile->dbf==0 ){
|
||||
if( !writeable && access(zFile,0) ){
|
||||
/* Trying to read a non-existant file. This is OK. All the
|
||||
** reads will return empty, which is what we want. */
|
||||
rc = SQLITE_OK;
|
||||
}else if( pBe->write==0 ){
|
||||
rc = SQLITE_READONLY;
|
||||
}else if( access(zFile,W_OK|R_OK) ){
|
||||
rc = SQLITE_PERM;
|
||||
}else{
|
||||
rc = SQLITE_BUSY;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
sqliteFree(zFile);
|
||||
pFile->nRef++;
|
||||
if( writeable && !pFile->writeable ){
|
||||
rc = SQLITE_READONLY;
|
||||
}
|
||||
}
|
||||
pCursr->pBe = pBe;
|
||||
pCursr->pFile = pFile;
|
||||
pCursr->readPending = 0;
|
||||
pCursr->needRewind = 1;
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqliteGdbmCloseCursor(pCursr);
|
||||
*ppCursr = 0;
|
||||
}else{
|
||||
*ppCursr = pCursr;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Drop a table from the database. The file on the disk that corresponds
|
||||
** to this table is deleted.
|
||||
*/
|
||||
static void sqliteGdbmDropTable(Dbbe *pBe, const char *zTable){
|
||||
char *zFile; /* Name of the table file */
|
||||
|
||||
zFile = sqliteFileOfTable((Dbbex*)pBe, zTable);
|
||||
unlink(zFile);
|
||||
sqliteFree(zFile);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a cursor previously opened by sqliteGdbmOpenCursor().
|
||||
**
|
||||
** There can be multiple cursors pointing to the same open file.
|
||||
** The underlying file is not closed until all cursors have been
|
||||
** closed. This routine decrements the BeFile.nref field of the
|
||||
** underlying file and closes the file when nref reaches 0.
|
||||
*/
|
||||
static void sqliteGdbmCloseCursor(DbbeCursor *pCursr){
|
||||
BeFile *pFile;
|
||||
Dbbex *pBe;
|
||||
if( pCursr==0 ) return;
|
||||
pFile = pCursr->pFile;
|
||||
pBe = pCursr->pBe;
|
||||
pFile->nRef--;
|
||||
if( pFile->dbf!=NULL ){
|
||||
gdbm_sync(pFile->dbf);
|
||||
}
|
||||
if( pFile->nRef<=0 ){
|
||||
if( pFile->dbf!=NULL ){
|
||||
gdbm_close(pFile->dbf);
|
||||
}
|
||||
if( pFile->pPrev ){
|
||||
pFile->pPrev->pNext = pFile->pNext;
|
||||
}else{
|
||||
pBe->pOpen = pFile->pNext;
|
||||
}
|
||||
if( pFile->pNext ){
|
||||
pFile->pNext->pPrev = pFile->pPrev;
|
||||
}
|
||||
if( pFile->delOnClose ){
|
||||
unlink(pFile->zName);
|
||||
}
|
||||
sqliteFree(pFile->zName);
|
||||
memset(pFile, 0, sizeof(*pFile));
|
||||
sqliteFree(pFile);
|
||||
}
|
||||
if( pCursr->key.dptr ) free(pCursr->key.dptr);
|
||||
if( pCursr->data.dptr ) free(pCursr->data.dptr);
|
||||
memset(pCursr, 0, sizeof(*pCursr));
|
||||
sqliteFree(pCursr);
|
||||
}
|
||||
|
||||
/*
|
||||
** Reorganize a table to reduce search times and disk usage.
|
||||
*/
|
||||
static int sqliteGdbmReorganizeTable(Dbbe *pBe, const char *zTable){
|
||||
DbbeCursor *pCrsr;
|
||||
int rc;
|
||||
|
||||
rc = sqliteGdbmOpenCursor(pBe, zTable, 1, &pCrsr);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
if( pCrsr && pCrsr->pFile && pCrsr->pFile->dbf ){
|
||||
gdbm_reorganize(pCrsr->pFile->dbf);
|
||||
}
|
||||
if( pCrsr ){
|
||||
sqliteGdbmCloseCursor(pCrsr);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Clear the given datum
|
||||
*/
|
||||
static void datumClear(datum *p){
|
||||
if( p->dptr ) free(p->dptr);
|
||||
p->dptr = 0;
|
||||
p->dsize = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Fetch a single record from an open cursor. Return 1 on success
|
||||
** and 0 on failure.
|
||||
*/
|
||||
static int sqliteGdbmFetch(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return pCursr->data.dptr!=0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return 1 if the given key is already in the table. Return 0
|
||||
** if it is not.
|
||||
*/
|
||||
static int sqliteGdbmTest(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
int result = 0;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
if( pCursr->pFile && pCursr->pFile->dbf ){
|
||||
result = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy bytes from the current key or data into a buffer supplied by
|
||||
** the calling function. Return the number of bytes copied.
|
||||
*/
|
||||
static
|
||||
int sqliteGdbmCopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( offset>=pCursr->key.dsize ) return 0;
|
||||
if( offset+size>pCursr->key.dsize ){
|
||||
n = pCursr->key.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->key.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
static
|
||||
int sqliteGdbmCopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
|
||||
int n;
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset>=pCursr->data.dsize ) return 0;
|
||||
if( offset+size>pCursr->data.dsize ){
|
||||
n = pCursr->data.dsize - offset;
|
||||
}else{
|
||||
n = size;
|
||||
}
|
||||
memcpy(zBuf, &pCursr->data.dptr[offset], n);
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to bytes from the key or data. The data returned
|
||||
** is ephemeral.
|
||||
*/
|
||||
static char *sqliteGdbmReadKey(DbbeCursor *pCursr, int offset){
|
||||
if( offset<0 || offset>=pCursr->key.dsize ) return "";
|
||||
return &pCursr->key.dptr[offset];
|
||||
}
|
||||
static char *sqliteGdbmReadData(DbbeCursor *pCursr, int offset){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
if( offset<0 || offset>=pCursr->data.dsize ) return "";
|
||||
return &pCursr->data.dptr[offset];
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the total number of bytes in either data or key.
|
||||
*/
|
||||
static int sqliteGdbmKeyLength(DbbeCursor *pCursr){
|
||||
return pCursr->key.dsize;
|
||||
}
|
||||
static int sqliteGdbmDataLength(DbbeCursor *pCursr){
|
||||
if( pCursr->readPending && pCursr->pFile && pCursr->pFile->dbf ){
|
||||
pCursr->data = gdbm_fetch(pCursr->pFile->dbf, pCursr->key);
|
||||
pCursr->readPending = 0;
|
||||
}
|
||||
return pCursr->data.dsize;
|
||||
}
|
||||
|
||||
/*
|
||||
** Make is so that the next call to sqliteNextKey() finds the first
|
||||
** key of the table.
|
||||
*/
|
||||
static int sqliteGdbmRewind(DbbeCursor *pCursr){
|
||||
pCursr->needRewind = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read the next key from the table. Return 1 on success. Return
|
||||
** 0 if there are no more keys.
|
||||
*/
|
||||
static int sqliteGdbmNextKey(DbbeCursor *pCursr){
|
||||
datum nextkey;
|
||||
int rc;
|
||||
if( pCursr==0 || pCursr->pFile==0 || pCursr->pFile->dbf==0 ){
|
||||
pCursr->readPending = 0;
|
||||
return 0;
|
||||
}
|
||||
if( pCursr->needRewind ){
|
||||
nextkey = gdbm_firstkey(pCursr->pFile->dbf);
|
||||
pCursr->needRewind = 0;
|
||||
}else{
|
||||
nextkey = gdbm_nextkey(pCursr->pFile->dbf, pCursr->key);
|
||||
}
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
pCursr->key = nextkey;
|
||||
if( pCursr->key.dptr ){
|
||||
pCursr->readPending = 1;
|
||||
rc = 1;
|
||||
}else{
|
||||
pCursr->needRewind = 1;
|
||||
pCursr->readPending = 0;
|
||||
rc = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a new integer key.
|
||||
*/
|
||||
static int sqliteGdbmNew(DbbeCursor *pCursr){
|
||||
int iKey;
|
||||
datum key;
|
||||
int go = 1;
|
||||
int i;
|
||||
struct rc4 *pRc4;
|
||||
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return 1;
|
||||
pRc4 = &pCursr->pBe->rc4;
|
||||
while( go ){
|
||||
iKey = 0;
|
||||
for(i=0; i<4; i++){
|
||||
iKey = (iKey<<8) + rc4byte(pRc4);
|
||||
}
|
||||
if( iKey==0 ) continue;
|
||||
key.dptr = (char*)&iKey;
|
||||
key.dsize = 4;
|
||||
go = gdbm_exists(pCursr->pFile->dbf, key);
|
||||
}
|
||||
return iKey;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write an entry into the table. Overwrite any prior entry with the
|
||||
** same key.
|
||||
*/
|
||||
static int
|
||||
sqliteGdbmPut(DbbeCursor *pCursr, int nKey,char *pKey,int nData,char *pData){
|
||||
datum data, key;
|
||||
int rc;
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
data.dsize = nData;
|
||||
data.dptr = pData;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_store(pCursr->pFile->dbf, key, data, GDBM_REPLACE);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove an entry from a table, if the entry exists.
|
||||
*/
|
||||
static int sqliteGdbmDelete(DbbeCursor *pCursr, int nKey, char *pKey){
|
||||
datum key;
|
||||
int rc;
|
||||
datumClear(&pCursr->key);
|
||||
datumClear(&pCursr->data);
|
||||
if( pCursr->pFile==0 || pCursr->pFile->dbf==0 ) return SQLITE_ERROR;
|
||||
key.dsize = nKey;
|
||||
key.dptr = pKey;
|
||||
rc = gdbm_delete(pCursr->pFile->dbf, key);
|
||||
if( rc ) rc = SQLITE_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a temporary file. The file should be deleted when closed.
|
||||
**
|
||||
** Note that we can't use the old Unix trick of opening the file
|
||||
** and then immediately unlinking the file. That works great
|
||||
** under Unix, but fails when we try to port to Windows.
|
||||
*/
|
||||
static int sqliteGdbmOpenTempFile(Dbbe *pDbbe, FILE **ppFile){
|
||||
char *zFile; /* Full name of the temporary file */
|
||||
char zBuf[50]; /* Base name of the temporary file */
|
||||
int i; /* Loop counter */
|
||||
int limit; /* Prevent an infinite loop */
|
||||
int rc = SQLITE_OK; /* Value returned by this function */
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
|
||||
for(i=0; i<pBe->nTemp; i++){
|
||||
if( pBe->apTemp[i]==0 ) break;
|
||||
}
|
||||
if( i>=pBe->nTemp ){
|
||||
pBe->nTemp++;
|
||||
pBe->apTemp = sqliteRealloc(pBe->apTemp, pBe->nTemp*sizeof(FILE*) );
|
||||
pBe->azTemp = sqliteRealloc(pBe->azTemp, pBe->nTemp*sizeof(char*) );
|
||||
}
|
||||
if( pBe->apTemp==0 ){
|
||||
*ppFile = 0;
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
limit = 4;
|
||||
zFile = 0;
|
||||
do{
|
||||
randomName(&pBe->rc4, zBuf, "/_temp_file_");
|
||||
sqliteFree(zFile);
|
||||
zFile = 0;
|
||||
sqliteSetString(&zFile, pBe->zDir, zBuf, 0);
|
||||
}while( access(zFile,0)==0 && limit-- >= 0 );
|
||||
*ppFile = pBe->apTemp[i] = fopen(zFile, "w+");
|
||||
if( pBe->apTemp[i]==0 ){
|
||||
rc = SQLITE_ERROR;
|
||||
sqliteFree(zFile);
|
||||
pBe->azTemp[i] = 0;
|
||||
}else{
|
||||
pBe->azTemp[i] = zFile;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a temporary file opened using sqliteGdbmOpenTempFile()
|
||||
*/
|
||||
static void sqliteGdbmCloseTempFile(Dbbe *pDbbe, FILE *f){
|
||||
int i;
|
||||
Dbbex *pBe = (Dbbex*)pDbbe;
|
||||
for(i=0; i<pBe->nTemp; i++){
|
||||
if( pBe->apTemp[i]==f ){
|
||||
unlink(pBe->azTemp[i]);
|
||||
sqliteFree(pBe->azTemp[i]);
|
||||
pBe->apTemp[i] = 0;
|
||||
pBe->azTemp[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** This routine opens a new database. For the GDBM driver
|
||||
** implemented here, the database name is the name of the directory
|
||||
** containing all the files of the database.
|
||||
**
|
||||
** If successful, a pointer to the Dbbe structure is returned.
|
||||
** If there are errors, an appropriate error message is left
|
||||
** in *pzErrMsg and NULL is returned.
|
||||
*/
|
||||
Dbbe *sqliteGdbmOpen(
|
||||
const char *zName, /* The name of the database */
|
||||
int writeFlag, /* True if we will be writing to the database */
|
||||
int createFlag, /* True to create database if it doesn't exist */
|
||||
char **pzErrMsg /* Write error messages (if any) here */
|
||||
){
|
||||
Dbbex *pNew;
|
||||
struct stat statbuf;
|
||||
char *zMaster;
|
||||
|
||||
if( !writeFlag ) createFlag = 0;
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
if( createFlag ) mkdir(zName, 0750);
|
||||
if( stat(zName, &statbuf)!=0 ){
|
||||
sqliteSetString(pzErrMsg, createFlag ?
|
||||
"can't find or create directory \"" : "can't find directory \"",
|
||||
zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if( !S_ISDIR(statbuf.st_mode) ){
|
||||
sqliteSetString(pzErrMsg, "not a directory: \"", zName, "\"", 0);
|
||||
return 0;
|
||||
}
|
||||
if( access(zName, writeFlag ? (X_OK|W_OK|R_OK) : (X_OK|R_OK)) ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied", 0);
|
||||
return 0;
|
||||
}
|
||||
zMaster = 0;
|
||||
sqliteSetString(&zMaster, zName, "/" MASTER_NAME ".tbl", 0);
|
||||
if( stat(zMaster, &statbuf)==0
|
||||
&& access(zMaster, writeFlag ? (W_OK|R_OK) : R_OK)!=0 ){
|
||||
sqliteSetString(pzErrMsg, "access permission denied for ", zMaster, 0);
|
||||
sqliteFree(zMaster);
|
||||
return 0;
|
||||
}
|
||||
sqliteFree(zMaster);
|
||||
pNew = sqliteMalloc(sizeof(Dbbex) + strlen(zName) + 1);
|
||||
if( pNew==0 ){
|
||||
sqliteSetString(pzErrMsg, "out of memory", 0);
|
||||
return 0;
|
||||
}
|
||||
pNew->dbbe.Close = sqliteGdbmClose;
|
||||
pNew->dbbe.OpenCursor = sqliteGdbmOpenCursor;
|
||||
pNew->dbbe.DropTable = sqliteGdbmDropTable;
|
||||
pNew->dbbe.ReorganizeTable = sqliteGdbmReorganizeTable;
|
||||
pNew->dbbe.CloseCursor = sqliteGdbmCloseCursor;
|
||||
pNew->dbbe.Fetch = sqliteGdbmFetch;
|
||||
pNew->dbbe.Test = sqliteGdbmTest;
|
||||
pNew->dbbe.CopyKey = sqliteGdbmCopyKey;
|
||||
pNew->dbbe.CopyData = sqliteGdbmCopyData;
|
||||
pNew->dbbe.ReadKey = sqliteGdbmReadKey;
|
||||
pNew->dbbe.ReadData = sqliteGdbmReadData;
|
||||
pNew->dbbe.KeyLength = sqliteGdbmKeyLength;
|
||||
pNew->dbbe.DataLength = sqliteGdbmDataLength;
|
||||
pNew->dbbe.NextKey = sqliteGdbmNextKey;
|
||||
pNew->dbbe.Rewind = sqliteGdbmRewind;
|
||||
pNew->dbbe.New = sqliteGdbmNew;
|
||||
pNew->dbbe.Put = sqliteGdbmPut;
|
||||
pNew->dbbe.Delete = sqliteGdbmDelete;
|
||||
pNew->dbbe.OpenTempFile = sqliteGdbmOpenTempFile;
|
||||
pNew->dbbe.CloseTempFile = sqliteGdbmCloseTempFile;
|
||||
pNew->zDir = (char*)&pNew[1];
|
||||
strcpy(pNew->zDir, zName);
|
||||
pNew->write = writeFlag;
|
||||
pNew->pOpen = 0;
|
||||
time(&statbuf.st_ctime);
|
||||
rc4init(&pNew->rc4, (char*)&statbuf, sizeof(statbuf));
|
||||
return &pNew->dbbe;
|
||||
}
|
||||
+708
@@ -0,0 +1,708 @@
|
||||
/*
|
||||
** Copyright (c) 2001 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** $Id: pg.c,v 1.1 2001/02/11 16:56:24 drh Exp $
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include "sqliteInt.h"
|
||||
#include "pg.h"
|
||||
|
||||
/*
|
||||
** Uncomment the following for a debug trace
|
||||
*/
|
||||
#if 1
|
||||
# define TRACE(X) printf X; fflush(stdout);
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Hash table sizes
|
||||
*/
|
||||
#define J_HASH_SIZE 127 /* Size of the journal page hash table */
|
||||
#define PG_HASH_SIZE 349 /* Size of the database page hash table */
|
||||
|
||||
/*
|
||||
** Forward declaration of structure
|
||||
*/
|
||||
typedef struct Pghdr Pghdr;
|
||||
|
||||
/*
|
||||
** All information about a single paging file is contained in an
|
||||
** instance of the following structure.
|
||||
*/
|
||||
struct Pgr {
|
||||
int fdMain; /* The main database file */
|
||||
char *zMain; /* Name of the database file */
|
||||
int fdJournal; /* The journal file */
|
||||
char *zJournal; /* Name of the journal file */
|
||||
int nMemPg; /* Number of memory-resident pages */
|
||||
int nJPg; /* Number of pages in the journal */
|
||||
int nDbPg; /* Number of pages in the database */
|
||||
int nRefPg; /* Number of pages currently in use */
|
||||
Pghdr *pLru, *pMru; /* Least and most recently used mem-page */
|
||||
Pghdr *pJidx; /* List of journal index pages */
|
||||
Pghdr *pAll; /* All pages, except journal index pages */
|
||||
u32 aJHash[J_HASH_SIZE]; /* Journal page hash table */
|
||||
Pghdr *aPgHash[PG_HASH_SIZE]; /* Mem-page hash table */
|
||||
};
|
||||
|
||||
/*
|
||||
** Each memory-resident page of the paging file has a header which
|
||||
** is an instance of the following structure.
|
||||
*/
|
||||
struct Pghdr {
|
||||
Pgr *p; /* Pointer back to the Pgr structure */
|
||||
int nRef; /* Number of references to this page */
|
||||
int isDirty; /* TRUE if needs to be written to disk */
|
||||
u32 dbpgno; /* Page number in the database file */
|
||||
u32 jpgno; /* Page number in the journal file */
|
||||
Pghdr *pNx; /* Next page on a list of them all */
|
||||
Pghdr *pLru; /* Less recently used pages */
|
||||
Pghdr *pMru; /* More recently used pages */
|
||||
Pghdr *pNxHash; /* Next with same dbpgno hash */
|
||||
Pghdr *pPvHash; /* Previous with the same dbpgno hash */
|
||||
};
|
||||
|
||||
/*
|
||||
** For a memory-resident page, the page data comes immediately after
|
||||
** the page header. The following macros can be used to change a
|
||||
** pointer to a page header into a pointer to the data, or vice
|
||||
** versa.
|
||||
*/
|
||||
#define PG_TO_DATA(X) ((void*)&(X)[1])
|
||||
#define DATA_TO_PG(X) (&((Pghdr*)(X))[-1])
|
||||
|
||||
/*
|
||||
** The number of in-memory pages that we accumulate before trying
|
||||
** to reuse older pages when new ones are requested.
|
||||
*/
|
||||
#define MX_MEM_PAGE 100
|
||||
|
||||
/*
|
||||
** The number of journal data pages that come between consecutive
|
||||
** journal index pages.
|
||||
*/
|
||||
#define N_J_DATAPAGE (SQLITE_PAGE_SIZE/(2*sizeof(u32)))
|
||||
|
||||
/*
|
||||
** An index page in the journal consists of an array of N_J_DATAPAGE
|
||||
** of the following structures. There is one instance of the following
|
||||
** structure for each of the N_J_DATAPAGE data pages that follow the
|
||||
** index.
|
||||
**
|
||||
** Let the journal page number that a JidxEntry describes be J. Then
|
||||
** the JidxEntry.dbpgno field is the page of the database file that
|
||||
** corresponds to the J page in the journal. The JidxEntry.next_jpgno
|
||||
** field hold the number of another journal page that contains
|
||||
** a database file page with the same hash as JidxEntry.dbpgno.
|
||||
**
|
||||
** All information is written to the journal index in big-endian
|
||||
** notation.
|
||||
*/
|
||||
typedef struct JidxEntry JidxEntry;
|
||||
struct JidxEntry {
|
||||
char dbpgno[sizeof(u32)]; /* Database page number for this entry */
|
||||
char next_jpgno[sizeof(u32)]; /* Next entry with same hash on dbpgno */
|
||||
};
|
||||
|
||||
/*
|
||||
** Read a page from a file into memory. Return SQLITE_OK if successful.
|
||||
** The "pgno" parameter tells where in the file to read the page.
|
||||
** The first page is 1. Files do not contain a page 0 since a page
|
||||
** number of 0 is used to indicate "no such page".
|
||||
*/
|
||||
static int sqlitePgRead(int fd, char *zBuf, u32 pgno){
|
||||
int got = 0;
|
||||
int amt;
|
||||
|
||||
assert( pgno>0 );
|
||||
assert( fd>=0 );
|
||||
lseek(fd, SEEK_SET, (pgno-1)*SQLITE_PAGE_SIZE);
|
||||
while( got<SQLITE_PAGE_SIZE ){
|
||||
amt = read(fd, &zBuf[got], SQLITE_PAGE_SIZE - got);
|
||||
if( amt<=0 ){
|
||||
memset(&zBuf[got], 0, SQLITE_PAGE_SIZE - got);
|
||||
return amt==0 ? SQLITE_OK : SQLITE_IOERR;
|
||||
}
|
||||
got += amt;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read a page from a file into memory. Return SQLITE_OK if successful.
|
||||
** The "pgno" parameter tells where in the file to write the page.
|
||||
** The first page is 1. Files do not contain a page 0 since a page
|
||||
** number of 0 is used to indicate "no such page".
|
||||
*/
|
||||
static int sqlitePgWrite(int fd, char *zBuf, u32 pgno){
|
||||
int done = 0;
|
||||
int amt;
|
||||
|
||||
assert( pgno>0 );
|
||||
assert( fd>=0 );
|
||||
lseek(fd, SEEK_SET, (pgno-1)*SQLITE_PAGE_SIZE);
|
||||
while( done<SQLITE_PAGE_SIZE ){
|
||||
amt = write(fd, &zBuf[done], SQLITE_PAGE_SIZE - done);
|
||||
if( amt<=0 ) return SQLITE_IOERR;
|
||||
done += amt;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Turn four bytes into an integer. The first byte is always the
|
||||
** most significant 8 bits.
|
||||
*/
|
||||
static u32 sqlitePgGetInt(const char *p){
|
||||
return ((p[0]&0xff)<<24) | ((p[1]&0xff)<<16) | ((p[2]&0xff)<<8) | (p[3]&0xff);
|
||||
}
|
||||
|
||||
/*
|
||||
** Turn an integer into 4 bytes. The first byte is always the
|
||||
** most significant 8 bits.
|
||||
*/
|
||||
static void sqlitePgPutInt(u32 v, char *p){
|
||||
p[3] = v & 0xff;
|
||||
v >>= 8;
|
||||
p[2] = v & 0xff;
|
||||
v >>= 8;
|
||||
p[1] = v & 0xff;
|
||||
v >>= 8;
|
||||
p[0] = v & 0xff;
|
||||
}
|
||||
|
||||
/*
|
||||
** Check the hash table for an in-memory page. Return a pointer to
|
||||
** the page header if found. Return NULL if the page is not in memory.
|
||||
*/
|
||||
static Pghdr *sqlitePgFind(Pgr *p, u32 pgno){
|
||||
int h;
|
||||
Pghdr *pPg;
|
||||
|
||||
if( pgno==0 ) return 0;
|
||||
h = pgno % PG_HASH_SIZE;
|
||||
for(pPg = p->aPgHash[h]; pPg; pPg=pPg->pNxHash){
|
||||
if( pPg->dbpgno==pgno ) return pPg;
|
||||
}
|
||||
TRACE(("PG: data page %u is %#x\n", pgno, (u32)pPg));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Locate and return an index page from the journal.
|
||||
**
|
||||
** The first page of a journal is the primary index. Additional
|
||||
** index pages are called secondary indices. Index pages appear
|
||||
** in the journal as often as needed. (If SQLITE_PAGE_SIZE==1024,
|
||||
** then there are 1024/sizeof(int)*2 = 128 database between each
|
||||
** pair of index pages.) Journal index pages are not hashed and
|
||||
** do no appear on the Pgr.pAll list. Index pages are on the
|
||||
** Pgr.pJidx list only. Index pages have Pghdr.dbpgno==0.
|
||||
**
|
||||
** If the requested index page is not already in memory, then a
|
||||
** new memory page is created to hold the index.
|
||||
**
|
||||
** This routine will return a NULL pointer if we run out of memory.
|
||||
*/
|
||||
static Pghdr *sqlitePgFindJidx(Pgr *p, u32 pgno){
|
||||
Pghdr *pPg;
|
||||
|
||||
assert( pgno % (N_J_DATAPAGE+1) == 1 );
|
||||
for(pPg=p->pJidx; pPg; pPg=pPg->pNx){
|
||||
if( pPg->jpgno==pgno ){
|
||||
TRACE(("PG: found j-index %u at %#x\n", pgno, (u32)pPg));
|
||||
return pPg;
|
||||
}
|
||||
}
|
||||
pPg = sqliteMalloc( sizeof(Pghdr)+SQLITE_PAGE_SIZE );
|
||||
if( pPg==0 ) return 0;
|
||||
pPg->jpgno = pgno;
|
||||
pPg->pNx = p->pJidx;
|
||||
p->pJidx = pPg;
|
||||
sqlitePgRead(p->fdJournal, PG_TO_DATA(pPg), pgno);
|
||||
TRACE(("PG: create j-index %u at %#x\n", pgno, (u32)pPg));
|
||||
return pPg;
|
||||
}
|
||||
|
||||
/*
|
||||
** Look in the journal to see if the given database page is stored
|
||||
** in the journal. If it is, return its journal page number. If
|
||||
** not, return 0.
|
||||
*/
|
||||
static u32 sqlitePgJournalPageNumber(Pgr *p, u32 dbpgno){
|
||||
u32 jpgno;
|
||||
|
||||
if( dbpgno==0 ) return 0;
|
||||
jpgno = p->aJHash[dbpgno % J_HASH_SIZE];
|
||||
while( jpgno!=0 ){
|
||||
int idx_num; /* Which journal index describes page jpgno */
|
||||
int ipgno; /* Page number for the journal index */
|
||||
int idx_slot; /* Which entry in index idx_num describes jpgno */
|
||||
Pghdr *pIdxPg; /* The index page for jpgno */
|
||||
JidxEntry *aIdx; /* The data for the index page */
|
||||
|
||||
idx_num = (jpgno - 1)/(N_J_DATAPAGE + 1);
|
||||
idx_slot = (jpgno - 1) % (N_J_DATAPAGE + 1) - 2;
|
||||
ipgno = idx_num * (N_J_DATAPAGE + 1) + 1;
|
||||
if( ipgno>p->nJPg ){
|
||||
jpgno = 0;
|
||||
break;
|
||||
}
|
||||
pIdxPg = sqlitePgFindJidx(p, ipgno);
|
||||
assert( pIdxPg!=0 );
|
||||
aIdx = PG_TO_DATA(pIdxPg);
|
||||
if( dbpgno==sqlitePgGetInt(aIdx[idx_slot].dbpgno) ){
|
||||
break;
|
||||
}
|
||||
jpgno = sqlitePgGetInt(aIdx[idx_slot].next_jpgno);
|
||||
}
|
||||
return jpgno;
|
||||
}
|
||||
|
||||
/*
|
||||
** Make a page not dirty by writing it to the journal.
|
||||
*/
|
||||
static int sqlitePgMakeClean(Pghdr *pPg){
|
||||
Pgr *p = pPg->p;
|
||||
int rc;
|
||||
|
||||
assert( pPg->isDirty );
|
||||
assert( p->fdJournal>=0 );
|
||||
if( pPg->jpgno==0 ){
|
||||
int jpgno; /* A newly allocate page in the journal */
|
||||
int idx_num; /* Which journal index describes page jpgno */
|
||||
int idx_slot; /* Which entry in index idx_num describes jpgno */
|
||||
Pghdr *pIdxPg; /* The index page for jpgno */
|
||||
JidxEntry *aIdx; /* The data for the index page */
|
||||
int h; /* The hash value for pPg->dbpgno */
|
||||
|
||||
jpgno = p->nJPg + 1;
|
||||
if( jpgno % (N_J_DATAPAGE + 1) == 1 ){
|
||||
jpgno++;
|
||||
}
|
||||
idx_num = (jpgno - 1)/(N_J_DATAPAGE + 1);
|
||||
idx_slot = (jpgno - 1) % (N_J_DATAPAGE + 1) - 2;
|
||||
pIdxPg = sqlitePgFindJidx(p, idx_num * (N_J_DATAPAGE + 1) + 1);
|
||||
assert( pIdxPg!=0 );
|
||||
aIdx = PG_TO_DATA(pIdxPg);
|
||||
sqlitePgPutInt(pPg->dbpgno, aIdx[idx_slot].dbpgno);
|
||||
h = pPg->dbpgno % J_HASH_SIZE;
|
||||
sqlitePgPutInt(p->aJHash[h], aIdx[idx_slot].next_jpgno);
|
||||
p->aJHash[h] = jpgno;
|
||||
p->nJPg = jpgno;
|
||||
pPg->jpgno = jpgno;
|
||||
TRACE(("PG: assign d-page %u to j-page %u\n", jpgno, pPg->dbpgno));
|
||||
}
|
||||
rc = sqlitePgWrite(p->fdJournal, PG_TO_DATA(pPg), pPg->jpgno);
|
||||
if( rc==SQLITE_OK ){
|
||||
pPg->isDirty = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Find the number of pages in the given file by measuring the size
|
||||
** of the file. Return 0 if there is any problem.
|
||||
*/
|
||||
static int sqlitePgPageCount(int fd){
|
||||
struct stat statbuf;
|
||||
if( fstat(fd, &statbuf)!=0 ) return 0;
|
||||
return statbuf.st_size/SQLITE_PAGE_SIZE;
|
||||
}
|
||||
|
||||
/*
|
||||
** This routine reads the journal and transfers pages from the
|
||||
** journal to the database.
|
||||
*/
|
||||
static int sqlitePgJournalPlayback(Pgr *p){
|
||||
Pghdr *pPg;
|
||||
JidxEntry *aIdx;
|
||||
int nJpg;
|
||||
int jpgno = 1;
|
||||
int i;
|
||||
int dbpgno;
|
||||
int rc;
|
||||
char idx[SQLITE_PAGE_SIZE];
|
||||
char pgbuf[SQLITE_PAGE_SIZE];
|
||||
|
||||
assert( p->fdJournal>=0 );
|
||||
nJpg = sqlitePgPageCount(p->fdJournal);
|
||||
while( jpgno<=nJpg ){
|
||||
if( !sqlitePgRead(p->fdJournal, idx, jpgno++) ) break;
|
||||
aIdx = (JidxEntry*)idx;
|
||||
for(i=0; i<N_J_DATAPAGE; i++){
|
||||
dbpgno = sqlitePgGetInt(&idx[i]);
|
||||
if( dbpgno==0 ){
|
||||
jpgno = nJpg+1;
|
||||
break;
|
||||
}
|
||||
pPg = sqlitePgFind(p, dbpgno);
|
||||
if( pPg ){
|
||||
rc = sqlitePgWrite(p->fdMain, PG_TO_DATA(pPg), dbpgno);
|
||||
TRACE(("PG: commit j-page %u to d-page %u from memory\n",jpgno,dbpgno));
|
||||
}else{
|
||||
rc = sqlitePgRead(p->fdJournal, pgbuf, jpgno);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
rc = sqlitePgWrite(p->fdMain, pgbuf, dbpgno);
|
||||
TRACE(("PG: commit j-page %u to d-page %u from disk\n",jpgno,dbpgno));
|
||||
}
|
||||
jpgno++;
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
TRACE(("PG: commit complete. deleting the journal.\n"));
|
||||
fsync(p->fdMain);
|
||||
close(p->fdJournal);
|
||||
p->fdJournal = -1;
|
||||
unlink(p->zJournal);
|
||||
for(pPg=p->pAll; pPg; pPg=pPg->pNx){
|
||||
pPg->isDirty = 0;
|
||||
pPg->jpgno = 0;
|
||||
}
|
||||
while( (pPg = p->pJidx)!=0 ){
|
||||
p->pAll = pPg->pNx;
|
||||
sqliteFree(pPg);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove the given page from the LRU list.
|
||||
*/
|
||||
static void sqlitePgUnlinkLru(Pghdr *pPg){
|
||||
Pgr *p = pPg->p;
|
||||
if( pPg->pLru ){
|
||||
pPg->pLru->pMru = pPg->pLru;
|
||||
}
|
||||
if( pPg->pMru ){
|
||||
pPg->pMru->pLru = pPg->pMru;
|
||||
}
|
||||
if( p->pLru==pPg ){
|
||||
p->pLru = pPg->pLru;
|
||||
}
|
||||
if( p->pMru==pPg ){
|
||||
p->pMru = pPg->pMru;
|
||||
}
|
||||
pPg->pLru = pPg->pMru = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open the database file and make *ppPgr pointer to a structure describing it.
|
||||
** Return SQLITE_OK on success or an error code if there is a failure.
|
||||
**
|
||||
** If there was an unfinished commit, complete it before returnning.
|
||||
*/
|
||||
int sqlitePgOpen(const char *zFilename, Pgr **ppPgr){
|
||||
Pgr *p;
|
||||
int n;
|
||||
|
||||
n = strlen(zFilename);
|
||||
p = sqliteMalloc( sizeof(*p) + n*2 + 4 );
|
||||
if( p==0 ){
|
||||
*ppPgr = 0;
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
p->zMain = (char*)&p[1];
|
||||
strcpy(p->zMain, zFilename);
|
||||
p->zJournal = &p->zMain[n+1];
|
||||
strcpy(p->zJournal, p->zMain);
|
||||
p->zJournal[n] = '~';
|
||||
p->zJournal[n+1] = 0;
|
||||
p->fdJournal = -1;
|
||||
p->fdMain = open(p->zMain, O_CREAT|O_RDWR, 0600);
|
||||
if( p->fdMain<0 ){
|
||||
*ppPgr = 0;
|
||||
sqliteFree(p);
|
||||
return SQLITE_PERM;
|
||||
}
|
||||
p->nDbPg = sqlitePgPageCount(p->fdMain);
|
||||
if( access(p->zJournal, R_OK)==0 ){
|
||||
sqlitePgJournalPlayback(p);
|
||||
}
|
||||
*ppPgr = p;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Close the database file. Any outstanding transactions are abandoned.
|
||||
*/
|
||||
int sqlitePgClose(Pgr *p){
|
||||
Pghdr *pPg;
|
||||
|
||||
if( p->fdMain ) close(p->fdMain);
|
||||
if( p->fdJournal ) close(p->fdJournal);
|
||||
unlink(p->zJournal);
|
||||
while( (pPg = p->pAll)!=0 ){
|
||||
p->pAll = pPg->pNx;
|
||||
sqliteFree(pPg);
|
||||
}
|
||||
while( (pPg = p->pJidx)!=0 ){
|
||||
p->pAll = pPg->pNx;
|
||||
sqliteFree(pPg);
|
||||
}
|
||||
sqliteFree(p);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Begin a new transaction. Return SQLITE_OK on success or an error
|
||||
** code if something goes wrong.
|
||||
*/
|
||||
int sqlitePgBeginTransaction(Pgr *p){
|
||||
assert( p->fdJournal<0 );
|
||||
if( p->nRefPg>0 ){
|
||||
/* release the read lock */
|
||||
}
|
||||
/* write lock the database */
|
||||
p->fdJournal = open(p->zJournal, O_CREAT|O_EXCL|O_RDWR, 0600);
|
||||
if( p->fdJournal<0 ){
|
||||
return SQLITE_PERM;
|
||||
}
|
||||
p->nJPg = 0;
|
||||
TRACE(("PG: begin transaction\n"));
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Commit the current transaction. Return SQLITE_OK or an error code.
|
||||
*/
|
||||
int sqlitePgCommit(Pgr *p){
|
||||
Pghdr *pPrimaryIdx = 0;
|
||||
Pghdr *pPg;
|
||||
int rc;
|
||||
|
||||
for(pPg=p->pAll; pPg; pPg=pPg->pNx){
|
||||
if( pPg->isDirty ){
|
||||
rc = sqlitePgMakeClean(pPg);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(pPg=p->pJidx; pPg; pPg=pPg->pNx){
|
||||
if( pPg->jpgno==1 ){
|
||||
pPrimaryIdx = pPg;
|
||||
}else{
|
||||
TRACE(("PG: writing j-index %u\n", pPg->jpgno));
|
||||
rc = sqlitePgMakeClean(pPg);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert( pPrimaryIdx!=0 );
|
||||
fsync(p->fdJournal);
|
||||
TRACE(("PG: writing j-index %u\n", pPrimaryIdx->jpgno));
|
||||
rc = sqlitePgMakeClean(pPrimaryIdx);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
fsync(p->fdJournal);
|
||||
rc = sqlitePgJournalPlayback(p);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
/* remove write lock from database */
|
||||
if( p->nRefPg>0 ){
|
||||
/* acquire read lock on database */
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Abandon the current transaction.
|
||||
*/
|
||||
int sqlitePgRollback(Pgr *p){
|
||||
Pghdr *pPg;
|
||||
|
||||
TRACE(("PG: begin rollback\n"));
|
||||
for(pPg=p->pAll; pPg; pPg=pPg->pNx){
|
||||
if( pPg->isDirty || pPg->jpgno!=0 ){
|
||||
pPg->isDirty = 0;
|
||||
pPg->jpgno = 0;
|
||||
if( pPg->nRef>0 ){
|
||||
TRACE(("PG: reloading d-page %u\n", pPg->dbpgno));
|
||||
sqlitePgRead(p->fdMain, PG_TO_DATA(pPg), pPg->dbpgno);
|
||||
}else{
|
||||
sqlitePgUnlinkLru(pPg);
|
||||
}
|
||||
}
|
||||
}
|
||||
close(p->fdJournal);
|
||||
p->fdJournal = -1;
|
||||
unlink(p->zJournal);
|
||||
while( (pPg = p->pJidx)!=0 ){
|
||||
p->pAll = pPg->pNx;
|
||||
sqliteFree(pPg);
|
||||
}
|
||||
p->nDbPg = sqlitePgPageCount(p->fdMain);
|
||||
/* remove write lock from database */
|
||||
if( p->nRefPg>0 ){
|
||||
/* acquire read lock on database */
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Get a page from the database. Return a pointer to the data for that
|
||||
** page.
|
||||
**
|
||||
** A NULL pointer will be returned if we run out of memory.
|
||||
*/
|
||||
int sqlitePgGet(Pgr *p, u32 pgno, void **ppData){
|
||||
Pghdr *pPg;
|
||||
int h;
|
||||
|
||||
pPg = sqlitePgFind(p, pgno);
|
||||
if( pPg ){
|
||||
pPg->nRef++;
|
||||
if( pPg->nRef==1 ){
|
||||
sqlitePgUnlinkLru(pPg);
|
||||
TRACE(("PG: d-page %u pulled from cache\n", pgno));
|
||||
}
|
||||
p->nRefPg++;
|
||||
if( p->nRefPg==1 ){
|
||||
/* Acquire a read lock */
|
||||
}
|
||||
*ppData = PG_TO_DATA(pPg);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
if( p->nMemPg<MX_MEM_PAGE || p->pLru==0 ){
|
||||
pPg = sqliteMalloc( sizeof(Pghdr) + SQLITE_PAGE_SIZE );
|
||||
if( pPg==0 ) return SQLITE_NOMEM;
|
||||
p->nMemPg++;
|
||||
pPg->pNx = p->pAll;
|
||||
p->pAll = pPg;
|
||||
pPg->p = p;
|
||||
TRACE(("PG: new page %d created.\n", p->nMemPg));
|
||||
}else{
|
||||
int rc;
|
||||
pPg = p->pLru;
|
||||
if( pPg->isDirty ){
|
||||
rc = sqlitePgMakeClean(pPg);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}
|
||||
sqlitePgUnlinkLru(pPg);
|
||||
h = pPg->dbpgno % PG_HASH_SIZE;
|
||||
if( pPg->pPvHash ){
|
||||
pPg->pPvHash->pNxHash = pPg->pNxHash;
|
||||
}else{
|
||||
assert( p->aPgHash[h]==pPg );
|
||||
p->aPgHash[h] = pPg->pNxHash;
|
||||
}
|
||||
if( pPg->pNxHash ){
|
||||
pPg->pNxHash->pPvHash = pPg->pPvHash;
|
||||
}
|
||||
TRACE(("PG: recycling d-page %u to d-page %u\n", pPg->dbpgno, pgno));
|
||||
}
|
||||
pPg->dbpgno = pgno;
|
||||
if( pgno>p->nDbPg ){
|
||||
p->nDbPg = pgno;
|
||||
}
|
||||
h = pgno % PG_HASH_SIZE;
|
||||
pPg->pPvHash = 0;
|
||||
pPg->pNxHash = p->aPgHash[h];
|
||||
if( pPg->pNxHash ){
|
||||
pPg->pNxHash->pPvHash = pPg;
|
||||
}
|
||||
p->aPgHash[h] = pPg;
|
||||
pPg->jpgno = sqlitePgJournalPageNumber(p, pgno);
|
||||
if( pPg->jpgno!=0 ){
|
||||
TRACE(("PG: reading d-page %u content from j-page %u\n", pgno, pPg->jpgno));
|
||||
sqlitePgRead(p->fdJournal, PG_TO_DATA(pPg), pPg->jpgno);
|
||||
}else if( pPg->dbpgno!=0 ){
|
||||
TRACE(("PG: reading d-page %u from database\n", pgno));
|
||||
sqlitePgRead(p->fdMain, PG_TO_DATA(pPg), pPg->dbpgno);
|
||||
}else{
|
||||
TRACE(("PG: reading zero page\n");
|
||||
memset(PG_TO_DATA(pPg), 0, SQLITE_PAGE_SIZE);
|
||||
}
|
||||
pPg->isDirty = 0;
|
||||
pPg->nRef = 1;
|
||||
p->nRefPg++;
|
||||
if( p->nRefPg==1 ){
|
||||
/* Acquire a read lock */
|
||||
}
|
||||
*ppData = PG_TO_DATA(pPg);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Release a reference to a database data page.
|
||||
*/
|
||||
int sqlitePgUnref(void *pData){
|
||||
Pghdr *pPg = DATA_TO_PG(pData);
|
||||
pPg->nRef--;
|
||||
assert( pPg->nRef>=0 );
|
||||
if( pPg->nRef==0 ){
|
||||
Pgr *p = pPg->p;
|
||||
pPg->pMru = 0;
|
||||
pPg->pLru = p->pLru;
|
||||
p->pLru = pPg;
|
||||
TRACE(("PG: d-page %u is unused\n", pPg->dbpgno));
|
||||
p->nRefPg--;
|
||||
if( p->nRefPg==0 ){
|
||||
/* Release the read lock */
|
||||
}
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** The database page in the argument has been modified. Write it back
|
||||
** to the database file on the next commit.
|
||||
*/
|
||||
int sqlitePgTouch(void *pD){
|
||||
Pghdr *pPg = DATA_TO_PG(pD);
|
||||
assert( pPg->p->fdJournal>=0 );
|
||||
if( pPg->isDirty==0 ){
|
||||
pPg->isDirty = 1;
|
||||
TRACE(("PG: d-page %u is dirty\n", pPg->dbpgno));
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the number of the first unused page at the end of the
|
||||
** database file.
|
||||
*/
|
||||
int sqlitePgCount(Pgr *p, u32 *pPgno){
|
||||
*pPgno = p->nDbPg;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the page number associated with the given page.
|
||||
*/
|
||||
u32 sqlitePgNum(void *pD){
|
||||
Pghdr *pPg = DATA_TO_PG(pD);
|
||||
return pPg->dbpgno;
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
** Copyright (c) 2001 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** $Id: pg.h,v 1.1 2001/02/11 16:56:24 drh Exp $
|
||||
*/
|
||||
|
||||
typedef struct Pgr Pgr;
|
||||
#define SQLITE_PAGE_SIZE 1024
|
||||
|
||||
|
||||
int sqlitePgOpen(const char *filename, Pgr **pp);
|
||||
int sqlitePgClose(Pgr*);
|
||||
int sqlitePgBeginTransaction(Pgr*);
|
||||
int sqlitePgCommit(Pgr*);
|
||||
int sqlitePgRollback(Pgr*);
|
||||
int sqlitePgGet(Pgr*, u32 pgno, void **);
|
||||
int sqlitePgUnref(void*);
|
||||
int sqlitePgTouch(void*);
|
||||
int sqlitePgCount(Pgr*, u32*);
|
||||
u32 sqlitePgNum(void*);
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
set sizes {1024 2048 4096 8192 16384 32768}
|
||||
set fmt { %-8s}
|
||||
|
||||
puts -nonewline "page size: "
|
||||
foreach s $sizes {
|
||||
puts -nonewline [format $fmt $s]
|
||||
}
|
||||
puts ""
|
||||
|
||||
puts -nonewline "on leaf: "
|
||||
foreach s $sizes {
|
||||
set x [expr {$s - 18*4}]
|
||||
set p($s) $x
|
||||
puts -nonewline [format $fmt $x]
|
||||
}
|
||||
puts ""
|
||||
|
||||
puts -nonewline "direct: "
|
||||
foreach s $sizes {
|
||||
set x [expr {$p($s) + 10*$s}]
|
||||
set p($s) $x
|
||||
puts -nonewline [format $fmt $x]
|
||||
}
|
||||
puts ""
|
||||
|
||||
puts -nonewline "indirect: "
|
||||
foreach s $sizes {
|
||||
set x [expr {$p($s) + ($s/4.0)*$s}]
|
||||
set p($s) $x
|
||||
puts -nonewline [format $fmt $x]
|
||||
}
|
||||
puts ""
|
||||
|
||||
puts -nonewline "dbl indir: "
|
||||
foreach s $sizes {
|
||||
set x [expr {$p($s) + ($s/4.0)*($s/4)*$s}]
|
||||
set p($s) $x
|
||||
puts -nonewline [format $fmt $x]
|
||||
}
|
||||
puts ""
|
||||
+3
-3
@@ -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.19 2000/08/28 15:51:44 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)
|
||||
@@ -738,7 +738,7 @@ void sqliteExprIfTrue(Parse *pParse, Expr *pExpr, int dest){
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate code for boolean expression such that a jump is made
|
||||
** Generate code for a boolean expression such that a jump is made
|
||||
** to the label "dest" if the expression is false but execution
|
||||
** continues straight thru if the expression is true.
|
||||
*/
|
||||
|
||||
+4
-4
@@ -24,7 +24,7 @@
|
||||
** This file contains C code routines that are called by the parser
|
||||
** to handle INSERT statements.
|
||||
**
|
||||
** $Id: insert.c,v 1.11 2000/06/21 13:59:12 drh Exp $
|
||||
** $Id: insert.c,v 1.12 2001/01/15 22:51:11 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -92,7 +92,7 @@ void sqliteInsert(
|
||||
if( pSelect ){
|
||||
int rc;
|
||||
srcTab = pParse->nTab++;
|
||||
sqliteVdbeAddOp(v, OP_Open, srcTab, 1, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenTbl, srcTab, 1, 0, 0);
|
||||
rc = sqliteSelect(pParse, pSelect, SRT_Table, srcTab);
|
||||
if( rc ) goto insert_cleanup;
|
||||
assert( pSelect->pEList );
|
||||
@@ -156,9 +156,9 @@ void sqliteInsert(
|
||||
** all indices of that table.
|
||||
*/
|
||||
base = pParse->nTab;
|
||||
sqliteVdbeAddOp(v, OP_Open, base, 1, pTab->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenTbl, base, 1, pTab->zName, 0);
|
||||
for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
|
||||
sqliteVdbeAddOp(v, OP_Open, idx+base, 1, pIdx->zName, 0);
|
||||
sqliteVdbeAddOp(v, OP_OpenIdx, idx+base, 1, pIdx->zName, 0);
|
||||
}
|
||||
|
||||
/* If the data source is a SELECT statement, then we have to create
|
||||
|
||||
+54
-27
@@ -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.19 2000/10/11 19:28:52 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;
|
||||
@@ -227,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;
|
||||
}
|
||||
@@ -239,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;
|
||||
@@ -257,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;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -300,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 ){
|
||||
@@ -311,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;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -382,3 +402,10 @@ void sqlite_busy_timeout(sqlite *db, int ms){
|
||||
sqlite_busy_handler(db, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Cause any pending operation to stop at its earliest opportunity.
|
||||
*/
|
||||
void sqlite_interrupt(sqlite *db){
|
||||
db->flags |= SQLITE_Interrupt;
|
||||
}
|
||||
|
||||
+6
-6
@@ -26,7 +26,7 @@
|
||||
** the parser. Lemon will also generate a header file containing
|
||||
** numeric codes for all of the tokens.
|
||||
**
|
||||
** @(#) $Id: parse.y,v 1.25 2000/08/01 09:56:27 drh Exp $
|
||||
** @(#) $Id: parse.y,v 1.26 2001/01/04 14:20:18 drh Exp $
|
||||
*/
|
||||
%token_prefix TK_
|
||||
%token_type {Token}
|
||||
@@ -114,12 +114,12 @@ ccons ::= CHECK LP expr RP.
|
||||
conslist_opt ::= .
|
||||
conslist_opt ::= COMMA conslist.
|
||||
conslist ::= conslist COMMA tcons.
|
||||
conslist ::= conslist tcons.
|
||||
conslist ::= tcons.
|
||||
tcons ::= CONSTRAINT id tcons2.
|
||||
tcons ::= tcons2.
|
||||
tcons2 ::= PRIMARY KEY LP idxlist(X) RP. {sqliteCreateIndex(pParse,0,0,X,0,0);}
|
||||
tcons2 ::= UNIQUE LP idlist RP.
|
||||
tcons2 ::= CHECK expr.
|
||||
tcons ::= CONSTRAINT id.
|
||||
tcons ::= PRIMARY KEY LP idxlist(X) RP. {sqliteCreateIndex(pParse,0,0,X,0,0);}
|
||||
tcons ::= UNIQUE LP idlist RP.
|
||||
tcons ::= CHECK expr.
|
||||
idlist ::= idlist COMMA id.
|
||||
idlist ::= id.
|
||||
|
||||
|
||||
+3
-4
@@ -46,7 +46,6 @@
|
||||
** + All functions are fully reentrant.
|
||||
**
|
||||
*/
|
||||
#include <ctype.h>
|
||||
#include "sqliteInt.h"
|
||||
|
||||
/*
|
||||
@@ -262,7 +261,7 @@ static int vxprintf(
|
||||
}
|
||||
c = *++fmt;
|
||||
}else{
|
||||
while( isdigit(c) ){
|
||||
while( c>='0' && c<='9' ){
|
||||
width = width*10 + c - '0';
|
||||
c = *++fmt;
|
||||
}
|
||||
@@ -282,7 +281,7 @@ static int vxprintf(
|
||||
#endif
|
||||
c = *++fmt;
|
||||
}else{
|
||||
while( isdigit(c) ){
|
||||
while( c>='0' && c<='9' ){
|
||||
precision = precision*10 + c - '0';
|
||||
c = *++fmt;
|
||||
}
|
||||
@@ -561,7 +560,7 @@ static int vxprintf(
|
||||
for(i=n=0; (c=arg[i])!=0; i++){
|
||||
if( c=='\'' ) n++;
|
||||
}
|
||||
n += i;
|
||||
n += i + 1;
|
||||
if( n>etBUFSIZE ){
|
||||
bufpt = zExtra = sqliteMalloc( n );
|
||||
}else{
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
** Copyright (c) 2000 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains code to implement a pseudo-random number
|
||||
** generator (PRNG) for SQLite.
|
||||
**
|
||||
** Random numbers are used by some of the database backends in order
|
||||
** to generate random integer keys for tables or random filenames.
|
||||
**
|
||||
** $Id: random.c,v 1.2 2001/01/31 13:28:09 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <time.h>
|
||||
|
||||
/*
|
||||
** Get a single 8-bit random value from the RC4 PRNG.
|
||||
*/
|
||||
int sqliteRandomByte(void){
|
||||
int t;
|
||||
|
||||
/*
|
||||
** The following structure holds the current state of the RC4 algorithm.
|
||||
** We use RC4 as a random number generator. Each call to RC4 gives
|
||||
** a random 8-bit number.
|
||||
**
|
||||
** Nothing in this file or anywhere else in SQLite does any kind of
|
||||
** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random
|
||||
** number generator) not as an encryption device.
|
||||
*/
|
||||
static struct {
|
||||
int isInit;
|
||||
int i, j;
|
||||
int s[256];
|
||||
} prng_state;
|
||||
|
||||
/* Initialize the state of the random number generator once,
|
||||
** the first time this routine is called. The seed value does
|
||||
** not need to contain a lot of randomness since we are not
|
||||
** trying to do secure encryption or anything like that...
|
||||
*/
|
||||
if( !prng_state.isInit ){
|
||||
int i;
|
||||
static char seed[] = " sqlite random seed";
|
||||
char k[256];
|
||||
time((time_t*)seed);
|
||||
prng_state.j = 0;
|
||||
prng_state.i = 0;
|
||||
for(i=0; i<256; i++){
|
||||
prng_state.s[i] = i;
|
||||
k[i] = seed[i%sizeof(seed)];
|
||||
}
|
||||
for(i=0; i<256; i++){
|
||||
int t;
|
||||
prng_state.j = (prng_state.j + prng_state.s[i] + k[i]) & 0xff;
|
||||
t = prng_state.s[prng_state.j];
|
||||
prng_state.s[prng_state.j] = prng_state.s[i];
|
||||
prng_state.s[i] = t;
|
||||
}
|
||||
prng_state.isInit = 1;
|
||||
}
|
||||
|
||||
/* Generate and return single random byte
|
||||
*/
|
||||
prng_state.i = (prng_state.i + 1) & 0xff;
|
||||
prng_state.j = (prng_state.j + prng_state.s[prng_state.i]) & 0xff;
|
||||
t = prng_state.s[prng_state.i];
|
||||
prng_state.s[prng_state.i] = prng_state.s[prng_state.j];
|
||||
prng_state.s[prng_state.j] = t;
|
||||
t = prng_state.s[prng_state.i] + prng_state.s[prng_state.j];
|
||||
return t & 0xff;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a random 32-bit integer. The integer is generated by making
|
||||
** 4 calls to sqliteRandomByte().
|
||||
*/
|
||||
int sqliteRandomInteger(void){
|
||||
int r;
|
||||
int i;
|
||||
r = sqliteRandomByte();
|
||||
for(i=1; i<4; i++){
|
||||
r = (r<<8) + sqliteRandomByte();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Generate a random filename with the given prefix. The new filename
|
||||
** is written into zBuf[]. The calling function must insure that
|
||||
** zBuf[] is big enough to hold the prefix plus 20 or so extra
|
||||
** characters.
|
||||
**
|
||||
** Very random names are chosen so that the chance of a
|
||||
** collision with an existing filename is very very small.
|
||||
*/
|
||||
void sqliteRandomName(char *zBuf, char *zPrefix){
|
||||
int i, j;
|
||||
static const char zRandomChars[] = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
strcpy(zBuf, zPrefix);
|
||||
j = strlen(zBuf);
|
||||
for(i=0; i<15; i++){
|
||||
int c = sqliteRandomByte() % (sizeof(zRandomChars) - 1);
|
||||
zBuf[j++] = zRandomChars[c];
|
||||
}
|
||||
zBuf[j] = 0;
|
||||
}
|
||||
+10
-8
@@ -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.29 2001/02/19 23:23:38 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);
|
||||
@@ -576,7 +578,7 @@ static int multiSelect(Parse *pParse, Select *p, int eDest, int iParm){
|
||||
}
|
||||
iBreak = sqliteVdbeMakeLabel(v);
|
||||
iCont = sqliteVdbeAddOp(v, OP_Next, tab1, iBreak, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_Key, tab1, 0, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_FullKey, tab1, 0, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_NotFound, tab2, iCont, 0, 0);
|
||||
rc = selectInnerLoop(pParse, 0, tab1, p->pEList->nExpr,
|
||||
p->pOrderBy, -1, eDest, iParm,
|
||||
@@ -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;
|
||||
|
||||
+36
-7
@@ -24,7 +24,7 @@
|
||||
** This file contains code to implement the "sqlite" command line
|
||||
** utility for accessing SQLite databases.
|
||||
**
|
||||
** $Id: shell.c,v 1.26 2000/10/08 22:20:58 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
|
||||
@@ -229,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.
|
||||
@@ -447,7 +464,6 @@ 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;
|
||||
if( nArg==1 ){
|
||||
@@ -669,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;
|
||||
@@ -703,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;
|
||||
@@ -745,15 +768,20 @@ int main(int argc, char **argv){
|
||||
);
|
||||
}
|
||||
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 );
|
||||
@@ -766,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;
|
||||
|
||||
+19
-5
@@ -24,7 +24,7 @@
|
||||
** This header file defines the interface that the sqlite library
|
||||
** presents to client programs.
|
||||
**
|
||||
** @(#) $Id: sqlite.h.in,v 1.5 2000/10/09 12:57:01 drh Exp $
|
||||
** @(#) $Id: sqlite.h.in,v 1.9 2001/01/20 19:52:49 drh Exp $
|
||||
*/
|
||||
#ifndef _SQLITE_H_
|
||||
#define _SQLITE_H_
|
||||
@@ -130,13 +130,27 @@ int 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.
|
||||
@@ -248,9 +262,9 @@ void sqlite_free_table(char **result);
|
||||
**
|
||||
** 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 double every '\'' character.
|
||||
** string from the argument list. But %q also doubles every '\'' character.
|
||||
** %q is designed for use inside a string literal. By doubling each '\''
|
||||
** character is escapes that character and allows it to be inserted into
|
||||
** character it escapes that character and allows it to be inserted into
|
||||
** the string.
|
||||
**
|
||||
** For example, so some string variable contains text as follows:
|
||||
|
||||
+26
-11
@@ -23,7 +23,7 @@
|
||||
*************************************************************************
|
||||
** Internal interface definitions for SQLite.
|
||||
**
|
||||
** @(#) $Id: sqliteInt.h,v 1.30 2000/08/28 15:51:44 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
|
||||
|
||||
@@ -123,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 */
|
||||
@@ -135,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
|
||||
@@ -167,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
|
||||
@@ -200,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 */
|
||||
};
|
||||
|
||||
@@ -325,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 */
|
||||
@@ -416,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*);
|
||||
|
||||
+23
-1
@@ -1,9 +1,31 @@
|
||||
/*
|
||||
** 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().
|
||||
**
|
||||
** This routines are in a separate files to that they will not be linked
|
||||
** These routines are in a separate files so that they will not be linked
|
||||
** if they are not used.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
|
||||
+4
-2
@@ -23,12 +23,12 @@
|
||||
*************************************************************************
|
||||
** A TCL Interface to SQLite
|
||||
**
|
||||
** $Id: tclsqlite.c,v 1.11 2000/10/11 19:28:52 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>
|
||||
|
||||
@@ -380,6 +380,7 @@ int Sqlite_SafeInit(Tcl_Interp *interp){
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/*
|
||||
** If compiled using mktclapp, this routine runs to initialize
|
||||
** everything.
|
||||
@@ -387,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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
hi
|
||||
+18
-5
@@ -27,7 +27,7 @@
|
||||
** individual tokens and sends those tokens one-by-one over to the
|
||||
** parser for analysis.
|
||||
**
|
||||
** $Id: tokenize.c,v 1.13 2000/08/09 17:17:25 drh Exp $
|
||||
** $Id: tokenize.c,v 1.17 2001/02/11 16:56:24 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <ctype.h>
|
||||
@@ -102,7 +102,7 @@ static Keyword aKeywordTable[] = {
|
||||
/*
|
||||
** This is the hash table
|
||||
*/
|
||||
#define KEY_HASH_SIZE 37
|
||||
#define KEY_HASH_SIZE 69
|
||||
static Keyword *apHashTable[KEY_HASH_SIZE];
|
||||
|
||||
|
||||
@@ -298,7 +298,7 @@ int sqliteGetToken(const char *z, int *tokenType){
|
||||
|
||||
/*
|
||||
** Run the parser on the given SQL string. The parser structure is
|
||||
** passed in. Return the number of errors.
|
||||
** passed in. An SQLITE_ status code.
|
||||
*/
|
||||
int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
|
||||
int nErr = 0;
|
||||
@@ -308,9 +308,11 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
|
||||
static FILE *trace = 0;
|
||||
extern void *sqliteParserAlloc(void*(*)(int));
|
||||
extern void sqliteParserFree(void*, void(*)(void*));
|
||||
extern int sqliteParser(void*, int, ...);
|
||||
extern int sqliteParser(void*, int, Token, Parse*);
|
||||
extern void sqliteParserTrace(FILE*, char *);
|
||||
|
||||
pParse->db->flags &= ~SQLITE_Interrupt;
|
||||
pParse->rc = SQLITE_OK;
|
||||
i = 0;
|
||||
sqliteParseInfoReset(pParse);
|
||||
pEngine = sqliteParserAlloc((void*(*)(int))malloc);
|
||||
@@ -318,10 +320,17 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
|
||||
sqliteSetString(pzErrMsg, "out of memory", 0);
|
||||
return 1;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
sqliteParserTrace(trace, "parser: ");
|
||||
#endif
|
||||
while( nErr==0 && i>=0 && zSql[i]!=0 ){
|
||||
int tokenType;
|
||||
|
||||
if( (pParse->db->flags & SQLITE_Interrupt)!=0 ){
|
||||
pParse->rc = SQLITE_INTERRUPT;
|
||||
sqliteSetString(pzErrMsg, "interrupt", 0);
|
||||
break;
|
||||
}
|
||||
pParse->sLastToken.z = &zSql[i];
|
||||
pParse->sLastToken.n = sqliteGetToken(&zSql[i], &tokenType);
|
||||
i += pParse->sLastToken.n;
|
||||
@@ -391,7 +400,7 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( nErr==0 ){
|
||||
if( nErr==0 && (pParse->db->flags & SQLITE_Interrupt)==0 ){
|
||||
sqliteParser(pEngine, 0, pParse->sLastToken, pParse);
|
||||
if( pParse->zErrMsg && pParse->sErrToken.z ){
|
||||
sqliteSetNString(pzErrMsg, "near \"", -1,
|
||||
@@ -423,5 +432,9 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){
|
||||
pParse->pNewTable = 0;
|
||||
}
|
||||
sqliteParseInfoReset(pParse);
|
||||
sqliteStrRealloc(pzErrMsg);
|
||||
if( nErr>0 && pParse->rc==SQLITE_OK ){
|
||||
pParse->rc = SQLITE_ERROR;
|
||||
}
|
||||
return nErr;
|
||||
}
|
||||
|
||||
+7
-5
@@ -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.10 2001/03/14 12:35:57 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -126,8 +126,10 @@ void sqliteUpdate(
|
||||
}
|
||||
if( i<pIdx->nColumn ) nIdx++;
|
||||
}
|
||||
apIdx = sqliteMalloc( sizeof(Index*) * nIdx );
|
||||
if( apIdx==0 ) goto update_cleanup;
|
||||
if( nIdx>0 ){
|
||||
apIdx = sqliteMalloc( sizeof(Index*) * nIdx );
|
||||
if( apIdx==0 ) goto update_cleanup;
|
||||
}
|
||||
for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
|
||||
for(i=0; i<pIdx->nColumn; i++){
|
||||
if( aXRef[pIdx->aiColumn[i]]>=0 ) break;
|
||||
@@ -159,9 +161,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
|
||||
|
||||
+12
-2
@@ -26,7 +26,7 @@
|
||||
** This file contains functions for allocating memory, comparing
|
||||
** strings, and stuff like that.
|
||||
**
|
||||
** $Id: util.c,v 1.16 2000/10/11 19:28:52 drh Exp $
|
||||
** $Id: util.c,v 1.18 2001/03/14 12:35:57 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <stdarg.h>
|
||||
@@ -38,6 +38,15 @@
|
||||
*/
|
||||
#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
|
||||
** no memory is available.
|
||||
@@ -51,6 +60,7 @@ void *sqliteMalloc_(int n, char *zFile, int line){
|
||||
sqlite_iMallocFail--;
|
||||
if( sqlite_iMallocFail==0 ) return 0;
|
||||
}
|
||||
if( n==0 ) return 0;
|
||||
k = (n+sizeof(int)-1)/sizeof(int);
|
||||
pi = malloc( (3+k)*sizeof(int));
|
||||
if( pi==0 ) return 0;
|
||||
@@ -361,7 +371,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;
|
||||
|
||||
+540
-422
File diff suppressed because it is too large
Load Diff
+95
-93
@@ -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.13 2000/10/11 19:28:53 drh Exp $
|
||||
** $Id: vdbe.h,v 1.16 2001/02/19 23:23:39 drh Exp $
|
||||
*/
|
||||
#ifndef _SQLITE_VDBE_H_
|
||||
#define _SQLITE_VDBE_H_
|
||||
@@ -71,118 +71,120 @@ 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_FullKey 15
|
||||
#define OP_Rewind 16
|
||||
#define OP_Next 17
|
||||
|
||||
#define OP_Destroy 16
|
||||
#define OP_Reorganize 17
|
||||
#define OP_Destroy 18
|
||||
#define OP_Reorganize 19
|
||||
|
||||
#define OP_ResetIdx 18
|
||||
#define OP_NextIdx 19
|
||||
#define OP_PutIdx 20
|
||||
#define OP_DeleteIdx 21
|
||||
#define OP_ResetIdx 20
|
||||
#define OP_NextIdx 21
|
||||
#define OP_PutIdx 22
|
||||
#define OP_DeleteIdx 23
|
||||
|
||||
#define OP_MemLoad 22
|
||||
#define OP_MemStore 23
|
||||
#define OP_MemLoad 24
|
||||
#define OP_MemStore 25
|
||||
|
||||
#define OP_ListOpen 24
|
||||
#define OP_ListWrite 25
|
||||
#define OP_ListRewind 26
|
||||
#define OP_ListRead 27
|
||||
#define OP_ListClose 28
|
||||
#define OP_ListOpen 26
|
||||
#define OP_ListWrite 27
|
||||
#define OP_ListRewind 28
|
||||
#define OP_ListRead 29
|
||||
#define OP_ListClose 30
|
||||
|
||||
#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 31
|
||||
#define OP_SortPut 32
|
||||
#define OP_SortMakeRec 33
|
||||
#define OP_SortMakeKey 34
|
||||
#define OP_Sort 35
|
||||
#define OP_SortNext 36
|
||||
#define OP_SortKey 37
|
||||
#define OP_SortCallback 38
|
||||
#define OP_SortClose 39
|
||||
|
||||
#define OP_FileOpen 38
|
||||
#define OP_FileRead 39
|
||||
#define OP_FileField 40
|
||||
#define OP_FileClose 41
|
||||
#define OP_FileOpen 40
|
||||
#define OP_FileRead 41
|
||||
#define OP_FileField 42
|
||||
#define OP_FileClose 43
|
||||
|
||||
#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 44
|
||||
#define OP_AggFocus 45
|
||||
#define OP_AggIncr 46
|
||||
#define OP_AggNext 47
|
||||
#define OP_AggSet 48
|
||||
#define OP_AggGet 49
|
||||
|
||||
#define OP_SetInsert 48
|
||||
#define OP_SetFound 49
|
||||
#define OP_SetNotFound 50
|
||||
#define OP_SetClear 51
|
||||
#define OP_SetInsert 50
|
||||
#define OP_SetFound 51
|
||||
#define OP_SetNotFound 52
|
||||
#define OP_SetClear 53
|
||||
|
||||
#define OP_MakeRecord 52
|
||||
#define OP_MakeKey 53
|
||||
#define OP_MakeRecord 54
|
||||
#define OP_MakeKey 55
|
||||
|
||||
#define OP_Goto 54
|
||||
#define OP_If 55
|
||||
#define OP_Halt 56
|
||||
#define OP_Goto 56
|
||||
#define OP_If 57
|
||||
#define OP_Halt 58
|
||||
|
||||
#define OP_ColumnCount 57
|
||||
#define OP_ColumnName 58
|
||||
#define OP_Callback 59
|
||||
#define OP_ColumnCount 59
|
||||
#define OP_ColumnName 60
|
||||
#define OP_Callback 61
|
||||
|
||||
#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 62
|
||||
#define OP_String 63
|
||||
#define OP_Null 64
|
||||
#define OP_Pop 65
|
||||
#define OP_Dup 66
|
||||
#define OP_Pull 67
|
||||
|
||||
#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 68
|
||||
#define OP_AddImm 69
|
||||
#define OP_Subtract 70
|
||||
#define OP_Multiply 71
|
||||
#define OP_Divide 72
|
||||
#define OP_Min 73
|
||||
#define OP_Max 74
|
||||
#define OP_Like 75
|
||||
#define OP_Glob 76
|
||||
#define OP_Eq 77
|
||||
#define OP_Ne 78
|
||||
#define OP_Lt 79
|
||||
#define OP_Le 80
|
||||
#define OP_Gt 81
|
||||
#define OP_Ge 82
|
||||
#define OP_IsNull 83
|
||||
#define OP_NotNull 84
|
||||
#define OP_Negative 85
|
||||
#define OP_And 86
|
||||
#define OP_Or 87
|
||||
#define OP_Not 88
|
||||
#define OP_Concat 89
|
||||
#define OP_Noop 90
|
||||
|
||||
#define OP_Strlen 89
|
||||
#define OP_Substr 90
|
||||
#define OP_Strlen 91
|
||||
#define OP_Substr 92
|
||||
|
||||
#define OP_MAX 90
|
||||
#define OP_MAX 92
|
||||
|
||||
/*
|
||||
** 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);
|
||||
|
||||
+4
-4
@@ -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.9 2000/08/22 18:29:34 drh Exp $
|
||||
** $Id: where.c,v 1.12 2001/02/19 23:23:39 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));
|
||||
@@ -338,8 +338,8 @@ WhereInfo *sqliteWhereBegin(
|
||||
if( (aExpr[j].prereqRight & loopMask)!=aExpr[j].prereqRight ) continue;
|
||||
if( (aExpr[j].prereqLeft & loopMask)!=aExpr[j].prereqLeft ) continue;
|
||||
if( haveKey ){
|
||||
sqliteVdbeAddOp(v, OP_Fetch, base+idx, 0, 0, 0);
|
||||
haveKey = 0;
|
||||
sqliteVdbeAddOp(v, OP_Fetch, base+idx, 0, 0, 0);
|
||||
}
|
||||
sqliteExprIfFalse(pParse, aExpr[j].p, cont);
|
||||
aExpr[j].p = 0;
|
||||
|
||||
+43
-6
@@ -22,24 +22,61 @@
|
||||
#***********************************************************************
|
||||
# This file runs all tests.
|
||||
#
|
||||
# $Id: all.test,v 1.2 2000/06/02 14:27:23 drh Exp $
|
||||
# $Id: all.test,v 1.5 2000/12/10 18:23:51 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
rename finish_test really_finish_test
|
||||
proc finish_test {} {}
|
||||
proc finish_test {} {memleak_check}
|
||||
|
||||
if {[file exists ./sqlite_test_count]} {
|
||||
set COUNT [exec cat ./sqlite_test_count]
|
||||
} else {
|
||||
set COUNT 1
|
||||
set COUNT 4
|
||||
}
|
||||
|
||||
if {[file exists ./sqlite_test_prefixes]} {
|
||||
set PREFIXES [exec cat ./sqlite_test_prefixes]
|
||||
} else {
|
||||
set PREFIXES {memory: gdbm:}
|
||||
}
|
||||
|
||||
# LeakList will hold a list of the number of unfreed mallocs after
|
||||
# each round of the test. This number should be constant. If it
|
||||
# grows, it may mean there is a memory leak in the library.
|
||||
#
|
||||
set LeakList {}
|
||||
|
||||
|
||||
for {set Counter 0} {$Counter<$COUNT} {incr Counter} {
|
||||
foreach testfile [lsort -dictionary [glob $testdir/*.test]] {
|
||||
if {[file tail $testfile]=="all.test"} continue
|
||||
source $testfile
|
||||
foreach p $PREFIXES {
|
||||
set dbprefix $p
|
||||
foreach testfile [lsort -dictionary [glob $testdir/*.test]] {
|
||||
if {[file tail $testfile]=="all.test"} continue
|
||||
source $testfile
|
||||
}
|
||||
}
|
||||
if {[info exists Leak]} {
|
||||
lappend LeakList $Leak
|
||||
}
|
||||
}
|
||||
|
||||
# Do one last test to look for a memory leak in the library. This will
|
||||
# only work if SQLite is compiled with the -DMEMORY_DEBUG=1 flag.
|
||||
#
|
||||
if {$LeakList!=""} {
|
||||
puts -nonewline memory-leak-test...
|
||||
incr ::nTest
|
||||
foreach x $LeakList {
|
||||
if {$x!=[lindex $LeakList 0]} {
|
||||
puts " failed!"
|
||||
puts "Expected: all values to be the same"
|
||||
puts " Got: $LeakList"
|
||||
incr ::nErr
|
||||
break
|
||||
}
|
||||
}
|
||||
puts " Ok"
|
||||
}
|
||||
|
||||
really_finish_test
|
||||
|
||||
+11
-11
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is exercising the code in dbbe.c.
|
||||
#
|
||||
# $Id: dbbe.test,v 1.3 2000/08/17 09:50:00 drh Exp $
|
||||
# $Id: dbbe.test,v 1.5 2001/01/31 13:28:09 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -32,13 +32,13 @@ source $testdir/tester.tcl
|
||||
#
|
||||
do_test dbbe-1.1 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
set v [catch {sqlite db testdb 0444} msg]
|
||||
lappend v $msg
|
||||
} {1 {can't find directory "testdb"}}
|
||||
do_test dbbe-1.2 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
set v [catch {sqlite db testdb/dummy 0666} msg]
|
||||
lappend v $msg
|
||||
} {1 {can't find or create directory "testdb/dummy"}}
|
||||
@@ -49,7 +49,7 @@ do_test dbbe-1.2 {
|
||||
#
|
||||
do_test dbbe-1.3 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
set v [catch {sqlite db testdb 0666} msg]
|
||||
lappend v $msg
|
||||
} {0 {}}
|
||||
@@ -58,7 +58,7 @@ do_test dbbe-1.3 {
|
||||
#
|
||||
do_test dbbe-1.4 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
set fd [open testdb w]
|
||||
puts $fd hi!
|
||||
close $fd
|
||||
@@ -70,7 +70,7 @@ do_test dbbe-1.4 {
|
||||
#
|
||||
do_test dbbe-1.5 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
file mkdir testdb
|
||||
file attributes testdb -permissions 0
|
||||
set v [catch {sqlite db testdb 0666} msg]
|
||||
@@ -81,7 +81,7 @@ do_test dbbe-1.5 {
|
||||
#
|
||||
do_test dbbe-1.6 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
sqlite db testdb 0666
|
||||
execsql {CREATE TABLE t1(x int)}
|
||||
db close
|
||||
@@ -91,7 +91,7 @@ do_test dbbe-1.6 {
|
||||
} {1 {access permission denied for testdb/sqlite_master.tbl}}
|
||||
do_test dbbe-1.6b {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
sqlite db testdb 0666
|
||||
execsql {CREATE TABLE t1(x int)}
|
||||
db close
|
||||
@@ -105,7 +105,7 @@ do_test dbbe-1.6b {
|
||||
#
|
||||
do_test dbbe-2.1 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
sqlite db testdb 0666
|
||||
execsql {
|
||||
CREATE TABLE t1(x int);
|
||||
@@ -123,14 +123,14 @@ do_test dbbe-2.1 {
|
||||
#
|
||||
do_test dbbe-3.1 {
|
||||
catch {db close}
|
||||
file delete -force testdb
|
||||
forcedelete testdb
|
||||
sqlite db testdb 0666
|
||||
execsql {CREATE TABLE t1(x int)}
|
||||
db close
|
||||
sqlite db testdb 0444
|
||||
set v [catch {execsql {INSERT INTO t1 VALUES(1)}} msg]
|
||||
lappend v $msg
|
||||
} {1 {table t1 is readonly}}
|
||||
} {7 {table t1 is readonly}}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
+8
-1
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the CREATE INDEX statement.
|
||||
#
|
||||
# $Id: index.test,v 1.7 2000/08/02 13:47:42 drh Exp $
|
||||
# $Id: index.test,v 1.8 2000/10/19 14:10:09 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -39,12 +39,14 @@ do_test index-1.1b {
|
||||
execsql {SELECT name, sql, tbl_name, type FROM sqlite_master
|
||||
WHERE name='index1'}
|
||||
} {index1 {CREATE INDEX index1 ON test1(f1)} test1 index}
|
||||
skipif memory:
|
||||
do_test index-1.1c {
|
||||
db close
|
||||
sqlite db testdb
|
||||
execsql {SELECT name, sql, tbl_name, type FROM sqlite_master
|
||||
WHERE name='index1'}
|
||||
} {index1 {CREATE INDEX index1 ON test1(f1)} test1 index}
|
||||
skipif memory:
|
||||
do_test index-1.1d {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -106,6 +108,7 @@ set r {}
|
||||
for {set i 1} {$i<100} {incr i} {
|
||||
lappend r testdb/index$i.tbl
|
||||
}
|
||||
skipif memory:
|
||||
do_test index-3.2 {
|
||||
execsql {INSERT INTO test1 VALUES(1,2,3,4,5)}
|
||||
lsort -dictionary [glob testdb/index*.tbl]
|
||||
@@ -223,6 +226,10 @@ do_test index-7.1 {
|
||||
for {set i 1} {$i<20} {incr i} {
|
||||
execsql "INSERT INTO test1 VALUES($i,[expr {int(pow(2,$i))}])"
|
||||
}
|
||||
execsql {SELECT count(*) FROM test1}
|
||||
} {19}
|
||||
skipif memory:
|
||||
do_test index-7.1b {
|
||||
lsort -dictionary [glob testdb/test1*.tbl]
|
||||
} {testdb/test1.tbl testdb/test1__primary_key.tbl}
|
||||
do_test index-7.2 {
|
||||
|
||||
+8
-4
@@ -23,7 +23,9 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this script is database locks.
|
||||
#
|
||||
# $Id: lock.test,v 1.2 2000/08/04 13:51:11 drh Exp $
|
||||
# $Id: lock.test,v 1.5 2001/01/04 14:20:18 drh Exp $
|
||||
|
||||
if {$dbprefix=="gdbm:"} {
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -59,7 +61,7 @@ do_test lock-1.2 {
|
||||
#
|
||||
set v [catch {execsql {UPDATE big SET f2='xyz' WHERE f1=11}} msg]
|
||||
lappend v $msg
|
||||
} {1 {table big is locked}}
|
||||
} {5 {table big is locked}}
|
||||
|
||||
do_test lock-1.3 {
|
||||
# Try to update the database in a separate process
|
||||
@@ -70,7 +72,7 @@ do_test lock-1.3 {
|
||||
puts $f "SELECT f2 FROM big WHERE f1=11;"
|
||||
close $f
|
||||
exec ./sqlite testdb <update.sql
|
||||
} "SQL error: table big is locked\n22"
|
||||
} "UPDATE big SET f2='xyz' WHERE f1=11;\nSQL error: table big is locked\n22"
|
||||
|
||||
do_test lock-1.4 {
|
||||
# Try to update the database using a timeout
|
||||
@@ -81,7 +83,7 @@ do_test lock-1.4 {
|
||||
puts $f "SELECT f2 FROM big WHERE f1=11;"
|
||||
close $f
|
||||
exec ./sqlite testdb <update.sql
|
||||
} "SQL error: table big is locked\n22"
|
||||
} "UPDATE big SET f2='xyz' WHERE f1=11;\nSQL error: table big is locked\n22"
|
||||
|
||||
do_test lock-1.5 {
|
||||
# Try to update the database using a timeout
|
||||
@@ -99,3 +101,5 @@ catch {exec kill -HUP $::lock_pid}
|
||||
catch {exec kill -9 $::lock_pid}
|
||||
|
||||
finish_test
|
||||
|
||||
}
|
||||
|
||||
+29
-1
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is exercising the code in main.c.
|
||||
#
|
||||
# $Id: main.test,v 1.3 2000/06/16 20:51:26 drh Exp $
|
||||
# $Id: main.test,v 1.4 2000/12/10 18:23:52 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -48,6 +48,34 @@ do_test main-1.4 {
|
||||
;
|
||||
}
|
||||
} {1}
|
||||
do_test main-1.5 {
|
||||
db complete {DROP TABLE 'xyz;}
|
||||
} {0}
|
||||
do_test main-1.6 {
|
||||
db complete {DROP TABLE 'xyz';}
|
||||
} {1}
|
||||
do_test main-1.7 {
|
||||
db complete {DROP TABLE "xyz;}
|
||||
} {0}
|
||||
do_test main-1.8 {
|
||||
db complete {DROP TABLE "xyz';}
|
||||
} {0}
|
||||
do_test main-1.9 {
|
||||
db complete {DROP TABLE "xyz";}
|
||||
} {1}
|
||||
do_test main-1.10 {
|
||||
db complete {DROP TABLE xyz; hi}
|
||||
} {0}
|
||||
do_test main-1.11 {
|
||||
db complete {DROP TABLE xyz; }
|
||||
} {1}
|
||||
do_test main-1.12 {
|
||||
db complete {DROP TABLE xyz; -- hi }
|
||||
} {1}
|
||||
do_test main-1.13 {
|
||||
db complete {DROP TABLE xyz; -- hi
|
||||
}
|
||||
} {1}
|
||||
|
||||
# Try to open a database with a corrupt master file.
|
||||
#
|
||||
|
||||
+12
-2
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the SELECT statement.
|
||||
#
|
||||
# $Id: select2.test,v 1.8 2000/07/29 13:07:00 drh Exp $
|
||||
# $Id: select2.test,v 1.9 2000/10/19 14:10:09 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -104,9 +104,16 @@ do_test select2-3.2c {
|
||||
execsql {SELECT f1 FROM tbl2 WHERE f2=1000}
|
||||
} {500}
|
||||
do_test select2-3.2d {
|
||||
execsql {SELECT fcnt() FROM tbl2 WHERE 1000=f2}
|
||||
} {2}
|
||||
do_test select2-3.2e {
|
||||
execsql {SELECT fcnt() FROM tbl2 WHERE f2=1000}
|
||||
} {2}
|
||||
testif gdbm:
|
||||
do_test select2-3.2f {
|
||||
set t1 [lindex [time {execsql {SELECT f1 FROM tbl2 WHERE 1000=f2}} 1] 0]
|
||||
set t2 [lindex [time {execsql {SELECT f1 FROM tbl2 WHERE f2=1000}} 1] 0]
|
||||
expr {$t1*0.8<$t2 && $t2*0.8<$t1}
|
||||
expr {$t1*0.7<$t2 && $t2*0.7<$t1}
|
||||
} {1}
|
||||
|
||||
# Make sure queries run faster with an index than without
|
||||
@@ -117,5 +124,8 @@ do_test select2-3.3 {
|
||||
set t2 [lindex [time {execsql {SELECT f1 FROM tbl2 WHERE f2==2000}} 1] 0]
|
||||
expr {$t1*10 < $t2}
|
||||
} {1}
|
||||
do_test select2-3.4 {
|
||||
expr {[execsql {SELECT fcnt() FROM tbl2 WHERE f2==2000}]>10}
|
||||
} {1}
|
||||
|
||||
finish_test
|
||||
|
||||
+12
-1
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the CREATE TABLE statement.
|
||||
#
|
||||
# $Id: table.test,v 1.6 2000/08/02 13:47:43 drh Exp $
|
||||
# $Id: table.test,v 1.7 2000/10/19 14:10:09 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -49,6 +49,9 @@ do_test table-1.1 {
|
||||
#
|
||||
do_test table-1.2 {
|
||||
execsql {INSERT INTO test1 VALUES('hi', 'y''all')}
|
||||
} {}
|
||||
testif gdbm:
|
||||
do_test table-1.2b {
|
||||
lsort [glob -nocomplain testdb/*.tbl]
|
||||
} {testdb/sqlite_master.tbl testdb/test1.tbl}
|
||||
|
||||
@@ -61,6 +64,7 @@ do_test table-1.3 {
|
||||
# Close and reopen the database. Verify that everything is
|
||||
# still the same.
|
||||
#
|
||||
skipif memory:
|
||||
do_test table-1.4 {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -76,6 +80,7 @@ do_test table-1.5 {
|
||||
|
||||
# Verify that the file associated with the database is gone.
|
||||
#
|
||||
testif gdbm:
|
||||
do_test table-1.5 {
|
||||
lsort [glob -nocomplain testdb/*.tbl]
|
||||
} {testdb/sqlite_master.tbl}
|
||||
@@ -83,6 +88,7 @@ do_test table-1.5 {
|
||||
# Close and reopen the database. Verify that the table is
|
||||
# still gone.
|
||||
#
|
||||
skipif memory:
|
||||
do_test table-1.6 {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -121,6 +127,7 @@ do_test table-2.1b {
|
||||
set v [catch {execsql {CREATE TABLE sqlite_master(two text)}} msg]
|
||||
lappend v $msg
|
||||
} {1 {table sqlite_master already exists}}
|
||||
skipif memory:
|
||||
do_test table-2.1c {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -138,6 +145,7 @@ do_test table-2.2a {
|
||||
set v [catch {execsql {CREATE TABLE test3(two text)}} msg]
|
||||
lappend v $msg
|
||||
} {1 {there is already an index named test3}}
|
||||
skipif memory:
|
||||
do_test table-2.2b {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -198,6 +206,7 @@ do_test table-3.4 {
|
||||
set v [catch {execsql {CREATE TABLE bIg(xyz foo)}} msg]
|
||||
lappend v $msg
|
||||
} {1 {table bIg already exists}}
|
||||
skipif memory:
|
||||
do_test table-3.5 {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -226,6 +235,7 @@ do_test table-4.1 {
|
||||
}
|
||||
execsql {SELECT name FROM sqlite_master WHERE type!='meta' ORDER BY name}
|
||||
} $r
|
||||
skipif memory:
|
||||
do_test table-4.1b {
|
||||
db close
|
||||
sqlite db testdb
|
||||
@@ -287,6 +297,7 @@ do_test table-5.4 {
|
||||
|
||||
# Create a table with a goofy name
|
||||
#
|
||||
testif gdbm:
|
||||
do_test table-6.1 {
|
||||
execsql {CREATE TABLE 'Spaces In This Name!'(x int)}
|
||||
execsql {INSERT INTO 'spaces in this name!' VALUES(1)}
|
||||
|
||||
+75
-7
@@ -23,13 +23,29 @@
|
||||
# This file implements some common TCL routines used for regression
|
||||
# testing the SQLite library
|
||||
#
|
||||
# $Id: tester.tcl,v 1.6 2000/09/21 13:01:37 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,6 +141,7 @@ proc finish_test {} {
|
||||
# A procedure to execute SQL
|
||||
#
|
||||
proc execsql {sql} {
|
||||
# puts "SQL = $sql"
|
||||
return [db eval $sql]
|
||||
}
|
||||
|
||||
@@ -97,3 +157,11 @@ proc execsql2 {sql} {
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
# Delete a file or directory
|
||||
#
|
||||
proc forcedelete {filename} {
|
||||
if {[catch {file delete -force $filename}]} {
|
||||
exec rm -rf $filename
|
||||
}
|
||||
}
|
||||
|
||||
+277
-23
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the UPDATE statement.
|
||||
#
|
||||
# $Id: update.test,v 1.4 2000/06/21 13:59:14 drh Exp $
|
||||
# $Id: update.test,v 1.5 2001/02/19 18:24:22 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -90,47 +90,301 @@ do_test update-3.8 {
|
||||
execsql {UPDATE test1 SET F2=f1, F1=f2}
|
||||
execsql {SELECT * FROM test1 ORDER BY F1}
|
||||
} {2 1 4 2 8 3 16 4 32 5 64 6 128 7 256 8 512 9 1024 10}
|
||||
|
||||
# Create an index and make sure updating works with an index.
|
||||
#
|
||||
do_test update-3.9 {
|
||||
execsql {CREATE INDEX index1 ON test1(f1)}
|
||||
execsql {CREATE INDEX index2 ON test1(f1)}
|
||||
execsql {UPDATE test1 SET f1=1025 WHERE f2==10}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1}
|
||||
} {2 1 4 2 8 3 16 4 32 5 64 6 128 7 256 8 512 9 1025 10}
|
||||
do_test update-3.10 {
|
||||
execsql {SELECT * FROM test1 WHERE f1=1025}
|
||||
} {1025 10}
|
||||
do_test update-3.11 {
|
||||
execsql {UPDATE test1 SET f2=11 WHERE f1==1025}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1}
|
||||
} {2 1 4 2 8 3 16 4 32 5 64 6 128 7 256 8 512 9 1025 11}
|
||||
do_test update-3.12 {
|
||||
execsql {SELECT * FROM test1 WHERE f1=1025}
|
||||
} {1025 11}
|
||||
execsql {UPDATE test1 SET F2=f1, F1=f2}
|
||||
execsql {SELECT * FROM test1 ORDER BY F1}
|
||||
} {1 2 2 4 3 8 4 16 5 32 6 64 7 128 8 256 9 512 10 1024}
|
||||
|
||||
# Create duplicate entries and make sure updating still
|
||||
# works.
|
||||
#
|
||||
do_test update-4.0 {
|
||||
execsql {
|
||||
DELETE FROM test1 WHERE f1<=5;
|
||||
INSERT INTO test1(f1,f2) VALUES(8,88);
|
||||
INSERT INTO test1(f1,f2) VALUES(8,888);
|
||||
INSERT INTO test1(f1,f2) VALUES(77,128);
|
||||
INSERT INTO test1(f1,f2) VALUES(777,128);
|
||||
}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-4.1 {
|
||||
execsql {UPDATE test1 SET f2=f2+1 WHERE f1==8}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 889 9 512 10 1024 77 128 777 128}
|
||||
do_test update-4.2 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2>800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-4.3 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2<800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-4.4 {
|
||||
execsql {UPDATE test1 SET f1=f1+1 WHERE f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 778 128}
|
||||
do_test update-4.5 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1>100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 777 128}
|
||||
do_test update-4.6 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1<=100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
|
||||
# Repeat the previous sequence of tests with an index.
|
||||
#
|
||||
do_test update-5.0 {
|
||||
execsql {CREATE INDEX idx1 ON test1(f1)}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-5.1 {
|
||||
execsql {UPDATE test1 SET f2=f2+1 WHERE f1==8}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 889 9 512 10 1024 77 128 777 128}
|
||||
do_test update-5.2 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2>800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-5.3 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2<800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-5.4 {
|
||||
execsql {UPDATE test1 SET f1=f1+1 WHERE f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 778 128}
|
||||
do_test update-5.4.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
|
||||
} {78 128}
|
||||
do_test update-5.4.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {778 128}
|
||||
do_test update-5.4.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 128 8 256 8 888}
|
||||
do_test update-5.5 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1>100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 777 128}
|
||||
do_test update-5.5.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
|
||||
} {78 128}
|
||||
do_test update-5.5.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-5.5.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
|
||||
} {777 128}
|
||||
do_test update-5.5.4 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 128 8 256 8 888}
|
||||
do_test update-5.6 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1<=100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-5.6.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==77 ORDER BY f1,f2}
|
||||
} {77 128}
|
||||
do_test update-5.6.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-5.6.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
|
||||
} {777 128}
|
||||
do_test update-5.6.4 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 256 8 888}
|
||||
|
||||
# Repeat the previous sequence of tests with a different index.
|
||||
#
|
||||
do_test update-6.0 {
|
||||
execsql {DROP INDEX idx1}
|
||||
execsql {CREATE INDEX idx1 ON test1(f2)}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-6.1 {
|
||||
execsql {UPDATE test1 SET f2=f2+1 WHERE f1==8}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 889 9 512 10 1024 77 128 777 128}
|
||||
do_test update-6.1.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 89 8 257 8 889}
|
||||
do_test update-6.1.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f2==89 ORDER BY f1,f2}
|
||||
} {8 89}
|
||||
do_test update-6.1.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==88 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-6.2 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2>800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-6.3 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2<800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-6.3.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 256 8 888}
|
||||
do_test update-6.3.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f2==89 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-6.3.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f2==88 ORDER BY f1,f2}
|
||||
} {8 88}
|
||||
do_test update-6.4 {
|
||||
execsql {UPDATE test1 SET f1=f1+1 WHERE f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 778 128}
|
||||
do_test update-6.4.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
|
||||
} {78 128}
|
||||
do_test update-6.4.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {778 128}
|
||||
do_test update-6.4.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 128 8 256 8 888}
|
||||
do_test update-6.5 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1>100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 777 128}
|
||||
do_test update-6.5.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
|
||||
} {78 128}
|
||||
do_test update-6.5.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-6.5.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
|
||||
} {777 128}
|
||||
do_test update-6.5.4 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 128 8 256 8 888}
|
||||
do_test update-6.6 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1<=100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-6.6.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==77 ORDER BY f1,f2}
|
||||
} {77 128}
|
||||
do_test update-6.6.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-6.6.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
|
||||
} {777 128}
|
||||
do_test update-6.6.4 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 256 8 888}
|
||||
|
||||
# Repeat the previous sequence of tests with multiple
|
||||
# indices
|
||||
#
|
||||
do_test update-7.0 {
|
||||
execsql {CREATE INDEX idx2 ON test1(f2)}
|
||||
execsql {CREATE INDEX idx3 ON test1(f1,f2)}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-7.1 {
|
||||
execsql {UPDATE test1 SET f2=f2+1 WHERE f1==8}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 889 9 512 10 1024 77 128 777 128}
|
||||
do_test update-7.1.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 89 8 257 8 889}
|
||||
do_test update-7.1.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f2==89 ORDER BY f1,f2}
|
||||
} {8 89}
|
||||
do_test update-7.1.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==88 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-7.2 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2>800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 89 8 257 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-7.3 {
|
||||
execsql {UPDATE test1 SET f2=f2-1 WHERE f1==8 and f2<800}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-7.3.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 256 8 888}
|
||||
do_test update-7.3.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f2==89 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-7.3.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f2==88 ORDER BY f1,f2}
|
||||
} {8 88}
|
||||
do_test update-7.4 {
|
||||
execsql {UPDATE test1 SET f1=f1+1 WHERE f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 778 128}
|
||||
do_test update-7.4.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
|
||||
} {78 128}
|
||||
do_test update-7.4.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {778 128}
|
||||
do_test update-7.4.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 128 8 256 8 888}
|
||||
do_test update-7.5 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1>100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 8 88 8 128 8 256 8 888 9 512 10 1024 78 128 777 128}
|
||||
do_test update-7.5.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==78 ORDER BY f1,f2}
|
||||
} {78 128}
|
||||
do_test update-7.5.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-7.5.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
|
||||
} {777 128}
|
||||
do_test update-7.5.4 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 128 8 256 8 888}
|
||||
do_test update-7.6 {
|
||||
execsql {UPDATE test1 SET f1=f1-1 WHERE f1<=100 and f2==128}
|
||||
execsql {SELECT * FROM test1 ORDER BY f1,f2}
|
||||
} {6 64 7 128 8 88 8 256 8 888 9 512 10 1024 77 128 777 128}
|
||||
do_test update-7.6.1 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==77 ORDER BY f1,f2}
|
||||
} {77 128}
|
||||
do_test update-7.6.2 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==778 ORDER BY f1,f2}
|
||||
} {}
|
||||
do_test update-7.6.3 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==777 ORDER BY f1,f2}
|
||||
} {777 128}
|
||||
do_test update-7.6.4 {
|
||||
execsql {SELECT * FROM test1 WHERE f1==8 ORDER BY f1,f2}
|
||||
} {8 88 8 256 8 888}
|
||||
|
||||
# Error messages
|
||||
#
|
||||
do_test update-4.1 {
|
||||
do_test update-9.1 {
|
||||
set v [catch {execsql {
|
||||
UPDATE test1 SET x=11 WHERE f1=1025
|
||||
}} msg]
|
||||
lappend v $msg
|
||||
} {1 {no such column: x}}
|
||||
do_test update-4.2 {
|
||||
do_test update-9.2 {
|
||||
set v [catch {execsql {
|
||||
UPDATE test1 SET f1=x(11) WHERE f1=1025
|
||||
}} msg]
|
||||
lappend v $msg
|
||||
} {1 {no such function: x}}
|
||||
do_test update-4.3 {
|
||||
do_test update-9.3 {
|
||||
set v [catch {execsql {
|
||||
UPDATE test1 SET f1=11 WHERE x=1025
|
||||
}} msg]
|
||||
lappend v $msg
|
||||
} {1 {no such column: x}}
|
||||
do_test update-4.4 {
|
||||
do_test update-9.4 {
|
||||
set v [catch {execsql {
|
||||
UPDATE test1 SET f1=11 WHERE x(f1)=1025
|
||||
}} msg]
|
||||
|
||||
+3
-1
@@ -23,7 +23,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the VACUUM statement.
|
||||
#
|
||||
# $Id: vacuum.test,v 1.1 2000/06/03 18:06:54 drh Exp $
|
||||
# $Id: vacuum.test,v 1.2 2000/10/19 14:10:10 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -53,6 +53,7 @@ execsql {INSERT INTO test1 VALUES(2)}
|
||||
execsql {INSERT INTO test1 VALUES(3)}
|
||||
execsql {INSERT INTO test2 VALUES(4)}
|
||||
|
||||
testif gdbm:
|
||||
do_test vacuum-1.3 {
|
||||
set b1 [file mtime testdb/test1.tbl]
|
||||
set b2 [file mtime testdb/test2.tbl]
|
||||
@@ -64,6 +65,7 @@ do_test vacuum-1.3 {
|
||||
set a3 [file mtime testdb/index1.tbl]
|
||||
expr {$a1>$b1 && $a2==$b2 && $a3==$b3}
|
||||
} {1}
|
||||
testif gdbm:
|
||||
do_test vacuum-1.4 {
|
||||
set b1 [file mtime testdb/test1.tbl]
|
||||
set b2 [file mtime testdb/test2.tbl]
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
** Copyright (c) 1999, 2000 D. Richard Hipp
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or
|
||||
** modify it under the terms of the GNU General Public
|
||||
** License as published by the Free Software Foundation; either
|
||||
** version 2 of the License, or (at your option) any later version.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
** General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public
|
||||
** License along with this library; if not, write to the
|
||||
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
** Boston, MA 02111-1307, USA.
|
||||
**
|
||||
** Author contact information:
|
||||
** drh@hwaci.com
|
||||
** http://www.hwaci.com/drh/
|
||||
**
|
||||
*************************************************************************
|
||||
** A utility print statistics about the content of a GDBM database.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <gdbm.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int bins[] = {
|
||||
4, 8, 12, 16, 24,
|
||||
32, 40, 48, 56, 64,
|
||||
80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256,
|
||||
288, 320, 352, 384, 416, 448, 480, 512, 1024, 2048, 4096, 8192,
|
||||
16384, 32768, 65536,
|
||||
};
|
||||
#define NBIN (sizeof(bins)/sizeof(bins[0])+1)
|
||||
|
||||
static int gAllSize[NBIN];
|
||||
static int gCount;
|
||||
static int gTotal;
|
||||
static int gMax;
|
||||
static int gKey;
|
||||
static int gMaxKey;
|
||||
|
||||
static int gdbm_stat(char *zFilename){
|
||||
GDBM_FILE p;
|
||||
datum data, key, next;
|
||||
int nEntry = 0;
|
||||
int keyTotal = 0;
|
||||
int dataTotal = 0;
|
||||
int allTotal = 0;
|
||||
int lMax = 0;
|
||||
int keySize[NBIN], dataSize[NBIN], allSize[NBIN];
|
||||
int i, priorSize;
|
||||
|
||||
p = gdbm_open(zFilename, 0, GDBM_READER, 0, 0);
|
||||
if( p==0 ){
|
||||
fprintf(stderr,"can't open file \"%s\"\n", zFilename);
|
||||
return 1;
|
||||
}
|
||||
for(i=0; i<NBIN; i++){
|
||||
keySize[i] = 0;
|
||||
dataSize[i] = 0;
|
||||
allSize[i] = 0;
|
||||
}
|
||||
key = gdbm_firstkey(p);
|
||||
while( key.dptr ){
|
||||
int all;
|
||||
nEntry++;
|
||||
gCount++;
|
||||
keyTotal += key.dsize;
|
||||
for(i=0; i<NBIN-1 && key.dsize>bins[i]; i++){}
|
||||
keySize[i]++;
|
||||
gKey += key.dsize;
|
||||
if( key.dsize>gMaxKey ) gMaxKey = key.dsize;
|
||||
data = gdbm_fetch(p, key);
|
||||
if( data.dptr==0 ) data.dsize = 0;
|
||||
dataTotal += data.dsize;
|
||||
for(i=0; i<NBIN-1 && data.dsize>bins[i]; i++){}
|
||||
dataSize[i]++;
|
||||
all = key.dsize + data.dsize;
|
||||
allTotal += all;
|
||||
gTotal += all;
|
||||
if( all>gMax ) gMax = all;
|
||||
if( all>lMax ) lMax = all;
|
||||
for(i=0; i<NBIN-1 && all>bins[i]; i++){}
|
||||
allSize[i]++;
|
||||
gAllSize[i]++;
|
||||
next = gdbm_nextkey(p, key);
|
||||
free( key.dptr );
|
||||
key = next;
|
||||
}
|
||||
gdbm_close(p);
|
||||
printf("%s:\n", zFilename);
|
||||
printf(" entries: %d\n", nEntry);
|
||||
printf(" keysize: %d (%d per entry)\n",
|
||||
keyTotal, nEntry>0 ? (keyTotal+nEntry-1)/nEntry : 0);
|
||||
printf(" datasize: %d (%d per entry)\n",
|
||||
dataTotal, nEntry>0 ? (dataTotal+nEntry-1)/nEntry : 0);
|
||||
printf(" size: %d (%d per entry)\n",
|
||||
allTotal, nEntry>0 ? (allTotal+nEntry-1)/nEntry : 0);
|
||||
priorSize = 0;
|
||||
for(i=0; i<NBIN-1; i++){
|
||||
if( keySize[i]==0 && dataSize[i]==0 ) continue;
|
||||
printf("%5d..%-5d %7d %7d %7d\n", priorSize, bins[i], keySize[i],
|
||||
dataSize[i], allSize[i]);
|
||||
priorSize = bins[i]+1;
|
||||
}
|
||||
if( keySize[NBIN-1]>0 || dataSize[NBIN-1]>0 ){
|
||||
printf("%5d..%-5d %7d %7d %7d\n", priorSize, lMax,
|
||||
keySize[NBIN-1], dataSize[NBIN-1], allSize[NBIN-1]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv){
|
||||
int i, ps, sum;
|
||||
int nErr = 0;
|
||||
for(i=1; i<argc; i++){
|
||||
nErr += gdbm_stat(argv[i]);
|
||||
}
|
||||
printf("*****************************************************************\n");
|
||||
printf("Entries: %d\n", gCount);
|
||||
printf("Size: %d\n", gTotal);
|
||||
printf("Avg Size: %d\n", gCount>0 ? (gTotal + gCount - 1)/gCount : 0);
|
||||
printf("Key Size: %d\n", gKey);
|
||||
printf("Avg Key Size: %d\n", gCount>0 ? (gKey + gCount - 1)/gCount : 0);
|
||||
printf("Max Key Size: %d\n\n", gMaxKey);
|
||||
ps = 0;
|
||||
sum = 0;
|
||||
for(i=0; i<NBIN-1; i++){
|
||||
if( gAllSize[i]==0 ) continue;
|
||||
sum += gAllSize[i];
|
||||
printf("%5d..%-5d %8d %3d%%\n",
|
||||
ps, bins[i], gAllSize[i], sum*100/gCount);
|
||||
ps = bins[i]+1;
|
||||
}
|
||||
if( gAllSize[NBIN-1]>0 ){
|
||||
printf("%5d..%-5d %8d 100%%\n", ps, gMax, gAllSize[NBIN-1]);
|
||||
}
|
||||
return nErr;
|
||||
}
|
||||
@@ -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.
|
||||
+34
-6
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Run this Tcl script to generate the sqlite.html file.
|
||||
#
|
||||
set rcsid {$Id: c_interface.tcl,v 1.10 2000/10/09 12:57:01 drh Exp $}
|
||||
set rcsid {$Id: c_interface.tcl,v 1.13 2000/11/28 20:46:39 drh Exp $}
|
||||
|
||||
puts {<html>
|
||||
<head>
|
||||
@@ -29,7 +29,7 @@ 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*);
|
||||
|
||||
@@ -68,6 +68,8 @@ int sqlite_get_table(
|
||||
|
||||
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*);
|
||||
@@ -132,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
|
||||
@@ -141,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
|
||||
@@ -263,6 +279,10 @@ 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>
|
||||
|
||||
@@ -317,6 +337,14 @@ 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 next interface routine to SQLite is a convenience function used
|
||||
@@ -430,7 +458,7 @@ meaning of single-quote within a string literal.
|
||||
</p>
|
||||
|
||||
<p>Consider an example. Suppose you are trying to insert a string
|
||||
values into a database table where the string value was obtained from
|
||||
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>
|
||||
|
||||
|
||||
+85
-2
@@ -17,9 +17,92 @@ proc chng {date desc} {
|
||||
puts "<DD><P><UL>$desc</UL></P></DD>"
|
||||
}
|
||||
|
||||
chng {2000 Oct 11 (Not Released)} {
|
||||
chng {2001 Mar 14 19 (1.0.24)} {
|
||||
<li>Fix a bug which was causing
|
||||
the UPDATE command to fail on systems where "malloc(0)" returns
|
||||
NULL. The problem does not appear Windows, Linux, or HPUX but does
|
||||
cause the library to fail on QNX.
|
||||
</li>
|
||||
}
|
||||
|
||||
chng {2001 Feb 19 (1.0.23)} {
|
||||
<li>An unrelated (and minor) bug from Mark Muranwski fixed. The algorithm
|
||||
for figuring out where to put temporary files for a "memory:" database
|
||||
was not working quite right.
|
||||
</li>
|
||||
}
|
||||
|
||||
chng {2001 Feb 19 (1.0.22)} {
|
||||
<li>The previous fix was not quite right. This one seems to work better.
|
||||
</li>
|
||||
}
|
||||
|
||||
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>Add Doug Lea's memory allocator to the distribution, for completeness.</li>
|
||||
<li>Removed a few variables that were not being used. Etc.</li>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Run this Tcl script to generate the crosscompile.html file.
|
||||
#
|
||||
set rcsid {$Id: crosscompile.tcl,v 1.3 2000/10/11 19:28:53 drh Exp $}
|
||||
set rcsid {$Id: crosscompile.tcl,v 1.4 2001/01/31 13:28:09 drh Exp $}
|
||||
|
||||
puts {<html>
|
||||
<head>
|
||||
@@ -148,9 +148,9 @@ cat >>systems.h <<\END
|
||||
#undef UNLOCK_FILE
|
||||
#define UNLOCK_FILE(x)
|
||||
#undef READLOCK_FILE
|
||||
#define READLOCK_FILE(x)
|
||||
#define READLOCK_FILE(x) lock_val=0;
|
||||
#undef WRITELOCK_FILE
|
||||
#define WRITELOCK_FILE(x)
|
||||
#define WRITELOCK_FILE(x) lock_val=0;
|
||||
#endif
|
||||
END
|
||||
</pre></blockquote>
|
||||
|
||||
@@ -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>}
|
||||
+9
-2
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Run this TCL script to generate HTML for the index.html file.
|
||||
#
|
||||
set rcsid {$Id: index.tcl,v 1.29 2000/09/30 22:46:07 drh Exp $}
|
||||
set rcsid {$Id: index.tcl,v 1.31 2001/02/19 23:28:37 drh Exp $}
|
||||
|
||||
puts {<html>
|
||||
<head><title>SQLite: An SQL Database Library Built Atop GDBM</title></head>
|
||||
@@ -61,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.22. Users of prior versions of SQLite should
|
||||
consider upgrading.</p>
|
||||
|
||||
<p>
|
||||
Among the SQL features that SQLite does not currently implement are:</p>
|
||||
|
||||
@@ -75,7 +81,8 @@ Among the SQL features that SQLite does not currently implement are:</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
|
||||
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
|
||||
|
||||
+26
-2
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Run this Tcl script to generate the sqlite.html file.
|
||||
#
|
||||
set rcsid {$Id: lang.tcl,v 1.5 2000/08/04 13:49:03 drh Exp $}
|
||||
set rcsid {$Id: lang.tcl,v 1.6 2001/02/20 13:06:31 drh Exp $}
|
||||
|
||||
puts {<html>
|
||||
<head>
|
||||
@@ -361,6 +361,30 @@ operand of an IN operator, then the first row of the result of the
|
||||
SELECT becomes the value used in the expression. If the SELECT yields
|
||||
more than one result row, all rows after the first are ignored. If
|
||||
the SELECT yeilds no rows, then the value of the SELECT is NULL.</p>
|
||||
|
||||
<p>The expression syntax currently supports the following
|
||||
functions:</p>
|
||||
|
||||
<blockquote><pre>
|
||||
<font color="#2c2cf0"><big>count min max sum
|
||||
avg length substr</big></font>
|
||||
</pre></blockquote>
|
||||
|
||||
<p>
|
||||
The functions <b>count</b>, <b>sum</b>, and <b>avg</b> and the functions
|
||||
<b>min</b> and <b>max</b> used with only one argument are all aggregate
|
||||
functions. This means that they are computed across all rows of the result.
|
||||
The functions <b>min</b> and <b>max</b>
|
||||
with two or more arguments and the
|
||||
functions <b>length</b> and <b>substr</b>
|
||||
are non-aggregates. Non-aggregate functions
|
||||
are computed separately for each row of the result.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The "<b>count(*)</b>" syntax is supported but
|
||||
"<b>count(distinct</b> <i>COLUMN-NAME</i><b>)</b>" is not.
|
||||
</p>
|
||||
}
|
||||
|
||||
Section INSERT insert
|
||||
@@ -492,7 +516,7 @@ puts {
|
||||
command found in PostgreSQL. If VACUUM is invoked with the name of a
|
||||
table or index, then the <b>gdbm_reorganize()</b> function is called
|
||||
on the corresponding GDBM file. If VACUUM is invoked with no arguments,
|
||||
then <b>gdbm_reorganize()</b> is call on every GDBM file in the database.</p>
|
||||
then <b>gdbm_reorganize()</b> is called for every GDBM file in the database.</p>
|
||||
|
||||
<p>It is a good idea to run VACUUM after creating large indices,
|
||||
especially indices where a single index value refers to many
|
||||
|
||||
+31
-7
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Run this Tcl script to generate the tclsqlite.html file.
|
||||
#
|
||||
set rcsid {$Id: tclsqlite.tcl,v 1.2 2000/10/08 22:20:58 drh Exp $}
|
||||
set rcsid {$Id: tclsqlite.tcl,v 1.3 2000/10/23 13:16:33 drh Exp $}
|
||||
|
||||
puts {<html>
|
||||
<head>
|
||||
@@ -30,19 +30,43 @@ namespace.</p>
|
||||
<p>The <b>sqlite</b> command is used as follows:</p>
|
||||
|
||||
<blockquote>
|
||||
<b>sqlite</b> <i>dbcmd database-directory-name</i>
|
||||
<b>sqlite</b> <i>dbcmd database-name</i>
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
The <b>sqlite</b> command opens the SQLite database located in the
|
||||
directory named by the second argument. If the database or directory
|
||||
does not exist, it is created. The <b>sqlite</b> command
|
||||
also creates a new Tcl
|
||||
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
|
||||
@@ -205,7 +229,7 @@ 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 library the underlies SQLite allows multiple simultaneous
|
||||
<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.
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Run this Tcl script to generate the vdbe.html file.
|
||||
#
|
||||
set rcsid {$Id: vdbe.tcl,v 1.5 2000/07/30 20:04:43 drh Exp $}
|
||||
set rcsid {$Id: vdbe.tcl,v 1.6 2000/11/28 20:46:41 drh Exp $}
|
||||
|
||||
puts {<html>
|
||||
<head>
|
||||
@@ -220,7 +220,7 @@ cursor to the "examp" file.</p>
|
||||
preprocessor macro, then
|
||||
there is a special SQL comment that will cause the
|
||||
the VDBE to traces the execution of programs.
|
||||
Though this features was originally intended for testing
|
||||
Though this feature was originally intended for testing
|
||||
and debugging, it might also be useful in learning about
|
||||
how the VDBE operates.
|
||||
Use the "<tt>--vdbe-trace-on--</tt>" comment to
|
||||
@@ -306,7 +306,7 @@ int Callback(void *pUserData, int nColumn, char *azData[], char *azColumnName[])
|
||||
|
||||
<p>The SQLite library supplies the VDBE with a pointer to the callback function
|
||||
and the <b>pUserData</b> pointer. (Both the callback and the user data were
|
||||
originally passed in as argument to the <b>sqlite_exec()</b> API function.)
|
||||
originally passed in as arguments to the <b>sqlite_exec()</b> API function.)
|
||||
The job of the VDBE is to
|
||||
come up with values for <b>nColumn</b>, <b>azData[]</b>,
|
||||
and <b>azColumnName[]</b>.
|
||||
@@ -769,7 +769,7 @@ a small percentage of the rows end up in the result. This can
|
||||
take a long time on a big table. To speed things up, SQLite
|
||||
can use an index.</p>
|
||||
|
||||
<p>An GDBM file associates a key with some data. For a SQLite
|
||||
<p>A GDBM file associates a key with some data. For a SQLite
|
||||
table, the GDBM file is set up so that the key is a integer
|
||||
and the data is the information for one row of the table.
|
||||
Indices in SQLite reverse this arrangement. The GDBM key
|
||||
@@ -1172,7 +1172,7 @@ with that key is brought into focus. A new bucket is created with
|
||||
the key if one did not previously exist. Once the bucket is in
|
||||
focus, the memory locations of the bucket are used to accumulate
|
||||
the values of the various aggregate functions. After the query
|
||||
loop terminates, the each bucket is visited once to generate a
|
||||
loop terminates, each bucket is visited once to generate a
|
||||
single row of the results.</p>
|
||||
|
||||
<p>An example will help to clarify this concept. Consider the
|
||||
@@ -1476,7 +1476,7 @@ VDBE halts.</p>
|
||||
All this code does is make an entry in the temporary table for each
|
||||
row of the examp2 table. The key for each temporary table entry
|
||||
is the "three" column of examp2 and the data
|
||||
entries is an empty string since it is never used.</p>
|
||||
is an empty string since it is never used.</p>
|
||||
|
||||
<p>The outer SELECT is implemented by instructions 8 through 19. In
|
||||
particular, the WHERE clause containing the IN operator is implemented
|
||||
|
||||
Reference in New Issue
Block a user