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