/*- * DBSQL - A SQL database engine. * * Copyright (C) 2007 The DBSQL Group, Inc. - All rights reserved. * * This library 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 3 of the License, or * (at your option) any later version. * * There are special exceptions to the terms and conditions of the GPL as it * is applied to this software. View the full text of the exception in file * LICENSE_EXCEPTIONS in the directory of this software distribution. * * This library 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. * * $Id: sm.c 7 2007-02-03 13:34:17Z gburd $ */ #include "dbsql_config.h" #include "dbsql_int.h" typedef struct { DB *db; char *file; int flags; #define SMR_TYPE_TABLE 0x0001 #define SMR_TYPE_INDEX 0x0002 } sm_rec_t; #define SM_SCHEMA_SIG "__DBSQL_schema_sig__" #define SM_FORMAT_VER "__DBSQL_format_sig__" #define SM_META_NAME "__DBSQL_meta__" /* * __sm_cmp_values -- * */ static int __sm_cmp_values(a, sz_a, b, sz_b) const void *a; size_t sz_a; const void *b; size_t sz_b; { int mcmp = memcmp(a, b, (sz_a <= sz_b) ? sz_a : sz_b); if (mcmp == 0) { if (sz_a == sz_b) return 0; else return ((sz_a < sz_b) ? -1 : 1); } else { return ((mcmp > 0) ? 1 : -1); } } /* * __sm_bt_compare -- * Compare data using the proper comparison function given the type * of data in the records being compared. * * PUBLIC: int __sm_bt_compare __P((DB *, const DBT *, const DBT *)); */ int __sm_bt_compare(db, dbt1, dbt2) DB *db; const DBT *dbt1; const DBT *dbt2; { return __sm_cmp_values(dbt1->data, dbt1->size, dbt2->data, dbt2->size); } /* * __sm_is_threaded -- * Return 1 if the environment was opened with DB_THREAD set, * otherwise 0; */ static int __sm_is_threaded(sm) sm_t *sm; { int flags; return (sm->dbp->flags & DBSQL_Threaded ? 1 : 0); } /* * __sm_next_from_seq -- * Using the dbsql_db_t.n DB_SEQUENCE return the next unused * u_int32_t that is unused. This sequence may wrap (if someone * has a very long lived and active database) which means that we * must make sure that there isn't a database file using this * sequence number already. * !!! If there are more than MAX_UINT32 open tables during the * lifetime of a database then there is the potential that we * will return a sequence number that is in use. * * STATIC: static u_int32_t __sm_next_from_seq __P((sm_t *, DB_TXN *)); */ static u_int32_t __sm_next_from_seq(sm, txn) sm_t *sm; DB_TXN *txn; { int rc; db_seq_t val; if ((rc = sm->n->get(sm->n, txn, 1, &val, 0)) != 0) __dbsql_panic(sm->dbp, rc); return (u_int32_t)val; } /* * __sqldb_seq_init -- * If 'init' is non-zero, don't expect to find __DBSQL__dbi__, * rather create it. 'dbi' is what replaces the concept of rootpage. * This sequence is used to reliabily generate sequential keys starting * with 2 up to max u_int32_t for use when naming DB files. Return a * new sequence or NULL on failure. * * STATIC: static DB_SEQUENCE *__sm_seq_init __P((sm_t *, DB *, DB_TXN *, * STATIC: const char *, int)); */ static DB_SEQUENCE * __sm_seq_init(sm, db, txn, name, init) sm_t *sm; DB *db; DB_TXN *txn; const char *name; int init; { int rc; DBT key; DB_SEQUENCE *seq; char kn[1024]; int flags; DBSQL_ASSERT(db != 0); DBSQL_ASSERT(txn != 0); flags = 0; if ((rc = db_sequence_create(&seq, db, 0)) != 0) return NULL; memset(&key, 0, sizeof(DBT)); if (name != 0) { snprintf(kn, sizeof(kn), "__DBSQL_%s_dbi__", name); } else { snprintf(kn, sizeof(kn), "__DBSQL_dbi__"); } key.data = kn; key.size = strlen(kn); if (__sm_is_threaded(sm)) flags |= DB_THREAD; if (init) { seq->set_flags(seq, DB_SEQ_INC | DB_SEQ_WRAP); seq->set_range(seq, 2, UINT32_MAX); seq->initial_value(seq, 2); flags |= DB_CREATE | DB_EXCL; } if ((rc = seq->open(seq, txn, &key, flags)) != 0) return NULL; return seq; } /* * __sm_meta_init -- * If 'init' is non-zero, don't expect to find __DBSQL__meta__, * rather create it. Return a reference to the meta database within * this 'db' or NULL on failure. * * STATIC: static DB *__sm_meta_init __P((sm_t *, DB_TXN *, const char *, * STATIC: int)); */ static DB * __sm_meta_init(sm, txn, name, init) sm_t *sm; DB_TXN *txn; const char *name; int init; { int rc; int flags = 0; char mn[1024]; DB *db; DBSQL_ASSERT(sm != 0); DBSQL_ASSERT(txn != 0); if (db_create(&db, sm->dbp->dbenv, 0) != 0) return NULL; /* * Open or create a database named __DBSQL__meta__ within * 'db'. */ if (name != 0) snprintf(mn, sizeof(mn), "__DBSQL_%s_meta__", name); if (init) flags |= DB_CREATE | DB_EXCL; if (__sm_is_threaded(sm)) flags |= DB_THREAD; if ((rc = db->open(db, txn, (name ? name : NULL), (name ? mn : NULL), DB_BTREE, flags, 0)) != 0) return NULL; return db; err: db->close(db, 0); return 0; } /* * __sm_init -- * * STATIC: static int __sm_init __P((sm_t *, int *)); */ static int __sm_init(sm, init) sm_t *sm; int *init; { int rc = DBSQL_SUCCESS; int flags = 0; db_seq_t val; u_int32_t n; DB_TXN *txn; DB_ENV *dbenv; DB *db; DBSQL_ASSERT(sm != 0); dbenv = sm->dbp->dbenv; *init = 1; if (F_ISSET(sm, SM_HAS_INIT) != 0) return DBSQL_SUCCESS; if ((rc = db_create(&db, dbenv, 0)) != 0) return DBSQL_CANTOPEN; /* * Try to open the database 'name', if its not there, try to create it. * TODO: when 4.3 supports named in memory files, this will change */ if (dbenv->txn_begin(dbenv, 0, &txn, 0) == ENOMEM) goto err; /* Try to create a new database called 'name'. */ if (__sm_is_threaded(sm)) flags |= DB_THREAD; if ((rc = db->open(db, txn, (F_ISSET(sm, SM_INMEM_DB) ? NULL : sm->name), (F_ISSET(sm, SM_INMEM_DB) ? NULL : SM_META_NAME), DB_BTREE, DB_CREATE | DB_EXCL | flags, 0)) == EEXIST) { /* Try to open the databse, it already exists */ if ((rc = db->open(db, txn, sm->name, SM_META_NAME, DB_BTREE, flags, 0)) == 0) *init = 0; else goto err; } else if (rc != 0) goto err; sm->primary = db; if ((sm->n = __sm_seq_init(sm, db, txn, sm->name, *init)) == 0) goto err; if ((sm->meta = __sm_meta_init(sm, txn, (F_ISSET(sm, SM_INMEM_DB) ? NULL : sm->name), *init)) == 0) goto err; txn->commit(txn, 0); txn = 0; return DBSQL_SUCCESS; err: if (sm->meta) db->close(sm->meta, 0); if (sm->n) sm->n->close(sm->n, 0); db->close(db, 0); if (txn) txn->abort(txn); return DBSQL_CANTOPEN; } /* * __sm_create -- * This routine is called to setup a storage manager for this * database. A storage manager is really context that allows * for the management of a group of Berkeley DB databases used * to represent the contents of a SQL database. A storage manager * may represent a set of datbases in memory or on disk. If the * set 'is_temp' then when the database is close all resources * should be deleted by the storage manager. * * PUBLIC: int __sm_create __P((DBSQL *, const char *, int, int, sm_t **)); * * dbenv DB_ENV that will contain our activity * name Name of the database, null means temporary * is_temp Non-zero if this is a temporary database * in_memory Non-zero if this database should live only in memory * sm OUT: Pointer to new sm_t instance */ int __sm_create(dbp, name, is_temp, in_memory, smp) DBSQL *dbp; const char *name; int is_temp; int in_memory; sm_t **smp; { int id, init, rc; sm_t *sm; char buf[1024]; DBSQL_ASSERT(smp != 0); if (__dbsql_calloc(dbp, 1, sizeof(sm_t), &sm) == ENOMEM) return ENOMEM; if (in_memory && is_temp) { sm->name = 0; F_SET(sm, SM_TEMP_DB | SM_INMEM_DB); } else { if (is_temp) { if (name) snprintf(buf, sizeof(buf), "%s_tmp", name); F_SET(sm, SM_TEMP_DB); } if (in_memory) F_SET(sm, SM_INMEM_DB); if (__dbsql_strdup(dbp, (is_temp ? buf : name), &sm->name) == ENOMEM) { __dbsql_free(dbp, sm); return ENOMEM; } } sm->dbp = dbp; __hash_init(&sm->dbs, DBSQL_HASH_INT, 0); if (__sm_init(sm, &init) != DBSQL_SUCCESS) { __dbsql_free(dbp, sm->name); __dbsql_free(dbp, sm); return DBSQL_CANTOPEN; } /* Create a binary tree for the DBSQL_MASTER table at location 2. */ if (init) { if (__sm_create_table(sm, &id) != DBSQL_SUCCESS) { if (sm->name) __dbsql_free(NULL, sm->name); __dbsql_free(dbp, sm); return DBSQL_CANTOPEN; } } else { id = 2; if (__sm_open_table(sm, &id) != DBSQL_SUCCESS) { if (sm->name) __dbsql_free(NULL, sm->name); __dbsql_free(dbp, sm); return DBSQL_CANTOPEN; } } (*smp) = sm; return DBSQL_SUCCESS; } /* * __sm_close_db -- * Close all managed resources and free all memory used in sm_t. If * this is a temporary database erase all files created as part of * the cleanup. * * PUBLIC: int __sm_close_db __P((sm_t *)); */ int __sm_close_db(sm) sm_t *sm; { int rc; hash_ele_t *p; sm_rec_t *smr; DBSQL_ASSERT(sm != 0); /* MUTEX_THREAD_LOCK(dbp->dbenv, sm->sm_mutexp);*/ sm->n->close(sm->n, 0); sm->meta->close(sm->meta, 0); sm->primary->close(sm->primary, 0); for(p = __hash_first(&sm->dbs); p; p = __hash_next(p)) { smr = (sm_rec_t *)__hash_data(p); if (F_ISSET(sm, SM_TEMP_DB)) { smr->db->close(smr->db, 0); if (F_ISSET(sm, SM_INMEM_DB) == 0) smr->db->remove(smr->db, smr->file, NULL, 0); } else { smr->db->close(smr->db, 0); } __dbsql_free(sm->dbp, smr); } if (sm->name) __dbsql_free(sm->dbp, sm->name); /* MUTEX_THREAD_UNLOCK(dbp->dbenv, sm->sm_mutexp);*/ return DBSQL_SUCCESS; } /* * __sm_checkpoint -- * * PUBLIC: int __sm_checkpoint __P((sm_t *)); */ int __sm_checkpoint(sm) sm_t *sm; { DBSQL_ASSERT(sm != 0); if (sm->dbp->dbenv) sm->dbp->dbenv->txn_checkpoint(sm->dbp->dbenv, 0, 0, 0); return DBSQL_SUCCESS; } /* * __sm_get_database_name -- * Return the name of the database. * * PUBLIC: char *__sm_get_database_name __P((sm_t *)); */ char * __sm_get_database_name(sm) sm_t *sm; { DBSQL_ASSERT(sm != 0); return (sm->name); } /* * __sm_begin_txn -- * * PUBLIC: int __sm_begin_txn __P((sm_t *)); */ int __sm_begin_txn(sm) sm_t *sm; { DBSQL_ASSERT(sm); return (sm->dbp->dbenv->txn_begin(sm->dbp->dbenv, 0, &sm->txn, 0)); } /* * __sm_commit_txn -- * Note that DBSQL->dbenv may be NULL. It might also be opened * without DB_INIT_TXN set. Only in the case when the DBENV exists * and has transactions enabled will there actually be transactional * protection. * * PUBLIC: int __sm_commit_txn __P((sm_t *)); */ int __sm_commit_txn(sm) sm_t *sm; { DBSQL_ASSERT(sm); if (sm->txn) sm->txn->commit(sm->txn, 0); sm->txn = 0; return DBSQL_SUCCESS; } /* * __sm_abort_txn -- * Note that DBSQL->dbenv may be NULL. It might also be opened * without DB_INIT_TXN set. Only in the case when the DBENV exists * and has transactions enabled will there actually be transactional * protection. * * PUBLIC: int __sm_abort_txn __P((sm_t *)); */ int __sm_abort_txn(sm) sm_t *sm; { DBSQL_ASSERT(sm); if (sm->txn) sm->txn->abort(sm->txn); sm->txn = 0; return DBSQL_SUCCESS; } /* * __sm_cursor -- * Create a new cursor for the btree index 'idb'. If 'write' is set * the then cursor is opened DB_WRITECURSOR. If 'write' is not set * it is expected that the cursor is transactionally protected such * that a sequential scan using __sm_cursor_next will consistently * read all values in the database. * * PUBLIC: int __sm_cursor __P((sm_t *, int, int, sm_cursor_t **)); */ int __sm_cursor(sm, id, write, smcp) sm_t *sm; int id; int write; sm_cursor_t **smcp; { int rc; sm_rec_t *smr; sm_cursor_t *smc; DB_ENV *dbenv; DBSQL_ASSERT(sm != 0); DBSQL_ASSERT(smcp != 0); *smcp = 0; dbenv = sm->dbp->dbenv; smr = (sm_rec_t *)__hash_find(&sm->dbs, (const void *)0, id); if (smr == 0) return DBSQL_INTERNAL; if (__dbsql_calloc(sm->dbp, 1, sizeof(sm_cursor_t), &smc) == ENOMEM) return DBSQL_NOMEM; smc->sm = sm; smc->db = smr->db; smc->id = id; if (dbenv->txn_begin(dbenv, sm->txn, &smc->txn, 0) != 0) { __dbsql_free(sm->dbp, smc); return DBSQL_INTERNAL; } if (write) F_SET(smc, SMC_RW_CURSOR); else F_SET(smc, SMC_RO_CURSOR); if (smr->db->cursor(smr->db, smc->txn, &smc->dbc, 0) != 0) { smc->txn->abort(smc->txn); __dbsql_free(sm->dbp, smc); return DBSQL_INTERNAL; } *smcp = smc; return DBSQL_SUCCESS; } /* * __sm_close_cursor -- * If this is a read-only cursor, discard the sm_cursor_t->txn. If * not, it should have been NULL, check that. Close the * sm_cursor_t->dbc and free the sm_cursor_t. * * PUBLIC: int __sm_close_cursor __P((sm_cursor_t *)); */ int __sm_close_cursor(smc) sm_cursor_t *smc; { int rc = DBSQL_SUCCESS; DBSQL_ASSERT(smc != 0); if (smc->dbc->c_close(smc->dbc) == DB_LOCK_DEADLOCK) { smc->txn->abort(smc->txn); rc = DBSQL_INTERNAL; } else smc->txn->commit(smc->txn, 0); __dbsql_free(smc->sm->dbp, smc); return rc; } /* * __sm_moveto -- * Move the cursor to a node near the key to be inserted. If the key * already exists in the table, then (result == 0). In this case we * can just replace the data associated with the entry, we don't need * to manipulate the tree. If there is no exact match, then the cursor * points at what would be either the predecessor (result == -1) or * successor (result == 1) of the searched-for key, were it to be * inserted. The new node becomes a child of this node. * * *result < 0 The cursor is left pointing at an entry that * is smaller than pKey or if the table is empty * and the cursor is therefore left point to nothing. * * *result == 0 The cursor is left pointing at an entry that * exactly matches pKey. * * *result > 0 The cursor is left pointing at an entry that * is larger than pKey. * * PUBLIC: int __sm_moveto __P((sm_cursor_t *, const void *, int, int *)); */ int __sm_moveto(smc, item, len, result) sm_cursor_t *smc; const void *item; int len; int *result; { int rc = DBSQL_SUCCESS; DBT key, data; DBC *dbc; DBSQL_ASSERT(smc); DBSQL_ASSERT(item); DBSQL_ASSERT(len); DBSQL_ASSERT(result); dbc = smc->dbc; memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; data.flags = DB_DBT_REALLOC; key.ulen = len; key.dlen = len; key.size = len; if (__dbsql_umalloc(smc->sm->dbp, len, &key.data) == ENOMEM) return DBSQL_INTERNAL; memcpy(key.data, item, len); switch (dbc->c_get(dbc, &key, &data, DB_SET_RANGE)) { case 0: *result = 0; rc = DBSQL_SUCCESS; break; case DB_NOTFOUND: if (data.data == 0) { /* There was no key >= item. */ switch(dbc->c_get(dbc, &key, &data, DB_LAST)) { case DB_NOTFOUND: /* FALLTHROUGH */ case 0: *result = -1; break; default: rc = DBSQL_INTERNAL; break; } } else { /* There was a key > but != item. */ if (dbc->c_get(dbc, &key, &data, DB_SET) != 0) { rc = DBSQL_INTERNAL; break; } *result = 1; } break; default: rc = DBSQL_INTERNAL; break; } if (key.data) __dbsql_ufree(smc->sm->dbp, key.data); if (data.data) __dbsql_ufree(smc->sm->dbp, data.data); return rc; } /* * __sm_next -- * Advance the cursor to the next entry in the database. If * successful then set *result=0 and return DBSQL_SUCCESS. * If the cursor was already at the last entry in the database, * then set *result=1. * * PUBLIC: int __sm_next __P((sm_cursor_t *, int *)); */ int __sm_next(smc, result) sm_cursor_t *smc; int *result; { int rc = DBSQL_SUCCESS; DBT key, data; DBSQL_ASSERT(smc); DBSQL_ASSERT(result); memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.flags = DB_DBT_MALLOC; data.flags = DB_DBT_MALLOC; switch(smc->dbc->c_get(smc->dbc, &key, &data, DB_NEXT)) { case 0: *result = 0; break; case DB_NOTFOUND: *result = 1; break; default: rc = DBSQL_INTERNAL; /* TODO */ break; } if (key.data) __dbsql_ufree(smc->sm->dbp, key.data); if (data.data) __dbsql_ufree(smc->sm->dbp, data.data); return DBSQL_SUCCESS; } /* * __sm_prev -- * Advance the cursor to the previous entry in the database. If * successful then set *result=0 and return DBSQL_SUCCESS. * If the cursor was already at the first entry in the database, * then set *result=1. * * PUBLIC: int __sm_prev __P((sm_cursor_t *, int *)); */ int __sm_prev(smc, result) sm_cursor_t *smc; int *result; { int rc = DBSQL_SUCCESS; DBT key, data; DBSQL_ASSERT(smc); DBSQL_ASSERT(result); memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.flags = DB_DBT_MALLOC; data.flags = DB_DBT_MALLOC; switch(smc->dbc->c_get(smc->dbc, &key, &data, DB_PREV)) { case 0: *result = 0; break; case DB_NOTFOUND: *result = 1; break; default: rc = DBSQL_INTERNAL; break; } if (key.data) __dbsql_ufree(smc->sm->dbp, key.data); if (data.data) __dbsql_ufree(smc->sm->dbp, data.data); return rc; } /* * __sm_key_size -- * Set *size to the size of the key in bytes of the entry the * cursor currently points to. Always return DBSQL_SUCCESS. * If the cursor is not currently pointing to an entry (which * can happen, for example, if the database is empty) then *size * is set to 0. * * PUBLIC: int __sm_key_size __P((sm_cursor_t *, int *)); */ int __sm_key_size(smc, size) sm_cursor_t *smc; int *size; { int rc = DBSQL_SUCCESS; DBT key, data; DBSQL_ASSERT(smc); DBSQL_ASSERT(size); memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.flags = DB_DBT_MALLOC; data.flags = DB_DBT_MALLOC; switch(smc->dbc->c_get(smc->dbc, &key, &data, DB_CURRENT)) { case 0: *size = key.size; break; case DB_NOTFOUND: *size = 0; break; default: rc = DBSQL_INTERNAL; /* TODO */ break; } if (key.data) __dbsql_ufree(smc->sm->dbp, key.data); if (data.data) __dbsql_ufree(smc->sm->dbp, data.data); return rc; } /* * __sm_data_size -- * Set 'size' to the size of the data in bytes of the entry the * cursor currently points to. Always return DBSQL_SUCCESS. * If the cursor is not currently pointing to an entry (which * can happen, for example, if the database is empty) then 'size' * is set to 0. * * PUBLIC: int __sm_data_size __P((sm_cursor_t *, int *)); */ int __sm_data_size(smc, size) sm_cursor_t *smc; int *size; { int rc = DBSQL_SUCCESS; DBT key, data; DBSQL_ASSERT(smc); DBSQL_ASSERT(size); memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.flags = DB_DBT_MALLOC; data.flags = DB_DBT_MALLOC; switch(smc->dbc->c_get(smc->dbc, &key, &data, DB_CURRENT)) { case 0: *size = data.size; break; case DB_NOTFOUND: *size = 0; break; default: rc = DBSQL_INTERNAL; break; } if (key.data) __dbsql_ufree(smc->sm->dbp, key.data); if (data.data) __dbsql_ufree(smc->sm->dbp, data.data); return rc; } /* * __sm_key_compare -- * Compare 'value[0:len]' against the key[0:smc->key.size - ignore]. * The comparison result is written to *result as follows: * * *result < 0 This means key < value * *result == 0 This means key == value for all len bytes * *result > 0 This means key > value * * When one is an exact prefix of the other, the shorter is * considered less than the longer one. In order to be equal they * must be exactly the same length. (The length of the key * is the actual key length minus ignore bytes.) * * PUBLIC: int __sm_key_compare __P((sm_cursor_t *, const void *, int, * PUBLIC: int, int *)); */ int __sm_key_compare(smc, value, len, ignore, result) sm_cursor_t *smc; const void *value; int len; int ignore; int *result; { int rc = DBSQL_SUCCESS; int nlen; DBT key, data; DBSQL_ASSERT(smc); DBSQL_ASSERT(value); DBSQL_ASSERT(ignore >= 0); memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.flags = DB_DBT_MALLOC; data.flags = DB_DBT_MALLOC; switch(smc->dbc->c_get(smc->dbc, &key, &data, DB_CURRENT)) { case 0: nlen = key.size - ignore; if (nlen < 0) { *result = -1; } else { *result = __sm_cmp_values(key.data, nlen, value, len); break; } default: rc = DBSQL_INTERNAL; break; } if (key.data) __dbsql_ufree(smc->sm->dbp, key.data); if (data.data) __dbsql_ufree(smc->sm->dbp, data.data); return rc; } /* * __sm_key -- * Copy from key.data[offset:offset + amt] into 'value'. * Return amount of bytes copied. * * PUBLIC: size_t __sm_key __P((sm_cursor_t *, size_t, size_t, const void *)); */ size_t __sm_key(smc, offset, len, value) sm_cursor_t *smc; size_t offset; size_t len; const void *value; { size_t amt = 0; DBT key, data; DBSQL_ASSERT(smc); memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.flags = DB_DBT_MALLOC; data.flags = DB_DBT_MALLOC; switch(smc->dbc->c_get(smc->dbc, &key, &data, DB_CURRENT)) { case 0: if (!key.data || ((len + offset) <= key.size)) { memcpy((void *)value, ((char*)key.data) + offset, len); amt = len; } else { memcpy((void *)value, ((char*)key.data) + offset, key.size - offset); amt = key.size - offset; } break; default: amt = 0; /* TODO error */ break; } if (key.data) __dbsql_ufree(smc->sm->dbp, key.data); if (data.data) __dbsql_ufree(smc->sm->dbp, data.data); return amt; } /* * __sm_data -- * Copy from data.data[offset:offset + amt] into 'value'. * Return amount of bytes copied. * * PUBLIC: size_t __sm_data __P((sm_cursor_t *, size_t, size_t, char *)); */ size_t __sm_data(smc, offset, len, value) sm_cursor_t *smc; size_t offset; size_t len; char *value; { size_t amt = 0; DBT key, data; DBSQL_ASSERT(smc); memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.flags = DB_DBT_MALLOC; data.flags = DB_DBT_MALLOC; switch(smc->dbc->c_get(smc->dbc, &key, &data, DB_CURRENT)) { case 0: if (!data.data || ((len + offset) <= data.size)) { memcpy((void *)value, ((char*)data.data) + offset,len); amt = len; } else { memcpy((void *)value, ((char*)data.data) + offset, data.size - offset); amt = data.size - offset; } break; default: amt = 0; /* TODO */ break; } if (key.data) __dbsql_ufree(smc->sm->dbp, key.data); if (data.data) __dbsql_ufree(smc->sm->dbp, data.data); return amt; } /* * __sm_first -- * Advance the cursor to the first entry in the database. If * successful then set *result=0 and return DBSQL_SUCCESS. * If the database was empty, then set *result=1. * * PUBLIC: int __sm_first __P((sm_cursor_t *, int *)); */ int __sm_first(smc, result) sm_cursor_t *smc; int *result; { int rc = DBSQL_SUCCESS; DBT key, data; DBSQL_ASSERT(smc); DBSQL_ASSERT(result); memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.flags = DB_DBT_MALLOC; data.flags = DB_DBT_MALLOC; switch(smc->dbc->c_get(smc->dbc, &key, &data, DB_FIRST)) { case 0: *result = 0; break; case DB_NOTFOUND: *result = 1; break; default: rc = DBSQL_INTERNAL; /* TODO: report error */ } if (key.data) __dbsql_ufree(smc->sm->dbp, key.data); if (data.data) __dbsql_ufree(smc->sm->dbp, data.data); return rc; } /* * __sm_last -- * Advance the cursor to the last entry in the database. If * successful then set *result=0 and return DBSQL_SUCCESS. * If the database was empty, then set *result=1. * * PUBLIC: int __sm_last __P((sm_cursor_t *, int *)); */ int __sm_last(smc, result) sm_cursor_t *smc; int *result; { int rc = DBSQL_SUCCESS; DBT key, data; DBSQL_ASSERT(smc); DBSQL_ASSERT(result); memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.flags = DB_DBT_MALLOC; data.flags = DB_DBT_MALLOC; switch(smc->dbc->c_get(smc->dbc, &key, &data, DB_LAST)) { case 0: *result = 0; break; case DB_NOTFOUND: *result = 1; break; default: rc = DBSQL_INTERNAL; /* TODO: report error */ } if (key.data) __dbsql_ufree(smc->sm->dbp, key.data); if (data.data) __dbsql_ufree(smc->sm->dbp, data.data); return rc; } /* * __sm_insert -- * Insert a new record. The key is given by (key,k_len) * and the data is given by (data,d_len). The cursor is used only to * define what database the record should be inserted into. The cursor * is left pointing at the new record. * * PUBLIC: int __sm_insert __P((sm_cursor_t *, const void *, int, * PUBLIC: const void *, int)); */ int __sm_insert(smc, k, k_len, v, v_len) sm_cursor_t *smc; const void *k; int k_len; const void *v; int v_len; { int rc = DBSQL_SUCCESS; DBT key, data; DBSQL_ASSERT(smc); DBSQL_ASSERT(k); DBSQL_ASSERT(k_len); memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.flags = DB_DBT_USERMEM; data.flags = DB_DBT_USERMEM; key.size = k_len; key.data = (void *)k; data.size = v_len; data.data = (void *)v; if (smc->db->put(smc->db, smc->txn, &key, &data, 0) != 0) rc = DBSQL_INTERNAL; else { /* Position the cursor to the new entry. */ key.ulen = k_len; data.ulen = v_len; if (smc->dbc->c_get(smc->dbc, &key, &data, DB_SET) != 0) rc = DBSQL_INTERNAL; } return rc; } /* * __sm_delete -- * Delete the entry that the cursor is pointing to. * * PUBLIC: int __sm_delete __P((sm_cursor_t *)); */ int __sm_delete(smc) sm_cursor_t *smc; { int rc = DBSQL_SUCCESS; DBSQL_ASSERT(smc); switch(smc->dbc->c_del(smc->dbc, 0)) { case 0: break; default: rc = DBSQL_INTERNAL; } return rc; } /* * __sm_drop_table -- * DB->close() the database, clean up the smr_t and DB->remove(). * * PUBLIC: int __sm_drop_table __P((sm_t *, int)); */ int __sm_drop_table(sm, id) sm_t *sm; int id; { int rc; sm_rec_t *smr; DBSQL_ASSERT(sm); DBSQL_ASSERT(id > 1); /* MUTEX_THREAD_LOCK(dbp->dbenv, sm->sm_mutexp);*/ smr = __hash_find(&sm->dbs, (const void *)0, id); if (smr == 0) return DBSQL_INTERNAL; rc = smr->db->close(smr->db, 0); if (F_ISSET(sm, SM_INMEM_DB) == 0) smr->db->remove(smr->db, smr->file, NULL, 0); __hash_insert(&sm->dbs, (void *)0, id, 0); if (smr->file) __dbsql_free(sm->dbp, smr->file); __dbsql_free(sm->dbp, smr); /* MUTEX_THREAD_UNLOCK(dbp->dbenv, sm->sm_mutexp);*/ return DBSQL_SUCCESS; } /* * __sm_clear_table -- * DB->truncate() * * PUBLIC: int __sm_clear_table __P((sm_t *, int)); */ int __sm_clear_table(sm, id) sm_t *sm; int id; { int rc; u_int32_t count = 0; sm_rec_t *smr; DBSQL_ASSERT(sm); smr = __hash_find(&sm->dbs, (const void *)0, id); if (smr == 0) return DBSQL_INTERNAL; if ((rc = smr->db->truncate(smr->db, sm->txn, &count, 0)) != 0) return rc; return DBSQL_SUCCESS; } /* * __sm_create_resource -- * A new "resource" is really a new DB_BTREE. Create one here with the * name _ where is the next sequence number from * [__DBSQL__dbi]. Set id to the value of and return * DBSQL_SUCCESS when things go our way. * * STATIC: static int __sm_resource __P((sm_t *, u_int32_t *, int, int)); */ static int __sm_resource(sm, id, type, init) sm_t *sm; u_int32_t *id; int type; int init; { int rc, flags; char name[1024]; db_seq_t val; u_int32_t n; DB_TXN *txn; DB_ENV *dbenv; sm_rec_t *smr; dbenv = sm->dbp->dbenv; if (__dbsql_calloc(sm->dbp, 1, sizeof(sm_rec_t), &smr) == ENOMEM) return DBSQL_CANTOPEN; /* Create and initialize database object, open the database. */ if ((rc = db_create(&smr->db, dbenv, 0)) != 0) return DBSQL_CANTOPEN; smr->db->set_bt_compare(smr->db, __sm_bt_compare); /* Start a transaction, get an id, open the database, commit. */ dbenv->txn_begin(dbenv, sm->txn, &txn, 0); if (init) { n = __sm_next_from_seq(sm, txn); *id = n; flags = DB_CREATE | DB_EXCL; } else { n = *id; flags = 0; } snprintf(name, sizeof(name), "%s_%s.%.10u", (F_ISSET(sm, SM_INMEM_DB) ? ":memory:" : sm->name), (type == SMR_TYPE_TABLE ? "tbl" : "idx"), n); __dbsql_strdup(sm->dbp, name, &smr->file); F_SET(smr, type); if ((rc = smr->db->open(smr->db, txn, (F_ISSET(sm, SM_INMEM_DB) ? NULL : name), NULL, DB_BTREE, flags, 0)) != 0) goto err; txn->commit(txn, 0); __hash_insert(&sm->dbs, (void *)0, n, smr); return DBSQL_SUCCESS; err: txn->abort(txn); smr->db->close(smr->db, 0); __dbsql_free(sm->dbp, smr->file); __dbsql_free(sm->dbp, smr); return DBSQL_CANTOPEN; } /* * __sm_open_table -- * Open an existing DB_BTREE containing "table" data. * * PUBLIC: int __sm_open_table __P((sm_t *, int *)); */ int __sm_open_table(sm, id) sm_t *sm; int *id; { return __sm_resource(sm, id, SMR_TYPE_TABLE, 0); } /* * __sm_create_table -- * A new "table" is really a new DB_BTREE. * * PUBLIC: int __sm_create_table __P((sm_t *, int *)); */ int __sm_create_table(sm, id) sm_t *sm; int *id; { return __sm_resource(sm, id, SMR_TYPE_TABLE, 1); } /* * __sm_create_index -- * * PUBLIC: int __sm_create_index __P((sm_t *, int *)); */ int __sm_create_index(sm, id) sm_t *sm; int *id; { return __sm_resource(sm, id, SMR_TYPE_INDEX, 1); } /* * __sm_set_format_version -- * * PUBLIC: int __sm_set_format_version __P((sm_t *, int, u_int32_t)); */ int __sm_set_format_version(sm, id, ver) sm_t *sm; int id; u_int32_t ver; { int rc; DBT key, data; DB_TXN *txn; DB_ENV *dbenv; static const char *k; DBSQL_ASSERT(sm != 0); k = SM_FORMAT_VER; dbenv = sm->dbp->dbenv; memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.data = (void *)k; key.size = strlen(k); data.data = &ver; data.size = sizeof(u_int32_t); dbenv->txn_begin(dbenv, sm->txn, &txn, 0); if ((rc = sm->meta->put(sm->meta, txn, &key, &data, 0)) != 0) { /* TODO: report error */ dbenv->err(dbenv, rc, "put/meta/ver"); rc = DBSQL_INTERNAL; txn->abort(txn); } else { txn->commit(txn, 0); rc = DBSQL_SUCCESS; } return rc; } /* * __sm_get_format_version -- * * PUBLIC: int __sm_get_format_version __P((sm_t *, u_int32_t *)); */ int __sm_get_format_version(sm, ver) sm_t *sm; u_int32_t *ver; { int rc; DBT key, data; DB_TXN *txn; DB_ENV *dbenv; u_int32_t value; static const char *k; DBSQL_ASSERT(sm != 0); k = SM_FORMAT_VER; dbenv = sm->dbp->dbenv; memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.data = (void *)k; key.size = strlen(k); data.data = &value; data.size = sizeof(u_int32_t); data.ulen = sizeof(u_int32_t); data.flags |= DB_DBT_USERMEM; dbenv->txn_begin(dbenv, sm->txn, &txn, 0); rc = sm->meta->get(sm->meta, txn, &key, &data, 0); if (rc == DB_NOTFOUND) { *ver = 0; rc = DBSQL_SUCCESS; } else if (rc != 0) { /* TODO: report error */ dbenv->err(dbenv, rc, "get/meta/ver"); rc = DBSQL_INTERNAL; goto err; } else { *ver = value; rc = DBSQL_SUCCESS; } txn->commit(txn, 0); return rc; err: txn->abort(txn); return rc; } /* * __sm_set_schema_sig -- * * PUBLIC: int __sm_set_schema_sig __P((sm_t *, u_int32_t)); */ int __sm_set_schema_sig(sm, sig) sm_t *sm; u_int32_t sig; { int rc; char *k = SM_SCHEMA_SIG; DBT key, data; DB_TXN *txn; DB_ENV *dbenv; DBSQL_ASSERT(sm != 0); dbenv = sm->dbp->dbenv; memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.data = k; key.size = strlen(k); data.data = &sig; data.size = sizeof(u_int32_t); dbenv->txn_begin(dbenv, sm->txn, &txn, 0); if ((rc = sm->meta->put(sm->meta, txn, &key, &data, 0)) != 0) { /* TODO: report error */ dbenv->err(dbenv, rc, "put/meta/sig"); rc = DBSQL_INTERNAL; txn->abort(txn); } else { rc = DBSQL_SUCCESS; txn->commit(txn, 0); } return rc; } /* * __sm_get_schema_sig -- * * PUBLIC: int __sm_get_schema_sig __P((sm_t *, u_int32_t *)); */ int __sm_get_schema_sig(sm, sig) sm_t *sm; u_int32_t *sig; { int rc; DBT key, data; DB_TXN *txn; DB_ENV *dbenv; u_int32_t value; static const char *k; DBSQL_ASSERT(sm != 0); k = SM_SCHEMA_SIG; dbenv = sm->dbp->dbenv; memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.data = (void *)k; key.size = strlen(k); data.data = &value; data.size = sizeof(u_int32_t); data.ulen = sizeof(u_int32_t); data.flags |= DB_DBT_USERMEM; dbenv->txn_begin(dbenv, sm->txn, &txn, 0); rc = sm->meta->get(sm->meta, txn, &key, &data, 0); if (rc == DB_NOTFOUND) { *sig = 0; rc = DBSQL_SUCCESS; } else if (rc != 0) { /* TODO: report error */ dbenv->err(dbenv, rc, "get/meta/sig"); rc = DBSQL_INTERNAL; goto err; } else { *sig = value; rc = DBSQL_SUCCESS; } txn->commit(txn, 0); return rc; err: txn->abort(txn); return rc; }