From 38b04883fe07fe92ef52aaff51eb7f1ea041008a Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Wed, 14 Sep 2005 22:45:49 +0000 Subject: r10232: Some work on ldb_sqlite3. It is still far from being usable in samba4 but I want to commit so that the work does not get lost by mistake. This is also a good way to get comments if somebody is interested. Sorry Derrell I ended up rewriting large parts of the code but I find this style much more readable. Thanks for the hard work done. Your work was a good reference for me. ah the current code also shows some good numbers sqlite3 generic test: uid search took 0.05 seconds real 0m12.492s user 0m0.492s sys 0m0.345s with tdb we still get better numbers: uid search took 0.46 seconds real 0m0.892s user 0m0.360s sys 0m0.468s but most of the time is spent in adding operations and I think there's still a lot of space for improvement. Simo. (This used to be commit ace9990060c10d0931f418934b2121aea9512ff7) --- source4/lib/ldb/ldb_sqlite3/ldb_sqlite3.c | 3787 ++++++++++++----------------- 1 file changed, 1563 insertions(+), 2224 deletions(-) (limited to 'source4/lib/ldb/ldb_sqlite3') diff --git a/source4/lib/ldb/ldb_sqlite3/ldb_sqlite3.c b/source4/lib/ldb/ldb_sqlite3/ldb_sqlite3.c index 76ef703ca5..e283e6b0c1 100644 --- a/source4/lib/ldb/ldb_sqlite3/ldb_sqlite3.c +++ b/source4/lib/ldb/ldb_sqlite3/ldb_sqlite3.c @@ -36,7 +36,6 @@ #include "includes.h" #include "ldb/include/ldb.h" #include "ldb/include/ldb_private.h" -#include "ldb/include/ldb_explode_dn.h" #include "ldb/ldb_sqlite3/ldb_sqlite3.h" /* @@ -48,644 +47,436 @@ # define TRUE (! FALSE) #endif -#define FILTER_ATTR_TABLE "temp_filter_attrs" #define RESULT_ATTR_TABLE "temp_result_attrs" //#define TEMPTAB /* for testing, create non-temporary table */ #define TEMPTAB "TEMPORARY" -//#define DEBUG_LOCKS +/* + * Static variables + */ +sqlite3_stmt * stmtGetEID = NULL; -#ifndef DEBUG_LOCKS -# define LOCK_DB(mod, name) lsqlite3_lock(mod, name) -# define UNLOCK_DB(mod, name) lsqlite3_unlock(mod, name) -#else -# define LOCK_DB(mod, name) lock_debug(mod, name, __FILE__, __LINE__) -# define UNLOCK_DB(mod, name) unlock_debug(mod, name, __FILE__, __LINE__) -#endif +static char *lsqlite3_tprintf(TALLOC_CTX *mem_ctx, const char *fmt, ...) +{ + char *str, *ret; + va_list ap; + + va_start(ap, fmt); + str = sqlite3_vmprintf(fmt, ap); + va_end(ap); + + if (str == NULL) return NULL; + + ret = talloc_strdup(mem_ctx, str); + if (ret == NULL) { + sqlite3_free(str); + return NULL; + } + + sqlite3_free(str); + return ret; +} + +static unsigned char base160tab[161] = { + 48 ,49 ,50 ,51 ,52 ,53 ,54 ,55 ,56 ,57 , /* 0-9 */ + 58 ,59 ,65 ,66 ,67 ,68 ,69 ,70 ,71 ,72 , /* : ; A-H */ + 73 ,74 ,75 ,76 ,77 ,78 ,79 ,80 ,81 ,82 , /* I-R */ + 83 ,84 ,85 ,86 ,87 ,88 ,89 ,90 ,97 ,98 , /* S-Z , a-b */ + 99 ,100,101,102,103,104,105,106,107,108, /* c-l */ + 109,110,111,112,113,114,115,116,117,118, /* m-v */ + 119,120,121,122,160,161,162,163,164,165, /* w-z, latin1 */ + 166,167,168,169,170,171,172,173,174,175, /* latin1 */ + 176,177,178,179,180,181,182,183,184,185, /* latin1 */ + 186,187,188,189,190,191,192,193,194,195, /* latin1 */ + 196,197,198,199,200,201,202,203,204,205, /* latin1 */ + 206,207,208,209,210,211,212,213,214,215, /* latin1 */ + 216,217,218,219,220,221,222,223,224,225, /* latin1 */ + 226,227,228,229,230,231,232,233,234,235, /* latin1 */ + 236,237,238,239,240,241,242,243,244,245, /* latin1 */ + 246,247,248,249,250,251,252,253,254,255, /* latin1 */ + '\0' +}; -#define QUERY_NOROWS(lsqlite3, bRollbackOnError, sql...) \ - do { \ - if (query_norows(lsqlite3, sql) != 0) { \ - if (bRollbackOnError) { \ - UNLOCK_DB(module, "rollback"); \ - } \ - return -1; \ - } \ - } while (0) - -#define QUERY_INT(lsqlite3, result_var, bRollbackOnError, sql...) \ - do { \ - if (query_int(lsqlite3, &result_var, sql) != 0) { \ - if (bRollbackOnError) { \ - UNLOCK_DB(module, "rollback"); \ - } \ - return -1; \ - } \ - } while (0) - -#define GET_EID(lsqlite3, result_var, bRollbackOnError, pDN) \ - do { \ - if (getEID(lsqlite3, &result_var, pDN) != 0) { \ - if (bRollbackOnError) { \ - UNLOCK_DB(module, "rollback"); \ - } \ - return -1; \ - } \ - } while (0) - - -#define SQLITE3_DEBUG_QUERY (1 << 0) -#define SQLITE3_DEBUG_INIT (1 << 1) -#define SQLITE3_DEBUG_ADD (1 << 2) -#define SQLITE3_DEBUG_NEWDN (1 << 3) -#define SQLITE3_DEBUG_SEARCH (1 << 4) /* - * Static variables + * base160() + * + * Convert an unsigned long integer into a base160 representation of the + * number. + * + * Parameters: + * val -- + * value to be converted + * + * result -- + * character array, 5 bytes long, into which the base160 representation + * will be placed. The result will be a four-digit representation of the + * number (with leading zeros prepended as necessary), and null + * terminated. + * + * Returns: + * Nothing */ -static int lsqlite3_debug = FALSE; -sqlite3_stmt * stmtGetEID = NULL; +static void +base160_sql(sqlite3_context * hContext, + int argc, + sqlite3_value ** argv) +{ + int i; + long long val; + char result[5]; + + val = sqlite3_value_int64(argv[0]); + + for (i = 3; i >= 0; i--) { + + result[i] = base160tab[val % 160]; + val /= 160; + } + + result[4] = '\0'; + + sqlite3_result_text(hContext, result, -1, SQLITE_TRANSIENT); +} + /* - * Forward declarations + * base160next_sql() + * + * This function enhances sqlite by adding a "base160_next()" function which is + * accessible via queries. + * + * Retrieve the next-greater number in the base160 sequence for the terminal + * tree node (the last four digits). Only one tree level (four digits) is + * operated on. + * + * Input: + * A character string: either an empty string (in which case no operation is + * performed), or a string of base160 digits with a length of a multiple of + * four digits. + * + * Output: + * Upon return, the trailing four digits (one tree level) will have been + * incremented by 1. */ -static int -lsqlite3_rename(struct ldb_module * module, - const char * olddn, - const char * newdn); +static void +base160next_sql(sqlite3_context * hContext, + int argc, + sqlite3_value ** argv) +{ + int i; + int len; + unsigned char * pTab; + unsigned char * pBase160 = + strdup(sqlite3_value_text(argv[0])); + unsigned char * pStart = pBase160; -static int -lsqlite3_delete(struct ldb_module *module, - const char *dn); + /* + * We need a minimum of four digits, and we will always get a multiple + * of four digits. + */ + if (pBase160 != NULL && + (len = strlen(pBase160)) >= 4 && + len % 4 == 0) { -static int -lsqlite3_search_bytree(struct ldb_module * module, - const char * pBaseDN, - enum ldb_scope scope, - struct ldb_parse_tree * pTree, - const char * const * attrs, - struct ldb_message *** pppRes); + if (pBase160 == NULL) { -static int -lsqlite3_search(struct ldb_module * module, - const char * pBaseDN, - enum ldb_scope scope, - const char * pExpression, - const char * const attrs[], - struct ldb_message *** pppRes); + sqlite3_result_null(hContext); + return; + } -static int -lsqlite3_add(struct ldb_module *module, - const struct ldb_message *msg); + pBase160 += strlen(pBase160) - 1; -static int -lsqlite3_modify(struct ldb_module *module, - const struct ldb_message *msg); + /* We only carry through four digits: one level in the tree */ + for (i = 0; i < 4; i++) { -static int -lsqlite3_lock(struct ldb_module *module, - const char *lockname); + /* What base160 value does this digit have? */ + pTab = strchr(base160tab, *pBase160); -static int -lsqlite3_unlock(struct ldb_module *module, - const char *lockname); + /* Is there a carry? */ + if (pTab < base160tab + sizeof(base160tab) - 1) { -static const char * -lsqlite3_errstring(struct ldb_module *module); + /* + * Nope. Just increment this value and we're + * done. + */ + *pBase160 = *++pTab; + break; + } else { -static int -initialize(struct lsqlite3_private *lsqlite3, - const char *url); + /* + * There's a carry. This value gets + * base160tab[0], we decrement the buffer + * pointer to get the next higher-order digit, + * and continue in the loop. + */ + *pBase160-- = base160tab[0]; + } + } -static int -destructor(void *p); + sqlite3_result_text(hContext, + pStart, + strlen(pStart), + free); + } else { + sqlite3_result_value(hContext, argv[0]); + if (pBase160 != NULL) { + free(pBase160); + } + } +} -static int -query_norows(const struct lsqlite3_private *lsqlite3, - const char *pSql, - ...); +static char *parsetree_to_sql(struct ldb_module *module, + void *mem_ctx, + const struct ldb_parse_tree *t) +{ + const struct ldb_attrib_handler *h; + struct ldb_val value, subval; + char *wild_card_string; + char *child, *tmp; + char *ret = NULL; + int i; -static int -query_int(const struct lsqlite3_private * lsqlite3, - long long * pRet, - const char * pSql, - ...); -static int -getEID(const struct lsqlite3_private * lsqlite3, - long long * pRet, - const char * pNormalizedDN); + switch(t->operation) { + case LDB_OP_AND: -static int case_fold_attr_required(void * hUserData, - char *attr); + tmp = parsetree_to_sql(module, mem_ctx, t->u.list.elements[0]); + if (tmp == NULL) return NULL; -static int case_fold_attr_not_required(void * hUserData, - char *attr); + for (i = 1; i < t->u.list.num_elements; i++) { -static int -add_msg_attr(void * hTalloc, - long long eid, - const char * pDN, - const char * pAttrName, - const char * pAttrValue, - long long prevEID, - int * pAllocated, - struct ldb_message *** pppRes); - -static char * -parsetree_to_sql(struct ldb_module *module, - char * hTalloc, - const struct ldb_parse_tree *t); + child = parsetree_to_sql(module, mem_ctx, t->u.list.elements[i]); + if (child == NULL) return NULL; -static int -parsetree_to_attrlist(struct ldb_module *module, - const struct ldb_parse_tree * t); + tmp = talloc_asprintf_append(tmp, "INTERSECT %s ", child); + if (tmp == NULL) return NULL; + } -static int -msg_to_sql(struct ldb_module * module, - const struct ldb_message * msg, - long long eid, - int use_flags); + ret = talloc_asprintf(mem_ctx, "SELECT * FROM ( %s )\n", tmp); -static int -new_dn(struct ldb_module * module, - char * pDN, - long long * pEID); + return ret; + + case LDB_OP_OR: -static void -base160_sql(sqlite3_context * hContext, - int argc, - sqlite3_value ** argv); + tmp = parsetree_to_sql(module, mem_ctx, t->u.list.elements[0]); + if (tmp == NULL) return NULL; -static void -base160next_sql(sqlite3_context * hContext, - int argc, - sqlite3_value ** argv); - -#ifdef DEBUG_LOCKS -static int lock_debug(struct ldb_module * module, - const char * lockname, - const char * pFileName, - int linenum); - -static int unlock_debug(struct ldb_module * module, - const char * lockname, - const char * pFileName, - int linenum); -#endif + for (i = 1; i < t->u.list.num_elements; i++) { + child = parsetree_to_sql(module, mem_ctx, t->u.list.elements[i]); + if (child == NULL) return NULL; -/* - * Table of operations for the sqlite3 backend - */ -static const struct ldb_module_ops lsqlite3_ops = { - .name = "sqlite", - .search = lsqlite3_search, - .search_bytree = lsqlite3_search_bytree, - .add_record = lsqlite3_add, - .modify_record = lsqlite3_modify, - .delete_record = lsqlite3_delete, - .rename_record = lsqlite3_rename, - .named_lock = lsqlite3_lock, - .named_unlock = lsqlite3_unlock, - .errstring = lsqlite3_errstring -}; + tmp = talloc_asprintf_append(tmp, "UNION %s ", child); + if (tmp == NULL) return NULL; + } + return talloc_asprintf(mem_ctx, "SELECT * FROM ( %s )", tmp); + case LDB_OP_NOT: + child = parsetree_to_sql(module, mem_ctx, t->u.isnot.child); + if (child == NULL) return NULL; -/* - * Public functions - */ + return talloc_asprintf(mem_ctx, + "SELECT eid FROM ldb_entry " + "WHERE eid NOT IN ( %s )", child); + + case LDB_OP_EQUALITY: + /* + * For simple searches, we want to retrieve the list of EIDs that + * match the criteria. + */ + h = ldb_attrib_handler(module->ldb, t->u.equality.attr); + + /* Get a canonicalised copy of the data */ + h->canonicalise_fn(module->ldb, mem_ctx, &(t->u.equality.value), &value); + if (value.data == NULL) { + return NULL; + } + + if (strcasecmp(t->u.equality.attr, "objectclass") == 0) { + /* + * For object classes, we want to search for all objectclasses + * that are subclasses as well. + */ + return lsqlite3_tprintf(mem_ctx, + "SELECT eid FROM ldb_attribute_values\n" + "WHERE norm_attr_name = 'OBJECTCLASS' " + "AND norm_attr_value IN\n" + " (SELECT class_name FROM ldb_object_classes\n" + " WHERE tree_key GLOB\n" + " (SELECT tree_key FROM ldb_object_classes\n" + " WHERE class_name = '%q'\n" + " ) || '*'\n" + " )\n", value.data); + + } else if (strcasecmp(t->u.equality.attr, "dn") == 0) { + /* DN query is a special ldb case */ + char *cdn = ldb_dn_linearize_casefold(module->ldb, + ldb_dn_explode(module->ldb, + value.data)); + + return lsqlite3_tprintf(mem_ctx, + "SELECT eid FROM ldb_entry " + "WHERE norm_dn = '%q'", cdn); + + } else { + /* A normal query. */ + return lsqlite3_tprintf(mem_ctx, + "SELECT eid FROM ldb_attribute_values " + "WHERE norm_attr_name = upper('%q') " + "AND norm_attr_value = '%q'", + t->u.equality.attr, + value.data); + + } + + case LDB_OP_SUBSTRING: + + wild_card_string = talloc_strdup(mem_ctx, + (t->u.substring.start_with_wildcard)?"*":""); + if (wild_card_string == NULL) return NULL; + + for (i = 0; t->u.substring.chunks[i]; i++) { + wild_card_string = talloc_asprintf_append(wild_card_string, "%s*", + t->u.substring.chunks[i]); + if (wild_card_string == NULL) return NULL; + } + + if ( ! t->u.substring.end_with_wildcard ) { + /* remove last wildcard */ + wild_card_string[strlen(wild_card_string) - 1] = '\0'; + } + + h = ldb_attrib_handler(module->ldb, t->u.substring.attr); + + subval.data = wild_card_string; + subval.length = strlen(wild_card_string) + 1; + + /* Get a canonicalised copy of the data */ + h->canonicalise_fn(module->ldb, mem_ctx, &(subval), &value); + if (value.data == NULL) { + return NULL; + } + + return lsqlite3_tprintf(mem_ctx, + "SELECT eid FROM ldb_attribute_values " + "WHERE norm_attr_name = upper('%q') " + "AND norm_attr_value GLOB '%q'", + t->u.substring.attr, + value.data); + + case LDB_OP_GREATER: + h = ldb_attrib_handler(module->ldb, t->u.equality.attr); + + /* Get a canonicalised copy of the data */ + h->canonicalise_fn(module->ldb, mem_ctx, &(t->u.equality.value), &value); + if (value.data == NULL) { + return NULL; + } + + return lsqlite3_tprintf(mem_ctx, + "SELECT eid FROM ldb_attribute_values " + "WHERE norm_attr_name = upper('%q') " + "AND norm_attr_value >= '%q'", + t->u.equality.attr, + value.data); + + case LDB_OP_LESS: + h = ldb_attrib_handler(module->ldb, t->u.equality.attr); + + /* Get a canonicalised copy of the data */ + h->canonicalise_fn(module->ldb, mem_ctx, &(t->u.equality.value), &value); + if (value.data == NULL) { + return NULL; + } + + return lsqlite3_tprintf(mem_ctx, + "SELECT eid FROM ldb_attribute_values " + "WHERE norm_attr_name = upper('%q') " + "AND norm_attr_value <= '%q'", + t->u.equality.attr, + value.data); + + case LDB_OP_PRESENT: + if (strcasecmp(t->u.present.attr, "dn")) { + return talloc_strdup(mem_ctx, "SELECT eid FROM ldb_entry"); + } + + return lsqlite3_tprintf(mem_ctx, + "SELECT eid FROM ldb_attribute_values " + "WHERE norm_attr_name = upper('%q')", + t->u.present.attr); + + case LDB_OP_APPROX: + h = ldb_attrib_handler(module->ldb, t->u.equality.attr); + + /* Get a canonicalised copy of the data */ + h->canonicalise_fn(module->ldb, mem_ctx, &(t->u.equality.value), &value); + if (value.data == NULL) { + return NULL; + } + + return lsqlite3_tprintf(mem_ctx, + "SELECT eid FROM ldb_attribute_values " + "WHERE norm_attr_name = upper('%q') " + "AND norm_attr_value LIKE '%q'", + t->u.equality.attr, + value.data); + + case LDB_OP_EXTENDED: +#warning "work out how to handle bitops" + return NULL; + + default: + break; + }; + + /* should never occur */ + abort(); + return NULL; +} /* - * connect to the database + * query_norows() + * + * This function is used for queries that are not expected to return any rows, + * e.g. BEGIN, COMMIT, ROLLBACK, CREATE TABLE, INSERT, UPDATE, DELETE, etc. + * There are no provisions here for returning data from rows in a table, so do + * not pass SELECT queries to this function. */ -int lsqlite3_connect(struct ldb_context *ldb, - const char *url, - unsigned int flags, - const char *options[]) +static int +query_norows(const struct lsqlite3_private *lsqlite3, + const char *pSql, + ...) { - int i; - int ret; - struct lsqlite3_private * lsqlite3 = NULL; - - lsqlite3 = talloc(ldb, struct lsqlite3_private); - if (!lsqlite3) { - goto failed; - } - - lsqlite3->sqlite = NULL; - lsqlite3->options = NULL; - lsqlite3->lock_count = 0; - - ret = initialize(lsqlite3, url); - if (ret != SQLITE_OK) { - goto failed; - } - - talloc_set_destructor(lsqlite3, destructor); - - ldb->modules = talloc(ldb, struct ldb_module); - if (!ldb->modules) { - goto failed; - } - ldb->modules->ldb = ldb; - ldb->modules->prev = ldb->modules->next = NULL; - ldb->modules->private_data = lsqlite3; - ldb->modules->ops = &lsqlite3_ops; - - if (options) { - /* - * take a copy of the options array, so we don't have to rely - * on the caller keeping it around (it might be dynamic) - */ - for (i=0;options[i];i++) ; - - lsqlite3->options = talloc_array(lsqlite3, char *, i+1); - if (!lsqlite3->options) { - goto failed; - } - - for (i=0;options[i];i++) { - - lsqlite3->options[i+1] = NULL; - lsqlite3->options[i] = - talloc_strdup(lsqlite3->options, options[i]); - if (!lsqlite3->options[i]) { - goto failed; - } - } - } - - return 0; - -failed: - if (lsqlite3->sqlite != NULL) { - (void) sqlite3_close(lsqlite3->sqlite); - } - talloc_free(lsqlite3); - return -1; -} - - -/* - * Interface functions referenced by lsqlite3_ops - */ - -/* rename a record */ -static int -lsqlite3_rename(struct ldb_module * module, - const char * pOldDN, - const char * pNewDN) -{ - const char *pOldNormalizedDN; - const char *pNewNormalizedDN; - long long eid; - struct lsqlite3_private * lsqlite3 = module->private_data; - - /* ignore ltdb specials */ - if (*pOldDN == '@' || *pNewDN == '@') { - return 0; - } - - /* Case-fold each of the DNs */ - pOldNormalizedDN = - ldb_dn_fold(module->ldb, pOldDN, module, case_fold_attr_required); - pNewNormalizedDN = - ldb_dn_fold(module->ldb, pNewDN, module, case_fold_attr_required); - - /* Begin a transaction */ - if (LOCK_DB(module, "transaction") < 0) { - return -1; - } - - /* Determine the eid of the DN being renamed */ - GET_EID(lsqlite3, eid, TRUE, pOldNormalizedDN); - - QUERY_NOROWS(lsqlite3, - TRUE, - "UPDATE ldb_entry " - " SET dn = %Q, " - " normalized_dn = %Q " - " WHERE eid = %lld;", - pNewDN, pNewNormalizedDN, eid); - - QUERY_NOROWS(lsqlite3, - TRUE, - "UPDATE ldb_attribute_values " - " SET attr_value = %Q, " - " attr_value_normalized = %Q " - " WHERE eid = %lld " - " AND attr_name = 'DN';", - pNewDN, - pNewNormalizedDN, - eid); - - /* Commit the transaction */ - if (UNLOCK_DB(module, "transaction") < 0) { - UNLOCK_DB(module, "rollback"); - return -1; - } - - return 0; -} - -/* delete a record */ -static int -lsqlite3_delete(struct ldb_module * module, - const char * pDN) -{ - char *pNormalizedDN; - long long eid; - struct lsqlite3_private * lsqlite3 = module->private_data; - - /* ignore ltdb specials */ - if (*pDN == '@') { - return 0; - } - - /* Begin a transaction */ - if (LOCK_DB(module, "transaction") < 0) { - return -1; - } - - /* Case-fold the DNs */ - pNormalizedDN = - ldb_dn_fold(module->ldb, pDN, module, case_fold_attr_required); - - /* Determine the eid of the DN being deleted */ - GET_EID(lsqlite3, eid, TRUE, pNormalizedDN); - - /* Delete attribute/value table entries pertaining to this DN */ - QUERY_NOROWS(lsqlite3, - TRUE, - "DELETE FROM ldb_attribute_values " - " WHERE eid = %lld;", - eid); - - /* Delete this entry */ - QUERY_NOROWS(lsqlite3, - TRUE, - "DELETE FROM ldb_entry " - " WHERE eid = %lld;", - eid); - - /* Commit the transaction */ - if (UNLOCK_DB(module, "transaction") < 0) { - UNLOCK_DB(module, "rollback"); - return -1; - } - - return 0; -} - -/* search for matching records, by tree */ -static int -lsqlite3_search_bytree(struct ldb_module * module, - const char * pBaseDN, - enum ldb_scope scope, - struct ldb_parse_tree * pTree, - const char * const * attrs, - struct ldb_message *** pppRes) -{ - int ret; - int allocated; - int bLoop; - long long eid = 0; - long long prevEID; - char * pSql = NULL; - char * pSqlConstraints; - char * hTalloc = NULL; - const char * pDN; - const char * pNormalizedBaseDN; - const char * pAttrName; - const char * pAttrValue; - const char * pResultAttrList; - const char * const * pRequestedAttrs; - sqlite3_stmt * pStmt; - struct lsqlite3_private * lsqlite3 = module->private_data; - - /* Allocate a temporary talloc context */ - if ((hTalloc = talloc_new(module->ldb)) == NULL) { - return -1; - } - - /* Case-fold the base DN */ - if ((pNormalizedBaseDN = - ldb_dn_fold(hTalloc, - pBaseDN == NULL ? "" : pBaseDN, - module, - case_fold_attr_required)) == NULL) { - - talloc_free(hTalloc); - return -1; - } - - /* Begin a transaction */ - if (LOCK_DB(module, "transaction") < 0) { - talloc_free(hTalloc); - return -1; - } + int ret; + int bLoop; + char * p; + sqlite3_stmt * pStmt; + va_list args; + + /* Begin access to variable argument list */ + va_start(args, pSql); - /* - * Obtain the eid of the base DN - */ - if ((ret = getEID(lsqlite3, &eid, pNormalizedBaseDN)) == SQLITE_DONE) { - UNLOCK_DB(module, "rollback"); - talloc_free(hTalloc); - return 0; - } else if (ret != SQLITE_OK) { - UNLOCK_DB(module, "rollback"); - talloc_free(hTalloc); + /* Format the query */ + if ((p = sqlite3_vmprintf(pSql, args)) == NULL) { return -1; } - /* Convert filter into a series of SQL conditions (constraints) */ - pSqlConstraints = parsetree_to_sql(module, hTalloc, pTree); - - /* Ensure we're starting with an empty result attribute table */ - QUERY_NOROWS(lsqlite3, - FALSE, - "DELETE FROM " RESULT_ATTR_TABLE "\n" - " WHERE 1;");/* avoid a schema change with WHERE 1 */ - - /* Initially, we don't know what the requested attributes are */ - pResultAttrList = NULL; - - /* Insert the list of requested attributes into this table */ - for (pRequestedAttrs = (const char * const *) attrs; - pRequestedAttrs != NULL && *pRequestedAttrs != NULL; - pRequestedAttrs++) { - - /* If any attribute in the list is "*" then... */ - if (strcmp(*pRequestedAttrs, "*") == 0) { - /* we want all attribute types */ - pResultAttrList = ""; - break; - - } else { - /* otherwise, add this name to the resuult list */ - QUERY_NOROWS(lsqlite3, - FALSE, - "INSERT OR IGNORE\n" - " INTO " RESULT_ATTR_TABLE "\n" - " (attr_name)\n" - " VALUES\n" - " (%Q);", - *pRequestedAttrs); - } - } - - /* If we didn't get a "*" for all attributes in the result list... */ - if (pResultAttrList == NULL) { - /* ... then we'll use the result attribute table */ - pResultAttrList = - " AND upper(av.attr_name) IN\n" - " (SELECT attr_name\n" - " FROM " RESULT_ATTR_TABLE ") "; - } - - /* Ensure we're starting with an empty filter attribute table */ - QUERY_NOROWS(lsqlite3, - FALSE, - "DELETE FROM " FILTER_ATTR_TABLE "\n" - " WHERE 1;");/* avoid a schema change with WHERE 1 */ - - /* - * Create a table of unique attribute names for our extra table list - */ - if ((ret = parsetree_to_attrlist(module, pTree)) != 0) { - ret = -1; - goto cleanup; - } - - switch(scope) { - case LDB_SCOPE_DEFAULT: - case LDB_SCOPE_SUBTREE: - pSql = sqlite3_mprintf( - "SELECT entry.eid,\n" - " entry.dn,\n" - " av.attr_name,\n" - " av.attr_value\n" - " FROM ldb_entry AS entry\n" - - " LEFT OUTER JOIN ldb_attribute_values AS av\n" - " ON av.eid = entry.eid\n" - " %s\n" - - " WHERE entry.eid IN\n" - " (SELECT DISTINCT ldb_entry.eid\n" - " FROM ldb_entry\n" - " WHERE ldb_entry.tree_key >=\n" - " (SELECT tree_key\n" - " FROM ldb_entry\n" - " WHERE eid = %lld)\n" - " AND ldb_entry.tree_key <\n" - " (SELECT base160_next(tree_key)\n" - " FROM ldb_entry\n" - " WHERE eid = %lld)\n" - " AND ldb_entry.eid IN\n(%s)\n" - " )\n" - " ORDER BY entry.tree_key DESC,\n" - " COALESCE(av.attr_name, '');", - pResultAttrList, - eid, - eid, - pSqlConstraints); - break; - - case LDB_SCOPE_BASE: - pSql = sqlite3_mprintf( - "SELECT entry.eid,\n" - " entry.dn,\n" - " av.attr_name,\n" - " av.attr_value\n" - " FROM ldb_entry AS entry\n" - - " LEFT OUTER JOIN ldb_attribute_values AS av\n" - " ON av.eid = entry.eid\n" - " %s\n" - - " WHERE entry.eid IN\n" - " (SELECT DISTINCT ldb_entry.eid\n" - " FROM ldb_entry\n" - " WHERE ldb_entry.eid = %lld\n" - " AND ldb_entry.eid IN\n(%s)\n" - " )\n" - " ORDER BY entry.tree_key DESC,\n" - " COALESCE(av.attr_name, '');", - pResultAttrList, - eid, - pSqlConstraints); - break; - - case LDB_SCOPE_ONELEVEL: - pSql = sqlite3_mprintf( - "SELECT entry.eid,\n" - " entry.dn,\n" - " av.attr_name,\n" - " av.attr_value\n" - " FROM ldb_entry AS entry\n" - - " LEFT OUTER JOIN ldb_attribute_values AS av\n" - " ON av.eid = entry.eid\n" - " %s\n" - - " WHERE entry.eid IN\n" - " (SELECT DISTINCT ldb_entry.eid\n" - " FROM ldb_entry\n" - " WHERE ldb_entry.tree_key >=\n" - " (SELECT tree_key\n" - " FROM ldb_entry\n" - " WHERE eid = %lld)\n" - " AND ldb_entry.tree_key <\n" - " (SELECT base160_next(tree_key)\n" - " FROM ldb_entry\n" - " WHERE eid = %lld)\n" - " AND length(ldb_entry.tree_key) =\n" - " (SELECT length(tree_key) + 4\n" - " FROM ldb_entry\n" - " WHERE eid = %lld)\n" - " AND ldb_entry.eid IN\n(%s)\n" - " )\n" - - " ORDER BY entry.tree_key DESC,\n" - " COALESCE(av.attr_name, '');\n", - pResultAttrList, - eid, - eid, - eid, - pSqlConstraints); - break; - } - - if (pSql == NULL) { - ret = -1; - goto cleanup; - } - - if (lsqlite3_debug & SQLITE3_DEBUG_SEARCH) { - printf("%s\n", pSql); - } - /* * Prepare and execute the SQL statement. Loop allows retrying on * certain errors, e.g. SQLITE_SCHEMA occurs if the schema changes, * requiring retrying the operation. */ for (bLoop = TRUE; bLoop; ) { - /* There are no allocate message structures yet */ - allocated = 0; - if (pppRes != NULL) { - *pppRes = NULL; - } /* Compile the SQL statement into sqlite virtual machine */ if ((ret = sqlite3_prepare(lsqlite3->sqlite, - pSql, + p, -1, &pStmt, NULL)) == SQLITE_SCHEMA) { @@ -693,62 +484,19 @@ lsqlite3_search_bytree(struct ldb_module * module, sqlite3_finalize(stmtGetEID); stmtGetEID = NULL; } - - if (pppRes != NULL && *pppRes != NULL) { - talloc_free(*pppRes); - } - continue; } else if (ret != SQLITE_OK) { ret = -1; break; } - /* Initially, we have no previous eid */ - prevEID = -1; - - /* Loop through the returned rows */ - for (ret = SQLITE_ROW; ret == SQLITE_ROW; ) { - - /* Get the next row */ - if ((ret = sqlite3_step(pStmt)) == SQLITE_ROW) { - - /* Get the values from this row */ - eid = sqlite3_column_int64(pStmt, 0); - pDN = sqlite3_column_text(pStmt, 1); - pAttrName = sqlite3_column_text(pStmt, 2); - pAttrValue = sqlite3_column_text(pStmt, 3); - - /* Add this result to the result set */ - if (add_msg_attr(hTalloc, - eid, - pDN, - pAttrName, - pAttrValue, - prevEID, - &allocated, - pppRes) != 0) { - - (void) sqlite3_finalize(pStmt); - ret = -1; - break; - } - - /* Save the most recent EID */ - prevEID = eid; - } - } - - if (ret == SQLITE_SCHEMA) { + /* No rows expected, so just step through machine code once */ + if ((ret = sqlite3_step(pStmt)) == SQLITE_SCHEMA) { if (stmtGetEID != NULL) { sqlite3_finalize(stmtGetEID); stmtGetEID = NULL; } - (void) sqlite3_finalize(pStmt); - if (pppRes != NULL && *pppRes != NULL) { - talloc_free(*pppRes); - } continue; } else if (ret != SQLITE_DONE) { (void) sqlite3_finalize(pStmt); @@ -762,10 +510,6 @@ lsqlite3_search_bytree(struct ldb_module * module, sqlite3_finalize(stmtGetEID); stmtGetEID = NULL; } - - if (pppRes != NULL && *pppRes != NULL) { - talloc_free(*pppRes); - } continue; } else if (ret != SQLITE_OK) { (void) sqlite3_finalize(pStmt); @@ -781,184 +525,24 @@ lsqlite3_search_bytree(struct ldb_module * module, bLoop = FALSE; } - /* We're alll done with this query */ - sqlite3_free(pSql); - - /* End the transaction */ - UNLOCK_DB(module, "rollback"); - - /* Were there any results? */ - if (ret != 0 || allocated == 0) { - /* Nope. We can free the results. */ - if (pppRes != NULL && *pppRes != NULL) { - talloc_free(*pppRes); - } - } - -cleanup: - /* Clean up our temporary tables */ - QUERY_NOROWS(lsqlite3, - FALSE, - "DELETE FROM " RESULT_ATTR_TABLE "\n" - " WHERE 1;");/* avoid a schema change with WHERE 1 */ - - QUERY_NOROWS(lsqlite3, - FALSE, - "DELETE FROM " FILTER_ATTR_TABLE "\n" - " WHERE 1;");/* avoid a schema change with WHERE 1 */ - + /* All done with variable argument list */ + va_end(args); - if (hTalloc != NULL) { - talloc_free(hTalloc); - } + /* Free the memory we allocated for our query string */ + sqlite3_free(p); - /* If error, return error code; otherwise return number of results */ - return ret == 0 ? allocated : ret; + return ret; } -/* search for matching records, by expression */ + +/* obtain a named lock */ static int -lsqlite3_search(struct ldb_module * module, - const char * pBaseDN, - enum ldb_scope scope, - const char * pExpression, - const char * const * attrs, - struct ldb_message *** pppRes) +lsqlite3_lock(struct ldb_module * module, + const char * lockname) { - int ret; - struct ldb_parse_tree * pTree; - - /* Handle tdb specials */ - if (pBaseDN != NULL && *pBaseDN == '@') { -#warning "handle tdb specials" - return 0; - } - -#if 0 -/* (|(objectclass=*)(dn=*)) is passed by the command line tool now instead */ - /* Handle the special case of requesting all */ - if (pExpression != NULL && *pExpression == '\0') { - pExpression = "dn=*"; - } -#endif - - /* Parse the filter expression into a tree we can work with */ - if ((pTree = ldb_parse_tree(module->ldb, pExpression)) == NULL) { - return -1; - } - - /* Now use the bytree function for the remainder of processing */ - ret = lsqlite3_search_bytree(module, pBaseDN, scope, - pTree, attrs, pppRes); - - /* Free the parse tree */ - talloc_free(pTree); - - /* All done. */ - return ret; -} - - -/* add a record */ -static int -lsqlite3_add(struct ldb_module *module, - const struct ldb_message *msg) -{ - long long eid; - - /* See if this is an ltdb special */ - if (*msg->dn == '@') { - /* Yup. We handle a few of these and ignore others */ - if (strcmp(msg->dn, "@SUBCLASSES") == 0) { -#warning "insert subclasses into object class tree" - } - - if (strcmp(msg->dn, "@INDEXLIST") == 0) { - /* explicitly ignored */ - return 0; - } - - /* Others are implicitly ignored */ - return 0; - } - - /* Begin a transaction */ - if (LOCK_DB(module, "transaction") < 0) { - return -1; - } - - /* - * Build any portions of the directory tree that don't exist. If the - * final component already exists, it's an error. - */ - if (new_dn(module, msg->dn, &eid) != 0) { - UNLOCK_DB(module, "rollback"); - return -1; - } - - /* Add attributes to this new entry */ - if (msg_to_sql(module, msg, eid, FALSE) != 0) { - UNLOCK_DB(module, "rollback"); - return -1; - } - - /* Everything worked. Commit it! */ - if (UNLOCK_DB(module, "transaction") < 0) { - UNLOCK_DB(module, "rollback"); - return -1; - } - return 0; -} - - -/* modify a record */ -static int -lsqlite3_modify(struct ldb_module * module, - const struct ldb_message * msg) -{ - char * pNormalizedDN; - long long eid; - struct lsqlite3_private * lsqlite3 = module->private_data; - - /* ignore ltdb specials */ - if (*msg->dn == '@') { - return 0; - } - - /* Begin a transaction */ - if (LOCK_DB(module, "transaction") < 0) { - return -1; - } - - /* Case-fold the DN so we can compare it to what's in the database */ - pNormalizedDN = ldb_dn_fold(module->ldb, msg->dn, - module, case_fold_attr_required); - - /* Determine the eid of the DN being deleted */ - GET_EID(lsqlite3, eid, TRUE, pNormalizedDN); - - /* Apply the message attributes */ - if (msg_to_sql(module, msg, eid, TRUE) != 0) { - UNLOCK_DB(module, "rollback"); - return -1; - } - - - /* Everything worked. Commit it! */ - if (UNLOCK_DB(module, "transaction") < 0) { - UNLOCK_DB(module, "rollback"); - return -1; - } - return 0 ; -} - -/* obtain a named lock */ -static int -lsqlite3_lock(struct ldb_module * module, - const char * lockname) -{ - struct lsqlite3_private * lsqlite3 = module->private_data; + struct lsqlite3_private * lsqlite3 = module->private_data; +/* FIXME if (lockname == NULL) { return -1; } @@ -971,7 +555,7 @@ lsqlite3_lock(struct ldb_module * module, } ++lsqlite3->lock_count; } - +*/ return 0; } @@ -982,6 +566,7 @@ lsqlite3_unlock(struct ldb_module *module, { struct lsqlite3_private * lsqlite3 = module->private_data; +/* FIXME if (lockname == NULL) { return -1; } @@ -997,1559 +582,1313 @@ lsqlite3_unlock(struct ldb_module *module, } else if (strcmp(lockname, "rollback") == 0) { query_norows(lsqlite3, "ROLLBACK;"); } - +*/ return 0; } -/* return extended error information */ -static const char * -lsqlite3_errstring(struct ldb_module *module) -{ - struct lsqlite3_private * lsqlite3 = module->private_data; - - return sqlite3_errmsg(lsqlite3->sqlite); -} - - - - /* - * Static functions + * query_int() + * + * This function is used for the common case of queries that return a single + * integer value. + * + * NOTE: If more than one value is returned by the query, all but the first + * one will be ignored. */ - static int -initialize(struct lsqlite3_private *lsqlite3, - const char *url) +query_int(const struct lsqlite3_private * lsqlite3, + long long * pRet, + const char * pSql, + ...) { int ret; - long long queryInt; - const char * pTail; - sqlite3_stmt * stmt; - const char * schema = - + int bLoop; + char * p; + sqlite3_stmt * pStmt; + va_list args; + + /* Begin access to variable argument list */ + va_start(args, pSql); + + /* Format the query */ + if ((p = sqlite3_vmprintf(pSql, args)) == NULL) { + return SQLITE_NOMEM; + } + + /* + * Prepare and execute the SQL statement. Loop allows retrying on + * certain errors, e.g. SQLITE_SCHEMA occurs if the schema changes, + * requiring retrying the operation. + */ + for (bLoop = TRUE; bLoop; ) { - "CREATE TABLE ldb_info AS " - " SELECT 'LDB' AS database_type," - " '1.0' AS version;" + /* Compile the SQL statement into sqlite virtual machine */ + if ((ret = sqlite3_prepare(lsqlite3->sqlite, + p, + -1, + &pStmt, + NULL)) == SQLITE_SCHEMA) { + if (stmtGetEID != NULL) { + sqlite3_finalize(stmtGetEID); + stmtGetEID = NULL; + } + continue; + } else if (ret != SQLITE_OK) { + break; + } - /* - * The entry table holds the information about an entry. - * This table is used to obtain the EID of the entry and to - * support scope=one and scope=base. The parent and child - * table is included in the entry table since all the other - * attributes are dependent on EID. - */ - "CREATE TABLE ldb_entry " - "(" - " eid INTEGER PRIMARY KEY," - " peid INTEGER REFERENCES ldb_entry," - " dn TEXT UNIQUE NOT NULL," - " normalized_dn TEXT UNIQUE NOT NULL," - " tree_key TEXT UNIQUE," - " max_child_num INTEGER DEFAULT 0," - " create_timestamp INTEGER," - " modify_timestamp INTEGER" - ");" + /* One row expected */ + if ((ret = sqlite3_step(pStmt)) == SQLITE_SCHEMA) { + if (stmtGetEID != NULL) { + sqlite3_finalize(stmtGetEID); + stmtGetEID = NULL; + } + (void) sqlite3_finalize(pStmt); + continue; + } else if (ret != SQLITE_ROW) { + (void) sqlite3_finalize(pStmt); + break; + } - - "CREATE TABLE ldb_object_classes" - "(" - " class_name TEXT PRIMARY KEY," - " parent_class_name TEXT," - " tree_key TEXT UNIQUE," - " max_child_num INTEGER DEFAULT 0" - ");" + /* Get the value to be returned */ + *pRet = sqlite3_column_int64(pStmt, 0); - /* - * We keep a full listing of attribute/value pairs here - */ - "CREATE TABLE ldb_attribute_values" - "(" - " eid INTEGER REFERENCES ldb_entry," - " attr_name TEXT," - " attr_value TEXT," - " attr_value_normalized TEXT " - ");" + /* Free the virtual machine */ + if ((ret = sqlite3_finalize(pStmt)) == SQLITE_SCHEMA) { + if (stmtGetEID != NULL) { + sqlite3_finalize(stmtGetEID); + stmtGetEID = NULL; + } + continue; + } else if (ret != SQLITE_OK) { + (void) sqlite3_finalize(pStmt); + break; + } - /* - * Indexes + * Normal condition is only one time through loop. Loop is + * rerun in error conditions, via "continue", above. */ - "CREATE INDEX ldb_entry_tree_key_idx " - " ON ldb_entry (tree_key);" + bLoop = FALSE; + } + + /* All done with variable argument list */ + va_end(args); + - "CREATE INDEX ldb_attribute_values_eid_idx " - " ON ldb_attribute_values (eid);" - - "CREATE INDEX ldb_attribute_values_name_value_idx " - " ON ldb_attribute_values (attr_name, attr_value_normalized);" - - + /* Free the memory we allocated for our query string */ + sqlite3_free(p); + + return ret; +} - /* - * Triggers - */ - - "CREATE TRIGGER ldb_entry_insert_tr" - " AFTER INSERT" - " ON ldb_entry" - " FOR EACH ROW" - " BEGIN" - " UPDATE ldb_entry" - " SET create_timestamp = strftime('%s', 'now')," - " modify_timestamp = strftime('%s', 'now')" - " ," - " tree_key = COALESCE(tree_key, " - " (" - " SELECT tree_key || " - " (SELECT base160(max_child_num + 1)" - " FROM ldb_entry" - " WHERE eid = new.peid)" - " FROM ldb_entry " - " WHERE eid = new.peid " - " ));" - " UPDATE ldb_entry " - " SET max_child_num = max_child_num + 1" - " WHERE eid = new.peid;" - " END;" - - "CREATE TRIGGER ldb_entry_update_tr" - " AFTER UPDATE" - " ON ldb_entry" - " FOR EACH ROW" - " BEGIN" - " UPDATE ldb_entry" - " SET modify_timestamp = strftime('%s', 'now')" - " WHERE eid = old.eid;" - " END;" - - "CREATE TRIGGER ldb_object_classes_insert_tr" - " AFTER INSERT" - " ON ldb_object_classes" - " FOR EACH ROW" - " BEGIN" - " UPDATE ldb_object_classes" - " SET tree_key = COALESCE(tree_key, " - " (" - " SELECT tree_key || " - " (SELECT base160(max_child_num + 1)" - " FROM ldb_object_classes" - " WHERE class_name = " - " new.parent_class_name)" - " FROM ldb_object_classes " - " WHERE class_name = new.parent_class_name " - " ));" - " UPDATE ldb_object_classes " - " SET max_child_num = max_child_num + 1" - " WHERE class_name = new.parent_class_name;" - " END;" - - /* - * Table initialization - */ +/* rename a record */ +static int lsqlite3_safe_rollback(sqlite3 *sqlite) +{ + char *errmsg; + int ret; - /* The root node */ - "INSERT INTO ldb_entry " - " (eid, peid, dn, normalized_dn, tree_key) " - " VALUES " - " (0, NULL, '', '', '0001');" + /* execute */ + ret = sqlite3_exec(sqlite, "ROLLBACK;", NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_safe_rollback: Serious Error: %s\n", errmsg); + free(errmsg); + } + return -1; + } - /* And the root node "dn" attribute */ - "INSERT INTO ldb_attribute_values " - " (eid, attr_name, attr_value, attr_value_normalized) " - " VALUES " - " (0, 'DN', '', '');" + return 0; +} - "INSERT INTO ldb_object_classes " - " (class_name, tree_key) " - " VALUES " - " ('TOP', '0001');" +/* return an eid as result */ +static int lsqlite3_eid_callback(void *result, int col_num, char **cols, char **names) +{ + long long *eid = (long long *)result; - ; - - /* Skip protocol indicator of url */ - if (strncmp(url, "sqlite://", 9) != 0) { - return SQLITE_MISUSE; - } - - /* Update pointer to just after the protocol indicator */ - url += 9; - - /* Try to open the (possibly empty/non-existent) database */ - if ((ret = sqlite3_open(url, &lsqlite3->sqlite)) != SQLITE_OK) { - return ret; - } - - /* In case this is a new database, enable auto_vacuum */ - if (query_norows(lsqlite3, "PRAGMA auto_vacuum=1;") != 0) { - return -1; - } - - /* Establish a busy timeout of 30 seconds */ - if ((ret = sqlite3_busy_timeout(lsqlite3->sqlite, - 30000)) != SQLITE_OK) { - return ret; - } + if (col_num != 1) return SQLITE_ABORT; + if (strcasecmp(names[0], "eid") != 0) return SQLITE_ABORT; - /* Create a function, callable from sql, to increment a tree_key */ - if ((ret = - sqlite3_create_function(lsqlite3->sqlite,/* handle */ - "base160_next", /* function name */ - 1, /* number of args */ - SQLITE_ANY, /* preferred text type */ - NULL, /* user data */ - base160next_sql, /* called func */ - NULL, /* step func */ - NULL /* final func */ - )) != SQLITE_OK) { - return ret; - } + *eid = atoll(cols[0]); + return SQLITE_OK; +} - /* Create a function, callable from sql, to convert int to base160 */ - if ((ret = - sqlite3_create_function(lsqlite3->sqlite,/* handle */ - "base160", /* function name */ - 1, /* number of args */ - SQLITE_ANY, /* preferred text type */ - NULL, /* user data */ - base160_sql, /* called func */ - NULL, /* step func */ - NULL /* final func */ - )) != SQLITE_OK) { - return ret; - } +struct lsqlite3_msgs { + int count; + struct ldb_message **msgs; + long long current_eid; + TALLOC_CTX *mem_ctx; +}; - /* Begin a transaction */ - if ((ret = query_norows(lsqlite3, "BEGIN EXCLUSIVE;")) != 0) { - return ret; - } - - /* Determine if this is a new database. No tables means it is. */ - if (query_int(lsqlite3, - &queryInt, - "SELECT COUNT(*)\n" - " FROM sqlite_master\n" - " WHERE type = 'table';") != 0) { - query_norows(lsqlite3, "ROLLBACK;"); - return -1; - } - - if (queryInt == 0) { - /* - * Create the database schema - */ - for (pTail = discard_const_p(char, schema); - pTail != NULL && *pTail != '\0'; - ) { - - if (lsqlite3_debug & SQLITE3_DEBUG_INIT) { - printf("Execute first query in:\n%s\n", pTail); - } - - if ((ret = sqlite3_prepare( - lsqlite3->sqlite, - pTail, - -1, - &stmt, - &pTail)) != SQLITE_OK || - (ret = sqlite3_step(stmt)) != SQLITE_DONE || - (ret = sqlite3_finalize(stmt)) != SQLITE_OK) { - - if (lsqlite3_debug & SQLITE3_DEBUG_INIT) { - printf("%s\n", - sqlite3_errmsg(lsqlite3->sqlite)); - printf("pTail = [%s]\n", pTail); - } - - query_norows(lsqlite3, "ROLLBACK;"); - (void) sqlite3_close(lsqlite3->sqlite); - return ret; - } - } - } else { - /* - * Ensure that the database we opened is one of ours - */ - if (query_int(lsqlite3, - &queryInt, - "SELECT " - " (SELECT COUNT(*) = 2" - " FROM sqlite_master " - " WHERE type = 'table' " - " AND name IN " - " (" - " 'ldb_entry', " - " 'ldb_object_classes' " - " ) " - " ) " - " AND " - " (SELECT 1 " - " FROM ldb_info " - " WHERE database_type = 'LDB' " - " AND version = '1.0'" - " );") != 0 || - queryInt != 1) { - - /* It's not one that we created. See ya! */ - query_norows(lsqlite3, "ROLLBACK;"); - (void) sqlite3_close(lsqlite3->sqlite); - return SQLITE_MISUSE; - } - } - - /* - * Create a temporary table to hold attributes requested in the result - * set of a search. - */ - query_norows(lsqlite3, "DROP TABLE " RESULT_ATTR_TABLE ";\n"); - if ((ret = - query_norows(lsqlite3, - "CREATE " TEMPTAB " TABLE " RESULT_ATTR_TABLE "\n" - " (\n" - " attr_name TEXT PRIMARY KEY\n" - " );")) != 0) { - query_norows(lsqlite3, "ROLLBACK;"); - return ret; - } +/* + * add a single set of ldap message values to a ldb_message + */ - /* - * Create a temporary table to hold the attributes used by filters - * during a search. - */ - query_norows(lsqlite3, "DROP TABLE " FILTER_ATTR_TABLE ";\n"); - if ((ret = - query_norows(lsqlite3, - "CREATE " TEMPTAB " TABLE " FILTER_ATTR_TABLE "\n" - " (\n" - " attr_name TEXT PRIMARY KEY\n" - " );")) != 0) { - query_norows(lsqlite3, "ROLLBACK;"); - return ret; - } +static int lsqlite3_search_callback(void *result, int col_num, char **cols, char **names) +{ + struct lsqlite3_msgs *msgs = (struct lsqlite3_msgs *)result; + struct ldb_message *msg; + long long eid; - /* Commit the transaction */ - if ((ret = query_norows(lsqlite3, "COMMIT;")) != 0) { - query_norows(lsqlite3, "ROLLBACK;"); - return ret; - } - - return SQLITE_OK; + /* eid, dn, attr_name, attr_value */ + if (col_num != 4) return SQLITE_ABORT; + + eid = atoll(cols[0]); + + if (eid != msgs->current_eid) { + msgs->msgs = talloc_realloc(msgs->mem_ctx, + msgs->msgs, + struct ldb_message *, + msgs->count + 2); + if (msgs->msgs == NULL) return SQLITE_ABORT; + + msgs->msgs[msgs->count] = talloc(msgs->msgs, struct ldb_message); + if (msgs->msgs[msgs->count] == NULL) return SQLITE_ABORT; + + msgs->msgs[msgs->count]->dn = NULL; + msgs->msgs[msgs->count]->num_elements = 0; + msgs->msgs[msgs->count]->elements = NULL; + msgs->msgs[msgs->count]->private_data = NULL; + + msgs->count++; + msgs->current_eid = eid; + } + + msg = msgs->msgs[msgs->count -1]; + + if (msg->dn == NULL) { + msg->dn = ldb_dn_explode(msg, cols[1]); + if (msg->dn == NULL) return SQLITE_ABORT; + } + + msg->elements = talloc_realloc(msg, + msg->elements, + struct ldb_message_element, + msg->num_elements + 1); + if (msg->elements == NULL) return SQLITE_ABORT; + + msg->elements[msg->num_elements].flags = 0; + msg->elements[msg->num_elements].name = talloc_strdup(msg->elements, cols[2]); + if (msg->elements[msg->num_elements].name == NULL) return SQLITE_ABORT; + + msg->elements[msg->num_elements].num_values = 1; + msg->elements[msg->num_elements].values = talloc_array(msg->elements, + struct ldb_val, 1); + if (msg->elements[msg->num_elements].values == NULL) return SQLITE_ABORT; + + msg->elements[msg->num_elements].values[0].length = strlen(cols[3]); + msg->elements[msg->num_elements].values[0].data = talloc_strdup(msg->elements, cols[3]); + if (msg->elements[msg->num_elements].values[0].data == NULL) return SQLITE_ABORT; + + msg->num_elements++; + + return SQLITE_OK; } -static int -destructor(void *p) + +/* + * lsqlite3_get_eid() + * lsqlite3_get_eid_ndn() + * + * These functions are used for the very common case of retrieving an EID value + * given a (normalized) DN. + */ + +static long long lsqlite3_get_eid_ndn(sqlite3 *sqlite, void *mem_ctx, const char *norm_dn) { - struct lsqlite3_private * lsqlite3 = p; - - if (lsqlite3->sqlite) { - sqlite3_close(lsqlite3->sqlite); + char *errmsg; + char *query; + long long eid = -1; + long long ret; + + /* get object eid */ + query = lsqlite3_tprintf(mem_ctx, "SELECT eid " + "FROM ldb_entry " + "WHERE norm_dn = '%q';", norm_dn); + if (query == NULL) return -1; + + ret = sqlite3_exec(sqlite, query, lsqlite3_eid_callback, &eid, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_get_eid: Fatal Error: %s\n", errmsg); + free(errmsg); + } + return -1; } - return 0; + + return eid; } +static long long lsqlite3_get_eid(struct ldb_module *module, const struct ldb_dn *dn) +{ + TALLOC_CTX *local_ctx; + struct lsqlite3_private *lsqlite3 = module->private_data; + long long eid = -1; + char *cdn; + + /* ignore ltdb specials */ + if (ldb_dn_is_special(dn)) { + return -1; + } + + /* create a local ctx */ + local_ctx = talloc_named(lsqlite3, 0, "lsqlite3_get_eid local context"); + if (local_ctx == NULL) { + return -1; + } + + cdn = ldb_dn_linearize(local_ctx, ldb_dn_casefold(module->ldb, dn)); + if (!cdn) goto done; + + eid = lsqlite3_get_eid_ndn(lsqlite3->sqlite, local_ctx, cdn); + +done: + talloc_free(local_ctx); + return eid; +} /* - * query_norows() - * - * This function is used for queries that are not expected to return any rows, - * e.g. BEGIN, COMMIT, ROLLBACK, CREATE TABLE, INSERT, UPDATE, DELETE, etc. - * There are no provisions here for returning data from rows in a table, so do - * not pass SELECT queries to this function. + * Interface functions referenced by lsqlite3_ops */ -static int -query_norows(const struct lsqlite3_private *lsqlite3, - const char *pSql, - ...) + +/* search for matching records, by tree */ +static int lsqlite3_search_bytree(struct ldb_module * module, const struct ldb_dn* basedn, + enum ldb_scope scope, struct ldb_parse_tree * tree, + const char * const * attrs, struct ldb_message *** res) { - int ret; - int bLoop; - char * p; - sqlite3_stmt * pStmt; - va_list args; - double t0 = 0; - double t1; - struct timeval tv; - struct timezone tz; - - if (lsqlite3_debug & SQLITE3_DEBUG_QUERY) { - gettimeofday(&tv, &tz); - t0 = (double) tv.tv_sec + ((double) tv.tv_usec / 1000000.0); + TALLOC_CTX *local_ctx; + struct lsqlite3_private *lsqlite3 = module->private_data; + struct lsqlite3_msgs msgs; + char *norm_basedn; + char *attr_list; + char *sqlfilter; + char *errmsg; + char *query; + int ret, i; + + /* create a local ctx */ + local_ctx = talloc_named(lsqlite3, 0, "lsqlite3_search_by_tree local context"); + if (local_ctx == NULL) { + return -1; } - /* Begin access to variable argument list */ - va_start(args, pSql); - - /* Format the query */ - if ((p = sqlite3_vmprintf(pSql, args)) == NULL) { - return -1; - } + if (basedn) { + norm_basedn = ldb_dn_linearize(local_ctx, ldb_dn_casefold(module->ldb, basedn)); + if (norm_basedn == NULL) goto failed; + } else norm_basedn = talloc_strdup(local_ctx, ""); + + if (*norm_basedn == '\0' && + (scope == LDB_SCOPE_BASE || scope == LDB_SCOPE_ONELEVEL)) + goto failed; + + /* Convert filter into a series of SQL conditions (constraints) */ + sqlfilter = parsetree_to_sql(module, local_ctx, tree); - /* - * Prepare and execute the SQL statement. Loop allows retrying on - * certain errors, e.g. SQLITE_SCHEMA occurs if the schema changes, - * requiring retrying the operation. - */ - for (bLoop = TRUE; bLoop; ) { - - /* Compile the SQL statement into sqlite virtual machine */ - if ((ret = sqlite3_prepare(lsqlite3->sqlite, - p, - -1, - &pStmt, - NULL)) == SQLITE_SCHEMA) { - if (stmtGetEID != NULL) { - sqlite3_finalize(stmtGetEID); - stmtGetEID = NULL; - } - continue; - } else if (ret != SQLITE_OK) { - ret = -1; - break; - } - - /* No rows expected, so just step through machine code once */ - if ((ret = sqlite3_step(pStmt)) == SQLITE_SCHEMA) { - if (stmtGetEID != NULL) { - sqlite3_finalize(stmtGetEID); - stmtGetEID = NULL; - } - (void) sqlite3_finalize(pStmt); - continue; - } else if (ret != SQLITE_DONE) { - (void) sqlite3_finalize(pStmt); - ret = -1; - break; - } + /* Initially, we don't know what the requested attributes are */ + if (attrs != NULL) { + attr_list = talloc_strdup(local_ctx, "AND norm_attr_name IN ("); + if (attr_list == NULL) goto failed; + + for (i = 0; attrs[i]; i++) { + char *norm_attr_name; + + /* If any attribute in the list is "*" then... */ + if (strcmp(attrs[i], "*") == 0) { + /* we want all attribute types */ + attr_list = talloc_strdup(local_ctx, ""); + if (attr_list == NULL) goto failed; + break; + } + + norm_attr_name = ldb_casefold(local_ctx, attrs[i]); + if (norm_attr_name == NULL) goto failed; + + attr_list = talloc_asprintf_append(attr_list, "'%q', ", + norm_attr_name); + if (attr_list == NULL) goto failed; + + } + + /* substitute the last ',' with ')' */ + attr_list[strlen(attr_list)-2] = ')'; + + } else { + attr_list = talloc_strdup(local_ctx, ""); + if (attr_list == NULL) goto failed; + } + + switch(scope) { + case LDB_SCOPE_DEFAULT: + case LDB_SCOPE_SUBTREE: + if (*norm_basedn != '\0') { + query = lsqlite3_tprintf(local_ctx, + "SELECT entry.eid,\n" + " entry.dn,\n" + " av.attr_name,\n" + " av.attr_value\n" + " FROM ldb_entry AS entry\n" + + " LEFT OUTER JOIN ldb_attribute_values AS av\n" + " ON av.eid = entry.eid\n" + + " WHERE entry.eid IN\n" + " (SELECT DISTINCT ldb_entry.eid\n" + " FROM ldb_entry\n" + " WHERE (ldb_entry.norm_dn GLOB('*,%q')\n" + " OR ldb_entry.norm_dn = '%q')\n" + " AND ldb_entry.eid IN\n" + " (%s)\n" + " )\n" + + " %s\n" + + " ORDER BY entry.eid ASC;", + norm_basedn, + norm_basedn, + sqlfilter, + attr_list); + } else { + query = lsqlite3_tprintf(local_ctx, + "SELECT entry.eid,\n" + " entry.dn,\n" + " av.attr_name,\n" + " av.attr_value\n" + " FROM ldb_entry AS entry\n" + + " LEFT OUTER JOIN ldb_attribute_values AS av\n" + " ON av.eid = entry.eid\n" + + " WHERE entry.eid IN\n" + " (SELECT DISTINCT ldb_entry.eid\n" + " FROM ldb_entry\n" + " WHERE ldb_entry.eid IN\n" + " (%s)\n" + " )\n" + + " %s\n" + + " ORDER BY entry.eid ASC;", + sqlfilter, + attr_list); + } + + break; - /* Free the virtual machine */ - if ((ret = sqlite3_finalize(pStmt)) == SQLITE_SCHEMA) { - if (stmtGetEID != NULL) { - sqlite3_finalize(stmtGetEID); - stmtGetEID = NULL; - } - continue; - } else if (ret != SQLITE_OK) { - (void) sqlite3_finalize(pStmt); - ret = -1; - break; - } + case LDB_SCOPE_BASE: + query = lsqlite3_tprintf(local_ctx, + "SELECT entry.eid,\n" + " entry.dn,\n" + " av.attr_name,\n" + " av.attr_value\n" + " FROM ldb_entry AS entry\n" + + " LEFT OUTER JOIN ldb_attribute_values AS av\n" + " ON av.eid = entry.eid\n" + + " WHERE entry.eid IN\n" + " (SELECT DISTINCT ldb_entry.eid\n" + " FROM ldb_entry\n" + " WHERE ldb_entry.norm_dn = '%q'\n" + " AND ldb_entry.eid IN\n" + " (%s)\n" + " )\n" + + " %s\n" + + " ORDER BY entry.eid ASC;", + norm_basedn, + sqlfilter, + attr_list); + break; - /* - * Normal condition is only one time through loop. Loop is - * rerun in error conditions, via "continue", above. - */ - ret = 0; - bLoop = FALSE; + case LDB_SCOPE_ONELEVEL: + query = lsqlite3_tprintf(local_ctx, + "SELECT entry.eid,\n" + " entry.dn,\n" + " av.attr_name,\n" + " av.attr_value\n" + " FROM ldb_entry AS entry\n" + + " LEFT OUTER JOIN ldb_attribute_values AS av\n" + " ON av.eid = entry.eid\n" + + " WHERE entry.eid IN\n" + " (SELECT DISTINCT ldb_entry.eid\n" + " FROM ldb_entry\n" + " WHERE norm_dn GLOB('*,%q')\n" + " AND NOT norm_dn GLOB('*,*,%q')\n" + " AND ldb_entry.eid IN\n(%s)\n" + " )\n" + + " %s\n" + + " ORDER BY entry.eid ASC;", + norm_basedn, + norm_basedn, + sqlfilter, + attr_list); + break; } - - /* All done with variable argument list */ - va_end(args); - - if (lsqlite3_debug & SQLITE3_DEBUG_QUERY) { - gettimeofday(&tv, NULL); - t1 = (double) tv.tv_sec + ((double) tv.tv_usec / 1000000.0); - printf("%1.6lf %s\n%s\n\n", t1 - t0, - ret == 0 ? "SUCCESS" : "FAIL", - p); + + if (query == NULL) { + ret = -1; + goto failed; } - /* Free the memory we allocated for our query string */ - sqlite3_free(p); - - return ret; -} + /* printf ("%s\n", query); */ + msgs.msgs = NULL; + msgs.count = 0; + msgs.current_eid = 0; + msgs.mem_ctx = local_ctx; -/* - * query_int() - * - * This function is used for the common case of queries that return a single - * integer value. - * - * NOTE: If more than one value is returned by the query, all but the first - * one will be ignored. - */ -static int -query_int(const struct lsqlite3_private * lsqlite3, - long long * pRet, - const char * pSql, - ...) -{ - int ret; - int bLoop; - char * p; - sqlite3_stmt * pStmt; - va_list args; - double t0 = 0; - double t1; - struct timeval tv; - struct timezone tz; - - if (lsqlite3_debug & SQLITE3_DEBUG_QUERY) { - gettimeofday(&tv, &tz); - t0 = (double) tv.tv_sec + ((double) tv.tv_usec / 1000000.0); + ret = sqlite3_exec(lsqlite3->sqlite, query, lsqlite3_search_callback, &msgs, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_search_bytree: Fatal Error: %s\n", errmsg); + free(errmsg); + } + goto failed; } - /* Begin access to variable argument list */ - va_start(args, pSql); - - /* Format the query */ - if ((p = sqlite3_vmprintf(pSql, args)) == NULL) { - return SQLITE_NOMEM; - } + for (i = 0; i < msgs.count; i++) { + msgs.msgs[i] = ldb_msg_canonicalize(module->ldb, msgs.msgs[i]); + if (msgs.msgs[i] == NULL) goto failed; + } + + *res = talloc_steal(module, msgs.msgs); + ret = msgs.count; + + talloc_free(local_ctx); + return ret; + +/* If error, return error code; otherwise return number of results */ +failed: + talloc_free(local_ctx); + return -1; +} + +/* search for matching records, by expression */ +static int lsqlite3_search(struct ldb_module * module, const struct ldb_dn *basedn, + enum ldb_scope scope, const char * expression, + const char * const *attrs, struct ldb_message *** res) +{ + struct ldb_parse_tree * tree; + int ret; - if (lsqlite3_debug & SQLITE3_DEBUG_QUERY) { - printf("%s\n", p); + /* Handle tdb specials */ + if (ldb_dn_is_special(basedn)) { +#warning "handle tdb specials" + return 0; } - /* - * Prepare and execute the SQL statement. Loop allows retrying on - * certain errors, e.g. SQLITE_SCHEMA occurs if the schema changes, - * requiring retrying the operation. - */ - for (bLoop = TRUE; bLoop; ) { - - /* Compile the SQL statement into sqlite virtual machine */ - if ((ret = sqlite3_prepare(lsqlite3->sqlite, - p, - -1, - &pStmt, - NULL)) == SQLITE_SCHEMA) { - if (stmtGetEID != NULL) { - sqlite3_finalize(stmtGetEID); - stmtGetEID = NULL; - } - continue; - } else if (ret != SQLITE_OK) { - break; - } - - /* One row expected */ - if ((ret = sqlite3_step(pStmt)) == SQLITE_SCHEMA) { - if (stmtGetEID != NULL) { - sqlite3_finalize(stmtGetEID); - stmtGetEID = NULL; - } - (void) sqlite3_finalize(pStmt); - continue; - } else if (ret != SQLITE_ROW) { - (void) sqlite3_finalize(pStmt); - break; - } - - /* Get the value to be returned */ - *pRet = sqlite3_column_int64(pStmt, 0); - - /* Free the virtual machine */ - if ((ret = sqlite3_finalize(pStmt)) == SQLITE_SCHEMA) { - if (stmtGetEID != NULL) { - sqlite3_finalize(stmtGetEID); - stmtGetEID = NULL; - } - continue; - } else if (ret != SQLITE_OK) { - (void) sqlite3_finalize(pStmt); - break; - } - - /* - * Normal condition is only one time through loop. Loop is - * rerun in error conditions, via "continue", above. - */ - bLoop = FALSE; +#if 0 +/* (|(objectclass=*)(dn=*)) is passed by the command line tool now instead */ + /* Handle the special case of requesting all */ + if (pExpression != NULL && *pExpression == '\0') { + pExpression = "dn=*"; } +#endif + + /* Parse the filter expression into a tree we can work with */ + if ((tree = ldb_parse_tree(module->ldb, expression)) == NULL) { + return -1; + } - /* All done with variable argument list */ - va_end(args); + /* Now use the bytree function for the remainder of processing */ + ret = lsqlite3_search_bytree(module, basedn, scope, tree, attrs, res); - - if (lsqlite3_debug & SQLITE3_DEBUG_QUERY) { - gettimeofday(&tv, NULL); - t1 = (double) tv.tv_sec + ((double) tv.tv_usec / 1000000.0); - printf("%1.6lf %s\n%s\n\n", t1 - t0, - ret == 0 ? "SUCCESS" : "FAIL", - p); - } - - /* Free the memory we allocated for our query string */ - sqlite3_free(p); + /* Free the parse tree */ + talloc_free(tree); + /* All done. */ return ret; } -/* - * getEID() - * - * This function is used for the very common case of retrieving an EID value - * given a normalized DN. - * - * NOTE: If more than one value is returned by the query, all but the first - * one will be ignored. - */ -static int -getEID(const struct lsqlite3_private * lsqlite3, - long long * pRet, - const char * pNormalizedDN) +/* add a record */ +static int lsqlite3_add(struct ldb_module *module, const struct ldb_message *msg) { - int ret; - int bLoop; - const char * query = - "SELECT eid\n" - " FROM ldb_attribute_values\n" - " WHERE attr_name = 'DN'\n" - " AND attr_value_normalized = :dn;"; - - /* - * Prepare and execute the SQL statement. Loop allows retrying on - * certain errors, e.g. SQLITE_SCHEMA occurs if the schema changes, - * requiring retrying the operation. - */ - for (bLoop = TRUE; bLoop; ) { - - /* Compile the SQL statement into sqlite virtual machine */ - ret = SQLITE_OK; - if (stmtGetEID == NULL && - (ret = sqlite3_prepare(lsqlite3->sqlite, - query, - -1, - &stmtGetEID, - NULL)) == SQLITE_SCHEMA) { - continue; - } else if (ret != SQLITE_OK) { - break; - } - - /* Bind our parameter */ - if ((ret = sqlite3_bind_text(stmtGetEID, - 1, - pNormalizedDN, - -1, - SQLITE_STATIC)) != SQLITE_OK) { - break; - } + TALLOC_CTX *local_ctx; + struct lsqlite3_private *lsqlite3 = module->private_data; + long long eid; + char *dn, *ndn; + char *errmsg; + char *query; + int rollback = 0; + int ret; + int i; + + /* create a local ctx */ + local_ctx = talloc_named(lsqlite3, 0, "lsqlite3_add local context"); + if (local_ctx == NULL) { + return -1; + } - /* One row expected */ - if ((ret = sqlite3_step(stmtGetEID)) == SQLITE_SCHEMA) { - (void) sqlite3_finalize(stmtGetEID); - stmtGetEID = NULL; - continue; - } else if (ret != SQLITE_ROW) { - (void) sqlite3_reset(stmtGetEID); - break; - } - - /* Get the value to be returned */ - *pRet = sqlite3_column_int64(stmtGetEID, 0); - - /* Free the virtual machine */ - if ((ret = sqlite3_reset(stmtGetEID)) == SQLITE_SCHEMA) { - (void) sqlite3_finalize(stmtGetEID); - stmtGetEID = NULL; - continue; - } else if (ret != SQLITE_OK) { - (void) sqlite3_finalize(stmtGetEID); - stmtGetEID = NULL; - break; - } - - /* - * Normal condition is only one time through loop. Loop is - * rerun in error conditions, via "continue", above. - */ - bLoop = FALSE; - } - - return ret; -} + /* See if this is an ltdb special */ + if (ldb_dn_is_special(msg->dn)) { + struct ldb_dn *c; + c = ldb_dn_explode(local_ctx, "@SUBCLASSES"); + if (ldb_dn_compare(module->ldb, msg->dn, c) == 0) { +#warning "insert subclasses into object class tree" + goto failed; + } -/* - callback function used in call to ldb_dn_fold() for determining whether an - attribute type requires case folding. -*/ -static int -case_fold_attr_required(void * hUserData, - char *attr) -{ -// struct ldb_module * module = hUserData; - - return TRUE; -} + c = ldb_dn_explode(local_ctx, "@INDEXLIST"); + if (ldb_dn_compare(module->ldb, msg->dn, c) == 0) { +#warning "should we handle indexes somehow ?" + goto failed; + } -static int -case_fold_attr_not_required(void * hUserData, - char *attr) -{ -// struct ldb_module * module = hUserData; - - return FALSE; -} + /* Others are implicitly ignored */ + return 0; + } + + /* create linearized and normalized dns */ + dn = ldb_dn_linearize(local_ctx, msg->dn); + ndn = ldb_dn_linearize(local_ctx, ldb_dn_casefold(module->ldb, msg->dn)); + if (dn == NULL || ndn == NULL) goto failed; + + query = lsqlite3_tprintf(local_ctx, + /* Begin the transaction */ + "BEGIN EXCLUSIVE; " + /* Add new entry */ + "INSERT OR ABORT INTO ldb_entry " + "('dn', 'norm_dn') " + "VALUES ('%q', '%q');", + dn, ndn); + if (query == NULL) goto failed; + + ret = sqlite3_exec(lsqlite3->sqlite, query, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_add: exec error: %s\n", errmsg); + free(errmsg); + } + lsqlite3_safe_rollback(lsqlite3->sqlite); + goto failed; + } + rollback = 1; + eid = lsqlite3_get_eid_ndn(lsqlite3->sqlite, local_ctx, ndn); + if (eid == -1) goto failed; -/* - * add a single set of ldap message values to a ldb_message - */ + for (i = 0; i < msg->num_elements; i++) { + const struct ldb_message_element *el = &msg->elements[i]; + const struct ldb_attrib_handler *h; + char *attr; + int j; -static int -add_msg_attr(void * hTalloc, - long long eid, - const char * pDN, - const char * pAttrName, - const char * pAttrValue, - long long prevEID, - int * pAllocated, - struct ldb_message *** pppRes) -{ - void * x; - struct ldb_message * msg; - struct ldb_message_element * el; - - /* Is this a different EID than the previous one? */ - if (eid != prevEID) { - /* Yup. Add another result to the result array */ - if ((x = talloc_realloc(hTalloc, - *pAllocated == 0 ? NULL : *pppRes, - struct ldb_message *, - *pAllocated + 1)) == NULL) { - - return -1; - } - - /* Save the new result list */ - *pppRes = x; + /* Get a case-folded copy of the attribute name */ + attr = ldb_casefold(local_ctx, el->name); + if (attr == NULL) goto failed; - /* Allocate a new result structure */ - if ((x = talloc(*pppRes, struct ldb_message)) == NULL) { - return -1; - } + h = ldb_attrib_handler(module->ldb, el->name); - /* Save the new result */ - (*pppRes)[*pAllocated] = x; + /* For each value of the specified attribute name... */ + for (j = 0; j < el->num_values; j++) { + struct ldb_val value; + char *insert; + + /* Get a canonicalised copy of the data */ + h->canonicalise_fn(module->ldb, local_ctx, &(el->values[j]), &value); + if (value.data == NULL) goto failed; + + insert = lsqlite3_tprintf(local_ctx, + "INSERT OR ROLLBACK INTO ldb_attribute_values " + "('eid', 'attr_name', 'norm_attr_name'," + " 'attr_value', 'norm_attr_value') " + "VALUES ('%lld', '%q', '%q', '%q', '%q');", + eid, el->name, attr, + el->values[j].data, value.data); + if (insert == NULL) goto failed; + + ret = sqlite3_exec(lsqlite3->sqlite, insert, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_add: insert error: %s\n", errmsg); + free(errmsg); + } + goto failed; + } + } + } - /* Steal the initial result and put it in its own context */ - talloc_steal(NULL, *pppRes); + ret = sqlite3_exec(lsqlite3->sqlite, "COMMIT;", NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_add: commit error: %s\n", errmsg); + free(errmsg); + } + goto failed; + } - /* We've allocated one more result */ - ++*pAllocated; - - /* Ensure that the message is initialized */ - msg = x; - if ((msg->dn = talloc_strdup(msg, pDN)) == NULL) { - return -1; - } - msg->num_elements = 0; - msg->elements = NULL; - msg->private_data = NULL; - } else { - /* Same EID. Point to the previous most-recent message */ - msg = (*pppRes)[*pAllocated - 1]; - } - - if (pAttrName != NULL && pAttrValue != NULL) { - /* - * Point to the most recent previous element. (If there are none, - * this will point to non-allocated memory, but the pointer will - * never be dereferenced.) - */ - el = &msg->elements[msg->num_elements - 1]; - - /* - * See if the most recent previous element has the same attr_name - */ - if (msg->num_elements == 0 || strcmp(el->name, pAttrName) != 0) { - - /* It's a new attr_name. Allocate another message element */ - if ((el = talloc_realloc(msg, - msg->elements, - struct ldb_message_element, - msg->num_elements + 1)) == NULL) { - return -1; - } - - /* Save the new element */ - msg->elements = el; - - /* Save the attribute name */ - if ((el->name = - talloc_strdup(msg->elements, pAttrName)) == NULL) { - - return -1; - } - - /* There's now one additional element */ - msg->num_elements++; - - /* No flags */ - el->flags = 0; - - /* Initialize number of attribute values for this type */ - el->num_values = 0; - el->values = NULL; - } - - /* Increase the value array size by 1 */ - if ((el->values = - talloc_realloc(el, - el->num_values == 0 ? NULL : el->values, - struct ldb_val, - el->num_values + 1)) == NULL) { - return -1; - } - - /* Save the new attribute value length */ - el->values[el->num_values].length = strlen(pAttrValue); - - /* Copy the new attribute value */ - if ((el->values[el->num_values].data = - talloc_memdup(el->values, - pAttrValue, - el->values[el->num_values].length)) == NULL) { - return -1; - } - - /* We now have one additional value of this type */ - el->num_values++; - } - - return 0; + talloc_free(local_ctx); + return 0; + +failed: + if (rollback) lsqlite3_safe_rollback(lsqlite3->sqlite); + talloc_free(local_ctx); + return -1; } -static char * -parsetree_to_sql(struct ldb_module *module, - char * hTalloc, - const struct ldb_parse_tree *t) + +/* modify a record */ +static int lsqlite3_modify(struct ldb_module *module, const struct ldb_message *msg) { - int i; - char * pNormalizedDN; - char * child; - char * p; - char * ret = NULL; - char * pAttrName; - - - switch(t->operation) { - case LDB_OP_SIMPLE: - break; - - case LDB_OP_EXTENDED: -#warning "work out how to handle bitops" - return NULL; + TALLOC_CTX *local_ctx; + struct lsqlite3_private *lsqlite3 = module->private_data; + long long eid; + char *errmsg; + int rollback = 0; + int ret; + int i; + + /* create a local ctx */ + local_ctx = talloc_named(lsqlite3, 0, "lsqlite3_modify local context"); + if (local_ctx == NULL) { + return -1; + } - case LDB_OP_AND: - ret = parsetree_to_sql(module, - hTalloc, - t->u.list.elements[0]); - - for (i = 1; i < t->u.list.num_elements; i++) { - child = - parsetree_to_sql( - module, - hTalloc, - t->u.list.elements[i]); - ret = talloc_asprintf_append(ret, - "INTERSECT\n" - "%s\n", - child); - talloc_free(child); - } - - child = ret; - ret = talloc_asprintf(hTalloc, - "SELECT * FROM (\n" - "%s\n" - ")\n", - child); - talloc_free(child); - return ret; - - case LDB_OP_OR: - ret = parsetree_to_sql(module, - hTalloc, - t->u.list.elements[0]); - - for (i = 1; i < t->u.list.num_elements; i++) { - child = - parsetree_to_sql( - module, - hTalloc, - t->u.list.elements[i]); - ret = talloc_asprintf_append(ret, - "UNION\n" - "%s\n", - child); - talloc_free(child); - } - child = ret; - ret = talloc_asprintf(hTalloc, - "SELECT * FROM (\n" - "%s\n" - ")\n", - child); - talloc_free(child); - return ret; - - case LDB_OP_NOT: - child = - parsetree_to_sql( - module, - hTalloc, - t->u.not.child); - ret = talloc_asprintf(hTalloc, - " SELECT eid\n" - " FROM ldb_entry\n" - " WHERE eid NOT IN (%s)\n", - child); - talloc_free(child); - return ret; - - default: - /* should never occur */ - abort(); - }; - - /* Get a case-folded copy of the attribute name */ - pAttrName = ldb_casefold((struct ldb_context *) module, - t->u.simple.attr); - - /* - * For simple searches, we want to retrieve the list of EIDs that - * match the criteria. - */ - if (t->u.simple.value.length == 1 && - (*(const char *) t->u.simple.value.data) == '*') { - /* - * Special case for "attr_name=*". In this case, we want the - * eid corresponding to all values in the specified attribute - * table. - */ - if ((p = sqlite3_mprintf(" SELECT eid\n" - " FROM ldb_attribute_values\n" - " WHERE attr_name = %Q", - pAttrName)) == NULL) { - return NULL; - } - - if (lsqlite3_debug & SQLITE3_DEBUG_QUERY) { - printf("%s\n", p); - } + /* See if this is an ltdb special */ + if (ldb_dn_is_special(msg->dn)) { + struct ldb_dn *c; - ret = talloc_strdup(hTalloc, p); - sqlite3_free(p); + c = ldb_dn_explode(local_ctx, "@SUBCLASSES"); + if (ldb_dn_compare(module->ldb, msg->dn, c) == 0) { +#warning "modify subclasses into object class tree" + goto failed; + } - } else if (strcasecmp(t->u.simple.attr, "objectclass") == 0) { - /* - * For object classes, we want to search for all objectclasses - * that are subclasses as well. - */ - if ((p = sqlite3_mprintf( - " SELECT eid\n" - " FROM ldb_attribute_values\n" - " WHERE attr_name = 'OBJECTCLASS' " - " AND attr_value_normalized IN\n" - " (SELECT class_name\n" - " FROM ldb_object_classes\n" - " WHERE tree_key GLOB\n" - " (SELECT tree_key\n" - " FROM ldb_object_classes\n" - " WHERE class_name = upper(%Q)) " - " || '*')\n", - t->u.simple.value.data)) == NULL) { - return NULL; - } - - if (lsqlite3_debug & SQLITE3_DEBUG_QUERY) { - printf("%s\n", p); - } + c = ldb_dn_explode(local_ctx, "@INDEXLIST"); + if (ldb_dn_compare(module->ldb, msg->dn, c) == 0) { +#warning "should we handle indexes somehow ?" + goto failed; + } - ret = talloc_strdup(hTalloc, p); - sqlite3_free(p); - - } else if (strcasecmp(t->u.simple.attr, "dn") == 0) { - pNormalizedDN = ldb_dn_fold(module->ldb, t->u.simple.value.data, - module, case_fold_attr_required); - if ((p = sqlite3_mprintf( - " SELECT eid\n" - " FROM ldb_attribute_values\n" - " WHERE attr_name = %Q\n" - " AND attr_value_normalized = %Q\n", - pAttrName, - pNormalizedDN)) == NULL) { - return NULL; - } - - if (lsqlite3_debug & SQLITE3_DEBUG_QUERY) { - printf("%s\n", p); - } + /* Others are implicitly ignored */ + return 0; + } - ret = talloc_strdup(hTalloc, p); - sqlite3_free(p); - } else { - /* A normal query. */ - if ((p = sqlite3_mprintf( - " SELECT eid\n" - " FROM ldb_attribute_values\n" - " WHERE attr_name = %Q\n" - " AND attr_value_normalized = upper(%Q)\n", - pAttrName, - t->u.simple.value.data)) == NULL) { - return NULL; - } - - if (lsqlite3_debug & SQLITE3_DEBUG_QUERY) { - printf("%s\n", p); - } + ret = sqlite3_exec(lsqlite3->sqlite, "BEGIN EXCLUSIVE;", NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_modify: error: %s\n", errmsg); + free(errmsg); + } + goto failed; + } + rollback = 1; - ret = talloc_strdup(hTalloc, p); - sqlite3_free(p); + eid = lsqlite3_get_eid(module, msg->dn); + if (eid == -1) { + goto failed; } - return ret; -} + for (i = 0; i < msg->num_elements; i++) { + const struct ldb_message_element *el = &msg->elements[i]; + const struct ldb_attrib_handler *h; + int flags = el->flags & LDB_FLAG_MOD_MASK; + char *attr; + char *mod; + int j; + + /* Get a case-folded copy of the attribute name */ + attr = ldb_casefold(local_ctx, el->name); + if (attr == NULL) { + goto failed; + } + h = ldb_attrib_handler(module->ldb, el->name); + + switch (flags) { + + case LDB_FLAG_MOD_REPLACE: + + /* remove all attributes before adding the replacements */ + mod = lsqlite3_tprintf(local_ctx, + "DELETE FROM ldb_attribute_values " + "WHERE eid = '%lld' " + "AND norm_attr_name = '%q';", + eid, attr); + if (mod == NULL) goto failed; + + ret = sqlite3_exec(lsqlite3->sqlite, mod, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_modify: error: %s\n", errmsg); + free(errmsg); + } + goto failed; + } -static int -parsetree_to_attrlist(struct ldb_module *module, - const struct ldb_parse_tree * t) -{ - int i; - struct lsqlite3_private * lsqlite3 = module->private_data; - - switch(t->operation) { - case LDB_OP_SIMPLE: - break; - - case LDB_OP_EXTENDED: -#warning "work out how to handle bitops" - return -1; + /* MISSING break is INTENTIONAL */ + + case LDB_FLAG_MOD_ADD: +#warning "We should throw an error if no value is provided!" + /* For each value of the specified attribute name... */ + for (j = 0; j < el->num_values; j++) { + struct ldb_val value; + + /* Get a canonicalised copy of the data */ + h->canonicalise_fn(module->ldb, local_ctx, &(el->values[j]), &value); + if (value.data == NULL) { + goto failed; + } + + mod = lsqlite3_tprintf(local_ctx, + "INSERT OR ROLLBACK INTO ldb_attribute_values " + "('eid', 'attr_name', 'norm_attr_name'," + " 'attr_value', 'norm_attr_value') " + "VALUES ('%lld', '%q', '%q', '%q', '%q');", + eid, el->name, attr, + el->values[j].data, value.data); + + if (mod == NULL) goto failed; + + ret = sqlite3_exec(lsqlite3->sqlite, mod, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_modify: error: %s\n", errmsg); + free(errmsg); + } + goto failed; + } + } - case LDB_OP_AND: - if (parsetree_to_attrlist( - module, - t->u.list.elements[0]) != 0) { - return -1; - } - - for (i = 1; i < t->u.list.num_elements; i++) { - if (parsetree_to_attrlist( - module, - t->u.list.elements[i]) != 0) { - return -1; - } - } - - return 0; - - case LDB_OP_OR: - if (parsetree_to_attrlist( - module, - t->u.list.elements[0]) != 0) { - return -1; - } - - for (i = 1; i < t->u.list.num_elements; i++) { - if (parsetree_to_attrlist( - module, - t->u.list.elements[i]) != 0) { - return -1; - } - } - - return 0; - - case LDB_OP_NOT: - if (parsetree_to_attrlist(module, - t->u.not.child) != 0) { - return -1; - } - - return 0; - - default: - /* should never occur */ - abort(); - }; - - QUERY_NOROWS(lsqlite3, - FALSE, - "INSERT OR IGNORE INTO " FILTER_ATTR_TABLE "\n" - " (attr_name)\n" - " VALUES\n" - " (%Q);", - t->u.simple.attr); - return 0; -} + break; + + case LDB_FLAG_MOD_DELETE: +#warning "We should throw an error if the attribute we are trying to delete does not exist!" + if (el->num_values == 0) { + mod = lsqlite3_tprintf(local_ctx, + "DELETE FROM ldb_attribute_values " + "WHERE eid = '%lld' " + "AND norm_attr_name = '%q';", + eid, attr); + if (mod == NULL) goto failed; + + ret = sqlite3_exec(lsqlite3->sqlite, mod, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_modify: error: %s\n", errmsg); + free(errmsg); + } + goto failed; + } + } + /* For each value of the specified attribute name... */ + for (j = 0; j < el->num_values; j++) { + struct ldb_val value; + + /* Get a canonicalised copy of the data */ + h->canonicalise_fn(module->ldb, local_ctx, &(el->values[j]), &value); + if (value.data == NULL) { + goto failed; + } + + mod = lsqlite3_tprintf(local_ctx, + "DELETE FROM ldb_attribute_values " + "WHERE eid = '%lld' " + "AND norm_attr_name = '%q' " + "AND norm_attr_value = '%q';", + eid, attr, value.data); + + if (mod == NULL) goto failed; + + ret = sqlite3_exec(lsqlite3->sqlite, mod, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_modify: error: %s\n", errmsg); + free(errmsg); + } + goto failed; + } + } -/* - * Issue a series of SQL statements to implement the ADD/MODIFY/DELETE - * requests in the ldb_message - */ -static int -msg_to_sql(struct ldb_module * module, - const struct ldb_message * msg, - long long eid, - int use_flags) -{ - int flags; - char * pAttrName; - unsigned int i; - unsigned int j; - struct lsqlite3_private * lsqlite3 = module->private_data; - - for (i = 0; i < msg->num_elements; i++) { - const struct ldb_message_element *el = &msg->elements[i]; - - if (! use_flags) { - flags = LDB_FLAG_MOD_ADD; - } else { - flags = el->flags & LDB_FLAG_MOD_MASK; - } - - /* Get a case-folded copy of the attribute name */ - pAttrName = ldb_casefold((struct ldb_context *) module, - el->name); - - /* For each value of the specified attribute name... */ - for (j = 0; j < el->num_values; j++) { - - /* ... bind the attribute value, if necessary */ - switch (flags) { - case LDB_FLAG_MOD_ADD: - QUERY_NOROWS( - lsqlite3, - FALSE, - "INSERT INTO ldb_attribute_values\n" - " (eid,\n" - " attr_name,\n" - " attr_value,\n" - " attr_value_normalized)\n" - " VALUES\n" - " (%lld, %Q, %Q, upper(%Q));", - eid, - pAttrName, - el->values[j].data, /* FIX ME */ - el->values[j].data); - - /* Is this a special "objectclass"? */ - if (strcasecmp(pAttrName, - "objectclass") != 0) { - /* Nope. */ - break; - } - - /* Handle special "objectclass" type */ - QUERY_NOROWS(lsqlite3, - FALSE, - "INSERT OR IGNORE " - " INTO ldb_object_classes " - " (class_name, " - " parent_class_name) " - " VALUES " - " (upper(%Q), 'TOP');", - ldb_casefold(module, - el->values[j].data)); - break; - - case LDB_FLAG_MOD_REPLACE: - QUERY_NOROWS( - lsqlite3, - FALSE, - "UPDATE ldb_attribute_values\n" - " SET attr_value = %Q,\n" - " attr_value_normalized =\n" - " upper(%Q)\n" - " WHERE eid = %lld\n" - " AND attr_name = %Q;", - el->values[j].data, /* FIX ME */ - el->values[j].data, - eid, - pAttrName); - break; - - case LDB_FLAG_MOD_DELETE: - /* No additional parameters to this query */ - QUERY_NOROWS( - lsqlite3, - FALSE, - "DELETE FROM ldb_attribute_values" - " WHERE eid = %lld " - " AND attr_name = %Q " - " AND attr_value_normalized =\n" - " upper(%Q);", - eid, - el->name, - el->values[j].data); - break; - } + break; } } - - return 0; -} + ret = sqlite3_exec(lsqlite3->sqlite, "COMMIT;", NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_modify: error: %s\n", errmsg); + free(errmsg); + } + goto failed; + } + + talloc_free(local_ctx); + return 0; +failed: + if (rollback) lsqlite3_safe_rollback(lsqlite3->sqlite); + talloc_free(local_ctx); + return -1; +} -static int -new_dn(struct ldb_module * module, - char * pDN, - long long * pEID) +/* delete a record */ +static int lsqlite3_delete(struct ldb_module *module, const struct ldb_dn *dn) { - int ret; - int bFirst; - int nComponent; - char * p; - char * pPartialDN; - char * pPartialNormalizedDN; - long long eid; - long long peid; - double t0 = 0; - double t1 = 0; - struct timeval tv; - struct timezone tz; - struct ldb_dn * pExplodedDN; - struct ldb_dn_component * pComponent; - struct ldb_context * ldb = module->ldb; - struct lsqlite3_private * lsqlite3 = module->private_data; - - if (lsqlite3_debug & SQLITE3_DEBUG_NEWDN) { - gettimeofday(&tv, &tz); - t0 = (double) tv.tv_sec + ((double) tv.tv_usec / 1000000.0); - } + TALLOC_CTX *local_ctx; + struct lsqlite3_private *lsqlite3 = module->private_data; + long long eid; + char *errmsg; + char *query; + int ret; + + /* ignore ltdb specials */ + if (ldb_dn_is_special(dn)) { + return 0; + } - /* Explode the DN */ - if ((pExplodedDN = - ldb_explode_dn(ldb, - pDN, - ldb, - case_fold_attr_not_required)) == NULL) { - return -1; - } - - if (lsqlite3_debug & SQLITE3_DEBUG_NEWDN) { - gettimeofday(&tv, NULL); - t1 = (double) tv.tv_sec + ((double) tv.tv_usec / 1000000.0); - printf("%1.6lf loc 1\n", t1 - t0); - t0 = t1; - } + /* create a local ctx */ + local_ctx = talloc_named(lsqlite3, 0, "lsqlite3_delete local context"); + if (local_ctx == NULL) { + return -1; + } - /* Allocate a string to hold the partial DN of each component */ - if ((pPartialDN = talloc_strdup(ldb, "")) == NULL) { - return -1; - } - - if ((pPartialNormalizedDN = talloc_strdup(pPartialDN, "")) == NULL) { - return -1; - } + eid = lsqlite3_get_eid(module, dn); + if (eid == -1) goto failed; + + query = lsqlite3_tprintf(local_ctx, + /* Begin the transaction */ + "BEGIN EXCLUSIVE; " + /* Delete entry */ + "DELETE FROM ldb_entry WHERE eid = %lld; " + /* Delete attributes */ + "DELETE FROM ldb_attribute_values WHERE eid = %lld; " + /* Commit */ + "COMMIT;", + eid, eid); + if (query == NULL) goto failed; + + ret = sqlite3_exec(lsqlite3->sqlite, query, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_delete: error getting eid: %s\n", errmsg); + free(errmsg); + } + lsqlite3_safe_rollback(lsqlite3->sqlite); + goto failed; + } + + talloc_free(local_ctx); + return 0; + +failed: + talloc_free(local_ctx); + return -1; +} + +/* rename a record */ +static int lsqlite3_rename(struct ldb_module *module, const struct ldb_dn *olddn, const struct ldb_dn *newdn) +{ + TALLOC_CTX *local_ctx; + struct lsqlite3_private *lsqlite3 = module->private_data; + char *new_dn, *new_cdn, *old_cdn; + char *errmsg; + char *query; + int ret; + + /* ignore ltdb specials */ + if (ldb_dn_is_special(olddn) || ldb_dn_is_special(newdn)) { + return 0; + } + + /* create a local ctx */ + local_ctx = talloc_named(lsqlite3, 0, "lsqlite3_rename local context"); + if (local_ctx == NULL) { + return -1; + } + + /* create linearized and normalized dns */ + old_cdn = ldb_dn_linearize(local_ctx, ldb_dn_casefold(module->ldb, olddn)); + new_cdn = ldb_dn_linearize(local_ctx, ldb_dn_casefold(module->ldb, newdn)); + new_dn = ldb_dn_linearize(local_ctx, newdn); + if (old_cdn == NULL || new_cdn == NULL || new_dn == NULL) goto failed; + + /* build the SQL query */ + query = lsqlite3_tprintf(local_ctx, + "UPDATE ldb_entry SET dn = '%q', norm_dn = '%q' " + "WHERE norm_dn = '%q';", + new_dn, new_cdn, old_cdn); + if (query == NULL) goto failed; + + /* execute */ + ret = sqlite3_exec(lsqlite3->sqlite, query, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + if (errmsg) { + printf("lsqlite3_rename: sqlite3_exec error: %s\n", errmsg); + free(errmsg); + } + goto failed; + } + + /* clean up and exit */ + talloc_free(local_ctx); + return 0; + +failed: + talloc_free(local_ctx); + return -1; +} +/* return extended error information */ +static const char * +lsqlite3_errstring(struct ldb_module *module) +{ + struct lsqlite3_private * lsqlite3 = module->private_data; - /* For each component of the DN (starting with the last one)... */ -#warning "convert this loop to recursive, and search backwards instead" - eid = 0; + return sqlite3_errmsg(lsqlite3->sqlite); +} + + + + +/* + * Static functions + */ - for (nComponent = pExplodedDN->comp_num - 1, bFirst = TRUE; - nComponent >= 0; - nComponent--, bFirst = FALSE) { +static int initialize(struct lsqlite3_private *lsqlite3, const char *url) +{ + int ret; + long long queryInt; + const char *pTail; + sqlite3_stmt *stmt; + const char *schema = - if (lsqlite3_debug & SQLITE3_DEBUG_NEWDN) { - gettimeofday(&tv, NULL); - t1 = ((double) tv.tv_sec + - ((double) tv.tv_usec / 1000000.0)); - printf("%1.6lf loc 2\n", t1 - t0); - t0 = t1; - } - /* Point to the component */ - pComponent = pExplodedDN->components[nComponent]; + "CREATE TABLE ldb_info AS " + " SELECT 'LDB' AS database_type," + " '1.0' AS version;" - /* Add this component on to the partial DN to date */ - if ((p = talloc_asprintf(ldb, - "%s%s%s", - pComponent->component, - bFirst ? "" : ",", - pPartialDN)) == NULL) { - return -1; - } + /* + * The entry table holds the information about an entry. + * This table is used to obtain the EID of the entry and to + * support scope=one and scope=base. The parent and child + * table is included in the entry table since all the other + * attributes are dependent on EID. + */ + "CREATE TABLE ldb_entry " + "(" + " eid INTEGER PRIMARY KEY AUTOINCREMENT," + " dn TEXT UNIQUE NOT NULL," + " norm_dn TEXT UNIQUE NOT NULL" + ");" - if (lsqlite3_debug & SQLITE3_DEBUG_NEWDN) { - gettimeofday(&tv, NULL); - t1 = ((double) tv.tv_sec + - ((double) tv.tv_usec / 1000000.0)); - printf("%1.6lf loc 3\n", t1 - t0); - t0 = t1; - } + + "CREATE TABLE ldb_object_classes" + "(" + " class_name TEXT PRIMARY KEY," + " parent_class_name TEXT," + " tree_key TEXT UNIQUE," + " max_child_num INTEGER DEFAULT 0" + ");" - /* No need for the old partial DN any more */ - talloc_free(pPartialDN); + /* + * We keep a full listing of attribute/value pairs here + */ + "CREATE TABLE ldb_attribute_values" + "(" + " eid INTEGER REFERENCES ldb_entry," + " attr_name TEXT," + " norm_attr_name TEXT," + " attr_value TEXT," + " norm_attr_value TEXT " + ");" - /* Save the new partial DN */ - pPartialDN = p; - pPartialNormalizedDN = ldb_dn_fold(pPartialDN, - p, - module, - case_fold_attr_required); + + /* + * Indexes + */ + "CREATE INDEX ldb_attribute_values_eid_idx " + " ON ldb_attribute_values (eid);" - if (lsqlite3_debug & SQLITE3_DEBUG_NEWDN) { - gettimeofday(&tv, NULL); - t1 = ((double) tv.tv_sec + - ((double) tv.tv_usec / 1000000.0)); - printf("%1.6lf loc 4\n", t1 - t0); - t0 = t1; - } + "CREATE INDEX ldb_attribute_values_name_value_idx " + " ON ldb_attribute_values (attr_name, norm_attr_value);" + + /* - * Ensure that an entry is in the ldb_entry table for this - * component. + * Triggers */ - if ((ret = getEID(lsqlite3, - &eid, - pPartialNormalizedDN)) == SQLITE_DONE) { - - QUERY_NOROWS(lsqlite3, - FALSE, - "INSERT INTO ldb_entry\n" - " (peid, dn, normalized_dn)\n" - " VALUES\n" - " (%lld, %Q, %Q);", - eid, pPartialDN, pPartialNormalizedDN); - if (lsqlite3_debug & SQLITE3_DEBUG_NEWDN) { - gettimeofday(&tv, NULL); - t1 = ((double) tv.tv_sec + - ((double) tv.tv_usec / 1000000.0)); - printf("%1.6lf loc 5\n", t1 - t0); - t0 = t1; - } - - /* Get the EID of the just inserted row */ - eid = sqlite3_last_insert_rowid(lsqlite3->sqlite); - - if (lsqlite3_debug & SQLITE3_DEBUG_NEWDN) { - gettimeofday(&tv, NULL); - t1 = ((double) tv.tv_sec + - ((double) tv.tv_usec / 1000000.0)); - printf("%1.6lf loc 8\n", t1 - t0); - t0 = t1; - } + "CREATE TRIGGER ldb_object_classes_insert_tr" + " AFTER INSERT" + " ON ldb_object_classes" + " FOR EACH ROW" + " BEGIN" + " UPDATE ldb_object_classes" + " SET tree_key = COALESCE(tree_key, " + " (" + " SELECT tree_key || " + " (SELECT base160(max_child_num + 1)" + " FROM ldb_object_classes" + " WHERE class_name = " + " new.parent_class_name)" + " FROM ldb_object_classes " + " WHERE class_name = new.parent_class_name " + " ));" + " UPDATE ldb_object_classes " + " SET max_child_num = max_child_num + 1" + " WHERE class_name = new.parent_class_name;" + " END;" - /* Also add DN attribute */ - QUERY_NOROWS(lsqlite3, - FALSE, - "INSERT INTO ldb_attribute_values\n" - " (eid,\n" - " attr_name,\n" - " attr_value,\n" - " attr_value_normalized) " - " VALUES " - " (%lld, 'DN', %Q, %Q);", - eid, - pPartialDN, /* FIX ME */ - pPartialNormalizedDN); + /* + * Table initialization + */ - } else if (ret != SQLITE_OK) { - UNLOCK_DB(module, "rollback"); - return -1; - } + "INSERT INTO ldb_object_classes " + " (class_name, tree_key) " + " VALUES " + " ('TOP', '0001');" - /* Save the parent EID */ - peid = eid; + ; + + /* Skip protocol indicator of url */ + if (strncmp(url, "sqlite://", 9) != 0) { + return SQLITE_MISUSE; } - if (lsqlite3_debug & SQLITE3_DEBUG_NEWDN) { - gettimeofday(&tv, NULL); - t1 = ((double) tv.tv_sec + - ((double) tv.tv_usec / 1000000.0)); - printf("%1.6lf loc 9\n", t1 - t0); - t0 = t1; + /* Update pointer to just after the protocol indicator */ + url += 9; + + /* Try to open the (possibly empty/non-existent) database */ + if ((ret = sqlite3_open(url, &lsqlite3->sqlite)) != SQLITE_OK) { + return ret; } - - /* Give 'em what they came for! */ - *pEID = eid; - return 0; -} - - -static unsigned char base160tab[161] = { - 48 ,49 ,50 ,51 ,52 ,53 ,54 ,55 ,56 ,57 , /* 0-9 */ - 58 ,59 ,65 ,66 ,67 ,68 ,69 ,70 ,71 ,72 , /* : ; A-H */ - 73 ,74 ,75 ,76 ,77 ,78 ,79 ,80 ,81 ,82 , /* I-R */ - 83 ,84 ,85 ,86 ,87 ,88 ,89 ,90 ,97 ,98 , /* S-Z , a-b */ - 99 ,100,101,102,103,104,105,106,107,108, /* c-l */ - 109,110,111,112,113,114,115,116,117,118, /* m-v */ - 119,120,121,122,160,161,162,163,164,165, /* w-z, latin1 */ - 166,167,168,169,170,171,172,173,174,175, /* latin1 */ - 176,177,178,179,180,181,182,183,184,185, /* latin1 */ - 186,187,188,189,190,191,192,193,194,195, /* latin1 */ - 196,197,198,199,200,201,202,203,204,205, /* latin1 */ - 206,207,208,209,210,211,212,213,214,215, /* latin1 */ - 216,217,218,219,220,221,222,223,224,225, /* latin1 */ - 226,227,228,229,230,231,232,233,234,235, /* latin1 */ - 236,237,238,239,240,241,242,243,244,245, /* latin1 */ - 246,247,248,249,250,251,252,253,254,255, /* latin1 */ - '\0' -}; - - -/* - * base160() - * - * Convert an unsigned long integer into a base160 representation of the - * number. - * - * Parameters: - * val -- - * value to be converted - * - * result -- - * character array, 5 bytes long, into which the base160 representation - * will be placed. The result will be a four-digit representation of the - * number (with leading zeros prepended as necessary), and null - * terminated. - * - * Returns: - * Nothing - */ -static void -base160_sql(sqlite3_context * hContext, - int argc, - sqlite3_value ** argv) -{ - int i; - long long val; - char result[5]; - - val = sqlite3_value_int64(argv[0]); - - for (i = 3; i >= 0; i--) { + /* In case this is a new database, enable auto_vacuum */ + if (query_norows(lsqlite3, "PRAGMA auto_vacuum=1;") != 0) { + return -1; + } - result[i] = base160tab[val % 160]; - val /= 160; - } - - result[4] = '\0'; - - sqlite3_result_text(hContext, result, -1, SQLITE_TRANSIENT); -} - - -/* - * base160next_sql() - * - * This function enhances sqlite by adding a "base160_next()" function which is - * accessible via queries. - * - * Retrieve the next-greater number in the base160 sequence for the terminal - * tree node (the last four digits). Only one tree level (four digits) is - * operated on. - * - * Input: - * A character string: either an empty string (in which case no operation is - * performed), or a string of base160 digits with a length of a multiple of - * four digits. - * - * Output: - * Upon return, the trailing four digits (one tree level) will have been - * incremented by 1. - */ -static void -base160next_sql(sqlite3_context * hContext, - int argc, - sqlite3_value ** argv) -{ - int i; - int len; - unsigned char * pTab; - unsigned char * pBase160 = - strdup(sqlite3_value_text(argv[0])); - unsigned char * pStart = pBase160; - - /* - * We need a minimum of four digits, and we will always get a multiple - * of four digits. - */ - if (pBase160 != NULL && - (len = strlen(pBase160)) >= 4 && - len % 4 == 0) { - - if (pBase160 == NULL) { - - sqlite3_result_null(hContext); - return; - } - - pBase160 += strlen(pBase160) - 1; - - /* We only carry through four digits: one level in the tree */ - for (i = 0; i < 4; i++) { - - /* What base160 value does this digit have? */ - pTab = strchr(base160tab, *pBase160); + /* Establish a busy timeout of 30 seconds */ + if ((ret = sqlite3_busy_timeout(lsqlite3->sqlite, + 30000)) != SQLITE_OK) { + return ret; + } - /* Is there a carry? */ - if (pTab < base160tab + sizeof(base160tab) - 1) { + /* Create a function, callable from sql, to increment a tree_key */ + if ((ret = + sqlite3_create_function(lsqlite3->sqlite,/* handle */ + "base160_next", /* function name */ + 1, /* number of args */ + SQLITE_ANY, /* preferred text type */ + NULL, /* user data */ + base160next_sql, /* called func */ + NULL, /* step func */ + NULL /* final func */ + )) != SQLITE_OK) { + return ret; + } - /* - * Nope. Just increment this value and we're - * done. - */ - *pBase160 = *++pTab; - break; - } else { + /* Create a function, callable from sql, to convert int to base160 */ + if ((ret = + sqlite3_create_function(lsqlite3->sqlite,/* handle */ + "base160", /* function name */ + 1, /* number of args */ + SQLITE_ANY, /* preferred text type */ + NULL, /* user data */ + base160_sql, /* called func */ + NULL, /* step func */ + NULL /* final func */ + )) != SQLITE_OK) { + return ret; + } - /* - * There's a carry. This value gets - * base160tab[0], we decrement the buffer - * pointer to get the next higher-order digit, - * and continue in the loop. - */ - *pBase160-- = base160tab[0]; + /* Begin a transaction */ + if ((ret = query_norows(lsqlite3, "BEGIN EXCLUSIVE;")) != 0) { + return ret; + } + + /* Determine if this is a new database. No tables means it is. */ + if (query_int(lsqlite3, + &queryInt, + "SELECT COUNT(*)\n" + " FROM sqlite_master\n" + " WHERE type = 'table';") != 0) { + query_norows(lsqlite3, "ROLLBACK;"); + return -1; + } + + if (queryInt == 0) { + /* + * Create the database schema + */ + for (pTail = discard_const_p(char, schema); + pTail != NULL && *pTail != '\0'; + ) { + + if ((ret = sqlite3_prepare( + lsqlite3->sqlite, + pTail, + -1, + &stmt, + &pTail)) != SQLITE_OK || + (ret = sqlite3_step(stmt)) != SQLITE_DONE || + (ret = sqlite3_finalize(stmt)) != SQLITE_OK) { + + query_norows(lsqlite3, "ROLLBACK;"); + (void) sqlite3_close(lsqlite3->sqlite); + return ret; } } - - sqlite3_result_text(hContext, - pStart, - strlen(pStart), - free); } else { - sqlite3_result_value(hContext, argv[0]); - if (pBase160 != NULL) { - free(pBase160); + /* + * Ensure that the database we opened is one of ours + */ + if (query_int(lsqlite3, + &queryInt, + "SELECT " + " (SELECT COUNT(*) = 2" + " FROM sqlite_master " + " WHERE type = 'table' " + " AND name IN " + " (" + " 'ldb_entry', " + " 'ldb_object_classes' " + " ) " + " ) " + " AND " + " (SELECT 1 " + " FROM ldb_info " + " WHERE database_type = 'LDB' " + " AND version = '1.0'" + " );") != 0 || + queryInt != 1) { + + /* It's not one that we created. See ya! */ + query_norows(lsqlite3, "ROLLBACK;"); + (void) sqlite3_close(lsqlite3->sqlite); + return SQLITE_MISUSE; } } -} + + /* + * Create a temporary table to hold attributes requested in the result + * set of a search. + */ + query_norows(lsqlite3, "DROP TABLE " RESULT_ATTR_TABLE ";\n"); + if ((ret = + query_norows(lsqlite3, + "CREATE " TEMPTAB " TABLE " RESULT_ATTR_TABLE "\n" + " (\n" + " attr_name TEXT PRIMARY KEY\n" + " );")) != 0) { + query_norows(lsqlite3, "ROLLBACK;"); + return ret; + } + /* Commit the transaction */ + if ((ret = query_norows(lsqlite3, "COMMIT;")) != 0) { + query_norows(lsqlite3, "ROLLBACK;"); + return ret; + } + + return SQLITE_OK; +} -#ifdef DEBUG_LOCKS -static int lock_debug(struct ldb_module * module, - const char * lockname, - const char * pFileName, - int linenum) +static int +destructor(void *p) { - int ret; - struct lsqlite3_private * lsqlite3 = module->private_data; + struct lsqlite3_private *lsqlite3 = p; + + if (lsqlite3->sqlite) { + sqlite3_close(lsqlite3->sqlite); + } + return 0; +} - printf("%s(%d): LOCK (%d) ", - pFileName, linenum, lsqlite3->lock_count); - ret = lsqlite3_lock(module, lockname); - printf("got %d\n", ret); - return ret; -} - -static int unlock_debug(struct ldb_module * module, - const char * lockname, - const char * pFileName, - int linenum) +/* + * Table of operations for the sqlite3 backend + */ +static const struct ldb_module_ops lsqlite3_ops = { + .name = "sqlite", + .search = lsqlite3_search, + .search_bytree = lsqlite3_search_bytree, + .add_record = lsqlite3_add, + .modify_record = lsqlite3_modify, + .delete_record = lsqlite3_delete, + .rename_record = lsqlite3_rename, + .named_lock = lsqlite3_lock, + .named_unlock = lsqlite3_unlock, + .errstring = lsqlite3_errstring +}; + +/* + * connect to the database + */ +int lsqlite3_connect(struct ldb_context *ldb, + const char *url, + unsigned int flags, + const char *options[]) { + int i; int ret; - struct lsqlite3_private * lsqlite3 = module->private_data; - - ret = lsqlite3_unlock(module, lockname); - printf("%s(%d): UNLOCK (%d) got %d\n", - pFileName, linenum, lsqlite3->lock_count, ret); - - return ret; + struct lsqlite3_private * lsqlite3 = NULL; + + lsqlite3 = talloc(ldb, struct lsqlite3_private); + if (!lsqlite3) { + goto failed; + } + + lsqlite3->sqlite = NULL; + lsqlite3->options = NULL; + lsqlite3->lock_count = 0; + + ret = initialize(lsqlite3, url); + if (ret != SQLITE_OK) { + goto failed; + } + + talloc_set_destructor(lsqlite3, destructor); + + ldb->modules = talloc(ldb, struct ldb_module); + if (!ldb->modules) { + goto failed; + } + ldb->modules->ldb = ldb; + ldb->modules->prev = ldb->modules->next = NULL; + ldb->modules->private_data = lsqlite3; + ldb->modules->ops = &lsqlite3_ops; + + if (options) { + /* + * take a copy of the options array, so we don't have to rely + * on the caller keeping it around (it might be dynamic) + */ + for (i=0;options[i];i++) ; + + lsqlite3->options = talloc_array(lsqlite3, char *, i+1); + if (!lsqlite3->options) { + goto failed; + } + + for (i=0;options[i];i++) { + + lsqlite3->options[i+1] = NULL; + lsqlite3->options[i] = + talloc_strdup(lsqlite3->options, options[i]); + if (!lsqlite3->options[i]) { + goto failed; + } + } + } + + return 0; + +failed: + if (lsqlite3->sqlite != NULL) { + (void) sqlite3_close(lsqlite3->sqlite); + } + talloc_free(lsqlite3); + return -1; } -#endif + -- cgit