Merge branches 'janakj/mysql' and 'master' of ssh://janakj@git.sip-router.org/sip...
authorJan Janak <jan@iptel.org>
Mon, 16 Feb 2009 00:07:20 +0000 (01:07 +0100)
committerJan Janak <jan@iptel.org>
Mon, 16 Feb 2009 00:07:20 +0000 (01:07 +0100)
* 'janakj/mysql' of ssh://janakj@git.sip-router.org/sip-router:

* 'master' of ssh://janakj@git.sip-router.org/sip-router:
  Database flags renamed from DB_* to SRDB_* to avoid conflicts.
  libsrdb1: futex warning fix
  - port from kamailio trunk, r5607
  - sync transformations add/lookup with kamailio
  init_mi_core() exported via mi.h
  mi include file
  MI: core part
  script parsing: while support
  script engine: while() support
  script engine: switch() and break execution
  script engine: switch() fixup and optimizations
  script parsing: C style switch() & case support
  expr engine: minor additions

14 files changed:
modules/db_mysql/Makefile [new file with mode: 0644]
modules/db_mysql/doc/mysql_parser.dia [new file with mode: 0644]
modules/db_mysql/my_cmd.c [new file with mode: 0644]
modules/db_mysql/my_cmd.h [new file with mode: 0644]
modules/db_mysql/my_con.c [new file with mode: 0644]
modules/db_mysql/my_con.h [new file with mode: 0644]
modules/db_mysql/my_fld.c [new file with mode: 0644]
modules/db_mysql/my_fld.h [new file with mode: 0644]
modules/db_mysql/my_res.c [new file with mode: 0644]
modules/db_mysql/my_res.h [new file with mode: 0644]
modules/db_mysql/my_uri.c [new file with mode: 0644]
modules/db_mysql/my_uri.h [new file with mode: 0644]
modules/db_mysql/mysql_mod.c [new file with mode: 0644]
modules/db_mysql/mysql_mod.h [new file with mode: 0644]

diff --git a/modules/db_mysql/Makefile b/modules/db_mysql/Makefile
new file mode 100644 (file)
index 0000000..13ac208
--- /dev/null
@@ -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
+
+include ../../Makefile.modules
diff --git a/modules/db_mysql/doc/mysql_parser.dia b/modules/db_mysql/doc/mysql_parser.dia
new file mode 100644 (file)
index 0000000..d4d0947
Binary files /dev/null and b/modules/db_mysql/doc/mysql_parser.dia differ
diff --git a/modules/db_mysql/my_cmd.c b/modules/db_mysql/my_cmd.c
new file mode 100644 (file)
index 0000000..f761265
--- /dev/null
@@ -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:
+ *    info@iptel.org
+ *
+ * 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;
+}
+
+/** @} */
diff --git a/modules/db_mysql/my_cmd.h b/modules/db_mysql/my_cmd.h
new file mode 100644 (file)
index 0000000..b5d8111
--- /dev/null
@@ -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:
+ *    info@iptel.org
+ *
+ * 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 */
diff --git a/modules/db_mysql/my_con.c b/modules/db_mysql/my_con.c
new file mode 100644 (file)
index 0000000..3b8cd05
--- /dev/null
@@ -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:
+ *    info@iptel.org
+ *
+ * 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;
+}
diff --git a/modules/db_mysql/my_con.h b/modules/db_mysql/my_con.h
new file mode 100644 (file)
index 0000000..b3ace15
--- /dev/null
@@ -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:
+ *    info@iptel.org
+ *
+ * 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 */
diff --git a/modules/db_mysql/my_fld.c b/modules/db_mysql/my_fld.c
new file mode 100644 (file)
index 0000000..732234d
--- /dev/null
@@ -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:
+ *    info@iptel.org
+ *
+ * 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;
+}
diff --git a/modules/db_mysql/my_fld.h b/modules/db_mysql/my_fld.h
new file mode 100644 (file)
index 0000000..b5e78d3
--- /dev/null
@@ -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:
+ *    info@iptel.org
+ *
+ * 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 */
diff --git a/modules/db_mysql/my_res.c b/modules/db_mysql/my_res.c
new file mode 100644 (file)
index 0000000..aa8f89d
--- /dev/null
@@ -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:
+ *    info@iptel.org
+ *
+ * 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;
+}
diff --git a/modules/db_mysql/my_res.h b/modules/db_mysql/my_res.h
new file mode 100644 (file)
index 0000000..8211d5e
--- /dev/null
@@ -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:
+ *    info@iptel.org
+ *
+ * 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 */
diff --git a/modules/db_mysql/my_uri.c b/modules/db_mysql/my_uri.c
new file mode 100644 (file)
index 0000000..9a4357d
--- /dev/null
@@ -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:
+ *    info@iptel.org
+ *
+ * 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;
+}
+
diff --git a/modules/db_mysql/my_uri.h b/modules/db_mysql/my_uri.h
new file mode 100644 (file)
index 0000000..024449c
--- /dev/null
@@ -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:
+ *    info@iptel.org
+ *
+ * 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 */
+
diff --git a/modules/db_mysql/mysql_mod.c b/modules/db_mysql/mysql_mod.c
new file mode 100644 (file)
index 0000000..469bfb6
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * $Id$
+ *
+ * MySQL module interface
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    info@iptel.org
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * History:
+ * --------
+ *  2003-03-11  updated to the new module exports interface (andrei)
+ *  2003-03-16  flags export parameter added (janakj)
+ */
+
+/** @addtogroup mysql
+ *  @{
+ */
+#include "mysql_mod.h"
+
+#include "my_uri.h"
+#include "my_con.h"
+#include "my_cmd.h"
+#include "my_fld.h"
+#include "my_res.h"
+
+#include "../../sr_module.h"
+#include "../../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},
+       {0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t params[] = {
+       {"ping_interval",   PARAM_INT, &my_ping_interval},
+       {"connect_timeout", PARAM_INT, &my_connect_to},
+       {"send_timeout",    PARAM_INT, &my_send_to},
+       {"receive_timeout", PARAM_INT, &my_recv_to},
+       {"retries",         PARAM_INT, &my_retries},
+       {0, 0, 0}
+};
+
+
+struct module_exports exports = {
+       "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 0;
+}
+
+/** @} */
diff --git a/modules/db_mysql/mysql_mod.h b/modules/db_mysql/mysql_mod.h
new file mode 100644 (file)
index 0000000..3a6f470
--- /dev/null
@@ -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:
+ *    info@iptel.org
+ *
+ * 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 */