driver-pgsql.c revision 46744f1c9837f189e1c1b13e4d83231b3c9dfff6
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen/* Copyright (C) 2004 Timo Sirainen */
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "lib.h"
8e361d2906b0e44f7175a20981f8d2280645b58bTimo Sirainen#include "ioloop.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "ioloop-internal.h" /* kind of dirty, but it should be fine.. */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "sql-api-private.h"
bdd7a96c363346f7c38f389791be1487ca08775bTimo Sirainen
bdd7a96c363346f7c38f389791be1487ca08775bTimo Sirainen#ifdef HAVE_PGSQL
cc833a7a4e2258afdc834ace4bfe6579820a1df3Timo Sirainen#include <stdlib.h>
cc833a7a4e2258afdc834ace4bfe6579820a1df3Timo Sirainen#include <time.h>
97cb20eb77d486ef67eac50567e3080faca025c1Timo Sirainen#include <libpq-fe.h>
97cb20eb77d486ef67eac50567e3080faca025c1Timo Sirainen
cc833a7a4e2258afdc834ace4bfe6579820a1df3Timo Sirainenstruct pgsql_db {
7e235b3a5f622813121cd18f351e036650aaf8f8Timo Sirainen struct sql_db api;
366eb7178f2c90d97134e0c2d1958f93fcdaba12Timo Sirainen
7e235b3a5f622813121cd18f351e036650aaf8f8Timo Sirainen pool_t pool;
7e235b3a5f622813121cd18f351e036650aaf8f8Timo Sirainen const char *connect_string;
fadd878cd6098f5b873c21c121209a922679dae4Timo Sirainen PGconn *pg;
fadd878cd6098f5b873c21c121209a922679dae4Timo Sirainen
863ea896fb31a16d1baec31e57650243b5547db6Timo Sirainen struct io *io;
863ea896fb31a16d1baec31e57650243b5547db6Timo Sirainen enum io_condition io_dir;
863ea896fb31a16d1baec31e57650243b5547db6Timo Sirainen
471e447023ab73a73f0f78da2afc0c55905330ddTimo Sirainen struct pgsql_queue *queue, **queue_tail;
4b231ca0bbe3b536acbd350101e183441ce0247aTimo Sirainen struct timeout *queue_to;
4b231ca0bbe3b536acbd350101e183441ce0247aTimo Sirainen
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen struct ioloop *ioloop;
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen struct sql_result *sync_result;
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen char *error;
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen time_t last_connect;
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen unsigned int connecting:1;
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen unsigned int connected:1;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned int querying:1;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen};
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstruct pgsql_result {
bbce20cb4e5739e9a06058cf8ee1f38a7f6884f6Timo Sirainen struct sql_result api;
bbce20cb4e5739e9a06058cf8ee1f38a7f6884f6Timo Sirainen PGresult *pgres;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned int rownum, rows;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned int fields_count;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen const char **fields;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen const char **values;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen sql_query_callback_t *callback;
55a210942dc7da58b2fd0b11bed8da6b030af5c1Timo Sirainen void *context;
fc7b17677ac1a5fa3f7fe13d5ef7dcfea8d9b4a1Timo Sirainen};
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainenstruct pgsql_queue {
8e361d2906b0e44f7175a20981f8d2280645b58bTimo Sirainen struct pgsql_queue *next;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen time_t created;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen char *query;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct pgsql_result *result;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen};
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen
44ff75ca53188056ff5a3e50428e3f2078800b3cTimo Sirainenstruct pgsql_transaction_context {
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen struct sql_transaction_context ctx;
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainen sql_commit_callback_t *callback;
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainen void *context;
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainen
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainen const char *error;
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainen unsigned int opened:1;
88b8aea03a24ef7a9efc30399080487b7eb03537Timo Sirainen unsigned int failed:1;
88b8aea03a24ef7a9efc30399080487b7eb03537Timo Sirainen};
88b8aea03a24ef7a9efc30399080487b7eb03537Timo Sirainen
44ff75ca53188056ff5a3e50428e3f2078800b3cTimo Sirainenextern struct sql_result driver_pgsql_result;
8872e5c991430f96138a46e36b7f3c2c40d8e5c2Timo Sirainen
8872e5c991430f96138a46e36b7f3c2c40d8e5c2Timo Sirainenstatic void queue_send_next(struct pgsql_db *db);
8872e5c991430f96138a46e36b7f3c2c40d8e5c2Timo Sirainen
651fc0f1e43fef3e02e0e7b5f498973b05f641d7Timo Sirainenstatic void driver_pgsql_close(struct pgsql_db *db)
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen{
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen if (db->io != NULL) {
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen io_remove(db->io);
d9fda7e3a0fa5551547ac3e3054b837fc77f4bfbTimo Sirainen db->io = NULL;
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen }
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen db->io_dir = 0;
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen PQfinish(db->pg);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen db->pg = NULL;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen db->connecting = FALSE;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen db->connected = FALSE;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen db->querying = FALSE;
029cfcdce65b284d5230adf1c920a5f526b03b5cTimo Sirainen}
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen
45e62043058738e294f89504c319d852e25943ccTimo Sirainenstatic const char *last_error(struct pgsql_db *db)
45e62043058738e294f89504c319d852e25943ccTimo Sirainen{
45e62043058738e294f89504c319d852e25943ccTimo Sirainen const char *msg;
45e62043058738e294f89504c319d852e25943ccTimo Sirainen size_t len;
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen msg = PQerrorMessage(db->pg);
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen if (msg == NULL)
d5960ce1c0adda5c9e259bc429123ebc29c60baeTimo Sirainen return "(no error set)";
d5960ce1c0adda5c9e259bc429123ebc29c60baeTimo Sirainen
13c6532dc104d23061e6901783ceb1ff8872c206Timo Sirainen /* Error message should contain trailing \n, we don't want it */
b780aa272b742a43579cdb523cc79cc8d4521306Timo Sirainen len = strlen(msg);
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen return len == 0 || msg[len-1] != '\n' ? msg :
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen t_strndup(msg, len-1);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen}
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstatic void connect_callback(void *context)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen{
6eb30032b4a50c383dea4c9c74342d906de6ad36Timo Sirainen struct pgsql_db *db = context;
43d32cbe60fdaef2699d99f1ca259053e9350411Timo Sirainen enum io_condition io_dir = 0;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen int ret;
bdd7a96c363346f7c38f389791be1487ca08775bTimo Sirainen
bdd7a96c363346f7c38f389791be1487ca08775bTimo Sirainen while ((ret = PQconnectPoll(db->pg)) == PGRES_POLLING_ACTIVE)
bdd7a96c363346f7c38f389791be1487ca08775bTimo Sirainen ;
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen switch (ret) {
4bc96ba6f1d67a90a75fa131bcd2cd508ea5a05aTimo Sirainen case PGRES_POLLING_READING:
4bc96ba6f1d67a90a75fa131bcd2cd508ea5a05aTimo Sirainen io_dir = IO_READ;
4bc96ba6f1d67a90a75fa131bcd2cd508ea5a05aTimo Sirainen break;
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen case PGRES_POLLING_WRITING:
4bc96ba6f1d67a90a75fa131bcd2cd508ea5a05aTimo Sirainen io_dir = IO_WRITE;
b222354c9553cd60b7dd418885e10c0473f73985Timo Sirainen break;
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen case PGRES_POLLING_OK:
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen i_info("pgsql: Connected to %s", PQdb(db->pg));
4bc96ba6f1d67a90a75fa131bcd2cd508ea5a05aTimo Sirainen db->connecting = FALSE;
4bc96ba6f1d67a90a75fa131bcd2cd508ea5a05aTimo Sirainen db->connected = TRUE;
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen break;
f537e7efaec891d6b3320ca94331d09ca8c4a4dbTimo Sirainen case PGRES_POLLING_FAILED:
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen i_error("pgsql: Connect failed to %s: %s",
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen PQdb(db->pg), last_error(db));
2e99f3f3bb35715ce5e0a75a2f2a9bac3ab4224bTimo Sirainen driver_pgsql_close(db);
2e99f3f3bb35715ce5e0a75a2f2a9bac3ab4224bTimo Sirainen return;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
902222fb0928d1701f20a384b73f327b1d9a15ddTimo Sirainen if (db->io_dir != io_dir) {
902222fb0928d1701f20a384b73f327b1d9a15ddTimo Sirainen if (db->io != NULL)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen io_remove(db->io);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen db->io = io_dir == 0 ? NULL :
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen io_add(PQsocket(db->pg), io_dir, connect_callback, db);
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen db->io_dir = io_dir;
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen }
6ded8819b9002150a95a7615e4f64f091c250464Timo Sirainen}
6ded8819b9002150a95a7615e4f64f091c250464Timo Sirainen
2f8da04d700cc23fcd6630226a4866e828b761bdTimo Sirainenstatic int driver_pgsql_connect(struct sql_db *_db)
2f8da04d700cc23fcd6630226a4866e828b761bdTimo Sirainen{
b87a4156eca6dcf6b29c504eb0cb9be2fdb11b63Timo Sirainen struct pgsql_db *db = (struct pgsql_db *)_db;
ad48319996942463675b53877092ab7e13a7a75aTimo Sirainen time_t now;
ad48319996942463675b53877092ab7e13a7a75aTimo Sirainen
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen /* don't try reconnecting more than once a second */
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen now = time(NULL);
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen if (db->connecting || db->last_connect == now)
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen return db->connected ? 1 : (db->connecting ? 0 : -1);
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen db->last_connect = now;
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen db->pg = PQconnectStart(db->connect_string);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (db->pg == NULL)
i_fatal("pgsql: PQconnectStart() failed (out of memory)");
if (PQstatus(db->pg) == CONNECTION_BAD) {
i_error("pgsql: Connect failed to %s: %s",
PQdb(db->pg), last_error(db));
driver_pgsql_close(db);
return -1;
} else {
/* nonblocking connecting begins. */
db->io = io_add(PQsocket(db->pg), IO_WRITE,
connect_callback, db);
db->io_dir = IO_WRITE;
db->connecting = TRUE;
return 0;
}
}
static struct sql_db *driver_pgsql_init(const char *connect_string)
{
struct pgsql_db *db;
db = i_new(struct pgsql_db, 1);
db->connect_string = i_strdup(connect_string);
db->api = driver_pgsql_db;
db->queue_tail = &db->queue;
return &db->api;
}
static void driver_pgsql_deinit(struct sql_db *_db)
{
struct pgsql_db *db = (struct pgsql_db *)_db;
driver_pgsql_close(db);
i_free(db->error);
i_free(db);
}
static enum sql_db_flags
driver_pgsql_get_flags(struct sql_db *db __attr_unused__)
{
return 0;
}
static void consume_results(void *context)
{
struct pgsql_db *db = context;
do {
if (!PQconsumeInput(db->pg)) {
db->connected = FALSE;
break;
}
if (PQisBusy(db->pg))
return;
} while (PQgetResult(db->pg) != NULL);
io_remove(db->io);
db->io = NULL;
db->querying = FALSE;
if (db->queue != NULL && db->connected)
queue_send_next(db);
}
static void driver_pgsql_result_free(struct sql_result *_result)
{
struct pgsql_db *db = (struct pgsql_db *)_result->db;
struct pgsql_result *result = (struct pgsql_result *)_result;
if (result->api.callback)
return;
if (result->pgres != NULL) {
PQclear(result->pgres);
/* we'll have to read the rest of the results as well */
i_assert(db->io == NULL);
db->io = io_add(PQsocket(db->pg), IO_READ,
consume_results, db);
db->io_dir = IO_READ;
consume_results(db);
} else {
db->querying = FALSE;
}
i_free(result->fields);
i_free(result->values);
i_free(result);
if (db->queue != NULL && !db->querying && db->connected)
queue_send_next(db);
}
static void result_finish(struct pgsql_result *result)
{
struct pgsql_db *db = (struct pgsql_db *)result->api.db;
int free_result = TRUE;
if (result->callback != NULL) {
result->api.callback = TRUE;
result->callback(&result->api, result->context);
result->api.callback = FALSE;
free_result = db->sync_result != &result->api;
}
if (free_result)
driver_pgsql_result_free(&result->api);
}
static void get_result(void *context)
{
struct pgsql_result *result = context;
struct pgsql_db *db = (struct pgsql_db *)result->api.db;
if (!PQconsumeInput(db->pg)) {
db->connected = FALSE;
result_finish(result);
return;
}
if (PQisBusy(db->pg)) {
if (db->io == NULL) {
db->io = io_add(PQsocket(db->pg), IO_READ,
get_result, result);
db->io_dir = IO_READ;
}
return;
}
if (db->io != NULL) {
io_remove(db->io);
db->io = NULL;
}
result->pgres = PQgetResult(db->pg);
result_finish(result);
}
static void flush_callback(void *context)
{
struct pgsql_result *result = context;
struct pgsql_db *db = (struct pgsql_db *)result->api.db;
int ret;
ret = PQflush(db->pg);
if (ret > 0)
return;
io_remove(db->io);
db->io = NULL;
if (ret < 0) {
db->connected = FALSE;
result_finish(result);
} else {
/* all flushed */
get_result(result);
}
}
static void send_query(struct pgsql_result *result, const char *query)
{
struct pgsql_db *db = (struct pgsql_db *)result->api.db;
int ret;
i_assert(db->io == NULL);
i_assert(!db->querying);
i_assert(db->connected);
if (!PQsendQuery(db->pg, query)) {
db->connected = FALSE;
result_finish(result);
return;
}
ret = PQflush(db->pg);
if (ret < 0) {
db->connected = FALSE;
result_finish(result);
return;
}
db->querying = TRUE;
if (ret > 0) {
/* write blocks */
db->io = io_add(PQsocket(db->pg), IO_WRITE,
flush_callback, result);
db->io_dir = IO_WRITE;
} else {
get_result(result);
}
}
static void queue_send_next(struct pgsql_db *db)
{
struct pgsql_queue *queue;
queue = db->queue;
db->queue = queue->next;
send_query(queue->result, queue->query);
i_free(queue->query);
i_free(queue);
}
static void queue_timeout(void *context)
{
struct pgsql_db *db = context;
if (db->querying)
return;
if (!db->connected) {
driver_pgsql_connect(&db->api);
return;
}
if (db->queue != NULL)
queue_send_next(db);
if (db->queue == NULL) {
timeout_remove(db->queue_to);
db->queue_to = NULL;
}
}
static void
driver_pgsql_queue_query(struct pgsql_result *result, const char *query)
{
struct pgsql_db *db = (struct pgsql_db *)result->api.db;
struct pgsql_queue *queue;
queue = i_new(struct pgsql_queue, 1);
queue->created = time(NULL);
queue->query = i_strdup(query);
queue->result = result;
*db->queue_tail = queue;
if (db->queue_to == NULL)
db->queue_to = timeout_add(5000, queue_timeout, db);
}
static void do_query(struct pgsql_result *result, const char *query)
{
struct pgsql_db *db = (struct pgsql_db *)result->api.db;
if (db->querying) {
/* only one query at a time */
driver_pgsql_queue_query(result, query);
return;
}
if (!db->connected) {
/* try connecting again */
driver_pgsql_connect(&db->api);
driver_pgsql_queue_query(result, query);
return;
}
if (db->queue == NULL)
send_query(result, query);
else {
/* there's already queries queued, send them first */
driver_pgsql_queue_query(result, query);
queue_send_next(db);
}
}
static void exec_callback(struct sql_result *result,
void *context __attr_unused__)
{
struct pgsql_db *db = (struct pgsql_db *)result->db;
i_error("pgsql: sql_exec() failed: %s", last_error(db));
}
static void driver_pgsql_exec(struct sql_db *db, const char *query)
{
struct pgsql_result *result;
result = i_new(struct pgsql_result, 1);
result->api = driver_pgsql_result;
result->api.db = db;
result->callback = exec_callback;
do_query(result, query);
}
static void driver_pgsql_query(struct sql_db *db, const char *query,
sql_query_callback_t *callback, void *context)
{
struct pgsql_result *result;
result = i_new(struct pgsql_result, 1);
result->api = driver_pgsql_result;
result->api.db = db;
result->callback = callback;
result->context = context;
do_query(result, query);
}
static void pgsql_query_s_callback(struct sql_result *result, void *context)
{
struct pgsql_db *db = context;
db->sync_result = result;
io_loop_stop(db->ioloop);
}
static struct sql_result *
driver_pgsql_query_s(struct sql_db *_db, const char *query)
{
struct pgsql_db *db = (struct pgsql_db *)_db;
struct io old_io;
if (db->io == NULL)
db->ioloop = io_loop_create(default_pool);
else {
/* have to move our existing I/O handler to new I/O loop */
old_io = *db->io;
io_remove(db->io);
db->ioloop = io_loop_create(default_pool);
db->io = io_add(old_io.fd, old_io.condition,
old_io.callback, old_io.context);
}
driver_pgsql_query(_db, query, pgsql_query_s_callback, db);
io_loop_run(db->ioloop);
io_loop_destroy(db->ioloop);
db->ioloop = NULL;
i_assert(db->io == NULL);
return db->sync_result;
}
static int driver_pgsql_result_next_row(struct sql_result *_result)
{
struct pgsql_result *result = (struct pgsql_result *)_result;
struct pgsql_db *db = (struct pgsql_db *)_result->db;
if (result->rows != 0) {
/* second time we're here */
return ++result->rownum < result->rows;
}
if (result->pgres == NULL)
return -1;
switch (PQresultStatus(result->pgres)) {
case PGRES_COMMAND_OK:
/* no rows returned */
return 0;
case PGRES_TUPLES_OK:
result->rows = PQntuples(result->pgres);
return result->rows > 0;
case PGRES_EMPTY_QUERY:
case PGRES_NONFATAL_ERROR:
/* nonfatal error */
return -1;
default:
/* treat as fatal error */
db->connected = FALSE;
return -1;
}
}
static void driver_pgsql_result_fetch_fields(struct pgsql_result *result)
{
unsigned int i;
if (result->fields != NULL)
return;
/* @UNSAFE */
result->fields_count = PQnfields(result->pgres);
result->fields = i_new(const char *, result->fields_count);
for (i = 0; i < result->fields_count; i++)
result->fields[i] = PQfname(result->pgres, i);
}
static unsigned int
driver_pgsql_result_get_fields_count(struct sql_result *_result)
{
struct pgsql_result *result = (struct pgsql_result *)_result;
driver_pgsql_result_fetch_fields(result);
return result->fields_count;
}
static const char *
driver_pgsql_result_get_field_name(struct sql_result *_result, unsigned int idx)
{
struct pgsql_result *result = (struct pgsql_result *)_result;
driver_pgsql_result_fetch_fields(result);
i_assert(idx < result->fields_count);
return result->fields[idx];
}
static int driver_pgsql_result_find_field(struct sql_result *_result,
const char *field_name)
{
struct pgsql_result *result = (struct pgsql_result *)_result;
unsigned int i;
driver_pgsql_result_fetch_fields(result);
for (i = 0; i < result->fields_count; i++) {
if (strcmp(result->fields[i], field_name) == 0)
return i;
}
return -1;
}
static const char *
driver_pgsql_result_get_field_value(struct sql_result *_result,
unsigned int idx)
{
struct pgsql_result *result = (struct pgsql_result *)_result;
if (PQgetisnull(result->pgres, result->rownum, idx))
return NULL;
return PQgetvalue(result->pgres, result->rownum, idx);
}
static const char *
driver_pgsql_result_find_field_value(struct sql_result *result,
const char *field_name)
{
int idx;
idx = driver_pgsql_result_find_field(result, field_name);
if (idx < 0)
return NULL;
return driver_pgsql_result_get_field_value(result, idx);
}
static const char *const *
driver_pgsql_result_get_values(struct sql_result *_result)
{
struct pgsql_result *result = (struct pgsql_result *)_result;
unsigned int i;
if (result->values == NULL) {
driver_pgsql_result_fetch_fields(result);
result->values = i_new(const char *, result->fields_count);
}
/* @UNSAFE */
for (i = 0; i < result->fields_count; i++) {
result->values[i] =
driver_pgsql_result_get_field_value(_result, i);
}
return result->values;
}
static const char *driver_pgsql_result_get_error(struct sql_result *_result)
{
struct pgsql_result *result = (struct pgsql_result *)_result;
struct pgsql_db *db = (struct pgsql_db *)_result->db;
const char *msg;
size_t len;
msg = PQresultErrorMessage(result->pgres);
if (msg == NULL)
return "(no error set)";
/* Error message should contain trailing \n, we don't want it */
len = strlen(msg);
i_free(db->error);
db->error = len == 0 || msg[len-1] != '\n' ?
i_strdup(msg) : i_strndup(msg, len-1);
return db->error;
}
static struct sql_transaction_context *
driver_pgsql_transaction_begin(struct sql_db *db)
{
struct pgsql_transaction_context *ctx;
ctx = i_new(struct pgsql_transaction_context, 1);
ctx->ctx.db = db;
return &ctx->ctx;
}
static void
transaction_commit_callback(struct sql_result *result, void *context)
{
struct pgsql_transaction_context *ctx =
(struct pgsql_transaction_context *)context;
if (sql_result_next_row(result) < 0)
ctx->callback(sql_result_get_error(result), ctx->context);
else
ctx->callback(NULL, ctx->context);
}
static void
driver_pgsql_transaction_commit(struct sql_transaction_context *_ctx,
sql_commit_callback_t *callback, void *context)
{
struct pgsql_transaction_context *ctx =
(struct pgsql_transaction_context *)_ctx;
if (ctx->failed) {
callback(ctx->error, context);
sql_exec(_ctx->db, "ROLLBACK");
i_free(ctx);
return;
}
ctx->callback = callback;
ctx->context = context;
sql_query(_ctx->db, "COMMIT", transaction_commit_callback, ctx);
}
static int
driver_pgsql_transaction_commit_s(struct sql_transaction_context *_ctx,
const char **error_r)
{
struct pgsql_transaction_context *ctx =
(struct pgsql_transaction_context *)_ctx;
struct sql_result *result;
if (ctx->failed) {
*error_r = ctx->error;
sql_exec(_ctx->db, "ROLLBACK");
} else {
result = sql_query_s(_ctx->db, "COMMIT");
if (sql_result_next_row(result) < 0)
*error_r = sql_result_get_error(result);
else
*error_r = NULL;
sql_result_free(result);
}
i_free(ctx);
return *error_r == NULL ? 0 : -1;
}
static void
driver_pgsql_transaction_rollback(struct sql_transaction_context *_ctx)
{
struct pgsql_transaction_context *ctx =
(struct pgsql_transaction_context *)_ctx;
sql_exec(_ctx->db, "ROLLBACK");
i_free(ctx);
}
static void
transaction_update_callback(struct sql_result *result, void *context)
{
struct pgsql_transaction_context *ctx =
(struct pgsql_transaction_context *)context;
if (sql_result_next_row(result) < 0) {
ctx->failed = TRUE;
ctx->error = sql_result_get_error(result);
}
}
static void
driver_pgsql_update(struct sql_transaction_context *_ctx, const char *query)
{
struct pgsql_transaction_context *ctx =
(struct pgsql_transaction_context *)_ctx;
if (ctx->failed)
return;
if (!ctx->opened) {
ctx->opened = TRUE;
sql_query(_ctx->db, "BEGIN", transaction_update_callback, ctx);
}
sql_query(_ctx->db, query, transaction_update_callback, ctx);
}
struct sql_db driver_pgsql_db = {
"pgsql",
driver_pgsql_init,
driver_pgsql_deinit,
driver_pgsql_get_flags,
driver_pgsql_connect,
driver_pgsql_exec,
driver_pgsql_query,
driver_pgsql_query_s,
driver_pgsql_transaction_begin,
driver_pgsql_transaction_commit,
driver_pgsql_transaction_commit_s,
driver_pgsql_transaction_rollback,
driver_pgsql_update
};
struct sql_result driver_pgsql_result = {
NULL,
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_find_field_value,
driver_pgsql_result_get_values,
driver_pgsql_result_get_error,
FALSE
};
#endif