diff src/lib-sql/sql-api.c @ 11261:1c8cc349ef55 HEAD

lib-sql: Use generic sql connection pooling code for mysql/pgsql. It's possible to give multiple host settings to do load balancing / HA. If one host is down, another one is tried. All queries are automatically retried in another host if they fail in first one. Since PostgreSQL support async queries, Dovecot can create multiple connections to the database as needed, so it can do lookups in parallel. The number of connections can be changed with maxconns=n in connect_query, the default is 5.
author Timo Sirainen <tss@iki.fi>
date Tue, 04 May 2010 17:55:23 +0300
parents 260e190306b0
children 11538925cbbb
line wrap: on
line diff
--- a/src/lib-sql/sql-api.c	Tue May 04 16:12:00 2010 +0300
+++ b/src/lib-sql/sql-api.c	Tue May 04 17:55:23 2010 +0300
@@ -2,9 +2,11 @@
 
 #include "lib.h"
 #include "array.h"
+#include "ioloop.h"
 #include "sql-api-private.h"
 
 #include <stdlib.h>
+#include <time.h>
 
 struct sql_db_module_register sql_db_module_register = { 0 };
 ARRAY_TYPE(sql_drivers) sql_drivers;
@@ -38,23 +40,36 @@
 	}
 }
 
-struct sql_db *sql_init(const char *db_driver,
-			const char *connect_string ATTR_UNUSED)
+static const struct sql_db *sql_find_driver(const char *name)
 {
 	const struct sql_db *const *drivers;
 	unsigned int i, count;
-	struct sql_db *db;
 
 	drivers = array_get(&sql_drivers, &count);
 	for (i = 0; i < count; i++) {
-		if (strcmp(db_driver, drivers[i]->name) == 0) {
-			db = drivers[i]->v.init(connect_string);
-			i_array_init(&db->module_contexts, 5);
-			return db;
-		}
+		if (strcmp(drivers[i]->name, name) == 0)
+			return drivers[i];
 	}
+	return NULL;
+}
 
-	i_fatal("Unknown database driver '%s'", db_driver);
+struct sql_db *sql_init(const char *db_driver, const char *connect_string)
+{
+	const struct sql_db *driver;
+	struct sql_db *db;
+
+	i_assert(connect_string != NULL);
+
+	driver = sql_find_driver(db_driver);
+	if (driver == NULL)
+		i_fatal("Unknown database driver '%s'", db_driver);
+
+	if ((driver->flags & SQL_DB_FLAG_POOLED) == 0)
+		db = driver->v.init(connect_string);
+	else
+		db = driver_sqlpool_init(connect_string, driver);
+	i_array_init(&db->module_contexts, 5);
+	return db;
 }
 
 void sql_deinit(struct sql_db **_db)
@@ -62,19 +77,46 @@
 	struct sql_db *db = *_db;
 
 	*_db = NULL;
+
+	if (db->to_reconnect != NULL)
+		timeout_remove(&db->to_reconnect);
 	db->v.deinit(db);
 }
 
 enum sql_db_flags sql_get_flags(struct sql_db *db)
 {
-	return db->v.get_flags(db);
+	return db->flags;
 }
 
 int sql_connect(struct sql_db *db)
 {
+	time_t now;
+
+	switch (db->state) {
+	case SQL_DB_STATE_DISCONNECTED:
+		break;
+	case SQL_DB_STATE_CONNECTING:
+		return 0;
+	default:
+		return 1;
+	}
+
+	/* don't try reconnecting more than once a second */
+	now = time(NULL);
+	if (db->last_connect_try + (time_t)db->connect_delay > now)
+		return -1;
+	db->last_connect_try = now;
+
 	return db->v.connect(db);
 }
 
+void sql_disconnect(struct sql_db *db)
+{
+	if (db->to_reconnect != NULL)
+		timeout_remove(&db->to_reconnect);
+	db->v.disconnect(db);
+}
+
 const char *sql_escape_string(struct sql_db *db, const char *string)
 {
 	return db->v.escape_string(db, string);
@@ -286,7 +328,7 @@
 static const char *
 sql_result_not_connected_get_error(struct sql_result *result ATTR_UNUSED)
 {
-	return "Not connected to database";
+	return SQL_ERRSTR_NOT_CONNECTED;
 }
 
 struct sql_transaction_context *sql_transaction_begin(struct sql_db *db)
@@ -332,11 +374,43 @@
 	ctx->db->v.update(ctx, query, affected_rows);
 }
 
+void sql_db_set_state(struct sql_db *db, enum sql_db_state state)
+{
+	enum sql_db_state old_state = db->state;
+
+	if (db->state == state)
+		return;
+
+	db->state = state;
+	if (db->state_change_callback != NULL) {
+		db->state_change_callback(db, old_state,
+					  db->state_change_context);
+	}
+}
+
+void sql_transaction_add_query(struct sql_transaction_context *ctx, pool_t pool,
+			       const char *query, unsigned int *affected_rows)
+{
+	struct sql_transaction_query *tquery;
+
+	tquery = p_new(pool, struct sql_transaction_query, 1);
+	tquery->trans = ctx;
+	tquery->query = p_strdup(pool, query);
+	tquery->affected_rows = affected_rows;
+
+	if (ctx->head == NULL)
+		ctx->head = tquery;
+	else
+		ctx->tail->next = tquery;
+	ctx->tail = tquery;
+}
+
 struct sql_result sql_not_connected_result = {
 	.v = {
 		sql_result_not_connected_free,
 		sql_result_not_connected_next_row,
 		NULL, NULL, NULL, NULL, NULL, NULL, NULL,
 		sql_result_not_connected_get_error
-	}
+	},
+	.failed_try_retry = TRUE
 };