Browse code

- db_ops module files added. - db_ops enables processing SQL commands directly in ser.cfg script. See README for more information. Full docbook doc come later.

Tomas Mandys authored on 07/06/2006 08:38:31
Showing 3 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+# $Id$
1
+#
2
+# db_ops module makefile
3
+#
4
+#
5
+# WARNING: do not run this directly, it should be run by the master Makefile
6
+
7
+include ../../Makefile.defs
8
+
9
+auto_gen=
10
+NAME=db_ops.so
11
+LIBS=
12
+
13
+
14
+include ../../Makefile.modules
15
+
16
+
0 17
new file mode 100644
... ...
@@ -0,0 +1,219 @@
0
+db_ops module
1
+=============
2
+
3
+Author: tomas.mandys�at iptel dot org
4
+
5
+db operations from route script
6
+
7
+mod_params
8
+-----------
9
+db_url - default database
10
+declare_query - declare query for @db.query (see select syntax) or for reference from db_query(declare_no)
11
+max_queries - number of max. open queries, i.e. max query_handle
12
+
13
+format:
14
+
15
+xltext = text_parsed_by_xl_lib
16
+database = type ":" user:psw "@" host "/" database_name
17
+field = xltext
18
+fields = field [ "," field ... ]
19
+op = "=" | "<" | ">" | "<="�| ">="  ; note: non-equal nit supported by db API
20
+where = fields
21
+ops = op [ "," op ... ]
22
+order = field
23
+value = xltext
24
+values = value [ "," value ...]
25
+
26
+select = "select/" [database "/"] table "/" fields "/" where "/" ops "/" order "/" values
27
+insert = "insert/" [database "/"] table "/" fields "/" values
28
+update = "update/" [database "/"] table "/" fields "/" where "/" ops "/" values
29
+replace = "replace/" [database "/"] table "/" fields "/" values
30
+delete = "delete/" [database "/"] table "/" where "/" ops "/" values
31
+raw_query = "select ...." | "insert ..."   # not delimited by "/"
32
+
33
+Note that even table/field/where/order are ptrcessed using xl_lib parser. Table name may be determined via AVP, probably useless but "magnifique".
34
+
35
+commands
36
+--------
37
+
38
+db_query ( (insert | update | replace | delete) | declare_no)
39
+
40
+db_query (select | declare_no, query_handle)
41
+  query is accesable using @db.fetch select
42
+db_close (query_handle)
43
+  note all close after script processing automatically
44
+
45
+db_foreach(select | declare_no, route)
46
+  call route for each row
47
+
48
+declare_no references to query declared using "declare_query"
49
+
50
+selects
51
+-------
52
+
53
+@db.query look in table defined using declare_query, opens query, fetches value and closes table
54
+
55
+@db.query[declare_no]                  .. get value from first row and first field
56
+@db.query[declare_no].count            .. get number of rows
57
+@db.query[declare_no].field[m]         .. get value from first row and m-th field
58
+@db.query[declare_no].row[n]           .. get value from n-th row and first field, negative values count from the end (-1 == last row)
59
+@db.query[declare_no].row[n].field[m]  .. get value from n-th row and m-th field
60
+
61
+@db.fetch get value from query opened using dbops_open_query. Note all opened queries are closed in POST_SCRIPT callback not to leak memory
62
+
63
+@db.fetch[query_handle]
64
+@db.fetch[query_handle].count
65
+@db.fetch[query_handle].field[m]
66
+@db.fetch[query_handle].row[n]
67
+@db.fetch[query_handle].row[n].field[m]
68
+
69
+m4 processor
70
+------------
71
+
72
+define(`db_declare_query', `gen_id(`db_declare_query_cnt') define(`$1', indir(`db_declare_query_cnt')) modparam("`db_ops'", "`declare_query'", $2)')
73
+
74
+usage:
75
+
76
+db_declare_query(SELECT_1, "select/foo/bar/rab///%$avp");
77
+db_declare_query(SELECT_2, "select/foo/bar/rab,bra/=,>//%$avp,1");
78
+db_declare_query(SELECT_3, "select contact from location where nat=1 and expires>=now()");
79
+
80
+@db.query[SELECT_2]
81
+db_query(SELECT_1, 1);
82
+
83
+
84
+Examples:
85
+--------
86
+1) example
87
+modparam("db_ops", "declare_query", "select/location/received/uid///%$f.uid");  #0
88
+modparam("db_ops", "declare_query", "select/subscriber/email_address,greeting/uid,allow_find///%$uidparam,1");  #1
89
+modparam("db_ops", "declare_query", "select/silo/body/uid//inc_time/%$f.uid");  #2
90
+modparam("db_ops", "declare_query", "delete from location where expires<now()");  # 3  raw query
91
+
92
+@db.query[0]               ..  SELECT received FROM location WHERE uid = "%$f.uid"
93
+@db.query[0].count         ..  SELECT count(*) FROM location WHERE uid = "%$f.uid"
94
+@db.query[1].field[0]      ..  SELECT email_address FROM subscriber WHERE uid = "%$f.uid" AND allow_find=1
95
+@db.query[1].field[1]      ..  SELECT greeting FROM subscriber WHERE uid = "%$f.uid" AND allow_find=1
96
+@db.query[2].count         ..  SELECT count(*) FROM silo WHERE uid = "%$f.uid"
97
+@db.query[2].row[-1]       ..  SELECT body FROM silo WHERE uid = "%$f.uid" ORDER BY inc_time .. last row
98
+
99
+
100
+db_query("delete/silo///");     ..  DELETE FROM silo
101
+db_query("delete/silo/expired/<=/%Ts");     ..  DELETE FROM silo WHERE expired <= now;
102
+db_query("insert/foo/bar,rab,abr,rbs/%$f.id,'hello world %fu',1,2");  .. INSERT INTO foo(bar,rab,abr,rbs) VALUES ("%$f.id","hello world %fu",1,2)
103
+db_query("update/foo/rab,abr/bar//'hello world %f',1,2,%$f.id");       .. UPDATE foo SET rab="hello world %fu",rbs=45 WHERE bar="%$f.id"
104
+
105
+db_query("delete/mysql://pretorian:sandra@net/fbi/identities//");
106
+
107
+db_query("select/silo/body/uid//inc_time/%$f.uid", 4);  .. SELECT body FROM silo WHERE uid = "%$f.uid" ORDER BY inc_time
108
+@db.fetch[4]             ..  get first raw
109
+@db.fetch[4].raw[1]      ..  get second raw
110
+
111
+db_query("select/silo/src_addr,dest_addr,body/uid//inc_time/%$t.uid", 5);  .. SELECT src_addr,dest_addr,body FROM silo WHERE uid = "%$t.uid" ORDER BY inc_time
112
+@db.fetch[5].raw[-1].field[1]	.. get last dest_addr
113
+db_close(4);
114
+db_close(5);
115
+
116
+# parametrization of queries
117
+$uidparam="xx";
118
+@db.query[1]
119
+
120
+$uidparam="yy";
121
+@db.query[1]
122
+
123
+$uidparam="zz";
124
+db_query(1, 5);
125
+$uidparam="qq";
126
+db_query(1, 6);
127
+if (@db.fetch[6] == @db.fetch[5]) ....
128
+
129
+db_close(5);
130
+db_close(6);
131
+
132
+db_query(SELECT_3, 1);
133
+forach(1, PROCESS_ROW_ROUTE);
134
+
135
+
136
+2)Test config
137
+-----------
138
+loadmodule "modules/mysql/mysql.so"
139
+loadmodule "modules/xlog/xlog.so"
140
+loadmodule "../../mods/db_ops/trunk/db_ops.so"
141
+
142
+# -------------------------  request routing logic -------------------
143
+
144
+
145
+modparam("db_ops", "declare_query", "select/location/received/uid///%$f.uid");
146
+modparam("db_ops", "declare_query", "select/subscriber/email_address,greeting/uid,allow_find///%$t.uid,1");
147
+modparam("db_ops", "declare_query", "select/silo/body/uid//inc_time/%$f.uid");
148
+modparam("db_ops", "declare_query", "select/flexroute/flexroute_name,avps_in,avps_out/flexroute_name//priority/%$fxname");
149
+modparam("db_ops", "declare_query", "select * from location");
150
+
151
+# main routing logic
152
+
153
+route{
154
+
155
+$f.uid="QWERTY";
156
+
157
+#db_query("delete/silo///");
158
+#db_query("insert/foo/bar,rab,abr,rbs/%$f.id,'hello world %fu %$f.uid',1,2");
159
+#db_query("update/foo/rab,abr/bar//'hello world %fu',1,%$f.id");
160
+
161
+#db_query("delete/mysql://root@localhost/tekcore/identities///");
162
+
163
+$fxname="enum";
164
+
165
+$f.count=@db.query[3].count;
166
+$f.r0f0=@db.query[3];
167
+$f.r0f1=@db.query[3].field[1];
168
+$f.r0f2=@db.query[3].field[2];
169
+$f.r1f0=@db.query[3].row[1];
170
+$f.r1f1=@db.query[3].row[1].field[1];
171
+$f.r1f2=@db.query[3].row[1].field[2];
172
+$f.rnf0=@db.query[3].row[-1];
173
+$f.rnf1=@db.query[3].row[-1].field[1];
174
+$f.rnf2=@db.query[3].row[-1].field[2];
175
+xlog("L_INFO","LOG_SELECT: count:%$f.count {{%$f.r0f0,'%$f.r0f1','%$f.r0f2'},{%$f.r1f0,'%$f.r1f1','%$f.r1f2'}, {%$f.rnf0,'%$f.rnf1','%$f.rnf2'}}\n");
176
+
177
+db_query("select/flexroute/domain_name,avps_in,avps_out/flexroute_name//priority/tc2ms", 4);
178
+
179
+$f.count=@db.fetch[4].count;
180
+$f.r0f0=@db.fetch[4];
181
+$f.r0f1=@db.fetch[4].field[1];
182
+$f.r0f2=@db.fetch[4].field[2];
183
+$f.r1f0=@db.fetch[4].row[1];
184
+$f.r1f1=@db.fetch[4].row[1].field[1];
185
+$f.r1f2=@db.fetch[4].row[1].field[2];
186
+$f.rnf0=@db.fetch[4].row[-1];
187
+$f.rnf1=@db.fetch[4].row[-1].field[1];
188
+$f.rnf2=@db.fetch[4].row[-1].field[2];
189
+
190
+xlog("L_INFO","LOG_FETCH: count:%$f.count {{%$f.r0f0,'%$f.r0f1','%$f.r0f2'},{%$f.r1f0,'%$f.r1f1','%$f.r1f2'}, {%$f.rnf0,'%$f.rnf1','%$f.rnf2'}}\n");
191
+
192
+
193
+#@db.fetch[4]             ..  get first raw
194
+#@db.fetch[4].raw[1]      ..  get second raw
195
+
196
+#parametrized query
197
+$fxname="tc2smg";
198
+db_query(3, 5);
199
+$f.count=@db.fetch[5].count;
200
+$f.r0f0=@db.fetch[5];
201
+$f.r0f1=@db.fetch[5].field[1];
202
+$f.r0f2=@db.fetch[5].field[2];
203
+$f.r1f0=@db.fetch[5].row[1];
204
+$f.r1f1=@db.fetch[5].row[1].field[1];
205
+$f.r1f2=@db.fetch[5].row[1].field[2];
206
+$f.rnf0=@db.fetch[5].row[-1];
207
+$f.rnf1=@db.fetch[5].row[-1].field[1];
208
+$f.rnf2=@db.fetch[5].row[-1].field[2];
209
+
210
+xlog("L_INFO","LOG_FETCH_REF: count:%$f.count {{%$f.r0f0,'%$f.r0f1','%$f.r0f2'},{%$f.r1f0,'%$f.r1f1','%$f.r1f2'}, {%$f.rnf0,'%$f.rnf1','%$f.rnf2'}}\n");
211
+
212
+#db_query("select/silo/src_addr,dest_addr,body/uid//inc_time/%$t.uid", 5);
213
+# $avm = db.fetch[5].raw[-1].field[1] ==
214
+db_close(4);
215
+db_close(5);
216
+
217
+}
218
+
0 219
new file mode 100644
... ...
@@ -0,0 +1,970 @@
0
+/*
1
+ * $Id$
2
+ *
3
+ * Copyright (C) 2006 iptelorg GmbH
4
+ *
5
+ * This file is part of ser, a free SIP server.
6
+ *
7
+ * ser is free software; you can redistribute it and/or modify
8
+ * it under the terms of the GNU General Public License as published by
9
+ * the Free Software Foundation; either version 2 of the License, or
10
+ * (at your option) any later version
11
+ *
12
+ * For a license to use the ser software under conditions
13
+ * other than those described here, or to purchase support for this
14
+ * software, please contact iptel.org by e-mail at the following addresses:
15
+ *    info@iptel.org
16
+ *
17
+ * ser is distributed in the hope that it will be useful,
18
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
+ * GNU General Public License for more details.
21
+ *
22
+ * You should have received a copy of the GNU General Public License
23
+ * along with this program; if not, write to the Free Software
24
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25
+ */
26
+
27
+#include <string.h>
28
+#include <stdlib.h>
29
+#include <stdio.h>
30
+#include <ctype.h>
31
+#include "../../sr_module.h"
32
+#include "../../mem/mem.h"
33
+#include "../../str.h"
34
+#include "../../error.h"
35
+#include "../../config.h"
36
+#include "../../trim.h"
37
+#include "../../db/db.h"
38
+#include "../../select.h"
39
+#include "../../script_cb.h"
40
+#include "../xlog/xl_lib.h"
41
+#include "../../route.h"
42
+#include "../../action.h"
43
+#include "../../ut.h"
44
+
45
+
46
+MODULE_VERSION
47
+
48
+static char* db_url = DEFAULT_DB_URL;
49
+static int xlbuf_size = 4096;
50
+static int max_queries = 10;   /* max possible opened queries */
51
+
52
+enum dbops_type {OPEN_QUERY_OPS, INSERT_OPS, UPDATE_OPS, REPLACE_OPS, DELETE_OPS};
53
+
54
+#define FLD_DELIM ','
55
+#define PART_DELIM '/'
56
+
57
+#define NO_SCRIPT -1
58
+
59
+static str* xl_nul = NULL;
60
+static xl_print_log_f* xl_print = NULL;
61
+static xl_parse_format_f* xl_parse = NULL;
62
+static xl_get_nulstr_f* xl_getnul = NULL;
63
+
64
+static char *xlbuf = 0;
65
+static char *xlbuf_tail;
66
+
67
+struct xlstr {
68
+	char *s;
69
+	xl_elog_t* xlfmt;
70
+};
71
+
72
+struct dbops_action {
73
+	char *db_url;
74
+	db_func_t dbf;
75
+        db_con_t *dbh;
76
+
77
+	enum dbops_type operation;
78
+	int is_raw_query;
79
+	struct xlstr table;
80
+	int field_count;
81
+	struct xlstr* fields;
82
+	int where_count;
83
+	struct xlstr* wheres;
84
+	int op_count;
85
+	struct xlstr* ops;
86
+	int value_count;
87
+	struct xlstr* values;
88
+	struct xlstr order;
89
+	struct xlstr raw;
90
+
91
+	db_res_t* result;   /* result of SELECT */
92
+
93
+	struct dbops_action* next;
94
+};
95
+
96
+struct dbops_open_query {
97
+	struct dbops_action* action;
98
+	db_res_t* result;
99
+	int cur_row_no;
100
+};
101
+
102
+
103
+/* list of all operations */
104
+static struct dbops_action* dbops_actions = 0;
105
+
106
+/* list of opened queries, close them in post script callback */
107
+struct dbops_open_query* open_queries = 0;
108
+
109
+#define eat_spaces(_p) \
110
+	while( *(_p)==' ' || *(_p)=='\t' ){\
111
+	(_p)++;}
112
+
113
+static void trim_apostr(char **s) {
114
+	int i;
115
+	while ( **s == '\'') {
116
+		(*s)++;
117
+	}
118
+	i = strlen(*s);
119
+	while (i && (*s)[i-1] == '\'') {
120
+		i--;
121
+		(*s)[i] = 0;
122
+	}
123
+}
124
+
125
+static int get_next_part(char** s, char** part, char delim, int read_only) {
126
+	char *c, *c2;
127
+	char flag = 0;
128
+
129
+	c = c2 = *s;
130
+	eat_spaces(c);
131
+
132
+	while (!(((*c2 == delim) && !flag) || *c2==0)) {
133
+		if (*c2=='\'')
134
+			flag = !flag;
135
+		*c2++;
136
+	}
137
+	if ((*c2)==0 && flag) {
138
+		LOG(L_ERR, "ERROR: db_ops: string '%s' is not terminated\n", *s);
139
+		return E_CFG;
140
+	}
141
+	if (*c2) {
142
+		if (!read_only) *c2 = 0;
143
+		*s = c2+1;
144
+	}
145
+	else {
146
+		*s = c2;
147
+	}
148
+	eat_spaces(*s);
149
+	c2--;
150
+	/* rtrim */
151
+	while ( c2 > c && ((*c2 == ' ')||(*c2 == '\t')) ) {
152
+		if (!read_only) *c2 = 0;
153
+		c2--;
154
+	}
155
+	*part = c;
156
+	return 0;
157
+}
158
+
159
+static int split_fields(char *part, int *n, struct xlstr **strs) {
160
+	int i, res;
161
+	char *c, *fld;
162
+
163
+	*n = 0;
164
+	*strs = 0;
165
+	c = part;
166
+	while (*c) {
167
+		res = get_next_part(&c, &fld, FLD_DELIM, 1);
168
+		if (res < 0) return res;
169
+		(*n)++;
170
+	}
171
+	*strs = pkg_malloc( (*n)*sizeof(**strs));
172
+	if (!strs) {
173
+		LOG(L_ERR, "ERROR: db_ops: split_fields: not enough pkg memory\n");
174
+		return E_OUT_OF_MEM;
175
+	}
176
+	memset(*strs, 0, (*n)*sizeof(**strs));
177
+	i = 0;
178
+	c = part;
179
+	while (*c) {
180
+		res = get_next_part(&c, &(*strs)[i].s, FLD_DELIM, 0);
181
+		if (res < 0) return res;
182
+		trim_apostr(&(*strs)[i].s);
183
+		i++;
184
+	}
185
+	return 0;
186
+}
187
+
188
+static int parse_ops(char* act_s, struct dbops_action** action) {
189
+	int res = 0;
190
+	char *c, *s, *part;
191
+
192
+	s = act_s;
193
+	*action = pkg_malloc(sizeof(**action));
194
+	if (!*action) return E_OUT_OF_MEM;
195
+	memset(*action, 0, sizeof(**action));
196
+
197
+	res = get_next_part(&s, &part, PART_DELIM, 0);
198
+	if (res < 0) return res;
199
+
200
+	for (c = part; *c && *c != PART_DELIM; c++) {
201
+		if (*c == ' ') {
202
+			(*action)->is_raw_query = 1;
203
+			*c = '\0';
204
+			break;
205
+		}
206
+	}
207
+	if (strcasecmp(part, "select") == 0)
208
+		(*action)->operation = OPEN_QUERY_OPS;
209
+	else if (strcasecmp(part, "insert") == 0)
210
+		(*action)->operation = INSERT_OPS;
211
+	else if (strcasecmp(part, "update") == 0)
212
+		(*action)->operation = UPDATE_OPS;
213
+	else if (strcasecmp(part, "replace") == 0)
214
+		(*action)->operation = REPLACE_OPS;
215
+	else if (strcasecmp(part, "delete") == 0)
216
+		(*action)->operation = DELETE_OPS;
217
+	else {
218
+		if ((*action)->is_raw_query) *c = ' ';
219
+		LOG(L_ERR, "db_ops: parse_ops: unknown type of query '%s'\n", part);
220
+		return E_CFG;
221
+	}
222
+	if ((*action)->is_raw_query) {
223
+		*c = ' ';
224
+		(*action)->raw.s = part;
225
+		DBG("db_ops: oper:%d database:'%s' rawtable:'%s'\n", (*action)->operation, (*action)->db_url, (*action)->raw.s);
226
+		return 0;
227
+	}
228
+
229
+	eat_spaces(s);
230
+	c = s;
231
+	while ( (*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9') || (*c == '_') ) {
232
+		c++;
233
+	}
234
+	if (*c == ':') { /* database part is optional*/
235
+		for (c=s; *c!=':'; c++) {
236
+			*c = tolower(*c);                       /* _type_://user:host/database_name/ */
237
+		}
238
+		(*action)->db_url = s;
239
+		s = c+1;
240
+		while (*s == '/') s++;
241
+		res = get_next_part(&s, &part, PART_DELIM, 1);  /* type://_user:host_/database_name/ */
242
+		if (res < 0) return res;
243
+
244
+
245
+		res = get_next_part(&s, &part, PART_DELIM, 0);  /* type://user:host/_database_name_/ */
246
+		if (res < 0) return res;
247
+	}
248
+
249
+	res = get_next_part(&s, &part, PART_DELIM, 0);
250
+	if (res < 0) return res;
251
+
252
+	if (!*part) {
253
+		LOG(L_ERR, "ERROR: db_ops: table not specified near '%s' in '%s'\n", s, act_s);
254
+		return E_CFG;
255
+	}
256
+	trim_apostr(&part);
257
+	(*action)->table.s = part;
258
+
259
+	res = get_next_part(&s, &part, PART_DELIM, 0);
260
+	if (res < 0) return res;
261
+	switch ((*action)->operation) {
262
+		case OPEN_QUERY_OPS:
263
+		case UPDATE_OPS:
264
+		case REPLACE_OPS:
265
+		case INSERT_OPS:
266
+			res = split_fields(part, &(*action)->field_count, &(*action)->fields);
267
+			if (res < 0) return res;
268
+			if ((*action)->field_count == 0) {
269
+				LOG(L_ERR, "ERROR: dbops: no field specified near '%s' �n '%s'\n", part, act_s);
270
+				return E_CFG;
271
+			}
272
+			break;
273
+		case DELETE_OPS:
274
+			res = split_fields(part, &(*action)->where_count, &(*action)->wheres);
275
+			if (res < 0) return res;
276
+			res = get_next_part(&s, &part, PART_DELIM, 0);
277
+			if (res < 0) return res;
278
+			res = split_fields(part, &(*action)->op_count, &(*action)->ops);
279
+			if (res < 0) return res;
280
+			break;
281
+		default:;
282
+	}
283
+
284
+	res = get_next_part(&s, &part, PART_DELIM, 0);
285
+	if (res < 0) return res;
286
+	switch ((*action)->operation) {
287
+		case OPEN_QUERY_OPS:
288
+		case UPDATE_OPS:
289
+			res = split_fields(part, &(*action)->where_count, &(*action)->wheres);
290
+			if (res < 0) return res;
291
+			res = get_next_part(&s, &part, PART_DELIM, 0);
292
+			if (res < 0) return res;
293
+			res = split_fields(part, &(*action)->op_count, &(*action)->ops);
294
+			if (res < 0) return res;
295
+			res = get_next_part(&s, &part, PART_DELIM, 0);
296
+			if (res < 0) return res;
297
+			switch ((*action)->operation) {
298
+				case OPEN_QUERY_OPS:
299
+					if (*part) {
300
+						(*action)->order.s = part;
301
+					}
302
+					res = get_next_part(&s, &part, PART_DELIM, 0);
303
+					if (res < 0) return res;
304
+					break;
305
+				default:;
306
+			}
307
+			/* no break; */
308
+		case REPLACE_OPS:
309
+		case INSERT_OPS:
310
+		case DELETE_OPS:
311
+			res = split_fields(part, &(*action)->value_count, &(*action)->values);
312
+			if (res < 0) return res;
313
+			break;
314
+		default:;
315
+	}
316
+	if (*s) {
317
+		LOG(L_ERR, "ERROR: db_ops: parse_ops: too many parameters/parts, remaining '%s' in '%s'\n", s, act_s);
318
+		return E_CFG;
319
+	}
320
+	/* check num of fields */
321
+	if ((((*action)->operation==OPEN_QUERY_OPS)?0:(*action)->field_count)+(*action)->where_count != (*action)->value_count) {
322
+		LOG(L_ERR, "ERROR: db_ops: parse_ops: number of values does not correspond to number of fields (%d+%d!=%d) in '%s'\n", ((*action)->operation==OPEN_QUERY_OPS)?0:(*action)->field_count,  (*action)->where_count, (*action)->value_count, act_s);
323
+		return E_CFG;
324
+	}
325
+
326
+	DBG("db_ops: oper:%d database:'%s' table:'%s' 'field#:'%d' where#:'%d' order:'%s' value#:%d\n", (*action)->operation, (*action)->db_url, (*action)->table.s, (*action)->field_count,  (*action)->where_count, (*action)->order.s, (*action)->value_count);
327
+	return 0;
328
+}
329
+
330
+static int parse_xlstr(struct xlstr* s) {
331
+
332
+	if (!s->s) return 0;
333
+	if (!strchr(s->s, '%')) return 0;
334
+	/* probably xl_log formating */
335
+
336
+	if (!xl_print) {
337
+		xl_print=(xl_print_log_f*)find_export("xprint", NO_SCRIPT, 0);
338
+
339
+		if (!xl_print) {
340
+			LOG(L_CRIT,"ERROR: db_ops: cannot find \"xprint\", is module xlog loaded?\n");
341
+			return E_UNSPEC;
342
+		}
343
+	}
344
+
345
+	if (!xl_parse) {
346
+		xl_parse=(xl_parse_format_f*)find_export("xparse", NO_SCRIPT, 0);
347
+
348
+		if (!xl_parse) {
349
+			LOG(L_CRIT,"ERROR: db_ops: cannot find \"xparse\", is module xlog loaded?\n");
350
+			return E_UNSPEC;
351
+		}
352
+	}
353
+
354
+	if (!xl_nul) {
355
+		xl_getnul=(xl_get_nulstr_f*)find_export("xnulstr", NO_SCRIPT, 0);
356
+		if (xl_getnul)
357
+			xl_nul=xl_getnul();
358
+
359
+		if (!xl_nul){
360
+			LOG(L_CRIT,"ERROR: db_ops: cannot find \"xnulstr\", is module xlog loaded?\n");
361
+			return E_UNSPEC;
362
+		}
363
+	else
364
+		LOG(L_INFO,"INFO: xlog null is \"%.*s\"\n", xl_nul->len, xl_nul->s);
365
+	}
366
+
367
+	if(xl_parse(s->s, &s->xlfmt) < 0) {
368
+		LOG(L_ERR, "ERROR: db_ops: wrong format '%s'\n", s->s);
369
+		return E_UNSPEC;
370
+	}
371
+
372
+	return 0;
373
+}
374
+
375
+static int eval_xlstr(struct sip_msg* msg, struct xlstr* s) {
376
+	static char* null_str = "";
377
+	int len;
378
+	if (s->xlfmt) {
379
+		len = xlbuf_size - (xlbuf_tail-xlbuf);
380
+		if (xl_print(msg, s->xlfmt, xlbuf_tail, &len) < 0) {
381
+			LOG(L_ERR, "ERROR: db_ops: eval_xlstr: Error while formating result\n");
382
+			return E_UNSPEC;
383
+		}
384
+
385
+		/* 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? */
386
+		if ((xl_nul) && (xl_nul->len==len) && strncmp(xl_nul->s, xlbuf_tail, len)==0) {
387
+			s->s = null_str;
388
+		}
389
+		else {
390
+			s->s = xlbuf_tail;
391
+			s->s[len] = '\0';
392
+			xlbuf_tail += len+1;
393
+		}
394
+	}
395
+	else {
396
+		if (!s->s)
397
+			s->s = null_str;
398
+	}
399
+	return 0;
400
+}
401
+
402
+static int dbops_func(struct sip_msg* m, struct dbops_action* action) {
403
+	void* buf;
404
+	db_key_t *wheres, *fields, order;
405
+	db_op_t *ops;
406
+	db_val_t *vals;
407
+	int res, i;
408
+
409
+	if (action->is_raw_query) {
410
+		DBG("db_ops: dbops_func(raw, %d, '%s'\n", action->operation, action->raw.s);
411
+		res = eval_xlstr(m, &action->raw);
412
+		if (res < 0) return res;
413
+		if (action->dbf.raw_query(action->dbh, action->raw.s, action->operation==OPEN_QUERY_OPS?&action->result:0) < 0) {
414
+			LOG(L_ERR, "ERROR: db_ops: database operation (%d) error, raw: '%s'\n", action->operation, action->raw.s);
415
+			return -1;
416
+		}
417
+		return 1;
418
+	}
419
+
420
+	DBG("db_ops: dbops_func(%d, '%s', %d, %d, %d)\n", action->operation, action->table.s, action->field_count, action->where_count, action->value_count);
421
+	res = eval_xlstr(m, &action->table);
422
+	if (res < 0) return res;
423
+	if (action->dbf.use_table(action->dbh, action->table.s) < 0) {
424
+		LOG(L_ERR, "ERROR: db_ops: func: Error while using table '%s'\n", action->table.s);
425
+		return -1;
426
+	}
427
+
428
+	buf = pkg_malloc((action->field_count+action->where_count)*sizeof(db_key_t) + action->value_count*sizeof(db_val_t) + action->where_count*sizeof(db_op_t) );
429
+	if (!buf) {
430
+		LOG(L_ERR, "ERROR: db_ops: func: cannot allocate memory for keys and values\n");
431
+		res = E_OUT_OF_MEM;
432
+		goto cleanup;
433
+	}
434
+	wheres = buf;
435
+	fields = &wheres[action->where_count];
436
+	vals = (void*) &fields[action->field_count];
437
+	ops = (void*) &vals[action->value_count];
438
+
439
+	for (i=0; i<action->field_count; i++) {
440
+		res = eval_xlstr(m, &action->fields[i]);
441
+		if (res < 0) goto cleanup;
442
+		fields[i] = action->fields[i].s;
443
+	}
444
+	for (i=0; i<action->where_count; i++) {
445
+		res = eval_xlstr(m, &action->wheres[i]);
446
+		if (res < 0) goto cleanup;
447
+		wheres[i] = action->wheres[i].s;
448
+
449
+		if (i < action->op_count) {
450
+			res = eval_xlstr(m, &action->ops[i]);
451
+			if (res < 0) goto cleanup;
452
+			ops[i] = action->ops[i].s;
453
+		}
454
+		else {
455
+			ops[i] = OP_EQ;
456
+		}
457
+	}
458
+
459
+	for (i=0; i<action->value_count; i++) {
460
+		res = eval_xlstr(m, &action->values[i]);
461
+		if (res < 0) goto cleanup;
462
+		vals[i].type = DB_STRING;
463
+		vals[i].nul = 0;
464
+		vals[i].val.string_val = action->values[i].s;
465
+	}
466
+
467
+	switch (action->operation) {
468
+		case OPEN_QUERY_OPS:
469
+			if (action->order.s) {
470
+				res = eval_xlstr(m, &action->order);
471
+				if (res < 0) return res;
472
+			}
473
+			order = action->order.s;
474
+			if (action->dbf.query(action->dbh, wheres, ops, vals, fields, action->where_count, action->field_count, order, &action->result) < 0) goto err;
475
+			break;
476
+		case INSERT_OPS:
477
+			if (action->dbf.insert(action->dbh, fields, vals, action->field_count) < 0) goto err;
478
+			break;
479
+		case UPDATE_OPS:
480
+			if (action->dbf.update(action->dbh, wheres, ops, &vals[action->field_count], fields, vals, action->where_count, action->field_count) < 0) goto err;
481
+			break;
482
+		case REPLACE_OPS:
483
+			if (action->dbf.replace(action->dbh, fields, vals, action->field_count) < 0) goto err;
484
+			break;
485
+		case DELETE_OPS:
486
+			if (action->dbf.delete(action->dbh, wheres, ops, vals, action->where_count) < 0) goto err;
487
+			break;
488
+		default:;
489
+	}
490
+	res = 1;
491
+cleanup:
492
+	pkg_free(buf);
493
+	return res;
494
+err:
495
+	LOG(L_ERR, "ERROR: db_ops: database operation (%d) error, table: '%s'\n", action->operation, action->table.s);
496
+	res = -1;
497
+	goto cleanup;
498
+}
499
+
500
+static int sel_get_field(str* res, int row_no, int field_no, db_res_t* result) {
501
+/* return string in static buffer, I'm not sure if local static variable is OK, e.g. when comparing 2 selects */
502
+	int len;
503
+	len = xlbuf_size-(xlbuf_tail-xlbuf);
504
+	res->s = xlbuf_tail;
505
+	res->len = 0;
506
+	if (field_no == -2) {  /* cur_row_no */
507
+		res->len = snprintf(res->s, len, "%d", row_no);
508
+	}
509
+	else if (field_no < 0) {  /* count(*) */
510
+		res->len = snprintf(res->s, len, "%d", RES_ROW_N(result));
511
+	}
512
+	else {
513
+		if ( (row_no >= 0 && row_no >= RES_ROW_N(result)) || (row_no < 0 && -row_no > RES_ROW_N(result)) ) {
514
+			LOG(L_ERR, "ERROR: db_ops: row (%d) does not exist, num rows: %d\n", row_no, RES_ROW_N(result));
515
+			return -1;
516
+		}
517
+		if (field_no >= RES_COL_N(result)) {
518
+			LOG(L_ERR, "ERROR: db_ops: field (%d) does not exist, num fields: %d\n", row_no, RES_COL_N(result));
519
+			return -1;
520
+		}
521
+		if (row_no < 0) {
522
+			row_no += RES_ROW_N(result);
523
+		}
524
+
525
+		if (!VAL_NULL(result->rows[row_no].values+field_no)) {
526
+			switch (VAL_TYPE(result->rows[row_no].values+field_no)) {
527
+				case DB_INT:
528
+					res->len = snprintf(res->s, len, "%d", VAL_INT(result->rows[row_no].values+field_no));
529
+					break;
530
+				case DB_FLOAT:
531
+					res->len = snprintf(res->s, len, "%f", VAL_FLOAT(result->rows[row_no].values+field_no));
532
+					break;
533
+				case DB_DOUBLE:
534
+					res->len = snprintf(res->s, len, "%f", VAL_DOUBLE(result->rows[row_no].values+field_no));
535
+					break;
536
+				case DB_STR:
537
+					res->len = snprintf(res->s, len, "%.*s", VAL_STR(result->rows[row_no].values+field_no).len, VAL_STR(result->rows[row_no].values+field_no).s);
538
+					break;
539
+				case DB_BLOB:
540
+					res->len = snprintf(res->s, len, "%.*s", VAL_BLOB(result->rows[row_no].values+field_no).len, VAL_STR(result->rows[row_no].values+field_no).s);
541
+					break;
542
+				case DB_STRING:
543
+					res->len = snprintf(res->s, len, "%s", VAL_STRING(result->rows[row_no].values+field_no));
544
+					break;
545
+				case DB_DATETIME:
546
+					res->len = snprintf(res->s, len, "%u", (unsigned int) VAL_TIME(result->rows[row_no].values+field_no));
547
+					break;
548
+				case DB_BITMAP:
549
+					res->len = snprintf(res->s, len, "%u", (unsigned int) VAL_BITMAP(result->rows[row_no].values+field_no));
550
+					break;
551
+				default:
552
+					break;
553
+			}
554
+		}
555
+	}
556
+	xlbuf_tail += res->len;
557
+	return 0;
558
+}
559
+
560
+static int sel_do_select(str* result, int query_no, int row_no, int field_no, struct sip_msg* msg) {
561
+	struct dbops_action *a;
562
+	int i, res;
563
+
564
+	if (query_no < 0) {
565
+		LOG(L_ERR, "ERROR: db_ops: select: query_no is negative (%d)\n", query_no);
566
+		return -1;
567
+	}
568
+	for (a=dbops_actions, i=query_no; i>0 && a && a->operation == OPEN_QUERY_OPS; a = a->next, i--);
569
+	if (!a || a->operation != OPEN_QUERY_OPS) {
570
+		LOG(L_ERR, "ERROR: db_ops: select: query not found. Check declare_query params (%d)\n", query_no);
571
+		return -1;
572
+	}
573
+	res = dbops_func(msg, a);
574
+	if (res < 0) return res;
575
+	res = sel_get_field(result, row_no, field_no, a->result);
576
+	a->dbf.free_result(a->dbh, a->result);
577
+	return res;
578
+}
579
+
580
+static int sel_do_fetch(str* result, int query_no, int row_no, int field_no, struct sip_msg* msg) {
581
+
582
+	if (query_no < 0 || query_no >= max_queries) {
583
+		LOG(L_ERR, "ERROR: db_ops: fetch: query_no (%d) must be from interval <0,%d)\n", query_no, max_queries);
584
+		return -1;
585
+	}
586
+	if (!open_queries[query_no].result) {
587
+		LOG(L_ERR, "ERROR: db_ops: fetch: query (%d) is not opened. Use db_query() first\n", query_no);
588
+		return -1;
589
+	}
590
+	return sel_get_field(result, open_queries[query_no].cur_row_no+row_no, field_no, open_queries[query_no].result);
591
+}
592
+
593
+static int sel_dbops(str* res, select_t* s, struct sip_msg* msg) {  /* dummy */
594
+	return 0;
595
+}
596
+
597
+static int sel_select(str* res, select_t* s, struct sip_msg* msg) {
598
+	return sel_do_select(res, s->params[2].v.i, 0, 0, msg);
599
+}
600
+
601
+static int sel_select_count(str* res, select_t* s, struct sip_msg* msg) {
602
+	return sel_do_select(res, s->params[2].v.i, 0, -1, msg);
603
+}
604
+
605
+static int sel_select_field(str* res, select_t* s, struct sip_msg* msg) {
606
+	return sel_do_select(res, s->params[2].v.i, 0, s->params[4].v.i, msg);
607
+}
608
+
609
+static int sel_select_row(str* res, select_t* s, struct sip_msg* msg) {
610
+	return sel_do_select(res, s->params[2].v.i, s->params[4].v.i, 0, msg);
611
+}
612
+
613
+static int sel_select_row_field(str* res, select_t* s, struct sip_msg* msg) {
614
+	return sel_do_select(res, s->params[2].v.i, s->params[4].v.i, s->params[6].v.i, msg);
615
+}
616
+
617
+static int sel_fetch(str* res, select_t* s, struct sip_msg* msg) {
618
+	return sel_do_fetch(res, s->params[2].v.i, 0, 0, msg);
619
+}
620
+
621
+static int sel_fetch_count(str* res, select_t* s, struct sip_msg* msg) {
622
+	return sel_do_fetch(res, s->params[2].v.i, 0, -1, msg);
623
+}
624
+
625
+static int sel_fetch_cur_row_no(str* res, select_t* s, struct sip_msg* msg) {
626
+	return sel_do_fetch(res, s->params[2].v.i, 0, -2, msg);
627
+}
628
+
629
+static int sel_fetch_field(str* res, select_t* s, struct sip_msg* msg) {
630
+	return sel_do_fetch(res, s->params[2].v.i, 0, s->params[4].v.i, msg);
631
+}
632
+
633
+static int sel_fetch_row(str* res, select_t* s, struct sip_msg* msg) {
634
+	return sel_do_fetch(res, s->params[2].v.i, s->params[4].v.i, 0, msg);
635
+}
636
+
637
+static int sel_fetch_row_field(str* res, select_t* s, struct sip_msg* msg) {
638
+	return sel_do_fetch(res, s->params[2].v.i, s->params[4].v.i, s->params[6].v.i, msg);
639
+}
640
+
641
+
642
+select_row_t sel_declaration[] = {
643
+        { NULL, SEL_PARAM_STR, STR_STATIC_INIT("db"), sel_dbops, SEL_PARAM_EXPECTED},
644
+
645
+	{ sel_dbops, SEL_PARAM_STR, STR_STATIC_INIT("query"), sel_select, CONSUME_NEXT_INT},
646
+	{ sel_select, SEL_PARAM_STR, STR_STATIC_INIT("count"), sel_select_count, 0},
647
+	{ sel_select, SEL_PARAM_STR, STR_STATIC_INIT("field"), sel_select_field, CONSUME_NEXT_INT},
648
+	{ sel_select, SEL_PARAM_STR, STR_STATIC_INIT("row"), sel_select_row, CONSUME_NEXT_INT},
649
+	{ sel_select_row, SEL_PARAM_STR, STR_STATIC_INIT("field"), sel_select_row_field, CONSUME_NEXT_INT},
650
+
651
+	{ sel_dbops, SEL_PARAM_STR, STR_STATIC_INIT("fetch"), sel_fetch, CONSUME_NEXT_INT},
652
+	{ sel_fetch, SEL_PARAM_STR, STR_STATIC_INIT("count"), sel_fetch_count, 0},
653
+	{ sel_fetch, SEL_PARAM_STR, STR_STATIC_INIT("row_no"), sel_fetch_cur_row_no, 0},
654
+	{ sel_fetch, SEL_PARAM_STR, STR_STATIC_INIT("field"), sel_fetch_field, CONSUME_NEXT_INT},
655
+	{ sel_fetch, SEL_PARAM_STR, STR_STATIC_INIT("row"), sel_fetch_row, CONSUME_NEXT_INT},
656
+	{ sel_fetch_row, SEL_PARAM_STR, STR_STATIC_INIT("field"), sel_fetch_row_field, CONSUME_NEXT_INT},
657
+
658
+        { NULL, SEL_PARAM_INT, STR_NULL, NULL, 0}
659
+};
660
+
661
+static int dbops_close_query_func(struct sip_msg* m, char* query_no, char* dummy) {
662
+
663
+	if (open_queries[(int) query_no].result) {
664
+		open_queries[(int) query_no].action->dbf.free_result(open_queries[(int) query_no].action->dbh, open_queries[(int) query_no].result);
665
+		open_queries[(int) query_no].result = 0;
666
+	}
667
+	return 1;
668
+}
669
+
670
+static int dbops_pre_script_cb(struct sip_msg *msg, void *param) {
671
+	xlbuf_tail = xlbuf;
672
+	return 1;
673
+}
674
+
675
+static int dbops_post_script_cb(struct sip_msg *msg, void *param) {
676
+	int i;
677
+	for (i=0; i<max_queries; i++) {
678
+		dbops_close_query_func(msg, (char*) i, 0);
679
+	}
680
+	return 1;
681
+}
682
+
683
+static int init_action(struct dbops_action* action) {
684
+	int cap, res, i;
685
+
686
+	if (!action->db_url)
687
+		action->db_url = db_url;
688
+	if (bind_dbmod(action->db_url, &action->dbf) < 0) { /* Find database module */
689
+		LOG(L_ERR, "ERROR: db_ops: mod_init: Can't bind database module '%s'\n", action->db_url);
690
+		return -1;
691
+	}
692
+	if (action->is_raw_query) {
693
+		cap = DB_CAP_RAW_QUERY;
694
+	}
695
+	else {
696
+		switch (action->operation) {
697
+			case OPEN_QUERY_OPS:
698
+				cap = DB_CAP_QUERY;
699
+				break;
700
+			case INSERT_OPS:
701
+				cap = DB_CAP_INSERT;
702
+				break;
703
+			case UPDATE_OPS:
704
+				cap = DB_CAP_UPDATE;
705
+				break;
706
+			case REPLACE_OPS:
707
+				cap = DB_CAP_REPLACE;
708
+				break;
709
+			case DELETE_OPS:
710
+				cap = DB_CAP_DELETE;
711
+				break;
712
+			default:
713
+				cap = 0;
714
+		}
715
+	}
716
+	if (!DB_CAPABILITY(action->dbf, cap)) {
717
+		LOG(L_ERR, "ERROR: db_ops: mod_init: Database module does not implement"
718
+		" all functions (%d) needed by the module '%s'\n", action->operation, action->db_url);
719
+		return -1;
720
+	}
721
+	res = parse_xlstr(&action->table);
722
+	if (res < 0) return res;
723
+	for (i=0; i<action->field_count; i++) {
724
+		res = parse_xlstr(&action->fields[i]);
725
+		if (res < 0) return res;
726
+	}
727
+
728
+	for (i=0; i<action->where_count; i++) {
729
+		res = parse_xlstr(&action->wheres[i]);
730
+		if (res < 0) return res;
731
+	}
732
+	for (i=0; i<action->value_count; i++) {
733
+		res = parse_xlstr(&action->values[i]);
734
+		if (res < 0) return res;
735
+	}
736
+	res = parse_xlstr(&action->order);
737
+	if (res < 0) return res;
738
+	res = parse_xlstr(&action->raw);
739
+
740
+        return res;
741
+}
742
+
743
+static int dbops_fixup_func(void** param, int init_act) {
744
+	struct dbops_action **p, *a;
745
+	char *c;
746
+	int res, declare_no, i;
747
+
748
+	/* check if is it a declare_no that references to declare_xxxx */
749
+	c = *param;
750
+	eat_spaces(c);
751
+	*param = c;
752
+	while (*c >= '0' && *c <= '9') c++;
753
+	if (c != *param && *c==0) {
754
+		declare_no = atoi((char*)*param);
755
+		for (a=dbops_actions, i=declare_no; i>0 && a; a = a->next, i--);
756
+		if (!a) {
757
+			LOG(L_ERR, "ERROR: db_ops: fixup_func: query (%d) not declared\n", declare_no);
758
+			return -1;
759
+		}
760
+		*param = (void*) a;
761
+		return 0;
762
+	}
763
+
764
+	for (p = &dbops_actions; *p; p=&(*p)->next);	/* add at the end of list */
765
+	res = parse_ops(*param, p);
766
+	if (res < 0) return res;
767
+	/* pkg_free(*param); do not free it!*/
768
+	*param = (void*) *p;
769
+	if (init_act)
770
+		return init_action(*p);   /* fixup is acquired after init_mod() therefore initialize new action */
771
+	else
772
+		return 0;
773
+}
774
+
775
+static int mod_init(void) {
776
+	struct dbops_action* p;
777
+
778
+	open_queries = pkg_malloc(max_queries*sizeof(*open_queries));
779
+	if (!open_queries) {
780
+		LOG(L_ERR, "ERROR: db_ops: could not allocate memory for open_queries\n");
781
+		return E_OUT_OF_MEM;
782
+	}
783
+	memset(open_queries, 0, max_queries*sizeof(*open_queries));
784
+
785
+	xlbuf = pkg_malloc((xlbuf_size+1)*sizeof(char));
786
+	if (!xlbuf) {
787
+		LOG(L_ERR, "ERROR: db_ops: out of memory, cannot create xlbuf\n");
788
+		return E_OUT_OF_MEM;
789
+	}
790
+
791
+	for (p=dbops_actions; p; p=p->next) {
792
+		init_action(p);
793
+	}
794
+
795
+	register_script_cb(dbops_pre_script_cb, REQ_TYPE_CB | RPL_TYPE_CB| PRE_SCRIPT_CB, 0);
796
+	register_script_cb(dbops_post_script_cb, REQ_TYPE_CB | RPL_TYPE_CB| POST_SCRIPT_CB, 0);
797
+	register_select_table(sel_declaration);
798
+
799
+	return 0;
800
+}
801
+
802
+static int child_init(int rank) {
803
+	struct dbops_action *p, *p2;
804
+	if (rank > 0 || rank == PROC_TIMER) {
805
+		for (p=dbops_actions; p; p=p->next) {
806
+			for (p2=dbops_actions; p!=p2; p2=p2->next) {  /* check if database is already opened */
807
+				if (strcmp(p->db_url, p2->db_url) == 0) {
808
+					p->dbh = p2->dbh;
809
+					break;
810
+				}
811
+			}
812
+			if (!p->dbh) {
813
+				p->dbh = p->dbf.init(p->db_url);   /* Get a new database connection */
814
+			}
815
+			if (!p->dbh) {
816
+				LOG(L_ERR, "ERROR: db_ops: child_init(%d): Error while connecting database '%s'\n", rank, p->db_url);
817
+				return -1;
818
+			}
819
+		}
820
+	}
821
+	return 0;
822
+}
823
+
824
+static int dbops_close_query_fixup(void** param, int param_no) {
825
+	int res, n;
826
+	res = fixup_int_12(param, param_no);
827
+	if (res < 0) return res;
828
+	n = (int) *param;
829
+	if (n < 0 || n >= max_queries) {
830
+		LOG(LOG_ERR, "ERROR: db_ops: query handle (%d) must be in interval <0..%d)\n", n, max_queries);
831
+		return E_CFG;
832
+	}
833
+	return 0;
834
+}
835
+
836
+static int dbops_query_fixup(void** param, int param_no) {
837
+	int res = 0;
838
+	if (param_no == 1) {
839
+		res = dbops_fixup_func(param, 1);
840
+		if (res < 0) return res;
841
+		if (((struct dbops_action*)*param)->operation == OPEN_QUERY_OPS) {
842
+			if (fixup_get_param_count(param, param_no) != 2) {
843
+				LOG(L_ERR, "ERROR: db_ops: query_fixup: SELECT query requires 2 parameters\n");
844
+				return E_CFG;
845
+			}
846
+		}
847
+		else {
848
+			if (fixup_get_param_count(param, param_no) != 1) {
849
+				LOG(L_ERR, "ERROR: db_ops: query_fixup: non SELECT query requires only 1 parameter\n");
850
+				return E_CFG;
851
+			}
852
+		}
853
+	}
854
+	else if (param_no == 2) {
855
+		return dbops_close_query_fixup(param, param_no);
856
+	}
857
+	return res;
858
+}
859
+
860
+static int dbops_query_func(struct sip_msg* m, char* dbops_action, char* query_no) {
861
+	if ( ((struct dbops_action*) dbops_action)->operation == OPEN_QUERY_OPS ) {
862
+		int res;
863
+		dbops_close_query_func(m, query_no, 0);
864
+		res = dbops_func(m, (void*) dbops_action);
865
+		if (res < 0) return res;
866
+		open_queries[(int) query_no].action = (struct dbops_action*) dbops_action;
867
+		open_queries[(int) query_no].cur_row_no = 0;
868
+		open_queries[(int) query_no].result = ((struct dbops_action*) dbops_action)->result;
869
+		return 1;
870
+	}
871
+	else
872
+		return dbops_func(m, (void*) dbops_action);
873
+}
874
+
875
+static int dbops_foreach_fixup(void** param, int param_no) {
876
+	int res;
877
+	if (param_no == 1) {
878
+		int n;
879
+		res = fixup_int_12(param, param_no);
880
+		if (res < 0) return res;
881
+		n = (int) *param;
882
+		if (n < 0 || n >= max_queries) {
883
+			LOG(LOG_ERR, "ERROR: db_ops: query handle (%d) must be in interval <0..%d)\n", n, max_queries);
884
+			return E_CFG;
885
+		}
886
+	}
887
+	else if (param_no == 2) {
888
+		int n;
889
+		n = route_get(&main_rt, (char*) *param);
890
+		if (n == -1) {
891
+			LOG(L_ERR, "ERROR: db_foreach: bad route\n");
892
+			return E_CFG;
893
+		}
894
+		pkg_free(*param);
895
+		*param=(void*) (unsigned int) n;
896
+	}
897
+	return 0;
898
+}
899
+
900
+
901
+static int dbops_foreach_func(struct sip_msg* m, char* query_no, char* route_no) {
902
+	int save_row_no, res;
903
+	if ((int)route_no >= main_rt.idx) {
904
+		BUG("invalid routing table number #%d of %d\n", (int) route_no, main_rt.idx);
905
+		return -1;
906
+	}
907
+	if (!main_rt.rlist[(int) route_no]) {
908
+		LOG(L_WARN, "WARN: route not declared (hash:%d)\n", (int) route_no);
909
+		return -1;
910
+	}
911
+	if (!open_queries[(int) query_no].result) {
912
+		LOG(L_ERR, "ERROR: db_ops: fetch: query (%d) is not opened. Use db_query() first\n", (int) query_no);
913
+		return -1;
914
+	}
915
+	res = -1;
916
+	save_row_no = open_queries[(int) query_no].cur_row_no;
917
+	for (open_queries[(int) query_no].cur_row_no=0; open_queries[(int) query_no].cur_row_no < RES_ROW_N(open_queries[(int) query_no].result); open_queries[(int) query_no].cur_row_no++) {
918
+		/* exec the routing script */
919
+		if (run_actions(main_rt.rlist[(int) route_no], m)<0){
920
+			LOG(L_WARN, "WARNING: db_foreach: error while trying route script\n");
921
+		}
922
+		else
923
+			res = 1;    /* at least one record processed correctly */
924
+	}
925
+	open_queries[(int) query_no].cur_row_no = save_row_no;
926
+	return res;
927
+}
928
+
929
+static int declare_query(modparam_t type, char* param) {
930
+	void* p = param;
931
+	return dbops_fixup_func(&p, 0);	/* add at the end of the action list */
932
+}
933
+
934
+/*
935
+ * Exported functions
936
+ */
937
+static cmd_export_t cmds[] = {
938
+	{"db_query", dbops_query_func, 1, dbops_query_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE | ONSEND_ROUTE},
939
+	{"db_query", dbops_query_func, 2, dbops_query_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE | ONSEND_ROUTE},
940
+	{"db_close", dbops_close_query_func, 1, dbops_close_query_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE | ONSEND_ROUTE},
941
+	{"db_foreach", dbops_foreach_func, 2, dbops_foreach_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE | ONSEND_ROUTE},
942
+	{0, 0, 0, 0, 0}
943
+};
944
+
945
+
946
+/*
947
+ * Exported parameters
948
+ */
949
+static param_export_t params[] = {
950
+	{"db_url",    PARAM_STRING, &db_url},
951
+	{"declare_query", PARAM_STRING|PARAM_USE_FUNC, (void*) declare_query},
952
+	{"xlbuf_size", PARAM_INT, &xlbuf_size},
953
+	{"max_queries", PARAM_INT, &max_queries},
954
+	{0, 0, 0}
955
+};
956
+
957
+
958
+struct module_exports exports = {
959
+	"db_ops",
960
+	cmds,        /* Exported commands */
961
+	0,	     /* RPC */
962
+	params,      /* Exported parameters */
963
+	mod_init,           /* module initialization function */
964
+	0,           /* response function*/
965
+	0,           /* destroy function */
966
+	0,           /* oncancel function */
967
+	child_init   /* per-child init function */
968
+};
969
+