Files
sqlite/src/dbbemem.c
drh daffd0e597 better handling of out-of-memory errors (CVS 207)
FossilOrigin-Name: 86b30cd0975dfea9424b9f9f0d4194aa71ce508b
2001-04-11 14:28:42 +00:00

779 lines
23 KiB
C

/*
** Copyright (c) 2000 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*************************************************************************
** This file contains code to implement the database backend (DBBE)
** for sqlite. The database backend is the interface between
** sqlite and the code that does the actually reading and writing
** of information to the disk.
**
** This file uses an in-memory hash table as the database backend.
** Nothing is ever written to disk using this backend. All information
** is forgotten when the program exits.
**
** $Id: dbbemem.c,v 1.14 2001/04/11 14:28:42 drh Exp $
*/
#include "sqliteInt.h"
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
typedef struct Array Array;
typedef struct ArrayElem ArrayElem;
typedef struct Datum Datum;
/* A complete associative array is an instance of the following structure.
** The internals of this structure are intended to be opaque -- client
** code should not attempt to access or modify the fields of this structure
** directly. Change this structure only by using the routines below.
** However, many of the "procedures" and "functions" for modifying and
** accessing this structure are really macros, so we can't really make
** this structure opaque.
*/
struct Array {
int count; /* Number of entries in the array */
ArrayElem *first; /* The first element of the array */
int htsize; /* Number of buckets in the hash table */
struct _Array_ht { /* the hash table */
int count; /* Number of entries with this hash */
ArrayElem *chain; /* Pointer to first entry with this hash */
} *ht;
};
/*
** An instance of the following structure stores a single key or
** data element.
*/
struct Datum {
int n;
void *p;
};
/* Each element in the associative array is an instance of the following
** structure. All elements are stored on a single doubly-linked list.
**
** Again, this structure is intended to be opaque, but it can't really
** be opaque because it is used by macros.
*/
struct ArrayElem {
ArrayElem *next, *prev; /* Next and previous elements in the array */
Datum key, data; /* Key and data for this element */
};
/* Some routines are so simple that they can be implemented as macros
** These are given first. */
/* Return the number of entries in the array */
#define ArrayCount(X) ((X)->count)
/* Return a pointer to the first element of the array */
#define ArrayFirst(X) ((X)->first)
/* Return a pointer to the next (or previous) element of the array */
#define ArrayNext(X) ((X)->next)
#define ArrayPrev(X) ((X)->prev)
/* Return TRUE if the element given is the last element in the array */
#define ArrayIsLast(X) ((X)->next==0)
#define ArrayIsFirst(X) ((X)->prev==0)
/* Return the data or key for an element of the array */
#define ArrayData(X) ((X)->data.p)
#define ArrayDataSize(X) ((X)->data.n)
#define ArrayKey(X) ((X)->key.p)
#define ArrayKeySize(X) ((X)->key.n)
/* Turn bulk memory into an associative array object by initializing the
** fields of the Array structure.
*/
static void ArrayInit(Array *new){
new->first = 0;
new->count = 0;
new->htsize = 0;
new->ht = 0;
}
/* Remove all entries from an associative array. Reclaim all memory.
** This is the opposite of ArrayInit().
*/
static void ArrayClear(Array *array){
ArrayElem *elem; /* For looping over all elements of the array */
elem = array->first;
array->first = 0;
array->count = 0;
if( array->ht ) sqliteFree(array->ht);
array->ht = 0;
array->htsize = 0;
while( elem ){
ArrayElem *next_elem = elem->next;
sqliteFree(elem);
elem = next_elem;
}
}
/*
** Generate a hash from an N-byte key
*/
static int ArrayHash(Datum d){
int h = 0;
while( d.n-- > 0 ){
/* The funky case "*(char**)&d.p" is to work around a bug the
** c89 compiler of HPUX. */
h = (h<<9) ^ (h<<3) ^ h ^ *((*(char**)&d.p)++);
}
if( h<0 ) h = -h;
return h;
}
/* Resize the hash table for a Array array
*/
static void ArrayRehash(Array *array, int new_size){
struct _Array_ht *new_ht; /* The new hash table */
ArrayElem *elem, *next_elem; /* For looping over existing elements */
int i; /* Loop counter */
ArrayElem *x; /* Element being copied to new hash table */
new_ht = sqliteMalloc( new_size*sizeof(struct _Array_ht) );
if( new_ht==0 ){ ArrayClear(array); return; }
if( array->ht ) sqliteFree(array->ht);
array->ht = new_ht;
array->htsize = new_size;
for(i=new_size-1; i>=0; i--){
new_ht[i].count = 0;
new_ht[i].chain = 0;
}
for(elem=array->first, array->first=0; elem; elem = next_elem){
int h = ArrayHash(elem->key) & (new_size-1);
next_elem = elem->next;
x = new_ht[h].chain;
if( x ){
elem->next = x;
elem->prev = x->prev;
if( x->prev ) x->prev->next = elem;
else array->first = elem;
x->prev = elem;
}else{
elem->next = array->first;
if( array->first ) array->first->prev = elem;
elem->prev = 0;
array->first = elem;
}
new_ht[h].chain = elem;
new_ht[h].count++;
}
}
/* This function (for internal use only) locates an element in an
** array that matches the given key. The hash for this key has
** already been computed and is passed as the 3rd parameter.
*/
static ArrayElem *ArrayFindElementGivenHash(
const Array *array, /* The array to be searched */
const Datum key, /* The key we are searching for */
int h /* The hash for this key. */
){
ArrayElem *elem; /* Used to loop thru the element list */
int count; /* Number of elements left to test */
if( array->count ){
elem = array->ht[h].chain;
count = array->ht[h].count;
while( count-- && elem ){
if( elem->key.n==key.n && memcmp(elem->key.p,key.p,key.n)==0 ){
return elem;
}
elem = elem->next;
}
}
return 0;
}
/* Attempt to locate an element of the associative array with a key
** that matches "key". Return the ArrayElement if found and NULL if
** if no match.
*/
static ArrayElem *ArrayFindElement(const Array *array, Datum key){
int h; /* A hash on key */
if( array->count==0 ) return 0;
h = ArrayHash(key);
return ArrayFindElementGivenHash(array, key, h & (array->htsize-1));
}
/* Remove a single entry from the array given a pointer to that
** element and a hash on the element's key.
*/
static void ArrayRemoveElementGivenHash(
Array *array, /* The array containing "elem" */
ArrayElem* elem, /* The element to be removed from the array */
int h /* Hash value for the element */
){
if( elem->prev ){
elem->prev->next = elem->next;
}else{
array->first = elem->next;
}
if( elem->next ){
elem->next->prev = elem->prev;
}
if( array->ht[h].chain==elem ){
array->ht[h].chain = elem->next;
}
array->ht[h].count--;
if( array->ht[h].count<=0 ){
array->ht[h].chain = 0;
}
sqliteFree( elem );
array->count--;
}
/* Attempt to locate an element of the associative array with a key
** that matches "key". Return the data for this element if it is
** found, or NULL if no match is found.
*/
static Datum ArrayFind(const Array *array, Datum key){
int h; /* A hash on key */
ArrayElem *elem; /* The element that matches key */
static Datum nil = {0, 0};
if( array->count==0 ) return nil;
h = ArrayHash(key);
elem = ArrayFindElementGivenHash(array, key, h & (array->htsize-1));
return elem ? elem->data : nil;
}
/* Insert an element into the array. The key will be "key" and
** the data will be "data".
**
** If no array element exists with a matching key, then a new
** array element is created. The key is copied into the new element.
** But only a pointer to the data is stored. NULL is returned.
**
** If another element already exists with the same key, then the
** new data replaces the old data and the old data is returned.
** The key is not copied in this instance.
**
** If the "data" parameter to this function is NULL, then the
** element corresponding to "key" is removed from the array.
*/
static Datum ArrayInsert(Array *array, Datum key, Datum data){
int hraw; /* Raw hash value of the key */
int h; /* the hash of the key modulo hash table size */
ArrayElem *elem; /* Used to loop thru the element list */
ArrayElem *new_elem; /* New element added to the array */
Datum rv; /* Return value */
static Datum nil = {0, 0};
hraw = ArrayHash(key);
h = hraw & (array->htsize-1);
elem = ArrayFindElementGivenHash(array,key,h);
if( elem ){
Datum old_data = elem->data;
if( data.p==0 ){
ArrayRemoveElementGivenHash(array,elem,h);
}else{
elem->data = data;
}
return old_data;
}
if( data.p==0 ) return nil;
new_elem = (ArrayElem*)sqliteMalloc( sizeof(ArrayElem) + key.n );
if( new_elem==0 ) return nil;
new_elem->key.n = key.n;
new_elem->key.p = (void*)&new_elem[1];
memcpy(new_elem->key.p, key.p, key.n);
array->count++;
if( array->htsize==0 ) ArrayRehash(array,4);
if( array->htsize==0 ) return nil;
if( array->count > array->htsize ){
ArrayRehash(array,array->htsize*2);
if( array->htsize==0 ){
sqliteFree(new_elem);
return nil;
}
}
h = hraw & (array->htsize-1);
elem = array->ht[h].chain;
if( elem ){
new_elem->next = elem;
new_elem->prev = elem->prev;
if( elem->prev ){ elem->prev->next = new_elem; }
else { array->first = new_elem; }
elem->prev = new_elem;
}else{
new_elem->next = array->first;
new_elem->prev = 0;
if( array->first ){ array->first->prev = new_elem; }
array->first = new_elem;
}
array->ht[h].count++;
array->ht[h].chain = new_elem;
new_elem->data = data;
rv.p = 0;
rv.n = 0;
return rv;
}
/*
** Information about each open database table is an instance of this
** structure. There will only be one such structure for each
** table. If the VDBE opens the same table twice (as will happen
** for a self-join, for example) then two DbbeCursor structures are
** created but there is only a single MTable structure.
*/
typedef struct MTable MTable;
struct MTable {
char *zName; /* Name of the table */
int delOnClose; /* Delete when closing */
int intKeyOnly; /* Use only integer keys on this table */
Array data; /* The data in this stable */
};
/*
** The following structure contains all information used by GDBM
** database driver. This is a subclass of the Dbbe structure.
*/
typedef struct Dbbex Dbbex;
struct Dbbex {
Dbbe dbbe; /* The base class */
Array tables; /* All tables of the database */
};
/*
** An cursor into a database file is an instance of the following structure.
** There can only be a single MTable structure for each disk file, but
** there can be multiple DbbeCursor structures. Each DbbeCursor represents
** a cursor pointing to a particular part of the open MTable. The
** MTable.nRef field hold a count of the number of DbbeCursor structures
** associated with the same disk file.
*/
struct DbbeCursor {
Dbbex *pBe; /* The database of which this record is a part */
MTable *pTble; /* The database file for this table */
ArrayElem *elem; /* Most recently accessed record */
int needRewind; /* Next key should be the first */
};
/*
** Forward declaration
*/
static void sqliteMemCloseCursor(DbbeCursor *pCursr);
/*
** Erase all the memory of an MTable
*/
static void deleteMTable(MTable *p){
ArrayElem *i;
for(i=ArrayFirst(&p->data); i; i=ArrayNext(i)){
void *data = ArrayData(i);
sqliteFree(data);
}
ArrayClear(&p->data);
sqliteFree(p->zName);
sqliteFree(p);
}
/*
** Completely shutdown the given database. Close all files. Free all memory.
*/
static void sqliteMemClose(Dbbe *pDbbe){
Dbbex *pBe = (Dbbex*)pDbbe;
MTable *pTble;
ArrayElem *j;
for(j=ArrayFirst(&pBe->tables); j; j=ArrayNext(j)){
pTble = ArrayData(j);
deleteMTable(pTble);
}
ArrayClear(&pBe->tables);
memset(pBe, 0, sizeof(*pBe));
sqliteFree(pBe);
}
/*
** Translate the name of an SQL table (or index) into its
** canonical name.
**
** Space to hold the canonical name is obtained from
** sqliteMalloc() and must be freed by the calling function.
*/
static char *sqliteNameOfTable(const char *zTable){
char *zNew = 0;
int i, c;
sqliteSetString(&zNew, zTable, 0);
if( zNew==0 ) return 0;
for(i=0; (c = zNew[i])!=0; i++){
if( isupper(c) ){
zNew[i] = tolower(c);
}
}
return zNew;
}
/*
** Open a new table cursor. Write a pointer to the corresponding
** DbbeCursor structure into *ppCursr. Return an integer success
** code:
**
** SQLITE_OK It worked!
**
** SQLITE_NOMEM sqliteMalloc() failed
**
** SQLITE_PERM Attempt to access a file for which file
** access permission is denied
**
** SQLITE_BUSY Another thread or process is already using
** the corresponding file and has that file locked.
**
** SQLITE_READONLY The current thread already has this file open
** readonly but you are trying to open for writing.
** (This can happen if a SELECT callback tries to
** do an UPDATE or DELETE.)
**
** If zTable is 0 or "", then a temporary database file is created and
** a cursor to that temporary file is opened. The temporary file
** will be deleted from the disk when it is closed.
*/
static int sqliteMemOpenCursor(
Dbbe *pDbbe, /* The database the table belongs to */
const char *zTable, /* The SQL name of the file to be opened */
int writeable, /* True to open for writing */
int intKeyOnly, /* True if only integer keys are used */
DbbeCursor **ppCursr /* Write the resulting table pointer here */
){
DbbeCursor *pCursr; /* The new table cursor */
char *zName; /* Canonical table name */
MTable *pTble; /* The underlying data file for this table */
int rc = SQLITE_OK; /* Return value */
Dbbex *pBe = (Dbbex*)pDbbe;
*ppCursr = 0;
pCursr = sqliteMalloc( sizeof(*pCursr) );
if( pCursr==0 ) return SQLITE_NOMEM;
if( zTable ){
Datum key;
zName = sqliteNameOfTable(zTable);
if( zName==0 ) return SQLITE_NOMEM;
key.p = zName;
key.n = strlen(zName);
pTble = ArrayFind(&pBe->tables, key).p;
}else{
zName = 0;
pTble = 0;
}
if( pTble==0 ){
pTble = sqliteMalloc( sizeof(*pTble) );
if( pTble==0 ){
sqliteFree(zName);
return SQLITE_NOMEM;
}
if( zName ){
Datum ins_key, ins_data;
pTble->zName = zName;
pTble->delOnClose = 0;
ins_data.p = pTble;
ins_data.n = sizeof( *pTble );
ins_key.p = zName;
ins_key.n = strlen(zName);
ArrayInsert(&pBe->tables, ins_key, ins_data);
}else{
pTble->zName = 0;
pTble->delOnClose = 1;
}
pTble->intKeyOnly = intKeyOnly;
ArrayInit(&pTble->data);
}else{
assert( pTble->intKeyOnly==intKeyOnly );
sqliteFree(zName);
}
pCursr->pBe = pBe;
pCursr->pTble = pTble;
pCursr->needRewind = 1;
*ppCursr = pCursr;
return rc;
}
/*
** Drop a table from the database. The file on the disk that corresponds
** to this table is deleted.
*/
static void sqliteMemDropTable(Dbbe *pDbbe, const char *zTable){
char *zName; /* Name of the table file */
Datum key, data;
MTable *pTble;
Dbbex *pBe = (Dbbex*)pDbbe;
zName = sqliteNameOfTable(zTable);
key.p = zName;
key.n = strlen(zName);
pTble = ArrayFind(&pBe->tables, key).p;
if( pTble ){
data.p = 0;
data.n = 0;
ArrayInsert(&pBe->tables, key, data);
deleteMTable(pTble);
}
sqliteFree(zName);
}
/*
** Close a cursor previously opened by sqliteMemOpenCursor().
**
** There can be multiple cursors pointing to the same open file.
** The underlying file is not closed until all cursors have been
** closed. This routine decrements the MTable.nref field of the
** underlying file and closes the file when nref reaches 0.
*/
static void sqliteMemCloseCursor(DbbeCursor *pCursr){
MTable *pTble;
Dbbex *pBe;
if( pCursr==0 ) return;
pTble = pCursr->pTble;
pBe = pCursr->pBe;
if( pTble->delOnClose ){
deleteMTable(pTble);
}
sqliteFree(pCursr);
}
/*
** Reorganize a table to reduce search times and disk usage.
*/
static int sqliteMemReorganizeTable(Dbbe *pBe, const char *zTable){
/* Do nothing */
return SQLITE_OK;
}
/*
** Fetch a single record from an open cursor. Return 1 on success
** and 0 on failure.
*/
static int sqliteMemFetch(DbbeCursor *pCursr, int nKey, char *pKey){
Datum key;
key.n = nKey;
key.p = pKey;
assert( nKey==4 || pCursr->pTble->intKeyOnly==0 );
pCursr->elem = ArrayFindElement(&pCursr->pTble->data, key);
return pCursr->elem!=0;
}
/*
** Return 1 if the given key is already in the table. Return 0
** if it is not.
*/
static int sqliteMemTest(DbbeCursor *pCursr, int nKey, char *pKey){
return sqliteMemFetch(pCursr, nKey, pKey);
}
/*
** Copy bytes from the current key or data into a buffer supplied by
** the calling function. Return the number of bytes copied.
*/
static
int sqliteMemCopyKey(DbbeCursor *pCursr, int offset, int size, char *zBuf){
int n;
if( pCursr->elem==0 ) return 0;
if( offset>=ArrayKeySize(pCursr->elem) ) return 0;
if( offset+size>ArrayKeySize(pCursr->elem) ){
n = ArrayKeySize(pCursr->elem) - offset;
}else{
n = size;
}
memcpy(zBuf, &((char*)ArrayKey(pCursr->elem))[offset], n);
return n;
}
static
int sqliteMemCopyData(DbbeCursor *pCursr, int offset, int size, char *zBuf){
int n;
if( pCursr->elem==0 ) return 0;
if( offset>=ArrayDataSize(pCursr->elem) ) return 0;
if( offset+size>ArrayDataSize(pCursr->elem) ){
n = ArrayDataSize(pCursr->elem) - offset;
}else{
n = size;
}
memcpy(zBuf, &((char*)ArrayData(pCursr->elem))[offset], n);
return n;
}
/*
** Return a pointer to bytes from the key or data. The data returned
** is ephemeral.
*/
static char *sqliteMemReadKey(DbbeCursor *pCursr, int offset){
if( pCursr->elem==0 || offset<0 || offset>=ArrayKeySize(pCursr->elem) ){
return "";
}
return &((char*)ArrayKey(pCursr->elem))[offset];
}
static char *sqliteMemReadData(DbbeCursor *pCursr, int offset){
if( pCursr->elem==0 || offset<0 || offset>=ArrayDataSize(pCursr->elem) ){
return "";
}
return &((char*)ArrayData(pCursr->elem))[offset];
}
/*
** Return the total number of bytes in either data or key.
*/
static int sqliteMemKeyLength(DbbeCursor *pCursr){
return pCursr->elem ? ArrayKeySize(pCursr->elem) : 0;
}
static int sqliteMemDataLength(DbbeCursor *pCursr){
return pCursr->elem ? ArrayDataSize(pCursr->elem) : 0;
}
/*
** Make is so that the next call to sqliteNextKey() finds the first
** key of the table.
*/
static int sqliteMemRewind(DbbeCursor *pCursr){
pCursr->needRewind = 1;
return SQLITE_OK;
}
/*
** Read the next key from the table. Return 1 on success. Return
** 0 if there are no more keys.
*/
static int sqliteMemNextKey(DbbeCursor *pCursr){
if( pCursr->needRewind || pCursr->elem==0 ){
pCursr->elem = ArrayFirst(&pCursr->pTble->data);
pCursr->needRewind = 0;
}else{
pCursr->elem = ArrayNext(pCursr->elem);
}
return pCursr->elem!=0;
}
/*
** Get a new integer key.
*/
static int sqliteMemNew(DbbeCursor *pCursr){
int iKey;
Datum key;
int go = 1;
while( go ){
iKey = sqliteRandomInteger() & 0x7fffffff;
if( iKey==0 ) continue;
key.p = (char*)&iKey;
key.n = 4;
go = ArrayFindElement(&pCursr->pTble->data, key)!=0;
}
return iKey;
}
/*
** Write an entry into the table. Overwrite any prior entry with the
** same key.
*/
static int sqliteMemPut(
DbbeCursor *pCursr, /* Write new entry into this database table */
int nKey, char *pKey, /* The key of the new entry */
int nData, char *pData /* The data of the new entry */
){
Datum data, key;
data.n = nData;
data.p = sqliteMalloc( data.n );
if( data.p==0 ) return SQLITE_NOMEM;
memcpy(data.p, pData, data.n);
key.n = nKey;
key.p = pKey;
assert( nKey==4 || pCursr->pTble->intKeyOnly==0 );
data = ArrayInsert(&pCursr->pTble->data, key, data);
if( data.p ){
sqliteFree(data.p);
}
return SQLITE_OK;
}
/*
** Remove an entry from a table, if the entry exists.
*/
static int sqliteMemDelete(DbbeCursor *pCursr, int nKey, char *pKey){
Datum key, data;
key.n = nKey;
key.p = pKey;
data.p = 0;
data.n = 0;
data = ArrayInsert(&pCursr->pTble->data, key, data);
if( data.p ){
sqliteFree(data.p);
}
return SQLITE_OK;
}
/*
** This variable contains pointers to all of the access methods
** used to implement the MEMORY backend.
*/
static struct DbbeMethods memoryMethods = {
/* Close */ sqliteMemClose,
/* OpenCursor */ sqliteMemOpenCursor,
/* DropTable */ sqliteMemDropTable,
/* ReorganizeTable */ sqliteMemReorganizeTable,
/* CloseCursor */ sqliteMemCloseCursor,
/* Fetch */ sqliteMemFetch,
/* Test */ sqliteMemTest,
/* CopyKey */ sqliteMemCopyKey,
/* CopyData */ sqliteMemCopyData,
/* ReadKey */ sqliteMemReadKey,
/* ReadData */ sqliteMemReadData,
/* KeyLength */ sqliteMemKeyLength,
/* DataLength */ sqliteMemDataLength,
/* NextKey */ sqliteMemNextKey,
/* Rewind */ sqliteMemRewind,
/* New */ sqliteMemNew,
/* Put */ sqliteMemPut,
/* Delete */ sqliteMemDelete,
};
/*
** This routine opens a new database. For the GDBM driver
** implemented here, the database name is the name of the directory
** containing all the files of the database.
**
** If successful, a pointer to the Dbbe structure is returned.
** If there are errors, an appropriate error message is left
** in *pzErrMsg and NULL is returned.
*/
Dbbe *sqliteMemOpen(
const char *zName, /* The name of the database */
int writeFlag, /* True if we will be writing to the database */
int createFlag, /* True to create database if it doesn't exist */
char **pzErrMsg /* Write error messages (if any) here */
){
Dbbex *pNew;
pNew = sqliteMalloc( sizeof(*pNew) );
if( pNew==0 ){
sqliteSetString(pzErrMsg, "out of memory", 0);
return 0;
}
ArrayInit(&pNew->tables);
pNew->dbbe.x = &memoryMethods;
return &pNew->dbbe;
}