driver-mysql.c revision c93aca832ee532010ead91b85fa9f614132e1be2
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher/* Copyright (c) 2003-2015 Dovecot authors, see the included COPYING file */
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher/* ugly way to tell clang that mysql.h is a system header and we don't want
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher to enable nonnull attributes for it by default.. */
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher const char *user, *password, *dbname, *host, *unix_socket;
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher const char *ssl_cert, *ssl_key, *ssl_ca, *ssl_ca_path, *ssl_cipher;
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagherextern const struct sql_db driver_mysql_db;
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagherextern const struct sql_result driver_mysql_result;
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagherextern const struct sql_result driver_mysql_error_result;
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagherstatic const char *mysql_prefix(struct mysql_db *db)
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher return t_strdup_printf("mysql(%s)", db->host);
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagherstatic int driver_mysql_connect(struct sql_db *_db)
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher struct mysql_db *db = (struct mysql_db *)_db;
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozek i_assert(db->api.state == SQL_DB_STATE_DISCONNECTED);
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozek sql_db_set_state(&db->api, SQL_DB_STATE_CONNECTING);
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher mysql_options(db->mysql, MYSQL_READ_DEFAULT_FILE,
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher mysql_options(db->mysql, MYSQL_READ_DEFAULT_GROUP,
f10ebaa51ecdcbbd10f171d19fe8e680e5bc74aaJakub Hrozek db->option_group != NULL ? db->option_group : "client");
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher if (!db->ssl_set && (db->ssl_ca != NULL || db->ssl_ca_path != NULL)) {
f45a20d6ba9e8d695ec3ab707f0cc082999aa4a3Jakub Hrozek mysql_ssl_set(db->mysql, db->ssl_key, db->ssl_cert,
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher mysql_options(db->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT,
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher i_fatal("mysql: SSL support not compiled in "
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher "(remove ssl_ca and ssl_ca_path settings)");
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher /* CLIENT_MULTI_RESULTS allows the use of stored procedures */
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher failed = mysql_real_connect(db->mysql, host, db->user, db->password,
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher secs_used = SQL_CONNECT_TIMEOUT_SECS - alarm(0);
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher /* connecting could have taken a while. make sure that any
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher timeouts that get added soon will get a refreshed
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED);
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher i_error("%s: Connect failed to database (%s): %s - "
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher "waiting for %u seconds before retry",
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher mysql_error(db->mysql), db->api.connect_delay);
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher sql_db_set_state(&db->api, SQL_DB_STATE_IDLE);
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagherstatic void driver_mysql_disconnect(struct sql_db *_db ATTR_UNUSED)
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozekstatic void driver_mysql_parse_connect_string(struct mysql_db *db,
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozek db->ssl_verify_server_cert = 0; /* FIXME: change to 1 for v2.3 */
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher args = t_strsplit_spaces(connect_string, " ");
a23014d69b56cbdf48ad05229c334648b5309d8fJakub Hrozek i_fatal("mysql: Missing value in connect string: %s",
261cdde02b40aa8dabb3d69e43586a5a220647e9Jakub Hrozek i_fatal("mysql: Invalid port number: %s", value);
261cdde02b40aa8dabb3d69e43586a5a220647e9Jakub Hrozek } else if (strcmp(name, "client_flags") == 0) {
261cdde02b40aa8dabb3d69e43586a5a220647e9Jakub Hrozek i_fatal("mysql: Invalid client flags: %s", value);
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher else if (strcmp(name, "ssl_ca_path") == 0)
6463ed1dcdd45416468b3fa178bd856b5a9ed2c3Jakub Hrozek else if (strcmp(name, "ssl_verify_server_cert") == 0) {
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher i_fatal("mysql: Invalid boolean: %s", value);
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher } else if (strcmp(name, "option_file") == 0)
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher else if (strcmp(name, "option_group") == 0)
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher i_fatal("mysql: Unknown connect string: %s", name);
dd3ba5c5b7d2a9d109963ae9e6c94fff34872221Stephen Gallagher i_fatal("mysql: No hosts given in connect string");
dd3ba5c5b7d2a9d109963ae9e6c94fff34872221Stephen Gallagherstatic struct sql_db *driver_mysql_init_v(const char *connect_string)
b20208b80e99abb79c00d5ec526caa9465859c52Jakub Hrozek pool = pool_alloconly_create("mysql driver", 1024);
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher driver_mysql_parse_connect_string(db, connect_string);
6463ed1dcdd45416468b3fa178bd856b5a9ed2c3Jakub Hrozekstatic void driver_mysql_deinit_v(struct sql_db *_db)
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher struct mysql_db *db = (struct mysql_db *)_db;
d25fa6f2608d5fe0617ada47f9d426f45deb96ffJakub Hrozek sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED);
d25fa6f2608d5fe0617ada47f9d426f45deb96ffJakub Hrozekstatic int driver_mysql_do_query(struct mysql_db *db, const char *query)
d25fa6f2608d5fe0617ada47f9d426f45deb96ffJakub Hrozek /* failed */
d25fa6f2608d5fe0617ada47f9d426f45deb96ffJakub Hrozek sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED);
d25fa6f2608d5fe0617ada47f9d426f45deb96ffJakub Hrozekstatic const char *
d25fa6f2608d5fe0617ada47f9d426f45deb96ffJakub Hrozekdriver_mysql_escape_string(struct sql_db *_db, const char *string)
d25fa6f2608d5fe0617ada47f9d426f45deb96ffJakub Hrozek /* try connecting */
d25fa6f2608d5fe0617ada47f9d426f45deb96ffJakub Hrozek /* FIXME: we don't have a valid connection, so fallback
d25fa6f2608d5fe0617ada47f9d426f45deb96ffJakub Hrozek to using default escaping. the next query will most
d25fa6f2608d5fe0617ada47f9d426f45deb96ffJakub Hrozek likely fail anyway so it shouldn't matter that much
d25fa6f2608d5fe0617ada47f9d426f45deb96ffJakub Hrozek what we return here.. Anyway, this API needs
d25fa6f2608d5fe0617ada47f9d426f45deb96ffJakub Hrozek changing so that the escaping function could already
a23014d69b56cbdf48ad05229c334648b5309d8fJakub Hrozek fail the query reliably. */
a7797068c4deb6ce2bdbcda27c45ff1bbb4a8e78Jakub Hrozek len = mysql_real_escape_string(db->mysql, to, string, len);
a7797068c4deb6ce2bdbcda27c45ff1bbb4a8e78Jakub Hrozekstatic void driver_mysql_exec(struct sql_db *_db, const char *query)
a23014d69b56cbdf48ad05229c334648b5309d8fJakub Hrozek mysql_prefix(db), query, mysql_error(db->mysql));
f45a20d6ba9e8d695ec3ab707f0cc082999aa4a3Jakub Hrozekstatic void driver_mysql_query(struct sql_db *db, const char *query,
f45a20d6ba9e8d695ec3ab707f0cc082999aa4a3Jakub Hrozekstatic struct sql_result *
f45a20d6ba9e8d695ec3ab707f0cc082999aa4a3Jakub Hrozekdriver_mysql_query_s(struct sql_db *_db, const char *query)
f45a20d6ba9e8d695ec3ab707f0cc082999aa4a3Jakub Hrozek /* query ok */
a23014d69b56cbdf48ad05229c334648b5309d8fJakub Hrozek result->affected_rows = mysql_affected_rows(db->mysql);
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozek result->result = mysql_store_result(db->mysql);
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozek /* Because we've enabled CLIENT_MULTI_RESULTS, we need to read
f10ebaa51ecdcbbd10f171d19fe8e680e5bc74aaJakub Hrozek (ignore) extra results - there should not be any.
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozek ret is: -1 = done, >0 = error, 0 = more results. */
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozek while ((ret = mysql_next_result(db->mysql)) == 0) ;
a23014d69b56cbdf48ad05229c334648b5309d8fJakub Hrozek (result->result != NULL || mysql_errno(db->mysql) == 0)) {
a23014d69b56cbdf48ad05229c334648b5309d8fJakub Hrozek /* failed */
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozekstatic void driver_mysql_result_free(struct sql_result *_result)
4c9419d98b89a6161a3dde11f9f80be39d12e72aJakub Hrozek struct mysql_result *result = (struct mysql_result *)_result;
4c9419d98b89a6161a3dde11f9f80be39d12e72aJakub Hrozek i_assert(_result != &sql_not_connected_result);
a23014d69b56cbdf48ad05229c334648b5309d8fJakub Hrozekstatic int driver_mysql_result_next_row(struct sql_result *_result)
a23014d69b56cbdf48ad05229c334648b5309d8fJakub Hrozek struct mysql_result *result = (struct mysql_result *)_result;
a7797068c4deb6ce2bdbcda27c45ff1bbb4a8e78Jakub Hrozek struct mysql_db *db = (struct mysql_db *)_result->db;
a23014d69b56cbdf48ad05229c334648b5309d8fJakub Hrozek /* no results */
a23014d69b56cbdf48ad05229c334648b5309d8fJakub Hrozekstatic void driver_mysql_result_fetch_fields(struct mysql_result *result)
a23014d69b56cbdf48ad05229c334648b5309d8fJakub Hrozek result->fields_count = mysql_num_fields(result->result);
a23014d69b56cbdf48ad05229c334648b5309d8fJakub Hrozek result->fields = mysql_fetch_fields(result->result);
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagherstatic unsigned int
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagherdriver_mysql_result_get_fields_count(struct sql_result *_result)
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher struct mysql_result *result = (struct mysql_result *)_result;
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozekstatic const char *
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagherdriver_mysql_result_get_field_name(struct sql_result *_result, unsigned int idx)
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher struct mysql_result *result = (struct mysql_result *)_result;
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozekstatic int driver_mysql_result_find_field(struct sql_result *_result,
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher struct mysql_result *result = (struct mysql_result *)_result;
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher unsigned int i;
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher for (i = 0; i < result->fields_count; i++) {
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher if (strcmp(result->fields[i].name, field_name) == 0)
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozekstatic const char *
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagherdriver_mysql_result_get_field_value(struct sql_result *_result,
36b56482ca1e53d832accef0354124fd79711172Jakub Hrozek unsigned int idx)
36b56482ca1e53d832accef0354124fd79711172Jakub Hrozek struct mysql_result *result = (struct mysql_result *)_result;
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozekstatic const unsigned char *
056302a92862fda16351d7192600746746f38e5dStephen Gallagherdriver_mysql_result_get_field_value_binary(struct sql_result *_result,
dd3ba5c5b7d2a9d109963ae9e6c94fff34872221Stephen Gallagher struct mysql_result *result = (struct mysql_result *)_result;
a23014d69b56cbdf48ad05229c334648b5309d8fJakub Hrozek unsigned long *lengths;
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozekstatic const char *
36b56482ca1e53d832accef0354124fd79711172Jakub Hrozekdriver_mysql_result_find_field_value(struct sql_result *result,
36b56482ca1e53d832accef0354124fd79711172Jakub Hrozek idx = driver_mysql_result_find_field(result, field_name);
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher return driver_mysql_result_get_field_value(result, idx);
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagherstatic const char *const *
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozekdriver_mysql_result_get_values(struct sql_result *_result)
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher struct mysql_result *result = (struct mysql_result *)_result;
6463ed1dcdd45416468b3fa178bd856b5a9ed2c3Jakub Hrozekstatic const char *driver_mysql_result_get_error(struct sql_result *_result)
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher struct mysql_db *db = (struct mysql_db *)_result->db;
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher if ((err == CR_SERVER_GONE_ERROR || err == CR_SERVER_LOST) &&
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher idle_time = ioloop_time - db->last_success;
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher errstr = t_strdup_printf("%s (idled for %u secs)",
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozekdriver_mysql_transaction_begin(struct sql_db *db)
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher ctx = i_new(struct mysql_transaction_context, 1);
2cb6f28b3a12bb714bf14494d31eb6b6fff64b8bJakub Hrozek ctx->query_pool = pool_alloconly_create("mysql transaction", 1024);
f45a20d6ba9e8d695ec3ab707f0cc082999aa4a3Jakub Hrozekdriver_mysql_transaction_commit(struct sql_transaction_context *ctx,
f45a20d6ba9e8d695ec3ab707f0cc082999aa4a3Jakub Hrozek sql_commit_callback_t *callback, void *context)
dd3ba5c5b7d2a9d109963ae9e6c94fff34872221Stephen Gallagher if (sql_transaction_commit_s(&ctx, &error) < 0)
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallaghertransaction_send_query(struct mysql_transaction_context *ctx, const char *query,
524ceecc11f3d458eb3c1cf1489c3ff6ccb22226Jakub Hrozek struct mysql_result *result = (struct mysql_result *)_result;
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozek i_assert(result->affected_rows != (my_ulonglong)-1);
dd3ba5c5b7d2a9d109963ae9e6c94fff34872221Stephen Gallagherstatic int driver_mysql_try_commit_s(struct mysql_transaction_context *ctx)
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher struct sql_transaction_context *_ctx = &ctx->ctx;
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher /* try to use a transaction in any case,
b20208b80e99abb79c00d5ec526caa9465859c52Jakub Hrozek even if it's not actually functional. */
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozek if (transaction_send_query(ctx, "BEGIN", NULL) < 0) {
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher if (_ctx->db->state != SQL_DB_STATE_DISCONNECTED)
b20208b80e99abb79c00d5ec526caa9465859c52Jakub Hrozek /* we got disconnected, retry */
0172959f117b545c8a6b1893f5f56818d82dd624Jakub Hrozek if (transaction_send_query(ctx, _ctx->head->query,
b47fd11a259c50e63cd674c7cba0da3f2549cae0Jakub Hrozek if (transaction_send_query(ctx, "COMMIT", NULL) < 0)
b20208b80e99abb79c00d5ec526caa9465859c52Jakub Hrozekdriver_mysql_transaction_commit_s(struct sql_transaction_context *_ctx,
0172959f117b545c8a6b1893f5f56818d82dd624Jakub Hrozek const char **error_r)
b20208b80e99abb79c00d5ec526caa9465859c52Jakub Hrozek struct mysql_db *db = (struct mysql_db *)_ctx->db;
0172959f117b545c8a6b1893f5f56818d82dd624Jakub Hrozekdriver_mysql_transaction_rollback(struct sql_transaction_context *_ctx)
b20208b80e99abb79c00d5ec526caa9465859c52Jakub Hrozekdriver_mysql_update(struct sql_transaction_context *_ctx, const char *query,
dd3ba5c5b7d2a9d109963ae9e6c94fff34872221Stephen Gallagher sql_transaction_add_query(&ctx->ctx, ctx->query_pool,
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher .flags = SQL_DB_FLAG_BLOCKING | SQL_DB_FLAG_POOLED,
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagherconst struct sql_result driver_mysql_result = {
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagher driver_mysql_result_get_field_value_binary,
52261fe16203dec6e6f69177c6d0a810b47d073fStephen Gallagherdriver_mysql_result_error_next_row(struct sql_result *result ATTR_UNUSED)
6463ed1dcdd45416468b3fa178bd856b5a9ed2c3Jakub Hrozekconst struct sql_result driver_mysql_error_result = {