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 \[email protected]\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 \[email protected]\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 \[email protected]\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));