--- /dev/null
+/*@-bounds@*/
+/*@-mustmod@*/
+/*@-paramuse@*/
+/*@-globuse@*/
+/*@-moduncon@*/
+/*@-noeffectuncon@*/
+/*@-compdef@*/
+/*@-compmempass@*/
+/*@-branchstate@*/
+/*@-modfilesystem@*/
+
+/*
+ * sqlite.c
+ * sqlite interface for rpmdb
+ *
+ * Author: Mark Hatle <mhatle@mvista.com> or <fray@kernel.crashing.org>
+ * Copyright (c) 2004 MontaVista Software, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * or GNU Library General Public License, at your option,
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * and GNU Library Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "system.h"
+
+#include <endian.h>
+
+#include <rpmlib.h>
+#include <rpmmacro.h>
+#include <rpmurl.h> /* XXX urlPath proto */
+
+#include <rpmdb.h>
+
+#include "sqlite3.h"
+
+#include "debug.h"
+
+/*@access rpmdb @*/
+/*@access dbiIndex @*/
+
+#if 0
+ /* Turn off some of the COMMIT transactions */
+ #define SQL_FAST_DB
+#endif
+
+#if 0
+ /* define this to trace the functions that are called in here... */
+ #define SQL_TRACE_FUNCTIONS
+ #define SQL_TRACE_ENCODINGS
+ #define SQL_TRACE_TRANSACTIONS
+ #define SQL_TRACE_CURSOR
+#endif
+
+/* Define the things normally in a header... */
+struct __sql_mem; typedef struct __sql_mem SQL_MEM;
+
+struct __sql_db; typedef struct __sql_db SQL_DB;
+struct __sql_dbcursor; typedef struct __sql_dbcursor SQL_CURSOR;
+
+struct __sql_db {
+ sqlite3 * db; /* Database pointer */
+
+ int transaction; /* Do we have a transaction open? */
+
+ SQL_CURSOR * head_cursor; /* List of open cursors */
+
+ int count;
+};
+
+struct __sql_dbcursor {
+ DBC * name; /* Which DBC am I emulating? */
+
+ /* Table -- result of query */
+ char **resultp;
+ int nrow;
+ int ncolumn;
+
+ int row_iterator; /* Which row are we on? 1, 2, 3 ... */
+
+ int all; /* Cursor is for all items, not a specific key */
+
+ SQL_MEM * memory;
+
+ int count;
+
+ struct __sql_dbcursor * next_cursor;
+};
+
+struct __sql_mem {
+ void * mem_ptr;
+ SQL_MEM * next;
+};
+
+/*===================================================================*/
+/*
+** How This Encoder Works
+**
+** The output is allowed to contain any character except 0x27 (') and
+** 0x00. This is accomplished by using an escape character to encode
+** 0x27 and 0x00 as a two-byte sequence. The escape character is always
+** 0x01. An 0x00 is encoded as the two byte sequence 0x01 0x01. The
+** 0x27 character is encoded as the two byte sequence 0x01 0x03. Finally,
+** the escape character itself is encoded as the two-character sequence
+** 0x01 0x02.
+**
+** To summarize, the encoder works by using an escape sequences as follows:
+**
+** 0x00 -> 0x01 0x01
+** 0x01 -> 0x01 0x02
+** 0x27 -> 0x01 0x03
+**
+** If that were all the encoder did, it would work, but in certain cases
+** it could double the size of the encoded string. For example, to
+** encode a string of 100 0x27 characters would require 100 instances of
+** the 0x01 0x03 escape sequence resulting in a 200-character output.
+** We would prefer to keep the size of the encoded string smaller than
+** this.
+**
+** To minimize the encoding size, we first add a fixed offset value to each
+** byte in the sequence. The addition is modulo 256. (That is to say, if
+** the sum of the original character value and the offset exceeds 256, then
+** the higher order bits are truncated.) The offset is chosen to minimize
+** the number of characters in the string that need to be escaped. For
+** example, in the case above where the string was composed of 100 0x27
+** characters, the offset might be 0x01. Each of the 0x27 characters would
+** then be converted into an 0x28 character which would not need to be
+** escaped at all and so the 100 character input string would be converted
+** into just 100 characters of output. Actually 101 characters of output -
+** we have to record the offset used as the first byte in the sequence so
+** that the string can be decoded. Since the offset value is stored as
+** part of the output string and the output string is not allowed to contain
+** characters 0x00 or 0x27, the offset cannot be 0x00 or 0x27.
+**
+** Here, then, are the encoding steps:
+**
+** (1) Choose an offset value and make it the first character of
+** output.
+**
+** (2) Copy each input character into the output buffer, one by
+** one, adding the offset value as you copy.
+**
+** (3) If the value of an input character plus offset is 0x00, replace
+** that one character by the two-character sequence 0x01 0x01.
+** If the sum is 0x01, replace it with 0x01 0x02. If the sum
+** is 0x27, replace it with 0x01 0x03.
+**
+** (4) Put a 0x00 terminator at the end of the output.
+**
+** Decoding is obvious:
+**
+** (5) Copy encoded characters except the first into the decode
+** buffer. Set the first encoded character aside for use as
+** the offset in step 7 below.
+**
+** (6) Convert each 0x01 0x01 sequence into a single character 0x00.
+** Convert 0x01 0x02 into 0x01. Convert 0x01 0x03 into 0x27.
+**
+** (7) Subtract the offset value that was the first character of
+** the encoded buffer from all characters in the output buffer.
+**
+** The only tricky part is step (1) - how to compute an offset value to
+** minimize the size of the output buffer. This is accomplished by testing
+** all offset values and picking the one that results in the fewest number
+** of escapes. To do that, we first scan the entire input and count the
+** number of occurances of each character value in the input. Suppose
+** the number of 0x00 characters is N(0), the number of occurances of 0x01
+** is N(1), and so forth up to the number of occurances of 0xff is N(255).
+** An offset of 0 is not allowed so we don't have to test it. The number
+** of escapes required for an offset of 1 is N(1)+N(2)+N(40). The number
+** of escapes required for an offset of 2 is N(2)+N(3)+N(41). And so forth.
+** In this way we find the offset that gives the minimum number of escapes,
+** and thus minimizes the length of the output string.
+*/
+
+/*
+** Encode a binary buffer "in" of size n bytes so that it contains
+** no instances of characters '\'' or '\000'. The output is
+** null-terminated and can be used as a string value in an INSERT
+** or UPDATE statement. Use sqlite_decode_binary() to convert the
+** string back into its original binary.
+**
+** The result is written into a preallocated output buffer "out".
+** "out" must be able to hold at least 2 +(257*n)/254 bytes.
+** In other words, the output will be expanded by as much as 3
+** bytes for every 254 bytes of input plus 2 bytes of fixed overhead.
+** (This is approximately 2 + 1.0118*n or about a 1.2% size increase.)
+**
+** The return value is the number of characters in the encoded
+** string, excluding the "\000" terminator.
+*/
+static size_t sqlite_encode_binary(const unsigned char *in, size_t n,
+ unsigned char *out)
+ /*@modifies out @*/
+{
+ long i, j, e, m;
+ int cnt[256];
+ if (n == 0) {
+ out[0] = 'x';
+ out[1] = 0;
+ return 1;
+ }
+ memset(cnt, 0, sizeof(cnt));
+ for(i=n-1; i>=0; i--){ cnt[in[i]]++; }
+ m = n;
+ for(i=1; i<256; i++){
+ int sum;
+ if( i=='\'' ) continue;
+ sum = cnt[i] + cnt[(i+1)&0xff] + cnt[(i+'\'')&0xff];
+ if( sum<m ){
+ m = sum;
+ e = i;
+ if( m==0 ) break;
+ }
+ }
+ out[0] = e;
+ j = 1;
+ for(i=0; i<n; i++){
+ int c = (in[i] - e)&0xff;
+ if( c==0 ){
+ out[j++] = 1;
+ out[j++] = 1;
+ }else if( c==1 ){
+ out[j++] = 1;
+ out[j++] = 2;
+ }else if( c=='\'' ){
+ out[j++] = 1;
+ out[j++] = 3;
+ }else{
+ out[j++] = c;
+ }
+ }
+ out[j] = 0;
+ return j;
+}
+
+/*
+** Decode the string "in" into binary data and write it into "out".
+** This routine reverses the encoding created by sqlite_encode_binary().
+** The output will always be a few bytes less than the input. The number
+** of bytes of output is returned. If the input is not a well-formed
+** encoding, -1 is returned.
+**
+** The "in" and "out" parameters may point to the same buffer in order
+** to decode a string in place.
+*/
+static size_t sqlite_decode_binary(const unsigned char *in, unsigned char *out)
+ /*@modifies out @*/
+{
+ long i;
+ int c, e;
+ e = *(in++);
+ i = 0;
+ while( (c = *(in++))!=0 ){
+ if( c==1 ){
+ c = *(in++);
+ if( c==1 ){
+ c = 0;
+ }else if( c==2 ){
+ c = 1;
+ }else if( c==3 ){
+ c = '\'';
+ }else{
+ return -1;
+ }
+ }
+ out[i++] = (c + e)&0xff;
+ }
+ return i;
+}
+/*===================================================================*/
+
+/*
+ * Transaction support
+ */
+
+static int sql_startTransaction(dbiIndex dbi)
+ /*@*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+ int rc = 0;
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ /* XXX: Transaction Support */
+ if (!sqldb->transaction) {
+ char * pzErrmsg;
+ rc = sqlite3_exec(sqldb->db, "BEGIN TRANSACTION;", NULL, NULL, &pzErrmsg);
+
+#ifdef SQL_TRACE_TRANSACTIONS
+ rpmMessage(RPMMESS_DEBUG, "Begin %s SQL transaction %s (%d)\n",
+ dbi->dbi_subfile, pzErrmsg, rc);
+#endif
+
+ if (rc == 0)
+ sqldb->transaction = 1;
+ }
+
+ return rc;
+}
+
+static int sql_endTransaction(dbiIndex dbi)
+ /*@*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+ int rc=0;
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ /* XXX: Transaction Support */
+ if (sqldb->transaction) {
+ char * pzErrmsg;
+ rc = sqlite3_exec(sqldb->db, "END TRANSACTION;", NULL, NULL, &pzErrmsg);
+
+#ifdef SQL_TRACE_TRANSACTIONS
+ rpmMessage(RPMMESS_DEBUG, "End %s SQL transaction %s (%d)\n",
+ dbi->dbi_subfile, pzErrmsg, rc);
+#endif
+
+ if (rc == 0)
+ sqldb->transaction = 0;
+ }
+
+ return rc;
+}
+
+static int sql_commitTransaction(dbiIndex dbi, int flag)
+ /*@*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+ int rc = 0;
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ /* XXX: Transactions */
+ if ( sqldb->transaction ) {
+ char * pzErrmsg;
+ rc = sqlite3_exec(sqldb->db, "COMMIT;", NULL, NULL, &pzErrmsg);
+
+#ifdef SQL_TRACE_TRANSACTIONS
+ rpmMessage(RPMMESS_DEBUG, "Commit %s SQL transaction(s) %s (%d)\n",
+ dbi->dbi_subfile, pzErrmsg, rc);
+#endif
+
+ sqldb->transaction=0;
+
+ /* Start a new transaction if we were in the middle of one */
+ if ( flag == 0 )
+ rc = sql_startTransaction(dbi);
+ }
+
+ return rc;
+}
+
+/*
+ * Allocate a temporary buffer
+ *
+ * Life span of memory in db3 is apparently a db_env,
+ * this works for db3 because items are mmaped from
+ * the database.. however we can't do that...
+ *
+ * Life span has been changed to the life of a cursor.
+ *
+ * Minor changes were required to RPM for this to work
+ * valgrind was used to verify...
+ *
+ */
+static void * allocTempBuffer(DBC * dbcursor, size_t len)
+ /*@*/
+{
+ DB * db = dbcursor->dbp;
+ SQL_DB * sqldb;
+ SQL_CURSOR * sqlcursor;
+ SQL_MEM * item;
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+ sqlcursor = sqldb->head_cursor;
+
+ /* Find our version of the db3 cursor */
+ while ( sqlcursor != NULL && sqlcursor->name != dbcursor ) {
+ sqlcursor = sqlcursor->next_cursor;
+ }
+
+ assert(sqlcursor != NULL);
+
+ item = xmalloc(sizeof(*item));
+ item->mem_ptr = xmalloc(len);
+
+#if 0
+ /* Only keep two pointers per cursor */
+ if ( sqlcursor->memory ) {
+ if ( sqlcursor->memory->next ) {
+ sqlcursor->memory->next->mem_ptr = _free(sqlcursor->memory->next->mem_ptr);
+
+ sqlcursor->memory->next = _free(sqlcursor->memory->next);
+
+ sqlcursor->count--;
+ }
+ }
+#endif
+
+ item->next = sqlcursor->memory;
+
+ sqlcursor->memory = item;
+ sqlcursor->count++;
+
+ return item->mem_ptr;
+}
+
+static int sql_busy_handler(void * dbi_void, int time)
+ /*@*/
+{
+/*@-castexpose@*/
+ dbiIndex dbi = (dbiIndex) dbi_void;
+/*@=castexpose@*/
+
+ rpmMessage(RPMMESS_WARNING, _("Unable to get lock on db %s, retrying... (%d)\n"),
+ dbi->dbi_file, time);
+
+ (void) sleep(1);
+
+ return 1;
+}
+
+/**
+ * Verify the DB is setup.. if not initialize it
+ *
+ * Create the table.. create the db_info
+ */
+static int sql_initDB(dbiIndex dbi)
+ /*@*/
+{
+#ifdef SQL_TRACE_FUNCTIONS
+ rpmMessage(RPMMESS_DEBUG, "sql_initDB()\n");
+#endif
+
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+ int rc = 0;
+
+ char ** resultp;
+ int nrow;
+ int ncolumn;
+ char * pzErrmsg;
+ char cmd[BUFSIZ];
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ /* Check if the table exists... */
+ sprintf(cmd,
+ "SELECT name FROM 'sqlite_master' WHERE type='table' and name='%s';",
+ dbi->dbi_subfile);
+ rc = sqlite3_get_table(sqldb->db, cmd,
+ &resultp, &nrow, &ncolumn, &pzErrmsg);
+
+ (void) sqlite3_free_table(resultp);
+
+ if ( rc == 0 && nrow < 1 ) {
+ sprintf(cmd, "CREATE TABLE '%s' (key blob UNIQUE, value blob);",
+ dbi->dbi_subfile);
+ rc = sqlite3_exec(sqldb->db, cmd, NULL, NULL, &pzErrmsg);
+
+ if ( rc == 0 ) {
+ sprintf(cmd, "CREATE TABLE 'db_info' (endian TEXT);");
+ rc = sqlite3_exec(sqldb->db, cmd, NULL, NULL, &pzErrmsg);
+ }
+
+ if ( rc == 0 ) {
+ sprintf(cmd, "INSERT INTO 'db_info' values('%d');", __BYTE_ORDER);
+ rc = sqlite3_exec(sqldb->db, cmd, NULL, NULL, &pzErrmsg);
+ }
+ }
+
+ if ( rc )
+ rpmMessage(RPMMESS_WARNING, "Unable to initDB %s (%d)\n",
+ pzErrmsg, rc);
+
+ return rc;
+}
+
+/**
+ * Close database cursor.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_cclose (dbiIndex dbi, /*@only@*/ DBC * dbcursor,
+ unsigned int flags)
+ /*@globals fileSystem @*/
+ /*@modifies dbi, *dbcursor, fileSystem @*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+ SQL_CURSOR * sqlcursor, *prev;
+ int rc = 0;
+
+#ifdef SQL_TRACE_FUNCTIONS
+ rpmMessage(RPMMESS_DEBUG, "sql_cclose()\n");
+#endif
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ prev=NULL;
+ sqlcursor = sqldb->head_cursor;
+
+ /* Find our version of the db3 cursor */
+ while ( sqlcursor != NULL && sqlcursor->name != dbcursor ) {
+ prev = sqlcursor;
+ sqlcursor = sqlcursor->next_cursor;
+ }
+
+ assert(sqlcursor != NULL);
+
+ /* Free memory */
+ if ( sqlcursor->resultp ) {
+ (void) sqlite3_free_table( sqlcursor->resultp );
+ }
+
+ if ( sqlcursor->memory ) {
+ SQL_MEM * curr_mem = sqlcursor->memory;
+ SQL_MEM * next_mem;
+ int loc_count=0;
+
+ while ( curr_mem ) {
+ next_mem = curr_mem->next;
+ free ( curr_mem->mem_ptr );
+ free ( curr_mem );
+ curr_mem = next_mem;
+ loc_count++;
+ }
+
+ if ( sqlcursor->count != loc_count)
+ rpmMessage(RPMMESS_DEBUG, "Alloced %ld -- free %ld\n",
+ sqlcursor->count, loc_count);
+ }
+
+ /* Remove from the list */
+ if (prev == NULL) {
+ sqldb->head_cursor = sqlcursor->next_cursor;
+ } else {
+ prev->next_cursor = sqlcursor->next_cursor;
+ }
+
+ sqldb->count--;
+
+/*@-kepttrans@*/
+ sqlcursor = _free(sqlcursor);
+/*@=kepttrans@*/
+ dbcursor = _free(dbcursor);
+
+#ifndef SQL_FAST_DB
+ if ( flags == DB_WRITECURSOR ) {
+ rc = sql_commitTransaction(dbi, 1);
+ } else {
+ rc = sql_endTransaction(dbi);
+ }
+#else
+ rc = sql_endTransaction(dbi);
+#endif
+
+ return rc;
+}
+
+/**
+ * Close index database, and destroy database handle.
+ * @param dbi index database handle
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_close(/*@only@*/ dbiIndex dbi, unsigned int flags)
+ /*@globals fileSystem @*/
+ /*@modifies dbi, fileSystem @*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+ int rc = 0;
+
+#ifdef SQL_TRACE_FUNCTIONS
+ rpmMessage(RPMMESS_DEBUG, "sql_close()\n");
+#endif
+
+ if (db && db->app_private && ((SQL_DB *)db->app_private)->db)
+ {
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ /* Commit, don't open a new one */
+ rc = sql_commitTransaction(dbi, 1);
+
+ /* close all cursors */
+/*@-infloops@*/
+ while ( sqldb->head_cursor ) {
+ (void) sql_cclose(dbi, sqldb->head_cursor->name, 0);
+ }
+/*@=infloops@*/
+
+ if (sqldb->count)
+ rpmMessage(RPMMESS_DEBUG, "cursors %ld\n", sqldb->count);
+
+ (void) sqlite3_close(sqldb->db);
+
+ rpmMessage(RPMMESS_DEBUG, _("closed sql db %s\n"),
+ dbi->dbi_subfile);
+
+#if 0
+ /* Since we didn't setup the memory, don't clear it! */
+ /* Free up memory */
+ if (rpmdb->db_dbenv != NULL) {
+ dbenv = rpmdb->db_dbenv;
+ if (rpmdb->db_opens == 1) {
+ rpmdb->db_dbenv = _free(rpmdb->db_dbenv);
+ }
+ rpmdb->db_opens--;
+ }
+#endif
+
+ if (dbi->dbi_stats) {
+ dbi->dbi_stats = _free(dbi->dbi_stats);
+ }
+
+ dbi->dbi_file = _free(dbi->dbi_file);
+#if 0
+ /* They're the same so only free one! */
+ dbi->dbi_subfile = _free(dbi->dbi_subfile);
+#endif
+ dbi->dbi_db->app_private = _free(dbi->dbi_db->app_private); /* sqldb */
+ dbi->dbi_db = _free(dbi->dbi_db);
+ }
+ dbi = _free(dbi);
+
+ return 0;
+}
+
+/**
+ * Return handle for an index database.
+ * @param rpmdb rpm database
+ * @param rpmtag rpm tag
+ * @return 0 on success
+ */
+static int sql_open(rpmdb rpmdb, rpmTag rpmtag, /*@out@*/ dbiIndex * dbip)
+ /*@globals rpmGlobalMacroContext, h_errno, fileSystem, internalState @*/
+ /*@modifies *dbip, rpmGlobalMacroContext, fileSystem, internalState @*/
+{
+/*@-nestedextern -shadow @*/
+ extern struct _dbiVec sqlitevec;
+/*@=nestedextern -shadow @*/
+
+ const char * urlfn = NULL;
+ const char * root;
+ const char * home;
+ const char * dbhome;
+ const char * dbfile;
+ const char * dbfname;
+ const char * sql_errcode;
+ int rc = 0;
+ int xx;
+
+ size_t len;
+
+ dbiIndex dbi = NULL;
+ DB * db = NULL;
+
+ SQL_DB * sqldb;
+
+#ifdef SQL_TRACE_FUNCTIONS
+ rpmMessage(RPMMESS_DEBUG, "sql_open()\n");
+#endif
+
+ dbi = xcalloc(1, sizeof(*dbi));
+
+/*@-assignexpose -newreftrans@*/
+/*@i@*/ dbi->dbi_rpmdb = rpmdb;
+/*@=assignexpose =newreftrans@*/
+ dbi->dbi_rpmtag = rpmtag;
+ dbi->dbi_api = 4; /* I have assigned 4 to sqlite */
+
+ /*
+ * Inverted lists have join length of 2, primary data has join length of 1.
+ * MGH: No idea what this is for.. but w/o it things don't work...
+ */
+ /*@-sizeoftype@*/
+ switch (dbi->dbi_rpmtag) {
+ case RPMDBI_PACKAGES:
+ case RPMDBI_DEPENDS:
+ dbi->dbi_jlen = 1 * sizeof(int_32);
+ break;
+ default:
+ dbi->dbi_jlen = 2 * sizeof(int_32);
+ break;
+ }
+ /*@=sizeoftype@*/
+
+ dbi->dbi_byteswapped = -1;
+
+ if (dbip)
+ *dbip = NULL;
+ /*
+ * Get the prefix/root component and directory path
+ */
+ root = rpmdb->db_root;
+ if ((root[0] == '/' && root[1] == '\0') || rpmdb->db_chrootDone)
+ root = NULL;
+ home = rpmdb->db_home;
+
+ dbi->dbi_root=root;
+ dbi->dbi_home=home;
+
+ dbfile = tagName(dbi->dbi_rpmtag);
+
+ /*
+ * Make a copy of the tagName result..
+ * use this for the filename and table name
+ */
+ {
+ char * t;
+ len = strlen(dbfile);
+ t = xcalloc(len + 1, sizeof(*t));
+ (void) stpcpy( t, dbfile );
+ dbi->dbi_file = t;
+/*@-kepttrans@*/ /* WRONG */
+ dbi->dbi_subfile = t;
+/*@=kepttrans@*/
+ }
+
+ dbi->dbi_mode=O_RDWR;
+
+ /*
+ * Either the root or directory components may be a URL. Concatenate,
+ * convert the URL to a path, and add the name of the file.
+ */
+ /*@-mods@*/
+ urlfn = rpmGenPath(root, home, NULL);
+ /*@=mods@*/
+ (void) urlPath(urlfn, &dbhome);
+
+ /*
+ * Create the /var/lib/rpm directory if it doesn't exist (root only).
+ */
+ (void) rpmioMkpath(dbhome, 0755, getuid(), getgid());
+
+ dbfname = rpmGenPath(dbhome, dbi->dbi_file, NULL);
+
+ rpmMessage(RPMMESS_DEBUG, _("opening sql db %s (%s) mode=0x%x\n"),
+ dbfname, dbi->dbi_subfile, dbi->dbi_mode);
+
+ /* Open the Database */
+ db = xcalloc(1, sizeof(*db));
+ sqldb = xcalloc(1,sizeof(*sqldb));
+
+ sql_errcode = NULL;
+ xx = sqlite3_open(dbfname, &sqldb->db);
+ if (xx != SQLITE_OK)
+ sql_errcode = sqlite3_errmsg(sqldb->db);
+
+ if (sqldb->db)
+ (void) sqlite3_busy_handler(sqldb->db, &sql_busy_handler, dbi);
+
+ sqldb->transaction = 0; /* Initialize no current transactions */
+ sqldb->head_cursor = NULL; /* no current cursors */
+
+ db->app_private = sqldb;
+ dbi->dbi_db = db;
+
+ if (sql_errcode != NULL)
+ {
+ rpmMessage(RPMMESS_DEBUG, "Unable to open database: %s\n", sql_errcode);
+ rc = EINVAL;
+ }
+
+#if 0
+ /* This setup colides with db3 if we're using it... */
+ /* Setup my db_env */
+ if ( rpmdb->db_dbenv == NULL ) {
+ dbenv = xcalloc(1, sizeof(DB_ENV));
+
+ rpmdb->db_dbenv = dbenv;
+ rpmdb->db_opens = 1;
+ } else {
+ dbenv = rpmdb->db_dbenv;
+
+ rpmdb->db_opens++;
+ }
+#endif
+
+ /* initialize table */
+ if ( rc == 0 )
+ rc = sql_initDB(dbi);
+
+ if (rc == 0 && dbi->dbi_db != NULL && dbip != NULL) {
+ dbi->dbi_vec = &sqlitevec;
+ *dbip = dbi;
+ }
+ else {
+ (void) sql_close(dbi, 0);
+ }
+
+ urlfn = _free(urlfn);
+ dbfname = _free(dbfname);
+
+ return rc;
+}
+
+/**
+ * Flush pending operations to disk.
+ * @param dbi index database handle
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_sync (dbiIndex dbi, unsigned int flags)
+ /*@globals fileSystem @*/
+ /*@modifies fileSystem @*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+ int rc = 0;
+
+#ifdef SQL_TRACE_FUNCTIONS
+ rpmMessage(RPMMESS_DEBUG, "sql_sync()\n");
+#endif
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+#ifndef SQL_FAST_DB
+ rc = sql_commitTransaction(dbi, 0);
+#endif
+
+ return rc;
+}
+
+/**
+ * Open database cursor.
+ * @param dbi index database handle
+ * @param txnid database transaction handle
+ * @retval dbcp address of new database cursor
+ * @param dbiflags DB_WRITECURSOR or 0
+ * @return 0 on success
+ */
+static int sql_copen (dbiIndex dbi, /*@null@*/ DB_TXN * txnid,
+ /*@out@*/ DBC ** dbcp, unsigned int flags)
+ /*@globals fileSystem @*/
+ /*@modifies dbi, *txnid, *dbcp, fileSystem @*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+ DBC * dbcursor;
+ SQL_CURSOR * sqlcursor;
+ int rc = 0;
+
+#ifdef SQL_TRACE_FUNCTIONS
+ rpmMessage(RPMMESS_DEBUG, "sql_copen()\n");
+#endif
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ dbcursor = xcalloc(1, sizeof(*dbcursor));
+ dbcursor->dbp=db;
+
+ sqlcursor = xcalloc(1, sizeof(*sqlcursor));
+ sqlcursor->name=dbcursor;
+ sqlcursor->resultp=NULL;
+ sqlcursor->nrow=0;
+ sqlcursor->ncolumn=0;
+ sqlcursor->row_iterator=0;
+ sqlcursor->all=0;
+ sqlcursor->memory=NULL;
+
+ sqlcursor->next_cursor = sqldb->head_cursor;
+ sqldb->head_cursor = sqlcursor;
+
+ sqldb->count++;
+
+ /* If we're going to write, start a transaction (lock the DB) */
+ if ( flags == DB_WRITECURSOR ) {
+ rc = sql_startTransaction(dbi);
+ }
+
+ if (dbcp)
+ /*@-onlytrans@*/ *dbcp = dbcursor; /*@=onlytrans@*/
+ else
+ /*@-kepttrans@*/ (void) sql_cclose(dbi, dbcursor, 0); /*@=kepttrans@*/
+
+ return rc;
+}
+
+/**
+ * Delete (key,data) pair(s) using db->del or dbcursor->c_del.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->del)
+ * @param key delete key value/length/flags
+ * @param data delete data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_cdel (dbiIndex dbi, /*@null@*/ DBC * dbcursor, DBT * key,
+ DBT * data, unsigned int flags)
+ /*@globals fileSystem @*/
+ /*@modifies *dbcursor, fileSystem @*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+ int rc = 0;
+ unsigned char * key_enc_string, * data_enc_string;
+ int key_len, data_len;
+ char * pzErrmsg;
+ char * cmd;
+
+#ifdef SQL_TRACE_FUNCTIONS
+ rpmMessage(RPMMESS_DEBUG, "sql_cdel()\n");
+#endif
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+#ifdef SQL_TRACE_CURSOR
+ rpmMessage(RPMMESS_DEBUG, " cdel on %s key 0x%x (%d), data 0x%x (%d), flags %d\n",
+ dbi->dbi_subfile,
+ *(long *)key->data, key->size,
+ *(long *)data->data, data->size,
+ flags);
+#endif
+
+ key_enc_string = alloca (2 + ((257 * key->size)/254) + 1);
+ key_len=sqlite_encode_binary((char *)key->data, key->size, key_enc_string);
+ key_enc_string[key_len]='\0';
+
+ data_enc_string = alloca (2 + ((257 * data->size)/254) + 1);
+ data_len=sqlite_encode_binary((char *)data->data, data->size, data_enc_string);
+ data_enc_string[data_len]='\0';
+
+#ifdef SQL_TRACE_ENCODINGS
+ rpmMessage(RPMMESS_DEBUG, " encoded key 0x%x (%d)\n", *(long *)key_enc_string, key_len);
+ rpmMessage(RPMMESS_DEBUG, " encoded data 0x%x (%d)\n", *(long *)data_enc_string, data_len);
+#endif
+
+ cmd = sqlite3_mprintf("DELETE FROM '%q' WHERE key='%q' AND value='%q';",
+ dbi->dbi_subfile, key_enc_string, data_enc_string);
+ rc = sqlite3_exec(sqldb->db, cmd, NULL, NULL, &pzErrmsg);
+ sqlite3_free(cmd);
+
+ if ( rc )
+ rpmMessage(RPMMESS_DEBUG, "cdel %s (%d)\n",
+ pzErrmsg, rc);
+
+ return rc;
+}
+
+/**
+ * Retrieve (key,data) pair using db->get or dbcursor->c_get.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->get)
+ * @param key retrieve key value/length/flags
+ * @param data retrieve data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_cget (dbiIndex dbi, /*@null@*/ DBC * dbcursor, DBT * key,
+ DBT * data, unsigned int flags)
+ /*@globals fileSystem @*/
+ /*@modifies dbi, dbcursor, *key, *data, fileSystem @*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+ SQL_CURSOR * sqlcursor;
+ int rc = 0;
+ int cleanup = 0;
+ char * pzErrmsg;
+ char * cmd;
+
+#ifdef SQL_TRACE_FUNCTIONS
+ rpmMessage(RPMMESS_DEBUG, "sql_cget()\n");
+#endif
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ if ( dbcursor == NULL ) {
+ rc = sql_copen ( dbi, NULL, &dbcursor, 0 );
+ cleanup = 1;
+ }
+
+ /* Find our version of the db3 cursor */
+ sqlcursor = sqldb->head_cursor;
+ while ( sqlcursor != NULL && sqlcursor->name != dbcursor ) {
+ sqlcursor = sqlcursor->next_cursor;
+ }
+
+ assert(sqlcursor != NULL);
+
+ /*
+ * First determine if we have a key, or if we're going to
+ * scan the whole DB
+ */
+
+ if ( rc == 0 && key->size == 0 ) {
+ sqlcursor->all++;
+
+ /*
+ * Scan the whole db..
+ * We are not guarenteed a return order..
+ * .. so get the 0x0 key first ..
+ * (rpm expects the 0x0 key first)
+ */
+ if ( sqlcursor->all == 1 && dbi->dbi_rpmtag == RPMDBI_PACKAGES ) {
+static int mykeydata;
+ flags=DB_SET;
+ key->size=4;
+/*@-immediatetrans@*/
+if (key->data == NULL) key->data = &mykeydata;
+/*@=immediatetrans@*/
+ memset(key->data, 0, 4);
+ }
+ }
+
+ /* New retrieval */
+ if ( rc == 0 && ( ( flags == DB_SET ) || ( sqlcursor->resultp == NULL )) ) {
+#ifdef SQL_TRACE_CURSOR
+ rpmMessage(RPMMESS_DEBUG, " cget rc %d, flags %d, sqlcursor->result 0%x\n",
+ rc, flags, sqlcursor->resultp);
+#endif
+
+ if ( sqlcursor->resultp ) {
+ (void) sqlite3_free_table( sqlcursor->resultp );
+ sqlcursor->resultp = NULL;
+ sqlcursor->nrow=0;
+ sqlcursor->ncolumn=0;
+ sqlcursor->row_iterator=0;
+ }
+
+ switch(key->size) {
+ case 0:
+ cmd = sqlite3_mprintf("SELECT key,value FROM '%q';",
+ dbi->dbi_subfile);
+ rc = sqlite3_get_table(sqldb->db, cmd,
+ &sqlcursor->resultp, &sqlcursor->nrow, &sqlcursor->ncolumn,
+ &pzErrmsg
+ );
+ sqlite3_free(cmd);
+ break;
+ default:
+ {
+ unsigned char * key_enc_string;
+ int key_len;
+
+#ifdef SQL_TRACE_CURSOR
+ rpmMessage(RPMMESS_DEBUG, " cget on %s find key 0x%x (%d), flags %d\n",
+ dbi->dbi_subfile,
+ key->data == NULL ? 0 : *(long *)key->data, key->size,
+ flags);
+#endif
+
+ key_enc_string = alloca (2 + ((257 * key->size)/254) + 1);
+ key_len=sqlite_encode_binary((char *)key->data, key->size, key_enc_string);
+ key_enc_string[key_len]='\0';
+
+#ifdef SQL_TRACE_ENCODINGS
+ rpmMessage(RPMMESS_DEBUG, " encoded key 0x%x (%d)\n",
+ *(long *)key_enc_string, key_len);
+#endif
+
+ cmd = sqlite3_mprintf("SELECT key,value FROM '%q' WHERE key='%q';",
+ dbi->dbi_subfile, key_enc_string);
+ rc = sqlite3_get_table(sqldb->db, cmd,
+ &sqlcursor->resultp, &sqlcursor->nrow, &sqlcursor->ncolumn,
+ &pzErrmsg);
+ sqlite3_free(cmd);
+
+ break;
+ }
+ }
+#ifdef SQL_TRACE_CURSOR
+ rpmMessage(RPMMESS_DEBUG, " cget got %d rows, %d columns\n",
+ sqlcursor->nrow, sqlcursor->ncolumn);
+#endif
+ }
+
+ if ( rc == 0 && ! sqlcursor->resultp )
+ rc = DB_NOTFOUND;
+
+repeat:
+ if ( rc == 0 ) {
+ sqlcursor->row_iterator++;
+ if ( sqlcursor->row_iterator > sqlcursor->nrow )
+ rc = DB_NOTFOUND; /* At the end of the list */
+ else {
+#ifdef SQL_TRACE_ENCODINGS
+ rpmMessage(RPMMESS_DEBUG, " encoded key 0x%x\n",
+ *(long *)sqlcursor->resultp[((sqlcursor->row_iterator*2)+0)]);
+ rpmMessage(RPMMESS_DEBUG, " encoded data 0x%x\n",
+ *(long *)sqlcursor->resultp[((sqlcursor->row_iterator*2)+1)]);
+#endif
+
+ /* If we're looking at the whole db, return the key */
+ if ( sqlcursor->all ) {
+ unsigned char * key_dec_string;
+ size_t key_len=strlen(sqlcursor->resultp[((sqlcursor->row_iterator*2)+0)]
+ );
+
+ key_dec_string=alloca (2 + ((257 * key_len)/254));
+
+ key->size=sqlite_decode_binary(
+ sqlcursor->resultp[((sqlcursor->row_iterator*2)+0)],
+ key_dec_string
+ );
+
+ if (key->flags & DB_DBT_MALLOC)
+ key->data=xmalloc(key->size);
+ else
+ key->data=allocTempBuffer(dbcursor, key->size);
+
+ (void) memcpy( key->data, key_dec_string, key->size );
+ }
+
+ /* Decode the data */
+ {
+ unsigned char * data_dec_string;
+ size_t data_len=strlen(sqlcursor->resultp[((sqlcursor->row_iterator*2)+1)]);
+
+ data_dec_string=alloca (2 + ((257 * data_len)/254));
+
+ data->size=sqlite_decode_binary(
+ sqlcursor->resultp[((sqlcursor->row_iterator*2)+1)],
+ data_dec_string
+ );
+
+ if (data->flags & DB_DBT_MALLOC)
+ data->data=xmalloc(data->size);
+ else
+ data->data=allocTempBuffer(dbcursor, data->size);
+
+ (void) memcpy( data->data, data_dec_string, data->size );
+ }
+
+ /* We need to skip this entry... (we've already returned it) */
+ if ( dbi->dbi_rpmtag == RPMDBI_PACKAGES &&
+ sqlcursor->all > 1 &&
+ key->size ==4 && *(long *)key->data == 0
+ ) {
+#ifdef SQL_TRACE_CURSOR
+ rpmMessage(RPMMESS_DEBUG, " cget on %s skipping 0x0 record\n",
+ dbi->dbi_subfile);
+#endif
+ goto repeat;
+ }
+
+#ifdef SQL_TRACE_CURSOR
+ rpmMessage(RPMMESS_DEBUG, " cget on %s found key 0x%x (%d)\n",
+ dbi->dbi_subfile,
+ key->data == NULL ? 0 : *(long *)key->data, key->size
+ );
+ rpmMessage(RPMMESS_DEBUG, " cget on %s found data 0x%x (%d)\n",
+ dbi->dbi_subfile,
+ key->data == NULL ? 0 : *(long *)data->data, data->size
+ );
+#endif
+ }
+ }
+
+#ifdef SQL_TRACE_CURSOR
+ if ( rc == DB_NOTFOUND )
+ rpmMessage(RPMMESS_DEBUG, " cget on %s not found\n",
+ dbi->dbi_subfile);
+#endif
+
+ /* If we retrieved the 0x0 record.. clear so next pass we'll get them all.. */
+ if ( sqlcursor->all == 1 && dbi->dbi_rpmtag == RPMDBI_PACKAGES ) {
+ if ( sqlcursor->resultp ) {
+ (void) sqlite3_free_table( sqlcursor->resultp );
+ sqlcursor->resultp = NULL;
+ sqlcursor->nrow=0;
+ sqlcursor->ncolumn=0;
+ sqlcursor->row_iterator=0;
+ }
+ }
+
+ if (cleanup)
+ /*@-temptrans@*/ (void) sql_cclose(dbi, dbcursor, 0); /*@=temptrans@*/
+
+ return rc;
+}
+
+/**
+ * Store (key,data) pair using db->put or dbcursor->c_put.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->put)
+ * @param key store key value/length/flags
+ * @param data store data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_cput (dbiIndex dbi, /*@null@*/ DBC * dbcursor, DBT * key,
+ DBT * data, unsigned int flags)
+ /*@globals fileSystem @*/
+ /*@modifies *dbcursor, fileSystem @*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+ int rc = 0;
+ unsigned char * key_enc_string, * data_enc_string;
+ int key_len, data_len;
+ char * pzErrmsg;
+ char * cmd;
+
+#ifdef SQL_TRACE_FUNCTIONS
+ rpmMessage(RPMMESS_DEBUG, "sql_cput()\n");
+#endif
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+#ifdef SQL_TRACE_CURSOR
+ rpmMessage(RPMMESS_DEBUG, " cput on %s key 0x%x (%d), data 0x%x (%d), flags %d\n",
+ dbi->dbi_subfile,
+ *(long *)key->data, key->size,
+ *(long *)data->data, data->size,
+ flags);
+#endif
+
+ key_enc_string = alloca (2 + ((257 * key->size)/254) + 1);
+ key_len=sqlite_encode_binary((char *)key->data, key->size, key_enc_string);
+ key_enc_string[key_len]='\0';
+
+ data_enc_string = alloca (2 + ((257 * data->size)/254) + 1);
+ data_len=sqlite_encode_binary((char *)data->data, data->size, data_enc_string);
+ data_enc_string[data_len]='\0';
+
+#ifdef SQL_TRACE_ENCODINGS
+ rpmMessage(RPMMESS_DEBUG, " encoded key 0x%x (%d)\n", *(long *)key_enc_string, key_len);
+ rpmMessage(RPMMESS_DEBUG, " encoded data 0x%x (%d)\n", *(long *)data_enc_string, data_len);
+#endif
+
+ cmd = sqlite3_mprintf("INSERT OR REPLACE INTO '%q' VALUES('%q', '%q');",
+ dbi->dbi_subfile, key_enc_string, data_enc_string);
+ rc = sqlite3_exec(sqldb->db, cmd, NULL, NULL, &pzErrmsg);
+ sqlite3_free(cmd);
+
+ if ( rc )
+ rpmMessage(RPMMESS_WARNING, "cput %s (%d)\n",
+ pzErrmsg, rc);
+
+ return rc;
+}
+
+/**
+ * Is database byte swapped?
+ * @param dbi index database handle
+ * @return 0 no
+ */
+static int sql_byteswapped (dbiIndex dbi)
+ /*@globals fileSystem @*/
+ /*@modifies fileSystem @*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+ int sql_rc, rc = 0;
+ char ** resultp;
+ int nrow;
+ int ncolumn;
+ char * pzErrmsg;
+ long db_endian;
+
+#ifdef SQL_TRACE_FUNCTIONS
+ rpmMessage(RPMMESS_DEBUG, "sql_byteswapped()\n");
+#endif
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ sql_rc = sqlite3_get_table(sqldb->db, "SELECT endian FROM 'db_info';",
+ &resultp, &nrow, &ncolumn, &pzErrmsg);
+
+ if (sql_rc == 0 && nrow > 0) {
+ db_endian=strtol(resultp[1], NULL, 10);
+
+ if ( db_endian == __BYTE_ORDER )
+ rc = 0; /* Native endian */
+ else
+ rc = 1; /* swapped */
+
+#if 0
+ rpmMessage(RPMMESS_DEBUG, "DB Endian %ld ?= %ld = %d\n",
+ db_endian, __BYTE_ORDER, rc);
+#endif
+ } else {
+ if ( sql_rc ) {
+ rpmMessage(RPMMESS_DEBUG, "db_info failed %s (%d)\n",
+ pzErrmsg, sql_rc);
+ }
+ rpmMessage(RPMMESS_WARNING, "Unable to determine DB endian.\n");
+ }
+
+ (void) sqlite3_free_table(resultp);
+
+ return rc;
+}
+
+/** \ingroup dbi
+ * Save statistics in database handle.
+ * @param dbi index database handle
+ * @param flags retrieve statistics that don't require traversal?
+ * @return 0 on success
+ */
+static int sql_stat (dbiIndex dbi, unsigned int flags)
+ /*@globals fileSystem @*/
+ /*@modifies dbi, fileSystem @*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+ int rc = 0;
+ char ** resultp;
+ int nrow;
+ int ncolumn;
+ char * pzErrmsg;
+ char * cmd;
+ long nkeys = -1;
+
+#ifdef SQL_TRACE_FUNCTIONS
+ rpmMessage(RPMMESS_DEBUG, "sql_stat()\n");
+#endif
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ if ( dbi->dbi_stats ) {
+ dbi->dbi_stats = _free(dbi->dbi_stats);
+ }
+
+/*@-sizeoftype@*/
+ dbi->dbi_stats = xcalloc(1, sizeof(DB_HASH_STAT));
+/*@=sizeoftype@*/
+
+ cmd = sqlite3_mprintf("SELECT COUNT('key') FROM '%q';", dbi->dbi_subfile);
+ rc = sqlite3_get_table(sqldb->db, cmd,
+ &resultp, &nrow, &ncolumn, &pzErrmsg);
+ sqlite3_free(cmd);
+
+ if ( rc == 0 && nrow > 0) {
+ nkeys=strtol(resultp[1], NULL, 10);
+
+ rpmMessage(RPMMESS_DEBUG, " stat on %s nkeys=%ld\n",
+ dbi->dbi_subfile, nkeys);
+ } else {
+ if ( rc ) {
+ rpmMessage(RPMMESS_DEBUG, "stat failed %s (%d)\n",
+ pzErrmsg, rc);
+ }
+ }
+
+ (void) sqlite3_free_table(resultp);
+
+ if (nkeys < 0)
+ nkeys = 4096; /* Good high value */
+
+ ((DB_HASH_STAT *)(dbi->dbi_stats))->hash_nkeys=nkeys;
+
+ return rc;
+}
+
+/**************************************************
+ *
+ * All of the following are not implemented!
+ * they are not used by the rest of the system
+ *
+ **************************************************/
+
+/**
+ * Associate secondary database with primary.
+ * @param dbi index database handle
+ * @param dbisecondary secondary index database handle
+ * @param callback create secondary key from primary (NULL if DB_RDONLY)
+ * @param flags DB_CREATE or 0
+ * @return 0 on success
+ */
+static int sql_associate (dbiIndex dbi, dbiIndex dbisecondary,
+ int (*callback) (DB *, const DBT *, const DBT *, DBT *),
+ unsigned int flags)
+ /*@*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+
+ /* unused */
+ rpmMessage(RPMMESS_ERROR, "sql_associate() not implemented\n");
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ return EINVAL;
+}
+
+/**
+ * Return join cursor for list of cursors.
+ * @param dbi index database handle
+ * @param curslist NULL terminated list of database cursors
+ * @retval dbcp address of join database cursor
+ * @param flags DB_JOIN_NOSORT or 0
+ * @return 0 on success
+ */
+static int sql_join (dbiIndex dbi, DBC ** curslist, /*@out@*/ DBC ** dbcp,
+ unsigned int flags)
+ /*@globals fileSystem @*/
+ /*@modifies dbi, *dbcp, fileSystem @*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+
+ /* unused */
+ rpmMessage(RPMMESS_ERROR, "sql_join() not implemented\n");
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ return EINVAL;
+}
+
+/**
+ * Duplicate a database cursor.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @retval dbcp address of new database cursor
+ * @param flags DB_POSITION for same position, 0 for uninitialized
+ * @return 0 on success
+ */
+static int sql_cdup (dbiIndex dbi, DBC * dbcursor, /*@out@*/ DBC ** dbcp,
+ unsigned int flags)
+ /*@globals fileSystem @*/
+ /*@modifies dbi, *dbcp, fileSystem @*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+
+ /* unused */
+ rpmMessage(RPMMESS_ERROR, "sql_cdup() not implemented\n");
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ return EINVAL;
+}
+
+/**
+ * Retrieve (key,data) pair using dbcursor->c_pget.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @param key secondary retrieve key value/length/flags
+ * @param pkey primary retrieve key value/length/flags
+ * @param data primary retrieve data value/length/flags
+ * @param flags DB_NEXT, DB_SET, or 0
+ * @return 0 on success
+ */
+static int sql_cpget (dbiIndex dbi, /*@null@*/ DBC * dbcursor,
+ DBT * key, DBT * pkey, DBT * data, unsigned int flags)
+ /*@globals fileSystem @*/
+ /*@modifies *dbcursor, *key, *pkey, *data, fileSystem @*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+
+ /* unused */
+ rpmMessage(RPMMESS_ERROR, "sql_cpget() not implemented\n");
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ return EINVAL;
+}
+
+/**
+ * Retrieve count of (possible) duplicate items using dbcursor->c_count.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @param countp address of count
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_ccount (dbiIndex dbi, /*@unused@*/ DBC * dbcursor,
+ /*@unused@*/ /*@out@*/ unsigned int * countp,
+ /*@unused@*/ unsigned int flags)
+ /*@globals fileSystem @*/
+ /*@modifies *dbcursor, fileSystem @*/
+{
+ DB * db = dbi->dbi_db;
+ SQL_DB * sqldb;
+
+ /* unused */
+ rpmMessage(RPMMESS_ERROR, "sql_cpget() not implemented\n");
+
+ assert(db != NULL);
+ sqldb = (SQL_DB *)db->app_private;
+ assert(sqldb != NULL && sqldb->db != NULL);
+
+ return EINVAL;
+}
+
+/* Major, minor, patch version of DB.. we're not using db.. so set to 0 */
+/* open, close, sync, associate, join */
+/* cursor_open, cursor_close, cursor_dup, cursor_delete, cursor_get, */
+/* cursor_pget?, cursor_put, cursor_count */
+/* db_bytewapped, stat */
+/*@observer@*/ /*@unchecked@*/
+struct _dbiVec sqlitevec = {
+ 0, 0, 0,
+ sql_open,
+ sql_close,
+ sql_sync,
+ sql_associate,
+ sql_join,
+ sql_copen,
+ sql_cclose,
+ sql_cdup,
+ sql_cdel,
+ sql_cget,
+ sql_cpget,
+ sql_cput,
+ sql_ccount,
+ sql_byteswapped,
+ sql_stat
+};
+
+/*@=modfilesystem@*/
+/*@=branchstate@*/
+/*@=compmempass@*/
+/*@=compdef@*/
+/*@=moduncon@*/
+/*@=noeffectuncon@*/
+/*@=globuse@*/
+/*@=paramuse@*/
+/*@=mustmod@*/
+/*@=bounds@*/