/* * Copyright (c) 1995, 1996 Gunther Schadow. All rights reserved. * * 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 program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "pg_config.h" IDENT("@(#) odbm.cc (Gunther Schadow) 8/9/95") #pragma implementation "odbm.h" #include "odbm.h" #include #include #include #include /* * Single object access constructor */ odbm::odbm(char **k, char **str, void *scl, size_t scl_size, const char *dbfn, int mode, int cs, int bs) { keyp = k; strings = str; if(str == NULL) nostrings = 0; else nostrings = (char **)scl - str; scalars.dptr = (char *)scl; scalars.dsize = scl_size; object_cnt = NULL; dbfp = new GDBM_FILE; reopen.dbfn = strdup(dbfn); reopen.mode = mode; reopen.cachesize = cs; reopen.blocksize = bs; *dbfp = gdbm_open(reopen.dbfn, bs, mode, 0666, NULL); if(*dbfp == NULL) ERROS("gdbm_open: %s", gdbm_strerror(gdbm_errno)); else if(cs != default_cachesize) if(gdbm_setopt(*dbfp, GDBM_CACHESIZE, &cs, sizeof(cs)) == FAIL) ERROS("gdbm_setopt: %s", gdbm_strerror(gdbm_errno)); // NULLify the strings for(int i = 0; i < nostrings; i++) strings[i] = NULL; } /* * Multi objects access constructor */ odbm::odbm(char **k, char **str, void *scl, size_t scl_size, odbm_file *dbf, unsigned int *obj_cnt, const char *dbfn, int mode, int cs, int bs) { keyp = k; strings = str; if(str == NULL) nostrings = 0; else nostrings = (char **)scl - str; scalars.dptr = (char *)scl; scalars.dsize = scl_size; reopen.dbfn = strdup(dbfn); reopen.mode = mode; reopen.cachesize = cs; reopen.blocksize = bs; dbfp = dbf; object_cnt = obj_cnt; if((*obj_cnt)++ == 0) { *dbfp = gdbm_open(reopen.dbfn, bs, mode, 0666, NULL); if(*dbfp == NULL) ERROS("gdbm_open: %s", gdbm_strerror(gdbm_errno)); else if(cs != default_cachesize) if(gdbm_setopt(*dbfp, GDBM_CACHESIZE, &cs, sizeof(cs)) == FAIL) ERROS("gdbm_setopt: %s", gdbm_strerror(gdbm_errno)); } key.dptr = NULL; data.dptr = NULL; // NULLify the strings for(int i = 0; i < nostrings; i++) strings[i] = NULL; } /* * destructor */ odbm::~odbm() { if(*keyp != &key.dptr[1] && *keyp != NULL) delete [] *keyp; if(key.dptr != NULL) delete [] key.dptr; for(int i = 0; i < nostrings; i++) { if(strings[i] != NULL && !(data.dptr <= strings[i] && strings[i] < data.dptr + data.dsize)) delete [] strings[i]; } if(data.dptr != NULL) delete [] data.dptr; if(object_cnt != NULL) { if(--*object_cnt == 0) gdbm_close(*dbfp); } else { gdbm_close(*dbfp); delete dbfp; } delete [] reopen.dbfn; } /* * free */ void odbm::free(void *p) { if(p == NULL) return; else if(p == &key.dptr[1]) return; if((data.dptr <= (char *)p) && ((char *)p < (data.dptr + data.dsize))) return; else ::free(p); } void odbm::free() { free(*keyp); for(int i = 0; i < nostrings; i++) { free(strings[i]); strings[i] = NULL; } } /* * Copy constructor and assignment operator */ odbm::odbm(const odbm &x) { // propagate gdbm file handle WARNING: copying a writer will fail! reopen = x.reopen; reopen.dbfn = strdup(reopen.dbfn); dbfp = x.dbfp; object_cnt = x.object_cnt; if(object_cnt == NULL) { // x is in single object access mode *dbfp = gdbm_open(reopen.dbfn, reopen.blocksize, reopen.mode, 0666, NULL); if(*dbfp == NULL) ERROS("gdbm_open: %s", gdbm_strerror(gdbm_errno)); else if(reopen.cachesize != default_cachesize) if(gdbm_setopt(*dbfp, GDBM_CACHESIZE, &reopen.cachesize, sizeof(reopen.cachesize)) == FAIL) ERROS("gdbm_setopt: %s", gdbm_strerror(gdbm_errno)); } else // x is in multi objects access mode (*object_cnt)++; nostrings = x.nostrings; indices = x.indices; // map references to derived object int diff = ((char *)this) - ((char *)&x); keyp = (char **)(((char *)x.keyp) + diff); strings = (char **)(((char *)x.strings) + diff); scalars.dsize = x.scalars.dsize; scalars.dptr = x.scalars.dptr + diff; // put all data of x into the block x.update_key(); x.build_data(); // copy and set the key key = x.key; key.dptr = strdup(key.dptr); *keyp = (char *)parse_key(); // copy and set the data data.dsize = x.data.dsize; data.dptr = new char [data.dsize]; memcpy(data.dptr, x.data.dptr, data.dsize); parse_data(); } /* * since the assignment operator is called only for a valid object, * the work here is rather simple */ odbm &odbm::operator = (const odbm &x) { // delete old key and strings if necessary if(*keyp != &key.dptr[1] && *keyp != NULL) delete [] *keyp; if(key.dptr != NULL) delete [] key.dptr; for(int i = 0; i < nostrings; i++) { if(strings[i] != NULL && !(data.dptr <= strings[i] && strings[i] < data.dptr + data.dsize)) delete [] strings[i]; } if(data.dptr != NULL) delete [] data.dptr; // put all data of x into the block x.update_key(); x.build_data(); // copy and set the key key = x.key; key.dptr = strdup(key.dptr); *keyp = (char *)parse_key(); // copy and set the data data.dsize = x.data.dsize; data.dptr = new char [data.dsize]; memcpy(data.dptr, x.data.dptr, data.dsize); parse_data(); return *this; } /******************************************************************* * Data Block * */ /* The data block is has the following layout: * * 1. Scalar Section -- the fixed size scalars, directly followed by the * 2. String Section -- the fix number of null terminated strings */ /* * build a data block: * This method is "const" even though it modifies the data datum. * It is still a problem that NULLs are mapped to empty strings. */ void odbm::build_data() const { // force the "const" errors to shut up odbm *that = (odbm *)this; // check if a new data block is needed register int i; if(data.dptr != NULL) { register bool all_strings_in_block = TRUE; register char *dptr = data.dptr; register char *eptr = data.dptr + data.dsize; for(i = 0; i < nostrings; i++) { all_strings_in_block = all_strings_in_block && (dptr <= strings[i] && strings[i] < eptr); } // is is assumed that the last string does not exceed the string block if(all_strings_in_block) // do not build new block, { // but copy the scalars memcpy(that->data.dptr, scalars.dptr, scalars.dsize); return; // and we are ready! } } // determine length of new data block to allocate register size_t size = scalars.dsize; for(i = 0; i < nostrings; i++) { if(strings[i] != NULL) size += strlen(strings[i]); else size++; // NULLs are mapped to "" strings, see below ... size++; } // remember the old data block char *old_data = that->data.dptr; // allocate a new data block that->data.dptr = new char[size]; that->data.dsize = size; // copy the scalar data memcpy(that->data.dptr, scalars.dptr, scalars.dsize); // copy the string data char *p = &that->data.dptr[scalars.dsize]; for(i = 0; i < nostrings; i++) { if(strings[i] != NULL) for(char *q = strings[i]; *q != '\0'; q++) *p++ = *q; *p++ = '\0'; // put a "" in place of a NULL anyway } // delete the old data block if(old_data != NULL) delete [] old_data; } /* * parse a data block: * the data block is immediately deleted after the scalars and strings * are copyied out of it. * * THIS IS WRONG: ``Unfortunately we cannot just leave the strings * where they are, because we still have to delete the strings separately, * which would result in ``general havoc'' (see free(3)).'' * * No! This is not true: If we do not allow the strings to be deleted nor * modified (appended) in place (const char*), then we can still keep * them together as a block. Thus, the user of odbm does have to care * about the strings only if he/she copies them out but not when he just * reads them from an odbm output! */ void odbm::parse_data() { // copy the scalar data memcpy(scalars.dptr, data.dptr, scalars.dsize); // copy the strings register char *p = &data.dptr[scalars.dsize]; int i; for(i = 0; i < nostrings - 1; i++) { strings[i] = p; while(*p++ != '\0'); // go to end of string } strings[i] = p; } /******************************************************************* * Key Block * */ /* The key block has the following layout: * * 1. Tag (one byte) -- identifies the key as a tuple(0) or index(>0) entry * 2. Key (string) -- the key or index value */ datum odbm::build_key(const char *k) const return r { r.dsize = 1 + strlen(k) + 1; r.dptr = new char [r.dsize + 1]; // always leave one extra byte for the index r.dptr[0] = 0; strcpy(&r.dptr[1], k); return r; } /* * This is used to update the actual key in the odbm object */ void odbm::update_key() const { if(*keyp != &key.dptr[1]) //if the key is outdated { odbm *that = (odbm *)this; // force the const warnings to be silent // delete it and build a new key if(key.dptr != NULL) delete [] that->key.dptr; datum new_key = build_key(*keyp); that->key = new_key; *that->keyp = (char *)parse_key(); } } /****************************************************************** * Index Facility * * an index entry is a special key/data pair, whose key is the value of * the index variable in a tuple and whose data is the key of the same * tuple. * the key of the index entry has the following layout: * * 1. Tag (one byte) -- identifies the key as an index(>0) entry * 2. Counter (u_int) -- used to create unique keys * 3. Value (string) -- the index value * * The value of the Tag byte is the ordinal number of the corresponding * string, i.e. the index to the strings[] array is this value decreased * by 1. * * The value of the index is the key of the data entry appended with one * byte, that tells the index manager whether it is an aoutomatically * created index (index_on()) or a manually created index (index()). * The values are: 0 for automatic and 1 for manual. In order to faciliate * the use of the index type byte, the function build_key() allocates it * already. * * The next free counter for a partiular index value is stored in an index * counter entry in the database. This index counter entry is a key/value * pair whose key is the index key with a counter of (u_int)-1 and * whose value contains an unsigned integer. */ /* * set an index on a variable */ void odbm::index_on(char * const *variable) const { odbm *that = (odbm *)this; that->indices.set(stringno(variable) - 1); } /* * find the ordinal number of a string (for building the index tag) * 0 is the key * > 0 is an index * -1 is neither key nor index (i.e. can not be found in the database) */ int odbm::stringno(const char *x) const { if(x == *keyp) return 0; else for(int i = 0; i < nostrings; i++) if(x == strings[i]) return i + 1; return -1; } int odbm::stringno(const char * const *x) const { int diff; if((char **)x == keyp) return 0; else { diff = x - strings; if(0 <= diff && diff < nostrings) return diff + 1; else return -1; } } #define AUTO_IDX ((char)0) #define MANU_IDX ((char)1) /* * insert the automatic indices for a record * datum *k points to the actual key */ void odbm::insert_indices() const { for(int i = indices.first(); i >= 0; i = indices.next(i)) { char *idx = strings[i]; u_int index_cnt = 0; datum index_key; datum index_cnt_value; if(idx != NULL) index_key.dsize = 1 + sizeof(u_int) + strlen(idx) + 1; else index_key.dsize = 1 + sizeof(u_int) + 1; index_key.dptr = (char *)alloca(index_key.dsize); index_key.dptr[0] = (char)(i + 1); if(idx != NULL) strcpy(&index_key.dptr[1 + sizeof(u_int)], idx); else index_key.dptr[1 + sizeof(u_int)] = 0; // index counter entry { u_int numbuf = (u_int)-1; memcpy(&index_key.dptr[1], &numbuf, sizeof(u_int)); } index_cnt_value = gdbm_fetch(*dbfp, index_key); if(index_cnt_value.dptr == NULL) { index_cnt_value.dsize = sizeof(u_int); index_cnt_value.dptr = (char *)alloca(sizeof(u_int)); } else index_cnt = *(u_int*)index_cnt_value.dptr; *(u_int*)index_cnt_value.dptr = index_cnt + 1; if(gdbm_store(*dbfp, index_key, index_cnt_value, GDBM_REPLACE) != SUCCESS) ERROS("gdbm_store:index_cnt %d`%s': %s", index_cnt, idx, gdbm_strerror(gdbm_errno)); // index entry memcpy(&index_key.dptr[1], &index_cnt, sizeof(u_int)); datum &the_key = *(datum*)&key; // make the key writable the_key.dptr[the_key.dsize++] = AUTO_IDX; // the index type if(gdbm_store(*dbfp, index_key, the_key, GDBM_INSERT) != SUCCESS) ERROS("gdbm_store:index %d`%s': %s", index_cnt, idx, gdbm_strerror(gdbm_errno)); the_key.dsize--; // restore the size of the key } } /* * Dereferenciate an index and return the data record. */ datum odbm::deref_index(int stringno, const char *idx, u_int *cursor, datum *key) const { if(key == NULL) key = (datum *)alloca(sizeof(datum)); datum index_key, data; if(idx != NULL) index_key.dsize = 1 + sizeof(u_int) + strlen(idx) + 1; else index_key.dsize = 1 + sizeof(u_int) + 1; index_key.dptr = (char *)alloca(index_key.dsize); index_key.dptr[0] = stringno; if(idx != NULL) strcpy(&index_key.dptr[1 + sizeof(u_int)], idx); else index_key.dptr[1 + sizeof(u_int)] = 0; // try the give cursor value memcpy(&index_key.dptr[1], cursor, sizeof(u_int)); *key = gdbm_fetch(*dbfp, index_key); if(key->dptr == NULL) return (datum){NULL, 0}; else { key->dsize--; // restore the size of the key (was: index type byte) data = gdbm_fetch(*dbfp, *key); } if(data.dptr != NULL) return data; // the index was orphaned, now prepare for trying up to max_cursor value // get the index counter entry for max_cursor u_int max_cursor; { u_int numbuf = (u_int)-1; memcpy(&index_key.dptr[1], &numbuf, sizeof(u_int)); } datum index_cnt_value = gdbm_fetch(*dbfp, index_key); if(index_cnt_value.dptr == NULL) return (datum){NULL, 0}; // index does not exist else max_cursor = *(u_int*)index_cnt_value.dptr; // try up to max_cursor value while(++(*cursor) < max_cursor) { memcpy(&index_key.dptr[1], cursor, sizeof(u_int)); *key = gdbm_fetch(*dbfp, index_key); if(key->dptr == NULL) return (datum){NULL, 0}; else { key->dsize--; // restore the size of the key (was: index type byte) data = gdbm_fetch(*dbfp, *key); } if(data.dptr != NULL) return data; } return (datum){NULL, 0}; // failed } /* * Manually set an index to the current record. A manually set index * has the index type byte set to one. This indicates that reindex won't * remove this index entry (it may still change it's cursor counter). */ void odbm::index(char * const *variable, const char *value) const { int strno = stringno(variable); u_int index_cnt = 0; datum index_key; datum index_cnt_value; if(value != NULL) index_key.dsize = 1 + sizeof(u_int) + strlen(value) + 1; else index_key.dsize = 1 + sizeof(u_int) + 1; index_key.dptr = (char *)alloca(index_key.dsize); index_key.dptr[0] = strno; if(value != NULL) strcpy(&index_key.dptr[1 + sizeof(u_int)], value); else index_key.dptr[1 + sizeof(u_int)] = 0; // index counter entry { u_int numbuf = (u_int)-1; memcpy(&index_key.dptr[1], &numbuf, sizeof(u_int)); } index_cnt_value = gdbm_fetch(*dbfp, index_key); if(index_cnt_value.dptr == NULL) { index_cnt_value.dsize = sizeof(u_int); index_cnt_value.dptr = (char *)alloca(sizeof(u_int)); } else index_cnt = *(u_int*)index_cnt_value.dptr; *(u_int*)index_cnt_value.dptr = index_cnt + 1; if(gdbm_store(*dbfp, index_key, index_cnt_value, GDBM_REPLACE) != SUCCESS) ERROS("gdbm_store:index_cnt %d`%s': %s", index_cnt, value, gdbm_strerror(gdbm_errno)); // index entry memcpy(&index_key.dptr[1], &index_cnt, sizeof(u_int)); datum &the_key = *(datum*)&key; // make the key writable the_key.dptr[the_key.dsize++] = MANU_IDX; // the index type if(gdbm_store(*dbfp, index_key, the_key, GDBM_INSERT) != SUCCESS) ERROS("gdbm_store:index %d`%s': %s", index_cnt, value, gdbm_strerror(gdbm_errno)); the_key.dsize--; // restore the size of the key } /*************************************************+ * retrieval methods */ /* * fetch with key found in structure. */ result odbm::fetch() { update_key(); datum new_data = gdbm_fetch(*dbfp, key); if(new_data.dptr == NULL) return FAIL; else { // delete the old data block if(data.dptr != NULL) delete [] data.dptr; // and activate the new one data = new_data; parse_data(); return SUCCESS; } } /* * fetch with supplied key, the key is copied as if it was read from * the database. */ result odbm::fetch(const char *k, u_int *cursor) { datum new_key; datum new_data; switch(int i = stringno(k)) { case 0: // could have called the simpler form return odbm::fetch(); case -1: // make a new key new_key = build_key(k); new_data = gdbm_fetch(*dbfp, new_key); break; default: // dereferenciate an index new_data = deref_index(i, k, cursor, &new_key); if(new_data.dptr == NULL) return FAIL; break; } if(new_data.dptr == NULL) { delete [] new_key.dptr; return FAIL; } else { // only now actualize the key if(key.dptr != NULL) delete [] key.dptr; key = new_key; *keyp = (char *)parse_key(); // delete the old data block if(data.dptr != NULL) delete [] data.dptr; // and activate the new one data = new_data; parse_data(); return SUCCESS; } } /* * fetch with key or index supplied */ result odbm::fetch(char **k, const char *value, u_int *cursor) { datum new_key; datum new_data; switch(int i = stringno(k)) { case 0: // could have called the simpler form return odbm::fetch(value); case -1: // the variable is no key nor string ERROR("variable is no key nor string"); default: // dereferenciate an index new_data = deref_index(i, value, cursor, &new_key); if(new_data.dptr == NULL) return FAIL; break; } if(new_data.dptr == NULL) { delete [] new_key.dptr; return FAIL; } else { // only now actualize the key if(key.dptr != NULL) delete [] key.dptr; key = new_key; *keyp = (char *)parse_key(); // delete the old data block if(data.dptr != NULL) delete [] data.dptr; // and activate the new one data = new_data; parse_data(); return SUCCESS; } } /* * look up the key found in structure */ bool odbm::exists() const { update_key(); return gdbm_exists(*dbfp, key); } /* * look up the supplied key */ bool odbm::exists(const char *k, u_int *cursor) const { datum new_key; bool r; switch(int i = stringno(k)) { case 0: // could have called the simpler form return odbm::exists(); case -1: // make a new key new_key = build_key(k); r = gdbm_exists(*dbfp, new_key); break; default: // dereferenciate an index r = ( deref_index(i, k, cursor, &new_key).dptr != NULL ); break; } delete [] new_key.dptr; return r; } /* * look up the supplied value for key or index */ bool odbm::exists(char **k, const char *value, u_int *cursor) const { datum new_key; bool r; switch(int i = stringno(k)) { case 0: return odbm::exists(value); case -1: // the variable is no key nor string ERROR("variable is no key nor string"); default: // dereferenciate an index r = ( deref_index(i, *k, cursor, &new_key).dptr != NULL ); break; } delete [] new_key.dptr; return r; } /* * fetch the first key in database */ const char * odbm::firstkey() const { odbm *that = (odbm *)this; // force the const warnings to be silent if(that->key.dptr != NULL) delete [] that->key.dptr; that->key = gdbm_firstkey(*dbfp); return parse_key(); } /* * fetch next key in database */ const char * odbm::nextkey() const { odbm *that = (odbm *)this; // force the const warnings to be silent datum lastkey = that->key; that->key = gdbm_nextkey(*dbfp, lastkey); if(lastkey.dptr != NULL) delete [] lastkey.dptr; return parse_key(); } /****************************************************************** * storage methods * */ /* * store key if not yet present */ void odbm::insert() const { update_key(); build_data(); if(gdbm_store(*dbfp, key, data, GDBM_INSERT) != SUCCESS) ERROS("gdbm_store `%s': %s", *keyp, gdbm_strerror(gdbm_errno)); insert_indices(); } /* * store under key found in structure * this does not yet work with indices since these indices become invalid. * One aproach is to find any associated index values and remove them. * However, this is costly since it requires all index entries with the * same value to be checked for the appropriate reference. */ void odbm::update() const { if(!indices.empty()) ERROR("not implemented with indices"); update_key(); build_data(); if(gdbm_store(*dbfp, key, data, GDBM_REPLACE) != SUCCESS) ERROS("gdbm_store `%s': %s", *keyp, gdbm_strerror(gdbm_errno)); } /***************************************************************** * removing methods * * When a record is removed which is referenced by some indices, these * index entries become orphaned but are not removed here. This saves * time. */ /* * remove the supplied key */ void odbm::remove(const char *k) const { datum new_key = build_key(k); if(gdbm_delete(*dbfp, new_key) != SUCCESS) if(gdbm_errno != GDBM_ITEM_NOT_FOUND) ERROS("gdbm_delete `%s': %s", *keyp, gdbm_strerror(gdbm_errno)); } /* * remove the key found in structure */ void odbm::remove() const { update_key(); if(gdbm_delete(*dbfp, key) != SUCCESS) if(gdbm_errno != GDBM_ITEM_NOT_FOUND) ERROS("gdbm_delete: `%s': %s", *keyp, gdbm_strerror(gdbm_errno)); } /****************************************************************** * management methods */ void odbm::reorganize() const { if(gdbm_reorganize(*dbfp) != SUCCESS) ERROS("gdbm_reorganize: %s", gdbm_strerror(gdbm_errno)); } void odbm::fastwrite() const { int fw = TRUE; if(gdbm_setopt(*dbfp, GDBM_FASTMODE, &fw, sizeof(fw)) == FAIL) ERROS("gdbm_setopt: %s", gdbm_strerror(gdbm_errno)); } void odbm::slowwrite() const { int fw = FALSE; if(gdbm_setopt(*dbfp, GDBM_FASTMODE, &fw, sizeof(fw)) == FAIL) ERROS("gdbm_setopt: %s", gdbm_strerror(gdbm_errno)); }