modules/db2_ops/db2_ops.c
5ad39751
 /*
  * Copyright (C) 2006 iptelorg GmbH
  *
ddbff735
  * This file is part of Kamailio, a free SIP server.
5ad39751
  *
ddbff735
  * Kamailio is free software; you can redistribute it and/or modify
5ad39751
  * 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
  *
ddbff735
  * Kamailio is distributed in the hope that it will be useful,
5ad39751
  * 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
5ad39751
  */
 
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <ctype.h>
 #include "../../sr_module.h"
 #include "../../mem/mem.h"
 #include "../../str.h"
 #include "../../error.h"
 #include "../../config.h"
 #include "../../trim.h"
7a7f1a7b
 #include "../../lib/srdb2/db.h"
5ad39751
 #include "../../select.h"
 #include "../../script_cb.h"
ff7bd782
 #include "../../modules/xprint/xp_lib.h"
5ad39751
 #include "../../route.h"
 #include "../../action.h"
 #include "../../ut.h"
9b738f01
 #include "../../str_hash.h"
5ad39751
 
 
 MODULE_VERSION
 
3e3b0350
 #define MODULE_NAME "db2_ops"
9b738f01
 #define MODULE_NAME2 "db"
 
5ad39751
 static char* db_url = DEFAULT_DB_URL;
 static int xlbuf_size = 4096;
 
 enum dbops_type {OPEN_QUERY_OPS, INSERT_OPS, UPDATE_OPS, REPLACE_OPS, DELETE_OPS};
 
 #define FLD_DELIM ','
 #define PART_DELIM '/'
 
 #define NO_SCRIPT -1
 
 static str* xl_nul = NULL;
 static xl_print_log_f* xl_print = NULL;
 static xl_parse_format_f* xl_parse = NULL;
 static xl_get_nulstr_f* xl_getnul = NULL;
 
 static char *xlbuf = 0;
 static char *xlbuf_tail;
 
 struct xlstr {
 	char *s;
 	xl_elog_t* xlfmt;
 };
 
9b738f01
 struct extra_ops { 
 	char *name;
 	int type;
 	char *value;
 };
 
5ad39751
 struct dbops_action {
9b738f01
 	char *query_name;
5ad39751
 	char *db_url;
9b738f01
 
 	db_ctx_t* ctx;
 	db_cmd_t* cmd;
5ad39751
 
 	enum dbops_type operation;
9b738f01
 	int query_no;
5ad39751
 	int is_raw_query;
 	struct xlstr table;
9b738f01
 
5ad39751
 	int field_count;
 	struct xlstr* fields;
9b738f01
 
5ad39751
 	int where_count;
 	struct xlstr* wheres;
 	int op_count;
9b738f01
 
5ad39751
 	struct xlstr* ops;
 	int value_count;
 	struct xlstr* values;
9b738f01
 	int* value_types;
 
5ad39751
 	struct xlstr order;
 	struct xlstr raw;
 
9b738f01
 	int extra_ops_count;	
 	struct extra_ops* extra_ops;
 
5ad39751
 	db_res_t* result;   /* result of SELECT */
 
 	struct dbops_action* next;
 };
 
9b738f01
 struct dbops_handle {
 	char *handle_name;
5ad39751
 	struct dbops_action* action;
 	db_res_t* result;
 	int cur_row_no;
9b738f01
 	struct dbops_handle *next;
5ad39751
 };
 
 
 /* list of all operations */
 static struct dbops_action* dbops_actions = 0;
 
9b738f01
 /* list of declared handles, close them in post script callback */
 static struct dbops_handle* dbops_handles = 0;
5ad39751
 
 #define eat_spaces(_p) \
 	while( *(_p)==' ' || *(_p)=='\t' ){\
 	(_p)++;}
 
9b738f01
 #define eat_alphanum(_p) \
 	while ( (*(_p) >= 'a' && *(_p) <= 'z') || (*(_p) >= 'A' && *(_p) <= 'Z') || (*(_p) >= '0' && *(_p) <= '9') || (*(_p) == '_') ) {\
 		(_p)++;\
 	}
 
 static struct dbops_action* find_action_by_name(char *name, int len) {
 	struct dbops_action *a;
 	if (len == -1) len = strlen(name);
 	for (a=dbops_actions; a; a = a->next) {		
 		if (a->query_name && strlen(a->query_name)==len && strncmp(name, a->query_name, len) == 0)
 			return a;
 	}
 	return NULL;
 }
 
 static struct dbops_handle* find_handle_by_name(char *name, int len) {
 	struct dbops_handle *a;
 	if (len == -1) len = strlen(name);
 	for (a=dbops_handles; a; a = a->next) {		
 		if (a->handle_name && strlen(a->handle_name)==len && strncmp(name, a->handle_name, len) == 0)
 			return a;
 	}
 	return NULL;
 }
 
5ad39751
 static void trim_apostr(char **s) {
 	int i;
 	while ( **s == '\'') {
 		(*s)++;
 	}
 	i = strlen(*s);
 	while (i && (*s)[i-1] == '\'') {
 		i--;
 		(*s)[i] = 0;
 	}
 }
 
 static int get_next_part(char** s, char** part, char delim, int read_only) {
 	char *c, *c2;
 	char flag = 0;
 
 	c = c2 = *s;
 	eat_spaces(c);
 
 	while (!(((*c2 == delim) && !flag) || *c2==0)) {
 		if (*c2=='\'')
 			flag = !flag;
e30bbff8
 		c2++;
5ad39751
 	}
 	if ((*c2)==0 && flag) {
9b738f01
 		ERR(MODULE_NAME": string '%s' is not terminated\n", *s);
5ad39751
 		return E_CFG;
 	}
 	if (*c2) {
 		if (!read_only) *c2 = 0;
 		*s = c2+1;
 	}
 	else {
 		*s = c2;
 	}
 	eat_spaces(*s);
 	c2--;
 	/* rtrim */
 	while ( c2 > c && ((*c2 == ' ')||(*c2 == '\t')) ) {
 		if (!read_only) *c2 = 0;
 		c2--;
 	}
 	*part = c;
 	return 0;
 }
 
 static int split_fields(char *part, int *n, struct xlstr **strs) {
 	int i, res;
 	char *c, *fld;
 
 	*n = 0;
 	*strs = 0;
 	c = part;
 	while (*c) {
 		res = get_next_part(&c, &fld, FLD_DELIM, 1);
 		if (res < 0) return res;
 		(*n)++;
 	}
 	*strs = pkg_malloc( (*n)*sizeof(**strs));
 	if (!strs) {
9b738f01
 		ERR(MODULE_NAME": split_fields: not enough pkg memory\n");
5ad39751
 		return E_OUT_OF_MEM;
 	}
 	memset(*strs, 0, (*n)*sizeof(**strs));
 	i = 0;
 	c = part;
 	while (*c) {
 		res = get_next_part(&c, &(*strs)[i].s, FLD_DELIM, 0);
 		if (res < 0) return res;
 		trim_apostr(&(*strs)[i].s);
 		i++;
 	}
 	return 0;
 }
 
9b738f01
 static int get_type(char **s, int *type) {
 	if (*s && (*s)[0] && (*s)[1]==':') {
 		switch ((*s)[0]) {
 			case 't':
 				*type = DB_DATETIME;
 				break;
 			case 'i':
 				*type = DB_INT;
 				break;
 			case 'f':
 				*type = DB_FLOAT;
 				break;
 			case 'd':
 				*type = DB_DOUBLE;
 				break;
 			case 's':
 				*type = DB_CSTR;
 				break;
 			default:
 				ERR(MODULE_NAME": get_type: bad param type in '%s'\n", *s);
 				return E_CFG;
 		}
 		(*s)+=2;
 	}
 	return 0;
 }
 
 static int parse_ops(char* act_s, struct dbops_action** action, int has_name) {
5d060859
 	int res = 0, i;
5ad39751
 	char *c, *s, *part;
9b738f01
 	static int query_no = 0;
5ad39751
 
 	s = act_s;
 	*action = pkg_malloc(sizeof(**action));
 	if (!*action) return E_OUT_OF_MEM;
 	memset(*action, 0, sizeof(**action));
9b738f01
 	(*action)->query_no = query_no++;
 
 	eat_spaces(s);
 	c = s;
 	eat_alphanum(c);
 	if (has_name) {
 		char *c2;
 		c2 = c;
 		eat_spaces(c2);
 		if (c != s && *c2 == '=') {
 			*c = '\0';
 			if (find_action_by_name(s, -1) != NULL) {
 				ERR(MODULE_NAME": parse_ops: duplicate query name: %s\n", s);
 				return E_CFG;
 			}		
 			(*action)->query_name = s;
 			s = c2+1;
 			eat_spaces(s);
 			c = s;
 			eat_alphanum(c);
 		}
 		else {
 			ERR(MODULE_NAME": parse_ops: query_no: %d, valid query name not found in '%s'\n%s\n%s\n", (*action)->query_no, s, c, c2);
 			return E_CFG;
 		}
 	}
5ad39751
 
9b738f01
 	if (c[0] == ':' && c[1] == '/' && c[2] == '/') { /* database part is optional */
 		for (c=s; *c!=':'; c++) {
 			*c = tolower(*c);                       /* _type_://user:host/database_name/ */
 		}
 		(*action)->db_url = s;
 		s = c+1;
 		while (*s == '/') s++;
 		res = get_next_part(&s, &part, PART_DELIM, 1);  /* type://_user:host_/database_name/ */
 		if (res < 0) return res;
 
 
 		res = get_next_part(&s, &part, PART_DELIM, 0);  /* type://user:host/_database_name_/ */
 		if (res < 0) return res;
 	}
5ad39751
 	res = get_next_part(&s, &part, PART_DELIM, 0);
 	if (res < 0) return res;
 
 	for (c = part; *c && *c != PART_DELIM; c++) {
 		if (*c == ' ') {
 			(*action)->is_raw_query = 1;
 			*c = '\0';
 			break;
 		}
 	}
 	if (strcasecmp(part, "select") == 0)
 		(*action)->operation = OPEN_QUERY_OPS;
 	else if (strcasecmp(part, "insert") == 0)
 		(*action)->operation = INSERT_OPS;
 	else if (strcasecmp(part, "update") == 0)
 		(*action)->operation = UPDATE_OPS;
 	else if (strcasecmp(part, "replace") == 0)
 		(*action)->operation = REPLACE_OPS;
 	else if (strcasecmp(part, "delete") == 0)
 		(*action)->operation = DELETE_OPS;
 	else {
 		if ((*action)->is_raw_query) *c = ' ';
9b738f01
 		ERR(MODULE_NAME": parse_ops: query: %s(%d), unknown type of query '%s'\n", (*action)->query_name, (*action)->query_no, part);
5ad39751
 		return E_CFG;
 	}
 	if ((*action)->is_raw_query) {
 		*c = ' ';
 		(*action)->raw.s = part;
9b738f01
 		(*action)->table.s = part;
5ad39751
 	}
 
9b738f01
 	res = get_next_part(&s, &part, PART_DELIM, 0);
 	if (res < 0) return res;
 	if (!(*action)->is_raw_query) {
 
 		if (!*part) {
 			ERR(MODULE_NAME": parse_ops: query: %s(%d), table not specified near '%s' in '%s'\n", (*action)->query_name, (*action)->query_no, s, act_s);
 			return E_CFG;
5ad39751
 		}
9b738f01
 		trim_apostr(&part);
 		(*action)->table.s = part;
5ad39751
 
9b738f01
 		res = get_next_part(&s, &part, PART_DELIM, 0);
 		if (res < 0) return res;
 		switch ((*action)->operation) {
 			case OPEN_QUERY_OPS:
 			case UPDATE_OPS:
 			case REPLACE_OPS:
 			case INSERT_OPS:
 				res = split_fields(part, &(*action)->field_count, &(*action)->fields);
 				if (res < 0) return res;
 				if ((*action)->field_count == 0) {
 					ERR(MODULE_NAME": parse_ops: query: %s(%d), no field specified near '%s' ?n '%s'\n", (*action)->query_name, (*action)->query_no, part, act_s);
 					return E_CFG;
 				}
 				break;
 			case DELETE_OPS:
 				res = split_fields(part, &(*action)->where_count, &(*action)->wheres);
 				if (res < 0) return res;
 				res = get_next_part(&s, &part, PART_DELIM, 0);
 				if (res < 0) return res;
 				res = split_fields(part, &(*action)->op_count, &(*action)->ops);
 				if (res < 0) return res;
 				break;
 			default:;
 		}
5ad39751
 
9b738f01
 		res = get_next_part(&s, &part, PART_DELIM, 0);
5ad39751
 		if (res < 0) return res;
9b738f01
 		switch ((*action)->operation) {
 			case OPEN_QUERY_OPS:
 			case UPDATE_OPS:
 				res = split_fields(part, &(*action)->where_count, &(*action)->wheres);
 				if (res < 0) return res;
 				res = get_next_part(&s, &part, PART_DELIM, 0);
 				if (res < 0) return res;
 				res = split_fields(part, &(*action)->op_count, &(*action)->ops);
 				if (res < 0) return res;
 				res = get_next_part(&s, &part, PART_DELIM, 0);
 				if (res < 0) return res;
 				switch ((*action)->operation) {
 					case OPEN_QUERY_OPS:
 						if (*part) {
 							(*action)->order.s = part;
 						}
 						res = get_next_part(&s, &part, PART_DELIM, 0);
 						if (res < 0) return res;
 						break;
 					default:;
 				}
 				break;
 			default:
 				;
 		}
5ad39751
 	}
 
9b738f01
 	/* values */
 	res = split_fields(part, &(*action)->value_count, &(*action)->values);
5ad39751
 	if (res < 0) return res;
 
9b738f01
 	if ((*action)->value_count) {
 		(*action)->value_types = (int*)pkg_malloc(sizeof(int) * (*action)->value_count);
 		if ((*action)->value_types == NULL) {
 			ERR(MODULE_NAME": No memory left\n");
 			return -1;
 		}
5ad39751
 
9b738f01
 		for (i=0; i<(*action)->value_count; i++) {
 			(*action)->value_types[i] = DB_CSTR; // DB_NONE; /* let decide db driver itself, FIXME: until jjanak changes then default type is string */
 			res = get_type(&(*action)->values[i].s, &(*action)->value_types[i]);
5ad39751
 			if (res < 0) return res;
9b738f01
 		}
5ad39751
 	}
 
9b738f01
 	/* extra options */
5ad39751
 	res = get_next_part(&s, &part, PART_DELIM, 0);
 	if (res < 0) return res;
9b738f01
 
 	(*action)->extra_ops_count = 0;
 	c = part;
 	while (*c) {
 		char *fld;
 		res = get_next_part(&c, &fld, FLD_DELIM, 1);
 		if (res < 0) return res;
 		(*action)->extra_ops_count++;
 	}
 	if ((*action)->extra_ops_count > 0) {
 		(*action)->extra_ops = pkg_malloc( (*action)->extra_ops_count*sizeof(*(*action)->extra_ops));
 		if (!(*action)->extra_ops) {
 			ERR(MODULE_NAME": parse_ops: not enough pkg memory\n");
 			return E_OUT_OF_MEM;
 		}
 		memset((*action)->extra_ops, 0, (*action)->extra_ops_count*sizeof(*(*action)->extra_ops));
 
 		i = 0;
 		c = part;
 		while (*c) {
 			char *fld;
 			res = get_next_part(&c, &fld, FLD_DELIM, 0);
5ad39751
 			if (res < 0) return res;
9b738f01
 			/* name=[i|s]:value */
 			(*action)->extra_ops[i].name = fld;
 			eat_alphanum(fld);
 			if (*fld != '=') {
 				ERR(MODULE_NAME": parse_ops: query: %s(%d), bad extra parameter format in '%s'\n", (*action)->query_name, (*action)->query_no, (*action)->extra_ops[i].name);
 				return E_CFG;
5ad39751
 			}
9b738f01
 			*fld = '\0';
 			fld++;
 			while (*fld==' ' || *fld=='\t') fld++;
 			(*action)->extra_ops[i].type = DB_NONE;
 			res = get_type(&fld, &(*action)->extra_ops[i].type);
5ad39751
 			if (res < 0) return res;
9b738f01
 			trim_apostr(&fld);
 			(*action)->extra_ops[i].value = fld;
 			DEBUG(MODULE_NAME": extra_ops #%d, name='%s', type=%d, val='%s'\n", i, (*action)->extra_ops[i].name, (*action)->extra_ops[i].type, (*action)->extra_ops[i].value);
 			i++;
 		}
5ad39751
 	}
9b738f01
 
5ad39751
 	if (*s) {
9b738f01
 		ERR(MODULE_NAME": parse_ops: query: %s(%d), too many parameters/parts, remaining '%s' in '%s'\n", (*action)->query_name, (*action)->query_no, s, act_s);
5ad39751
 		return E_CFG;
 	}
9b738f01
 	if ((*action)->is_raw_query) {
 		DEBUG(MODULE_NAME": query: %s(%d) oper:%d database:'%s' query:'%s' value#:%d extra_ops#:%d\n", (*action)->query_name, (*action)->query_no, (*action)->operation, (*action)->db_url, (*action)->raw.s, (*action)->value_count, (*action)->extra_ops_count);
 	}
 	else {
 		/* check num of fields */
 		if ((((*action)->operation==OPEN_QUERY_OPS)?0:(*action)->field_count)+(*action)->where_count != (*action)->value_count) {
 			ERR(MODULE_NAME": parse_ops: query: %s(%d), number of values does not correspond to number of fields (%d+%d!=%d) in '%s'\n", (*action)->query_name, (*action)->query_no, ((*action)->operation==OPEN_QUERY_OPS)?0:(*action)->field_count,  (*action)->where_count, (*action)->value_count, act_s);
 			return E_CFG;
 		}
 		DEBUG(MODULE_NAME": query_no:%d oper:%d database:'%s' table:'%s' 'field#:'%d' where#:'%d' order:'%s' value#:%d extra_ops#:%d\n", (*action)->query_no, (*action)->operation, (*action)->db_url, (*action)->table.s, (*action)->field_count,  (*action)->where_count, (*action)->order.s, (*action)->value_count, (*action)->extra_ops_count);
5ad39751
 	}
 	return 0;
 }
 
 static int parse_xlstr(struct xlstr* s) {
 
 	if (!s->s) return 0;
 	if (!strchr(s->s, '%')) return 0;
 	/* probably xl_log formating */
 
 	if (!xl_print) {
 		xl_print=(xl_print_log_f*)find_export("xprint", NO_SCRIPT, 0);
 
 		if (!xl_print) {
cf49d5da
 			ERR(MODULE_NAME": cannot find \"xprint\", is module xprint loaded?\n");
5ad39751
 			return E_UNSPEC;
 		}
 	}
 
 	if (!xl_parse) {
 		xl_parse=(xl_parse_format_f*)find_export("xparse", NO_SCRIPT, 0);
 
 		if (!xl_parse) {
cf49d5da
 			ERR(MODULE_NAME": cannot find \"xparse\", is module xprint loaded?\n");
5ad39751
 			return E_UNSPEC;
 		}
 	}
 
 	if (!xl_nul) {
 		xl_getnul=(xl_get_nulstr_f*)find_export("xnulstr", NO_SCRIPT, 0);
 		if (xl_getnul)
 			xl_nul=xl_getnul();
 
 		if (!xl_nul){
cf49d5da
 			ERR(MODULE_NAME": cannot find \"xnulstr\", is module xprint loaded?\n");
5ad39751
 			return E_UNSPEC;
 		}
 	else
cf49d5da
 		INFO(MODULE_NAME": xprint null is \"%.*s\"\n", xl_nul->len, xl_nul->s);
5ad39751
 	}
 
 	if(xl_parse(s->s, &s->xlfmt) < 0) {
9b738f01
 		ERR(MODULE_NAME": wrong format '%s'\n", s->s);
5ad39751
 		return E_UNSPEC;
 	}
 
 	return 0;
 }
 
 static int eval_xlstr(struct sip_msg* msg, struct xlstr* s) {
 	static char* null_str = "";
 	int len;
 	if (s->xlfmt) {
 		len = xlbuf_size - (xlbuf_tail-xlbuf);
 		if (xl_print(msg, s->xlfmt, xlbuf_tail, &len) < 0) {
9b738f01
 			ERR(MODULE_NAME": eval_xlstr: Error while formating result\n");
5ad39751
 			return E_UNSPEC;
 		}
 
 		/* note: xl_null value is returned as "<null>" string. It's pretty useless checking "if xlbuf_tail==xl_null then xlbuf_tail="";" because xl_null may be also inside string. What about implementing xl_set_nullstr to xl_lib? */
 		if ((xl_nul) && (xl_nul->len==len) && strncmp(xl_nul->s, xlbuf_tail, len)==0) {
 			s->s = null_str;
 		}
 		else {
 			s->s = xlbuf_tail;
 			s->s[len] = '\0';
 			xlbuf_tail += len+1;
 		}
 	}
 	else {
 		if (!s->s)
 			s->s = null_str;
 	}
 	return 0;
 }
 
9b738f01
 static int dbops_func(struct sip_msg* m, struct dbops_action* action) 
 {
 /*	char* order;*/
5ad39751
 	int res, i;
9b738f01
 /* raw query is pre-compiled too 
5ad39751
 	if (action->is_raw_query) {
9b738f01
 		DEBUG(MODULE_NAME": dbops_func(raw, %d, '%s'\n", action->operation, action->raw.s);
5ad39751
 		res = eval_xlstr(m, &action->raw);
 		if (res < 0) return res;
9b738f01
 		
 	   	 * FIXME: We have to make sure that we do not use pre-compiled statements
 		 * here because these must not change at runtime: to be checked by janakj
 		 *
 		action->cmd->table.s = action->raw.s;
 		action->cmd->table.len = strlen(action->raw.s);
 		
 		if (db_exec((action->operation==OPEN_QUERY_OPS?&action->result:NULL), action->cmd) < 0) {
 			ERR(MODULE_NAME": database operation (%d) error, raw: '%s'\n", action->operation, action->raw.s);
5ad39751
 			return -1;
 		}
 		return 1;
 	}
9b738f01
 */
 	if (action->is_raw_query) {
 		DEBUG(MODULE_NAME": dbops_func(%s, %d, raw, %d, '%s', %d)\n", action->query_name, action->query_no, action->operation, action->cmd->table.s, action->value_count);
 		/* raw query is pre-compiled too 
 		res = eval_xlstr(m, &action->raw);
 		if (res < 0) return res;
 		
 	   	 * FIXME: We have to make sure that we do not use pre-compiled statements
 		 * here because these must not change at runtime: to be checked by janakj
 		 *
 		action->cmd->table.s = action->raw.s;
 		action->cmd->table.len = strlen(action->raw.s);
 		*/
5ad39751
 	}
9b738f01
 	else {
 		DEBUG(MODULE_NAME": dbops_func(%s, %d, %d, '%s', %d, %d, %d)\n", action->query_name, action->query_no, action->operation, action->table.s, action->field_count, action->where_count, action->value_count);
 	
 		/* FIXME: We do not support volatile table names yet */
 		/*	res = eval_xlstr(m, &action->table); 
 			if (res < 0) return res;
 		*/
 
 		/*
 		 * FIXME: Changing field names in result set is not yet supported
 			for (i=0; i<action->field_count; i++) {
 				res = eval_xlstr(m, &action->fields[i]);
 				if (res < 0) goto cleanup;
 				action->cmd->result[i].v.name = action->fields[i].s;
 			}
 		*/
 		/*
 		 * FIXME: Changing parameters names and operation not yet
 		 * supported at runtime
 			for (i=0; i<action->where_count; i++) {
 				res = eval_xlstr(m, &action->wheres[i]);
 				if (res < 0) goto cleanup;
 				action->cmd->params[i].name = action->wheres[i].s;
 
 				if (i < action->op_count) {
 					res = eval_xlstr(m, &action->ops[i]);
 					if (res < 0) goto cleanup;
 					action->cmd->params[i].op = action->ops[i].s;
 				}
 				else {
 					action->cmd->params[i].op = OP_EQ;
 				}
 			}
 		*/
 		/* FIXME: Changing parameters names and operation not yet
 		 * supported at runtime
 			if (action->operation == OPEN_QUERY_OPS) {
 				if (action->order.s) {
 					res = eval_xlstr(m, &action->order);
 					if (res < 0) return res;
 				}
 				order = action->order.s;
 			}
 		*/
5ad39751
 	}
9b738f01
 	for (i=0; i<action->value_count; i++) {
 		char *end;
 		db_fld_t *cmd_params;
 		res = eval_xlstr(m, &action->values[i]);
5ad39751
 		if (res < 0) goto cleanup;
 
9b738f01
 		/* split single db_ops values range to db_api matches and vals ranges */
 		if (action->is_raw_query) {
 			cmd_params = action->cmd->match+i;
5ad39751
 		}
 		else {
9b738f01
 			switch (action->operation) {
 				case OPEN_QUERY_OPS:
 				case DELETE_OPS:
 					cmd_params = action->cmd->match+i;
 					break;
 				case UPDATE_OPS:
 					if (i < action->field_count)
 						cmd_params = action->cmd->vals+i;
 					else
 						cmd_params = action->cmd->match+i-action->field_count;
 					break;
 				case REPLACE_OPS:
 				case INSERT_OPS:
 					cmd_params = action->cmd->vals+i;
 					break;
 				default:
 					BUG("Unknown operation type: %d\n", action->operation);
 					goto err;
 			}
5ad39751
 		}
9b738f01
 		
 		if (!action->values[i].s || !action->values[i].s[0]) cmd_params->flags |= DB_NULL;
 		switch (cmd_params->type) {
5d060859
 			case DB_DATETIME:
9b738f01
 				if (!(cmd_params->flags & DB_NULL))
 					cmd_params->v.time = strtol(action->values[i].s, &end, 10);
5d060859
 				break;
 			case DB_INT:
9b738f01
 				if (!(cmd_params->flags & DB_NULL))
 					cmd_params->v.int4 = strtol(action->values[i].s, &end, 10);
5d060859
 				break;
 			case DB_FLOAT:
9b738f01
 				if (!(cmd_params->flags & DB_NULL))
5d060859
 				#ifdef  __USE_ISOC99
9b738f01
 					cmd_params->v.flt = strtof(action->values[i].s, &end);
5d060859
 				#else
9b738f01
 					cmd_params->v.flt = strtod(action->values[i].s, &end);
5d060859
 				#endif
 				break;
 			case DB_DOUBLE:
9b738f01
 				if (!(cmd_params->flags & DB_NULL))
 					cmd_params->v.dbl = strtod(action->values[i].s, &end);
5d060859
 				break;
9b738f01
 			case DB_CSTR:
 				cmd_params->v.cstr = action->values[i].s;
 				cmd_params->flags &= ~DB_NULL;
5d060859
 				break;
9b738f01
 		default:
 				BUG("Unknown value type: %d\n", cmd_params->type);
5d060859
 				goto err;
 		}
5ad39751
 	}
9b738f01
 	if (db_exec((action->operation==OPEN_QUERY_OPS?&action->result:NULL), action->cmd) < 0) goto err;
5ad39751
 	res = 1;
 cleanup:
 	return res;
 err:
9b738f01
 	ERR(MODULE_NAME": query: %s(%d), database operation (%d) error, table: '%s'\n", action->query_name, action->query_no, action->operation, action->table.s);
5ad39751
 	res = -1;
 	goto cleanup;
 }
 
9b738f01
 static int do_seek(db_res_t* result, int *cur_row_no, int row_no) {
 
 	if (row_no == *cur_row_no) return 0;
 	if (row_no < *cur_row_no) *cur_row_no = -1;
 
 	DEBUG(MODULE_NAME": do_seek: currowno:%d, rowno=%d\n", *cur_row_no, row_no);
 	if (*cur_row_no < 0) {
 		if (!db_first(result)) return -1;
 		*cur_row_no = 0;
 	}
 	while (*cur_row_no < row_no) {
 		if (!db_next(result)) {
 			*cur_row_no = -1;
 			return -1;
 		}
 		(*cur_row_no)++;
 	}
 	return 0;
 }
 
 static int sel_get_field(str* res, int *cur_row_no, int field_no, db_res_t* result) {
5ad39751
 /* return string in static buffer, I'm not sure if local static variable is OK, e.g. when comparing 2 selects */
 	int len;
9b738f01
 	db_rec_t* rec;
5ad39751
 	len = xlbuf_size-(xlbuf_tail-xlbuf);
 	res->s = xlbuf_tail;
 	res->len = 0;
 	if (field_no == -2) {  /* cur_row_no */
9b738f01
 		res->len = snprintf(res->s, len, "%d", *cur_row_no);
5ad39751
 	}
9b738f01
 	else if (field_no < 0) {  /* count(*) | is empty */
 		int n;
 		if (*cur_row_no < 0) {
 			rec = db_first(result);
 			if (rec) {
 				*cur_row_no = 0;
 			}
 		}
 		if (field_no == -3) { /* is_empty */
 			if (*cur_row_no >= 0) 
 				n = 0;
 			else
 				n = 1;
 		}
 		else {
 			n = 0;
 			if (*cur_row_no >= 0) {
 				do {
 					n++;
 					rec = db_next(result);
 				} while (rec);
 			}
 			*cur_row_no = -1;
 		}
 		res->len = snprintf(res->s, len, "%d", n);
5ad39751
 	}
 	else {
9b738f01
 		if (*cur_row_no < 0) {
 			ERR(MODULE_NAME": cursor points beyond data\n");
 			return -1;	
5ad39751
 		}
9b738f01
 		if (field_no >= result->field_count) {
 			ERR(MODULE_NAME": field (%d) does not exist, num fields: %d\n", field_no, result->field_count);
5ad39751
 			return -1;
 		}
9b738f01
 		rec = result->cur_rec;
 		if (!(rec->fld[field_no].flags & DB_NULL)) {
 			switch (rec->fld[field_no].type) {
5ad39751
 				case DB_INT:
9b738f01
 					res->len = snprintf(res->s, len, "%d", rec->fld[field_no].v.int4);
5ad39751
 					break;
 				case DB_FLOAT:
9b738f01
 					res->len = snprintf(res->s, len, "%f", rec->fld[field_no].v.flt);
5ad39751
 					break;
 				case DB_DOUBLE:
9b738f01
 					res->len = snprintf(res->s, len, "%f", rec->fld[field_no].v.dbl);
5ad39751
 					break;
 				case DB_STR:
9b738f01
 					res->len = snprintf(res->s, len, "%.*s", 
 										rec->fld[field_no].v.lstr.len,
 										rec->fld[field_no].v.lstr.s);
5ad39751
 					break;
 				case DB_BLOB:
9b738f01
 					res->len = snprintf(res->s, len, "%.*s", 
 										rec->fld[field_no].v.blob.len,
 										rec->fld[field_no].v.blob.s);
5ad39751
 					break;
9b738f01
 				case DB_CSTR:
 					res->len = snprintf(res->s, len, "%s", rec->fld[field_no].v.cstr);
5ad39751
 					break;
 				case DB_DATETIME:
9b738f01
 					res->len = snprintf(res->s, len, "%u", (unsigned int) rec->fld[field_no].v.time);
5ad39751
 					break;
 				case DB_BITMAP:
9b738f01
 					res->len = snprintf(res->s, len, "%u", (unsigned int) rec->fld[field_no].v.bitmap);
5ad39751
 					break;
 				default:
 					break;
 			}
 		}
 	}
 	xlbuf_tail += res->len;
 	return 0;
 }
 
9b738f01
 static int sel_do_select(str* result, str *query_name, int row_no, int field_no, struct sip_msg* msg) {
5ad39751
 	struct dbops_action *a;
9b738f01
 	int cur_row_no, res;
5ad39751
 
9b738f01
 	a = find_action_by_name(query_name->s, query_name->len);
 	if (!a) {
 		ERR(MODULE_NAME": select: query: %.*s not declared using declare_query param\n", query_name->len, query_name->s);
5ad39751
 		return -1;
 	}
9b738f01
 	if (a->operation != OPEN_QUERY_OPS) {
 		ERR(MODULE_NAME": select: query: %.*s is not select\n", query_name->len, query_name->s);
 		return -1;
 	}
 
 	if (row_no < 0) {
 		ERR(MODULE_NAME": select: Row number must not be negative: %d\n", row_no);
5ad39751
 		return -1;
 	}
9b738f01
 
5ad39751
 	res = dbops_func(msg, a);
 	if (res < 0) return res;
9b738f01
 	cur_row_no = -1;
 	if (field_no >= 0) {
 		if (do_seek(a->result, &cur_row_no, row_no) < 0)
 			return -1;
 	}
 
 	res = sel_get_field(result, &cur_row_no, field_no, a->result);
 	db_res_free(a->result);
5ad39751
 	return res;
 }
 
9b738f01
 static inline int check_query_opened(struct dbops_handle *handle, char *f) {
 	if (!handle->result) {
 		ERR(MODULE_NAME": %s: handle '%s' is not opened. Use db_query() first\n", f, handle->handle_name);
5ad39751
 		return -1;
 	}
9b738f01
 	else
 		return 1;
 }
 
 static int sel_do_fetch(str* result, str *handle_name, int field_no, struct sip_msg* msg) {
 	struct dbops_handle *a;
 	a = find_handle_by_name(handle_name->s, handle_name->len);
 	if (!a) {
 		ERR(MODULE_NAME": fetch: handle (%.*s) is not declared\n", handle_name->len, handle_name->s);
5ad39751
 		return -1;
 	}
9b738f01
 	if (check_query_opened(a, "fetch") < 0) return -1;	
 	return sel_get_field(result, &a->cur_row_no, field_no, a->result);
5ad39751
 }
 
 static int sel_dbops(str* res, select_t* s, struct sip_msg* msg) {  /* dummy */
 	return 0;
 }
 
 static int sel_select(str* res, select_t* s, struct sip_msg* msg) {
9b738f01
 	return sel_do_select(res, &s->params[2].v.s, 0, 0, msg);
5ad39751
 }
 
9b738f01
 static int sel_select_is_empty(str* res, select_t* s, struct sip_msg* msg) {
 	return sel_do_select(res, &s->params[2].v.s, 0, -3, msg);
5ad39751
 }
 
9b738f01
 static int sel_select_count(str* res, select_t* s, struct sip_msg* msg) {
 	return sel_do_select(res, &s->params[2].v.s, 0, -1, msg);
5ad39751
 }
 
 static int sel_select_row(str* res, select_t* s, struct sip_msg* msg) {
9b738f01
 	return sel_do_select(res, &s->params[2].v.s, s->params[4].v.i, 0, msg);
5ad39751
 }
 
9b738f01
 
5ad39751
 static int sel_select_row_field(str* res, select_t* s, struct sip_msg* msg) {
9b738f01
 	return sel_do_select(res, &s->params[2].v.s, s->params[4].v.i, s->params[6].v.i, msg);
 }
 
 static int sel_select_field(str* res, select_t* s, struct sip_msg* msg) {
 	return sel_do_select(res, &s->params[2].v.s, 0, s->params[4].v.i, msg);
5ad39751
 }
 
 static int sel_fetch(str* res, select_t* s, struct sip_msg* msg) {
9b738f01
 	return sel_do_fetch(res, &s->params[2].v.s, 0, msg);
5ad39751
 }
 
9b738f01
 static int sel_fetch_field(str* res, select_t* s, struct sip_msg* msg) {
 	return sel_do_fetch(res, &s->params[2].v.s, s->params[4].v.i, msg);
5ad39751
 }
 
 static int sel_fetch_cur_row_no(str* res, select_t* s, struct sip_msg* msg) {
9b738f01
 	return sel_do_fetch(res, &s->params[2].v.s, -2, msg);
5ad39751
 }
 
9b738f01
 static int sel_fetch_is_empty(str* res, select_t* s, struct sip_msg* msg) {
 	return sel_do_fetch(res, &s->params[2].v.s, -3, msg);
5ad39751
 }
 
9b738f01
 static int sel_fetch_count(str* res, select_t* s, struct sip_msg* msg) {
 	return sel_do_fetch(res, &s->params[2].v.s, -1, msg);
5ad39751
 }
 
 
c7fb8951
 SELECT_F(select_any_nameaddr)
 SELECT_F(select_any_uri)
5ad39751
 
 select_row_t sel_declaration[] = {
9b738f01
 	{ NULL, SEL_PARAM_STR, STR_STATIC_INIT(MODULE_NAME2), sel_dbops, SEL_PARAM_EXPECTED},
5ad39751
 
9b738f01
 	{ sel_dbops, SEL_PARAM_STR, STR_STATIC_INIT("query"), sel_select, CONSUME_NEXT_STR},
 	{ sel_select, SEL_PARAM_STR, STR_STATIC_INIT("is_empty"), sel_select_is_empty},
 	{ sel_select, SEL_PARAM_STR, STR_STATIC_INIT("count"), sel_select_count},
c7fb8951
 	{ sel_select, SEL_PARAM_STR, STR_STATIC_INIT("nameaddr"), select_any_nameaddr, NESTED | CONSUME_NEXT_STR},
 	{ sel_select, SEL_PARAM_STR, STR_STATIC_INIT("uri"), select_any_uri, NESTED | CONSUME_NEXT_STR},
5ad39751
 	{ sel_select, SEL_PARAM_STR, STR_STATIC_INIT("field"), sel_select_field, CONSUME_NEXT_INT},
c7fb8951
 	{ sel_select_field, SEL_PARAM_STR, STR_STATIC_INIT("nameaddr"), select_any_nameaddr, NESTED | CONSUME_NEXT_STR},
 	{ sel_select_field, SEL_PARAM_STR, STR_STATIC_INIT("uri"), select_any_uri, NESTED | CONSUME_NEXT_STR},
9b738f01
 	
5ad39751
 	{ sel_select, SEL_PARAM_STR, STR_STATIC_INIT("row"), sel_select_row, CONSUME_NEXT_INT},
 	{ sel_select_row, SEL_PARAM_STR, STR_STATIC_INIT("field"), sel_select_row_field, CONSUME_NEXT_INT},
c7fb8951
 	{ sel_select_row_field, SEL_PARAM_STR, STR_STATIC_INIT("nameaddr"), select_any_nameaddr, NESTED | CONSUME_NEXT_STR},
 	{ sel_select_row_field, SEL_PARAM_STR, STR_STATIC_INIT("uri"), select_any_uri, NESTED | CONSUME_NEXT_STR},
9b738f01
 	
 	{ sel_dbops, SEL_PARAM_STR, STR_STATIC_INIT("fetch"), sel_fetch, CONSUME_NEXT_STR},
c7fb8951
 	{ sel_fetch, SEL_PARAM_STR, STR_STATIC_INIT("nameaddr"), select_any_nameaddr, NESTED | CONSUME_NEXT_STR},
 	{ sel_fetch, SEL_PARAM_STR, STR_STATIC_INIT("uri"), select_any_uri, NESTED | CONSUME_NEXT_STR},
5ad39751
 	{ sel_fetch, SEL_PARAM_STR, STR_STATIC_INIT("row_no"), sel_fetch_cur_row_no, 0},
9b738f01
 	{ sel_fetch, SEL_PARAM_STR, STR_STATIC_INIT("count"), sel_fetch_count, 0},
 	{ sel_fetch, SEL_PARAM_STR, STR_STATIC_INIT("is_empty"), sel_fetch_is_empty, 0},
5ad39751
 	{ sel_fetch, SEL_PARAM_STR, STR_STATIC_INIT("field"), sel_fetch_field, CONSUME_NEXT_INT},
c7fb8951
 	{ sel_fetch_field, SEL_PARAM_STR, STR_STATIC_INIT("nameaddr"), select_any_nameaddr, NESTED | CONSUME_NEXT_STR},
 	{ sel_fetch_field, SEL_PARAM_STR, STR_STATIC_INIT("uri"), select_any_uri, NESTED | CONSUME_NEXT_STR},
9b738f01
 		
 	{ NULL, SEL_PARAM_INT, STR_NULL, NULL, 0}
5ad39751
 };
 
9b738f01
 static int dbops_close_query_func(struct sip_msg* m, char* handle, char* dummy) {
 	struct dbops_handle *a = (void *)handle;
 	if (a->result) {
 		db_res_free(a->result);
 		a->result = 0;
5ad39751
 	}
 	return 1;
 }
 
8216129f
 static int dbops_pre_script_cb(struct sip_msg *msg, unsigned int flags, void *param) {
5ad39751
 	xlbuf_tail = xlbuf;
 	return 1;
 }
 
8216129f
 static int dbops_post_script_cb(struct sip_msg *msg, unsigned int flags, void *param) {
9b738f01
 	struct dbops_handle *a;
 	for (a = dbops_handles; a; a=a->next) {
 		dbops_close_query_func(msg, (char*) a, 0);
5ad39751
 	}
 	return 1;
 }
 
 static int init_action(struct dbops_action* action) {
9b738f01
 	int res, i;
5ad39751
 
 	if (!action->db_url)
 		action->db_url = db_url;
9b738f01
 
5ad39751
 	res = parse_xlstr(&action->table);
 	if (res < 0) return res;
 	for (i=0; i<action->field_count; i++) {
 		res = parse_xlstr(&action->fields[i]);
 		if (res < 0) return res;
 	}
 
 	for (i=0; i<action->where_count; i++) {
 		res = parse_xlstr(&action->wheres[i]);
 		if (res < 0) return res;
 	}
 	for (i=0; i<action->value_count; i++) {
 		res = parse_xlstr(&action->values[i]);
 		if (res < 0) return res;
 	}
 	res = parse_xlstr(&action->order);
 	if (res < 0) return res;
 	res = parse_xlstr(&action->raw);
 
         return res;
 }
 
 static int dbops_fixup_func(void** param, int init_act) {
 	struct dbops_action **p, *a;
 	char *c;
9b738f01
 	int res;
5ad39751
 
 	/* check if is it a declare_no that references to declare_xxxx */
 	c = *param;
 	eat_spaces(c);
 	*param = c;
9b738f01
 	eat_alphanum(c);
 	if (*c == 0) {
 		a = find_action_by_name(*param, -1);
5ad39751
 		if (!a) {
9b738f01
 			ERR(MODULE_NAME": fixup_func: query (%s) not declared\n", (char*) *param);
5ad39751
 			return -1;
 		}
 		*param = (void*) a;
 		return 0;
 	}
 
 	for (p = &dbops_actions; *p; p=&(*p)->next);	/* add at the end of list */
9b738f01
 	res = parse_ops(*param, p, init_act == 0 /* declare query has name */);
5ad39751
 	if (res < 0) return res;
 	/* pkg_free(*param); do not free it!*/
 	*param = (void*) *p;
 	if (init_act)
 		return init_action(*p);   /* fixup is acquired after init_mod() therefore initialize new action */
 	else
 		return 0;
 }
 
 static int mod_init(void) {
 	struct dbops_action* p;
 
 	xlbuf = pkg_malloc((xlbuf_size+1)*sizeof(char));
 	if (!xlbuf) {
9b738f01
 		ERR(MODULE_NAME": out of memory, cannot create xlbuf\n");
5ad39751
 		return E_OUT_OF_MEM;
 	}
 
 	for (p=dbops_actions; p; p=p->next) {
13b5ead5
 		int res;
 		res = init_action(p);
 		if (res < 0)
 			return res;
5ad39751
 	}
 
8216129f
 	register_script_cb(dbops_pre_script_cb, REQUEST_CB | ONREPLY_CB | PRE_SCRIPT_CB, 0);
 	register_script_cb(dbops_post_script_cb, REQUEST_CB | ONREPLY_CB | POST_SCRIPT_CB, 0);
5ad39751
 	register_select_table(sel_declaration);
 
 	return 0;
 }
 
9b738f01
 
 static int build_match(db_fld_t** match, struct dbops_action* p)
 {
 	int i;
 	db_fld_t* newp;
 
 	if (!p->where_count) {
 		*match = NULL;
 		return 0;
 	}
 
 	newp = (db_fld_t*)pkg_malloc(sizeof(db_fld_t) * (p->where_count + 1));
 	if (newp == NULL) {
 		ERR(MODULE_NAME": No memory left\n");
 		return -1;
 	}
 	memset(newp, '\0', sizeof(db_fld_t) * p->where_count);
 
 	for(i = 0; i < p->where_count; i++) {
 		newp[i].name = p->wheres[i].s;
 		newp[i].type = p->value_types[i];
 
 		if (i < p->op_count) {
 			if (!strcmp(p->ops[i].s, "=")) {
 				newp[i].op = DB_EQ;
 			} else if (!strcmp(p->ops[i].s, "<=")) {
 				newp[i].op = DB_LEQ;
 			} else if (!strcmp(p->ops[i].s, "<")) {
 				newp[i].op = DB_LT;
 			} else if (!strcmp(p->ops[i].s, ">")) {
 				newp[i].op = DB_GT;
 			} else if (!strcmp(p->ops[i].s, ">=")) {
 				newp[i].op = DB_GEQ;
a354229c
 			} else if (!strcmp(p->ops[i].s, "<>")) {
 				newp[i].op = DB_NE;
 			} else if (!strcmp(p->ops[i].s, "!=")) {
 				newp[i].op = DB_NE;
9b738f01
 			} else {
 				ERR(MODULE_NAME": Unsupported operator type: %s\n", p->ops[i].s);
 				pkg_free(newp);
 				return -1;
 			}
 		}
 		else {
 			newp[i].op = DB_EQ;
 		}
 	}
 	newp[i].name = NULL;
 	
 	*match = newp;
 	return 0;
 }
 
 
 static int build_result(db_fld_t** result, struct dbops_action* p)
 {
 	int i;
 	db_fld_t* newp;
 
 	if (!p->field_count) {
 		*result = NULL;
 		return 0;
 	}
 
 	newp = (db_fld_t*)pkg_malloc(sizeof(db_fld_t) * (p->field_count + 1));
 	if (newp == NULL) {
 		ERR(MODULE_NAME": No memory left\n");
 		return -1;
 	}
 	memset(newp, '\0', sizeof(db_fld_t) * p->field_count);
 
 	for(i = 0; i < p->field_count; i++) {
 		newp[i].name = p->fields[i].s;
 		newp[i].type = DB_NONE;
 	}
 	newp[i].name = NULL;
 	*result = newp;
 	return 0;
 }
 
 
 static int build_params(db_fld_t** params, struct dbops_action* p)
 {
 	int i;
 	db_fld_t* newp;
 
 	if (!p->value_count) {
 		*params = NULL;
 		return 0;
 	}
 
 	newp = (db_fld_t*)pkg_malloc(sizeof(db_fld_t) * (p->value_count - p->where_count + 1));
 	if (newp == NULL) {
 		ERR(MODULE_NAME": No memory left\n");
 		return -1;
 	}
 	memset(newp, '\0', sizeof(db_fld_t) * p->value_count);
 
 	for(i = 0; i < p->value_count - p->where_count; i++) {
 		newp[i].name = (i < p->field_count)?p->fields[i].s:""; /* in case of raw query it's empty */
 		newp[i].type = p->value_types[i];
 	}
 	newp[i].name = NULL;
 	
 	*params = newp;
 	return 0;
 }
 
 
 
 static int init_db(struct dbops_action* p)
 {
 	db_fld_t* matches = NULL, *result = NULL, *values = NULL;
 	int type, i;
 
 	DEBUG(MODULE_NAME": init_db: query: %s(%d)\n", p->query_name, p->query_no);
 	if (p->db_url == NULL) {
 		ERR(MODULE_NAME": No database URL specified\n");
 		return -1;
 	}
 	p->ctx = db_ctx(MODULE_NAME);
 	if (p->ctx == NULL) {
 		ERR(MODULE_NAME": Error while initializing database layer\n");
 		return -1;
 	}
 	
 	if (db_add_db(p->ctx, p->db_url) < 0) return -1;
 	if (db_connect(p->ctx) < 0) return -1;
 
 	if (p->is_raw_query) {
 		type = DB_SQL;
 		if (build_params(&matches, p) < 0) return -1;
 	}
 	else {
 		switch(p->operation) {
 		case INSERT_OPS:
 		case REPLACE_OPS:
 			type = DB_PUT;
 			if (build_params(&values, p) < 0) return -1;
 			break;
 
 		case UPDATE_OPS:
 			type = DB_UPD;
 			if (build_match(&matches, p) < 0) return -1;
 			if (build_params(&values, p) < 0) {
 				if (matches) pkg_free(matches);
 				return -1;
 			}
 			break;
 
 		case DELETE_OPS:
 			type = DB_DEL;
 
 			if (build_match(&matches, p) < 0) return -1;
 			break;
 
 		case OPEN_QUERY_OPS:
 			type = DB_GET;
 			if (build_match(&matches, p) < 0) return -1;
 			if (build_result(&result, p) < 0) {
 				if (matches) pkg_free(matches);
 				return -1;
 			}
 			break;
 		default:
 			BUG("Unknown operation %d\n", p->operation);
 			return -1;
 		}
 	}
 
 	p->cmd = db_cmd(type, p->ctx, p->table.s, result, matches, values);
 	if (p->cmd == NULL) {
 		ERR(MODULE_NAME": init_db: query: %s(%d), error while compiling database query\n", p->query_name, p->query_no);
 		if (values) pkg_free(values);
 		if (matches) pkg_free(matches);
 		if (result) pkg_free(result);
 		db_disconnect(p->ctx);
 		db_ctx_free(p->ctx);
 		return -1;
 	}
 	if (values) pkg_free(values);
 	if (matches) pkg_free(matches);
 	if (result) pkg_free(result);
 
 	for (i=0; i<p->extra_ops_count; i++) {
 		char *end;
 		DEBUG(MODULE_NAME": init_db: query_no: %s(%d), setopt('%s', %i, '%s'\n", p->query_name, p->query_no, p->extra_ops[i].name, p->extra_ops[i].type, p->extra_ops[i].value);
 		switch (p->extra_ops[i].type) {
 			case DB_NONE: 
 				/* set null ?? */
 				break;			
 			case DB_DATETIME: {
 				time_t v;
 				v = strtol(p->extra_ops[i].value, &end, 10);
 				if (db_setopt(p->cmd, p->extra_ops[i].name, v) < 0) return -1;
 				break;
 			}
 			case DB_INT: {
 				int v;
 				v = strtol(p->extra_ops[i].value, &end, 10);
 				if (db_setopt(p->cmd, p->extra_ops[i].name, v) < 0) return -1;
 				break;
 			}
 			case DB_FLOAT: {
 				float v;
 				#ifdef  __USE_ISOC99
 				v = strtof(p->extra_ops[i].value, &end);
 				#else
 				v = strtod(p->extra_ops[i].value, &end);
 				#endif
 				if (db_setopt(p->cmd, p->extra_ops[i].name, v) < 0) return -1;
 				break;
 			}
 			case DB_DOUBLE: {
 				double v;
 				v = strtod(p->extra_ops[i].value, &end);
 				if (db_setopt(p->cmd, p->extra_ops[i].name, v) < 0) return -1;
 				break;
 			}
 			case DB_CSTR:
 				if (db_setopt(p->cmd, p->extra_ops[i].name, p->extra_ops[i].value) < 0) return -1;
 				break;
 		default:
 				BUG("Unknown extra_op type: %d\n", p->extra_ops[i].type);
 				return -1;
 		}
 	}
 	return 0;
 }
 
5ad39751
 static int child_init(int rank) {
 	struct dbops_action *p, *p2;
8fb14fe8
 	if (rank!=PROC_INIT && rank != PROC_MAIN && rank != PROC_TCP_MAIN) {
5ad39751
 		for (p=dbops_actions; p; p=p->next) {
 			for (p2=dbops_actions; p!=p2; p2=p2->next) {  /* check if database is already opened */
 				if (strcmp(p->db_url, p2->db_url) == 0) {
9b738f01
 					p->ctx = p2->ctx;
5ad39751
 					break;
 				}
 			}
9b738f01
 
 			/* FIXME: Initialize database layer here */
 
 			if (init_db(p) < 0) {
 				ERR(MODULE_NAME": CHILD INIT #err\n");
 
5ad39751
 				return -1;
 			}
 		}
 	}
 	return 0;
 }
 
 static int dbops_close_query_fixup(void** param, int param_no) {
9b738f01
 	struct dbops_handle *a;
 	a = find_handle_by_name((char*) *param, -1);
 	if (!a) {
 		ERR(MODULE_NAME": handle '%s' is not declared\n", (char*) *param);
5ad39751
 		return E_CFG;
 	}
ffc5cdf0
 	pkg_free (*param);
9b738f01
 	*param = (void*) a;
5ad39751
 	return 0;
 }
 
 static int dbops_query_fixup(void** param, int param_no) {
 	int res = 0;
 	if (param_no == 1) {
 		res = dbops_fixup_func(param, 1);
 		if (res < 0) return res;
 		if (((struct dbops_action*)*param)->operation == OPEN_QUERY_OPS) {
 			if (fixup_get_param_count(param, param_no) != 2) {
9b738f01
 				ERR(MODULE_NAME": query_fixup: SELECT query requires 2 parameters\n");
5ad39751
 				return E_CFG;
 			}
 		}
 		else {
 			if (fixup_get_param_count(param, param_no) != 1) {
9b738f01
 				ERR(MODULE_NAME": query_fixup: non SELECT query requires only 1 parameter\n");
5ad39751
 				return E_CFG;
 			}
 		}
 	}
 	else if (param_no == 2) {
 		return dbops_close_query_fixup(param, param_no);
 	}
 	return res;
 }
 
9b738f01
 static int dbops_query_func(struct sip_msg* m, char* dbops_action, char* handle) {
5ad39751
 	if ( ((struct dbops_action*) dbops_action)->operation == OPEN_QUERY_OPS ) {
 		int res;
9b738f01
 		struct dbops_handle *a = (void*) handle;
 		dbops_close_query_func(m, handle, 0);
5ad39751
 		res = dbops_func(m, (void*) dbops_action);
 		if (res < 0) return res;
9b738f01
 		a->action = (struct dbops_action*) dbops_action;
 		a->cur_row_no = -1;
 		a->result = ((struct dbops_action*) dbops_action)->result;
 		res = do_seek(((struct dbops_action*) dbops_action)->result, &a->cur_row_no, 0);
 		if (res < 0) return res;
5ad39751
 		return 1;
 	}
 	else
 		return dbops_func(m, (void*) dbops_action);
 }
 
9b738f01
 static int dbops_seek_fixup(void** param, int param_no) {
 	if (param_no == 1) {
 		return dbops_close_query_fixup(param, param_no);
 	}
 	else if (param_no == 2) {
 		return fixup_int_12(param, param_no);
 	}
 	return 0;
 }
 
 static int dbops_seek_func(struct sip_msg* m, char* handle, char* row_no) {
 	int res, n;
 	struct dbops_handle *a = (void *) handle;
 	res = check_query_opened(a, "seek");
 	if (res < 0) return res;
 
 	if (get_int_fparam(&n, m, (fparam_t*) row_no) < 0) {
 		return -1;
 	}
 	res = do_seek(a->result, &a->cur_row_no, n);
 	if (res < 0) return res;
 	return 1;
 }
 
 static int dbops_first_func(struct sip_msg* m, char* handle, char* row_no) {
 	int res;
 	struct dbops_handle *a = (void *) handle;
 	res = check_query_opened(a, "first");
 	if (res < 0) return res;
 	
 	a->cur_row_no = -1; /* force seek */
 	res = do_seek(a->result, &a->cur_row_no, 0);
 	if (res < 0) return res;
 	return 1;
 }
 
 static int dbops_next_func(struct sip_msg* m, char* handle, char* row_no) {
 	int res;
 	struct dbops_handle *a = (void *) handle;
 	res = check_query_opened(a, "next");
 	if (res < 0) return res;
 	
 	res = do_seek(a->result, &a->cur_row_no, a->cur_row_no+1);
 	if (res < 0) return res;
 	return 1;
 }
 
5ad39751
 static int dbops_foreach_fixup(void** param, int param_no) {
 	if (param_no == 1) {
9b738f01
 		return dbops_close_query_fixup(param, param_no);
5ad39751
 	}
 	else if (param_no == 2) {
 		int n;
 		n = route_get(&main_rt, (char*) *param);
 		if (n == -1) {
9b738f01
 			ERR(MODULE_NAME": db_foreach: bad route\n");
5ad39751
 			return E_CFG;
 		}
 		pkg_free(*param);
74cbfbfe
 		*param=(void*) (unsigned long) n;
5ad39751
 	}
 	return 0;
 }
 
 
9b738f01
 static int dbops_foreach_func(struct sip_msg* m, char* handle, char* route_no) {
 	int res;
 	db_rec_t* rec;
 	struct dbops_handle *a = (void *) handle;
4955c477
 	struct run_act_ctx ra_ctx;
 	
74cbfbfe
 	if ((long)route_no >= main_rt.idx) {
 		BUG("invalid routing table number #%ld of %d\n", (long) route_no, main_rt.idx);
5ad39751
 		return -1;
 	}
74cbfbfe
 	if (!main_rt.rlist[(long) route_no]) {
9b738f01
 		WARN(MODULE_NAME": route not declared (hash:%ld)\n", (long) route_no);
5ad39751
 		return -1;
 	}
9b738f01
 	res = check_query_opened(a, "for_each");
 	if (res < 0) return res;
 
5ad39751
 	res = -1;
9b738f01
 	a->cur_row_no = 0;
 	for(rec = db_first(a->result);
 			rec != NULL;
 			rec = db_next(a->result),
 			a->cur_row_no++) {
5ad39751
 		/* exec the routing script */
4955c477
 		init_run_actions_ctx(&ra_ctx);
 		res = run_actions(&ra_ctx, main_rt.rlist[(long) route_no], m);
d20e5448
 		if (res <= 0) break;
5ad39751
 	}
9b738f01
 	if (!rec) a->cur_row_no = -1;
5ad39751
 	return res;
 }
 
 static int declare_query(modparam_t type, char* param) {
 	void* p = param;
 	return dbops_fixup_func(&p, 0);	/* add at the end of the action list */
 }
 
9b738f01
 static int declare_handle(modparam_t type, char* param) {
 	struct dbops_handle *a;
 	if (strlen(param) == 0) {
 		ERR(MODULE_NAME": declare_handle: handle name is empty\n");
 		return E_CFG;
 	}
 	a = find_handle_by_name(param, -1);
 	if (a) {
 		ERR(MODULE_NAME": declare_handle: handle '%s' already exists\n", param);
 		return E_CFG;
 	}
 	
 	a = pkg_malloc(sizeof(*a));
 	if (!a) {
 		ERR(MODULE_NAME": Out od memory\n");
 		return E_OUT_OF_MEM;
 	}
 	memset(a, 0, sizeof(*a));
 	a->handle_name = param;
 	a->next = dbops_handles;
 	dbops_handles = a;
 	return 0;
 }
 
 static int dbops_proper_func(struct sip_msg* m, char* dummy1, char* dummy2) {
8216129f
 	dbops_pre_script_cb(m, 0, NULL);
 	dbops_post_script_cb(m, 0, NULL);
9b738f01
 	return 1;
 }
 
5ad39751
 /*
  * Exported functions
  */
 static cmd_export_t cmds[] = {
9b738f01
 	{MODULE_NAME2"_query", dbops_query_func, 1, dbops_query_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE | ONSEND_ROUTE},
 	{MODULE_NAME2"_query", dbops_query_func, 2, dbops_query_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE | ONSEND_ROUTE},
 	{MODULE_NAME2"_first", dbops_first_func, 1, dbops_close_query_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE | ONSEND_ROUTE},
 	{MODULE_NAME2"_next", dbops_next_func, 1, dbops_close_query_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE | ONSEND_ROUTE},
 	{MODULE_NAME2"_seek", dbops_seek_func, 2, dbops_seek_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE | ONSEND_ROUTE},
 	{MODULE_NAME2"_close", dbops_close_query_func, 1, dbops_close_query_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE | ONSEND_ROUTE},
 	{MODULE_NAME2"_foreach", dbops_foreach_func, 2, dbops_foreach_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE | ONSEND_ROUTE},
 	{MODULE_NAME2"_proper", dbops_proper_func, 0, 0, FAILURE_ROUTE},
5ad39751
 	{0, 0, 0, 0, 0}
 };
 
 
 /*
  * Exported parameters
  */
 static param_export_t params[] = {
 	{"db_url",    PARAM_STRING, &db_url},
 	{"declare_query", PARAM_STRING|PARAM_USE_FUNC, (void*) declare_query},
9b738f01
 	{"declare_handle", PARAM_STRING|PARAM_USE_FUNC, (void*) declare_handle},
5ad39751
 	{"xlbuf_size", PARAM_INT, &xlbuf_size},
 	{0, 0, 0}
 };
 
 
 struct module_exports exports = {
9b738f01
 	MODULE_NAME,
5ad39751
 	cmds,        /* Exported commands */
 	0,	     /* RPC */
 	params,      /* Exported parameters */
 	mod_init,           /* module initialization function */
 	0,           /* response function*/
 	0,           /* destroy function */
 	0,           /* oncancel function */
 	child_init   /* per-child init function */
 };