--- /dev/null
+/*
+ * jabberd - Jabber Open Source Server
+ * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney,
+ * Ryan Eatmon, Robert Norris
+ * Copyright (c) 2004 Christof Meerwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA
+ */
+
+/* Released under the GPL by Chris Parker <parkerc@i-vsn.com>, IVSN
+ * to the Jabberd project.
+ */
+
+/* Modified and updated for SQLite 3 by Christof Meerwald,
+ * http://cmeerw.org
+ */
+
+#include "sm.h"
+
+#ifdef STORAGE_SQLITE
+
+#include <sqlite3.h>
+
+/** internal structure, holds our data */
+typedef struct drvdata_st {
+ sqlite3 *db;
+ char *prefix;
+ int txn;
+} *drvdata_t;
+
+#define BLOCKSIZE (1024)
+
+
+/** internal: do and return the math and ensure it gets realloc'd */
+static int _st_sqlite_realloc (void **oblocks, int len) {
+ void *nblocks;
+ int nlen;
+
+ /* round up to standard block sizes */
+ nlen = (((len-1)/BLOCKSIZE)+1)*BLOCKSIZE;
+
+ /* keep trying till we get it */
+ while ((nblocks = realloc(*oblocks, nlen)) == NULL) sleep (1);
+ *oblocks = nblocks;
+
+ return nlen;
+}
+
+/** this is the safety check used to make sure there's always enough mem */
+#define SQLITE_SAFE(blocks, size, len) \
+ if((size) >= (len)) \
+ len = _st_sqlite_realloc((void**)&(blocks),(size) + 1);
+
+#define SQLITE_SAFE_CAT(blocks, size, len, s1) \
+ do { \
+ SQLITE_SAFE(blocks, size + sizeof (s1) - 1, len); \
+ memcpy (&blocks[size], s1, sizeof (s1)); \
+ size += sizeof (s1) - 1; \
+ } while (0)
+
+#define SQLITE_SAFE_CAT3(blocks, size, len, s1, s2, s3) \
+ do { \
+ const unsigned int l = strlen (s2); \
+ SQLITE_SAFE(blocks, size + sizeof (s1) + l + sizeof (s2) - 2, len); \
+ memcpy (&blocks[size], s1, sizeof (s1) - 1); \
+ memcpy (&blocks[size + sizeof (s1) - 1], s2, l); \
+ memcpy (&blocks[size + sizeof (s1) - 1 + l], s3, sizeof (s3)); \
+ size += sizeof (s1) + l + sizeof (s3) - 2; \
+ } while (0)
+
+static void _st_sqlite_convert_filter_recursive (st_filter_t f, char **buf,
+ int *buflen, int *nbuf) {
+
+ st_filter_t scan;
+
+ switch (f->type) {
+ case st_filter_type_PAIR:
+ SQLITE_SAFE_CAT3 ((*buf), *nbuf, *buflen,
+ "( \"", f->key, "\" = ? ) ");
+ break;
+
+ case st_filter_type_AND:
+ SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, "( ");
+
+ for (scan = f->sub; scan != NULL; scan = scan->next) {
+ _st_sqlite_convert_filter_recursive (scan, buf,
+ buflen, nbuf);
+
+ if (scan->next != NULL) {
+ SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, "AND ");
+ }
+ }
+
+ SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, ") ");
+
+ return;
+
+ case st_filter_type_OR:
+ SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, "( ");
+
+ for (scan = f->sub; scan != NULL; scan = scan->next) {
+ _st_sqlite_convert_filter_recursive (scan, buf,
+ buflen, nbuf);
+
+ if (scan->next != NULL) {
+ SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, "OR ");
+ }
+ }
+
+ SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, ") ");
+
+ return;
+
+ case st_filter_type_NOT:
+ SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, "( NOT ");
+
+ _st_sqlite_convert_filter_recursive(f->sub, buf,
+ buflen, nbuf);
+
+ SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, ") ");
+
+ return;
+ }
+}
+
+static char *_st_sqlite_convert_filter (st_driver_t drv, const char *owner,
+ const char *filter) {
+
+ drvdata_t data = (drvdata_t) drv->private;
+ char *buf = NULL;
+ unsigned int buflen = 0, nbuf = 0;
+ st_filter_t f;
+
+
+ SQLITE_SAFE_CAT (buf, nbuf, buflen, "\"collection-owner\" = ?");
+
+ f = storage_filter (filter);
+ if (f == NULL) {
+ return buf;
+ }
+
+ SQLITE_SAFE_CAT (buf, nbuf, buflen, " AND ");
+
+ _st_sqlite_convert_filter_recursive (f, &buf, &buflen, &nbuf);
+
+ pool_free (f->p);
+
+ return buf;
+}
+
+static void _st_sqlite_bind_filter_recursive (st_filter_t f,
+ sqlite3_stmt *stmt,
+ unsigned int bind_off) {
+
+ st_filter_t scan;
+ unsigned int i;
+
+ switch (f->type) {
+ case st_filter_type_PAIR:
+ sqlite3_bind_text (stmt, bind_off, f->val, strlen (f->val),
+ SQLITE_TRANSIENT);
+ break;
+
+ case st_filter_type_AND:
+ for (scan = f->sub, i = 0; scan != NULL; scan = scan->next, ++i) {
+ _st_sqlite_bind_filter_recursive (scan, stmt, bind_off + i);
+ }
+ return;
+
+ case st_filter_type_OR:
+ for (scan = f->sub, i = 0; scan != NULL; scan = scan->next, ++i) {
+ _st_sqlite_bind_filter_recursive (scan, stmt, bind_off + i);
+ }
+ return;
+
+ case st_filter_type_NOT:
+ _st_sqlite_bind_filter_recursive(f->sub, stmt, bind_off);
+ return;
+ }
+}
+
+static void _st_sqlite_bind_filter (st_driver_t drv, const char *owner,
+ const char *filter,
+ sqlite3_stmt *stmt,
+ unsigned int bind_off) {
+
+ drvdata_t data = (drvdata_t) drv->private;
+ st_filter_t f;
+
+
+ sqlite3_bind_text (stmt, bind_off, owner, strlen (owner),
+ SQLITE_TRANSIENT);
+
+ f = storage_filter (filter);
+ if (f == NULL) {
+ return;
+ }
+
+ _st_sqlite_bind_filter_recursive (f, stmt, bind_off + 1);
+
+ pool_free (f->p);
+}
+
+static st_ret_t _st_sqlite_add_type (st_driver_t drv, const char *type) {
+
+ return st_SUCCESS;
+}
+
+static st_ret_t _st_sqlite_put_guts (st_driver_t drv, const char *type,
+ const char *owner, os_t os) {
+
+ drvdata_t data = (drvdata_t) drv->private;
+ char *left = NULL, *right = NULL;
+ unsigned int lleft = 0, lright = 0;
+ os_object_t o;
+ char *key, *cval = NULL;
+ void *val;
+ os_type_t ot;
+ char *xml;
+ int xlen;
+ char tbuf[128];
+ int res;
+
+ if (os_count (os) == 0) {
+ return st_SUCCESS;
+ }
+
+ if (data->prefix != NULL) {
+ snprintf (tbuf, sizeof (tbuf), "%s%s", data->prefix, type);
+ type = tbuf;
+ }
+
+ if (os_iter_first (os)) {
+ do {
+
+ unsigned int i = 0;
+ unsigned int nleft = 0, nright = 0;
+ sqlite3_stmt *stmt;
+
+
+ SQLITE_SAFE_CAT3 (left, nleft, lleft,
+ "INSERT INTO \"", type,
+ "\" ( \"collection-owner\"");
+ SQLITE_SAFE_CAT (right, nright, lright, " ) VALUES ( ?");
+
+ o = os_iter_object (os);
+ if (os_object_iter_first(o))
+ do {
+ os_object_iter_get (o, &key, &val, &ot);
+
+ log_debug (ZONE, "key %s val %s", key, cval);
+
+ SQLITE_SAFE_CAT3 (left, nleft, lleft,
+ ", \"", key, "\"");
+
+ SQLITE_SAFE_CAT (right, nright, lright, ", ?");
+
+ } while (os_object_iter_next (o));
+
+ SQLITE_SAFE (left, nleft + nright, lleft);
+ memcpy (&left[nleft], right, nright);
+ nleft += nright;
+ free (right);
+
+ SQLITE_SAFE_CAT (left, nleft, lleft, " )");
+
+ log_debug (ZONE, "prepared sql: %s", left);
+
+ res = sqlite3_prepare (data->db, left, strlen (left), &stmt, NULL);
+ free (left);
+ if (res != SQLITE_OK) {
+ log_write (drv->st->sm->log, LOG_ERR,
+ "sqlite: sql insert failed: %s",
+ sqlite3_errmsg (data->db));
+ return st_FAILED;
+ }
+
+ sqlite3_bind_text (stmt, 1, owner, strlen (owner),
+ SQLITE_TRANSIENT);
+
+ o = os_iter_object (os);
+ if (os_object_iter_first(o))
+ do {
+ os_object_iter_get (o, &key, &val, &ot);
+
+ switch(ot) {
+ case os_type_BOOLEAN:
+ sqlite3_bind_int (stmt, i + 2, (int) val ? 1 : 0);
+ break;
+
+ case os_type_INTEGER:
+ sqlite3_bind_int (stmt, i + 2, (int) val);
+ break;
+
+ case os_type_STRING:
+ sqlite3_bind_text (stmt, i + 2,
+ (const char *) val,
+ strlen ((const char *) val),
+ SQLITE_TRANSIENT);
+ break;
+
+ /* !!! might not be a good idea to mark nads this way */
+ case os_type_NAD:
+ nad_print ((nad_t) val, 0, &xml, &xlen);
+ cval = (char *) malloc(sizeof(char) * (xlen + 4));
+ memcpy (&cval[3], xml, xlen + 1);
+ memcpy (cval, "NAD", 3);
+
+ sqlite3_bind_text (stmt, i + 2,
+ cval, xlen + 3, free);
+ break;
+ }
+
+ i += 1;
+ } while (os_object_iter_next (o));
+
+ res = sqlite3_step (stmt);
+ if (res != SQLITE_DONE) {
+ log_write (drv->st->sm->log, LOG_ERR,
+ "sqlite: sql insert failed: %s",
+ sqlite3_errmsg (data->db));
+ sqlite3_finalize (stmt);
+ return st_FAILED;
+ }
+ sqlite3_finalize (stmt);
+
+ } while (os_iter_next (os));
+ }
+
+ return st_SUCCESS;
+}
+
+static st_ret_t _st_sqlite_put (st_driver_t drv, const char *type,
+ const char *owner, os_t os) {
+
+ drvdata_t data = (drvdata_t) drv->private;
+ int res;
+ char *err_msg = NULL;
+
+ if (os_count (os) == 0) {
+ return st_SUCCESS;
+ }
+
+ if (data->txn) {
+
+ res = sqlite3_exec (data->db,
+ "BEGIN", NULL, NULL,
+ &err_msg);
+ if (res != SQLITE_OK) {
+ log_write (drv->st->sm->log, LOG_ERR,
+ "sqlite: sql transaction begin failed: %s",
+ err_msg);
+ sqlite3_free (err_msg);
+ return st_FAILED;
+ }
+ }
+
+ if (_st_sqlite_put_guts (drv, type, owner, os) != st_SUCCESS) {
+ if (data->txn) {
+ res = sqlite3_exec (data->db, "ROLLBACK",
+ NULL, NULL, NULL);
+ }
+ return st_FAILED;
+ }
+
+ if (data->txn) {
+
+ res = sqlite3_exec (data->db, "COMMIT", NULL, NULL, &err_msg);
+ if (res != SQLITE_OK) {
+ log_write (drv->st->sm->log, LOG_ERR,
+ "sqlite: sql transaction commit failed: %s",
+ err_msg);
+ sqlite3_exec (data->db, "ROLLBACK", NULL, NULL, NULL);
+ return st_FAILED;
+ }
+ }
+ return st_SUCCESS;
+}
+
+static st_ret_t _st_sqlite_get (st_driver_t drv, const char *type,
+ const char *owner, const char *filter,
+ os_t *os) {
+
+ drvdata_t data = (drvdata_t) drv->private;
+ char *cond, *buf = NULL;
+ unsigned int nbuf = 0;
+ unsigned int buflen = 0;
+ int i;
+ unsigned long *lengths;
+ unsigned int num_rows = 0;
+ os_object_t o;
+ const char *val;
+ os_type_t ot;
+ int ival;
+ nad_t nad;
+ char tbuf[128];
+
+ sqlite3_stmt *stmt;
+ int result;
+
+ if (data->prefix != NULL) {
+ snprintf (tbuf, sizeof (tbuf), "%s%s", data->prefix, type);
+ type = tbuf;
+ }
+
+ cond = _st_sqlite_convert_filter (drv, owner, filter);
+
+ SQLITE_SAFE_CAT3 (buf, nbuf, buflen,
+ "SELECT * FROM \"", type, "\" WHERE ");
+ strcpy (&buf[nbuf], cond);
+ /* ORDER BY 'object-sequence'", type, cond); */
+ free (cond);
+
+ log_debug (ZONE, "prepared sql: %s", buf);
+
+ result = sqlite3_prepare (data->db, buf, strlen (buf), &stmt, NULL);
+ free (buf);
+ if (result != SQLITE_OK) {
+ return st_FAILED;
+ }
+
+ _st_sqlite_bind_filter (drv, owner, filter, stmt, 1);
+
+ *os = os_new ();
+
+ do {
+
+ unsigned int num_cols;
+
+ result = sqlite3_step (stmt);
+
+ if (result != SQLITE_ROW) {
+ continue;
+ }
+
+ o = os_object_new (*os);
+ num_cols = sqlite3_data_count (stmt);
+
+ for (i = 0; i < num_cols; i++) {
+
+ const char *colname;
+ int coltype;
+
+ colname = sqlite3_column_name (stmt, i);
+
+ if (strcmp (colname, "collection-owner") == 0 ||
+ strcmp (colname, "object-sequence") == 0) {
+ continue;
+ }
+
+ coltype = sqlite3_column_type (stmt, i);
+
+ if (coltype == SQLITE_NULL) {
+ log_debug (ZONE, "coldata is NULL");
+ continue;
+ }
+
+ if (coltype == SQLITE_INTEGER) {
+ if (!strcmp (sqlite3_column_decltype (stmt, i), "BOOL")) {
+ ot = os_type_BOOLEAN;
+ } else {
+ ot = os_type_INTEGER;
+ }
+
+ ival = sqlite3_column_int (stmt, i);
+ os_object_put (o, colname, &ival, ot);
+
+ } else if (coltype == SQLITE3_TEXT) {
+ ot = os_type_STRING;
+
+ val = sqlite3_column_text (stmt, i);
+ os_object_put (o, colname, val, ot);
+
+ } else {
+ log_write (drv->st->sm->log,
+ LOG_NOTICE,
+ "sqlite: unknown field: %s:%d",
+ colname, coltype);
+ }
+ }
+
+ num_rows++;
+
+ } while (result == SQLITE_ROW);
+
+ sqlite3_finalize (stmt);
+
+ if (num_rows == 0) {
+ return st_NOTFOUND;
+ }
+
+ return st_SUCCESS;
+}
+
+static st_ret_t _st_sqlite_delete (st_driver_t drv, const char *type,
+ const char *owner, const char *filter) {
+
+ drvdata_t data = (drvdata_t) drv->private;
+ char *cond, *buf = NULL;
+ unsigned int nbuf = 0;
+ unsigned int buflen = 0;
+ char tbuf[128];
+ int res;
+ sqlite3_stmt *stmt;
+
+ if (data->prefix != NULL) {
+ snprintf (tbuf, sizeof (tbuf), "%s%s", data->prefix, type);
+ type = tbuf;
+ }
+
+ cond = _st_sqlite_convert_filter (drv, owner, filter);
+ log_debug (ZONE, "generated filter: %s", cond);
+
+ SQLITE_SAFE_CAT3 (buf, nbuf, buflen,
+ "DELETE FROM \"", type, "\" WHERE ");
+ strcpy (&buf[nbuf], cond);
+ free (cond);
+
+ log_debug (ZONE, "prepared sql: %s", buf);
+
+ res = sqlite3_prepare (data->db, buf, strlen (buf), &stmt, NULL);
+ free (buf);
+ if (res != SQLITE_OK) {
+ return st_FAILED;
+ }
+
+ _st_sqlite_bind_filter (drv, owner, filter, stmt, 1);
+
+ res = sqlite3_step (stmt);
+ if (res != SQLITE_DONE) {
+ log_write (drv->st->sm->log, LOG_ERR,
+ "sqlite: sql delete failed: %s",
+ sqlite3_errmsg (data->db));
+ sqlite3_finalize (stmt);
+ return st_FAILED;
+ }
+ sqlite3_finalize (stmt);
+
+ return st_SUCCESS;
+}
+
+static st_ret_t _st_sqlite_replace (st_driver_t drv, const char *type,
+ const char *owner, const char *filter,
+ os_t os) {
+
+ drvdata_t data = (drvdata_t) drv->private;
+
+ int res;
+ char *err_msg = NULL;
+
+ if (data->txn) {
+
+ res = sqlite3_exec (data->db, "BEGIN", NULL, NULL, &err_msg);
+ if (res != SQLITE_OK) {
+ log_write (drv->st->sm->log, LOG_ERR,
+ "sqlite: sql transaction begin failed: %s",
+ err_msg);
+ sqlite3_free (err_msg);
+ return st_FAILED;
+ }
+ }
+
+ if (_st_sqlite_delete (drv, type, owner, filter) == st_FAILED) {
+ if (data->txn) {
+ sqlite3_exec (data->db, "ROLLBACK", NULL, NULL, NULL);
+ }
+ return st_FAILED;
+ }
+
+ if (_st_sqlite_put_guts (drv, type, owner, os) == st_FAILED) {
+ if (data->txn) {
+ sqlite3_exec (data->db, "ROLLBACK", NULL, NULL, NULL);
+ }
+ return st_FAILED;
+ }
+
+ if (data->txn) {
+
+ res = sqlite3_exec (data->db, "COMMIT", NULL, NULL, &err_msg);
+
+ if (res != SQLITE_OK) {
+ log_write (drv->st->sm->log, LOG_ERR,
+ "sqlite: sql transaction commit failed: %s",
+ err_msg);
+ sqlite3_exec (data->db, "ROLLBACK", NULL, NULL, NULL);
+
+ return st_FAILED;
+ }
+ }
+
+ return st_SUCCESS;
+}
+
+static void _st_sqlite_free (st_driver_t drv) {
+
+ drvdata_t data = (drvdata_t) drv->private;
+
+ sqlite3_close (data->db);
+
+ free (data);
+}
+
+st_ret_t st_sqlite_init(st_driver_t drv) {
+
+ char *dbname;
+ sqlite3 *db;
+ drvdata_t data;
+ int ret;
+ char *busy_timeout;
+
+ dbname = config_get_one (drv->st->sm->config,
+ "storage.sqlite.dbname", 0);
+ if (dbname == NULL) {
+ log_write (drv->st->sm->log, LOG_ERR,
+ "sqlite: invalid driver config");
+ return st_FAILED;
+ }
+
+ ret = sqlite3_open (dbname, &db);
+ if (ret != SQLITE_OK) {
+ log_write (drv->st->sm->log, LOG_ERR,
+ "sqlite: can't open database");
+ return st_FAILED;
+ }
+
+ data = (drvdata_t) malloc (sizeof (struct drvdata_st));
+ memset(data, 0, sizeof(struct drvdata_st));
+
+ data->db = db;
+
+ if (config_get_one (drv->st->sm->config,
+ "storage.sqlite.transactions", 0) != NULL) {
+ data->txn = 1;
+ } else {
+ log_write (drv->st->sm->log, LOG_WARNING,
+ "sqlite: transactions disabled");
+ }
+
+ busy_timeout = config_get_one (drv->st->sm->config,
+ "storage.sqlite.busy-timeout", 0);
+ if (busy_timeout != NULL) {
+ sqlite3_busy_timeout (db, atoi (busy_timeout));
+ }
+
+ data->prefix = config_get_one (drv->st->sm->config,
+ "storage.sqlite.prefix", 0);
+
+ drv->private = (void *) data;
+ drv->add_type = _st_sqlite_add_type;
+ drv->put = _st_sqlite_put;
+ drv->get = _st_sqlite_get;
+ drv->delete = _st_sqlite_delete;
+ drv->replace = _st_sqlite_replace;
+ drv->free = _st_sqlite_free;
+
+ return st_SUCCESS;
+}
+
+#endif