diff --git a/VERSION b/VERSION index 9084fa2f71..c2320f5be4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0 +1.0.20 diff --git a/manifest b/manifest index bd9711067d..5694e61bb7 100644 --- a/manifest +++ b/manifest @@ -1,28 +1,31 @@ -C hi\s(CVS\s1716) -D 2001-02-06T14:10:27 +C Perpare\sto\sfork\sSQLite2.0\sdevelop\sinto\sa\sseparate\stree\s(CVS\s183) +D 2001-02-11T16:56:24 F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4 F Makefile.in 7efa81e2985b45ba73db27d55b70cc927f5abfd7 F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958 -F VERSION 05e17b646a817240c206186f94f8f4c70974d5dc +F VERSION 153302ac968751d918e44c3f26774dcfe50ddc0a F configure 3dc1edb9dcf60215e31ff72b447935ab62211442 x F configure.in d892ca33db7e88a055519ce2f36dcb11020e8fff F doc/lemon.html e233a3e97a779c7a87e1bc4528c664a58e49dd47 F doc/report1.txt ad0a41513479f1be0355d1f3f074e66779ff2282 F src/TODO 38a68a489e56e9fd4a96263e0ff9404a47368ad4 F src/build.c 7aa5879bf58ea6bbff22c26c59d1130021fa6ca4 -F src/db.c fff070e77423bbf98f5b138f23c605006a61066d -F src/db.h 6c8b8b6777f7c55b37cb851e9fe4bc3f379920c0 F src/dbbe.c 162d29b09ac379f160892c5795efc14099dcc8eb F src/dbbe.h 0435a36906a839cce062608f51bd9d3e79878fec F src/dbbegdbm.c 5bfcb1b4ee47a98c5eae83041e9716cd3233fd0e F src/dbbemem.c 215e107830ddf0d5a565bb7c20dd7200a925ef75 F src/delete.c b83dc815f83220a82df13f1f1f479187d305fe6a +F src/ex/README b745b00acce2d892f60c40111dacdfc48e0c1c7a +F src/ex/db.c f1419ae6c93e40b5ac6e39fe7efd95d868e6f9d7 +F src/ex/db.h 3f2933ee20c147fe494835786e4c6f3a562def4e +F src/ex/dbbebdb1.c 61ed414307f829478614def33de640bbe5b2f770 +F src/ex/dbbemird.c b00aef85656fa0a101dac2c32e12922ad106715a +F src/ex/pg.c 2bbf6a94f37226d06337868b6bf4d7affc60197f +F src/ex/pg.h 23a4ac807b0546ec2bb6239ec8bd3e06926572cd F src/expr.c 49bc261fdc4f4fb91c74cd668a9a952c00e85931 F src/insert.c 4bc1cab84f7805d560a1417734a532843e30b762 -F src/main.c 92fcd6d967ceee1f96a5b9543779eef6e9b56913 +F src/main.c 5afe29c425b875acede20f609485866eb5b276f6 F src/parse.y 25ee4d8efccc4b247c32fe4ab194e3dd8fd5a4ee -F src/pg.c 2981173b2a752ef3578168e7af1a32ff22b70db6 -F src/pg.h a95c4803a1aae99449aa2c0a1af0c8d863a3f340 F src/printf.c af0dc65c293427272e1949c7807b1d88f10004fd F src/random.c b36c3f57dc80c8f354e6bfbf39cf1e1de021d54a F src/select.c 0cadab95c8011ddbffe804de94f12f3c0e317863 @@ -33,7 +36,7 @@ F src/sqliteInt.h fd513fa6b7ac94919f85ebfa183aaa194284ce16 F src/table.c 5be76051a8ed6f6bfa641f4adc52529efa34fbf9 F src/tclsqlite.c 2a925e1835c348f07dd17c87d95ae9a577833407 F src/test.file 55ca6286e3e4f4fba5d0448333fa99fc5a404a73 -F src/tokenize.c 6843f1d7a5d2ee08ceb10bdecfcc8684131ffcf7 +F src/tokenize.c c7ad428f38e56342eb2025320480b5ae9ece1b90 F src/update.c 9692fbac8e95fdbc5318d39db576aa6c57b9c8ab F src/util.c 0298100e6427a4b644f767ede12276fa7170fbb6 F src/vdbe.c 6e613f25b0fe1c81b097f46a8fe68c68c39a6abf @@ -75,7 +78,7 @@ F www/arch.fig 4f246003b7da23bd63b8b0af0618afb4ee3055c8 F www/arch.png 8dae0766d42ed3de9ed013c1341a5792bcf633e6 F www/arch.tcl a40380c1fe0080c43e6cc5c20ed70731511b06be F www/c_interface.tcl 11be2d5826eb7d6efd629751d3b483c1ed78ba14 -F www/changes.tcl cb276a239c98524731e2780c70deb01b2e7e4bcc +F www/changes.tcl 2bd34627e9dc459f53d7e11630d92660be974b10 F www/crosscompile.tcl c99efacb3aefaa550c6e80d91b240f55eb9fd33e F www/fileformat.tcl cfb7fba80b7275555281ba2f256c00734bcdd1c9 F www/index.tcl b19418d506f90968deef972bf1b427d98bdf13e0 @@ -85,7 +88,7 @@ F www/opcode.tcl cb3a1abf8b7b9be9f3a228d097d6bf8b742c2b6f F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f F www/tclsqlite.tcl 06f81c401f79a04f2c5ebfb97e7c176225c0aef2 F www/vdbe.tcl 0c8aaa529dd216ccbf7daaabd80985e413d5f9ad -P 29305c3c9eb805a1f5beb82ad50cba1d3bbd3465 -R b22c18ed2ce590a98ae62ee327d6d335 +P 5128135c7e12f763722981ad39ecb7835db49142 +R ab8718eb81b6e7a57fbb613590282f19 U drh -Z 85d522fbac6369cd564c9bf8a4621677 +Z 4c71efe3f5c2467e23cd148f67512b61 diff --git a/manifest.uuid b/manifest.uuid index 0c42c90875..2f3646a6e5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5128135c7e12f763722981ad39ecb7835db49142 \ No newline at end of file +6adb6078871114ba19ab601bb94d43ff9e03e43f \ No newline at end of file diff --git a/src/ex/README b/src/ex/README new file mode 100644 index 0000000000..29f7142feb --- /dev/null +++ b/src/ex/README @@ -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. diff --git a/src/db.c b/src/ex/db.c similarity index 99% rename from src/db.c rename to src/ex/db.c index 3a9c6687bb..358ddc9d3e 100644 --- a/src/db.c +++ b/src/ex/db.c @@ -21,7 +21,7 @@ ** http://www.hwaci.com/drh/ ** ************************************************************************* -** $Id: db.c,v 1.6 2001/01/31 13:28:08 drh Exp $ +** $Id: db.c,v 1.1 2001/02/11 16:56:24 drh Exp $ */ #include "sqliteInt.h" #include "pg.h" diff --git a/src/db.h b/src/ex/db.h similarity index 97% rename from src/db.h rename to src/ex/db.h index cf7d6853de..67a0906409 100644 --- a/src/db.h +++ b/src/ex/db.h @@ -21,7 +21,7 @@ ** http://www.hwaci.com/drh/ ** ************************************************************************* -** $Id: db.h,v 1.5 2001/01/31 13:28:09 drh Exp $ +** $Id: db.h,v 1.1 2001/02/11 16:56:24 drh Exp $ */ typedef struct Db Db; diff --git a/src/ex/dbbebdb1.c b/src/ex/dbbebdb1.c new file mode 100644 index 0000000000..f1d3d9d4dc --- /dev/null +++ b/src/ex/dbbebdb1.c @@ -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 +#include +#include +#include +#include +#include +#include + +/* +** 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; +} diff --git a/src/ex/dbbemird.c b/src/ex/dbbemird.c new file mode 100644 index 0000000000..7ca9759d60 --- /dev/null +++ b/src/ex/dbbemird.c @@ -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 +#include +#include +#include +#include + +/* +** 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; inTemp; 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; inTemp; 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; inTemp; 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; +} diff --git a/src/pg.c b/src/ex/pg.c similarity index 99% rename from src/pg.c rename to src/ex/pg.c index f622a80d77..5a826c8145 100644 --- a/src/pg.c +++ b/src/ex/pg.c @@ -21,7 +21,7 @@ ** http://www.hwaci.com/drh/ ** ************************************************************************* -** $Id: pg.c,v 1.4 2001/01/25 01:45:41 drh Exp $ +** $Id: pg.c,v 1.1 2001/02/11 16:56:24 drh Exp $ */ #include #include diff --git a/src/pg.h b/src/ex/pg.h similarity index 96% rename from src/pg.h rename to src/ex/pg.h index cdf8a27624..f0fd50acf8 100644 --- a/src/pg.h +++ b/src/ex/pg.h @@ -21,7 +21,7 @@ ** http://www.hwaci.com/drh/ ** ************************************************************************* -** $Id: pg.h,v 1.3 2001/01/21 00:58:09 drh Exp $ +** $Id: pg.h,v 1.1 2001/02/11 16:56:24 drh Exp $ */ typedef struct Pgr Pgr; diff --git a/src/main.c b/src/main.c index 47f85d8210..4205b70bed 100644 --- a/src/main.c +++ b/src/main.c @@ -26,7 +26,7 @@ ** other files are for internal use by SQLite and should not be ** accessed by users of the library. ** -** $Id: main.c,v 1.24 2001/01/15 22:51:11 drh Exp $ +** $Id: main.c,v 1.25 2001/02/11 16:56:24 drh Exp $ */ #include "sqliteInt.h" #include @@ -124,7 +124,7 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){ ** database scheme. */ static VdbeOp initProg[] = { - { OP_OpenTbl, 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"}, diff --git a/src/tokenize.c b/src/tokenize.c index fddad500ea..3942fc402f 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -27,7 +27,7 @@ ** individual tokens and sends those tokens one-by-one over to the ** parser for analysis. ** -** $Id: tokenize.c,v 1.16 2000/12/10 18:23:51 drh Exp $ +** $Id: tokenize.c,v 1.17 2001/02/11 16:56:24 drh Exp $ */ #include "sqliteInt.h" #include @@ -308,7 +308,7 @@ 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; diff --git a/www/changes.tcl b/www/changes.tcl index 9391756646..657728738d 100644 --- a/www/changes.tcl +++ b/www/changes.tcl @@ -17,6 +17,17 @@ proc chng {date desc} { puts "

    $desc

" } +chng {2001 Feb 11 (1.0.20)} { +
  • 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.
  • +} + +chng {2001 Feb 6 (1.0.19)} { +
  • Fix a strange (but valid) C declaration that was causing problems + for QNX. No logical changes.
  • +} + chng {2001 Jan 4 (1.0.18)} {
  • Print the offending SQL statement when an error occurs.
  • Do not require commas between constraints in CREATE TABLE statements.