Selaa lähdekoodia

Merge branch 'master' of ssh://[email protected]/sip-router into flatstore

* 'master' of ssh://[email protected]/sip-router: (194 commits)
  Support for db driver names with "db_" prefix in libsrdb2.
  Make sure mod_init from kamailio/db_mysql is called.
  Export kamailio/db_mysql parameters through the module api.
  Export db_bind_api through the module interface.
  Eliminate conflicts with files imported from ser/mysql modules.
  DB_* renamed to DB1_* in files originating from kamailio/db_mysql.
  db_res renamed to db1_res in files originating from kamailio/db_mysql.
  db_con renamed to db1_con in files originating from kamailio/db_mysql.
  Integration of both modules, first step.
  Module name changed to db_mysql.
  Changes necessary to make the module compile in the sip-router tree.
  - fixed auto_reconnect c&p error
  - add one DBG log to each drivers error condition, that the mem is freed
  - fix one DBG msg (still using module prefix), two small indention fixes
  - partial revert of commit rev5359 for db_mysql module
  - add group functionality to read content from specified sections
  - unify common rows and row allocation functionality in the DB API core
  - change behaviour of db_str2val, this now copy strings
  - move db_mysql and db_unixodbc str2val implementation to the DB core,
  - fix a few errors in doxygen documentation
  ...
Jan Janak 16 vuotta sitten
vanhempi
commit
abae24ebc3

+ 28 - 14
lib/srdb2/db_drv.c

@@ -62,28 +62,42 @@ void db_drv_free(db_drv_t* ptr)
  */
 int db_drv_func(db_drv_func_t* func, str* module, char* func_name)
 {
-	char* buf;
-
-	buf = pkg_malloc(module->len + 1);
-	if (buf == NULL) {
+	static str prefix = STR_STATIC_INIT("db_");
+	char* buf = NULL, *name;
+	
+	if ((buf = pkg_malloc(prefix.len + module->len + 1)) == NULL) {
 		LOG(L_ERR, "db_drv_func: No memory left\n");
-		return -1;
+		goto error;
 	}
-	memcpy(buf, module->s, module->len);
-	buf[module->len] = '\0';
-
-	if (find_module_by_name(buf) == 0) {
-		ERR("db_drv_func: database driver for '%s' not found\n", buf);
-		pkg_free(buf);
-		return -1;
+	
+	memcpy(buf, prefix.s, prefix.len);
+	memcpy(buf + prefix.len, module->s, module->len);
+	buf[prefix.len + module->len] = '\0';
+	
+	/* First try to find the module with prefix "db_" */
+	name = buf;
+	if (find_module_by_name(name) == 0) {
+		/* Not found, so try without the prefix */
+		name = buf + prefix.len;
+		if (find_module_by_name(name) == 0) {
+			ERR("db_drv_func: database driver for '%.*s' not found\n", STR_FMT(module));
+			goto error;
+		}
 	}
-	*func = (db_drv_func_t)find_mod_export(buf, func_name, 0, 0);
-	pkg_free(buf);
+	
+	*func = (db_drv_func_t)find_mod_export(name, func_name, 0, 0);
+	
+	if (buf) pkg_free(buf);
 	if (*func) return 0;
 	else return 1;
+	
+error:
+	if (buf) pkg_free(buf);
+	return -1;
 }
 
 
+
 /*
  * Call function with name <func_name> in DB driver <module>, give
  * it pointer <db_struct> as the pointer to the corresponding DB structure

+ 25 - 0
modules/db_mysql/Makefile

@@ -0,0 +1,25 @@
+# $Id$
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=db_mysql.so
+
+# mysql.h locations (freebsd,openbsd  solaris)
+DEFS +=-DSER_MOD_INTERFACE -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
+
+SERLIBPATH=../../lib
+SER_LIBS=$(SERLIBPATH)/srdb2/srdb2 $(SERLIBPATH)/srdb1/srdb1
+
+include ../../Makefile.modules

BIN
modules/db_mysql/doc/mysql_parser.dia


+ 171 - 0
modules/db_mysql/km_README

@@ -0,0 +1,171 @@
+mysql Module
+
+Daniel-Constantin Mierla
+
+   <[email protected]>
+
+Edited by
+
+Daniel-Constantin Mierla
+
+   <[email protected]>
+
+   Copyright © 2006 voice-system.ro
+     __________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1.1. Overview
+        1.2. Dependencies
+
+              1.2.1. Kamailio Modules
+              1.2.2. External Libraries or Applications
+
+        1.3. Exported Parameters
+
+              1.3.1. ping_interval (integer)
+              1.3.2. timeout_interval (integer)
+              1.3.3. auto_reconnect (integer)
+
+        1.4. Exported Functions
+        1.5. Installation
+        1.6. Reading configuration from my.cnf
+
+   List of Examples
+
+   1.1. Set ping_interval parameter
+   1.2. Set timeout_interval parameter
+   1.3. Set auto_reconnect parameter
+   1.4. Set a my.cnf group in db_url parameter
+   1.5. Adding a kamailio group to my.cnf
+   1.6. Using [client] and specific group
+
+Chapter 1. Admin Guide
+
+1.1. Overview
+
+   This is a module which provides MySQL connectivity for
+   Kamailio. It implements the DB API defined in Kamailio.
+
+1.2. Dependencies
+
+1.2.1. Kamailio Modules
+
+   The following modules must be loaded before this module:
+     * No dependencies on other Kamailio modules.
+
+1.2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed
+   before running Kamailio with this module loaded:
+     * mysql - the development libraries forthe Mysql database. In
+       some Linux distributions named "libmysqlclient-dev".
+
+1.3. Exported Parameters
+
+1.3.1. ping_interval (integer)
+
+   Time interval in seconds to send ping messages to MySQL server
+   in order to keep the connection open.
+
+   Default value is 300 (5 min).
+
+   Example 1.1. Set ping_interval parameter
+...
+modparam("db_mysql", "ping_interval", 600)
+...
+
+1.3.2. timeout_interval (integer)
+
+   Time interval (in seconds) after that an connection attempt,
+   read or write request is aborted. The value counts three times,
+   as several retries are done from the driver before it gives up.
+
+   The read timeout parameter is ignored on MySQL driver versions
+   prior to "5.1.12", "5.0.25" and "4.1.22". The write timeout
+   parameter is ignored on versions prior to "5.1.12" and
+   "5.0.25", the "4.1" release don't support it at all.
+
+   Default value is 2 (6 sec).
+
+   Example 1.2. Set timeout_interval parameter
+...
+modparam("db_mysql", "timeout_interval", 2)
+...
+
+1.3.3. auto_reconnect (integer)
+
+   Configure whether the module should automatically reconnect to
+   MySQL server if the connection was lost.
+
+   Default value is 1 (1 - on / 0 - off).
+
+   Example 1.3. Set auto_reconnect parameter
+...
+modparam("db_mysql", "auto_reconnect", 0)
+...
+
+1.4. Exported Functions
+
+   No function exported to be used from configuration file.
+
+1.5. Installation
+
+   Because it dependes on an external library, the mysql module is
+   not compiled and installed by default. You can use one of these
+   options.
+     * - edit the "Makefile" and remove "db_mysql" from
+       "excluded_modules" list. Then follow the standard procedure
+       to install Kamailio: "make all; make install".
+     * - from command line use: 'make all
+       include_modules="db_mysql"; make install
+       include_modules="db_mysql"'.
+
+1.6. Reading configuration from my.cnf
+
+   In order to take into account specific mysql client options, a
+   my.cnf config group can be passed using the db_url module
+   parameter. This is done by setting [group] in front of or
+   instead of the host part. The following examples are valid
+   db_url definitions, which include a my.cnf group:
+     * mysql://user:pass@[group]host:port/db
+     * mysql://user:pass@[group]:port/db
+     * mysql://user:pass@[group]/db
+     * mysql://[group]/db
+
+   Example 1.4. Set a my.cnf group in db_url parameter
+...
+modparam("usrloc", "db_url", "mysql://[kamailio]/kamailio)
+...
+
+   Example 1.5. Adding a kamailio group to my.cnf
+...
+[kamailio]
+socket = /path/to/mysql.sock
+user = kamailiouser
+password = kamailiopass
+default-character-set = utf8
+...
+
+   In addition to the given group, also the [client] section is
+   read, in the order given in my.cnf. So if you for example
+   specify a socket in both your specific group and the client
+   group, then the value is taken from the last one.
+
+   Example 1.6. Using [client] and specific group
+...
+[client]
+socket = /var/run/mysql/mysqld.sock
+
+[kamailio]
+socket = /path/to/mysqld.sock
+user = kamailiouser
+password = kamailiopass
+default-character-set = utf8
+...
+
+   In the example given above, the socket /path/to/mysqld.sock is
+   used by Kamailio because both [kamailio] and [client] define
+   this option, and the latter overwrites the first.

+ 118 - 0
modules/db_mysql/km_db_mysql.c

@@ -0,0 +1,118 @@
+/* 
+ * $Id$ 
+ *
+ * MySQL module interface
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2008 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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)
+ */
+
+/*! \file
+ *  \brief DB_MYSQL :: Core
+ *  \ingroup db_mysql
+ *  Module: \ref db_mysql
+ */
+
+/*! \defgroup db_mysql DB_MYSQL :: the MySQL driver for Kamailio
+ *  \brief The Kamailio database interface to the MySQL database
+ *  - http://www.mysql.org
+ *
+ */
+
+#include "../../sr_module.h"
+#include "km_dbase.h"
+#include "km_db_mysql.h"
+
+#include <mysql/mysql.h>
+
+unsigned int db_mysql_timeout_interval = 2;   /* Default is 6 seconds */
+unsigned int db_mysql_auto_reconnect = 1;     /* Default is enabled   */
+
+/* MODULE_VERSION */
+
+/*! \brief
+ * MySQL database module interface
+ */
+static kam_cmd_export_t cmds[] = {
+	{"db_bind_api",         (cmd_function)db_mysql_bind_api,      0, 0, 0, 0},
+	{0, 0, 0, 0, 0, 0}
+};
+
+/*! \brief
+ * Exported parameters
+ */
+static param_export_t params[] = {
+/*	{"ping_interval",    INT_PARAM, &db_mysql_ping_interval}, */
+	{"timeout_interval", INT_PARAM, &db_mysql_timeout_interval},
+	{"auto_reconnect",   INT_PARAM, &db_mysql_auto_reconnect},
+	{0, 0, 0}
+};
+
+struct kam_module_exports kam_exports = {	
+	"db_mysql",
+	DEFAULT_DLFLAGS, /* dlopen flags */
+	cmds,
+	params,          /*  module parameters */
+	0,               /* exported statistics */
+	0,               /* exported MI functions */
+	0,               /* exported pseudo-variables */
+	0,               /* extra processes */
+	kam_mysql_mod_init,  /* module initialization function */
+	0,               /* response function*/
+	0,               /* destroy function */
+	0                /* per-child init function */
+};
+
+
+int kam_mysql_mod_init(void)
+{
+	LM_DBG("MySQL client version is %s\n", mysql_get_client_info());
+	return 0;
+}
+
+int db_mysql_bind_api(db_func_t *dbb)
+{
+	if(dbb==NULL)
+		return -1;
+
+	memset(dbb, 0, sizeof(db_func_t));
+
+	dbb->use_table        = db_mysql_use_table;
+	dbb->init             = db_mysql_init;
+	dbb->close            = db_mysql_close;
+	dbb->query            = db_mysql_query;
+	dbb->fetch_result     = db_mysql_fetch_result;
+	dbb->raw_query        = db_mysql_raw_query;
+	dbb->free_result      = db_mysql_free_result;
+	dbb->insert           = db_mysql_insert;
+	dbb->delete           = db_mysql_delete;
+	dbb->update           = db_mysql_update;
+	dbb->replace          = db_mysql_replace;
+	dbb->last_inserted_id = db_last_inserted_id;
+	dbb->insert_update    = db_insert_update;
+
+	return 0;
+}
+

+ 50 - 0
modules/db_mysql/km_db_mysql.h

@@ -0,0 +1,50 @@
+/* 
+ * $Id$ 
+ *
+ * MySQL module interface
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2008 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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)
+ */
+
+/*! \file
+ *  \brief DB_MYSQL :: Core
+ *  \ingroup db_mysql
+ *  Module: \ref db_mysql
+ */
+
+
+#ifndef KM_DB_MOD_H
+#define KM_DB_MOD_H
+
+#include "../../lib/srdb1/db.h"
+
+extern unsigned int db_mysql_timeout_interval;
+extern unsigned int db_mysql_auto_reconnect;
+
+int db_mysql_bind_api(db_func_t *dbb);
+
+int kam_mysql_mod_init(void);
+
+#endif /* KM_DB_MOD_H */

+ 537 - 0
modules/db_mysql/km_dbase.c

@@ -0,0 +1,537 @@
+/* 
+ * $Id$ 
+ *
+ * MySQL module core functions
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2007-2008 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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
+ */
+
+/*!
+ * \file
+ * \brief Implementation of core functions for the MySQL driver.
+ *
+ * This file contains the implementation of core functions for the MySQL
+ * database driver, for example to submit a query or fetch a result.
+ * \ingroup db_mysql
+ *  Module: \ref db_mysql
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <mysql/mysql.h>
+#include <mysql/errmsg.h>
+#include <mysql/mysql_version.h>
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "../../lib/srdb1/db_query.h"
+#include "../../lib/srdb1/db_ut.h"
+#include "mysql_mod.h"
+#include "km_val.h"
+#include "km_my_con.h"
+#include "km_res.h"
+#include "km_row.h"
+#include "km_db_mysql.h"
+#include "km_dbase.h"
+
+
+/**
+ * \brief Send a SQL query to the server.
+ *
+ * Send a SQL query to the database server. This methods tries to reconnect
+ * to the server if the connection is gone and the auto_reconnect parameter is
+ * enabled. It also issues a mysql_ping before the query to connect again after
+ * a long waiting period because for some older mysql versions the auto reconnect
+ * don't work sufficient. If auto_reconnect is enabled and the server supports it,
+ * then the mysql_ping is probably not necessary, but its safer to do it in this
+ * cases too.
+ *
+ * \param _h handle for the db
+ * \param _s executed query
+ * \return zero on success, negative value on failure
+ */
+static int db_mysql_submit_query(const db1_con_t* _h, const str* _s)
+{	
+	time_t t;
+	int i, code;
+
+	if (!_h || !_s || !_s->s) {
+		LM_ERR("invalid parameter value\n");
+		return -1;
+	}
+
+	if (my_ping_interval) {
+		t = time(0);
+		if ((t - CON_TIMESTAMP(_h)) > my_ping_interval) {
+			if (mysql_ping(CON_CONNECTION(_h))) {
+				LM_WARN("driver error on ping: %s\n", mysql_error(CON_CONNECTION(_h)));
+			}
+		}
+		/*
+		 * We're doing later a query anyway that will reset the timout of the server,
+		 * so it makes sense to set the timestamp value to the actual time in order
+		 * to prevent unnecessary pings.
+		 */
+		CON_TIMESTAMP(_h) = t;
+	}
+
+	/* screws up the terminal when the query contains a BLOB :-( (by bogdan)
+	 * LM_DBG("submit_query(): %.*s\n", _s->len, _s->s);
+	 */
+
+	/* When a server connection is lost and a query is attempted, most of
+	 * the time the query will return a CR_SERVER_LOST, then at the second
+	 * attempt to execute it, the mysql lib will reconnect and succeed.
+	 * However is a few cases, the first attempt returns CR_SERVER_GONE_ERROR
+	 * the second CR_SERVER_LOST and only the third succeeds.
+	 * Thus the 3 in the loop count. Increasing the loop count over this
+	 * value shouldn't be needed, but it doesn't hurt either, since the loop
+	 * will most of the time stop at the second or sometimes at the third
+	 * iteration.
+	 */
+	for (i=0; i < (db_mysql_auto_reconnect ? 3 : 1); i++) {
+		if (mysql_real_query(CON_CONNECTION(_h), _s->s, _s->len) == 0) {
+			return 0;
+		}
+		code = mysql_errno(CON_CONNECTION(_h));
+		if (code != CR_SERVER_GONE_ERROR && code != CR_SERVER_LOST) {
+			break;
+		}
+	}
+	LM_ERR("driver error on query: %s\n", mysql_error(CON_CONNECTION(_h)));
+	return -2;
+}
+
+
+
+/**
+ * Initialize the database module.
+ * No function should be called before this
+ * \param _url URL used for initialization
+ * \return zero on success, negative value on failure
+ */
+db1_con_t* db_mysql_init(const str* _url)
+{
+	return db_do_init(_url, (void *)db_mysql_new_connection);
+}
+
+
+/**
+ * Shut down the database module.
+ * No function should be called after this
+ * \param _h handle to the closed connection
+ * \return zero on success, negative value on failure
+ */
+void db_mysql_close(db1_con_t* _h)
+{
+	db_do_close(_h, db_mysql_free_connection);
+}
+
+
+/**
+ * Retrieve a result set
+ * \param _h handle to the database
+ * \param _r result set that should be retrieved
+ * \return zero on success, negative value on failure
+ */
+static int db_mysql_store_result(const db1_con_t* _h, db1_res_t** _r)
+{
+	if ((!_h) || (!_r)) {
+		LM_ERR("invalid parameter value\n");
+		return -1;
+	}
+
+	*_r = db_new_result();
+	if (*_r == 0) {
+		LM_ERR("no memory left\n");
+		return -2;
+	}
+
+	CON_RESULT(_h) = mysql_store_result(CON_CONNECTION(_h));
+	if (!CON_RESULT(_h)) {
+		if (mysql_field_count(CON_CONNECTION(_h)) == 0) {
+			(*_r)->col.n = 0;
+			(*_r)->n = 0;
+			goto done;
+		} else {
+			LM_ERR("driver error: %s\n", mysql_error(CON_CONNECTION(_h)));
+			db_free_result(*_r);
+			*_r = 0;
+			return -3;
+		}
+	}
+
+	if (db_mysql_convert_result(_h, *_r) < 0) {
+		LM_ERR("error while converting result\n");
+		LM_DBG("freeing result set at %p\n", _r);
+		pkg_free(*_r);
+		*_r = 0;
+		/* all mem on openser API side is already freed by
+		 * db_mysql_convert_result in case of error, but we also need
+		 * to free the mem from the mysql lib side */
+		mysql_free_result(CON_RESULT(_h));
+#if (MYSQL_VERSION_ID >= 40100)
+		while( mysql_next_result( CON_CONNECTION(_h) ) > 0 ) {
+			MYSQL_RES *res = mysql_store_result( CON_CONNECTION(_h) );
+			mysql_free_result(res);
+		}
+#endif
+		CON_RESULT(_h) = 0;
+		return -4;
+	}
+
+done:
+#if (MYSQL_VERSION_ID >= 40100)
+	while( mysql_next_result( CON_CONNECTION(_h) ) > 0 ) {
+		MYSQL_RES *res = mysql_store_result( CON_CONNECTION(_h) );
+		mysql_free_result(res);
+	}
+#endif
+
+	return 0;
+}
+
+
+/**
+ * Release a result set from memory.
+ * \param _h handle to the database
+ * \param _r result set that should be freed
+ * \return zero on success, negative value on failure
+ */
+int db_mysql_free_result(db1_con_t* _h, db1_res_t* _r)
+{
+     if ((!_h) || (!_r)) {
+	     LM_ERR("invalid parameter value\n");
+	     return -1;
+     }
+
+     if (db_free_result(_r) < 0) {
+	     LM_ERR("unable to free result structure\n");
+	     return -1;
+     }
+     mysql_free_result(CON_RESULT(_h));
+     CON_RESULT(_h) = 0;
+     return 0;
+}
+
+
+/**
+ * Query a table for specified rows.
+ * \param _h structure representing database connection
+ * \param _k key names
+ * \param _op operators
+ *\param  _v values of the keys that must match
+ * \param _c column names to return
+ * \param _n number of key=values pairs to compare
+ * \param _nc number of columns to return
+ * \param _o order by the specified column
+ * \param _r pointer to a structure representing the result
+ * \return zero on success, negative value on failure
+ */
+int db_mysql_query(const db1_con_t* _h, const db_key_t* _k, const db_op_t* _op,
+	     const db_val_t* _v, const db_key_t* _c, const int _n, const int _nc,
+	     const db_key_t _o, db1_res_t** _r)
+{
+	return db_do_query(_h, _k, _op, _v, _c, _n, _nc, _o, _r,
+	db_mysql_val2str, db_mysql_submit_query, db_mysql_store_result);
+}
+
+/**
+ * \brief Gets a partial result set, fetch rows from a result
+ *
+ * Gets a partial result set, fetch a number of rows from a database result.
+ * This function initialize the given result structure on the first run, and
+ * fetches the nrows number of rows. On subsequenting runs, it uses the
+ * existing result and fetches more rows, until it reaches the end of the
+ * result set. Because of this the result needs to be null in the first
+ * invocation of the function. If the number of wanted rows is zero, the
+ * function returns anything with a result of zero.
+ * \param _h structure representing the database connection
+ * \param _r pointer to a structure representing the result
+ * \param nrows number of fetched rows
+ * \return zero on success, negative value on failure
+ */
+int db_mysql_fetch_result(const db1_con_t* _h, db1_res_t** _r, const int nrows)
+{
+	int rows, i;
+
+	if (!_h || !_r || nrows < 0) {
+		LM_ERR("Invalid parameter value\n");
+		return -1;
+	}
+
+	/* exit if the fetch count is zero */
+	if (nrows == 0) {
+		db_free_result(*_r);
+		*_r = 0;
+		return 0;
+	}
+
+	if(*_r==0) {
+		/* Allocate a new result structure */
+		*_r = db_new_result();
+		if (*_r == 0) {
+			LM_ERR("no memory left\n");
+			return -2;
+		}
+
+		CON_RESULT(_h) = mysql_store_result(CON_CONNECTION(_h));
+		if (!CON_RESULT(_h)) {
+			if (mysql_field_count(CON_CONNECTION(_h)) == 0) {
+				(*_r)->col.n = 0;
+				(*_r)->n = 0;
+				return 0;
+			} else {
+				LM_ERR("driver error: %s\n", mysql_error(CON_CONNECTION(_h)));
+				db_free_result(*_r);
+				*_r = 0;
+				return -3;
+			}
+		}
+		if (db_mysql_get_columns(_h, *_r) < 0) {
+			LM_ERR("error while getting column names\n");
+			return -4;
+		}
+
+		RES_NUM_ROWS(*_r) = mysql_num_rows(CON_RESULT(_h));
+		if (!RES_NUM_ROWS(*_r)) {
+			LM_DBG("no rows returned from the query\n");
+			RES_ROWS(*_r) = 0;
+			return 0;
+		}
+
+	} else {
+		/* free old rows */
+		if(RES_ROWS(*_r)!=0)
+			db_free_rows(*_r);
+		RES_ROWS(*_r) = 0;
+		RES_ROW_N(*_r) = 0;
+	}
+
+	/* determine the number of rows remaining to be processed */
+	rows = RES_NUM_ROWS(*_r) - RES_LAST_ROW(*_r);
+
+	/* If there aren't any more rows left to process, exit */
+	if(rows<=0)
+		return 0;
+
+	/* if the fetch count is less than the remaining rows to process                 */
+	/* set the number of rows to process (during this call) equal to the fetch count */
+	if(nrows < rows)
+		rows = nrows;
+
+	RES_ROW_N(*_r) = rows;
+
+	LM_DBG("converting row %d of %d count %d\n", RES_LAST_ROW(*_r),
+			RES_NUM_ROWS(*_r), RES_ROW_N(*_r));
+
+	RES_ROWS(*_r) = (struct db_row*)pkg_malloc(sizeof(db_row_t) * rows);
+	if (!RES_ROWS(*_r)) {
+		LM_ERR("no memory left\n");
+		return -5;
+	}
+
+	for(i = 0; i < rows; i++) {
+		CON_ROW(_h) = mysql_fetch_row(CON_RESULT(_h));
+		if (!CON_ROW(_h)) {
+			LM_ERR("driver error: %s\n", mysql_error(CON_CONNECTION(_h)));
+			RES_ROW_N(*_r) = i;
+			db_free_rows(*_r);
+			return -6;
+		}
+		if (db_mysql_convert_row(_h, *_r, &(RES_ROWS(*_r)[i])) < 0) {
+			LM_ERR("error while converting row #%d\n", i);
+			RES_ROW_N(*_r) = i;
+			db_free_rows(*_r);
+			return -7;
+		}
+	}
+
+	/* update the total number of rows processed */
+	RES_LAST_ROW(*_r) += rows;
+	return 0;
+}
+
+/**
+ * Execute a raw SQL query.
+ * \param _h handle for the database
+ * \param _s raw query string
+ * \param _r result set for storage
+ * \return zero on success, negative value on failure
+ */
+int db_mysql_raw_query(const db1_con_t* _h, const str* _s, db1_res_t** _r)
+{
+	return db_do_raw_query(_h, _s, _r, db_mysql_submit_query,
+	db_mysql_store_result);
+}
+
+
+/**
+ * Insert a row into a specified table.
+ * \param _h structure representing database connection
+ * \param _k key names
+ * \param _v values of the keys
+ * \param _n number of key=value pairs
+ * \return zero on success, negative value on failure
+ */
+int db_mysql_insert(const db1_con_t* _h, const db_key_t* _k, const db_val_t* _v, const int _n)
+{
+	return db_do_insert(_h, _k, _v, _n, db_mysql_val2str,
+	db_mysql_submit_query);
+}
+
+
+/**
+ * Delete a row from the specified table
+ * \param _h structure representing database connection
+ * \param _k key names
+ * \param _o operators
+ * \param _v values of the keys that must match
+ * \param _n number of key=value pairs
+ * \return zero on success, negative value on failure
+ */
+int db_mysql_delete(const db1_con_t* _h, const db_key_t* _k, const db_op_t* _o,
+	const db_val_t* _v, const int _n)
+{
+	return db_do_delete(_h, _k, _o, _v, _n, db_mysql_val2str,
+	db_mysql_submit_query);
+}
+
+
+/**
+ * Update some rows in the specified table
+ * \param _h structure representing database connection
+ * \param _k key names
+ * \param _o operators
+ * \param _v values of the keys that must match
+ * \param _uk updated columns
+ * \param _uv updated values of the columns
+ * \param _n number of key=value pairs
+ * \param _un number of columns to update
+ * \return zero on success, negative value on failure
+ */
+int db_mysql_update(const db1_con_t* _h, const db_key_t* _k, const db_op_t* _o, 
+	const db_val_t* _v, const db_key_t* _uk, const db_val_t* _uv, const int _n, 
+	const int _un)
+{
+	return db_do_update(_h, _k, _o, _v, _uk, _uv, _n, _un, db_mysql_val2str,
+	db_mysql_submit_query);
+}
+
+
+/**
+ * Just like insert, but replace the row if it exists.
+ * \param _h database handle
+ * \param _k key names
+ * \param _v values of the keys that must match
+ * \param _n number of key=value pairs
+ * \return zero on success, negative value on failure
+ */
+int db_mysql_replace(const db1_con_t* _h, const db_key_t* _k, const db_val_t* _v, const int _n)
+{
+	return db_do_replace(_h, _k, _v, _n, db_mysql_val2str,
+	db_mysql_submit_query);
+}
+
+
+/**
+ * Returns the last inserted ID.
+ * \param _h database handle
+ * \return returns the ID as integer or returns 0 if the previous statement
+ * does not use an AUTO_INCREMENT value.
+ */
+int db_last_inserted_id(const db1_con_t* _h)
+{
+	if (!_h) {
+		LM_ERR("invalid parameter value\n");
+		return -1;
+	}
+	return mysql_insert_id(CON_CONNECTION(_h));
+}
+
+
+ /**
+  * Insert a row into a specified table, update on duplicate key.
+  * \param _h structure representing database connection
+  * \param _k key names
+  * \param _v values of the keys
+  * \param _n number of key=value pairs
+ */
+ int db_insert_update(const db1_con_t* _h, const db_key_t* _k, const db_val_t* _v,
+	const int _n)
+ {
+	int off, ret;
+	static str  sql_str;
+	static char sql_buf[SQL_BUF_LEN];
+ 
+	if ((!_h) || (!_k) || (!_v) || (!_n)) {
+		LM_ERR("invalid parameter value\n");
+		return -1;
+	}
+ 
+	ret = snprintf(sql_buf, SQL_BUF_LEN, "insert into %.*s (", CON_TABLE(_h)->len, CON_TABLE(_h)->s);
+	if (ret < 0 || ret >= SQL_BUF_LEN) goto error;
+	off = ret;
+
+	ret = db_print_columns(sql_buf + off, SQL_BUF_LEN - off, _k, _n);
+	if (ret < 0) return -1;
+	off += ret;
+
+	ret = snprintf(sql_buf + off, SQL_BUF_LEN - off, ") values (");
+	if (ret < 0 || ret >= (SQL_BUF_LEN - off)) goto error;
+	off += ret;
+	ret = db_print_values(_h, sql_buf + off, SQL_BUF_LEN - off, _v, _n, db_mysql_val2str);
+	if (ret < 0) return -1;
+	off += ret;
+
+	*(sql_buf + off++) = ')';
+	
+	ret = snprintf(sql_buf + off, SQL_BUF_LEN - off, " on duplicate key update ");
+	if (ret < 0 || ret >= (SQL_BUF_LEN - off)) goto error;
+	off += ret;
+	
+	ret = db_print_set(_h, sql_buf + off, SQL_BUF_LEN - off, _k, _v, _n, db_mysql_val2str);
+	if (ret < 0) return -1;
+	off += ret;
+	
+	sql_str.s = sql_buf;
+	sql_str.len = off;
+ 
+	if (db_mysql_submit_query(_h, &sql_str) < 0) {
+		LM_ERR("error while submitting query\n");
+		return -2;
+	}
+	return 0;
+
+error:
+	LM_ERR("error while preparing insert_update operation\n");
+	return -1;
+}
+
+
+/**
+ * Store the name of table that will be used by subsequent database functions
+ * \param _h database handle
+ * \param _t table name
+ * \return zero on success, negative value on failure
+ */
+int db_mysql_use_table(db1_con_t* _h, const str* _t)
+{
+	return db_use_table(_h, _t);
+}

+ 128 - 0
modules/db_mysql/km_dbase.h

@@ -0,0 +1,128 @@
+/*
+ * $Id$
+ *
+ * MySQL module core functions
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2008 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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
+ */
+
+/*! \file
+ *  \brief DB_MYSQL :: Core
+ *  \ingroup db_mysql
+ *  Module: \ref db_mysql
+ */
+
+
+
+#ifndef KM_DBASE_H
+#define KM_DBASE_H
+
+
+#include "../../lib/srdb1/db_con.h"
+#include "../../lib/srdb1/db_res.h"
+#include "../../lib/srdb1/db_key.h"
+#include "../../lib/srdb1/db_op.h"
+#include "../../lib/srdb1/db_val.h"
+#include "../../str.h"
+
+/*! \brief
+ * Initialize database connection
+ */
+db1_con_t* db_mysql_init(const str* _sqlurl);
+
+
+/*! \brief
+ * Close a database connection
+ */
+void db_mysql_close(db1_con_t* _h);
+
+
+/*! \brief
+ * Free all memory allocated by get_result
+ */
+int db_mysql_free_result(db1_con_t* _h, db1_res_t* _r);
+
+
+/*! \brief
+ * Do a query
+ */
+int db_mysql_query(const db1_con_t* _h, const db_key_t* _k, const db_op_t* _op,
+	     const db_val_t* _v, const db_key_t* _c, const int _n, const int _nc,
+	     const db_key_t _o, db1_res_t** _r);
+
+
+/*! \brief
+ * fetch rows from a result
+ */
+int db_mysql_fetch_result(const db1_con_t* _h, db1_res_t** _r, const int nrows);
+
+
+/*! \brief
+ * Raw SQL query
+ */
+int db_mysql_raw_query(const db1_con_t* _h, const str* _s, db1_res_t** _r);
+
+
+/*! \brief
+ * Insert a row into table
+ */
+int db_mysql_insert(const db1_con_t* _h, const db_key_t* _k, const db_val_t* _v, const int _n);
+
+
+/*! \brief
+ * Delete a row from table
+ */
+int db_mysql_delete(const db1_con_t* _h, const db_key_t* _k, const 
+	db_op_t* _o, const db_val_t* _v, const int _n);
+
+
+/*! \brief
+ * Update a row in table
+ */
+int db_mysql_update(const db1_con_t* _h, const db_key_t* _k, const db_op_t* _o,
+	const db_val_t* _v, const db_key_t* _uk, const db_val_t* _uv, const int _n,
+	const int _un);
+
+
+/*! \brief
+ * Just like insert, but replace the row if it exists
+ */
+int db_mysql_replace(const db1_con_t* handle, const db_key_t* keys, const db_val_t* vals, const int n);
+
+/*! \brief
+ * Returns the last inserted ID
+ */
+int db_last_inserted_id(const db1_con_t* _h);
+
+/*! \brief
+ * Insert a row into table, update on duplicate key
+ */
+int db_insert_update(const db1_con_t* _h, const db_key_t* _k, const db_val_t* _v,
+	const int _n);
+
+
+/*! \brief
+ * Store name of table that will be used by
+ * subsequent database functions
+ */
+int db_mysql_use_table(db1_con_t* _h, const str* _t);
+
+
+#endif /* KM_DBASE_H */

+ 41 - 0
modules/db_mysql/km_doc/db_mysql.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+
+<!ENTITY admin SYSTEM "db_mysql_admin.xml">
+<!ENTITY faq SYSTEM "../../../doc/module_faq.xml">
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../doc/entities.xml">
+%docentities;
+
+]>
+
+<book>
+    <bookinfo>
+	<title>mysql Module</title>
+	<productname class="trade">&kamailioname;</productname>
+	<authorgroup>
+	    <author>
+		<firstname>Daniel-Constantin</firstname>
+		<surname>Mierla</surname>
+		<email>[email protected]</email>
+	    </author>
+	    <editor>
+		<firstname>Daniel-Constantin</firstname>
+		<surname>Mierla</surname>
+		    <email>[email protected]</email>
+	    </editor>
+	</authorgroup>
+	<copyright>
+	    <year>2006</year>
+	    <holder>&voicesystem;</holder>
+	</copyright>
+    </bookinfo>
+    <toc></toc>
+    
+    &admin;
+    &faq;
+    
+</book>

+ 198 - 0
modules/db_mysql/km_doc/db_mysql_admin.xml

@@ -0,0 +1,198 @@
+<!-- Module User's Guide -->
+
+<chapter>
+	
+	<title>&adminguide;</title>
+	
+	<section>
+	<title>Overview</title>
+	<para>
+		This is a module which provides MySQL connectivity for Kamailio.
+		It implements the DB API defined in Kamailio.
+	</para>
+	</section>
+
+	<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The following modules must be loaded before this module:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>No dependencies on other &kamailio; modules</emphasis>.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed before running
+		&kamailio; with this module loaded:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>mysql</emphasis> - the development libraries forthe Mysql database. In some Linux distributions named "libmysqlclient-dev".
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	</section>
+	<section>
+	<title>Exported Parameters</title>
+	<section>
+		<title><varname>ping_interval</varname> (integer)</title>
+		<para>
+		Time interval in seconds to send ping messages to MySQL server in order to keep
+		the connection open.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 300 (5 min).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>ping_interval</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("db_mysql", "ping_interval", 600)
+...
+</programlisting>
+		</example>
+	</section>
+		<section>
+		<title><varname>timeout_interval</varname> (integer)</title>
+		<para>
+		Time interval (in seconds) after that an connection attempt, read or write request
+		is aborted. The value counts three times, as several retries are done
+		from the driver before it gives up.
+		</para>
+		<para>
+		The read timeout parameter is ignored on MySQL driver versions prior to
+		<quote>5.1.12</quote>, <quote>5.0.25</quote> and <quote>4.1.22</quote>.
+		The write timeout parameter is ignored on versions prior to <quote>5.1.12</quote>
+		and <quote>5.0.25</quote>, the <quote>4.1</quote> release don't support it at all.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 2 (6 sec).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>timeout_interval</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("db_mysql", "timeout_interval", 2)
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>auto_reconnect</varname> (integer)</title>
+		<para>
+		Configure whether the module should automatically reconnect to MySQL server if the
+		connection was lost.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 1 (1 - on / 0 - off).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>auto_reconnect</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("db_mysql", "auto_reconnect", 0)
+...
+</programlisting>
+		</example>
+	</section>
+	</section>
+	<section>
+	<title>Exported Functions</title>
+		<para>
+		No function exported to be used from configuration file.
+		</para>
+	</section>
+	<section>
+	<title>Installation</title>
+		<para>
+		Because it dependes on an external library, the mysql module is not
+		compiled and installed by default. You can use one of these options.
+		</para>
+		<itemizedlist>
+			<listitem>
+			<para>
+			- edit the "Makefile" and remove "db_mysql" from "excluded_modules"
+			list. Then follow the standard procedure to install &kamailio;:
+			"make all; make install".
+			</para>
+			</listitem>
+			<listitem>
+			<para>
+			- from command line use: 'make all include_modules="db_mysql";
+			make install include_modules="db_mysql"'.
+			</para>
+			</listitem>
+		</itemizedlist>
+	</section>
+	<section>
+		<title>Reading configuration from my.cnf</title>
+		<para>
+		In order to take into account specific mysql client options, a my.cnf config group can be passed using the <emphasis>db_url</emphasis> module parameter. This is done by setting <emphasis>[group]</emphasis> in front of or instead of the host part. The following examples are valid <emphasis>db_url</emphasis> definitions, which include a my.cnf group:
+		</para>
+
+		<itemizedlist>
+			<listitem>mysql://user:pass@[group]host:port/db</listitem>
+			<listitem>mysql://user:pass@[group]:port/db</listitem>
+			<listitem>mysql://user:pass@[group]/db</listitem>
+			<listitem>mysql://[group]/db</listitem>
+		</itemizedlist>
+		<example>
+		<title>Set a my.cnf group in <varname>db_url</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("usrloc", "db_url", "mysql://[kamailio]/kamailio)
+...
+</programlisting>
+		</example>
+		<example>
+		<title>Adding a kamailio group to my.cnf</title>
+		<programlisting format="linespecific">
+...
+[kamailio]
+socket = /path/to/mysql.sock
+user = kamailiouser
+password = kamailiopass
+default-character-set = utf8
+...
+</programlisting>
+		</example>
+		<para>
+		In addition to the given group, also the <emphasis>[client]</emphasis> section is read, in the order given in my.cnf. So if you for example specify a <emphasis>socket</emphasis> in both your specific group and the client group, then the value is taken from the last one.
+		</para>
+		<example>
+		<title>Using [client] and specific group</title>
+		<programlisting format="linespecific">
+...
+[client]
+socket = /var/run/mysql/mysqld.sock
+
+[kamailio]
+socket = /path/to/mysqld.sock
+user = kamailiouser
+password = kamailiopass
+default-character-set = utf8
+...
+</programlisting>
+		</example>
+		<para>
+		In the example given above, the socket <emphasis>/path/to/mysqld.sock</emphasis> is used by &kamailio; because both <emphasis>[kamailio]</emphasis> and <emphasis>[client]</emphasis> define this option, and the latter overwrites the first.
+		</para>
+	</section>
+</chapter>
+

BIN
modules/db_mysql/km_doc/db_mysql_parser.dia


+ 151 - 0
modules/db_mysql/km_my_con.c

@@ -0,0 +1,151 @@
+/* 
+ * $Id$
+ *
+ * Copyright (C) 2001-2004 iptel.org
+ * Copyright (C) 2008 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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
+ */
+
+/*! \file
+ *  \brief DB_MYSQL :: Connections
+ *  \ingroup db_mysql
+ *  Module: \ref db_mysql
+ */
+
+
+#include "km_my_con.h"
+#include "km_db_mysql.h"
+#include <mysql/mysql_version.h>
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "../../ut.h"
+
+
+/*! \brief
+ * Create a new connection structure,
+ * open the MySQL connection and set reference count to 1
+ */
+struct my_con* db_mysql_new_connection(const struct db_id* id)
+{
+	struct my_con* ptr;
+	char *host, *grp;
+
+	if (!id) {
+		LM_ERR("invalid parameter value\n");
+		return 0;
+	}
+
+	ptr = (struct my_con*)pkg_malloc(sizeof(struct my_con));
+	if (!ptr) {
+		LM_ERR("no private memory left\n");
+		return 0;
+	}
+
+	memset(ptr, 0, sizeof(struct my_con));
+	ptr->ref = 1;
+	
+	ptr->con = (MYSQL*)pkg_malloc(sizeof(MYSQL));
+	if (!ptr->con) {
+		LM_ERR("no private memory left\n");
+		goto err;
+	}
+
+	mysql_init(ptr->con);
+
+	if (id->host[0] == '[' && (host = strchr(id->host, ']')) != NULL) {
+		grp = id->host + 1;
+		*host = '\0';
+		if (host != id->host + strlen(id->host)-1) {
+			host += 1; // host found after closing bracket
+		}
+		else {
+			// let mysql read host info from my.cnf
+			// (defaults to "localhost")
+			host = NULL;
+		}
+		// read [client] and [<grp>] sections in the order
+		// given in my.cnf
+		mysql_options(ptr->con, MYSQL_READ_DEFAULT_GROUP, grp);
+	}
+	else {
+		host = id->host;
+	}
+
+	if (id->port) {
+		LM_DBG("opening connection: mysql://xxxx:xxxx@%s:%d/%s\n", ZSW(host),
+			id->port, ZSW(id->database));
+	} else {
+		LM_DBG("opening connection: mysql://xxxx:xxxx@%s/%s\n", ZSW(host),
+			ZSW(id->database));
+	}
+
+	// set connect, read and write timeout, the value counts three times
+	mysql_options(ptr->con, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&db_mysql_timeout_interval);
+	mysql_options(ptr->con, MYSQL_OPT_READ_TIMEOUT, (const char *)&db_mysql_timeout_interval);
+	mysql_options(ptr->con, MYSQL_OPT_WRITE_TIMEOUT, (const char *)&db_mysql_timeout_interval);
+
+#if (MYSQL_VERSION_ID >= 40100)
+	if (!mysql_real_connect(ptr->con, host, id->username, id->password,
+				id->database, id->port, 0, CLIENT_MULTI_STATEMENTS)) {
+#else
+	if (!mysql_real_connect(ptr->con, host, id->username, id->password,
+				id->database, id->port, 0, 0)) {
+#endif
+		LM_ERR("driver error: %s\n", mysql_error(ptr->con));
+		mysql_close(ptr->con);
+		goto err;
+	}
+	/* force reconnection if enabled */
+	if (db_mysql_auto_reconnect)
+		ptr->con->reconnect = 1;
+	else 
+		ptr->con->reconnect = 0;
+
+	LM_DBG("connection type is %s\n", mysql_get_host_info(ptr->con));
+	LM_DBG("protocol version is %d\n", mysql_get_proto_info(ptr->con));
+	LM_DBG("server version is %s\n", mysql_get_server_info(ptr->con));
+
+	ptr->timestamp = time(0);
+	ptr->id = (struct db_id*)id;
+	return ptr;
+
+ err:
+	if (ptr && ptr->con) pkg_free(ptr->con);
+	if (ptr) pkg_free(ptr);
+	return 0;
+}
+
+
+/*! \brief
+ * Close the connection and release memory
+ */
+void db_mysql_free_connection(struct pool_con* con)
+{
+	if (!con) return;
+
+	struct my_con * _c;
+	_c = (struct my_con*) con;
+
+	if (_c->res) mysql_free_result(_c->res);
+	if (_c->id) free_db_id(_c->id);
+	if (_c->con) {
+		mysql_close(_c->con);
+		pkg_free(_c->con);
+	}
+	pkg_free(_c);
+}

+ 75 - 0
modules/db_mysql/km_my_con.h

@@ -0,0 +1,75 @@
+/* 
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2008 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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
+ */
+
+/*! \file
+ *  \brief DB_MYSQL :: Core
+ *  \ref my_con.c
+ *  \ingroup db_mysql
+ *  Module: \ref db_mysql
+ */
+
+
+#ifndef KM_MY_CON_H
+#define KM_MY_CON_H
+
+#include "../../lib/srdb1/db_pool.h"
+#include "../../lib/srdb1/db_id.h"
+
+#include <time.h>
+#include <mysql/mysql.h>
+
+
+struct my_con {
+	struct db_id* id;        /*!< Connection identifier */
+	unsigned int ref;        /*!< Reference count */
+	struct pool_con* next;   /*!< Next connection in the pool */
+
+	MYSQL_RES* res;          /*!< Actual result */
+	MYSQL* con;              /*!< Connection representation */
+	MYSQL_ROW row;           /*!< Actual row in the result */
+	time_t timestamp;        /*!< Timestamp of last query */
+};
+
+
+/*
+ * Some convenience wrappers
+ */
+#define CON_RESULT(db_con)     (((struct my_con*)((db_con)->tail))->res)
+#define CON_CONNECTION(db_con) (((struct my_con*)((db_con)->tail))->con)
+#define CON_ROW(db_con)        (((struct my_con*)((db_con)->tail))->row)
+#define CON_TIMESTAMP(db_con)  (((struct my_con*)((db_con)->tail))->timestamp)
+
+
+/*! \brief
+ * Create a new connection structure,
+ * open the MySQL connection and set reference count to 1
+ */
+struct my_con* db_mysql_new_connection(const struct db_id* id);
+
+
+/*! \brief
+ * Close the connection and release memory
+ */
+void db_mysql_free_connection(struct pool_con* con);
+
+#endif /* KM_MY_CON_H */

+ 223 - 0
modules/db_mysql/km_res.c

@@ -0,0 +1,223 @@
+/* 
+ * $Id$ 
+ *
+ * MySQL module result related functions
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2007-2008 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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
+ */
+
+
+/*! \file
+ *  \brief DB_MYSQL :: Result related functions
+ *  \ingroup db_mysql
+ *  Module: \ref db_mysql
+ */
+
+
+#include <string.h>
+#include <mysql/mysql.h>
+#include "../../lib/srdb1/db_res.h"
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "km_row.h"
+#include "km_my_con.h"
+#include "km_res.h"
+
+
+/*!
+ * \brief Get and convert columns from a result
+ *
+ * Get and convert columns from a result, fills the result structure
+ * with data from the database.
+ * \param _h database connection
+ * \param _r database result set
+ * \return 0 on success, negative on failure
+ */
+int db_mysql_get_columns(const db1_con_t* _h, db1_res_t* _r)
+{
+	int col;
+	MYSQL_FIELD* fields;
+
+	if ((!_h) || (!_r)) {
+		LM_ERR("invalid parameter\n");
+		return -1;
+	}
+
+	RES_COL_N(_r) = mysql_field_count(CON_CONNECTION(_h));
+	if (!RES_COL_N(_r)) {
+		LM_ERR("no columns returned from the query\n");
+		return -2;
+	} else {
+		LM_DBG("%d columns returned from the query\n", RES_COL_N(_r));
+	}
+	
+	if (db_allocate_columns(_r, RES_COL_N(_r)) != 0) {
+		LM_ERR("could not allocate columns\n");
+		return -3;
+	}
+
+	fields = mysql_fetch_fields(CON_RESULT(_h));
+	for(col = 0; col < RES_COL_N(_r); col++) {
+		RES_NAMES(_r)[col] = (str*)pkg_malloc(sizeof(str));
+		if (! RES_NAMES(_r)[col]) {
+			LM_ERR("no private memory left\n");
+			db_free_columns(_r);
+			return -4;
+		}
+		LM_DBG("allocate %lu bytes for RES_NAMES[%d] at %p\n",
+				(unsigned long)sizeof(str), col, RES_NAMES(_r)[col]);
+
+		/* The pointer that is here returned is part of the result structure. */
+		RES_NAMES(_r)[col]->s = fields[col].name;
+		RES_NAMES(_r)[col]->len = strlen(fields[col].name);
+
+		LM_DBG("RES_NAMES(%p)[%d]=[%.*s]\n", RES_NAMES(_r)[col], col,
+				RES_NAMES(_r)[col]->len, RES_NAMES(_r)[col]->s);
+
+		switch(fields[col].type) {
+			case MYSQL_TYPE_TINY:
+			case MYSQL_TYPE_SHORT:
+			case MYSQL_TYPE_LONG:
+			case MYSQL_TYPE_INT24:
+			case MYSQL_TYPE_TIMESTAMP:
+				LM_DBG("use DB1_INT result type\n");
+				RES_TYPES(_r)[col] = DB1_INT;
+				break;
+
+			case MYSQL_TYPE_LONGLONG:
+				LM_DBG("use DB1_BIGINT result type\n");
+				RES_TYPES(_r)[col] = DB1_BIGINT;
+				break;
+
+			case MYSQL_TYPE_FLOAT:
+			case MYSQL_TYPE_DOUBLE:
+				LM_DBG("use DB1_DOUBLE result type\n");
+				RES_TYPES(_r)[col] = DB1_DOUBLE;
+				break;
+
+			case MYSQL_TYPE_DATETIME:
+				LM_DBG("use DB1_DATETIME result type\n");
+				RES_TYPES(_r)[col] = DB1_DATETIME;
+				break;
+
+			case MYSQL_TYPE_BLOB:
+				LM_DBG("use DB1_BLOB result type\n");
+				RES_TYPES(_r)[col] = DB1_BLOB;
+				break;
+
+			case FIELD_TYPE_SET:
+				LM_DBG("use DB1_BITMAP result type\n");
+				RES_TYPES(_r)[col] = DB1_BITMAP;
+				break;
+
+			case MYSQL_TYPE_DECIMAL:
+			#if MYSQL_VERSION_ID > 49999
+			case MYSQL_TYPE_NEWDECIMAL:
+			#endif
+			case MYSQL_TYPE_STRING:
+			case MYSQL_TYPE_VAR_STRING:
+				LM_DBG("use DB1_STRING result type\n");
+				RES_TYPES(_r)[col] = DB1_STRING;
+				break;
+
+			default:
+				LM_WARN("unhandled data type column (%.*s) type id (%d), "
+						"use DB1_STRING as default\n", RES_NAMES(_r)[col]->len,
+						RES_NAMES(_r)[col]->s, fields[col].type);
+				RES_TYPES(_r)[col] = DB1_STRING;
+				break;
+		}
+	}
+	return 0;
+}
+
+
+/*!
+ * \brief Convert rows from mysql to db API representation
+ * \param _h database connection
+ * \param _r database result set
+ * \return 0 on success, negative on failure
+ */
+static inline int db_mysql_convert_rows(const db1_con_t* _h, db1_res_t* _r)
+{
+	int row;
+
+	if ((!_h) || (!_r)) {
+		LM_ERR("invalid parameter\n");
+		return -1;
+	}
+
+	RES_ROW_N(_r) = mysql_num_rows(CON_RESULT(_h));
+	if (!RES_ROW_N(_r)) {
+		LM_DBG("no rows returned from the query\n");
+		RES_ROWS(_r) = 0;
+		return 0;
+	}
+
+	if (db_allocate_rows(_r) < 0) {
+		LM_ERR("could not allocate rows");
+		return -2;
+	}
+
+	for(row = 0; row < RES_ROW_N(_r); row++) {
+		CON_ROW(_h) = mysql_fetch_row(CON_RESULT(_h));
+		if (!CON_ROW(_h)) {
+			LM_ERR("driver error: %s\n", mysql_error(CON_CONNECTION(_h)));
+			RES_ROW_N(_r) = row;
+			db_free_rows(_r);
+			return -3;
+		}
+		if (db_mysql_convert_row(_h, _r, &(RES_ROWS(_r)[row])) < 0) {
+			LM_ERR("error while converting row #%d\n", row);
+			RES_ROW_N(_r) = row;
+			db_free_rows(_r);
+			return -4;
+		}
+	}
+	return 0;
+}
+
+
+/*!
+ * \brief Fill the result structure with data from database
+ * \param _h database connection
+ * \param _r database result
+ * \return 0 on success, negative on failure
+ */
+int db_mysql_convert_result(const db1_con_t* _h, db1_res_t* _r)
+{
+	if ((!_h) || (!_r)) {
+		LM_ERR("invalid parameter\n");
+		return -1;
+	}
+
+	if (db_mysql_get_columns(_h, _r) < 0) {
+		LM_ERR("error while getting column names\n");
+		return -2;
+	}
+
+	if (db_mysql_convert_rows(_h, _r) < 0) {
+		LM_ERR("error while converting rows\n");
+		db_free_columns(_r);
+		return -3;
+	}
+	return 0;
+}
+

+ 58 - 0
modules/db_mysql/km_res.h

@@ -0,0 +1,58 @@
+/* 
+ * $Id$ 
+ *
+ * MySQL module result related functions
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2007-2008 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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
+ */
+
+/*! \file
+ *  \brief DB_MYSQL :: Result related functions
+ *  \ref res.c
+ *  \ingroup db_mysql
+ *  Module: \ref db_mysql
+ */
+
+
+#ifndef KM_RES_H
+#define KM_RES_H
+
+#include "../../lib/srdb1/db_res.h"
+#include "../../lib/srdb1/db_con.h"
+
+
+/*!
+ * \brief Fill the result structure with data from database
+ * \param _h database connection
+ * \param _r database result
+ * \return 0 on success, negative on failure
+ */
+int db_mysql_convert_result(const db1_con_t* _h, db1_res_t* _r);
+
+
+/*!
+ * \brief Get and convert columns from a result
+ * \param _h database connection
+ * \param _r database result set
+ * \return 0 on success, negative on failure
+ */
+int db_mysql_get_columns(const db1_con_t* _h, db1_res_t* _r);
+
+#endif

+ 74 - 0
modules/db_mysql/km_row.c

@@ -0,0 +1,74 @@
+/* 
+ * $Id$ 
+ *
+ * MySQL module row related functions
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2008 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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
+ */
+
+/*! \file
+ *  \brief DB_MYSQL :: Row related functions
+ *  \ingroup db_mysql
+ *  Module: \ref db_mysql
+ */
+
+#include "../../dprint.h"
+#include "../../mem/mem.h"
+#include "../../lib/srdb1/db_row.h"
+#include "../../lib/srdb1/db_val.h"
+#include "km_my_con.h"
+#include "km_val.h"
+#include "km_row.h"
+
+/*!
+ * \brief Convert a row from result into DB API representation
+ * \param _h database connection
+ * \param _res database result in the DB API representation
+ * \param _r database result row
+ * \return 0 on success, -1 on failure
+ */
+int db_mysql_convert_row(const db1_con_t* _h, db1_res_t* _res, db_row_t* _r)
+{
+	unsigned long* lengths;
+	int i;
+
+	if ((!_h) || (!_res) || (!_r)) {
+		LM_ERR("invalid parameter value\n");
+		return -1;
+	}
+
+	if (db_allocate_row(_res, _r) != 0) {
+		LM_ERR("could not allocate row");
+		return -2;
+	}
+	
+	lengths = mysql_fetch_lengths(CON_RESULT(_h));
+
+	for(i = 0; i < RES_COL_N(_res); i++) {
+		if (db_str2val(RES_TYPES(_res)[i], &(ROW_VALUES(_r)[i]),
+			    ((MYSQL_ROW)CON_ROW(_h))[i], lengths[i], 0) < 0) {
+			LM_ERR("failed to convert value\n");
+			LM_DBG("free row at %p\n", _r);
+			db_free_row(_r);
+			return -3;
+		}
+	}
+	return 0;
+}

+ 51 - 0
modules/db_mysql/km_row.h

@@ -0,0 +1,51 @@
+/* 
+ * $Id$ 
+ *
+ * MySQL module row related functions
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2008 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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
+ */
+
+/*! \file
+ *  \brief DB_MYSQL :: Row related functions
+ *  \ref row.c
+ *  \ingroup db_mysql
+ *  Module: \ref db_mysql
+ */
+
+
+#ifndef KM_ROW_H
+#define KM_ROW_H
+
+#include "../../lib/srdb1/db_con.h"
+#include "../../lib/srdb1/db_res.h"
+#include "../../lib/srdb1/db_row.h"
+
+
+/*!
+ * \brief Convert a row from result into DB API representation
+ * \param _h database connection
+ * \param _res database result in the DB API representation
+ * \param _r database result row
+ * \return 0 on success, -1 on failure
+ */
+int db_mysql_convert_row(const db1_con_t* _h, db1_res_t* _res, db_row_t* _r);
+
+#endif

+ 107 - 0
modules/db_mysql/km_val.c

@@ -0,0 +1,107 @@
+/* 
+ * $Id$ 
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2008 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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
+ */
+
+/*! \file
+ *  \brief DB_MYSQL :: Data conversions
+ *  \ingroup db_mysql
+ *  Module: \ref db_mysql
+ */
+
+#include "../../dprint.h"
+#include "../../lib/srdb1/db_ut.h"
+#include "km_val.h"
+#include "km_my_con.h"
+
+
+/*!
+ * \brief Converting a value to a string
+ *
+ * Converting a value to a string, used when converting result from a query
+ * \param _c database connection
+ * \param _v source value
+ * \param _s target string
+ * \param _len target string length
+ * \return 0 on success, negative on error
+ */
+int db_mysql_val2str(const db1_con_t* _c, const db_val_t* _v, char* _s, int* _len)
+{
+	int l, tmp;
+	char* old_s;
+
+	tmp = db_val2str(_c, _v, _s, _len);
+	if (tmp < 1)
+		return tmp;
+
+	switch(VAL_TYPE(_v)) {
+	case DB1_STRING:
+		l = strlen(VAL_STRING(_v));
+		if (*_len < (l * 2 + 3)) {
+			LM_ERR("destination buffer too short\n");
+			return -6;
+		} else {
+			old_s = _s;
+			*_s++ = '\'';
+			_s += mysql_real_escape_string(CON_CONNECTION(_c), _s, VAL_STRING(_v), l);
+			*_s++ = '\'';
+			*_s = '\0'; /* FIXME */
+			*_len = _s - old_s;
+			return 0;
+		}
+		break;
+
+	case DB1_STR:
+		if (*_len < (VAL_STR(_v).len * 2 + 3)) {
+			LM_ERR("destination buffer too short\n");
+			return -7;
+		} else {
+			old_s = _s;
+			*_s++ = '\'';
+			_s += mysql_real_escape_string(CON_CONNECTION(_c), _s, VAL_STR(_v).s, VAL_STR(_v).len);
+			*_s++ = '\'';
+			*_s = '\0';
+			*_len = _s - old_s;
+			return 0;
+		}
+		break;
+
+	case DB1_BLOB:
+		l = VAL_BLOB(_v).len;
+		if (*_len < (l * 2 + 3)) {
+			LM_ERR("destination buffer too short\n");
+			return -9;
+		} else {
+			old_s = _s;
+			*_s++ = '\'';
+			_s += mysql_real_escape_string(CON_CONNECTION(_c), _s, VAL_STR(_v).s, l);
+			*_s++ = '\'';
+			*_s = '\0';
+			*_len = _s - old_s;
+			return 0;
+		}			
+		break;
+
+	default:
+		LM_DBG("unknown data type\n");
+		return -10;
+	}
+}

+ 52 - 0
modules/db_mysql/km_val.h

@@ -0,0 +1,52 @@
+/* 
+ * $Id$ 
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2008 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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
+ */
+
+/*! \file
+ *  \brief DB_MYSQL :: Data conversions
+ *  \ref val.c
+ *  \ingroup db_mysql
+ *  Module: \ref db_mysql
+ */
+
+
+#ifndef KM_VAL_H
+#define KM_VAL_H
+
+#include <mysql/mysql.h>
+#include "../../lib/srdb1/db_val.h"
+#include "../../lib/srdb1/db.h"
+
+
+/*!
+ * \brief Converting a value to a string
+ *
+ * Converting a value to a string, used when converting result from a query
+ * \param _c database connection
+ * \param _v source value
+ * \param _s target string
+ * \param _len target string length
+ * \return 0 on success, negative on error
+ */
+int db_mysql_val2str(const db1_con_t* _con, const db_val_t* _v, char* _s, int* _len);
+
+#endif

+ 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 "../../lib/srdb2/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 "../../lib/srdb2/db_drv.h"
+#include "../../lib/srdb2/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 "../../lib/srdb2/db_pool.h"
+#include "../../lib/srdb2/db_con.h"
+#include "../../lib/srdb2/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 "../../lib/srdb2/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 "../../lib/srdb2/db_drv.h"
+#include "../../lib/srdb2/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 "../../lib/srdb2/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 "../../lib/srdb2/db_drv.h"
+#include "../../lib/srdb2/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 "../../lib/srdb2/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 "../../lib/srdb2/db_uri.h"
+#include "../../lib/srdb2/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 */
+

+ 151 - 0
modules/db_mysql/mysql_mod.c

@@ -0,0 +1,151 @@
+/*
+ * $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 "km_db_mysql.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 "../../lib/srdb2/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},
+	{"db_bind_api",         (cmd_function)db_mysql_bind_api,      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},
+
+	{"timeout_interval", INT_PARAM, &db_mysql_timeout_interval},
+	{"auto_reconnect",   INT_PARAM, &db_mysql_auto_reconnect},
+	{0, 0, 0}
+};
+
+
+struct module_exports exports = {
+	"db_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 kam_mysql_mod_init();
+}
+
+/** @} */

+ 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 */