driver-mysql.c revision 575fa28d92361c068b2c08d393605e1de661d922
335N/A/* Copyright (c) 2003-2016 Dovecot authors, see the included COPYING file */
1186N/A
1186N/A#include "lib.h"
0N/A#include "ioloop.h"
0N/A#include "array.h"
0N/A#include "hex-binary.h"
335N/A#include "str.h"
0N/A#include "net.h"
0N/A#include "sql-api-private.h"
0N/A
0N/A#ifdef BUILD_MYSQL
0N/A#include <unistd.h>
0N/A#include <time.h>
0N/A#ifdef HAVE_ATTR_NULL
0N/A/* ugly way to tell clang that mysql.h is a system header and we don't want
0N/A to enable nonnull attributes for it by default.. */
0N/A# 4 "driver-mysql.c" 3
0N/A#endif
0N/A#include <mysql.h>
0N/A#ifdef HAVE_ATTR_NULL
1244N/A# 4 "driver-mysql.c" 3
1384N/A# line 20
1186N/A#endif
1474N/A#include <errmsg.h>
1474N/A
1474N/A#define MYSQL_DEFAULT_READ_TIMEOUT_SECS 30
1474N/A#define MYSQL_DEFAULT_WRITE_TIMEOUT_SECS 30
1474N/A
1186N/Astruct mysql_db {
1355N/A struct sql_db api;
1355N/A
1186N/A pool_t pool;
1186N/A const char *user, *password, *dbname, *host, *unix_socket;
1186N/A const char *ssl_cert, *ssl_key, *ssl_ca, *ssl_ca_path, *ssl_cipher;
1186N/A int ssl_verify_server_cert;
1325N/A const char *option_file, *option_group;
1325N/A in_port_t port;
1186N/A unsigned int client_flags;
1463N/A unsigned int connect_timeout, read_timeout, write_timeout;
1186N/A time_t last_success;
1463N/A
1186N/A MYSQL *mysql;
1186N/A unsigned int next_query_connection;
1186N/A
1127N/A unsigned int ssl_set:1;
0N/A};
1186N/A
1384N/Astruct mysql_result {
1384N/A struct sql_result api;
1186N/A
1355N/A MYSQL_RES *result;
1325N/A MYSQL_ROW row;
1355N/A
1384N/A MYSQL_FIELD *fields;
1355N/A unsigned int fields_count;
1281N/A
1108N/A my_ulonglong affected_rows;
1473N/A};
1473N/A
1473N/Astruct mysql_transaction_context {
1473N/A struct sql_transaction_context ctx;
1473N/A
1473N/A pool_t query_pool;
1473N/A const char *error;
1473N/A
1473N/A unsigned int failed:1;
1473N/A};
1473N/A
1473N/Aextern const struct sql_db driver_mysql_db;
1473N/Aextern const struct sql_result driver_mysql_result;
1473N/Aextern const struct sql_result driver_mysql_error_result;
1145N/A
1281N/Astatic const char *mysql_prefix(struct mysql_db *db)
1108N/A{
1186N/A return db->host == NULL ? "mysql" :
335N/A t_strdup_printf("mysql(%s)", db->host);
1364N/A}
1186N/A
1186N/Astatic int driver_mysql_connect(struct sql_db *_db)
1473N/A{
1473N/A struct mysql_db *db = (struct mysql_db *)_db;
1281N/A const char *unix_socket, *host;
1473N/A unsigned long client_flags = db->client_flags;
1473N/A unsigned int secs_used;
1473N/A time_t start_time;
1473N/A bool failed;
1473N/A
1473N/A i_assert(db->api.state == SQL_DB_STATE_DISCONNECTED);
1473N/A
1474N/A sql_db_set_state(&db->api, SQL_DB_STATE_CONNECTING);
1474N/A
1384N/A if (db->host == NULL) {
1474N/A /* assume option_file overrides the host, or if not we'll just
1473N/A connect to localhost */
1473N/A unix_socket = NULL;
1473N/A host = NULL;
1473N/A } else if (*db->host == '/') {
1473N/A unix_socket = db->host;
1473N/A host = NULL;
1473N/A } else {
1473N/A unix_socket = NULL;
1473N/A host = db->host;
1473N/A }
1473N/A
1473N/A if (db->option_file != NULL) {
1473N/A mysql_options(db->mysql, MYSQL_READ_DEFAULT_FILE,
1473N/A db->option_file);
1473N/A }
1325N/A
1474N/A mysql_options(db->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &db->connect_timeout);
1474N/A mysql_options(db->mysql, MYSQL_OPT_READ_TIMEOUT, &db->read_timeout);
1474N/A mysql_options(db->mysql, MYSQL_OPT_WRITE_TIMEOUT, &db->write_timeout);
1474N/A mysql_options(db->mysql, MYSQL_READ_DEFAULT_GROUP,
1474N/A db->option_group != NULL ? db->option_group : "client");
1474N/A
1474N/A if (!db->ssl_set && (db->ssl_ca != NULL || db->ssl_ca_path != NULL)) {
1474N/A#ifdef HAVE_MYSQL_SSL
1474N/A mysql_ssl_set(db->mysql, db->ssl_key, db->ssl_cert,
1474N/A db->ssl_ca, db->ssl_ca_path
1474N/A#ifdef HAVE_MYSQL_SSL_CIPHER
1474N/A , db->ssl_cipher
1474N/A#endif
1474N/A );
1474N/A#ifdef HAVE_MYSQL_SSL_VERIFY_SERVER_CERT
1474N/A mysql_options(db->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT,
1474N/A (void *)&db->ssl_verify_server_cert);
1474N/A#endif
1474N/A db->ssl_set = TRUE;
1474N/A#else
1474N/A i_fatal("mysql: SSL support not compiled in "
1463N/A "(remove ssl_ca and ssl_ca_path settings)");
1474N/A#endif
1325N/A }
1325N/A
1325N/A#ifdef CLIENT_MULTI_RESULTS
1325N/A client_flags |= CLIENT_MULTI_RESULTS;
1325N/A#endif
1325N/A /* CLIENT_MULTI_RESULTS allows the use of stored procedures */
1474N/A start_time = time(NULL);
1474N/A failed = mysql_real_connect(db->mysql, host, db->user, db->password,
1384N/A db->dbname, db->port, unix_socket,
1474N/A client_flags) == NULL;
1325N/A secs_used = time(NULL) - start_time;
1325N/A if (failed) {
1325N/A /* connecting could have taken a while. make sure that any
1325N/A timeouts that get added soon will get a refreshed
1474N/A timestamp. */
1474N/A io_loop_time_refresh();
1474N/A
1474N/A if (db->api.connect_delay < secs_used)
1474N/A db->api.connect_delay = secs_used;
1474N/A sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED);
1474N/A i_error("%s: Connect failed to database (%s): %s - "
1474N/A "waiting for %u seconds before retry",
1474N/A mysql_prefix(db), db->dbname,
1474N/A mysql_error(db->mysql), db->api.connect_delay);
1474N/A return -1;
1474N/A } else {
1463N/A db->last_success = ioloop_time;
1463N/A sql_db_set_state(&db->api, SQL_DB_STATE_IDLE);
1474N/A return 1;
1474N/A }
1474N/A}
1474N/A
1474N/Astatic void driver_mysql_disconnect(struct sql_db *_db ATTR_UNUSED)
1474N/A{
1325N/A}
1325N/A
1389N/Astatic void driver_mysql_parse_connect_string(struct mysql_db *db,
1325N/A const char *connect_string)
1474N/A{
1469N/A const char *const *args, *name, *value;
1474N/A const char **field;
1474N/A
1474N/A db->ssl_cipher = "HIGH";
1474N/A db->ssl_verify_server_cert = 1;
1474N/A db->connect_timeout = SQL_CONNECT_TIMEOUT_SECS;
1325N/A db->read_timeout = MYSQL_DEFAULT_READ_TIMEOUT_SECS;
1474N/A db->write_timeout = MYSQL_DEFAULT_WRITE_TIMEOUT_SECS;
1325N/A
1474N/A args = t_strsplit_spaces(connect_string, " ");
1474N/A for (; *args != NULL; args++) {
1474N/A value = strchr(*args, '=');
1474N/A if (value == NULL) {
1474N/A i_fatal("mysql: Missing value in connect string: %s",
1474N/A *args);
1474N/A }
1474N/A name = t_strdup_until(*args, value);
1474N/A value++;
1463N/A
1474N/A field = NULL;
1474N/A if (strcmp(name, "host") == 0 ||
1474N/A strcmp(name, "hostaddr") == 0)
1474N/A field = &db->host;
1474N/A else if (strcmp(name, "user") == 0)
1474N/A field = &db->user;
1474N/A else if (strcmp(name, "password") == 0)
1474N/A field = &db->password;
1368N/A else if (strcmp(name, "dbname") == 0)
1474N/A field = &db->dbname;
1368N/A else if (strcmp(name, "port") == 0) {
1368N/A if (net_str2port(value, &db->port) < 0)
1368N/A i_fatal("mysql: Invalid port number: %s", value);
1368N/A } else if (strcmp(name, "client_flags") == 0) {
1368N/A if (str_to_uint(value, &db->client_flags) < 0)
1368N/A i_fatal("mysql: Invalid client flags: %s", value);
1474N/A } else if (strcmp(name, "connect_timeout") == 0) {
1474N/A if (str_to_uint(value, &db->connect_timeout) < 0)
1474N/A i_fatal("mysql: Invalid read_timeout: %s", value);
1474N/A } else if (strcmp(name, "read_timeout") == 0) {
1474N/A if (str_to_uint(value, &db->read_timeout) < 0)
1474N/A i_fatal("mysql: Invalid read_timeout: %s", value);
1474N/A } else if (strcmp(name, "write_timeout") == 0) {
1325N/A if (str_to_uint(value, &db->write_timeout) < 0)
1325N/A i_fatal("mysql: Invalid read_timeout: %s", value);
1389N/A } else if (strcmp(name, "ssl_cert") == 0)
1474N/A field = &db->ssl_cert;
1469N/A else if (strcmp(name, "ssl_key") == 0)
1469N/A field = &db->ssl_key;
1469N/A else if (strcmp(name, "ssl_ca") == 0)
1474N/A field = &db->ssl_ca;
1474N/A else if (strcmp(name, "ssl_ca_path") == 0)
1474N/A field = &db->ssl_ca_path;
1469N/A else if (strcmp(name, "ssl_cipher") == 0)
1463N/A field = &db->ssl_cipher;
1325N/A else if (strcmp(name, "ssl_verify_server_cert") == 0) {
1463N/A if (strcmp(value, "yes") == 0)
1325N/A db->ssl_verify_server_cert = 1;
1325N/A else if (strcmp(value, "no") == 0)
1325N/A db->ssl_verify_server_cert = 0;
1368N/A else
1474N/A i_fatal("mysql: Invalid boolean: %s", value);
1474N/A } else if (strcmp(name, "option_file") == 0)
1474N/A field = &db->option_file;
1474N/A else if (strcmp(name, "option_group") == 0)
1474N/A field = &db->option_group;
1474N/A else
1474N/A i_fatal("mysql: Unknown connect string: %s", name);
1474N/A
1474N/A if (field != NULL)
1474N/A *field = p_strdup(db->pool, value);
1474N/A }
1474N/A
1474N/A if (db->host == NULL && db->option_file == NULL)
1474N/A i_fatal("mysql: No hosts given in connect string");
1474N/A
1474N/A db->mysql = mysql_init(NULL);
1474N/A if (db->mysql == NULL)
1474N/A i_fatal("mysql_init() failed");
1368N/A}
1474N/A
1474N/Astatic struct sql_db *driver_mysql_init_v(const char *connect_string)
1474N/A{
1474N/A struct mysql_db *db;
1368N/A pool_t pool;
1474N/A
1474N/A pool = pool_alloconly_create("mysql driver", 1024);
1474N/A db = p_new(pool, struct mysql_db, 1);
1474N/A db->pool = pool;
1368N/A db->api = driver_mysql_db;
1474N/A
1474N/A T_BEGIN {
1474N/A driver_mysql_parse_connect_string(db, connect_string);
1368N/A } T_END;
1370N/A return &db->api;
1474N/A}
1474N/A
1368N/Astatic void driver_mysql_deinit_v(struct sql_db *_db)
1474N/A{
1474N/A struct mysql_db *db = (struct mysql_db *)_db;
1474N/A
1474N/A _db->no_reconnect = TRUE;
1474N/A sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED);
1474N/A
1474N/A mysql_close(db->mysql);
1474N/A array_free(&_db->module_contexts);
1474N/A pool_unref(&db->pool);
1474N/A}
1474N/A
1474N/Astatic int driver_mysql_do_query(struct mysql_db *db, const char *query)
1368N/A{
1368N/A if (mysql_query(db->mysql, query) == 0)
1469N/A return 0;
1368N/A
1369N/A /* failed */
1474N/A switch (mysql_errno(db->mysql)) {
1368N/A case CR_SERVER_GONE_ERROR:
1186N/A case CR_SERVER_LOST:
1368N/A sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED);
1473N/A break;
1473N/A default:
1473N/A break;
1473N/A }
1473N/A return -1;
1473N/A}
1473N/A
1473N/Astatic const char *
1473N/Adriver_mysql_escape_string(struct sql_db *_db, const char *string)
1473N/A{
1473N/A struct mysql_db *db = (struct mysql_db *)_db;
1473N/A size_t len = strlen(string);
1473N/A char *to;
1473N/A
1473N/A if (_db->state == SQL_DB_STATE_DISCONNECTED) {
1473N/A /* try connecting */
1473N/A (void)sql_connect(&db->api);
1473N/A }
1473N/A
1473N/A if (db->mysql == NULL) {
1473N/A /* FIXME: we don't have a valid connection, so fallback
1473N/A to using default escaping. the next query will most
1473N/A likely fail anyway so it shouldn't matter that much
1473N/A what we return here.. Anyway, this API needs
1473N/A changing so that the escaping function could already
1473N/A fail the query reliably. */
1473N/A to = t_buffer_get(len * 2 + 1);
1473N/A len = mysql_escape_string(to, string, len);
1473N/A t_buffer_alloc(len + 1);
1473N/A return to;
1473N/A }
1473N/A
1473N/A to = t_buffer_get(len * 2 + 1);
1473N/A len = mysql_real_escape_string(db->mysql, to, string, len);
1473N/A t_buffer_alloc(len + 1);
1473N/A return to;
1473N/A}
1473N/A
1473N/Astatic void driver_mysql_exec(struct sql_db *_db, const char *query)
1473N/A{
1473N/A struct mysql_db *db = (struct mysql_db *)_db;
1473N/A
1473N/A if (driver_mysql_do_query(db, query) < 0) {
1473N/A i_error("%s: Query '%s' failed: %s",
1473N/A mysql_prefix(db), query, mysql_error(db->mysql));
1473N/A }
1473N/A}
1473N/A
1473N/Astatic void driver_mysql_query(struct sql_db *db, const char *query,
1186N/A sql_query_callback_t *callback, void *context)
1186N/A{
1473N/A struct sql_result *result;
1473N/A
1473N/A result = sql_query_s(db, query);
1384N/A result->callback = TRUE;
1384N/A callback(result, context);
1473N/A result->callback = FALSE;
1473N/A sql_result_unref(result);
1473N/A}
1473N/A
1473N/Astatic struct sql_result *
1473N/Adriver_mysql_query_s(struct sql_db *_db, const char *query)
1473N/A{
1473N/A struct mysql_db *db = (struct mysql_db *)_db;
1473N/A struct mysql_result *result;
1473N/A int ret;
1473N/A
1473N/A result = i_new(struct mysql_result, 1);
1473N/A result->api = driver_mysql_result;
1473N/A
1473N/A if (driver_mysql_do_query(db, query) < 0)
1473N/A result->api = driver_mysql_error_result;
1473N/A else {
1473N/A /* query ok */
1473N/A result->affected_rows = mysql_affected_rows(db->mysql);
1186N/A result->result = mysql_store_result(db->mysql);
1473N/A#ifdef CLIENT_MULTI_RESULTS
1473N/A /* Because we've enabled CLIENT_MULTI_RESULTS, we need to read
1473N/A (ignore) extra results - there should not be any.
1473N/A ret is: -1 = done, >0 = error, 0 = more results. */
1473N/A while ((ret = mysql_next_result(db->mysql)) == 0) ;
1473N/A#else
1473N/A ret = -1;
1473N/A#endif
1473N/A
1473N/A if (ret < 0 &&
1473N/A (result->result != NULL || mysql_errno(db->mysql) == 0)) {
1473N/A /* ok */
1473N/A } else {
1186N/A /* failed */
1186N/A if (result->result != NULL)
1473N/A mysql_free_result(result->result);
1186N/A result->api = driver_mysql_error_result;
1186N/A }
1473N/A }
1473N/A
1473N/A result->api.db = _db;
1186N/A result->api.refcount = 1;
1186N/A return &result->api;
1473N/A}
1186N/A
1473N/Astatic void driver_mysql_result_free(struct sql_result *_result)
1473N/A{
1473N/A struct mysql_result *result = (struct mysql_result *)_result;
1473N/A
1473N/A i_assert(_result != &sql_not_connected_result);
1473N/A if (_result->callback)
1473N/A return;
1473N/A
1473N/A if (result->result != NULL)
1473N/A mysql_free_result(result->result);
1473N/A i_free(result);
1473N/A}
1473N/A
1473N/Astatic int driver_mysql_result_next_row(struct sql_result *_result)
1473N/A{
1186N/A struct mysql_result *result = (struct mysql_result *)_result;
1384N/A struct mysql_db *db = (struct mysql_db *)_result->db;
1473N/A int ret;
1370N/A
1186N/A if (result->result == NULL) {
1384N/A /* no results */
1473N/A return 0;
1473N/A }
1473N/A
1473N/A result->row = mysql_fetch_row(result->result);
1473N/A if (result->row != NULL)
1473N/A ret = 1;
1473N/A else {
1473N/A if (mysql_errno(db->mysql) != 0)
1473N/A return -1;
1473N/A ret = 0;
1473N/A }
1473N/A db->last_success = ioloop_time;
1473N/A return ret;
1186N/A}
1186N/A
1473N/Astatic void driver_mysql_result_fetch_fields(struct mysql_result *result)
1186N/A{
1473N/A if (result->fields != NULL)
1473N/A return;
1473N/A
1473N/A result->fields_count = mysql_num_fields(result->result);
1186N/A result->fields = mysql_fetch_fields(result->result);
1384N/A}
1473N/A
1473N/Astatic unsigned int
1473N/Adriver_mysql_result_get_fields_count(struct sql_result *_result)
1473N/A{
1473N/A struct mysql_result *result = (struct mysql_result *)_result;
1473N/A
1473N/A driver_mysql_result_fetch_fields(result);
1473N/A return result->fields_count;
1370N/A}
1186N/A
1473N/Astatic const char *
1186N/Adriver_mysql_result_get_field_name(struct sql_result *_result, unsigned int idx)
1469N/A{
1469N/A struct mysql_result *result = (struct mysql_result *)_result;
1473N/A
1473N/A driver_mysql_result_fetch_fields(result);
1473N/A i_assert(idx < result->fields_count);
1473N/A return result->fields[idx].name;
1473N/A}
1473N/A
1473N/Astatic int driver_mysql_result_find_field(struct sql_result *_result,
1473N/A const char *field_name)
1473N/A{
1473N/A struct mysql_result *result = (struct mysql_result *)_result;
1473N/A unsigned int i;
1473N/A
1473N/A driver_mysql_result_fetch_fields(result);
1186N/A for (i = 0; i < result->fields_count; i++) {
1186N/A if (strcmp(result->fields[i].name, field_name) == 0)
1186N/A return i;
477N/A }
1186N/A return -1;
335N/A}
1186N/A
static const char *
driver_mysql_result_get_field_value(struct sql_result *_result,
unsigned int idx)
{
struct mysql_result *result = (struct mysql_result *)_result;
return (const char *)result->row[idx];
}
static const unsigned char *
driver_mysql_result_get_field_value_binary(struct sql_result *_result,
unsigned int idx, size_t *size_r)
{
struct mysql_result *result = (struct mysql_result *)_result;
unsigned long *lengths;
lengths = mysql_fetch_lengths(result->result);
*size_r = lengths[idx];
return (const void *)result->row[idx];
}
static const char *
driver_mysql_result_find_field_value(struct sql_result *result,
const char *field_name)
{
int idx;
idx = driver_mysql_result_find_field(result, field_name);
if (idx < 0)
return NULL;
return driver_mysql_result_get_field_value(result, idx);
}
static const char *const *
driver_mysql_result_get_values(struct sql_result *_result)
{
struct mysql_result *result = (struct mysql_result *)_result;
return (const char *const *)result->row;
}
static const char *driver_mysql_result_get_error(struct sql_result *_result)
{
struct mysql_db *db = (struct mysql_db *)_result->db;
const char *errstr;
unsigned int idle_time;
int err;
err = mysql_errno(db->mysql);
errstr = mysql_error(db->mysql);
if ((err == CR_SERVER_GONE_ERROR || err == CR_SERVER_LOST) &&
db->last_success != 0) {
idle_time = ioloop_time - db->last_success;
errstr = t_strdup_printf("%s (idled for %u secs)",
errstr, idle_time);
}
return errstr;
}
static struct sql_transaction_context *
driver_mysql_transaction_begin(struct sql_db *db)
{
struct mysql_transaction_context *ctx;
ctx = i_new(struct mysql_transaction_context, 1);
ctx->ctx.db = db;
ctx->query_pool = pool_alloconly_create("mysql transaction", 1024);
return &ctx->ctx;
}
static void
driver_mysql_transaction_commit(struct sql_transaction_context *ctx,
sql_commit_callback_t *callback, void *context)
{
const char *error;
if (sql_transaction_commit_s(&ctx, &error) < 0)
callback(error, context);
else
callback(NULL, context);
}
static int ATTR_NULL(3)
transaction_send_query(struct mysql_transaction_context *ctx, const char *query,
unsigned int *affected_rows_r)
{
struct sql_result *_result;
int ret = 0;
if (ctx->failed)
return -1;
_result = sql_query_s(ctx->ctx.db, query);
if (sql_result_next_row(_result) < 0) {
ctx->error = sql_result_get_error(_result);
ctx->failed = TRUE;
ret = -1;
} else if (affected_rows_r != NULL) {
struct mysql_result *result = (struct mysql_result *)_result;
i_assert(result->affected_rows != (my_ulonglong)-1);
*affected_rows_r = result->affected_rows;
}
sql_result_unref(_result);
return ret;
}
static int driver_mysql_try_commit_s(struct mysql_transaction_context *ctx)
{
struct sql_transaction_context *_ctx = &ctx->ctx;
/* try to use a transaction in any case,
even if it's not actually functional. */
if (transaction_send_query(ctx, "BEGIN", NULL) < 0) {
if (_ctx->db->state != SQL_DB_STATE_DISCONNECTED)
return -1;
/* we got disconnected, retry */
return 0;
}
while (_ctx->head != NULL) {
if (transaction_send_query(ctx, _ctx->head->query,
_ctx->head->affected_rows) < 0)
return -1;
_ctx->head = _ctx->head->next;
}
if (transaction_send_query(ctx, "COMMIT", NULL) < 0)
return -1;
return 1;
}
static int
driver_mysql_transaction_commit_s(struct sql_transaction_context *_ctx,
const char **error_r)
{
struct mysql_transaction_context *ctx =
(struct mysql_transaction_context *)_ctx;
struct mysql_db *db = (struct mysql_db *)_ctx->db;
int ret = 1;
*error_r = NULL;
if (_ctx->head != NULL) {
ret = driver_mysql_try_commit_s(ctx);
*error_r = t_strdup(ctx->error);
if (ret == 0) {
i_info("%s: Disconnected from database, "
"retrying commit", db->dbname);
if (sql_connect(_ctx->db) >= 0) {
ctx->failed = FALSE;
ret = driver_mysql_try_commit_s(ctx);
}
}
}
sql_transaction_rollback(&_ctx);
return ret <= 0 ? -1 : 0;
}
static void
driver_mysql_transaction_rollback(struct sql_transaction_context *_ctx)
{
struct mysql_transaction_context *ctx =
(struct mysql_transaction_context *)_ctx;
pool_unref(&ctx->query_pool);
i_free(ctx);
}
static void
driver_mysql_update(struct sql_transaction_context *_ctx, const char *query,
unsigned int *affected_rows)
{
struct mysql_transaction_context *ctx =
(struct mysql_transaction_context *)_ctx;
sql_transaction_add_query(&ctx->ctx, ctx->query_pool,
query, affected_rows);
}
static const char *
driver_mysql_escape_blob(struct sql_db *_db ATTR_UNUSED,
const unsigned char *data, size_t size)
{
string_t *str = t_str_new(128);
str_append(str, "HEX('");
binary_to_hex_append(str, data, size);
str_append_c(str, ')');
return str_c(str);
}
const struct sql_db driver_mysql_db = {
.name = "mysql",
.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,
driver_mysql_transaction_begin,
driver_mysql_transaction_commit,
driver_mysql_transaction_commit_s,
driver_mysql_transaction_rollback,
driver_mysql_update,
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
}
};
static int
driver_mysql_result_error_next_row(struct sql_result *result ATTR_UNUSED)
{
return -1;
}
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
},
.failed_try_retry = TRUE
};
const char *driver_mysql_version = DOVECOT_ABI_VERSION;
void driver_mysql_init(void);
void driver_mysql_deinit(void);
void driver_mysql_init(void)
{
sql_driver_register(&driver_mysql_db);
}
void driver_mysql_deinit(void)
{
sql_driver_unregister(&driver_mysql_db);
}
#endif