#include "lib.h"
#include "eacces-error.h"
#include "array.h"
+#include "hash.h"
+#include "llist.h"
#include "ioloop.h"
#include "str.h"
#include "hex-binary.h"
bool connected:1;
};
+struct sqlite_prepared_statement {
+ struct sql_prepared_statement api;
+ sqlite3_stmt *handle;
+ char *error;
+ /* Prepared statement cannot be used concurrently by multiple statements */
+ bool locked:1;
+};
+
struct sqlite_statement {
struct sql_statement api;
- struct event *event;
+ struct sqlite_prepared_statement *prep_stmt;
sqlite3_stmt *handle;
- char *error;
+ const char *error;
int rc;
};
static const char*
driver_sqlite_result_str(struct sql_db *_db, int rc);
+static int
+driver_sqlite_prepared_statement_reopen(struct sqlite_db *db,
+ struct sqlite_prepared_statement *stmt);
+
+static void driver_sqlite_finalize_handle(struct sql_db *db,
+ struct sqlite3_stmt **_handle,
+ const char *query)
+{
+ sqlite3_stmt *handle = *_handle;
+ i_assert(handle != NULL);
+ *_handle = NULL;
+
+ int rc = sqlite3_finalize(handle);
+ if (rc == SQLITE_NOMEM) {
+ i_fatal_status(FATAL_OUTOFMEM, "sqlite3_finalize(%s) failed: %s (%d)",
+ query, sqlite3_errstr(rc), rc);
+ } else if (rc != SQLITE_OK) {
+ const char *errstr = driver_sqlite_result_str(db, rc);
+ e_debug(db->event, "sqlite3_finalize(%s) failed: %s",
+ query, errstr);
+ }
+}
+
+static void driver_sqlite_finalize_prepared_statements(struct sqlite_db *db)
+{
+ if (!hash_table_is_created(db->api.prepared_stmt_hash))
+ return;
+ struct hash_iterate_context *iter =
+ hash_table_iterate_init(db->api.prepared_stmt_hash);
+ char *key ATTR_UNUSED;
+ struct sql_prepared_statement *value;
+
+ while (hash_table_iterate(iter, db->api.prepared_stmt_hash, &key, &value)) {
+ /* finalize handle */
+ struct sqlite_prepared_statement *stmt =
+ container_of(value, struct sqlite_prepared_statement, api);
+ if (stmt->handle != NULL)
+ driver_sqlite_finalize_handle(&db->api, &stmt->handle,
+ stmt->api.query_template);
+ }
+
+ hash_table_iterate_deinit(&iter);
+}
+
+static void driver_sqlite_reopen_prepared_statements(struct sqlite_db *db)
+{
+ if (!hash_table_is_created(db->api.prepared_stmt_hash))
+ return;
+ struct hash_iterate_context *iter =
+ hash_table_iterate_init(db->api.prepared_stmt_hash);
+ char *key ATTR_UNUSED;
+ struct sql_prepared_statement *value;
+
+ while (hash_table_iterate(iter, db->api.prepared_stmt_hash, &key, &value)) {
+ struct sqlite_prepared_statement *stmt =
+ container_of(value, struct sqlite_prepared_statement, api);
+ driver_sqlite_prepared_statement_reopen(db, stmt);
+ }
+
+ hash_table_iterate_deinit(&iter);
+}
static void driver_sqlite_disconnect(struct sql_db *_db)
{
struct sqlite_db *db = container_of(_db, struct sqlite_db, api);
sql_connection_log_finished(_db);
+ driver_sqlite_finalize_prepared_statements(db);
int rc = sqlite3_close(db->sqlite);
if (rc != SQLITE_OK) {
e_error(db->api.event, "sqlite3_close() failed: %s (%d)",
case SQLITE_OK:
db->connected = TRUE;
sqlite3_busy_timeout(db->sqlite, sqlite_busy_timeout);
+ driver_sqlite_reopen_prepared_statements(db);
return 1;
case SQLITE_READONLY:
i_assert(!db->set->readonly);
}
static struct sql_statement *
-driver_sqlite_statement_init(struct sql_db *_db, const char *query_template)
+driver_sqlite_statement_init_prepared(struct sql_prepared_statement *_prep_stmt)
{
+ struct sqlite_prepared_statement *prep_stmt =
+ container_of(_prep_stmt, struct sqlite_prepared_statement, api);
+ struct sqlite_db *db = container_of(_prep_stmt->db, struct sqlite_db, api);
+ i_assert(!prep_stmt->locked);
+
pool_t pool = pool_alloconly_create("sqlite statement", 1024);
+ struct sqlite_statement *stmt = p_new(pool, struct sqlite_statement, 1);
+ stmt->api.pool = pool;
+ stmt->api.db = _prep_stmt->db;
+ stmt->api.query_template = _prep_stmt->query_template;
+
+ /* handle is only valid if we are connected */
+ if (driver_sqlite_connect(_prep_stmt->db) < 0) {
+ i_free(prep_stmt->error);
+ prep_stmt->error = i_strdup(
+ driver_sqlite_result_str(_prep_stmt->db,
+ db->connect_rc));
+ }
+
+ i_assert(prep_stmt->handle != NULL || prep_stmt->error != NULL);
+
+ stmt->error = p_strdup(stmt->api.pool, prep_stmt->error);
+ stmt->prep_stmt = prep_stmt;
+ stmt->handle = prep_stmt->handle;
+ prep_stmt->locked = TRUE;
+
+ return &stmt->api;
+}
+
+static void
+driver_sqlite_release_prepared_statement(struct sqlite_statement *stmt)
+{
+ i_assert(stmt->prep_stmt != NULL);
+ i_assert(stmt->prep_stmt->locked);
+
+ struct sqlite_prepared_statement *prep_stmt = stmt->prep_stmt;
+ prep_stmt->locked = FALSE;
+ stmt->prep_stmt = NULL;
+ stmt->handle = NULL;
+
+ if (prep_stmt->handle == NULL)
+ return;
+
+ sqlite3_reset(prep_stmt->handle);
+ sqlite3_clear_bindings(prep_stmt->handle);
+}
+
+static struct sql_statement *
+driver_sqlite_statement_init(struct sql_db *_db, const char *query_template)
+{
struct sqlite_db *db = container_of(_db, struct sqlite_db, api);
+ pool_t pool = pool_alloconly_create("sqlite statement", 1024);
struct sqlite_statement *stmt = p_new(pool, struct sqlite_statement, 1);
const char *tail;
- int rc;
stmt->api.db = _db;
+ stmt->api.pool = pool;
stmt->api.query_template = p_strdup(pool, query_template);
- stmt->event = event_create(_db->event);
if (driver_sqlite_connect(_db) < 0) {
- rc = db->connect_rc;
+ stmt->rc = db->connect_rc;
} else {
- rc = sqlite3_prepare_v2(db->sqlite, query_template, -1,
+ stmt->rc = sqlite3_prepare_v2(db->sqlite, query_template, -1,
&stmt->handle, &tail);
}
- if (!SQLITE_IS_OK(rc)) {
- stmt->error = p_strdup(pool, driver_sqlite_result_str(_db, rc));
- stmt->rc = rc;
+ if (!SQLITE_IS_OK(stmt->rc)) {
+ stmt->error = p_strdup(pool,
+ driver_sqlite_result_str(_db,
+ stmt->rc));
} else if (*tail != '\0') {
- e_warning(_db->event, "Query '%s': '%s' unparsed",
- query_template, tail);
+ stmt->error = p_strdup_printf(stmt->api.pool, "'%s' unparsed",
+ tail);
+ stmt->rc = SQLITE_ERROR;
}
return &stmt->api;
}
{
struct sqlite_statement *stmt =
container_of(_stmt, struct sqlite_statement, api);
- int rc = SQLITE_OK;
- if (stmt->handle != NULL)
- rc = sqlite3_finalize(stmt->handle);
- if (rc == SQLITE_NOMEM) {
- i_fatal_status(FATAL_OUTOFMEM, "finalize failed: %s (%d)",
- sqlite3_errstr(rc), rc);
- } else if (rc != SQLITE_OK) {
- e_warning(stmt->event, "finalize failed: %s (%d)",
- sqlite3_errstr(rc), rc);
+
+ if (stmt->prep_stmt != NULL) {
+ driver_sqlite_release_prepared_statement(stmt);
+ } else if (stmt->handle != NULL) {
+ driver_sqlite_finalize_handle(stmt->api.db, &stmt->handle,
+ stmt->api.query_template);
}
- event_unref(&stmt->event);
-}
-static void driver_sqlite_statement_free(struct sqlite_statement **_stmt)
-{
- struct sqlite_statement *stmt = *_stmt;
- *_stmt = NULL;
- if (stmt == NULL)
- return;
- driver_sqlite_statement_abort(&stmt->api);
+ i_assert(stmt->handle == NULL);
+ i_assert(stmt->prep_stmt == NULL);
+ pool_unref(&stmt->api.pool);
}
static int driver_sqlite_exec_query(struct sqlite_db *db, const char *query,
if (_result->callback)
return;
- driver_sqlite_statement_free(&result->stmt);
+ driver_sqlite_statement_abort(&result->stmt->api);
+ result->stmt = NULL;
+
event_unref(&result->api.event);
i_free(result->row);
i_free(result->error);
return str_c(str);
}
+static struct sql_result *
+driver_sqlite_statement_query_s(struct sql_statement *_stmt)
+{
+ struct sqlite_statement *stmt =
+ container_of(_stmt, struct sqlite_statement, api);
+ struct sqlite_result *result =
+ driver_sqlite_statement_result_prepare(stmt);
+
+ driver_sqlite_result_log(result, sql_statement_get_log_query(_stmt));
+ return &result->api;
+}
+
+static int
+driver_sqlite_prepared_statement_reopen(struct sqlite_db *db,
+ struct sqlite_prepared_statement *prep_stmt)
+{
+ /* Maybe it works this time round */
+ i_free(prep_stmt->error);
+ prep_stmt->api.db = &db->api;
+ /* driver_sqlite_result_str() ignores rc for connect failures */
+ int rc = SQLITE_OK;
+ if (driver_sqlite_connect(&db->api) < 0 ||
+ (rc = sqlite3_prepare_v2(db->sqlite, prep_stmt->api.query_template,
+ -1, &prep_stmt->handle, NULL)) != SQLITE_OK) {
+ prep_stmt->error =
+ i_strdup(driver_sqlite_result_str(&db->api, rc));
+ return -1;
+ } else {
+ e_debug(db->api.event, "Prepared query '%s'",
+ prep_stmt->api.query_template);
+ }
+ return 0;
+}
+
+static struct sql_prepared_statement *
+driver_sqlite_prepared_statement_init(struct sql_db *_db,
+ const char *query_template)
+{
+ struct sqlite_db *db = container_of(_db, struct sqlite_db, api);
+ struct sqlite_prepared_statement *prep_stmt =
+ i_new(struct sqlite_prepared_statement, 1);
+ prep_stmt->api.query_template = i_strdup(query_template);
+ prep_stmt->api.refcount = 1;
+ prep_stmt->api.db = _db;
+
+ (void)driver_sqlite_prepared_statement_reopen(db, prep_stmt);
+
+ return &prep_stmt->api;
+}
+
+static void
+driver_sqlite_prepared_statement_deinit(struct sql_prepared_statement *_prep_stmt)
+{
+ struct sqlite_prepared_statement *prep_stmt =
+ container_of(_prep_stmt, struct sqlite_prepared_statement, api);
+ if (prep_stmt->handle != NULL) {
+ driver_sqlite_finalize_handle(prep_stmt->api.db, &prep_stmt->handle,
+ prep_stmt->api.query_template);
+ }
+ i_free(prep_stmt->api.query_template);
+ i_free(prep_stmt->error);
+ i_free(_prep_stmt);
+}
+
+static void
+driver_sqlite_bind_error(const char *func, struct sqlite_statement *stmt,
+ unsigned int column_idx)
+{
+ const char *errstr = driver_sqlite_result_str(stmt->api.db, stmt->rc);
+ if (stmt->rc == SQLITE_NOMEM) {
+ i_fatal_status(FATAL_OUTOFMEM, "%s(%u) for query '%s': %s",
+ func, column_idx, stmt->api.query_template,
+ errstr);
+ } else if (stmt->rc != SQLITE_OK) {
+ stmt->error = p_strdup_printf(stmt->api.pool, "%s(%u) for query '%s': %s",
+ func, column_idx, stmt->api.query_template,
+ errstr);
+ }
+}
+
+static void
+driver_sqlite_statement_bind_str(struct sql_statement *_stmt,
+ unsigned int column_idx, const char *value)
+{
+ struct sqlite_statement *stmt =
+ container_of(_stmt, struct sqlite_statement, api);
+ if (stmt->rc != SQLITE_OK)
+ return;
+ stmt->rc = sqlite3_bind_text(stmt->handle, column_idx + 1, value, -1,
+ SQLITE_TRANSIENT);
+ driver_sqlite_bind_error("sqlite3_bind_text", stmt, column_idx);
+}
+
+static void
+driver_sqlite_statement_bind_binary(struct sql_statement *_stmt,
+ unsigned int column_idx, const void *value,
+ size_t value_size)
+{
+ struct sqlite_statement *stmt =
+ container_of(_stmt, struct sqlite_statement, api);
+ if (stmt->rc != SQLITE_OK)
+ return;
+ stmt->rc = sqlite3_bind_blob(stmt->handle, column_idx + 1, value,
+ value_size, SQLITE_TRANSIENT);
+ driver_sqlite_bind_error("sqlite3_bind_blob", stmt, column_idx);
+}
+
+static void
+driver_sqlite_statement_bind_int64(struct sql_statement *_stmt,
+ unsigned int column_idx, int64_t value)
+{
+ struct sqlite_statement *stmt =
+ container_of(_stmt, struct sqlite_statement, api);
+ if (stmt->rc != SQLITE_OK)
+ return;
+ stmt->rc = sqlite3_bind_int64(stmt->handle, column_idx + 1, value);
+ driver_sqlite_bind_error("sqlite3_bind_int64", stmt, column_idx);
+}
+
+static void
+driver_sqlite_statement_bind_double(struct sql_statement *_stmt,
+ unsigned int column_idx, double value)
+{
+ struct sqlite_statement *stmt =
+ container_of(_stmt, struct sqlite_statement, api);
+ if (stmt->rc != SQLITE_OK)
+ return;
+ stmt->rc = sqlite3_bind_double(stmt->handle, column_idx + 1, value);
+ driver_sqlite_bind_error("sqlite3_bind_double", stmt, column_idx);
+}
+
+static void
+driver_sqlite_statement_bind_uuid(struct sql_statement *stmt,
+ unsigned int column_idx, const guid_128_t uuid)
+
+{
+ const char *uuid_str = guid_128_to_uuid_string(uuid, FORMAT_RECORD);
+ driver_sqlite_statement_bind_str(stmt, column_idx, uuid_str);
+}
+
+static void
+driver_sqlite_update_stmt(struct sql_transaction_context *_ctx,
+ struct sql_statement *_stmt,
+ unsigned int *affected_rows)
+{
+ struct sqlite_transaction_context *ctx =
+ container_of(_ctx, struct sqlite_transaction_context, ctx);
+ struct sqlite_db *db =
+ container_of(_ctx->db, struct sqlite_db, api);
+ struct sqlite_statement *stmt =
+ container_of(_stmt, struct sqlite_statement, api);
+ /* execute statement */
+ struct sql_result *_res = driver_sqlite_statement_query_s(&stmt->api);
+ struct sqlite_result *res =
+ container_of(_res, struct sqlite_result, api);
+ if (sql_result_next_row(_res) < 0) {
+ ctx->rc = res->rc;
+ i_free(ctx->error);
+ ctx->error = i_strdup(driver_sqlite_result_str(stmt->api.db, ctx->rc));
+ if (affected_rows != NULL)
+ *affected_rows = 0;
+ } else if (SQLITE_IS_OK(res->rc) && affected_rows != NULL)
+ *affected_rows = sqlite3_changes(db->sqlite);
+
+ sql_result_unref(_res);
+}
+
const struct sql_db driver_sqlite_db = {
.name = "sqlite",
.flags =
.update = driver_sqlite_update,
.escape_blob = driver_sqlite_escape_blob,
+
+ .prepared_statement_init = driver_sqlite_prepared_statement_init,
+ .prepared_statement_deinit = driver_sqlite_prepared_statement_deinit,
+
+ .statement_init = driver_sqlite_statement_init,
+ .statement_init_prepared = driver_sqlite_statement_init_prepared,
+ .statement_abort = driver_sqlite_statement_abort,
+
+ .statement_bind_str = driver_sqlite_statement_bind_str,
+ .statement_bind_binary = driver_sqlite_statement_bind_binary,
+ .statement_bind_int64 = driver_sqlite_statement_bind_int64,
+ .statement_bind_double = driver_sqlite_statement_bind_double,
+ .statement_bind_uuid = driver_sqlite_statement_bind_uuid,
+
+ .statement_query_s = driver_sqlite_statement_query_s,
+
+ .update_stmt = driver_sqlite_update_stmt,
+
}
};
static const char sql_create_db[] =
"CREATE TABLE bar(\n"
" foo VARCHAR(255)\n"
-");\n";
+");\n"
+"CREATE TABLE test2(\n"
+" str VARCHAR(255), uuid VARCHAR(36),\n"
+" num INT, blob BLOB\n"
+")\n";
static void setup_database(struct sql_db *sql)
{
test_assert(sql_result_next_row(cursor) == SQL_RESULT_NEXT_LAST);
sql_result_unref(cursor);
- sql_unref(&sql);
+ struct sql_prepared_statement *prep_stmt =
+ sql_prepared_statement_init(sql, "INSERT INTO bar VALUES(?)");
+ struct sql_statement *stmt =
+ sql_statement_init_prepared(prep_stmt);
+ sql_statement_bind_str(stmt, 0, "value3");
+ cursor = sql_statement_query_s(&stmt);
+ test_assert(sql_result_next_row(cursor) == SQL_RESULT_NEXT_LAST);
+ sql_result_unref(cursor);
+
+ stmt = sql_statement_init(sql, "SELECT foo FROM bar WHERE foo = ?");
+ sql_statement_bind_str(stmt, 0, "value3");
+ cursor = sql_statement_query_s(&stmt);
+ test_assert(sql_result_next_row(cursor) == SQL_RESULT_NEXT_OK);
+ test_assert_ucmp(sql_result_get_fields_count(cursor), ==, 1);
+ test_assert_strcmp(sql_result_get_field_name(cursor, 0), "foo");
+ test_assert_strcmp(sql_result_get_field_value(cursor, 0), "value3");
+ sql_result_unref(cursor);
+ sql_prepared_statement_unref(&prep_stmt);
+
+ stmt = sql_statement_init(sql, "INSERT INTO test2 VALUES(?,?,?,?)");
+ sql_statement_bind_str(stmt, 0, "test_str");
+ guid_128_t uuid;
+ int ret = guid_128_from_uuid_string("426b3821-3c6c-4ed7-a936-ec8d664c53d0", uuid);
+ i_assert(ret == 0);
+ sql_statement_bind_uuid(stmt, 1, uuid);
+ sql_statement_bind_int64(stmt, 2, 123456);
+ sql_statement_bind_binary(stmt, 3, "\xFF\xFF\x00\x00\xFF", 5);
+ cursor = sql_statement_query_s(&stmt);
+ test_assert(sql_result_next_row(cursor) == SQL_RESULT_NEXT_LAST);
+ sql_result_unref(cursor);
+
+ stmt = sql_statement_init(sql, "SELECT * FROM test2");
+ cursor = sql_statement_query_s(&stmt);
+ test_assert(sql_result_next_row(cursor) == SQL_RESULT_NEXT_OK);
+ test_assert_ucmp(sql_result_get_fields_count(cursor), ==, 4);
+ test_assert_strcmp(sql_result_get_field_name(cursor, 0), "str");
+ test_assert_strcmp(sql_result_get_field_value(cursor, 0), "test_str");
+ test_assert_strcmp(sql_result_get_field_name(cursor, 1), "uuid");
+ test_assert_strcmp(sql_result_get_field_value(cursor, 1), "426b3821-3c6c-4ed7-a936-ec8d664c53d0");
+ test_assert_strcmp(sql_result_get_field_name(cursor, 2), "num");
+ test_assert_strcmp(sql_result_get_field_value(cursor, 2), "123456");
+ size_t size;
+ const unsigned char *value =
+ sql_result_get_field_value_binary(cursor, 3, &size);
+ test_assert_ucmp(size, ==, 5);
+ test_assert_memcmp(value, "\xFF\xFF\x00\x00\xFF", 5);
+ sql_result_unref(cursor);
+
+ prep_stmt = sql_prepared_statement_init(sql, "SELECT foo FROM bar WHERE foo = ?");
+ sql_disconnect(sql);
+ stmt = sql_statement_init_prepared(prep_stmt);
+ sql_statement_bind_str(stmt, 0, "value3");
+ cursor = sql_statement_query_s(&stmt);
+ test_assert(sql_result_next_row(cursor) == SQL_RESULT_NEXT_OK);
+ test_assert_ucmp(sql_result_get_fields_count(cursor), ==, 1);
+ test_assert_strcmp(sql_result_get_field_name(cursor, 0), "foo");
+ test_assert_strcmp(sql_result_get_field_value(cursor, 0), "value3");
+ sql_result_unref(cursor);
+ sql_prepared_statement_unref(&prep_stmt);
+
+ sql_unref(&sql);
driver_sqlite_deinit();
sql_drivers_deinit_without_drivers();
settings_simple_deinit(&set);