changeset 22525:7c8bf126fd39

dict-sql: Use prepared statements Create a hash table of query template -> prepared statement and fill it out as needed. This could have been done some alternative ways that wouldn't require building the string first, but this should still be fast enough and much easier to implement.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Thu, 07 Sep 2017 15:40:16 +0300
parents c5a2743428a0
children 33f66557b72e
files src/lib-dict/dict-sql.c
diffstat 1 files changed, 46 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-dict/dict-sql.c	Sat Aug 26 23:27:21 2017 +0300
+++ b/src/lib-dict/dict-sql.c	Thu Sep 07 15:40:16 2017 +0300
@@ -4,6 +4,7 @@
 #include "array.h"
 #include "istream.h"
 #include "hex-binary.h"
+#include "hash.h"
 #include "str.h"
 #include "sql-api-private.h"
 #include "sql-db-cache.h"
@@ -30,6 +31,9 @@
 	const char *username;
 	const struct dict_sql_settings *set;
 
+	/* query template => prepared statement */
+	HASH_TABLE(const char *, struct sql_prepared_statement *) prep_stmt_hash;
+
 	unsigned int has_on_duplicate_key:1;
 };
 
@@ -116,14 +120,36 @@
 
 	dict->db = sql_db_cache_new(dict_sql_db_cache, driver->name,
 				    dict->set->connect);
+	if ((sql_get_flags(dict->db) & SQL_DB_FLAG_PREP_STATEMENTS) != 0) {
+		hash_table_create(&dict->prep_stmt_hash, dict->pool,
+				  0, str_hash, strcmp);
+	}
 	*dict_r = &dict->dict;
 	return 0;
 }
 
+static void sql_dict_prep_stmt_hash_free(struct sql_dict *dict)
+{
+	struct hash_iterate_context *iter;
+	struct sql_prepared_statement *prep_stmt;
+	const char *query;
+
+	if (!hash_table_is_created(dict->prep_stmt_hash))
+		return;
+
+	iter = hash_table_iterate_init(dict->prep_stmt_hash);
+	while (hash_table_iterate(iter, dict->prep_stmt_hash, &query, &prep_stmt))
+		sql_prepared_statement_deinit(&prep_stmt);
+	hash_table_iterate_deinit(&iter);
+
+	hash_table_destroy(&dict->prep_stmt_hash);
+}
+
 static void sql_dict_deinit(struct dict *_dict)
 {
 	struct sql_dict *dict = (struct sql_dict *)_dict;
 
+	sql_dict_prep_stmt_hash_free(dict);
 	sql_deinit(&dict->db);
 	pool_unref(&dict->pool);
 }
@@ -250,12 +276,27 @@
 }
 
 static struct sql_statement *
-sql_dict_statement_init(struct sql_db *db, const char *query,
+sql_dict_statement_init(struct sql_dict *dict, const char *query,
 			const ARRAY_TYPE(sql_dict_param) *params)
 {
-	struct sql_statement *stmt = sql_statement_init(db, query);
+	struct sql_statement *stmt;
+	struct sql_prepared_statement *prep_stmt;
 	const struct sql_dict_param *param;
 
+	if (hash_table_is_created(dict->prep_stmt_hash)) {
+		prep_stmt = hash_table_lookup(dict->prep_stmt_hash, query);
+		if (prep_stmt == NULL) {
+			const char *query_dup = p_strdup(dict->pool, query);
+			prep_stmt = sql_prepared_statement_init(dict->db, query);
+			hash_table_insert(dict->prep_stmt_hash, query_dup, prep_stmt);
+		}
+		stmt = sql_statement_init_prepared(prep_stmt);
+	} else {
+		/* Prepared statements not supported by the backend.
+		   Just use regular statements to avoid wasting memory. */
+		stmt = sql_statement_init(dict->db, query);
+	}
+
 	array_foreach(params, param) {
 		sql_dict_statement_bind(stmt, array_foreach_idx(params, param),
 					param);
@@ -435,7 +476,7 @@
 			"sql dict lookup: Failed to lookup key %s: %s", key, error);
 		return -1;
 	}
-	*stmt_r = sql_dict_statement_init(dict->db, str_c(query), &params);
+	*stmt_r = sql_dict_statement_init(dict, str_c(query), &params);
 	return 0;
 }
 
@@ -699,7 +740,7 @@
 			(unsigned long long)(ctx->ctx.max_rows - ctx->ctx.row_count));
 	}
 
-	*stmt_r = sql_dict_statement_init(dict->db, str_c(query), &params);
+	*stmt_r = sql_dict_statement_init(dict, str_c(query), &params);
 	ctx->map = map;
 	return 1;
 }
@@ -989,7 +1030,7 @@
 {
 	struct sql_dict *dict = (struct sql_dict *)ctx->ctx.dict;
 	struct sql_statement *stmt =
-		sql_dict_statement_init(dict->db, query, params);
+		sql_dict_statement_init(dict, query, params);
 
 	if (ctx->ctx.timestamp.tv_sec != 0)
 		sql_statement_set_timestamp(stmt, &ctx->ctx.timestamp);