#include "sm.h"
#ifdef STORAGE_SQLITE
#include <sqlite3.h>
typedef struct drvdata_st {
sqlite3 *db;
char *prefix;
int txn;
} *drvdata_t;
#define BLOCKSIZE (1024)
static int _st_sqlite_realloc (void **oblocks, int len) {
void *nblocks;
int nlen;
nlen = (((len-1)/BLOCKSIZE)+1)*BLOCKSIZE;
while ((nblocks = realloc(*oblocks, nlen)) == NULL) sleep (1);
*oblocks = nblocks;
return nlen;
}
#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;
unsigned int nleft = 0, nright = 0;
os_object_t o;
char *key, *cval = NULL;
void *val;
os_type_t ot;
char *xml;
int xlen;
char tbuf[128];
int res;
unsigned int i = 0;
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 {
i = 0;
nleft = 0;
nright = 0;
lleft = 0;
lright = 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);
right = NULL;
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);
left = NULL;
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;
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);
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) {
os_free(*os);
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