modules/db_mysql/my_cmd.c
4198a3a2
 /* 
  * Copyright (C) 2001-2003 FhG Fokus
  * Copyright (C) 2006-2007 iptelorg GmbH
  *
db26c5ce
  * This file is part of Kamailio, a free SIP server.
4198a3a2
  *
db26c5ce
  * Kamailio is free software; you can redistribute it and/or modify
4198a3a2
  * 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
  *
db26c5ce
  * Kamailio is distributed in the hope that it will be useful,
4198a3a2
  * 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 
9e1ff448
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
4198a3a2
  */
 
1528972f
 /** @addtogroup mysql
  *  @{
  */
 
3298a448
 /* the following macro will break the compile on solaris */
 #if !defined (__SVR4) && !defined (__sun)
    #define _XOPEN_SOURCE 4     /* bsd */
 #endif
4198a3a2
 #define _XOPEN_SOURCE_EXTENDED 1    /* solaris */
 #define _SVID_SOURCE 1 /* timegm */
 
a201f9ae
 #include "my_cmd.h"
 
 #include "my_con.h"
 #include "mysql_mod.h"
 #include "my_fld.h"
 
 #include "../../mem/mem.h"
 #include "../../str.h"
9bbb835a
 #include "../../lib/srdb2/db_cmd.h"
a201f9ae
 #include "../../ut.h"
9c5da732
 #include "../../dprint.h"
a201f9ae
 
4198a3a2
 #include <strings.h>
 #include <stdio.h>
 #include <time.h>  /*strptime, XOPEN issue must be >=4 */
 #include <string.h>
ea3fd384
 #include <mysql/errmsg.h>
 #include <mysql/mysqld_error.h>
4198a3a2
 
a3c3408f
 #define STR_BUF_SIZE 1024
4198a3a2
 
356e43fe
 #ifdef MYSQL_FAKE_NULL
a201f9ae
 
356e43fe
 #define FAKE_NULL_STRING "[~NULL~]"
905f205d
 static str  FAKE_NULL_STR = STR_STATIC_INIT(FAKE_NULL_STRING);
a201f9ae
 
356e43fe
 /* avoid warning: this decimal constant is unsigned only in ISO C90 :-) */
a201f9ae
 #define FAKE_NULL_INT (-2147483647 - 1)
356e43fe
 #endif
 
4198a3a2
 enum {
 	STR_DELETE,
 	STR_INSERT,
 	STR_UPDATE,
 	STR_SELECT,
 	STR_REPLACE,
1528972f
 	STR_SET,
4198a3a2
 	STR_WHERE,
 	STR_IS,
 	STR_AND,
 	STR_OR,
 	STR_ESC,
 	STR_OP_EQ,
90060642
 	STR_OP_NE,
4198a3a2
 	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 "),
356e43fe
 	STR_STATIC_INIT("update "),
4198a3a2
 	STR_STATIC_INIT("select "),
 	STR_STATIC_INIT("replace "),
356e43fe
 	STR_STATIC_INIT(" set "),
4198a3a2
 	STR_STATIC_INIT(" where "),
 	STR_STATIC_INIT(" is "),
 	STR_STATIC_INIT(" and "),
 	STR_STATIC_INIT(" or "),
 	STR_STATIC_INIT("?"),
 	STR_STATIC_INIT("="),
90060642
 	STR_STATIC_INIT("!="),
4198a3a2
 	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)
 
 
a201f9ae
 static int upload_cmd(db_cmd_t* cmd);
ea3fd384
 
4198a3a2
 
25bdcd1f
 static void my_cmd_free(db_cmd_t* cmd, struct my_cmd* payload)
4198a3a2
 {
 	db_drv_free(&payload->gen);
a201f9ae
 	if (payload->sql_cmd.s) pkg_free(payload->sql_cmd.s);
4198a3a2
 	if (payload->st) mysql_stmt_close(payload->st);
 	pkg_free(payload);
 }
 
 
905f205d
 /** Builds a DELETE SQL statement.The function builds DELETE statement where
  * cmd->match specify WHERE clause.  
c867df9a
  * @param sql_cmd SQL statement as a result of this function 
a201f9ae
  * @param cmd input for statement creation
c867df9a
  * @return -1 on error, 0 on success
1528972f
  */
a201f9ae
 static int build_delete_cmd(str* sql_cmd, db_cmd_t* cmd)
4198a3a2
 {
 	db_fld_t* fld;
 	int i;
 	char* p;
 
a201f9ae
 	sql_cmd->len = strings[STR_DELETE].len;
 	sql_cmd->len += cmd->table.len;
4198a3a2
 
1528972f
 	if (!DB_FLD_EMPTY(cmd->match)) {
a201f9ae
 		sql_cmd->len += strings[STR_WHERE].len;
4198a3a2
 
1528972f
 		for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
a201f9ae
 			sql_cmd->len += strlen(fld[i].name);
4198a3a2
 
 			switch(fld[i].op) {
a201f9ae
 			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;
4198a3a2
 			default:
a201f9ae
 				ERR("mysql: Unsupported db_fld operator %d\n", fld[i].op);
4198a3a2
 				return -1;
 			}
 
a201f9ae
 			sql_cmd->len += strings[STR_ESC].len;
4198a3a2
 			
a201f9ae
 			if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += strings[STR_AND].len;
4198a3a2
 		}
 	}
 
a201f9ae
 	sql_cmd->s = pkg_malloc(sql_cmd->len + 1);
 	if (sql_cmd->s == NULL) {
 		ERR("mysql: No memory left\n");
4198a3a2
 		return -1;
 	}
a201f9ae
 	p = sql_cmd->s;
4198a3a2
 	
 	APPEND_STR(p, strings[STR_DELETE]);
 	APPEND_STR(p, cmd->table);
 
1528972f
 	if (!DB_FLD_EMPTY(cmd->match)) {
4198a3a2
 		APPEND_STR(p, strings[STR_WHERE]);
 
1528972f
 		for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
4198a3a2
 			APPEND_CSTR(p, fld[i].name);
 
 			switch(fld[i].op) {
 			case DB_EQ:  APPEND_STR(p, strings[STR_OP_EQ]);  break;
90060642
 			case DB_NE:  APPEND_STR(p, strings[STR_OP_NE]);  break;
4198a3a2
 			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;
 }
 
 
1528972f
 /**
  *  Builds SELECT statement where cmd->values specify column names
  *  and cmd->match specify WHERE clause.
a201f9ae
  * @param sql_cmd SQL statement as a result of this function
  * @param cmd     input for statement creation
1528972f
  */
a201f9ae
 static int build_select_cmd(str* sql_cmd, db_cmd_t* cmd)
4198a3a2
 {
 	db_fld_t* fld;
 	int i;
 	char* p;
 
a201f9ae
 	sql_cmd->len = strings[STR_SELECT].len;
4198a3a2
 
 	if (DB_FLD_EMPTY(cmd->result)) {
a201f9ae
 		sql_cmd->len += 1; /* "*" */
4198a3a2
 	} else {
 		for(i = 0, fld = cmd->result; !DB_FLD_LAST(fld[i]); i++) {
a201f9ae
 			sql_cmd->len += strlen(fld[i].name);
 			if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += 1; /* , */
4198a3a2
 		}
 	}
a201f9ae
 	sql_cmd->len += strings[STR_FROM].len;
 	sql_cmd->len += cmd->table.len;
4198a3a2
 
1528972f
 	if (!DB_FLD_EMPTY(cmd->match)) {
a201f9ae
 		sql_cmd->len += strings[STR_WHERE].len;
4198a3a2
 
1528972f
 		for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
a201f9ae
 			sql_cmd->len += strlen(fld[i].name);
4198a3a2
 
 			switch(fld[i].op) {
a201f9ae
 			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;
4198a3a2
 			default:
a201f9ae
 				ERR("mysql: Unsupported db_fld operator %d\n", fld[i].op);
4198a3a2
 				return -1;
 			}
 
a201f9ae
 			sql_cmd->len += strings[STR_ESC].len;
4198a3a2
 			
a201f9ae
 			if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += strings[STR_AND].len;
4198a3a2
 		}
 	}
 
a201f9ae
 	sql_cmd->s = pkg_malloc(sql_cmd->len + 1);
 	if (sql_cmd->s == NULL) {
 		ERR("mysql: No memory left\n");
4198a3a2
 		return -1;
 	}
a201f9ae
 	p = sql_cmd->s;
4198a3a2
 	
 	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);
 
1528972f
 	if (!DB_FLD_EMPTY(cmd->match)) {
4198a3a2
 		APPEND_STR(p, strings[STR_WHERE]);
 
1528972f
 		for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
4198a3a2
 			APPEND_CSTR(p, fld[i].name);
 
 			switch(fld[i].op) {
 			case DB_EQ:  APPEND_STR(p, strings[STR_OP_EQ]);  break;
90060642
 			case DB_NE:  APPEND_STR(p, strings[STR_OP_NE]);  break;
4198a3a2
 			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]);
 		}
 	}
a201f9ae
 
4198a3a2
 	*p = '\0';
 	return 0;
 }
 
 
1528972f
 /**
  *  Builds REPLACE statement where cmd->values specify column names.
a201f9ae
  * @param sql_cmd SQL statement as a result of this function
  * @param cmd     input for statement creation
1528972f
  */
a201f9ae
 static int build_replace_cmd(str* sql_cmd, db_cmd_t* cmd)
4198a3a2
 {
 	db_fld_t* fld;
 	int i;
 	char* p;
 
a201f9ae
 	sql_cmd->len = strings[STR_REPLACE].len;
 	sql_cmd->len += cmd->table.len;
 	sql_cmd->len += 2; /* " (" */
4198a3a2
 
1528972f
 	for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
a201f9ae
 		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 */
4198a3a2
 	}
a201f9ae
 	sql_cmd->len += strings[STR_VALUES].len;
     sql_cmd->len += 1; /* ) */
4198a3a2
 
a201f9ae
 	sql_cmd->s = pkg_malloc(sql_cmd->len + 1);
 	if (sql_cmd->s == NULL) {
 		ERR("mysql: No memory left\n");
4198a3a2
 		return -1;
 	}
a201f9ae
 	p = sql_cmd->s;
4198a3a2
 	
 	APPEND_STR(p, strings[STR_REPLACE]);
 	APPEND_STR(p, cmd->table);
 	*p++ = ' ';
 	*p++ = '(';
 
1528972f
 	for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
4198a3a2
 		APPEND_CSTR(p, fld[i].name);
 		if (!DB_FLD_LAST(fld[i + 1])) *p++ = ',';
 	}
 	APPEND_STR(p, strings[STR_VALUES]);
 
1528972f
 	for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
4198a3a2
 		APPEND_STR(p, strings[STR_ESC]);
 		if (!DB_FLD_LAST(fld[i + 1])) *p++ = ',';
 	}
 	*p++ = ')';
 	*p = '\0';
 	return 0;
 }
 
a201f9ae
 
1528972f
 /**
  *  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 */ 
 };
a201f9ae
 
 
1528972f
 /**
  *  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) {
a201f9ae
 			ERR("mysql: No memory left\n");
1528972f
 			return -1;
 		}
356e43fe
 		if (sb->s) {
 			memcpy(newp, sb->s, sb->len);
 			pkg_free(sb->s);
 		}
1528972f
 		sb->s = newp;
 		sb->size = new_size;
 	}
 	memcpy(sb->s + sb->len, nstr->s, nstr->len);
 	sb->len += nstr->len;
 	return 0;
 }
a201f9ae
 
 
1528972f
 /**
  *  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.
a201f9ae
  * @param sql_cmd  SQL statement as a result of this function
  * @param cmd      input for statement creation
1528972f
  */
a201f9ae
 static int build_update_cmd(str* sql_cmd, db_cmd_t* cmd)
1528972f
 {
 	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;
90060642
 			case DB_NE:  rv |= sb_add(&sql_buf, &strings[STR_OP_NE]);  break;
1528972f
 			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;
 	}
a201f9ae
 	sql_cmd->s = sql_buf.s;
 	sql_cmd->len = sql_buf.len;
1528972f
 	return 0;
 
 err:
 	if (sql_buf.s) pkg_free(sql_buf.s);
 	return -1;
 }
 
4198a3a2
 
4ed9aa9d
 static inline void update_field(MYSQL_BIND *param, db_fld_t* fld)
4198a3a2
 {
4ed9aa9d
 	struct my_fld* fp;      /* field payload */
4198a3a2
 	struct tm* t;
4ed9aa9d
 	
 	fp = DB_GET_PAYLOAD(fld);
4198a3a2
 
356e43fe
 #ifndef MYSQL_FAKE_NULL
4ed9aa9d
 	fp->is_null = fld->flags & DB_NULL;
 	if (fp->is_null) return;
356e43fe
 #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
4ed9aa9d
 	switch(fld->type) {
 	case DB_STR:
 		param->buffer = fld->v.lstr.s;
 		fp->length = fld->v.lstr.len;
 		break;
4198a3a2
 
4ed9aa9d
 	case DB_BLOB:
 		param->buffer = fld->v.blob.s;
 		fp->length = fld->v.blob.len;
 		break;
4198a3a2
 
4ed9aa9d
 	case DB_CSTR:
 		param->buffer = (char*)fld->v.cstr;
 		fp->length = strlen(fld->v.cstr);
 		break;
4198a3a2
 
4ed9aa9d
 	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;
4198a3a2
 
4ed9aa9d
 	}
 }
4198a3a2
 
a201f9ae
 
4ed9aa9d
 /**
a201f9ae
  * 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
4ed9aa9d
  */
a201f9ae
 static inline void set_mysql_params(db_cmd_t* cmd)
4ed9aa9d
 {
a201f9ae
 	struct my_cmd* mcmd;
 	int i;
25bdcd1f
 
a201f9ae
 	mcmd = DB_GET_PAYLOAD(cmd);
4ed9aa9d
 
 	/* 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)
 	 */
a201f9ae
 	for(i = 0; i < cmd->vals_count; i++) {
 		update_field(mcmd->st->params + i, cmd->vals + i);
4198a3a2
 	}
 
a201f9ae
 	for(i = 0; i < cmd->match_count; i++) {
 		update_field(mcmd->st->params + cmd->vals_count + i, cmd->match + i);
 	}
4198a3a2
 }
 
 
 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;
 
e55ca85d
 	/* Iterate through all the fields returned by MySQL and convert
 	 * them to DB API representation if necessary
4198a3a2
 	 */
 
 	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:
145a90df
 			result[i].v.lstr.len = rp->length;
356e43fe
 #ifdef MYSQL_FAKE_NULL
 			if (STR_EQ(FAKE_NULL_STR,result[i].v.lstr)) {
 				result[i].flags |= DB_NULL;
 			}
 #endif
4198a3a2
 			break;
 
 		case DB_BLOB:
 			result[i].v.blob.len = rp->length;
 			break;
 
 		case DB_CSTR:
a3c3408f
 			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';
 			}
356e43fe
 #ifdef MYSQL_FAKE_NULL
 			if (strcmp(FAKE_NULL_STR.s,result[i].v.cstr)==0) {
 				result[i].flags |= DB_NULL;
 			}
 #endif
4198a3a2
 			break;
356e43fe
 			
4198a3a2
 		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;
356e43fe
 			t.tm_year = rp->time.year - 1900;
4198a3a2
 
 			/* 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;
25bdcd1f
 
 		case DB_INT:
356e43fe
 #ifdef MYSQL_FAKE_NULL
 			if (FAKE_NULL_INT==result[i].v.int4) {
 				result[i].flags |= DB_NULL;
 			}
 			break;
 #endif
 		case DB_NONE:
25bdcd1f
 		case DB_FLOAT:
 		case DB_DOUBLE:
 		case DB_BITMAP:
 			/* No need to do anything for these types */
 			break;
4198a3a2
 		}
 	}
356e43fe
 	
4198a3a2
 	return 0;
 }
 
a201f9ae
 
1528972f
 /**
a201f9ae
  * 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
1528972f
  */
a201f9ae
 static int exec_cmd_safe(db_cmd_t* cmd)
4198a3a2
 {
0e564bcd
     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++) {
 	if ((mcon->flags & MY_CONNECTED) == 0) {
 	    /* The connection is disconnected, try to reconnect */
 	    if (my_con_connect(con)) {
 		INFO("mysql: exec_cmd_safe failed to re-connect\n");
 		continue;
 	    }
 	}	
 	
 	/* 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.
ea3fd384
 	 */
0e564bcd
 	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");
 		continue;
 	    } else if (err > 0) {
 		/* DB API error, this is a serious problem such as memory
 		 * allocation failure, bail out
a201f9ae
 		 */
0e564bcd
 		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;
ea3fd384
 		}
0e564bcd
 	    }
 	    return 0;
ea3fd384
 	}
0e564bcd
 	
     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;
1528972f
 }
 
 
a201f9ae
 int my_cmd_exec(db_res_t* res, db_cmd_t* cmd)
1528972f
 {
 	struct my_cmd* mcmd;
 
 	mcmd = DB_GET_PAYLOAD(cmd);
ea3fd384
 
356e43fe
 	mcmd->next_flag = -1;
a201f9ae
 	return exec_cmd_safe(cmd);
4198a3a2
 }
 
 
904c3cfa
 /**
  * Set MYSQL_BIND item.
  * @param bind destination
  * @param fld  source
  */
a201f9ae
 static void set_field(MYSQL_BIND *bind, db_fld_t* fld)
4198a3a2
 {
 	struct my_fld* f;
1528972f
 	
 	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;
 	
 	}
 }
 
a201f9ae
 
1528972f
 /**
a201f9ae
  * 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
904c3cfa
  * @param params1 first set of params
  * @param params2 second set of params
a201f9ae
  * @return 0 if OK, <0 on MySQL error, >0 on DB API error
904c3cfa
  * @see update_params
1528972f
  */
a201f9ae
 static int bind_mysql_params(MYSQL_STMT* st, db_fld_t* params1, db_fld_t* params2)
1528972f
 {
 	int my_idx, fld_idx;
4ed9aa9d
 	int count1, count2;
1528972f
 	MYSQL_BIND* my_params;
a201f9ae
 	int err = 0;
4198a3a2
 
 	/* Calculate the number of parameters */
4ed9aa9d
 	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++);
a201f9ae
 	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;
356e43fe
 	}
 	
a201f9ae
 	my_params = (MYSQL_BIND*)pkg_malloc(sizeof(MYSQL_BIND) * (count1 + count2));
1528972f
 	if (my_params == NULL) {
a201f9ae
 		ERR("mysql: No memory left\n");
4198a3a2
 		return -1;
 	}
a201f9ae
 	memset(my_params, '\0', sizeof(MYSQL_BIND) * (count1 + count2));
4198a3a2
 
4ed9aa9d
 	/* params1 */
1528972f
 	my_idx = 0;
4ed9aa9d
 	for (fld_idx = 0; fld_idx < count1; fld_idx++, my_idx++) {
 		set_field(&my_params[my_idx], params1 + fld_idx);
4198a3a2
 	}
4ed9aa9d
 	/* params2 */
 	for (fld_idx = 0; fld_idx < count2; fld_idx++, my_idx++) {
 		set_field(&my_params[my_idx], params2 + fld_idx);
1528972f
 	}
a201f9ae
 
 	err = mysql_stmt_bind_param(st, my_params);
 	if (err) {
 		ERR("mysql: libmysqlclient: %d, %s\n", 
 			mysql_stmt_errno(st), mysql_stmt_error(st));
4198a3a2
 		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
 	 */
1528972f
 	pkg_free(my_params);
a201f9ae
 	return err;
4198a3a2
    
  error:
1528972f
 	if (my_params) pkg_free(my_params);
a201f9ae
 	return err;
4198a3a2
 }
 
356e43fe
 
 /*
  * 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.
  */
56941828
 static int check_result(db_cmd_t* cmd, struct my_cmd* payload)
356e43fe
 {
 	int i, n;
 	MYSQL_FIELD *fld;
a201f9ae
 	MYSQL_RES *meta = NULL;
356e43fe
 
 	meta = mysql_stmt_result_metadata(payload->st);
 	if (meta == NULL) {
03547cef
 		/* No error means no result set to be checked */
 		if (mysql_stmt_errno(payload->st) == 0) return 0;
a201f9ae
 		ERR("mysql: Error while getting metadata of SQL command: %d, %s\n",
 			mysql_stmt_errno(payload->st), mysql_stmt_error(payload->st));
 		return -1;
356e43fe
 	}
 	n = mysql_num_fields(meta);
 	if (cmd->result == NULL) {
 		/* The result set parameter of db_cmd function was empty, that
a201f9ae
 		 * means the command is select * and we have to create the array
356e43fe
 		 * of result fields in the cmd structure manually.
 		 */
a201f9ae
 		cmd->result = db_fld(n + 1);
356e43fe
 		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);
a201f9ae
 			if (f->name == NULL) {
 				ERR("mysql: Out of private memory\n");
 				goto error;
 			}
356e43fe
 			strcpy(f->name, fld->name);
 			cmd->result[i].name = f->name;
 		}
a201f9ae
 	} else {
356e43fe
 		if (cmd->result_count != n) {
a201f9ae
 			BUG("mysql: Number of fields in MySQL result does not match number of parameters in DB API\n");
356e43fe
 			goto error;
 		}
 	}
a201f9ae
 
356e43fe
 	/* 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
a201f9ae
 	 * update it from the database as well. 
356e43fe
 	 */
 	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:
a201f9ae
 			ERR("mysql: Unsupported MySQL column type: %d, table: %s, column: %s\n",
356e43fe
 				fld->type, cmd->table.s, fld->name);
 			goto error;
 		}
 	}
a201f9ae
 	
 	if (meta) mysql_free_result(meta);
356e43fe
 	return 0;
a201f9ae
 
356e43fe
 error:
 	if (meta) mysql_free_result(meta);
a201f9ae
 	return 1;
356e43fe
 }
 
 
145a90df
 /* FIXME: Add support for DB_NONE, in this case the function should determine
56941828
  * the type of the column in the database and set the field type appropriately.
  * This function must be called after check_result.
145a90df
  */
4198a3a2
 static int bind_result(MYSQL_STMT* st, db_fld_t* fld)
 {
a201f9ae
 	int i, n, err = 0;
4198a3a2
 	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++);
56941828
 	/* Return immediately if there are no fields in the result set */
 	if (n == 0) return 0;
 
4198a3a2
 	result = (MYSQL_BIND*)pkg_malloc(sizeof(MYSQL_BIND) * n);
 	if (result == NULL) {
a201f9ae
 		ERR("mysql: No memory left\n");
 		return 1;
4198a3a2
 	}
 	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;
ea3fd384
 			if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
4198a3a2
 			if (f->buf.s == NULL) {
a201f9ae
 				ERR("mysql: No memory left\n");
 				err = 1;
356e43fe
 				goto error;
4198a3a2
 			}
 			result[i].buffer = f->buf.s;
145a90df
 			fld[i].v.lstr.s = f->buf.s;
4198a3a2
 			result[i].buffer_length = STR_BUF_SIZE - 1;
 			break;
 
 		case DB_CSTR:
 			result[i].buffer_type = MYSQL_TYPE_VAR_STRING;
ea3fd384
 			if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
4198a3a2
 			if (f->buf.s == NULL) {
a201f9ae
 				ERR("mysql: No memory left\n");
 				err = 1;
356e43fe
 				goto error;
4198a3a2
 			}
 			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;
ea3fd384
 			if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
4198a3a2
 			if (f->buf.s == NULL) {
a201f9ae
 				ERR("mysql: No memory left\n");
 				err = 1;
356e43fe
 				goto error;
4198a3a2
 			}
 			result[i].buffer = f->buf.s;
 			fld[i].v.blob.s = f->buf.s;
 			result[i].buffer_length = STR_BUF_SIZE - 1;
 			break;
 
25bdcd1f
 		case DB_NONE:
 			/* Eliminates gcc warning */
 			break;
 
4198a3a2
 		}
 	}
a201f9ae
 
 	err = mysql_stmt_bind_result(st, result);
 	if (err) {
 		ERR("mysql: Error while binding result: %s\n", mysql_stmt_error(st));
4198a3a2
 		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
 	 */
a201f9ae
 	if (result) pkg_free(result);
4198a3a2
 	return 0;
    
  error:
 	if (result) pkg_free(result);
a201f9ae
 	return err;
4198a3a2
 }
 
 
a201f9ae
 /**
  * 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)
4198a3a2
 {
0e564bcd
     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]);
     /* Do not upload the command if the connection is not connected */
     if ((mcon->flags & MY_CONNECTED) == 0) {
 	err = 1;
 	goto error;
     }
 
     /* 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 */
     if (mysql_stmt_prepare(res->st, res->sql_cmd.s, res->sql_cmd.len)) {
 	err = mysql_stmt_errno(res->st);    
 	ERR("mysql: libmysql: %d, %s\n", err, mysql_stmt_error(res->st));
 	ERR("mysql: An error occurred while uploading command to server\n");
     }
     if (err == CR_SERVER_LOST ||
 	err == CR_SERVER_GONE_ERROR) {
 	/* Connection to the server was lost, mark the connection as
 	 * disconnected. In this case mysql_stmt_prepare invalidates the
 	 * connection internally and calling another mysql function on that
 	 * connection would crash. To make sure that no other mysql function
 	 * gets called unless the connection is reconnected we disconnect it
 	 * explicitly here. This is a workaround for mysql bug #33384. */
 	my_con_disconnect(cmd->ctx->con[db_payload_idx]);
     }
     if (err) {
 	/* Report mysql error to the caller */
 	err = -1;
 	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);
a201f9ae
 	if (err) goto error;
0e564bcd
 	err = bind_result(res->st, cmd->result);
 	if (err) goto error;
     }
     
     res->last_reset = mcon->resets;
     return 0;
     
 error:
     if (res->st) {
 	mysql_stmt_close(res->st);
 	res->st = NULL;
     }
     return err;
ea3fd384
 }
 
 
 int my_cmd(db_cmd_t* cmd)
 {
 	struct my_cmd* res;
  
 	res = (struct my_cmd*)pkg_malloc(sizeof(struct my_cmd));
 	if (res == NULL) {
a201f9ae
 		ERR("mysql: No memory left\n");
ea3fd384
 		goto error;
 	}
 	memset(res, '\0', sizeof(struct my_cmd));
905f205d
 	/* Fetch all data to client at once by default */
 	res->flags |= MY_FETCH_ALL;
ea3fd384
 	if (db_drv_init(&res->gen, my_cmd_free) < 0) goto error;
 
 	switch(cmd->type) {
 	case DB_PUT:
 		if (DB_FLD_EMPTY(cmd->vals)) {
a201f9ae
 			BUG("mysql: No parameters provided for DB_PUT in context '%.*s'\n", 
ea3fd384
 				cmd->ctx->id.len, ZSW(cmd->ctx->id.s));
 			goto error;
 		}
a201f9ae
 		if (build_replace_cmd(&res->sql_cmd, cmd) < 0) goto error;
ea3fd384
 		break;
 
 	case DB_DEL:
a201f9ae
 		if (build_delete_cmd(&res->sql_cmd, cmd) < 0) goto error;
ea3fd384
 		break;
 
 	case DB_GET:
a201f9ae
 		if (build_select_cmd(&res->sql_cmd, cmd) < 0) goto error;
ea3fd384
 		break;
 
 	case DB_UPD:
a201f9ae
 		if (build_update_cmd(&res->sql_cmd, cmd) < 0) goto error;
ea3fd384
 		break;
 
 	case DB_SQL:
a201f9ae
 		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;
356e43fe
         break;
ea3fd384
 	}
4198a3a2
 
 	DB_SET_PAYLOAD(cmd, res);
0e564bcd
 
 	/* In order to check all the parameters and results, we need to upload
 	 * the command to the server. We need to do that here before we report
 	 * back that the command was created successfully. Hence, this
 	 * function requires the corresponding connection be established. We
 	 * would not be able to check parameters if we don't do that there and
 	 * that could result in repeated execution failures at runtime.
 	 */
 	if (upload_cmd(cmd)) goto error;
4198a3a2
 	return 0;
 
  error:
 	if (res) {
ae322c06
 		DB_SET_PAYLOAD(cmd, NULL);
4198a3a2
 		db_drv_free(&res->gen);
a201f9ae
 		if (res->sql_cmd.s) pkg_free(res->sql_cmd.s);
4198a3a2
 		pkg_free(res);
 	}
 	return -1;
 }
 
356e43fe
 
 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 */
a201f9ae
 		ERR("mysql: Unbuffered queries do not support cursor reset.\n");
356e43fe
 		return -1;
 	default:
 		return my_cmd_next(res);
 	}
 }
 
 
4198a3a2
 int my_cmd_next(db_res_t* res)
 {
 	int ret;
 	struct my_cmd* mcmd;
 
 	mcmd = DB_GET_PAYLOAD(res->cmd);
356e43fe
 	if (mcmd->next_flag == 2 || mcmd->next_flag == -2) return 1;
0af03a1d
 
 	if (mcmd->st == NULL) {
 		ERR("mysql: Prepared statement not found\n");
 		return -1;
 	}
 
4198a3a2
 	ret = mysql_stmt_fetch(mcmd->st);
 	
356e43fe
 	if (ret == MYSQL_NO_DATA) {
 		mcmd->next_flag =  mcmd->next_flag<0?-2:2;
 		return 1;
 	}
3bbe2e87
 	/* MYSQL_DATA_TRUNCATED is only defined in mysql >= 5.0 */
 #if defined MYSQL_DATA_TRUNCATED
356e43fe
 	if (ret == MYSQL_DATA_TRUNCATED) {
 		int i;
a201f9ae
 		ERR("mysql: mysql_stmt_fetch, data truncated, fields: %d\n", res->cmd->result_count);
356e43fe
 		for (i = 0; i < res->cmd->result_count; i++) {
 			if (mcmd->st->bind[i].error /*&& mcmd->st->bind[i].buffer_length*/) {
a201f9ae
 				ERR("mysql: truncation, bind %d, length: %lu, buffer_length: %lu\n", 
 					i, *(mcmd->st->bind[i].length), mcmd->st->bind[i].buffer_length);
356e43fe
 			}
 		}
3bbe2e87
 		ret = 0;
356e43fe
 	}
3bbe2e87
 #endif
356e43fe
 	if (mcmd->next_flag <= 0) {
 		mcmd->next_flag++;
 	}
3bbe2e87
 	if (ret != 0) {
a201f9ae
 		ERR("mysql: Error in mysql_stmt_fetch (ret=%d): %s\n", ret, mysql_stmt_error(mcmd->st));
4198a3a2
 		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;
 }
 
905f205d
 
 int my_getopt(db_cmd_t* cmd, char* optname, va_list ap)
 {
 	struct my_cmd* mcmd;
3df2afaa
 	long long* id;
905f205d
 	int* val;
 
 	mcmd = (struct my_cmd*)DB_GET_PAYLOAD(cmd);
 
3df2afaa
 	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)) {
905f205d
 		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;
 }
 
1528972f
 /** @} */