changeset 22513:59b0c5ee8adf

lib-sql: Explicitly specify used *_vfuncs methods for drivers. This allows adding more methods without modifying all the existing drivers.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Tue, 22 Aug 2017 11:31:51 +0300
parents bcc3a15c18a6
children 7d189977dfe3
files src/lib-sql/driver-cassandra.c src/lib-sql/driver-mysql.c src/lib-sql/driver-pgsql.c src/lib-sql/driver-sqlite.c src/lib-sql/driver-sqlpool.c src/lib-sql/driver-test.c
diffstat 6 files changed, 618 insertions(+), 118 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-sql/driver-cassandra.c	Tue Aug 08 20:07:18 2017 +0300
+++ b/src/lib-sql/driver-cassandra.c	Tue Aug 22 11:31:51 2017 +0300
@@ -1611,24 +1611,23 @@
 	.flags = 0,
 
 	.v = {
-		driver_cassandra_init_v,
-		driver_cassandra_deinit_v,
-		driver_cassandra_connect,
-		driver_cassandra_disconnect,
-		driver_cassandra_escape_string,
-		driver_cassandra_exec,
-		driver_cassandra_query,
-		driver_cassandra_query_s,
+		.init = driver_cassandra_init_v,
+		.deinit = driver_cassandra_deinit_v,
+		.connect = driver_cassandra_connect,
+		.disconnect = driver_cassandra_disconnect,
+		.escape_string = driver_cassandra_escape_string,
+		.exec = driver_cassandra_exec,
+		.query = driver_cassandra_query,
+		.query_s = driver_cassandra_query_s,
 
-		driver_cassandra_transaction_begin,
-		NULL,
-		driver_cassandra_transaction_commit_s,
-		driver_cassandra_transaction_rollback,
+		.transaction_begin = driver_cassandra_transaction_begin,
+		.transaction_commit2 = driver_cassandra_transaction_commit,
+		.transaction_commit_s = driver_cassandra_transaction_commit_s,
+		.transaction_rollback = driver_cassandra_transaction_rollback,
 
-		driver_cassandra_update,
+		.update = driver_cassandra_update,
 
-		driver_cassandra_escape_blob,
-		driver_cassandra_transaction_commit
+		.escape_blob = driver_cassandra_escape_blob,
 	}
 };
 
--- a/src/lib-sql/driver-mysql.c	Tue Aug 08 20:07:18 2017 +0300
+++ b/src/lib-sql/driver-mysql.c	Tue Aug 22 11:31:51 2017 +0300
@@ -662,40 +662,38 @@
 	.flags = SQL_DB_FLAG_BLOCKING | SQL_DB_FLAG_POOLED,
 
 	.v = {
-		driver_mysql_init_v,
-		driver_mysql_deinit_v,
-		driver_mysql_connect,
-		driver_mysql_disconnect,
-		driver_mysql_escape_string,
-		driver_mysql_exec,
-		driver_mysql_query,
-		driver_mysql_query_s,
+		.init = driver_mysql_init_v,
+		.deinit = driver_mysql_deinit_v,
+		.connect = driver_mysql_connect,
+		.disconnect = driver_mysql_disconnect,
+		.escape_string = driver_mysql_escape_string,
+		.exec = driver_mysql_exec,
+		.query = driver_mysql_query,
+		.query_s = driver_mysql_query_s,
 
-		driver_mysql_transaction_begin,
-		driver_mysql_transaction_commit,
-		driver_mysql_transaction_commit_s,
-		driver_mysql_transaction_rollback,
+		.transaction_begin = driver_mysql_transaction_begin,
+		.transaction_commit = driver_mysql_transaction_commit,
+		.transaction_commit_s = driver_mysql_transaction_commit_s,
+		.transaction_rollback = driver_mysql_transaction_rollback,
 
-		driver_mysql_update,
+		.update = driver_mysql_update,
 
-		driver_mysql_escape_blob,
-		NULL
+		.escape_blob = driver_mysql_escape_blob,
 	}
 };
 
 const struct sql_result driver_mysql_result = {
 	.v = {
-		driver_mysql_result_free,
-		driver_mysql_result_next_row,
-		driver_mysql_result_get_fields_count,
-		driver_mysql_result_get_field_name,
-		driver_mysql_result_find_field,
-		driver_mysql_result_get_field_value,
-		driver_mysql_result_get_field_value_binary,
-		driver_mysql_result_find_field_value,
-		driver_mysql_result_get_values,
-		driver_mysql_result_get_error,
-		NULL,
+		.free = driver_mysql_result_free,
+		.next_row = driver_mysql_result_next_row,
+		.get_fields_count = driver_mysql_result_get_fields_count,
+		.get_field_name = driver_mysql_result_get_field_name,
+		.find_field = driver_mysql_result_find_field,
+		.get_field_value = driver_mysql_result_get_field_value,
+		.get_field_value_binary = driver_mysql_result_get_field_value_binary,
+		.find_field_value = driver_mysql_result_find_field_value,
+		.get_values = driver_mysql_result_get_values,
+		.get_error = driver_mysql_result_get_error,
 	}
 };
 
@@ -707,11 +705,9 @@
 
 const struct sql_result driver_mysql_error_result = {
 	.v = {
-		driver_mysql_result_free,
-		driver_mysql_result_error_next_row,
-		NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-		driver_mysql_result_get_error,
-		NULL,
+		.free = driver_mysql_result_free,
+		.next_row = driver_mysql_result_error_next_row,
+		.get_error = driver_mysql_result_get_error,
 	},
 	.failed_try_retry = TRUE
 };
--- a/src/lib-sql/driver-pgsql.c	Tue Aug 08 20:07:18 2017 +0300
+++ b/src/lib-sql/driver-pgsql.c	Tue Aug 22 11:31:51 2017 +0300
@@ -1149,40 +1149,38 @@
 	.flags = SQL_DB_FLAG_POOLED,
 
 	.v = {
-		driver_pgsql_init_v,
-		driver_pgsql_deinit_v,
-		driver_pgsql_connect,
-		driver_pgsql_disconnect,
-		driver_pgsql_escape_string,
-		driver_pgsql_exec,
-		driver_pgsql_query,
-		driver_pgsql_query_s,
+		.init = driver_pgsql_init_v,
+		.deinit = driver_pgsql_deinit_v,
+		.connect = driver_pgsql_connect,
+		.disconnect = driver_pgsql_disconnect,
+		.escape_string = driver_pgsql_escape_string,
+		.exec = driver_pgsql_exec,
+		.query = driver_pgsql_query,
+		.query_s = driver_pgsql_query_s,
 
-		driver_pgsql_transaction_begin,
-		driver_pgsql_transaction_commit,
-		driver_pgsql_transaction_commit_s,
-		driver_pgsql_transaction_rollback,
+		.transaction_begin = driver_pgsql_transaction_begin,
+		.transaction_commit = driver_pgsql_transaction_commit,
+		.transaction_commit_s = driver_pgsql_transaction_commit_s,
+		.transaction_rollback = driver_pgsql_transaction_rollback,
 
-		driver_pgsql_update,
+		.update = driver_pgsql_update,
 
-		driver_pgsql_escape_blob,
-		NULL
+		.escape_blob = driver_pgsql_escape_blob,
 	}
 };
 
 const struct sql_result driver_pgsql_result = {
 	.v = {
-		driver_pgsql_result_free,
-		driver_pgsql_result_next_row,
-		driver_pgsql_result_get_fields_count,
-		driver_pgsql_result_get_field_name,
-		driver_pgsql_result_find_field,
-		driver_pgsql_result_get_field_value,
-		driver_pgsql_result_get_field_value_binary,
-		driver_pgsql_result_find_field_value,
-		driver_pgsql_result_get_values,
-		driver_pgsql_result_get_error,
-		NULL,
+		.free = driver_pgsql_result_free,
+		.next_row = driver_pgsql_result_next_row,
+		.get_fields_count = driver_pgsql_result_get_fields_count,
+		.get_field_name = driver_pgsql_result_get_field_name,
+		.find_field = driver_pgsql_result_find_field,
+		.get_field_value = driver_pgsql_result_get_field_value,
+		.get_field_value_binary = driver_pgsql_result_get_field_value_binary,
+		.find_field_value = driver_pgsql_result_find_field_value,
+		.get_values = driver_pgsql_result_get_values,
+		.get_error = driver_pgsql_result_get_error,
 	}
 };
 
--- a/src/lib-sql/driver-sqlite.c	Tue Aug 08 20:07:18 2017 +0300
+++ b/src/lib-sql/driver-sqlite.c	Tue Aug 22 11:31:51 2017 +0300
@@ -410,39 +410,38 @@
 	.flags = SQL_DB_FLAG_BLOCKING,
 
 	.v = {
-		driver_sqlite_init_v,
-		driver_sqlite_deinit_v,
-		driver_sqlite_connect,
-		driver_sqlite_disconnect,
-		driver_sqlite_escape_string,
-		driver_sqlite_exec,
-		driver_sqlite_query,
-		driver_sqlite_query_s,
+		.init = driver_sqlite_init_v,
+		.deinit = driver_sqlite_deinit_v,
+		.connect = driver_sqlite_connect,
+		.disconnect = driver_sqlite_disconnect,
+		.escape_string = driver_sqlite_escape_string,
+		.exec = driver_sqlite_exec,
+		.query = driver_sqlite_query,
+		.query_s = driver_sqlite_query_s,
 
-		driver_sqlite_transaction_begin,
-		driver_sqlite_transaction_commit,
-		driver_sqlite_transaction_commit_s,
-		driver_sqlite_transaction_rollback,
-		driver_sqlite_update,
+		.transaction_begin = driver_sqlite_transaction_begin,
+		.transaction_commit = driver_sqlite_transaction_commit,
+		.transaction_commit_s = driver_sqlite_transaction_commit_s,
+		.transaction_rollback = driver_sqlite_transaction_rollback,
 
-		driver_sqlite_escape_blob,
-		NULL
+		.update = driver_sqlite_update,
+
+		.escape_blob = driver_sqlite_escape_blob,
 	}
 };
 
 const struct sql_result driver_sqlite_result = {
 	.v = {
-		driver_sqlite_result_free,
-		driver_sqlite_result_next_row,
-		driver_sqlite_result_get_fields_count,
-		driver_sqlite_result_get_field_name,
-		driver_sqlite_result_find_field,
-		driver_sqlite_result_get_field_value,
-		driver_sqlite_result_get_field_value_binary,
-		driver_sqlite_result_find_field_value,
-		driver_sqlite_result_get_values,
-		driver_sqlite_result_get_error,
-		NULL,
+		.free = driver_sqlite_result_free,
+		.next_row = driver_sqlite_result_next_row,
+		.get_fields_count = driver_sqlite_result_get_fields_count,
+		.get_field_name = driver_sqlite_result_get_field_name,
+		.find_field = driver_sqlite_result_find_field,
+		.get_field_value = driver_sqlite_result_get_field_value,
+		.get_field_value_binary = driver_sqlite_result_get_field_value_binary,
+		.find_field_value = driver_sqlite_result_find_field_value,
+		.get_values = driver_sqlite_result_get_values,
+		.get_error = driver_sqlite_result_get_error,
 	}
 };
 
@@ -454,11 +453,9 @@
 
 const struct sql_result driver_sqlite_error_result = {
 	.v = {
-		driver_sqlite_result_free,
-		driver_sqlite_result_error_next_row,
-		NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-		driver_sqlite_result_get_error,
-		NULL,
+		.free = driver_sqlite_result_free,
+		.next_row = driver_sqlite_result_error_next_row,
+		.get_error = driver_sqlite_result_get_error,
 	}
 };
 
--- a/src/lib-sql/driver-sqlpool.c	Tue Aug 08 20:07:18 2017 +0300
+++ b/src/lib-sql/driver-sqlpool.c	Tue Aug 22 11:31:51 2017 +0300
@@ -816,23 +816,21 @@
 	"",
 
 	.v = {
-		NULL,
-		driver_sqlpool_deinit,
-		driver_sqlpool_connect,
-		driver_sqlpool_disconnect,
-		driver_sqlpool_escape_string,
-		driver_sqlpool_exec,
-		driver_sqlpool_query,
-		driver_sqlpool_query_s,
+		.deinit = driver_sqlpool_deinit,
+		.connect = driver_sqlpool_connect,
+		.disconnect = driver_sqlpool_disconnect,
+		.escape_string = driver_sqlpool_escape_string,
+		.exec = driver_sqlpool_exec,
+		.query = driver_sqlpool_query,
+		.query_s = driver_sqlpool_query_s,
 
-		driver_sqlpool_transaction_begin,
-		driver_sqlpool_transaction_commit,
-		driver_sqlpool_transaction_commit_s,
-		driver_sqlpool_transaction_rollback,
+		.transaction_begin = driver_sqlpool_transaction_begin,
+		.transaction_commit = driver_sqlpool_transaction_commit,
+		.transaction_commit_s = driver_sqlpool_transaction_commit_s,
+		.transaction_rollback = driver_sqlpool_transaction_rollback,
 
-		driver_sqlpool_update,
+		.update = driver_sqlpool_update,
 
-		driver_sqlpool_escape_blob,
-		NULL
+		.escape_blob = driver_sqlpool_escape_blob,
 	}
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-sql/driver-test.c	Tue Aug 22 11:31:51 2017 +0300
@@ -0,0 +1,512 @@
+/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-lib.h"
+#include "str.h"
+#include "buffer.h"
+#include "sql-api-private.h"
+#include "driver-test.h"
+#include "array.h"
+#include "hex-binary.h"
+
+struct test_sql_db {
+	struct sql_db api;
+
+	pool_t pool;
+	ARRAY(struct test_driver_result) expected;
+	const char *error;
+	bool failed:1;
+};
+
+struct test_sql_result {
+	struct sql_result api;
+	struct test_driver_result *result;
+	const char *error;
+};
+
+static struct sql_db *driver_test_mysql_init(const char *connect_string);
+static struct sql_db *driver_test_cassandra_init(const char *connect_string);
+static struct sql_db *driver_test_sqlite_init(const char *connect_string);
+static void driver_test_deinit(struct sql_db *_db);
+static int driver_test_connect(struct sql_db *_db);
+static void driver_test_disconnect(struct sql_db *_db);
+static const char *
+driver_test_mysql_escape_string(struct sql_db *_db, const char *string);
+static const char *
+driver_test_escape_string(struct sql_db *_db, const char *string);
+static void driver_test_exec(struct sql_db *_db, const char *query);
+static void driver_test_query(struct sql_db *_db, const char *query,
+			       sql_query_callback_t *callback, void *context);
+static struct sql_result *
+driver_test_query_s(struct sql_db *_db, const char *query);
+static struct sql_transaction_context *
+driver_test_transaction_begin(struct sql_db *_db);
+static void driver_test_transaction_commit(struct sql_transaction_context *ctx,
+					    sql_commit_callback_t *callback,
+					    void *context);
+static int
+driver_test_transaction_commit_s(struct sql_transaction_context *ctx,
+				  const char **error_r);
+static void
+driver_test_transaction_rollback(struct sql_transaction_context *ctx);
+static void
+driver_test_update(struct sql_transaction_context *ctx, const char *query,
+		    unsigned int *affected_rows);
+static const char *
+driver_test_mysql_escape_blob(struct sql_db *_db, const unsigned char *data,
+			 size_t size);
+static const char *
+driver_test_escape_blob(struct sql_db *_db, const unsigned char *data,
+			 size_t size);
+
+static void driver_test_result_free(struct sql_result *result);
+static int driver_test_result_next_row(struct sql_result *result);
+
+static unsigned int
+driver_test_result_get_fields_count(struct sql_result *result);
+static const char *
+driver_test_result_get_field_name(struct sql_result *result, unsigned int idx);
+static int
+driver_test_result_find_field(struct sql_result *result, const char *field_name);
+
+static const char *
+driver_test_result_get_field_value(struct sql_result *result, unsigned int idx);
+static const unsigned char *
+driver_test_result_get_field_value_binary(struct sql_result *result,
+					   unsigned int idx, size_t *size_r);
+static const char *
+driver_test_result_find_field_value(struct sql_result *result,
+				     const char *field_name);
+static const char *const *
+driver_test_result_get_values(struct sql_result *result);
+
+const char *driver_test_result_get_error(struct sql_result *result);
+
+
+const struct sql_db driver_test_mysql_db = {
+	.name = "mysql",
+
+	.v = {
+		.init = driver_test_mysql_init,
+		.deinit = driver_test_deinit,
+		.connect = driver_test_connect,
+		.disconnect = driver_test_disconnect,
+		.escape_string = driver_test_mysql_escape_string,
+		.exec = driver_test_exec,
+		.query = driver_test_query,
+		.query_s = driver_test_query_s,
+
+		.transaction_begin = driver_test_transaction_begin,
+		.transaction_commit = driver_test_transaction_commit,
+		.transaction_commit_s = driver_test_transaction_commit_s,
+		.transaction_rollback = driver_test_transaction_rollback,
+		.update = driver_test_update,
+
+		.escape_blob = driver_test_mysql_escape_blob,
+	}
+};
+
+const struct sql_db driver_test_cassandra_db = {
+	.name = "cassandra",
+
+	.v = {
+		.init = driver_test_cassandra_init,
+		.deinit = driver_test_deinit,
+		.connect = driver_test_connect,
+		.disconnect = driver_test_disconnect,
+		.escape_string = driver_test_escape_string,
+		.exec = driver_test_exec,
+		.query = driver_test_query,
+		.query_s = driver_test_query_s,
+
+		.transaction_begin = driver_test_transaction_begin,
+		.transaction_commit = driver_test_transaction_commit,
+		.transaction_commit_s = driver_test_transaction_commit_s,
+		.transaction_rollback = driver_test_transaction_rollback,
+		.update = driver_test_update,
+
+		.escape_blob = driver_test_escape_blob,
+	}
+};
+
+const struct sql_db driver_test_sqlite_db = {
+	.name = "sqlite",
+
+	.v = {
+		.init = driver_test_sqlite_init,
+		.deinit = driver_test_deinit,
+		.connect = driver_test_connect,
+		.disconnect = driver_test_disconnect,
+		.escape_string = driver_test_escape_string,
+		.exec = driver_test_exec,
+		.query = driver_test_query,
+		.query_s = driver_test_query_s,
+
+		.transaction_begin = driver_test_transaction_begin,
+		.transaction_commit = driver_test_transaction_commit,
+		.transaction_commit_s = driver_test_transaction_commit_s,
+		.transaction_rollback = driver_test_transaction_rollback,
+		.update = driver_test_update,
+
+		.escape_blob = driver_test_escape_blob,
+	}
+};
+
+
+const struct sql_result driver_test_result = {
+	.v = {
+		.free = driver_test_result_free,
+		.next_row = driver_test_result_next_row,
+		.get_fields_count = driver_test_result_get_fields_count,
+		.get_field_name = driver_test_result_get_field_name,
+		.find_field = driver_test_result_find_field,
+		.get_field_value = driver_test_result_get_field_value,
+		.get_field_value_binary = driver_test_result_get_field_value_binary,
+		.find_field_value = driver_test_result_find_field_value,
+		.get_values = driver_test_result_get_values,
+		.get_error = driver_test_result_get_error,
+	}
+};
+
+void sql_driver_test_register(void)
+{
+	sql_driver_register(&driver_test_mysql_db);
+	sql_driver_register(&driver_test_cassandra_db);
+	sql_driver_register(&driver_test_sqlite_db);
+}
+
+void sql_driver_test_unregister(void)
+{
+	sql_driver_unregister(&driver_test_mysql_db);
+	sql_driver_unregister(&driver_test_cassandra_db);
+	sql_driver_unregister(&driver_test_sqlite_db);
+}
+
+static struct sql_db *driver_test_init(const struct sql_db *driver,
+					const char *connect_string ATTR_UNUSED)
+{
+	pool_t pool = pool_alloconly_create(MEMPOOL_GROWING" test sql driver", 2048);
+	struct test_sql_db *ret = p_new(pool, struct test_sql_db, 1);
+	ret->pool = pool;
+	ret->api = *driver;
+	p_array_init(&ret->expected, pool, 8);
+	return &ret->api;
+}
+
+static struct sql_db *driver_test_mysql_init(const char *connect_string)
+{
+	return driver_test_init(&driver_test_mysql_db, connect_string);
+}
+
+static struct sql_db *driver_test_cassandra_init(const char *connect_string)
+{
+	return driver_test_init(&driver_test_cassandra_db, connect_string);
+}
+
+static struct sql_db *driver_test_sqlite_init(const char *connect_string)
+{
+	return driver_test_init(&driver_test_sqlite_db, connect_string);
+}
+
+static void driver_test_deinit(struct sql_db *_db ATTR_UNUSED)
+{
+	struct test_sql_db *db = (struct test_sql_db*)_db;
+	array_free(&_db->module_contexts);
+	pool_unref(&db->pool);
+}
+
+static int driver_test_connect(struct sql_db *_db ATTR_UNUSED)
+{
+	/* nix */
+	return 0;
+}
+
+static void driver_test_disconnect(struct sql_db *_db ATTR_UNUSED)
+{ }
+
+static const char *
+driver_test_mysql_escape_string(struct sql_db *_db ATTR_UNUSED,
+				 const char *string)
+{
+	string_t *esc = t_str_new(strlen(string));
+	for(const char *ptr = string; *ptr != '\0'; ptr++) {
+		if (*ptr == '\n' || *ptr == '\r' || *ptr == '\\' ||
+		    *ptr == '\'' || *ptr == '\"' || *ptr == '\x1a')
+			str_append_c(esc, '\\');
+		str_append_c(esc, *ptr);
+	}
+	return str_c(esc);
+}
+
+static const char *
+driver_test_escape_string(struct sql_db *_db ATTR_UNUSED, const char *string)
+{
+	return string;
+}
+
+static void driver_test_exec(struct sql_db *_db, const char *query)
+{
+	struct test_sql_db *db = (struct test_sql_db*)_db;
+	struct test_driver_result *result =
+		array_idx_modifiable(&db->expected, 0);
+	i_assert(result->cur < result->nqueries);
+
+/*	i_debug("DUMMY EXECUTE: %s", query);
+	i_debug("DUMMY EXPECT : %s", result->queries[result->cur]); */
+
+	test_assert(strcmp(result->queries[result->cur], query)==0);
+
+	if (strcmp(result->queries[result->cur], query) != 0) {
+		db->error = "Invalid query";
+		db->failed = TRUE;
+	}
+
+	result->cur++;
+}
+
+static void
+driver_test_query(struct sql_db *_db, const char *query,
+		   sql_query_callback_t *callback, void *context)
+{
+	struct sql_result *result = driver_test_query_s(_db, query);
+	if (callback != NULL)
+		callback(result, context);
+}
+
+static struct sql_result *
+driver_test_query_s(struct sql_db *_db, const char *query)
+{
+	struct test_sql_db *db = (struct test_sql_db*)_db;
+	struct test_driver_result *result =
+		array_idx_modifiable(&db->expected, 0);
+	struct test_sql_result *res = i_new(struct test_sql_result, 1);
+
+	driver_test_exec(_db, query);
+
+	if (db->failed) {
+		res->api.failed = TRUE;
+	}
+
+	res->api.v = driver_test_result.v;
+	res->api.db = _db;
+	if (result->result != NULL) {
+		res->result = i_new(struct test_driver_result, 1);
+		memcpy(res->result, result, sizeof(*result));
+	}
+	res->api.refcount = 1;
+
+	/* drop it from array if it's used up */
+	if (result->cur == result->nqueries)
+		array_delete(&db->expected, 0, 1);
+
+	return &res->api;
+}
+
+static struct sql_transaction_context *
+driver_test_transaction_begin(struct sql_db *_db)
+{
+	struct sql_transaction_context *ctx =
+		i_new(struct sql_transaction_context, 1);
+	ctx->db = _db;
+	return ctx;
+}
+
+static void
+driver_test_transaction_commit(struct sql_transaction_context *ctx,
+				sql_commit_callback_t *callback, void *context)
+{
+	struct sql_commit_result res;
+	res.error_type = driver_test_transaction_commit_s(ctx, &res.error);
+	callback(&res, context);
+}
+
+static int
+driver_test_transaction_commit_s(struct sql_transaction_context *ctx,
+				  const char **error_r)
+{
+	struct test_sql_db *db = (struct test_sql_db*)ctx->db;
+	int ret = 0;
+
+	if (db->error != NULL) {
+		*error_r = db->error;
+		ret = -1;
+	}
+	i_free(ctx);
+	db->error = NULL;
+	db->failed = FALSE;
+
+	return ret;
+}
+
+static void
+driver_test_transaction_rollback(struct sql_transaction_context *ctx)
+{
+	struct test_sql_db *db = (struct test_sql_db*)ctx->db;
+	i_free(ctx);
+	db->error = NULL;
+	db->failed = FALSE;
+}
+
+static void
+driver_test_update(struct sql_transaction_context *ctx, const char *query,
+		    unsigned int *affected_rows)
+{
+	struct test_sql_db *db= (struct test_sql_db*)ctx->db;
+	struct test_driver_result *result =
+		array_idx_modifiable(&db->expected, 0);
+	driver_test_exec(ctx->db, query);
+
+	if (affected_rows != NULL)
+		*affected_rows = result->affected_rows;
+
+	/* drop it from array if it's used up */
+	if (result->cur == result->nqueries)
+		array_delete(&db->expected, 0, 1);
+}
+
+static const char *
+driver_test_mysql_escape_blob(struct sql_db *_db ATTR_UNUSED,
+			       const unsigned char *data, size_t size)
+{
+	return t_strdup_printf("X'%s'", binary_to_hex(data,size));
+}
+
+static const char *
+driver_test_escape_blob(struct sql_db *_db ATTR_UNUSED,
+			 const unsigned char *data, size_t size)
+{
+	return t_strdup_printf("X'%s'", binary_to_hex(data,size));
+}
+
+static void driver_test_result_free(struct sql_result *result)
+{
+        struct test_sql_result *tsr =
+                (struct test_sql_result *)result;
+	if (tsr->result != NULL)
+		i_free(tsr->result);
+	i_free(result);
+}
+
+static int driver_test_result_next_row(struct sql_result *result)
+{
+	struct test_sql_result *tsr =
+		(struct test_sql_result *)result;
+	struct test_driver_result *r = tsr->result;
+
+	if (r == NULL) return 0;
+
+	struct test_driver_result_set *rs =
+		&(r->result[r->cur-1]);
+	if (rs->cur <= rs->rows) {
+		rs->cur++;
+	}
+
+	return rs->cur <= rs->rows ? 1 : 0;
+}
+
+static unsigned int
+driver_test_result_get_fields_count(struct sql_result *result)
+{
+        struct test_sql_result *tsr =
+                (struct test_sql_result *)result;
+        struct test_driver_result *r = tsr->result;
+	struct test_driver_result_set *rs =
+		&(r->result[r->cur-1]);
+	return rs->cols;
+}
+
+static const char *
+driver_test_result_get_field_name(struct sql_result *result, unsigned int idx)
+{
+        struct test_sql_result *tsr =
+                (struct test_sql_result *)result;
+        struct test_driver_result *r = tsr->result;
+	struct test_driver_result_set *rs =
+		&(r->result[r->cur-1]);
+	i_assert(idx < rs->cols);
+	return rs->col_names[idx];
+}
+
+static int
+driver_test_result_find_field(struct sql_result *result, const char *field_name)
+{
+        struct test_sql_result *tsr =
+                (struct test_sql_result *)result;
+        struct test_driver_result *r = tsr->result;
+	struct test_driver_result_set *rs =
+		&(r->result[r->cur-1]);
+	for(size_t i = 0; i < rs->cols; i++) {
+		if (strcmp(field_name, rs->col_names[i])==0)
+			return i;
+	}
+	return -1;
+}
+
+static const char *
+driver_test_result_get_field_value(struct sql_result *result, unsigned int idx)
+{
+        struct test_sql_result *tsr =
+                (struct test_sql_result *)result;
+        struct test_driver_result *r = tsr->result;
+	struct test_driver_result_set *rs =
+		&(r->result[r->cur-1]);
+
+	i_assert(idx < rs->cols);
+	i_assert(rs->cur <= rs->rows);
+
+	return rs->row_data[rs->cur-1][idx];
+}
+static const unsigned char *
+driver_test_result_get_field_value_binary(struct sql_result *result,
+					   unsigned int idx, size_t *size_r)
+{
+	buffer_t *buf = buffer_create_dynamic(pool_datastack_create(), 64);
+	const char *value = driver_test_result_get_field_value(result, idx);
+	/* expect it hex encoded */
+	if (hex_to_binary(value, buf) < 0) {
+		*size_r = 0;
+		return NULL;
+	}
+	*size_r = buf->used;
+	return buf->data;
+}
+static const char *
+driver_test_result_find_field_value(struct sql_result *result,
+				     const char *field_name)
+{
+	int idx = driver_test_result_find_field(result, field_name);
+	if (idx < 0) return NULL;
+	return driver_test_result_get_field_value(result, idx);
+}
+static const char *const *
+driver_test_result_get_values(struct sql_result *result)
+{
+        struct test_sql_result *tsr =
+                (struct test_sql_result *)result;
+        struct test_driver_result *r = tsr->result;
+	struct test_driver_result_set *rs =
+		&(r->result[r->cur-1]);
+	i_assert(rs->cur <= rs->rows);
+	return rs->row_data[rs->cur-1];
+}
+
+const char *driver_test_result_get_error(struct sql_result *result)
+{
+        struct test_sql_result *tsr =
+                (struct test_sql_result *)result;
+	return tsr->error;
+}
+
+
+void sql_driver_test_add_expected_result(struct sql_db *_db,
+					  const struct test_driver_result *result)
+{
+	struct test_sql_db *db = (struct test_sql_db*)_db;
+	array_append(&db->expected, result, 1);
+}
+
+void sql_driver_test_clear_expected_results(struct sql_db *_db)
+{
+	struct test_sql_db *db = (struct test_sql_db*)_db;
+	array_clear(&db->expected);
+}