Răsfoiți Sursa

Merge commit 'ser_mysql/master' into mysql

* commit 'ser_mysql/master': (138 commits)
  - fixed a crash in mysql_stmt_fetch when the statement does not exist
  - mysql versions < 5.0 do not support MYSQL_DATA_TRUNCATED
  - buffer size increased to 1024
  - put doxygen docs in correct group
  - support for 'last_id' getopt, which retrieves the unique id of the last
  - Support for fetching all data to the client at once
  - check_result_columns renamed to check_result
  - better check of the return value of mysql_stmt_result_metadata
  - reordered included header files for better detection of missing headers
  added non equal operator to DB API and mysql module
  - a couple of minor bug fixes backported from internal verion
  - dispose driver-specific driver properly on error
  - re-submit compiled queries to the server upon reconnect
  added doxygen comment to deal with these files
  updated update_params() to work with 3 sets of params
  added common prefix 'mysql:' into INFOs
  more descriptive comments
  removed forgotten abort() from error handler.
  added doxygen comment
  Preparation for implementation of UPDATE db statement.
  ...
Jan Janak 16 ani în urmă
părinte
comite
81e893341f

+ 22 - 0
modules/db_mysql/Makefile

@@ -0,0 +1,22 @@
+# $Id$
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=mysql.so
+
+# mysql.h locations (freebsd,openbsd  solaris)
+DEFS +=-I$(LOCALBASE)/include -I$(LOCALBASE)/include/mysql \
+		-I$(LOCALBASE)/mysql/include \
+		-I/usr/include/mysql
+
+# libmysqlclient locations on RH/Suse, Solaris /OpenBSD, FreeBSD
+# (Debian does the right thing and puts it in /usr/lib)
+LIBS=-L/usr/lib/mysql -L$(LOCALBASE)/lib -L$(LOCALBASE)/lib/mysql \
+		-L$(LOCALBASE)/mysql/lib/mysql/ \
+		-L$(LOCALBASE)/mysql/lib \
+		-L/usr/lib64/mysql \
+		-lmysqlclient -lz
+
+include ../../Makefile.modules

BIN
modules/db_mysql/doc/mysql_parser.dia


+ 1341 - 0
modules/db_mysql/my_cmd.c

@@ -0,0 +1,1341 @@
+/* 
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2006-2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/** @addtogroup mysql
+ *  @{
+ */
+
+#define _XOPEN_SOURCE 4     /* bsd */
+#define _XOPEN_SOURCE_EXTENDED 1    /* solaris */
+#define _SVID_SOURCE 1 /* timegm */
+
+#include "my_cmd.h"
+
+#include "my_con.h"
+#include "mysql_mod.h"
+#include "my_fld.h"
+
+#include "../../mem/mem.h"
+#include "../../str.h"
+#include "../../db/db_cmd.h"
+#include "../../ut.h"
+
+#include <strings.h>
+#include <stdio.h>
+#include <time.h>  /*strptime, XOPEN issue must be >=4 */
+#include <string.h>
+#include <mysql/errmsg.h>
+#include <mysql/mysqld_error.h>
+
+#define STR_BUF_SIZE 1024
+
+#ifdef MYSQL_FAKE_NULL
+
+#define FAKE_NULL_STRING "[~NULL~]"
+static str  FAKE_NULL_STR = STR_STATIC_INIT(FAKE_NULL_STRING);
+
+/* avoid warning: this decimal constant is unsigned only in ISO C90 :-) */
+#define FAKE_NULL_INT (-2147483647 - 1)
+#endif
+
+enum {
+	STR_DELETE,
+	STR_INSERT,
+	STR_UPDATE,
+	STR_SELECT,
+	STR_REPLACE,
+	STR_SET,
+	STR_WHERE,
+	STR_IS,
+	STR_AND,
+	STR_OR,
+	STR_ESC,
+	STR_OP_EQ,
+	STR_OP_NE,
+	STR_OP_LT,
+	STR_OP_GT,
+	STR_OP_LEQ,
+	STR_OP_GEQ,
+	STR_VALUES,
+	STR_FROM
+};
+
+static str strings[] = {
+	STR_STATIC_INIT("delete from "),
+	STR_STATIC_INIT("insert into "),
+	STR_STATIC_INIT("update "),
+	STR_STATIC_INIT("select "),
+	STR_STATIC_INIT("replace "),
+	STR_STATIC_INIT(" set "),
+	STR_STATIC_INIT(" where "),
+	STR_STATIC_INIT(" is "),
+	STR_STATIC_INIT(" and "),
+	STR_STATIC_INIT(" or "),
+	STR_STATIC_INIT("?"),
+	STR_STATIC_INIT("="),
+	STR_STATIC_INIT("!="),
+	STR_STATIC_INIT("<"),
+	STR_STATIC_INIT(">"),
+	STR_STATIC_INIT("<="),
+	STR_STATIC_INIT(">="),
+	STR_STATIC_INIT(") values ("),
+	STR_STATIC_INIT(" from ")
+};
+
+
+#define APPEND_STR(p, str) do {		 \
+	memcpy((p), (str).s, (str).len); \
+	(p) += (str).len;				 \
+} while(0)
+
+
+#define APPEND_CSTR(p, cstr) do { \
+    int _len = strlen(cstr);      \
+	memcpy((p), (cstr), _len);	  \
+	(p) += _len;				  \
+} while(0)
+
+
+static int upload_cmd(db_cmd_t* cmd);
+
+
+static void my_cmd_free(db_cmd_t* cmd, struct my_cmd* payload)
+{
+	db_drv_free(&payload->gen);
+	if (payload->sql_cmd.s) pkg_free(payload->sql_cmd.s);
+	if (payload->st) mysql_stmt_close(payload->st);
+	pkg_free(payload);
+}
+
+
+/** Builds a DELETE SQL statement.The function builds DELETE statement where
+ * cmd->match specify WHERE clause.  
+ * @param cmd SQL statement as a result of this function 
+ * @param cmd input for statement creation
+ */
+static int build_delete_cmd(str* sql_cmd, db_cmd_t* cmd)
+{
+	db_fld_t* fld;
+	int i;
+	char* p;
+
+	sql_cmd->len = strings[STR_DELETE].len;
+	sql_cmd->len += cmd->table.len;
+
+	if (!DB_FLD_EMPTY(cmd->match)) {
+		sql_cmd->len += strings[STR_WHERE].len;
+
+		for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
+			sql_cmd->len += strlen(fld[i].name);
+
+			switch(fld[i].op) {
+			case DB_EQ:  sql_cmd->len += strings[STR_OP_EQ].len; break;
+			case DB_NE:  sql_cmd->len += strings[STR_OP_NE].len; break;
+			case DB_LT:  sql_cmd->len += strings[STR_OP_LT].len; break;
+			case DB_GT:  sql_cmd->len += strings[STR_OP_GT].len; break;
+			case DB_LEQ: sql_cmd->len += strings[STR_OP_LEQ].len; break;
+			case DB_GEQ: sql_cmd->len += strings[STR_OP_GEQ].len; break;
+			default:
+				ERR("mysql: Unsupported db_fld operator %d\n", fld[i].op);
+				return -1;
+			}
+
+			sql_cmd->len += strings[STR_ESC].len;
+			
+			if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += strings[STR_AND].len;
+		}
+	}
+
+	sql_cmd->s = pkg_malloc(sql_cmd->len + 1);
+	if (sql_cmd->s == NULL) {
+		ERR("mysql: No memory left\n");
+		return -1;
+	}
+	p = sql_cmd->s;
+	
+	APPEND_STR(p, strings[STR_DELETE]);
+	APPEND_STR(p, cmd->table);
+
+	if (!DB_FLD_EMPTY(cmd->match)) {
+		APPEND_STR(p, strings[STR_WHERE]);
+
+		for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
+			APPEND_CSTR(p, fld[i].name);
+
+			switch(fld[i].op) {
+			case DB_EQ:  APPEND_STR(p, strings[STR_OP_EQ]);  break;
+			case DB_NE:  APPEND_STR(p, strings[STR_OP_NE]);  break;
+			case DB_LT:  APPEND_STR(p, strings[STR_OP_LT]);  break;
+			case DB_GT:  APPEND_STR(p, strings[STR_OP_GT]);  break;
+			case DB_LEQ: APPEND_STR(p, strings[STR_OP_LEQ]); break;
+			case DB_GEQ: APPEND_STR(p, strings[STR_OP_GEQ]); break;
+			}
+			
+			APPEND_STR(p, strings[STR_ESC]);
+			if (!DB_FLD_LAST(fld[i + 1])) APPEND_STR(p, strings[STR_AND]);
+		}
+	}
+			
+	*p = '\0';
+	return 0;
+}
+
+
+/**
+ *  Builds SELECT statement where cmd->values specify column names
+ *  and cmd->match specify WHERE clause.
+ * @param sql_cmd SQL statement as a result of this function
+ * @param cmd     input for statement creation
+ */
+static int build_select_cmd(str* sql_cmd, db_cmd_t* cmd)
+{
+	db_fld_t* fld;
+	int i;
+	char* p;
+
+	sql_cmd->len = strings[STR_SELECT].len;
+
+	if (DB_FLD_EMPTY(cmd->result)) {
+		sql_cmd->len += 1; /* "*" */
+	} else {
+		for(i = 0, fld = cmd->result; !DB_FLD_LAST(fld[i]); i++) {
+			sql_cmd->len += strlen(fld[i].name);
+			if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += 1; /* , */
+		}
+	}
+	sql_cmd->len += strings[STR_FROM].len;
+	sql_cmd->len += cmd->table.len;
+
+	if (!DB_FLD_EMPTY(cmd->match)) {
+		sql_cmd->len += strings[STR_WHERE].len;
+
+		for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
+			sql_cmd->len += strlen(fld[i].name);
+
+			switch(fld[i].op) {
+			case DB_EQ:  sql_cmd->len += strings[STR_OP_EQ].len; break;
+			case DB_NE:  sql_cmd->len += strings[STR_OP_NE].len; break;
+			case DB_LT:  sql_cmd->len += strings[STR_OP_LT].len; break;
+			case DB_GT:  sql_cmd->len += strings[STR_OP_GT].len; break;
+			case DB_LEQ: sql_cmd->len += strings[STR_OP_LEQ].len; break;
+			case DB_GEQ: sql_cmd->len += strings[STR_OP_GEQ].len; break;
+			default:
+				ERR("mysql: Unsupported db_fld operator %d\n", fld[i].op);
+				return -1;
+			}
+
+			sql_cmd->len += strings[STR_ESC].len;
+			
+			if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += strings[STR_AND].len;
+		}
+	}
+
+	sql_cmd->s = pkg_malloc(sql_cmd->len + 1);
+	if (sql_cmd->s == NULL) {
+		ERR("mysql: No memory left\n");
+		return -1;
+	}
+	p = sql_cmd->s;
+	
+	APPEND_STR(p, strings[STR_SELECT]);
+	if (DB_FLD_EMPTY(cmd->result)) {
+		*p++ = '*';
+	} else {
+		for(i = 0, fld = cmd->result; !DB_FLD_LAST(fld[i]); i++) {
+			APPEND_CSTR(p, fld[i].name);
+			if (!DB_FLD_LAST(fld[i + 1])) *p++ = ',';
+		}
+	}
+	APPEND_STR(p, strings[STR_FROM]);
+	APPEND_STR(p, cmd->table);
+
+	if (!DB_FLD_EMPTY(cmd->match)) {
+		APPEND_STR(p, strings[STR_WHERE]);
+
+		for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
+			APPEND_CSTR(p, fld[i].name);
+
+			switch(fld[i].op) {
+			case DB_EQ:  APPEND_STR(p, strings[STR_OP_EQ]);  break;
+			case DB_NE:  APPEND_STR(p, strings[STR_OP_NE]);  break;
+			case DB_LT:  APPEND_STR(p, strings[STR_OP_LT]);  break;
+			case DB_GT:  APPEND_STR(p, strings[STR_OP_GT]);  break;
+			case DB_LEQ: APPEND_STR(p, strings[STR_OP_LEQ]); break;
+			case DB_GEQ: APPEND_STR(p, strings[STR_OP_GEQ]); break;
+			}
+			
+			APPEND_STR(p, strings[STR_ESC]);
+			if (!DB_FLD_LAST(fld[i + 1])) APPEND_STR(p, strings[STR_AND]);
+		}
+	}
+
+	*p = '\0';
+	return 0;
+}
+
+
+/**
+ *  Builds REPLACE statement where cmd->values specify column names.
+ * @param sql_cmd SQL statement as a result of this function
+ * @param cmd     input for statement creation
+ */
+static int build_replace_cmd(str* sql_cmd, db_cmd_t* cmd)
+{
+	db_fld_t* fld;
+	int i;
+	char* p;
+
+	sql_cmd->len = strings[STR_REPLACE].len;
+	sql_cmd->len += cmd->table.len;
+	sql_cmd->len += 2; /* " (" */
+
+	for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
+		sql_cmd->len += strlen(fld[i].name);
+		sql_cmd->len += strings[STR_ESC].len;
+		if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += 2; /* , twice */
+	}
+	sql_cmd->len += strings[STR_VALUES].len;
+    sql_cmd->len += 1; /* ) */
+
+	sql_cmd->s = pkg_malloc(sql_cmd->len + 1);
+	if (sql_cmd->s == NULL) {
+		ERR("mysql: No memory left\n");
+		return -1;
+	}
+	p = sql_cmd->s;
+	
+	APPEND_STR(p, strings[STR_REPLACE]);
+	APPEND_STR(p, cmd->table);
+	*p++ = ' ';
+	*p++ = '(';
+
+	for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
+		APPEND_CSTR(p, fld[i].name);
+		if (!DB_FLD_LAST(fld[i + 1])) *p++ = ',';
+	}
+	APPEND_STR(p, strings[STR_VALUES]);
+
+	for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
+		APPEND_STR(p, strings[STR_ESC]);
+		if (!DB_FLD_LAST(fld[i + 1])) *p++ = ',';
+	}
+	*p++ = ')';
+	*p = '\0';
+	return 0;
+}
+
+
+/**
+ *  Reallocatable string buffer.
+ */
+struct string_buffer {
+	char *s;			/**< allocated memory itself */
+	int   len;			/**< used memory */
+	int   size;			/**< total size of allocated memory */
+	int   increment;	/**< increment when realloc is necessary */ 
+};
+
+
+/**
+ *  Add new string into string buffer.
+ * @param sb    string buffer
+ * @param nstr  string to add
+ * @return      0 if OK, -1 if failed
+ */
+static inline int sb_add(struct string_buffer *sb, str *nstr)
+{
+	int new_size = 0;
+	int rsize = sb->len + nstr->len;
+	int asize;
+	char *newp;
+	
+	if ( rsize > sb->size ) {
+		asize = rsize - sb->size;
+		new_size = sb->size + (asize / sb->increment  + (asize % sb->increment > 0)) * sb->increment;
+		newp = pkg_malloc(new_size);
+		if (!newp) {
+			ERR("mysql: No memory left\n");
+			return -1;
+		}
+		if (sb->s) {
+			memcpy(newp, sb->s, sb->len);
+			pkg_free(sb->s);
+		}
+		sb->s = newp;
+		sb->size = new_size;
+	}
+	memcpy(sb->s + sb->len, nstr->s, nstr->len);
+	sb->len += nstr->len;
+	return 0;
+}
+
+
+/**
+ *  Set members of str variable.
+ *  Used for temporary str variables. 
+ */
+static inline str* set_str(str *str, const char *s)
+{
+	str->s = (char *)s;
+	str->len = strlen(s);
+	return str;
+}
+
+
+/**
+ *  Builds UPDATE statement where cmd->valss specify column name-value pairs
+ *  and cmd->match specify WHERE clause.
+ * @param sql_cmd  SQL statement as a result of this function
+ * @param cmd      input for statement creation
+ */
+static int build_update_cmd(str* sql_cmd, db_cmd_t* cmd)
+{
+	struct string_buffer sql_buf = {.s = NULL, .len = 0, .size = 0, .increment = 128};
+	db_fld_t* fld;
+	int i;
+	int rv = 0;
+	str tmpstr;
+
+	rv = sb_add(&sql_buf, &strings[STR_UPDATE]);	/* "UPDATE " */
+	rv |= sb_add(&sql_buf, &cmd->table);			/* table name */
+	rv |= sb_add(&sql_buf, &strings[STR_SET]);		/* " SET " */
+
+	/* column name-value pairs */
+	for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
+		rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
+		rv |= sb_add(&sql_buf, set_str(&tmpstr, " = "));
+		rv |= sb_add(&sql_buf, &strings[STR_ESC]);
+		if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, set_str(&tmpstr, ", "));
+	}
+	if (rv) {
+		goto err;
+	}
+
+	if (!DB_FLD_EMPTY(cmd->match)) {
+		rv |= sb_add(&sql_buf, &strings[STR_WHERE]);
+
+		for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
+			rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
+
+			switch(fld[i].op) {
+			case DB_EQ:  rv |= sb_add(&sql_buf, &strings[STR_OP_EQ]);  break;
+			case DB_NE:  rv |= sb_add(&sql_buf, &strings[STR_OP_NE]);  break;
+			case DB_LT:  rv |= sb_add(&sql_buf, &strings[STR_OP_LT]);  break;
+			case DB_GT:  rv |= sb_add(&sql_buf, &strings[STR_OP_GT]);  break;
+			case DB_LEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_LEQ]); break;
+			case DB_GEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_GEQ]); break;
+			}
+			
+			rv |= sb_add(&sql_buf, &strings[STR_ESC]);
+			if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, &strings[STR_AND]);
+		}
+	}
+	rv |= sb_add(&sql_buf, set_str(&tmpstr, "\0"));
+	if (rv) {
+		goto err;
+	}
+	sql_cmd->s = sql_buf.s;
+	sql_cmd->len = sql_buf.len;
+	return 0;
+
+err:
+	if (sql_buf.s) pkg_free(sql_buf.s);
+	return -1;
+}
+
+
+static inline void update_field(MYSQL_BIND *param, db_fld_t* fld)
+{
+	struct my_fld* fp;      /* field payload */
+	struct tm* t;
+	
+	fp = DB_GET_PAYLOAD(fld);
+
+#ifndef MYSQL_FAKE_NULL
+	fp->is_null = fld->flags & DB_NULL;
+	if (fp->is_null) return;
+#else
+	if (fld->flags & DB_NULL) {
+		switch(fld->type) {
+		case DB_STR:
+		case DB_CSTR:
+			param->buffer = FAKE_NULL_STR.s;
+			fp->length = FAKE_NULL_STR.len;
+			break;
+		case DB_INT:
+			*(int*)param->buffer = FAKE_NULL_INT;
+			break;
+		case DB_BLOB:
+		case DB_DATETIME:
+		case DB_NONE:
+		case DB_FLOAT:
+		case DB_DOUBLE:
+		case DB_BITMAP:
+			/* we don't have fake null value for these types */
+			fp->is_null = DB_NULL;
+			break;
+		}
+		return;
+	}
+#endif
+	switch(fld->type) {
+	case DB_STR:
+		param->buffer = fld->v.lstr.s;
+		fp->length = fld->v.lstr.len;
+		break;
+
+	case DB_BLOB:
+		param->buffer = fld->v.blob.s;
+		fp->length = fld->v.blob.len;
+		break;
+
+	case DB_CSTR:
+		param->buffer = (char*)fld->v.cstr;
+		fp->length = strlen(fld->v.cstr);
+		break;
+
+	case DB_DATETIME:
+		t = gmtime(&fld->v.time);
+		fp->time.second = t->tm_sec;
+		fp->time.minute = t->tm_min;
+		fp->time.hour = t->tm_hour;
+		fp->time.day = t->tm_mday;
+		fp->time.month = t->tm_mon + 1;
+		fp->time.year = t->tm_year + 1900;
+		break;
+		
+	case DB_NONE:
+	case DB_INT:
+	case DB_FLOAT:
+	case DB_DOUBLE:
+	case DB_BITMAP:
+		/* No need to do anything for these types */
+		break;
+
+	}
+}
+
+
+/**
+ * Update values of MySQL bound parameters with values from
+ * the DB API.
+ * @param cmd Command structure which contains pointers to MYSQL_STMT and parameters values
+ * @see bind_mysql_params
+ */
+static inline void set_mysql_params(db_cmd_t* cmd)
+{
+	struct my_cmd* mcmd;
+	int i;
+
+	mcmd = DB_GET_PAYLOAD(cmd);
+
+	/* FIXME: We are updating internals of the prepared statement here,
+	 * this is probably not nice but I could not find another way of
+	 * updating the pointer to the buffer without the need to run
+	 * mysql_stmt_bind_param again (which would be innefficient)
+	 */
+	for(i = 0; i < cmd->vals_count; i++) {
+		update_field(mcmd->st->params + i, cmd->vals + i);
+	}
+
+	for(i = 0; i < cmd->match_count; i++) {
+		update_field(mcmd->st->params + cmd->vals_count + i, cmd->match + i);
+	}
+}
+
+
+static inline int update_result(db_fld_t* result, MYSQL_STMT* st)
+{
+	int i;
+	struct my_fld* rp; /* Payload of the current field in result */
+	struct tm t;
+
+	/* Iterate through all the fields returned by MySQL and convert
+	 * them to DB API representation if necessary
+	 */
+
+	for(i = 0; i < st->field_count; i++) {
+		rp = DB_GET_PAYLOAD(result + i);
+
+		if (rp->is_null) {
+			result[i].flags |= DB_NULL;
+			continue;
+		} else {
+			result[i].flags &= ~DB_NULL;
+		}
+
+		switch(result[i].type) {
+		case DB_STR:
+			result[i].v.lstr.len = rp->length;
+#ifdef MYSQL_FAKE_NULL
+			if (STR_EQ(FAKE_NULL_STR,result[i].v.lstr)) {
+				result[i].flags |= DB_NULL;
+			}
+#endif
+			break;
+
+		case DB_BLOB:
+			result[i].v.blob.len = rp->length;
+			break;
+
+		case DB_CSTR:
+			if (rp->length < STR_BUF_SIZE) {
+				result[i].v.cstr[rp->length] = '\0';
+			} else {
+				/* Truncated field but rp->length contains full size,
+				 * zero terminated the last byte in the buffer
+				 */
+				result[i].v.cstr[STR_BUF_SIZE - 1] = '\0';
+			}
+#ifdef MYSQL_FAKE_NULL
+			if (strcmp(FAKE_NULL_STR.s,result[i].v.cstr)==0) {
+				result[i].flags |= DB_NULL;
+			}
+#endif
+			break;
+			
+		case DB_DATETIME:
+			memset(&t, '\0', sizeof(struct tm));
+			t.tm_sec = rp->time.second;
+			t.tm_min = rp->time.minute;
+			t.tm_hour = rp->time.hour;
+			t.tm_mday = rp->time.day;
+			t.tm_mon = rp->time.month - 1;
+			t.tm_year = rp->time.year - 1900;
+
+			/* Daylight saving information got lost in the database
+			 * so let timegm to guess it. This eliminates the bug when
+			 * contacts reloaded from the database have different time
+			 * of expiration by one hour when daylight saving is used
+			 */ 
+			t.tm_isdst = -1;
+#ifdef HAVE_TIMEGM
+			result[i].v.time = timegm(&t);
+#else
+			result[i].v.time = _timegm(&t);
+#endif /* HAVE_TIMEGM */
+			break;
+
+		case DB_INT:
+#ifdef MYSQL_FAKE_NULL
+			if (FAKE_NULL_INT==result[i].v.int4) {
+				result[i].flags |= DB_NULL;
+			}
+			break;
+#endif
+		case DB_NONE:
+		case DB_FLOAT:
+		case DB_DOUBLE:
+		case DB_BITMAP:
+			/* No need to do anything for these types */
+			break;
+		}
+	}
+	
+	return 0;
+}
+
+
+/**
+ * This is the main command execution function. The function contains
+ * all the necessary logic to detect reset or disconnected database
+ * connections and uploads commands to the server if necessary.
+ * @param cmd Command to be executed
+ * @return    0 if OK, <0 on MySQL failure, >0 on DB API failure
+ */
+static int exec_cmd_safe(db_cmd_t* cmd)
+{
+	int i, err;
+	db_con_t* con;
+	struct my_cmd* mcmd;
+	struct my_con* mcon;
+
+	/* First things first: retrieve connection info
+	 * from the currently active connection and also
+	 * mysql payload from the database command
+	 */
+	mcmd = DB_GET_PAYLOAD(cmd);
+	con = cmd->ctx->con[db_payload_idx];
+	mcon = DB_GET_PAYLOAD(con);
+
+	for(i = 0; i <= my_retries; i++) {
+		/* Next check the number of resets in the database connection,
+		 * if this number is higher than the number we keep in my_cmd
+		 * structure in last_reset variable then the connection was
+		 * reset and we need to upload the command again to the server
+		 * before executing it, because the server recycles all server
+		 * side information upon disconnect.
+		 */
+		if (mcon->resets > mcmd->last_reset) {
+			INFO("mysql: Connection reset detected, uploading command to server\n");
+			err = upload_cmd(cmd);
+			if (err < 0) {
+				INFO("mysql: Error while uploading command\n");
+				/* MySQL error, skip execution and try again if we have attempts left */
+				continue;
+			} else if (err > 0) {
+				/* DB API error, this is a serious problem such
+				 * as memory allocation failure, bail out
+				 */
+				return 1;
+			}
+		}
+
+		set_mysql_params(cmd);
+		err = mysql_stmt_execute(mcmd->st);
+		if (err == 0) {
+			/* The command was executed successfully, now fetch all data
+			 * to the client if it was requested by the user */
+			if (mcmd->flags & MY_FETCH_ALL) {
+				err = mysql_stmt_store_result(mcmd->st);
+				if (err) {
+					INFO("mysql: Error while fetching data to client.\n");
+					goto error;
+				}
+			}
+			return 0;
+		}
+		
+	error:
+		/* Command execution failed, log a message and try to reconnect */
+		INFO("mysql: libmysql: %d, %s\n", mysql_stmt_errno(mcmd->st),
+			 mysql_stmt_error(mcmd->st));
+		INFO("mysql: Error while executing command on server, trying to reconnect\n");
+		my_con_disconnect(con);
+		if (my_con_connect(con)) {
+			INFO("mysql: Failed to reconnect server\n");
+		} else {
+			INFO("mysql: Successfully reconnected server\n");
+		}
+	}
+
+	INFO("mysql: Failed to execute command, giving up\n");
+	return -1;
+}
+
+
+int my_cmd_exec(db_res_t* res, db_cmd_t* cmd)
+{
+	struct my_cmd* mcmd;
+
+	mcmd = DB_GET_PAYLOAD(cmd);
+
+	mcmd->next_flag = -1;
+	return exec_cmd_safe(cmd);
+}
+
+
+/**
+ * Set MYSQL_BIND item.
+ * @param bind destination
+ * @param fld  source
+ */
+static void set_field(MYSQL_BIND *bind, db_fld_t* fld)
+{
+	struct my_fld* f;
+	
+	f = DB_GET_PAYLOAD(fld);
+	bind->is_null = &f->is_null;
+	/* We can do it for all the types here, mysql will ignore it
+	 * for fixed-size types such as MYSQL_TYPE_LONG
+	 */
+	bind->length = &f->length;
+	switch(fld->type) {
+	case DB_INT:
+	case DB_BITMAP:
+		bind->buffer_type = MYSQL_TYPE_LONG;
+		bind->buffer = &fld->v.int4;
+		break;
+	
+	case DB_FLOAT:
+		bind->buffer_type = MYSQL_TYPE_FLOAT;
+		bind->buffer = &fld->v.flt;
+		break;
+		
+	case DB_DOUBLE:
+		bind->buffer_type = MYSQL_TYPE_DOUBLE;
+		bind->buffer = &fld->v.dbl;
+		break;
+	
+	case DB_DATETIME:
+		bind->buffer_type = MYSQL_TYPE_DATETIME;
+		bind->buffer = &f->time;
+		break;
+	
+	case DB_STR:
+	case DB_CSTR:
+		bind->buffer_type = MYSQL_TYPE_VAR_STRING;
+		bind->buffer = ""; /* Updated on runtime */
+		break;
+	
+	case DB_BLOB:
+		bind->buffer_type = MYSQL_TYPE_BLOB;
+		bind->buffer = ""; /* Updated on runtime */
+		break;
+	
+	case DB_NONE:
+		/* Eliminates gcc warning */
+		break;
+	
+	}
+}
+
+
+/**
+ * Bind params, give real values into prepared statement.
+ * Up to two sets of parameters are provided.
+ * Both of them are used in UPDATE command, params1 as colspecs and values and
+ * params2 as WHERE clause. In other cases one set could be enough because values
+ * or match (WHERE clause) is needed.
+ * @param st MySQL command statement
+ * @param params1 first set of params
+ * @param params2 second set of params
+ * @return 0 if OK, <0 on MySQL error, >0 on DB API error
+ * @see update_params
+ */
+static int bind_mysql_params(MYSQL_STMT* st, db_fld_t* params1, db_fld_t* params2)
+{
+	int my_idx, fld_idx;
+	int count1, count2;
+	MYSQL_BIND* my_params;
+	int err = 0;
+
+	/* Calculate the number of parameters */
+	for(count1 = 0; !DB_FLD_EMPTY(params1) && !DB_FLD_LAST(params1[count1]); count1++);
+	for(count2 = 0; !DB_FLD_EMPTY(params2) && !DB_FLD_LAST(params2[count2]); count2++);
+	if (st->param_count != count1 + count2) {
+		BUG("mysql: Number of parameters in SQL command does not match number of DB API parameters\n");
+		return 1;
+	}
+	
+	my_params = (MYSQL_BIND*)pkg_malloc(sizeof(MYSQL_BIND) * (count1 + count2));
+	if (my_params == NULL) {
+		ERR("mysql: No memory left\n");
+		return -1;
+	}
+	memset(my_params, '\0', sizeof(MYSQL_BIND) * (count1 + count2));
+
+	/* params1 */
+	my_idx = 0;
+	for (fld_idx = 0; fld_idx < count1; fld_idx++, my_idx++) {
+		set_field(&my_params[my_idx], params1 + fld_idx);
+	}
+	/* params2 */
+	for (fld_idx = 0; fld_idx < count2; fld_idx++, my_idx++) {
+		set_field(&my_params[my_idx], params2 + fld_idx);
+	}
+
+	err = mysql_stmt_bind_param(st, my_params);
+	if (err) {
+		ERR("mysql: libmysqlclient: %d, %s\n", 
+			mysql_stmt_errno(st), mysql_stmt_error(st));
+		goto error;
+	}
+
+	/* We do not need the array of MYSQL_BIND anymore, mysql_stmt_bind_param
+	 * creates a copy in the statement and we will update it there
+	 */
+	pkg_free(my_params);
+	return err;
+   
+ error:
+	if (my_params) pkg_free(my_params);
+	return err;
+}
+
+
+/*
+ * FIXME: This function will only work if we have one db connection
+ * in every context, otherwise it would initialize the result set
+ * from the first connection in the context.
+ */
+static int check_result(db_cmd_t* cmd, struct my_cmd* payload)
+{
+	int i, n;
+	MYSQL_FIELD *fld;
+	MYSQL_RES *meta = NULL;
+
+	meta = mysql_stmt_result_metadata(payload->st);
+	if (meta == NULL) {
+		/* No error means no result set to be checked */
+		if (mysql_stmt_errno(payload->st) == 0) return 0;
+		ERR("mysql: Error while getting metadata of SQL command: %d, %s\n",
+			mysql_stmt_errno(payload->st), mysql_stmt_error(payload->st));
+		return -1;
+	}
+	n = mysql_num_fields(meta);
+	if (cmd->result == NULL) {
+		/* The result set parameter of db_cmd function was empty, that
+		 * means the command is select * and we have to create the array
+		 * of result fields in the cmd structure manually.
+		 */
+		cmd->result = db_fld(n + 1);
+		cmd->result_count = n;
+		for(i = 0; i < cmd->result_count; i++) {
+			struct my_fld *f;
+			if (my_fld(cmd->result + i, cmd->table.s) < 0) goto error;
+			f = DB_GET_PAYLOAD(cmd->result + i);
+			fld = mysql_fetch_field_direct(meta, i);
+			f->name = pkg_malloc(strlen(fld->name)+1);
+			if (f->name == NULL) {
+				ERR("mysql: Out of private memory\n");
+				goto error;
+			}
+			strcpy(f->name, fld->name);
+			cmd->result[i].name = f->name;
+		}
+	} else {
+		if (cmd->result_count != n) {
+			BUG("mysql: Number of fields in MySQL result does not match number of parameters in DB API\n");
+			goto error;
+		}
+	}
+
+	/* Now iterate through all the columns in the result set and replace
+	 * any occurrence of DB_UNKNOWN type with the type of the column
+	 * retrieved from the database and if no column name was provided then
+	 * update it from the database as well. 
+	 */
+	for(i = 0; i < cmd->result_count; i++) {
+		fld = mysql_fetch_field_direct(meta, i);
+		if (cmd->result[i].type != DB_NONE) continue;
+		switch(fld->type) {
+		case MYSQL_TYPE_TINY:
+		case MYSQL_TYPE_SHORT:
+		case MYSQL_TYPE_INT24:
+		case MYSQL_TYPE_LONG:
+			cmd->result[i].type = DB_INT;
+			break;
+
+		case MYSQL_TYPE_FLOAT:
+			cmd->result[i].type = DB_FLOAT;
+			break;
+
+		case MYSQL_TYPE_DOUBLE:
+			cmd->result[i].type = DB_DOUBLE;
+			break;
+
+		case MYSQL_TYPE_TIMESTAMP:
+		case MYSQL_TYPE_DATETIME:
+			cmd->result[i].type = DB_DATETIME;
+			break;
+
+		case MYSQL_TYPE_STRING:
+		case MYSQL_TYPE_VAR_STRING:
+			cmd->result[i].type = DB_STR;
+			break;
+
+		default:
+			ERR("mysql: Unsupported MySQL column type: %d, table: %s, column: %s\n",
+				fld->type, cmd->table.s, fld->name);
+			goto error;
+		}
+	}
+	
+	if (meta) mysql_free_result(meta);
+	return 0;
+
+error:
+	if (meta) mysql_free_result(meta);
+	return 1;
+}
+
+
+/* FIXME: Add support for DB_NONE, in this case the function should determine
+ * the type of the column in the database and set the field type appropriately.
+ * This function must be called after check_result.
+ */
+static int bind_result(MYSQL_STMT* st, db_fld_t* fld)
+{
+	int i, n, err = 0;
+	struct my_fld* f;
+	MYSQL_BIND* result;
+
+	/* Calculate the number of fields in the result */
+	for(n = 0; !DB_FLD_EMPTY(fld) && !DB_FLD_LAST(fld[n]); n++);
+	/* Return immediately if there are no fields in the result set */
+	if (n == 0) return 0;
+
+	result = (MYSQL_BIND*)pkg_malloc(sizeof(MYSQL_BIND) * n);
+	if (result == NULL) {
+		ERR("mysql: No memory left\n");
+		return 1;
+	}
+	memset(result, '\0', sizeof(MYSQL_BIND) * n);
+	
+	for(i = 0; i < n; i++) {
+		f = DB_GET_PAYLOAD(fld + i);
+		result[i].is_null = &f->is_null;
+		/* We can do it for all the types here, mysql will ignore it
+		 * for fixed-size types such as MYSQL_TYPE_LONG
+		 */
+		result[i].length = &f->length;
+		switch(fld[i].type) {
+		case DB_INT:
+		case DB_BITMAP:
+			result[i].buffer_type = MYSQL_TYPE_LONG;
+			result[i].buffer = &fld[i].v.int4;
+			break;
+
+		case DB_FLOAT:
+			result[i].buffer_type = MYSQL_TYPE_FLOAT;
+			result[i].buffer = &fld[i].v.flt;
+			break;
+			
+		case DB_DOUBLE:
+			result[i].buffer_type = MYSQL_TYPE_DOUBLE;
+			result[i].buffer = &fld[i].v.dbl;
+			break;
+
+		case DB_DATETIME:
+			result[i].buffer_type = MYSQL_TYPE_DATETIME;
+			result[i].buffer = &f->time;
+			break;
+
+		case DB_STR:
+			result[i].buffer_type = MYSQL_TYPE_VAR_STRING;
+			if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
+			if (f->buf.s == NULL) {
+				ERR("mysql: No memory left\n");
+				err = 1;
+				goto error;
+			}
+			result[i].buffer = f->buf.s;
+			fld[i].v.lstr.s = f->buf.s;
+			result[i].buffer_length = STR_BUF_SIZE - 1;
+			break;
+
+		case DB_CSTR:
+			result[i].buffer_type = MYSQL_TYPE_VAR_STRING;
+			if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
+			if (f->buf.s == NULL) {
+				ERR("mysql: No memory left\n");
+				err = 1;
+				goto error;
+			}
+			result[i].buffer = f->buf.s;
+			fld[i].v.cstr = f->buf.s;
+			result[i].buffer_length = STR_BUF_SIZE - 1;
+			break;
+
+		case DB_BLOB:
+			result[i].buffer_type = MYSQL_TYPE_BLOB;
+			if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
+			if (f->buf.s == NULL) {
+				ERR("mysql: No memory left\n");
+				err = 1;
+				goto error;
+			}
+			result[i].buffer = f->buf.s;
+			fld[i].v.blob.s = f->buf.s;
+			result[i].buffer_length = STR_BUF_SIZE - 1;
+			break;
+
+		case DB_NONE:
+			/* Eliminates gcc warning */
+			break;
+
+		}
+	}
+
+	err = mysql_stmt_bind_result(st, result);
+	if (err) {
+		ERR("mysql: Error while binding result: %s\n", mysql_stmt_error(st));
+		goto error;
+	}
+
+	/* We do not need the array of MYSQL_BIND anymore, mysql_stmt_bind_param
+	 * creates a copy in the statement and we will update it there
+	 */
+	if (result) pkg_free(result);
+	return 0;
+   
+ error:
+	if (result) pkg_free(result);
+	return err;
+}
+
+
+/**
+ * Upload database command to the server
+ * @param cmd  Command to be uploaded
+ * @return     0 if OK, >0 on DB API errors, <0 on MySQL errors
+ */
+static int upload_cmd(db_cmd_t* cmd)
+{
+	struct my_cmd* res;
+	struct my_con* mcon;
+	int err = 0;
+
+	res = DB_GET_PAYLOAD(cmd);
+
+	/* FIXME: The function should take the connection as one of parameters */
+	mcon = DB_GET_PAYLOAD(cmd->ctx->con[db_payload_idx]);
+
+	/* If there is a previous pre-compiled statement, close it first */
+	if (res->st) mysql_stmt_close(res->st);
+	res->st = NULL;
+
+	/* Create a new pre-compiled statement data structure */
+	res->st = mysql_stmt_init(mcon->con);
+	if (res->st == NULL) {
+		ERR("mysql: Error while creating new MySQL_STMT data structure (no memory left)\n");
+		err = 1;
+		goto error;
+	}
+
+	/* Try to upload the command to the server */
+	err = mysql_stmt_prepare(res->st, res->sql_cmd.s, res->sql_cmd.len);
+	if (err) {
+		ERR("mysql: libmysql: %d, %s\n", mysql_stmt_errno(res->st), 
+			mysql_stmt_error(res->st));
+		ERR("mysql: An error occurred while uploading a command to MySQL server\n");
+		goto error;
+	}
+
+	err = bind_mysql_params(res->st, cmd->vals, cmd->match);
+	if (err) goto error;
+
+	if (cmd->type == DB_GET || cmd->type == DB_SQL) {
+		err = check_result(cmd, res);
+		if (err) goto error;
+		err = bind_result(res->st, cmd->result);
+		if (err) goto error;
+	}
+
+	res->last_reset = mcon->resets;
+	return 0;
+
+ error:
+	if (res->st) {
+		ERR("mysql: libmysqlclient: %d, %s\n", 
+			mysql_stmt_errno(res->st), 
+			mysql_stmt_error(res->st));
+		mysql_stmt_close(res->st);
+		res->st = NULL;
+	}
+	return err;
+}
+
+
+int my_cmd(db_cmd_t* cmd)
+{
+	struct my_cmd* res;
+ 
+	res = (struct my_cmd*)pkg_malloc(sizeof(struct my_cmd));
+	if (res == NULL) {
+		ERR("mysql: No memory left\n");
+		goto error;
+	}
+	memset(res, '\0', sizeof(struct my_cmd));
+	/* Fetch all data to client at once by default */
+	res->flags |= MY_FETCH_ALL;
+	if (db_drv_init(&res->gen, my_cmd_free) < 0) goto error;
+
+	switch(cmd->type) {
+	case DB_PUT:
+		if (DB_FLD_EMPTY(cmd->vals)) {
+			BUG("mysql: No parameters provided for DB_PUT in context '%.*s'\n", 
+				cmd->ctx->id.len, ZSW(cmd->ctx->id.s));
+			goto error;
+		}
+		if (build_replace_cmd(&res->sql_cmd, cmd) < 0) goto error;
+		break;
+
+	case DB_DEL:
+		if (build_delete_cmd(&res->sql_cmd, cmd) < 0) goto error;
+		break;
+
+	case DB_GET:
+		if (build_select_cmd(&res->sql_cmd, cmd) < 0) goto error;
+		break;
+
+	case DB_UPD:
+		if (build_update_cmd(&res->sql_cmd, cmd) < 0) goto error;
+		break;
+
+	case DB_SQL:
+		res->sql_cmd.s = (char*)pkg_malloc(cmd->table.len);
+		if (res->sql_cmd.s == NULL) {
+			ERR("mysql: Out of private memory\n");
+			goto error;
+		}
+		memcpy(res->sql_cmd.s,cmd->table.s, cmd->table.len);
+		res->sql_cmd.len = cmd->table.len;
+        break;
+	}
+
+	DB_SET_PAYLOAD(cmd, res);
+	if (upload_cmd(cmd) != 0) goto error;
+	return 0;
+
+ error:
+	if (res) {
+		DB_SET_PAYLOAD(cmd, NULL);
+		db_drv_free(&res->gen);
+		if (res->sql_cmd.s) pkg_free(res->sql_cmd.s);
+		pkg_free(res);
+	}
+	return -1;
+}
+
+
+int my_cmd_first(db_res_t* res) {
+	struct my_cmd* mcmd;
+
+	mcmd = DB_GET_PAYLOAD(res->cmd);
+	switch (mcmd->next_flag) {
+	case -2: /* table is empty */
+		return 1;
+	case 0:  /* cursor position is 0 */
+		return 0;
+	case 1:  /* next row */
+	case 2:  /* EOF */
+		ERR("mysql: Unbuffered queries do not support cursor reset.\n");
+		return -1;
+	default:
+		return my_cmd_next(res);
+	}
+}
+
+
+int my_cmd_next(db_res_t* res)
+{
+	int ret;
+	struct my_cmd* mcmd;
+
+	mcmd = DB_GET_PAYLOAD(res->cmd);
+	if (mcmd->next_flag == 2 || mcmd->next_flag == -2) return 1;
+
+	if (mcmd->st == NULL) {
+		ERR("mysql: Prepared statement not found\n");
+		return -1;
+	}
+
+	ret = mysql_stmt_fetch(mcmd->st);
+	
+	if (ret == MYSQL_NO_DATA) {
+		mcmd->next_flag =  mcmd->next_flag<0?-2:2;
+		return 1;
+	}
+	/* MYSQL_DATA_TRUNCATED is only defined in mysql >= 5.0 */
+#if defined MYSQL_DATA_TRUNCATED
+	if (ret == MYSQL_DATA_TRUNCATED) {
+		int i;
+		ERR("mysql: mysql_stmt_fetch, data truncated, fields: %d\n", res->cmd->result_count);
+		for (i = 0; i < res->cmd->result_count; i++) {
+			if (mcmd->st->bind[i].error /*&& mcmd->st->bind[i].buffer_length*/) {
+				ERR("mysql: truncation, bind %d, length: %lu, buffer_length: %lu\n", 
+					i, *(mcmd->st->bind[i].length), mcmd->st->bind[i].buffer_length);
+			}
+		}
+		ret = 0;
+	}
+#endif
+	if (mcmd->next_flag <= 0) {
+		mcmd->next_flag++;
+	}
+	if (ret != 0) {
+		ERR("mysql: Error in mysql_stmt_fetch (ret=%d): %s\n", ret, mysql_stmt_error(mcmd->st));
+		return -1;
+	}
+
+	if (update_result(res->cmd->result, mcmd->st) < 0) {
+		mysql_stmt_free_result(mcmd->st);
+		return -1;
+	}
+
+	res->cur_rec->fld = res->cmd->result;
+	return 0;
+}
+
+
+int my_getopt(db_cmd_t* cmd, char* optname, va_list ap)
+{
+	struct my_cmd* mcmd;
+	long long* id;
+	int* val;
+
+	mcmd = (struct my_cmd*)DB_GET_PAYLOAD(cmd);
+
+	if (!strcasecmp("last_id", optname)) {
+		id = va_arg(ap, long long*);
+		if (id == NULL) {
+			BUG("mysql: NULL pointer passed to 'last_id' option\n");
+			goto error;
+		}
+
+		if (mcmd->st->last_errno != 0) {
+			BUG("mysql: Option 'last_id' called but previous command failed, "
+				"check your code\n");
+			return -1;
+		}
+
+		*id = mysql_stmt_insert_id(mcmd->st);
+		if ((*id) == 0) {
+			BUG("mysql: Option 'last_id' called but there is no auto-increment"
+				" column in table, SQL command: %.*s\n", STR_FMT(&mcmd->sql_cmd));
+			return -1;
+		}
+	} else if (!strcasecmp("fetch_all", optname)) {
+		val = va_arg(ap, int*);
+		if (val == NULL) {
+			BUG("mysql: NULL pointer passed to 'fetch_all' DB option\n");
+			goto error;
+		}
+		*val = mcmd->flags;
+	} else {
+		return 1;
+	}
+	return 0;
+
+ error:
+	return -1;
+}
+
+
+int my_setopt(db_cmd_t* cmd, char* optname, va_list ap)
+{
+	struct my_cmd* mcmd;
+	int* val;
+
+	mcmd = (struct my_cmd*)DB_GET_PAYLOAD(cmd);
+	if (!strcasecmp("fetch_all", optname)) {
+		val = va_arg(ap, int*);
+		if (val != 0) {
+			mcmd->flags |= MY_FETCH_ALL;
+		} else {
+			mcmd->flags &= ~MY_FETCH_ALL;
+		}
+	} else {
+		return 1;
+	}
+	return 0;
+}
+
+/** @} */

+ 73 - 0
modules/db_mysql/my_cmd.h

@@ -0,0 +1,73 @@
+/* 
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2006-2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _MY_CMD_H
+#define _MY_CMD_H  1
+
+#include "../../db/db_drv.h"
+#include "../../db/db_cmd.h"
+#include <mysql/mysql.h>
+#include <stdarg.h>
+
+typedef enum my_flags {
+	/** Fetch all data from the server to the client at once */
+	MY_FETCH_ALL = (1 << 0),
+} my_flags_t;
+
+struct my_cmd {
+	db_drv_t gen;
+
+	str sql_cmd; /**< Database command represented in SQL language */
+	int next_flag;
+	MYSQL_STMT* st; /**< MySQL pre-compiled statement handle */
+
+	/** This is the sequential number of the last
+	 * connection reset last time the command was
+	 * uploaded to the server. If the reset number
+	 * in the corresponding my_con structure is higher
+	 * than the number in this variable then we need
+	 * to upload the command again, because the
+	 * the connection was reconnected meanwhile.
+	 */
+	unsigned int last_reset;
+	unsigned int flags; /**< Various flags, mainly used by setopt and getopt */
+};
+
+int my_cmd(db_cmd_t* cmd);
+
+int my_cmd_exec(db_res_t* res, db_cmd_t* cmd);
+
+int my_cmd_first(db_res_t* res);
+
+int my_cmd_next(db_res_t* res);
+
+int my_getopt(db_cmd_t* cmd, char* optname, va_list ap);
+
+int my_setopt(db_cmd_t* cmd, char* optname, va_list ap);
+
+#endif /* _MY_CMD_H */

+ 195 - 0
modules/db_mysql/my_con.c

@@ -0,0 +1,195 @@
+/* 
+ * $Id$
+ *
+ * Copyright (C) 2001-2004 iptel.org
+ * Copyright (C) 2006-2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "my_con.h"
+
+#include "mysql_mod.h"
+#include "my_uri.h"
+
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "../../ut.h"
+
+#include <string.h>
+#include <time.h>
+
+
+/*
+ * Close the connection and release memory
+ */
+static void my_con_free(db_con_t* con, struct my_con* payload)
+{
+	if (!payload) return;
+	
+	/* Delete the structure only if there are no more references
+	 * to it in the connection pool
+	 */
+	if (db_pool_remove((db_pool_entry_t*)payload) == 0) return;
+	
+	db_pool_entry_free(&payload->gen);
+	if (payload->con) pkg_free(payload->con);
+	pkg_free(payload);
+}
+
+
+int my_con_connect(db_con_t* con)
+{
+	struct my_con* mcon;
+	struct my_uri* muri;
+	
+	mcon = DB_GET_PAYLOAD(con);
+	muri = DB_GET_PAYLOAD(con->uri);
+	
+	/* Do not reconnect already connected connections */
+	if (mcon->flags & MY_CONNECTED) return 0;
+
+	DBG("mysql: Connecting to %.*s:%.*s\n",
+		con->uri->scheme.len, ZSW(con->uri->scheme.s),
+		con->uri->body.len, ZSW(con->uri->body.s));
+
+	if (my_connect_to) {
+		if (mysql_options(mcon->con, MYSQL_OPT_CONNECT_TIMEOUT, 
+						  (char*)&my_connect_to))
+			WARN("mysql: failed to set MYSQL_OPT_CONNECT_TIMEOUT\n");
+	}
+
+#if MYSQL_VERSION_ID >= 40101 
+	if ((my_client_ver >= 50025) || 
+		((my_client_ver >= 40122) && 
+		 (my_client_ver < 50000))) {
+		if (my_send_to) {
+			if (mysql_options(mcon->con, MYSQL_OPT_WRITE_TIMEOUT , 
+							  (char*)&my_send_to))
+				WARN("mysql: failed to set MYSQL_OPT_WRITE_TIMEOUT\n");
+		}
+		if (my_recv_to){
+			if (mysql_options(mcon->con, MYSQL_OPT_READ_TIMEOUT , 
+							  (char*)&my_recv_to))
+				WARN("mysql: failed to set MYSQL_OPT_READ_TIMEOUT\n");
+		}
+	}
+#endif
+	
+	if (!mysql_real_connect(mcon->con, muri->host, muri->username, 
+							muri->password, muri->database, muri->port, 0, 0)) {
+		LOG(L_ERR, "mysql: %s\n", mysql_error(mcon->con));
+		return -1;
+	}
+	
+	DBG("mysql: Connection type is %s\n", mysql_get_host_info(mcon->con));
+	DBG("mysql: Protocol version is %d\n", mysql_get_proto_info(mcon->con));
+	DBG("mysql: Server version is %s\n", mysql_get_server_info(mcon->con));
+
+	mcon->flags |= MY_CONNECTED;
+
+	/* Increase the variable that keeps track of number of connects performed
+	 * on this connection. The mysql module uses the variable to determine
+	 * when a pre-compiled command needs to be uploaded to the server again.
+	 * If the number in the my_con structure is large than the number kept
+	 * in my_cmd then it means that we have to upload the command to the server
+	 * again because the connection was reconnected meanwhile.
+	 */
+	mcon->resets++;
+	return 0;
+}
+
+
+void my_con_disconnect(db_con_t* con)
+{
+	struct my_con* mcon;
+
+	mcon = DB_GET_PAYLOAD(con);
+
+	if ((mcon->flags & MY_CONNECTED) == 0) return;
+
+	DBG("mysql: Disconnecting from %.*s:%.*s\n",
+		con->uri->scheme.len, ZSW(con->uri->scheme.s),
+		con->uri->body.len, ZSW(con->uri->body.s));
+
+	mysql_close(mcon->con);
+	mcon->flags &= ~MY_CONNECTED;
+}
+
+
+int my_con(db_con_t* con)
+{
+	struct my_con* ptr;
+	struct my_uri* uri;
+
+	/* First try to lookup the connection in the connection pool and
+	 * re-use it if a match is found
+	 */
+	ptr = (struct my_con*)db_pool_get(con->uri);
+	if (ptr) {
+		DBG("mysql: Connection to %.*s:%.*s found in connection pool\n",
+			con->uri->scheme.len, ZSW(con->uri->scheme.s),
+			con->uri->body.len, ZSW(con->uri->body.s));
+		goto found;
+	}
+
+	ptr = (struct my_con*)pkg_malloc(sizeof(struct my_con));
+	if (!ptr) {
+		LOG(L_ERR, "mysql: No memory left\n");
+		goto error;
+	}
+	memset(ptr, '\0', sizeof(struct my_con));
+	if (db_pool_entry_init(&ptr->gen, my_con_free, con->uri) < 0) goto error;
+
+	ptr->con = (MYSQL*)pkg_malloc(sizeof(MYSQL));
+	if (!ptr->con) {
+		LOG(L_ERR, "mysql: No enough memory\n");
+		goto error;
+	}
+	mysql_init(ptr->con);
+
+	uri = DB_GET_PAYLOAD(con->uri);
+	DBG("mysql: Creating new connection to: %.*s:%.*s\n",
+		con->uri->scheme.len, ZSW(con->uri->scheme.s),
+		con->uri->body.len, ZSW(con->uri->body.s));
+
+	/* Put the newly created mysql connection into the pool */
+	db_pool_put((struct db_pool_entry*)ptr);
+	DBG("mysql: Connection stored in connection pool\n");
+
+ found:
+	/* Attach driver payload to the db_con structure and set connect and
+	 * disconnect functions
+	 */
+	DB_SET_PAYLOAD(con, ptr);
+	con->connect = my_con_connect;
+	con->disconnect = my_con_disconnect;
+	return 0;
+
+ error:
+	if (ptr) {
+		db_pool_entry_free(&ptr->gen);
+		if (ptr->con) pkg_free(ptr->con);
+		pkg_free(ptr);
+	}
+	return 0;
+}

+ 69 - 0
modules/db_mysql/my_con.h

@@ -0,0 +1,69 @@
+/* 
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2006-2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _MY_CON_H
+#define _MY_CON_H  1
+
+#include "../../db/db_pool.h"
+#include "../../db/db_con.h"
+#include "../../db/db_uri.h"
+
+#include <time.h>
+#include <mysql/mysql.h>
+
+enum my_con_flags {
+	MY_CONNECTED = 1
+};
+
+typedef struct my_con {
+	/* Generic part of the structure */
+	db_pool_entry_t gen;
+
+	MYSQL* con;
+	unsigned int flags;
+	
+	/* We keep the number of connection resets in this variable,
+	 * this variable is incremented each time the module performs
+	 * a re-connect on the connection. This is used by my_cmd
+	 * related functions to check if a pre-compiled command needs
+	 * to be uploaded to the server before executing it.
+	 */
+	unsigned int resets;
+} my_con_t;
+
+
+/*
+ * Create a new connection structure,
+ * open the MySQL connection and set reference count to 1
+ */
+int my_con(db_con_t* con);
+
+int my_con_connect(db_con_t* con);
+void my_con_disconnect(db_con_t* con);
+
+#endif /* _MY_CON_H */

+ 65 - 0
modules/db_mysql/my_fld.c

@@ -0,0 +1,65 @@
+/* 
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2006-2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "my_fld.h"
+
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "../../db/db_gen.h"
+
+#include <string.h>
+
+
+static void my_fld_free(db_fld_t* fld, struct my_fld* payload)
+{
+	db_drv_free(&payload->gen);
+	if (payload->buf.s) pkg_free(payload->buf.s);
+	if (payload->name) pkg_free(payload->name);
+	pkg_free(payload);
+}
+
+
+int my_fld(db_fld_t* fld, char* table)
+{
+	struct my_fld* res;
+
+	res = (struct my_fld*)pkg_malloc(sizeof(struct my_fld));
+	if (res == NULL) {
+		ERR("mysql: No memory left\n");
+		return -1;
+	}
+	memset(res, '\0', sizeof(struct my_fld));
+	if (db_drv_init(&res->gen, my_fld_free) < 0) goto error;
+
+	DB_SET_PAYLOAD(fld, res);
+	return 0;
+
+ error:
+	if (res) pkg_free(res);
+	return -1;
+}

+ 54 - 0
modules/db_mysql/my_fld.h

@@ -0,0 +1,54 @@
+/* 
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2006-2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _MY_FLD_H
+#define _MY_FLD_H  1
+
+/** @addtogroup mysql
+ *  @{
+ */
+
+#include "../../db/db_drv.h"
+#include "../../db/db_fld.h"
+#include <mysql/mysql.h>
+
+struct my_fld {
+	db_drv_t gen;
+
+	char* name;
+	my_bool is_null;
+	MYSQL_TIME time;
+	unsigned long length;
+	str buf;
+};
+
+int my_fld(db_fld_t* fld, char* table);
+
+/** @} */
+
+#endif /* _MY_FLD_H */

+ 80 - 0
modules/db_mysql/my_res.c

@@ -0,0 +1,80 @@
+/* 
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2006-2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "my_res.h"
+
+#include "my_cmd.h"
+
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "../../db/db_gen.h"
+
+#include <mysql/mysql.h>
+
+
+void my_res_free(db_res_t* res, struct my_res* payload)
+{
+	struct my_cmd* mcmd;
+
+	mcmd = DB_GET_PAYLOAD(res->cmd);
+
+	if (mcmd->st && mysql_stmt_free_result(mcmd->st)) {
+		ERR("mysql: Error while freeing MySQL result: %d, %s\n", 
+			mysql_stmt_errno(mcmd->st), mysql_stmt_error(mcmd->st));
+	}
+
+	db_drv_free(&payload->gen);
+	pkg_free(payload);
+}
+
+
+/*
+ * Attach a mysql specific structure to db_res, this structure contains a pointer
+ * to my_res_free which releases the mysql result stored in the mysql statement
+ * and if there is a cursor open in the statement then it will be closed as well
+ */
+int my_res(db_res_t* res)
+{
+	struct my_res* mr;
+
+	mr = (struct my_res*)pkg_malloc(sizeof(struct my_res));
+	if (mr == NULL) {
+		ERR("mysql: No memory left\n");
+		return -1;
+	}
+	if (db_drv_init(&mr->gen, my_res_free) < 0) goto error;
+	DB_SET_PAYLOAD(res, mr);
+	return 0;
+	
+ error:
+	if (mr) {
+		db_drv_free(&mr->gen);
+		pkg_free(mr);
+	}
+	return -1;
+}

+ 41 - 0
modules/db_mysql/my_res.h

@@ -0,0 +1,41 @@
+/* 
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2006-2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _MY_RES_H
+#define _MY_RES_H  1
+
+#include "../../db/db_drv.h"
+#include "../../db/db_res.h"
+
+struct my_res {
+	db_drv_t gen;
+};
+
+int my_res(db_res_t* cmd);
+
+#endif /* _MY_RES_H */

+ 282 - 0
modules/db_mysql/my_uri.c

@@ -0,0 +1,282 @@
+/* 
+ * $Id$ 
+ *
+ * MySQL module interface
+ *
+ * Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2006-2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "my_uri.h"
+
+#include "../../dprint.h"
+#include "../../mem/mem.h"
+#include "../../ut.h"
+#include "../../db/db_gen.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+
+/* compare s1 & s2  with a function f (which should return 0 if ==);
+ * s1 & s2 can be null
+ * return 0 if match, 1 if not */
+#define cmpstr(s1, s2, f) \
+	((s1)!=(s2)) && ((s1)==0 || (s2)==0 || (f)((s1), (s2))!=0)
+
+/*
+ * Compare two connection identifiers
+ */
+static unsigned char my_uri_cmp(db_uri_t* uri1, db_uri_t* uri2)
+{
+	struct my_uri* muri1, *muri2;
+
+	if (!uri1 || !uri2) return 0;
+
+	muri1 = DB_GET_PAYLOAD(uri1);
+	muri2 = DB_GET_PAYLOAD(uri2);
+	if (muri1->port != muri2->port) return 0;
+
+	if (cmpstr(muri1->username, muri2->username, strcmp)) return 0;
+	if (cmpstr(muri1->password, muri2->password, strcmp)) return 0;
+	if (cmpstr(muri1->host, muri2->host, strcasecmp)) return 0;
+	if (cmpstr(muri1->database, muri2->database, strcmp)) return 0;
+	return 1;
+}
+
+
+
+/*
+ * Duplicate a string
+ */
+static int dupl_string(char** dst, const char* begin, const char* end)
+{
+	if (*dst) pkg_free(*dst);
+
+	*dst = pkg_malloc(end - begin + 1);
+	if ((*dst) == NULL) {
+		return -1;
+	}
+
+	memcpy(*dst, begin, end - begin);
+	(*dst)[end - begin] = '\0';
+	return 0;
+}
+
+
+/*
+ * Parse mysql URI of form 
+ * //[username[:password]@]hostname[:port]/database
+ *
+ * Returns 0 if parsing was successful and -1 otherwise
+ */
+static int parse_mysql_uri(struct my_uri* res, str* uri)
+{
+#define SHORTEST_DB_URL "//a/b"
+#define SHORTEST_DB_URL_LEN (sizeof(SHORTEST_DB_URL) - 1)
+
+	enum state {
+		ST_SLASH1,     /* First slash */
+		ST_SLASH2,     /* Second slash */
+		ST_USER_HOST,  /* Username or hostname */
+		ST_PASS_PORT,  /* Password or port part */
+		ST_HOST,       /* Hostname part */
+		ST_PORT,       /* Port part */
+		ST_DB          /* Database part */
+	};
+
+	enum state st;
+	int  i;
+	const char* begin;
+	char* prev_token;
+
+	prev_token = 0;
+
+	if (!res || !res) {
+		goto err;
+	}
+	
+	if (uri->len < SHORTEST_DB_URL_LEN) {
+		goto err;
+	}
+	
+	st = ST_SLASH1;
+	begin = uri->s;
+
+	for(i = 0; i < uri->len; i++) {
+		switch(st) {
+		case ST_SLASH1:
+			switch(uri->s[i]) {
+			case '/':
+				st = ST_SLASH2;
+				break;
+
+			default:
+				goto err;
+			}
+			break;
+
+		case ST_SLASH2:
+			switch(uri->s[i]) {
+			case '/':
+				st = ST_USER_HOST;
+				begin = uri->s + i + 1;
+				break;
+				
+			default:
+				goto err;
+			}
+			break;
+
+		case ST_USER_HOST:
+			switch(uri->s[i]) {
+			case '@':
+				st = ST_HOST;
+				if (dupl_string(&res->username, begin, uri->s + i) < 0) goto err;
+				begin = uri->s + i + 1;
+				break;
+
+			case ':':
+				st = ST_PASS_PORT;
+				if (dupl_string(&prev_token, begin, uri->s + i) < 0) goto err;
+				begin = uri->s + i + 1;
+				break;
+
+			case '/':
+				if (dupl_string(&res->host, begin, uri->s + i) < 0) goto err;
+				if (dupl_string(&res->database, uri->s + i + 1, uri->s + uri->len) < 0) goto err;
+				return 0;
+			}
+			break;
+
+		case ST_PASS_PORT:
+			switch(uri->s[i]) {
+			case '@':
+				st = ST_HOST;
+				res->username = prev_token;
+				if (dupl_string(&res->password, begin, uri->s + i) < 0) goto err;
+				begin = uri->s + i + 1;
+				break;
+
+			case '/':
+				res->host = prev_token;
+				res->port = str2s(begin, uri->s + i - begin, 0);
+				if (dupl_string(&res->database, uri->s + i + 1, uri->s + uri->len) < 0) goto err;
+				return 0;
+			}
+			break;
+
+		case ST_HOST:
+			switch(uri->s[i]) {
+			case ':':
+				st = ST_PORT;
+				if (dupl_string(&res->host, begin, uri->s + i) < 0) goto err;
+				begin = uri->s + i + 1;
+				break;
+
+			case '/':
+				if (dupl_string(&res->host, begin, uri->s + i) < 0) goto err;
+				if (dupl_string(&res->database, uri->s + i + 1, uri->s + uri->len) < 0) goto err;
+				return 0;
+			}
+			break;
+
+		case ST_PORT:
+			switch(uri->s[i]) {
+			case '/':
+				res->port = str2s(begin, uri->s + i - begin, 0);
+				if (dupl_string(&res->database, uri->s + i + 1, uri->s + uri->len) < 0) goto err;
+				return 0;
+			}
+			break;
+			
+		case ST_DB:
+			break;
+		}
+	}
+
+	if (st != ST_DB) goto err;
+	return 0;
+
+ err:
+	if (prev_token) pkg_free(prev_token);
+	if (res == NULL) return -1;
+	if (res->username) {
+		pkg_free(res->username);
+		res->username = NULL;
+	}
+	if (res->password) {
+		pkg_free(res->password);
+		res->password = NULL;
+	}
+	if (res->host) {
+		pkg_free(res->host);
+		res->host = NULL;
+	}
+	if (res->database) {
+		pkg_free(res->database);
+		res->database = NULL;
+	}
+	return -1;
+}
+
+
+
+static void my_uri_free(db_uri_t* uri, struct my_uri* payload)
+{
+	if (payload == NULL) return;
+	db_drv_free(&payload->drv);
+	if (payload->username) pkg_free(payload->username);
+	if (payload->password) pkg_free(payload->password);
+	if (payload->host) pkg_free(payload->host);
+	if (payload->database) pkg_free(payload->database);
+	pkg_free(payload);
+}
+
+
+int my_uri(db_uri_t* uri)
+{
+	struct my_uri* res;
+
+	res = (struct my_uri*)pkg_malloc(sizeof(struct my_uri));
+	if (res == NULL) {
+		ERR("mysql: No memory left\n");
+		goto error;
+	}
+	memset(res, '\0', sizeof(struct my_uri));
+	if (db_drv_init(&res->drv, my_uri_free) < 0) goto error;
+	if (parse_mysql_uri(res, &uri->body) < 0) goto error;
+
+	DB_SET_PAYLOAD(uri, res);
+	uri->cmp = my_uri_cmp;
+	return 0;
+
+ error:
+	if (res) {
+		db_drv_free(&res->drv);
+		if (res) pkg_free(res);
+	}
+	return -1;
+}
+

+ 51 - 0
modules/db_mysql/my_uri.h

@@ -0,0 +1,51 @@
+/* 
+ * $Id$ 
+ *
+ * MySQL module interface
+ *
+ * Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2006-2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _MY_URI_H
+#define _MY_URI_H
+
+#include "../../db/db_uri.h"
+#include "../../db/db_drv.h"
+
+struct my_uri {
+	db_drv_t drv;
+	char* username;
+	char* password;
+	char* host;
+	unsigned short port;
+	char* database;
+};
+
+
+int my_uri(db_uri_t* uri);
+
+
+#endif /* _MY_URI_H */
+

+ 146 - 0
modules/db_mysql/mysql_mod.c

@@ -0,0 +1,146 @@
+/*
+ * $Id$
+ *
+ * MySQL module interface
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * History:
+ * --------
+ *  2003-03-11  updated to the new module exports interface (andrei)
+ *  2003-03-16  flags export parameter added (janakj)
+ */
+
+/** @addtogroup mysql
+ *  @{
+ */
+ 
+#include "mysql_mod.h"
+
+#include "my_uri.h"
+#include "my_con.h"
+#include "my_cmd.h"
+#include "my_fld.h"
+#include "my_res.h"
+
+#include "../../sr_module.h"
+#include "../../db/db.h"
+
+int my_ping_interval = 5 * 60; /* Default is 5 minutes */
+unsigned int my_connect_to = 2; /* 2 s by default */
+unsigned int my_send_to = 0; /*  enabled only for mysql >= 5.25  */
+unsigned int my_recv_to = 0; /* enabled only for mysql >= 5.25 */
+unsigned int my_retries = 1;    /* Number of retries when command fails */
+
+unsigned long my_client_ver = 0;
+
+#define DEFAULT_MY_SEND_TO  2   /* in seconds */
+#define DEFAULT_MY_RECV_TO  4   /* in seconds */
+
+static int mysql_mod_init(void);
+
+MODULE_VERSION
+
+
+/*
+ * MySQL database module interface
+ */
+static cmd_export_t cmds[] = {
+	{"db_ctx",    (cmd_function)NULL,         0, 0, 0},
+	{"db_con",    (cmd_function)my_con,       0, 0, 0},
+	{"db_uri",    (cmd_function)my_uri,       0, 0, 0},
+	{"db_cmd",    (cmd_function)my_cmd,       0, 0, 0},
+	{"db_put",    (cmd_function)my_cmd_exec,  0, 0, 0},
+	{"db_del",    (cmd_function)my_cmd_exec,  0, 0, 0},
+	{"db_get",    (cmd_function)my_cmd_exec,  0, 0, 0},
+	{"db_upd",    (cmd_function)my_cmd_exec,  0, 0, 0},
+	{"db_sql",    (cmd_function)my_cmd_exec,  0, 0, 0},
+	{"db_res",    (cmd_function)my_res,       0, 0, 0},
+	{"db_fld",    (cmd_function)my_fld,       0, 0, 0},
+	{"db_first",  (cmd_function)my_cmd_first, 0, 0, 0},
+	{"db_next",   (cmd_function)my_cmd_next,  0, 0, 0},
+	{"db_setopt", (cmd_function)my_setopt,    0, 0, 0},
+	{"db_getopt", (cmd_function)my_getopt,    0, 0, 0},
+	{0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t params[] = {
+	{"ping_interval",   PARAM_INT, &my_ping_interval},
+	{"connect_timeout", PARAM_INT, &my_connect_to},
+	{"send_timeout",    PARAM_INT, &my_send_to},
+	{"receive_timeout", PARAM_INT, &my_recv_to},
+	{"retries",         PARAM_INT, &my_retries},
+	{0, 0, 0}
+};
+
+
+struct module_exports exports = {
+	"mysql",
+	cmds,
+	0,               /* RPC method */
+	params,          /*  module parameters */
+	mysql_mod_init,  /* module initialization function */
+	0,               /* response function*/
+	0,               /* destroy function */
+	0,               /* oncancel function */
+	0                /* per-child init function */
+};
+
+
+static int mysql_mod_init(void)
+{
+#if MYSQL_VERSION_ID >= 40101
+	my_client_ver = mysql_get_client_version();
+	if ((my_client_ver >= 50025) || 
+		((my_client_ver >= 40122) && 
+		 (my_client_ver < 50000))) {
+		if (my_send_to == 0) {
+			my_send_to= DEFAULT_MY_SEND_TO;
+		}
+		if (my_recv_to == 0) {
+			my_recv_to= DEFAULT_MY_RECV_TO;
+		}
+	} else if (my_recv_to || my_send_to) {
+		LOG(L_WARN, "WARNING: mysql send or received timeout set, but "
+			" not supported by the installed mysql client library"
+			" (needed at least 4.1.22 or 5.0.25, but installed %ld)\n",
+			my_client_ver);
+	}
+#else
+	if (my_recv_to || my_send_to) {
+		LOG(L_WARN, "WARNING: mysql send or received timeout set, but "
+			" not supported by the mysql client library used to compile"
+			" the mysql module (needed at least 4.1.1 but "
+			" compiled against %ld)\n", MYSQL_VERSION_ID);
+	}
+#endif
+	return 0;
+}
+
+/** @} */

+ 52 - 0
modules/db_mysql/mysql_mod.h

@@ -0,0 +1,52 @@
+/* 
+ * $Id$ 
+ *
+ * MySQL module interface
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * History:
+ * --------
+ *  2003-03-11  updated to the new module exports interface (andrei)
+ *  2003-03-16  flags export parameter added (janakj)
+ */
+
+#ifndef _MYSQL_MOD_H
+#define _MYSQL_MOD_H
+
+/** @defgroup mysql MySQL db driver
+ *  @ingroup DB_API
+ */
+/** @{ */
+extern int my_ping_interval;
+extern unsigned int my_connect_to;
+extern unsigned int my_send_to;
+extern unsigned int my_recv_to;
+extern unsigned long my_client_ver;
+extern unsigned int my_retries;
+
+/** @} */
+
+#endif /* _MYSQL_MOD_H */