driver-pgsql.c (dovecot-2.3.16) | : | driver-pgsql.c (dovecot-2.3.17) | ||
---|---|---|---|---|
/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ | /* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ | |||
#include "lib.h" | #include "lib.h" | |||
#include "array.h" | #include "array.h" | |||
#include "ioloop.h" | #include "ioloop.h" | |||
#include "hex-binary.h" | #include "hex-binary.h" | |||
#include "str.h" | #include "str.h" | |||
#include "time-util.h" | #include "time-util.h" | |||
#include "sql-api-private.h" | #include "sql-api-private.h" | |||
#include "llist.h" | ||||
#ifdef BUILD_PGSQL | #ifdef BUILD_PGSQL | |||
#include <libpq-fe.h> | #include <libpq-fe.h> | |||
#define PGSQL_DNS_WARN_MSECS 500 | #define PGSQL_DNS_WARN_MSECS 500 | |||
struct pgsql_db { | struct pgsql_db { | |||
struct sql_db api; | struct sql_db api; | |||
pool_t pool; | pool_t pool; | |||
char *connect_string; | char *connect_string; | |||
char *host; | char *host; | |||
PGconn *pg; | PGconn *pg; | |||
struct io *io; | struct io *io; | |||
struct timeout *to_connect; | struct timeout *to_connect; | |||
enum io_condition io_dir; | enum io_condition io_dir; | |||
struct pgsql_result *pending_results; | ||||
struct pgsql_result *cur_result; | struct pgsql_result *cur_result; | |||
struct ioloop *ioloop, *orig_ioloop; | struct ioloop *ioloop, *orig_ioloop; | |||
struct sql_result *sync_result; | struct sql_result *sync_result; | |||
bool (*next_callback)(void *); | bool (*next_callback)(void *); | |||
void *next_context; | void *next_context; | |||
char *error; | char *error; | |||
const char *connect_state; | const char *connect_state; | |||
bool fatal_error:1; | bool fatal_error:1; | |||
}; | }; | |||
struct pgsql_binary_value { | struct pgsql_binary_value { | |||
unsigned char *value; | unsigned char *value; | |||
size_t size; | size_t size; | |||
}; | }; | |||
struct pgsql_result { | struct pgsql_result { | |||
struct sql_result api; | struct sql_result api; | |||
struct pgsql_result *prev, *next; | ||||
PGresult *pgres; | PGresult *pgres; | |||
struct timeout *to; | struct timeout *to; | |||
unsigned int rownum, rows; | unsigned int rownum, rows; | |||
unsigned int fields_count; | unsigned int fields_count; | |||
const char **fields; | const char **fields; | |||
const char **values; | const char **values; | |||
char *query; | char *query; | |||
ARRAY(struct pgsql_binary_value) binary_values; | ARRAY(struct pgsql_binary_value) binary_values; | |||
skipping to change at line 286 | skipping to change at line 291 | |||
} | } | |||
static void driver_pgsql_free(struct pgsql_db **_db) | static void driver_pgsql_free(struct pgsql_db **_db) | |||
{ | { | |||
struct pgsql_db *db = *_db; | struct pgsql_db *db = *_db; | |||
*_db = NULL; | *_db = NULL; | |||
event_unref(&db->api.event); | event_unref(&db->api.event); | |||
i_free(db->connect_string); | i_free(db->connect_string); | |||
i_free(db->host); | i_free(db->host); | |||
i_free(db->error); | ||||
array_free(&db->api.module_contexts); | array_free(&db->api.module_contexts); | |||
i_free(db); | i_free(db); | |||
} | } | |||
static enum sql_db_flags driver_pgsql_get_flags(struct sql_db *db) | ||||
{ | ||||
switch (db->state) { | ||||
case SQL_DB_STATE_DISCONNECTED: | ||||
if (sql_connect(db) < 0) | ||||
break; | ||||
/* fall through */ | ||||
case SQL_DB_STATE_CONNECTING: | ||||
/* Wait for connection to finish, so we can get the flags | ||||
reliably. */ | ||||
sql_wait(db); | ||||
break; | ||||
case SQL_DB_STATE_IDLE: | ||||
case SQL_DB_STATE_BUSY: | ||||
break; | ||||
} | ||||
return db->flags; | ||||
} | ||||
static int driver_pgsql_init_full_v(const struct sql_settings *set, | static int driver_pgsql_init_full_v(const struct sql_settings *set, | |||
struct sql_db **db_r, const char **error_r AT TR_UNUSED) | struct sql_db **db_r, const char **error_r AT TR_UNUSED) | |||
{ | { | |||
struct pgsql_db *db; | struct pgsql_db *db; | |||
db = i_new(struct pgsql_db, 1); | db = i_new(struct pgsql_db, 1); | |||
db->connect_string = i_strdup(set->connect_string); | db->connect_string = i_strdup(set->connect_string); | |||
db->api = driver_pgsql_db; | db->api = driver_pgsql_db; | |||
db->api.event = event_create(set->event_parent); | db->api.event = event_create(set->event_parent); | |||
event_add_category(db->api.event, &event_category_pgsql); | event_add_category(db->api.event, &event_category_pgsql); | |||
skipping to change at line 414 | skipping to change at line 439 | |||
} | } | |||
static void result_finish(struct pgsql_result *result) | static void result_finish(struct pgsql_result *result) | |||
{ | { | |||
struct pgsql_db *db = (struct pgsql_db *)result->api.db; | struct pgsql_db *db = (struct pgsql_db *)result->api.db; | |||
bool free_result = TRUE; | bool free_result = TRUE; | |||
int duration; | int duration; | |||
i_assert(db->io == NULL); | i_assert(db->io == NULL); | |||
timeout_remove(&result->to); | timeout_remove(&result->to); | |||
DLLIST_REMOVE(&db->pending_results, result); | ||||
/* if connection to server was lost, we don't yet see that the | /* if connection to server was lost, we don't yet see that the | |||
connection is bad. we only see the fatal error, so assume it also | connection is bad. we only see the fatal error, so assume it also | |||
means disconnection. */ | means disconnection. */ | |||
if (PQstatus(db->pg) == CONNECTION_BAD || result->pgres == NULL || | if (PQstatus(db->pg) == CONNECTION_BAD || result->pgres == NULL || | |||
PQresultStatus(result->pgres) == PGRES_FATAL_ERROR) | PQresultStatus(result->pgres) == PGRES_FATAL_ERROR) | |||
db->fatal_error = TRUE; | db->fatal_error = TRUE; | |||
if (db->fatal_error) { | if (db->fatal_error) { | |||
result->api.failed = TRUE; | result->api.failed = TRUE; | |||
skipping to change at line 525 | skipping to change at line 551 | |||
{ | { | |||
struct pgsql_db *db = (struct pgsql_db *)result->api.db; | struct pgsql_db *db = (struct pgsql_db *)result->api.db; | |||
int ret; | int ret; | |||
i_assert(SQL_DB_IS_READY(&db->api)); | i_assert(SQL_DB_IS_READY(&db->api)); | |||
i_assert(db->cur_result == NULL); | i_assert(db->cur_result == NULL); | |||
i_assert(db->io == NULL); | i_assert(db->io == NULL); | |||
driver_pgsql_set_state(db, SQL_DB_STATE_BUSY); | driver_pgsql_set_state(db, SQL_DB_STATE_BUSY); | |||
db->cur_result = result; | db->cur_result = result; | |||
DLLIST_PREPEND(&db->pending_results, result); | ||||
result->to = timeout_add(SQL_QUERY_TIMEOUT_SECS * 1000, | result->to = timeout_add(SQL_QUERY_TIMEOUT_SECS * 1000, | |||
query_timeout, result); | query_timeout, result); | |||
result->query = i_strdup(query); | result->query = i_strdup(query); | |||
if (PQsendQuery(db->pg, query) == 0 || | if (PQsendQuery(db->pg, query) == 0 || | |||
(ret = PQflush(db->pg)) < 0) { | (ret = PQflush(db->pg)) < 0) { | |||
/* failed to send query */ | /* failed to send query */ | |||
result_finish(result); | result_finish(result); | |||
return; | return; | |||
} | } | |||
skipping to change at line 819 | skipping to change at line 846 | |||
if (PQgetisnull(result->pgres, result->rownum, idx) != 0) { | if (PQgetisnull(result->pgres, result->rownum, idx) != 0) { | |||
*size_r = 0; | *size_r = 0; | |||
return NULL; | return NULL; | |||
} | } | |||
value = PQgetvalue(result->pgres, result->rownum, idx); | value = PQgetvalue(result->pgres, result->rownum, idx); | |||
if (!array_is_created(&result->binary_values)) | if (!array_is_created(&result->binary_values)) | |||
i_array_init(&result->binary_values, idx + 1); | i_array_init(&result->binary_values, idx + 1); | |||
binary_value = array_idx_modifiable(&result->binary_values, idx); | binary_value = array_idx_get_space(&result->binary_values, idx); | |||
if (binary_value->value == NULL) { | if (binary_value->value == NULL) { | |||
binary_value->value = | binary_value->value = | |||
PQunescapeBytea((const unsigned char *)value, | PQunescapeBytea((const unsigned char *)value, | |||
&binary_value->size); | &binary_value->size); | |||
} | } | |||
*size_r = binary_value->size; | *size_r = binary_value->size; | |||
return binary_value->value; | return binary_value->value; | |||
} | } | |||
skipping to change at line 1229 | skipping to change at line 1256 | |||
sql_transaction_add_query(_ctx, ctx->query_pool, query, affected_rows); | sql_transaction_add_query(_ctx, ctx->query_pool, query, affected_rows); | |||
} | } | |||
static const char * | static const char * | |||
driver_pgsql_escape_blob(struct sql_db *_db ATTR_UNUSED, | driver_pgsql_escape_blob(struct sql_db *_db ATTR_UNUSED, | |||
const unsigned char *data, size_t size) | const unsigned char *data, size_t size) | |||
{ | { | |||
string_t *str = t_str_new(128); | string_t *str = t_str_new(128); | |||
str_append(str, "E'\\x"); | str_append(str, "E'\\\\x"); | |||
binary_to_hex_append(str, data, size); | binary_to_hex_append(str, data, size); | |||
str_append_c(str, '\''); | str_append_c(str, '\''); | |||
return str_c(str); | return str_c(str); | |||
} | } | |||
static bool driver_pgsql_have_work(struct pgsql_db *db) | ||||
{ | ||||
return db->next_callback != NULL || db->pending_results != NULL || | ||||
db->api.state == SQL_DB_STATE_CONNECTING; | ||||
} | ||||
static void driver_pgsql_wait(struct sql_db *_db) | ||||
{ | ||||
struct pgsql_db *db = (struct pgsql_db *)_db; | ||||
if (!driver_pgsql_have_work(db)) | ||||
return; | ||||
struct ioloop *prev_ioloop = current_ioloop; | ||||
db->ioloop = io_loop_create(); | ||||
db->io = io_loop_move_io(&db->io); | ||||
while (driver_pgsql_have_work(db)) | ||||
io_loop_run(db->ioloop); | ||||
io_loop_set_current(prev_ioloop); | ||||
db->io = io_loop_move_io(&db->io); | ||||
io_loop_set_current(db->ioloop); | ||||
io_loop_destroy(&db->ioloop); | ||||
} | ||||
const struct sql_db driver_pgsql_db = { | const struct sql_db driver_pgsql_db = { | |||
.name = "pgsql", | .name = "pgsql", | |||
.flags = SQL_DB_FLAG_POOLED, | .flags = SQL_DB_FLAG_POOLED, | |||
.v = { | .v = { | |||
.get_flags = driver_pgsql_get_flags, | ||||
.init_full = driver_pgsql_init_full_v, | .init_full = driver_pgsql_init_full_v, | |||
.deinit = driver_pgsql_deinit_v, | .deinit = driver_pgsql_deinit_v, | |||
.connect = driver_pgsql_connect, | .connect = driver_pgsql_connect, | |||
.disconnect = driver_pgsql_disconnect, | .disconnect = driver_pgsql_disconnect, | |||
.escape_string = driver_pgsql_escape_string, | .escape_string = driver_pgsql_escape_string, | |||
.exec = driver_pgsql_exec, | .exec = driver_pgsql_exec, | |||
.query = driver_pgsql_query, | .query = driver_pgsql_query, | |||
.query_s = driver_pgsql_query_s, | .query_s = driver_pgsql_query_s, | |||
.wait = driver_pgsql_wait, | ||||
.transaction_begin = driver_pgsql_transaction_begin, | .transaction_begin = driver_pgsql_transaction_begin, | |||
.transaction_commit = driver_pgsql_transaction_commit, | .transaction_commit = driver_pgsql_transaction_commit, | |||
.transaction_commit_s = driver_pgsql_transaction_commit_s, | .transaction_commit_s = driver_pgsql_transaction_commit_s, | |||
.transaction_rollback = driver_pgsql_transaction_rollback, | .transaction_rollback = driver_pgsql_transaction_rollback, | |||
.update = driver_pgsql_update, | .update = driver_pgsql_update, | |||
.escape_blob = driver_pgsql_escape_blob, | .escape_blob = driver_pgsql_escape_blob, | |||
} | } | |||
End of changes. 12 change blocks. | ||||
2 lines changed or deleted | 56 lines changed or added |