On Thursday, 6th Januar 2011, 21:02:17 Victor Duchovni wrote:
> On Thu, Jan 06, 2011 at 04:56:48PM +0100, Stefan Jakobs wrote:
> > > In this case, it is not as critical to set such a flag, but it is
> > > important to allow the existing scan to continue to completion, and
> > > ignore or (just note) new requests until it does. Once a scan
> > > completes, new scans can proceed either immediately (saved flag) or
> > > when next requested.
> > 
> > That's what I have implemented. If a cleanup process is already running
> > and a second cleanup process starts then the second process will quit as
> > if the database was empty and it will log a warning
> 
> No warning is necessary. With a large database the cleanup thread may
> run longer than the scheduled interval between threads. This is fine.

OK, I attached the final(?) version of the mysql-write-support patch.
Is there any chance that the patch will make it into a stable Postfix release?

Regards
Stefan

diff -ur postfix-2.7.1.orig/man/man5/mysql_table.5 postfix-2.7.1/man/man5/mysql_table.5
--- postfix-2.7.1.orig/man/man5/mysql_table.5	2008-07-21 13:50:13.000000000 +0200
+++ postfix-2.7.1/man/man5/mysql_table.5	2010-12-11 20:39:20.000000000 +0100
@@ -126,6 +126,15 @@
 .nf
     dbname = customer_database
 .fi
+.IP "\fBcache_tblname\fR"
+The name of the cache table. Postfix will perform cache cleanups
+on this table. Example:
+.nf
+    cache_tblname = verify 
+.fi
+.IP
+This parameter is available with Postfix MySQL write support patch.
+
 .IP "\fBquery\fR"
 The SQL query template used to search the database, where \fB%s\fR
 is a substitute for the address Postfix is trying to resolve,
@@ -191,6 +200,139 @@
 parameter is not specified.
 
 NOTE: DO NOT put quotes around the query parameter.
+.IP "\fBinsert\fR"
+The SQL insert template used to insert a input key, value pair into the
+database, where \fB%s\fR is a substitute for the input key and \fB%v\fR
+is a substitute for the value, e.g.
+.nf
+    insert = INSERT verify SET address='%s', data='%v'
+.fi
+
+This parameter supports the following '%' expansions:
+.RS
+.IP "\fB\fB%%\fR\fR"
+This is replaced by a literal '%' character.
+.IP "\fB\fB%s\fR\fR"
+This is replaced by the input key.
+SQL quoting is used to make sure that the input key does not
+add unexpected metacharacters.
+.IP "\fB\fB%v\fR\fR"
+This is replaced by the corresponding value.
+SQL quoting is used to make sure that the value does not
+add unexpected metacharacters.
+.IP "\fB\fB%u\fR\fR"
+When the input key is an address of the form u...@domain, \fB%u\fR
+is replaced by the SQL quoted local part of the address.
+Otherwise, \fB%u\fR is replaced by the entire search string.
+If the localpart is empty, the insert is suppressed and returns
+no results.
+.IP "\fB\fB%d\fR\fR"
+When the input key is an address of the form u...@domain, \fB%d\fR
+is replaced by the SQL quoted domain part of the address.
+Otherwise, the insert is suppressed and returns no results.
+.IP "\fB\fB%[SVUD]\fR\fR"
+The upper-case equivalents of the above expansions behave in the
+\fBinsert\fR parameter identically to their lower-case counter-parts.
+.IP "\fB\fB%[1-9]\fR\fR"
+The patterns %1, %2, ... %9 are replaced by the corresponding
+most significant component of the input key's domain. If the
+input key is \fiu...@mail.example.com\fr, then %1 is \fBcom\fR,
+%2 is \fBexample\fR and %3 is \fBmail\fR. If the input key is
+unqualified or does not have enough domain components to satisfy
+all the specified patterns, the insert is suppressed and returns
+no results.
+.RE
+.IP
+This parameter is available with Postfix MySQL write support patch.
+
+NOTE: DO NOT put quotes around the query parameter.
+.IP "\fBupdate\fR"
+The SQL update template used to update a input key, value pair in the
+database, where \fB%s\fR is a substitute for the input key and \fB%v\fR
+is a substitute for the value, e.g.
+.nf
+    update = UPDATE verify SET data='%v' WHERE address='%s'
+.fi
+
+This parameter supports the following '%' expansions:
+.RS
+.IP "\fB\fB%%\fR\fR"
+This is replaced by a literal '%' character.
+.IP "\fB\fB%s\fR\fR"
+This is replaced by the input key.
+SQL quoting is used to make sure that the input key does not
+add unexpected metacharacters.
+.IP "\fB\fB%v\fR\fR"
+This is replaced by the corresponding value.
+SQL quoting is used to make sure that the value does not
+add unexpected metacharacters.
+.IP "\fB\fB%u\fR\fR"
+When the input key is an address of the form u...@domain, \fB%u\fR
+is replaced by the SQL quoted local part of the address.
+Otherwise, \fB%u\fR is replaced by the entire search string.
+If the localpart is empty, the update is suppressed and returns
+no results.
+.IP "\fB\fB%d\fR\fR"
+When the input key is an address of the form u...@domain, \fB%d\fR
+is replaced by the SQL quoted domain part of the address.
+Otherwise, the update is suppressed and returns no results.
+.IP "\fB\fB%[SVUD]\fR\fR"
+The upper-case equivalents of the above expansions behave in the
+\fBupdate\fR parameter identically to their lower-case counter-parts.
+.IP "\fB\fB%[1-9]\fR\fR"
+The patterns %1, %2, ... %9 are replaced by the corresponding
+most significant component of the input key's domain. If the
+input key is \fiu...@mail.example.com\fr, then %1 is \fBcom\fR,
+%2 is \fBexample\fR and %3 is \fBmail\fR. If the input key is
+unqualified or does not have enough domain components to satisfy
+all the specified patterns, the update is suppressed and returns
+no results.
+.RE
+.IP
+This parameter is available with Postfix MySQL write support patch.
+
+NOTE: DO NOT put quotes around the update parameter.
+.IP "\fBdelete\fR"
+The SQL delete template used to delete a input key from the database,
+where \fB%s\fR is a substitute for the input key, e.g.
+.nf
+    delete = DELETE FROM verify WHERE address='%s'
+.fi
+
+This parameter supports the following '%' expansions:
+.RS
+.IP "\fB\fB%%\fR\fR"
+This is replaced by a literal '%' character.
+.IP "\fB\fB%s\fR\fR"
+This is replaced by the input key.
+SQL quoting is used to make sure that the input key does not
+add unexpected metacharacters.
+.IP "\fB\fB%u\fR\fR"
+When the input key is an address of the form u...@domain, \fB%u\fR
+is replaced by the SQL quoted local part of the address.
+Otherwise, \fB%u\fR is replaced by the entire search string.
+If the localpart is empty, the delete is suppressed and returns
+no results.
+.IP "\fB\fB%d\fR\fR"
+When the input key is an address of the form u...@domain, \fB%d\fR
+is replaced by the SQL quoted domain part of the address.
+Otherwise, the delete is suppressed and returns no results.
+.IP "\fB\fB%[SVUD]\fR\fR"
+The upper-case equivalents of the above expansions behave in the
+\fBdelete\fR parameter identically to their lower-case counter-parts.
+.IP "\fB\fB%[1-9]\fR\fR"
+The patterns %1, %2, ... %9 are replaced by the corresponding
+most significant component of the input key's domain. If the
+input key is \fiu...@mail.example.com\fr, then %1 is \fBcom\fR,
+%2 is \fBexample\fR and %3 is \fBmail\fR. If the input key is
+unqualified or does not have enough domain components to satisfy
+all the specified patterns, the delete is suppressed and returns
+no results.
+.RE
+.IP
+This parameter is available with Postfix MySQL write support patch.
+
+NOTE: DO NOT put quotes around the delete parameter.
 .IP "\fBresult_format (default: \fB%s\fR)\fR"
 Format template applied to result attributes. Most commonly used
 to append (or prepend) text to the result. This parameter supports
@@ -343,3 +485,6 @@
 Institute of Mathematics of the Romanian Academy
 P.O. BOX 1-764
 RO-014700 Bucharest, ROMANIA
+
+MySQL write support by:
+Stefan Jakobs
diff -ur postfix-2.7.1.orig/src/global/db_common.c postfix-2.7.1/src/global/db_common.c
--- postfix-2.7.1.orig/src/global/db_common.c	2009-10-05 22:33:16.000000000 +0200
+++ postfix-2.7.1/src/global/db_common.c	2010-10-21 17:48:32.000000000 +0200
@@ -23,6 +23,15 @@
 /*	VSTRING	*buf;
 /*	void	(*quote_func)(DICT *, const char *, VSTRING *);
 /*
+/*	int	db_common_expand2(ctx, format, value, data, key, buf, quote_func);
+/*	void	*ctx;
+/*	const char *format;
+/*	const char *value;
+/*	const char *data;
+/*	const char *key;
+/*	VSTRING	*buf;
+/*	void	(*quote_func)(DICT *, const char *, VSTRING *);
+/*
 /*	int	db_common_check_domain(domain_list, addr);
 /*	STRING_LIST *domain_list;
 /*	const char *addr;
@@ -66,6 +75,8 @@
 /*	A literal percent character.
 /* .IP %s
 /*	The entire lookup key \fIaddr\fR.
+/* .IP %v
+/*	The corresponding data to the lookup key \fIaddr\fR.
 /* .IP %u
 /*	If \fBaddr\fR is a fully qualified address, the local part of the
 /*	address.  Otherwise \fIaddr\fR.
@@ -77,6 +88,8 @@
 /*	The following '%' expansions are performed on the lookup \fBkey\fR:
 /* .IP %S
 /*	The entire lookup key \fIkey\fR.
+/* .IP %V
+/*	The corresponding data to the lookup key \fIaddr\fR.
 /* .IP %U
 /*	If \fBkey\fR is a fully qualified address, the local part of the
 /*	address.  Otherwise \fIkey\fR.
@@ -286,6 +299,15 @@
 			         const char *key, VSTRING *result,
 			         db_quote_callback_t quote_func)
 {
+    return db_common_expand2(ctxArg, format, value, NULL, key, result, quote_func);
+}
+
+/* db_common_expand2 - expand query and result templates */
+
+int     db_common_expand2(void *ctxArg, const char *format, const char *value,
+			         const char *data, const char *key, VSTRING *result,
+			         db_quote_callback_t quote_func)
+{
     const char *myname = "db_common_expand";
     DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxArg;
     const char *vdomain = 0;
@@ -382,7 +404,8 @@
     } while (0)
 
     /*
-     * Replace all instances of %s with the address to look up. Replace %u
+     * Replace all instances of %s with the address to look up. Replace %v
+     * with the data value portion. Replace %u
      * with the user portion, and %d with the domain portion. "%%" expands to
      * "%".  lowercase -> addr, uppercase -> key
      */
@@ -398,6 +421,16 @@
 		QUOTE_VAL(ctx->dict, quote_func, value, result);
 		break;
 
+	    case 'v':
+                /* Don't silenty skip empty query string */
+                if (*data == 0) {
+	            msg_warn("table \"%s:%s\": empty query string"
+		             " -- ignored", ctx->dict->type, ctx->dict->name);
+	            return (0);
+                }
+		QUOTE_VAL(ctx->dict, quote_func, data, result);
+		break;
+
 	    case 'u':
 		if (vdomain) {
 		    if (vuser == 0)
@@ -424,6 +457,18 @@
 		    QUOTE_VAL(ctx->dict, quote_func, value, result);
 		break;
 
+            case 'V':
+                if (! key) {
+                    /* Don't silenty skip empty query string */
+                    if (*data == 0) {
+	                msg_warn("table \"%s:%s\": empty query string"
+		                 " -- ignored", ctx->dict->type, ctx->dict->name);
+        	            return (0);
+                    }
+                    QUOTE_VAL(ctx->dict, quote_func, data, result);
+                }
+                break;
+
 	    case 'U':
 		if (key) {
 		    if (kdomain) {
diff -ur postfix-2.7.1.orig/src/global/db_common.h postfix-2.7.1/src/global/db_common.h
--- postfix-2.7.1.orig/src/global/db_common.h	2005-09-23 01:50:50.000000000 +0200
+++ postfix-2.7.1/src/global/db_common.h	2010-10-21 17:47:24.000000000 +0200
@@ -25,6 +25,8 @@
 extern int db_common_dict_partial(void *);
 extern int db_common_expand(void *, const char *, const char *,
 			    const char *, VSTRING *, db_quote_callback_t);
+extern int db_common_expand2(void *, const char *, const char *, const char *,
+			    const char *, VSTRING *, db_quote_callback_t);
 extern int db_common_check_domain(void *, const char *);
 extern void db_common_free_ctx(void *);
 extern void db_common_sql_build_query(VSTRING *query, CFG_PARSER *parser);
diff -ur postfix-2.7.1.orig/src/global/dict_mysql.c postfix-2.7.1/src/global/dict_mysql.c
--- postfix-2.7.1.orig/src/global/dict_mysql.c	2007-01-04 21:07:38.000000000 +0100
+++ postfix-2.7.1/src/global/dict_mysql.c	2010-12-17 17:46:47.000000000 +0100
@@ -42,7 +42,7 @@
 /* .IP other_name
 /*	reference for outside use.
 /* .IP open_flags
-/*	Must be O_RDONLY.
+/*	See open(2). Must be O_RDWR for write access.
 /* .IP dict_flags
 /*	See dict_open(3).
 /* .PP
@@ -57,6 +57,8 @@
 /*	Password for the above.
 /* .IP \fIdbname\fR
 /*	Name of the database.
+/* .IP \fIcache_tblname\fR
+/*	Name of the cache table. Used for automatic table cleanups.
 /* .IP \fIdomain\fR
 /*      List of domains the queries should be restricted to.  If
 /*      specified, only FQDN addresses whose domain parts matching this
@@ -69,6 +71,18 @@
 /*	No query is specified, the legacy variables \fItable\fR,
 /*	\fIselect_field\fR, \fIwhere_field\fR and \fIadditional_conditions\fR
 /*	are used to construct the query template.
+/* .IP \fIinsert\fR
+/*      Insert template, before the query is actually issued, variable
+/*	substitutions are performed. See mysql_table(5) for details. 
+/*	Legacy variables are not available for this query.
+/* .IP \fIupdate\fR
+/*      Update template, before the query is actually issued, variable
+/*	substitutions are performed. See mysql_table(5) for details. 
+/*	Legacy variables are not available for this query.
+/* .IP \fIdelete\fR
+/*      Delete template, before the query is actually issued, variable
+/*	substitutions are performed. See mysql_table(5) for details. 
+/*	Legacy variables are not available for this query.
 /* .IP \fIresult_format\fR
 /*      The format used to expand results from queries.  Substitutions
 /*      are performed as described in mysql_table(5). Defaults to returning
@@ -220,12 +234,17 @@
     DICT    dict;
     CFG_PARSER *parser;
     char   *query;
+    char   *insert;
+    char   *update;
+    char   *delete;
     char   *result_format;
     void   *ctx;
     int     expansion_limit;
     char   *username;
     char   *password;
     char   *dbname;
+    char   *cache_tblname;
+    int     handler_open;	/* semaphore */
     ARGV   *hosts;
     PLMYSQL *pldb;
 #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
@@ -248,6 +267,10 @@
 static PLMYSQL *plmysql_init(ARGV *);
 static MYSQL_RES *plmysql_query(DICT_MYSQL *, const char *, VSTRING *, char *,
 				char *, char *);
+static int plmysql_update(DICT_MYSQL *, const char *, const char *,
+			 const char *,VSTRING *, char *, char *, char *);
+static MYSQL_RES *plmysql_sequence(DICT_MYSQL *, int, VSTRING *, char *,
+			 	char *, char *);
 static void plmysql_dealloc(PLMYSQL *);
 static void plmysql_close_host(HOST *);
 static void plmysql_down_host(HOST *);
@@ -258,6 +281,13 @@
 static void mysql_parse_config(DICT_MYSQL *, const char *);
 static HOST *host_init(const char *);
 
+#define INIT_VSTR(buf, len) do { \
+	if (buf == 0) \
+	    buf = vstring_alloc(len); \
+	VSTRING_RESET(buf); \
+	VSTRING_TERMINATE(buf); \
+    } while (0)
+
 /* dict_mysql_quote - escape SQL metacharacters in input string */
 
 static void dict_mysql_quote(DICT *dict, const char *name, VSTRING *result)
@@ -326,13 +356,6 @@
         return (0);
     }
 
-#define INIT_VSTR(buf, len) do { \
-	if (buf == 0) \
-	    buf = vstring_alloc(len); \
-	VSTRING_RESET(buf); \
-	VSTRING_TERMINATE(buf); \
-    } while (0)
-
     INIT_VSTR(query, 10);
 
     /*
@@ -388,6 +411,261 @@
     return ((dict_errno == 0 && *r) ? r : 0);
 }
 
+/* dict_mysql_update - update/insert database entry */
+
+static void dict_mysql_update(DICT *dict, const char *name, const char *val)
+{
+    const char *myname = "dict_mysql_update";
+    DICT_MYSQL *dict_mysql = (DICT_MYSQL *)dict;
+    MYSQL_RES *query_res;
+    static VSTRING *query;
+    int     numrows;
+    db_quote_callback_t quote_func = dict_mysql_quote;
+
+    dict_errno = 0;
+
+    /*
+     * Optionally fold the key.
+     */
+    if (dict->flags & DICT_FLAG_FOLD_FIX) {
+	if (dict->fold_buf == 0)
+	    dict->fold_buf = vstring_alloc(10);
+	vstring_strcpy(dict->fold_buf, name);
+	name = lowercase(vstring_str(dict->fold_buf));
+    }
+
+    INIT_VSTR(query, 10);
+
+    /*
+     * Suppress the lookup if the query expansion is empty
+     *
+     * This initial expansion is outside the context of any
+     * specific host connection, we just want to check the
+     * key pre-requisites, so when quoting happens separately
+     * for each connection, we don't bother with quoting...
+     */
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+    quote_func = 0;
+#endif
+    if (!db_common_expand(dict_mysql->ctx, dict_mysql->query,
+    			  name, 0, query, quote_func))
+        return;
+    
+    /* check if name exists already */
+    /* do the query - set dict_errno & cleanup if there's an error */
+    if ((query_res = plmysql_query(dict_mysql, name, query,
+				   dict_mysql->dbname,
+				   dict_mysql->username,
+				   dict_mysql->password)) == 0) {
+	dict_errno = DICT_ERR_RETRY;
+	return;
+    }
+
+    numrows = mysql_num_rows(query_res);
+    mysql_free_result(query_res);
+    if (msg_verbose)
+	msg_info("%s: retrieved %d rows", myname, numrows);
+    if (numrows == 0) {		/* do insert */
+        if (!db_common_expand2(dict_mysql->ctx, dict_mysql->insert,
+    			  name, val, 0, query, quote_func))
+            return;
+
+        if ((numrows = plmysql_update(dict_mysql, name, val, dict_mysql->insert, query,
+			        dict_mysql->dbname,
+			        dict_mysql->username,
+			        dict_mysql->password)) == 0) {
+	    dict_errno = DICT_ERR_RETRY;
+	    return;
+	}
+
+    } else {			/* do update */
+        if (!db_common_expand2(dict_mysql->ctx, dict_mysql->update,
+    			  name, val, 0, query, quote_func))
+            return;
+
+        if ((numrows = plmysql_update(dict_mysql, name, val, dict_mysql->update, query,
+			        dict_mysql->dbname,
+			        dict_mysql->username,
+			        dict_mysql->password)) == 0) {
+	    dict_errno = DICT_ERR_RETRY;
+	    return;
+	}
+
+    }
+    if (msg_verbose)
+	msg_info("%s: updated %d rows", myname, numrows);
+}
+
+/* dict_mysql_delete - delete database entry */
+
+static int dict_mysql_delete(DICT *dict, const char *name)
+{
+    const char *myname = "dict_mysql_delete";
+    DICT_MYSQL *dict_mysql = (DICT_MYSQL *)dict;
+    static VSTRING *query;
+    int     numrows;
+    db_quote_callback_t quote_func = dict_mysql_quote;
+
+    dict_errno = 0;
+
+    /*
+     * Optionally fold the key.
+     */
+    if (dict->flags & DICT_FLAG_FOLD_FIX) {
+	if (dict->fold_buf == 0)
+	    dict->fold_buf = vstring_alloc(10);
+	vstring_strcpy(dict->fold_buf, name);
+	name = lowercase(vstring_str(dict->fold_buf));
+    }
+
+    INIT_VSTR(query, 10);
+
+    /*
+     * Suppress the lookup if the query expansion is empty
+     *
+     * This initial expansion is outside the context of any
+     * specific host connection, we just want to check the
+     * key pre-requisites, so when quoting happens separately
+     * for each connection, we don't bother with quoting...
+     */
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+    quote_func = 0;
+#endif
+    if (!db_common_expand(dict_mysql->ctx, dict_mysql->delete,
+    			  name, 0, query, quote_func))
+        return (1);
+    
+    /* delete - set dict_errno & cleanup on error */
+    if ((numrows = plmysql_update(dict_mysql, name, NULL, dict_mysql->delete, query,
+				   dict_mysql->dbname,
+				   dict_mysql->username,
+				   dict_mysql->password)) == 0) {
+	dict_errno = DICT_ERR_RETRY;
+	return (1);
+    }
+
+    if (msg_verbose)
+	msg_info("%s: deleted %d rows", myname, numrows);
+    if (numrows == 0) {		/* failure */
+        msg_warn("%s: %s: delete failed, key: '%s'",
+			 myname, dict_mysql->parser->name, name);
+	return (1);
+    } else { 				/* OK */
+	return (0);
+    }
+}
+
+/* dict_mysql_sequence - traverse the db */
+
+static int dict_mysql_sequence(DICT *dict, int function, 
+				const char **key, const char **value)
+{
+    const char *myname = "dict_mysql_sequence";
+    DICT_MYSQL *dict_mysql = (DICT_MYSQL *)dict;
+    MYSQL_RES  *seq_res;
+    MYSQL_ROW row;
+    static VSTRING *result;
+    static VSTRING *name;
+    const char *format = "%s";
+    VSTRING *seq = vstring_alloc(10);
+    int plmysql_errno = 0;
+    dict_errno = 0;
+
+    INIT_VSTR(name, 10);
+    INIT_VSTR(result, 10);
+
+    switch (function) {
+    case DICT_SEQ_FUN_FIRST:
+	if (dict_mysql->handler_open) {
+	    /* free mem and return as finished */ 
+            vstring_free(seq);
+	    if (msg_verbose)
+	        msg_info("%s: handler still open, cleanup skipped", myname);
+	    return (1);
+	} else {
+            /* initialise handler */
+            vstring_sprintf(seq, "HANDLER %s OPEN", dict_mysql->cache_tblname);
+            plmysql_sequence(dict_mysql, plmysql_errno, seq,
+                                dict_mysql->dbname,
+                                dict_mysql->username,
+                                dict_mysql->password);
+	    if (plmysql_errno > 0){
+                dict_errno = DICT_ERR_RETRY;
+                vstring_free(seq);
+                return (1);
+            } 
+	    /* lock */
+	    dict_mysql->handler_open = 1;
+            vstring_sprintf(seq, "HANDLER %s READ FIRST", dict_mysql->cache_tblname);
+	    if (msg_verbose)
+	        msg_info("%s: handler opened", myname);
+        }
+	break;
+    case DICT_SEQ_FUN_NEXT:
+        vstring_sprintf(seq, "HANDLER %s READ NEXT", dict_mysql->cache_tblname);
+	break;
+    default:
+        msg_panic("%s: invalid function %d", myname, function);
+	return (1);
+    }
+
+    if (msg_verbose)
+         msg_info("%s: handler read next", myname);
+    if ((seq_res = plmysql_sequence(dict_mysql, plmysql_errno, seq,
+                                   dict_mysql->dbname,
+        	                   dict_mysql->username,
+                                   dict_mysql->password)) == 0) {
+        dict_errno = DICT_ERR_RETRY;
+        vstring_free(seq);
+	/* unlock */
+	dict_mysql->handler_open = 0;
+        return (1);
+    } 
+    row = mysql_fetch_row(seq_res); 
+    if (row == NULL) {
+	/* close handler */
+        vstring_sprintf(seq, "HANDLER %s CLOSE", dict_mysql->cache_tblname);
+        plmysql_sequence(dict_mysql, plmysql_errno, seq,
+                              dict_mysql->dbname,
+                              dict_mysql->username,
+                              dict_mysql->password);
+	if (plmysql_errno > 0) 
+            dict_errno = DICT_ERR_RETRY;
+	if (msg_verbose)
+	    msg_info("%s: handler closed", myname);
+        vstring_free(seq);
+	/* unlock */
+	dict_mysql->handler_open = 0;
+	return(1);
+    }
+    vstring_free(seq);
+    if (mysql_num_fields(seq_res) >= 2) {
+	if (!db_common_expand(dict_mysql->ctx, format,
+                          row[0], 0, name, 0)) {
+	    /* unlock */
+	    dict_mysql->handler_open = 0;
+	    return (1);
+	}
+        if (!db_common_expand(dict_mysql->ctx, format,
+                          row[1], 0, result, 0)) {
+	    /* unlock */
+	    dict_mysql->handler_open = 0;
+	    return (1);
+	}
+    } else {
+        msg_warn("%s: invalid number of rows in query response: %d.",
+                  myname, mysql_num_fields(seq_res));
+        dict_errno = DICT_ERR_RETRY;
+	/* unlock */
+	dict_mysql->handler_open = 0;
+        return (1);
+    }
+
+    *key = vstring_str(name);
+    *value = vstring_str(result); 
+    return (0); 
+}
+
 /* dict_mysql_check_stat - check the status of a host */
 
 static int dict_mysql_check_stat(HOST *host, unsigned stat, unsigned type,
@@ -486,7 +764,6 @@
  *			on failure of all db instances, return 0;
  *			close unnecessary active connections
  */
-
 static MYSQL_RES *plmysql_query(DICT_MYSQL *dict_mysql,
 				        const char *name,
 					VSTRING *query,
@@ -514,8 +791,8 @@
 #endif
 
 	if (!(mysql_query(host->db, vstring_str(query)))) {
-	    if ((res = mysql_store_result(host->db)) == 0) {
-		msg_warn("mysql query failed: %s", mysql_error(host->db));
+	    if ((res = mysql_store_result(host->db)) == 0) { 
+		msg_warn("dict_mysql: query failed: %s", mysql_error(host->db));
 		plmysql_down_host(host);
 	    } else {
 		if (msg_verbose)
@@ -533,6 +810,101 @@
 }
 
 /*
+ * plmysql_update - process a MySQL update, insert or delete query.
+ *			Return number of affected rows on success.
+ *			On failure, log failure and try other db instances.
+ *			on failure of all db instances, return 0;
+ *			close unnecessary active connections
+ */
+static int plmysql_update(DICT_MYSQL *dict_mysql,
+			        const char *name,
+				const char *val,
+				const char *update,
+				VSTRING *query,
+			        char *dbname,
+			        char *username,
+			        char *password)
+{
+    PLMYSQL *PLDB = dict_mysql->pldb;
+    HOST   *host;
+    int numrows;
+
+    while ((host = dict_mysql_get_active(PLDB, dbname, username, password)) != NULL) {
+
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+	/*
+	 * The active host is used to escape strings in the
+	 * context of the active connection's character encoding.
+	 */
+	dict_mysql->active_host = host;
+	VSTRING_RESET(query);
+	VSTRING_TERMINATE(query);
+	db_common_expand2(dict_mysql->ctx, update,
+			 name, val, 0, query, dict_mysql_quote);
+	dict_mysql->active_host = 0;
+#endif
+
+	if (!(mysql_query(host->db, vstring_str(query)))) {
+	    if ((numrows = mysql_affected_rows(host->db)) == 0) {
+                msg_warn("dict_mysql: update failed: %s", mysql_error(host->db));
+                plmysql_down_host(host);
+            } else {
+                if (msg_verbose)
+                    msg_info("dict_mysql: successful update from host %s", host->hostname);
+                event_request_timer(dict_mysql_event, (char *) host, IDLE_CONN_INTV);
+                break;
+            }
+	} else {
+	    msg_warn("dict_mysql: update failed: %s", mysql_error(host->db));
+	    plmysql_down_host(host);
+	    numrows = 0;
+	}
+    }
+    return (numrows);
+}
+
+/*
+ * plmysql_sequence - process a MySQL sequence query. Return MYSQL_RES* on success.  
+ *         On failure, log failure and try other db instances.
+ *         on failure of all db instances, return 0;
+ *         close unnecessary active connections;
+ */
+static MYSQL_RES *plmysql_sequence(DICT_MYSQL *dict_mysql, int errno,
+					VSTRING *query,
+				        char *dbname,
+				        char *username,
+				        char *password)
+{
+    PLMYSQL *PLDB = dict_mysql->pldb;
+    HOST   *host;
+    MYSQL_RES *res = 0; 
+
+    while ((host = dict_mysql_get_active(PLDB, dbname, username, password)) != NULL) {
+
+	if (!(mysql_query(host->db, vstring_str(query)))) {
+	    /* an empty result set returns 0, too. Use errno to differentiate *
+	     * between an error and an empty result set */
+	    if ((res = mysql_store_result(host->db)) == 0 && 
+		(errno = mysql_errno(host->db)) > 0) { 
+		msg_warn("dict_mysql: faulty sequence result: %s", mysql_error(host->db));
+		plmysql_down_host(host);
+	    } else {
+		if (msg_verbose)
+		    msg_info("dict_mysql: successful sequence query from host %s",
+					 host->hostname);
+		event_request_timer(dict_mysql_event, (char *) host, IDLE_CONN_INTV);
+		break;
+	    }
+	} else {
+	    msg_warn("dict_mysql: sequence query failed: %s", mysql_error(host->db));
+	    plmysql_down_host(host);
+	}
+    }
+
+    return res;
+}
+
+/*
  * plmysql_connect_single -
  * used to reconnect to a single database when one is down or none is
  * connected yet. Log all errors and set the stat field of host accordingly
@@ -595,6 +967,7 @@
     dict_mysql->username = cfg_get_str(p, "user", "", 0, 0);
     dict_mysql->password = cfg_get_str(p, "password", "", 0, 0);
     dict_mysql->dbname = cfg_get_str(p, "dbname", "", 1, 0);
+    dict_mysql->cache_tblname = cfg_get_str(p, "cache_tblname", "", 0, 0);
     dict_mysql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0);
     /*
      * XXX: The default should be non-zero for safety, but that is not
@@ -613,6 +986,10 @@
 	dict_mysql->query = vstring_export(buf);
     }
 
+    dict_mysql->insert = cfg_get_str(p, "insert", NULL, 0, 0);
+    dict_mysql->update = cfg_get_str(p, "update", NULL, 0, 0);
+    dict_mysql->delete = cfg_get_str(p, "delete", NULL, 0, 0);
+
     /*
      * Must parse all templates before we can use db_common_expand()
      */
@@ -654,14 +1031,17 @@
 
     /*
      * Sanity checks.
-     */
+     *
     if (open_flags != O_RDONLY)
 	msg_fatal("%s:%s map requires O_RDONLY access mode",
 		  DICT_TYPE_MYSQL, name);
-
+    */
     dict_mysql = (DICT_MYSQL *) dict_alloc(DICT_TYPE_MYSQL, name,
 					   sizeof(DICT_MYSQL));
     dict_mysql->dict.lookup = dict_mysql_lookup;
+    dict_mysql->dict.update = dict_mysql_update;
+    dict_mysql->dict.delete = dict_mysql_delete;
+    dict_mysql->dict.sequence = dict_mysql_sequence;
     dict_mysql->dict.close = dict_mysql_close;
     dict_mysql->dict.flags = dict_flags;
     mysql_parse_config(dict_mysql, name);
@@ -669,6 +1049,8 @@
     dict_mysql->active_host = 0;
 #endif
     dict_mysql->pldb = plmysql_init(dict_mysql->hosts);
+    /* initialise semaphore */
+    dict_mysql->handler_open = 0;
     if (dict_mysql->pldb == NULL)
 	msg_fatal("couldn't intialize pldb!\n");
     return (DICT_DEBUG (&dict_mysql->dict));

Reply via email to