diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-sql/driver-test.c | 514 |
1 files changed, 514 insertions, 0 deletions
diff --git a/src/lib-sql/driver-test.c b/src/lib-sql/driver-test.c new file mode 100644 index 0000000..9d4db76 --- /dev/null +++ b/src/lib-sql/driver-test.c @@ -0,0 +1,514 @@ +/* Copyright (c) 2017-2018 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", + .flags = SQL_DB_FLAG_BLOCKING | SQL_DB_FLAG_ON_DUPLICATE_KEY, + + .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", + .flags = SQL_DB_FLAG_ON_CONFLICT_DO | SQL_DB_FLAG_BLOCKING, + + .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_front_modifiable(&db->expected); + 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); + + 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_front_modifiable(&db->expected); + 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_pop_front(&db->expected); + + 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_front_modifiable(&db->expected); + 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_pop_front(&db->expected); +} + +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 = t_buffer_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_push_back(&db->expected, result); +} + +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); +} |