changeset 3737:d67092398377 HEAD

Added dictionary API and implementation for SQL. It's meant for looking and modifying key=value pairs. In future we may need to use this for many different things, but for now it's only used by quota-dict plugin.
author Timo Sirainen <tss@iki.fi>
date Sat, 10 Dec 2005 20:58:59 +0200
parents 3dd0ab18d8da
children 732b62dc1976
files configure.in src/Makefile.am src/lib-dict/.cvsignore src/lib-dict/Makefile.am src/lib-dict/dict-private.h src/lib-dict/dict-sql.c src/lib-dict/dict-sql.h src/lib-dict/dict.c src/lib-dict/dict.h
diffstat 9 files changed, 548 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/configure.in	Sat Dec 10 20:57:11 2005 +0200
+++ b/configure.in	Sat Dec 10 20:58:59 2005 +0200
@@ -1477,6 +1477,7 @@
 src/lib-sql/Makefile
 src/lib-auth/Makefile
 src/lib-charset/Makefile
+src/lib-dict/Makefile
 src/lib-imap/Makefile
 src/lib-index/Makefile
 src/lib-mail/Makefile
--- a/src/Makefile.am	Sat Dec 10 20:57:11 2005 +0200
+++ b/src/Makefile.am	Sat Dec 10 20:58:59 2005 +0200
@@ -8,6 +8,7 @@
 
 SUBDIRS = \
 	lib \
+	lib-dict \
 	lib-sql \
 	lib-ntlm \
 	lib-settings \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dict/.cvsignore	Sat Dec 10 20:58:59 2005 +0200
@@ -0,0 +1,8 @@
+*.la
+*.lo
+*.o
+.deps
+.libs
+Makefile
+Makefile.in
+so_locations
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dict/Makefile.am	Sat Dec 10 20:58:59 2005 +0200
@@ -0,0 +1,14 @@
+noinst_LIBRARIES = libdict.a
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-sql \
+	$(SQL_CFLAGS)
+
+libdict_a_SOURCES = \
+	dict.c \
+	dict-sql.c
+
+noinst_HEADERS = \
+	dict.h \
+	dict-private.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dict/dict-private.h	Sat Dec 10 20:58:59 2005 +0200
@@ -0,0 +1,43 @@
+#ifndef __DICT_PRIVATE_H
+#define __DICT_PRIVATE_H
+
+#include "dict.h"
+
+struct dict_vfuncs {
+	struct dict *(*init)(struct dict *dict_class, const char *uri);
+	void (*deinit)(struct dict *dict);
+
+	char *(*lookup)(struct dict *dict, pool_t pool, const char *key);
+
+	struct dict_iterate_context *
+		(*iterate_init)(struct dict *dict, const char *path,
+				int recurse);
+	int (*iterate)(struct dict_iterate_context *ctx,
+		       const char **key_r, const char **value_r);
+	void (*iterate_deinit)(struct dict_iterate_context *ctx);
+
+	struct dict_transaction_context *(*transaction_init)(struct dict *dict);
+	int (*transaction_commit)(struct dict_transaction_context *ctx);
+	void (*transaction_rollback)(struct dict_transaction_context *ctx);
+
+	void (*set)(struct dict_transaction_context *ctx,
+		    const char *key, const char *value);
+	void (*atomic_inc)(struct dict_transaction_context *ctx,
+			   const char *key, long long diff);
+};
+
+struct dict {
+	const char *name;
+
+	struct dict_vfuncs v;
+};
+
+struct dict_iterate_context {
+	struct dict *dict;
+};
+
+struct dict_transaction_context {
+	struct dict *dict;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dict/dict-sql.c	Sat Dec 10 20:58:59 2005 +0200
@@ -0,0 +1,285 @@
+/* Copyright (C) 2005 Timo Sirainen */
+
+#include "lib.h"
+#include "istream.h"
+#include "str.h"
+#include "strescape.h"
+#include "sql-api-private.h"
+#include "dict-private.h"
+#include "dict-sql.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+struct sql_dict {
+	struct dict dict;
+
+	pool_t pool;
+	struct sql_db *db;
+
+	const char *connect_string;
+	const char *table, *select_field, *where_field;
+};
+
+struct sql_dict_iterate_context {
+	struct dict_iterate_context ctx;
+
+	struct sql_result *result;
+};
+
+static int sql_dict_read_config(struct sql_dict *dict, const char *path)
+{
+	struct istream *input;
+	const char *line, *value;
+	int fd;
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1) {
+		i_error("open(%s) failed: %m", path);
+		return -1;
+	}
+
+	input = i_stream_create_file(fd, default_pool, (size_t)-1, FALSE);
+	while ((line = i_stream_read_next_line(input)) != NULL) {
+		value = strchr(line, '=');
+		if (value == NULL)
+			continue;
+
+		t_push();
+		line = t_strdup_until(line, value);
+		value++;
+
+		if (strcmp(line, "connect") == 0)
+			dict->connect_string = p_strdup(dict->pool, value);
+		else if (strcmp(line, "table") == 0)
+			dict->table = p_strdup(dict->pool, value);
+		else if (strcmp(line, "select_field") == 0)
+			dict->select_field = p_strdup(dict->pool, value);
+		else if (strcmp(line, "where_field") == 0)
+			dict->where_field = p_strdup(dict->pool, value);
+
+		t_pop();
+	}
+	i_stream_unref(input);
+	(void)close(fd);
+	return 0;
+}
+
+static struct dict *sql_dict_init(struct dict *dict_class, const char *uri)
+{
+	struct sql_dict *dict;
+	pool_t pool;
+
+	pool = pool_alloconly_create("sql dict", 1024);
+	dict = p_new(pool, struct sql_dict, 1);
+	dict->pool = pool;
+	dict->dict = *dict_class;
+
+	if (sql_dict_read_config(dict, uri) <= 0) {
+		pool_unref(pool);
+		return NULL;
+	}
+
+	t_push();
+	dict->db = sql_init(dict_class->name, dict->connect_string);
+	t_pop();
+	return &dict->dict;
+}
+
+static void sql_dict_deinit(struct dict *_dict)
+{
+	struct sql_dict *dict = (struct sql_dict *)_dict;
+
+	sql_deinit(dict->db);
+}
+
+static char *sql_dict_lookup(struct dict *_dict, pool_t pool, const char *key)
+{
+	struct sql_dict *dict = (struct sql_dict *)_dict;
+	struct sql_result *result;
+	const char *query;
+	char *ret;
+
+	t_push();
+	query = t_strdup_printf("SELECT %s FROM %s WHERE %s = '%s'",
+				dict->select_field, dict->table,
+				dict->where_field, str_escape(key));
+	result = sql_query_s(dict->db, query);
+	t_pop();
+
+	if (sql_result_next_row(result) <= 0)
+		ret = NULL;
+	else
+                ret = p_strdup(pool, sql_result_get_field_value(result, 0));
+
+	sql_result_free(result);
+	return ret;
+}
+
+static struct dict_iterate_context *
+sql_dict_iterate_init(struct dict *_dict, const char *path, int recurse)
+{
+	struct sql_dict *dict = (struct sql_dict *)_dict;
+        struct sql_dict_iterate_context *ctx;
+	string_t *query;
+
+	ctx = i_new(struct sql_dict_iterate_context, 1);
+	ctx->ctx.dict = _dict;
+
+	t_push();
+	query = t_str_new(256);
+	str_printfa(query, "SELECT %s, %s FROM %s WHERE %s LIKE '%s/%%'",
+		    dict->where_field, dict->select_field,
+		    dict->table, dict->where_field, str_escape(path));
+	if (!recurse) {
+		str_printfa(query, " AND %s NOT LIKE '%s/%%/%%'",
+			    dict->where_field, str_escape(path));
+	}
+	ctx->result = sql_query_s(dict->db, str_c(query));
+	t_pop();
+
+	return &ctx->ctx;
+}
+
+static int sql_dict_iterate(struct dict_iterate_context *_ctx,
+			    const char **key_r, const char **value_r)
+{
+	struct sql_dict_iterate_context *ctx =
+		(struct sql_dict_iterate_context *)_ctx;
+	int ret;
+
+	if ((ret = sql_result_next_row(ctx->result)) <= 0)
+		return ret;
+
+	*key_r = sql_result_get_field_value(ctx->result, 0);
+	*value_r = sql_result_get_field_value(ctx->result, 1);
+	return 1;
+}
+
+static void sql_dict_iterate_deinit(struct dict_iterate_context *_ctx)
+{
+	struct sql_dict_iterate_context *ctx =
+		(struct sql_dict_iterate_context *)_ctx;
+
+	sql_result_free(ctx->result);
+}
+
+struct sql_dict_transaction_context {
+	struct dict_transaction_context ctx;
+
+	struct sql_transaction_context *sql_ctx;
+};
+
+static struct dict_transaction_context *
+sql_dict_transaction_init(struct dict *_dict)
+{
+	struct sql_dict *dict = (struct sql_dict *)_dict;
+	struct sql_dict_transaction_context *ctx;
+
+	ctx = i_new(struct sql_dict_transaction_context, 1);
+	ctx->ctx.dict = _dict;
+	ctx->sql_ctx = sql_transaction_begin(dict->db);
+
+	return &ctx->ctx;
+}
+
+static int sql_dict_transaction_commit(struct dict_transaction_context *_ctx)
+{
+	struct sql_dict_transaction_context *ctx =
+		(struct sql_dict_transaction_context *)_ctx;
+	const char *error;
+	int ret;
+
+	ret = sql_transaction_commit_s(ctx->sql_ctx, &error);
+	if (ret < 0)
+		i_error("sql dict: commit failed: %s", error);
+	i_free(ctx);
+	return ret;
+}
+
+static void sql_dict_transaction_rollback(struct dict_transaction_context *_ctx)
+{
+	struct sql_dict_transaction_context *ctx =
+		(struct sql_dict_transaction_context *)_ctx;
+
+	sql_transaction_rollback(ctx->sql_ctx);
+	i_free(ctx);
+}
+
+static void sql_dict_set(struct dict_transaction_context *_ctx,
+			 const char *key, const char *value)
+{
+	struct sql_dict_transaction_context *ctx =
+		(struct sql_dict_transaction_context *)_ctx;
+	struct sql_dict *dict = (struct sql_dict *)_ctx->dict;
+	const char *query;
+
+	t_push();
+	query = t_strdup_printf("UPDATE %s SET %s = '%s' WHERE %s = '%s'",
+				dict->table, dict->select_field, str_escape(value),
+				dict->where_field, str_escape(key));
+	sql_update(ctx->sql_ctx, query);
+	t_pop();
+}
+
+static void sql_dict_atomic_inc(struct dict_transaction_context *_ctx,
+				const char *key, long long diff)
+{
+	struct sql_dict_transaction_context *ctx =
+		(struct sql_dict_transaction_context *)_ctx;
+	struct sql_dict *dict = (struct sql_dict *)_ctx->dict;
+	const char *query;
+
+	t_push();
+	query = t_strdup_printf("UPDATE %s SET %s = %s + %lld WHERE %s = '%s'",
+				dict->table, dict->select_field,
+				dict->select_field, diff,
+				dict->where_field, str_escape(key));
+	sql_update(ctx->sql_ctx, query);
+	t_pop();
+}
+
+static struct dict sql_dict = {
+	MEMBER(name) "sql",
+
+	{
+		sql_dict_init,
+		sql_dict_deinit,
+		sql_dict_lookup,
+		sql_dict_iterate_init,
+		sql_dict_iterate,
+		sql_dict_iterate_deinit,
+		sql_dict_transaction_init,
+		sql_dict_transaction_commit,
+		sql_dict_transaction_rollback,
+		sql_dict_set,
+		sql_dict_atomic_inc
+	}
+};
+
+static struct dict *dict_sql_classes;
+
+void dict_sql_register(void)
+{
+	int i, count;
+
+	/* @UNSAFE */
+	for (count = 0; sql_db_drivers[count] != NULL; count++) ;
+	dict_sql_classes = i_new(struct dict, count);
+
+	for (i = 0; i < count; i++) {
+		dict_sql_classes[i] = sql_dict;
+		dict_sql_classes[i].name = sql_db_drivers[i]->name;
+
+		dict_class_register(&dict_sql_classes[i]);
+	}
+}
+
+void dict_sql_unregister(void)
+{
+	int i;
+
+	for (i = 0; sql_db_drivers[i] != NULL; i++)
+		dict_class_unregister(&dict_sql_classes[i]);
+	i_free(dict_sql_classes);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dict/dict-sql.h	Sat Dec 10 20:58:59 2005 +0200
@@ -0,0 +1,7 @@
+#ifndef __DICT_SQL_H
+#define __DICT_SQL_H
+
+void dict_sql_register(void);
+void dict_sql_unregister(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dict/dict.c	Sat Dec 10 20:58:59 2005 +0200
@@ -0,0 +1,143 @@
+/* Copyright (C) 2005 Timo Sirainen */
+
+#include "lib.h"
+#include "array.h"
+#include "dict-sql.h"
+#include "dict-private.h"
+
+static array_t ARRAY_DEFINE(dict_classes, struct dict *);
+static int dict_count = 0;
+
+static void dict_class_register_all(void)
+{
+	dict_sql_register();
+}
+
+static void dict_class_unregister_all(void)
+{
+	dict_sql_unregister();
+}
+
+static struct dict *dict_class_lookup(const char *name)
+{
+	struct dict *const *dicts;
+	unsigned int i, count;
+
+	dicts = array_get(&dict_classes, &count);
+	for (i = 0; i < count; i++) {
+		if (strcmp(dicts[i]->name, name) == 0)
+			return dicts[i];
+	}
+	return NULL;
+}
+
+void dict_class_register(struct dict *dict_class)
+{
+	if (!array_is_created(&dict_classes))
+		ARRAY_CREATE(&dict_classes, default_pool, struct dict *, 8);
+
+	if (dict_class_lookup(dict_class->name) != NULL) {
+		i_fatal("dict_class_register(%s): Already registered",
+			dict_class->name);
+	}
+	array_append(&dict_classes, &dict_class, 1);
+}
+
+void dict_class_unregister(struct dict *dict_class)
+{
+	struct dict *const *dicts;
+	unsigned int i, count;
+
+	dicts = array_get(&dict_classes, &count);
+	for (i = 0; i < count; i++) {
+		if (dicts[i] == dict_class) {
+			array_delete(&dict_classes, i, 1);
+			break;
+		}
+	}
+
+	i_assert(i < count);
+
+	if (array_count(&dict_classes) == 0)
+		array_free(&dict_classes);
+}
+
+struct dict *dict_init(const char *uri)
+{
+	struct dict *dict;
+	const char *p;
+
+	if (dict_count++ == 0)
+		dict_class_register_all();
+
+	p = strchr(uri, ':');
+	if (p == NULL) {
+		i_error("URI is missing ':': %s", uri);
+		return NULL;
+	}
+
+	t_push();
+	dict = dict_class_lookup(t_strdup_until(uri, p));
+	t_pop();
+	if (dict == NULL)
+		return NULL;
+
+	return dict->v.init(dict, p+1);
+}
+
+void dict_deinit(struct dict *dict)
+{
+	dict->v.deinit(dict);
+
+	if (--dict_count == 0)
+		dict_class_unregister_all();
+}
+
+char *dict_lookup(struct dict *dict, pool_t pool, const char *key)
+{
+	return dict->v.lookup(dict, pool, key);
+}
+
+struct dict_iterate_context *
+dict_iterate_init(struct dict *dict, const char *path, int recurse)
+{
+	return dict->v.iterate_init(dict, path, recurse);
+}
+
+int dict_iterate(struct dict_iterate_context *ctx,
+		 const char **key_r, const char **value_r)
+{
+	return ctx->dict->v.iterate(ctx, key_r, value_r);
+}
+
+void dict_iterate_deinit(struct dict_iterate_context *ctx)
+{
+	ctx->dict->v.iterate_deinit(ctx);
+}
+
+struct dict_transaction_context *dict_transaction_begin(struct dict *dict)
+{
+	return dict->v.transaction_init(dict);
+}
+
+int dict_transaction_commit(struct dict_transaction_context *ctx)
+{
+	return ctx->dict->v.transaction_commit(ctx);
+}
+
+void dict_transaction_rollback(struct dict_transaction_context *ctx)
+{
+	ctx->dict->v.transaction_rollback(ctx);
+}
+
+void dict_set(struct dict_transaction_context *ctx,
+	      const char *key, const char *value)
+{
+	ctx->dict->v.set(ctx, key, value);
+}
+
+void dict_atomic_inc(struct dict_transaction_context *ctx,
+		     const char *key, long long diff)
+{
+	ctx->dict->v.atomic_inc(ctx, key, diff);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dict/dict.h	Sat Dec 10 20:58:59 2005 +0200
@@ -0,0 +1,46 @@
+#ifndef __DICT_H
+#define __DICT_H
+
+#define DICT_PATH_PRIVATE "priv/"
+#define DICT_PATH_SHARED "shared/"
+
+struct dict;
+
+void dict_class_register(struct dict *dict_class);
+void dict_class_unregister(struct dict *dict_class);
+
+/* Open dictionary with given URI (type:data).
+   If URI is invalid, returns NULL. */
+struct dict *dict_init(const char *uri);
+/* Close dictionary. */
+void dict_deinit(struct dict *dict);
+
+/* Return value for key, or NULL if not found. */
+char *dict_lookup(struct dict *dict, pool_t pool, const char *key);
+
+/* Iterate through all values in a path. If recurse is FALSE, keys in
+   the given path are returned, but not their children. */
+struct dict_iterate_context *
+dict_iterate_init(struct dict *dict, const char *path, int recurse);
+/* Returns -1 = error, 0 = finished, 1 = key/value set */
+int dict_iterate(struct dict_iterate_context *ctx,
+		 const char **key_r, const char **value_r);
+void dict_iterate_deinit(struct dict_iterate_context *ctx);
+
+/* Start a new dictionary transaction. */
+struct dict_transaction_context *dict_transaction_begin(struct dict *dict);
+/* Commit the transaction. Returns 0 if ok, -1 if failed. */
+int dict_transaction_commit(struct dict_transaction_context *ctx);
+/* Rollback all changes made in transaction. */
+void dict_transaction_rollback(struct dict_transaction_context *ctx);
+
+/* Set key=value in dictionary. */
+void dict_set(struct dict_transaction_context *ctx,
+	      const char *key, const char *value);
+/* Increase/decrease a numeric value in dictionary. Note that the value is
+   changed when transaction is being committed, so you can't know beforehand
+   what the value will become. */
+void dict_atomic_inc(struct dict_transaction_context *ctx,
+		     const char *key, long long diff);
+
+#endif