Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2001-2003 FhG Fokus |
3 | | * Copyright (C) 2007-2008 1&1 Internet AG |
4 | | * |
5 | | * This file is part of opensips, a free SIP server. |
6 | | * |
7 | | * opensips 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 | | * opensips is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | * GNU General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU General Public License |
18 | | * along with this program; if not, write to the Free Software |
19 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
20 | | */ |
21 | | /* |
22 | | * History: |
23 | | * -------- |
24 | | * 2004-06-06 bind_dbmod takes dbf as parameter (andrei) |
25 | | * 2006-10-10 Added support for retrieving the last inserted ID (Carsten Bock, BASIS AudioNet GmbH) |
26 | | */ |
27 | | |
28 | | /** |
29 | | * \file db/db.c |
30 | | * \brief Generic Database Interface |
31 | | * |
32 | | * This is a generic database interface for modules that need to utilize a |
33 | | * database. The interface should be used by all modules that access database. |
34 | | * The interface will be independent of the underlying database server. |
35 | | * Notes: |
36 | | * If possible, use the predefined macros if you need to access any structure |
37 | | * attributes. |
38 | | * For additional description, see the comments in the sources of mysql module. |
39 | | * |
40 | | * If you want to see more complicated examples of how the API could be used, |
41 | | * take a look at the sources of the usrloc or auth modules. |
42 | | */ |
43 | | |
44 | | #include "../dprint.h" |
45 | | #include "../sr_module.h" |
46 | | #include "../mem/mem.h" |
47 | | #include "../mem/meminfo.h" |
48 | | #include "../ut.h" |
49 | | #include "db_cap.h" |
50 | | #include "db_id.h" |
51 | | #include "db_pool.h" |
52 | | #include "db.h" |
53 | | |
54 | | #include "db_insertq.h" |
55 | | |
56 | | char *db_version_table = VERSION_TABLE; |
57 | | char *db_default_url = NULL; |
58 | | int db_max_async_connections = 10; |
59 | | |
60 | | /** maximal length of a SQL URL */ |
61 | | static unsigned int MAX_URL_LENGTH = 255; |
62 | | #define COLUMN_OVERHEAD 256 |
63 | | |
64 | | stat_var *sql_total_queries; |
65 | | stat_var *sql_slow_queries; |
66 | | |
67 | | int init_db_support(void) |
68 | 0 | { |
69 | | /* init query list now in shm |
70 | | * so all processes that will be forked from now on |
71 | | * will have access to it |
72 | | * |
73 | | * if it fails, give it a try and carry on */ |
74 | 0 | if (init_ql_support() != 0) { |
75 | 0 | LM_ERR("failed to initialise buffering query list\n"); |
76 | 0 | query_buffer_size = 0; |
77 | 0 | *query_list = NULL; |
78 | 0 | } |
79 | |
|
80 | 0 | if (register_stat("sql", "sql_total_queries", &sql_total_queries, 0) || |
81 | 0 | register_stat("sql", "sql_slow_queries", &sql_slow_queries, 0)) { |
82 | 0 | LM_ERR("failed to register SQL stats\n"); |
83 | 0 | return -1; |
84 | 0 | } |
85 | | |
86 | 0 | return 0; |
87 | 0 | } |
88 | | |
89 | | int estimate_available_rows(int payload_size, int column_count) |
90 | 0 | { |
91 | 0 | struct mem_info info; |
92 | 0 | memset(&info, 0, sizeof (struct mem_info)); |
93 | | |
94 | |
|
95 | | #ifdef pkg_info |
96 | | |
97 | | pkg_info(&info); |
98 | | return (int) (info.free / (payload_size + column_count * COLUMN_OVERHEAD)); |
99 | | #else |
100 | |
|
101 | 0 | return 0; |
102 | 0 | #endif |
103 | | |
104 | |
|
105 | 0 | } |
106 | | |
107 | | int db_check_api(db_func_t* dbf, char *mname) |
108 | 0 | { |
109 | 0 | if(dbf==NULL) |
110 | 0 | return -1; |
111 | | |
112 | | /* All modules must export db_use_table */ |
113 | 0 | if (dbf->use_table == 0) { |
114 | 0 | LM_ERR("module %s does not export db_use_table function\n", mname); |
115 | 0 | goto error; |
116 | 0 | } |
117 | | |
118 | | /* All modules must export db_init */ |
119 | 0 | if (dbf->init == 0) { |
120 | 0 | LM_ERR("module %s does not export db_init function\n", mname); |
121 | 0 | goto error; |
122 | 0 | } |
123 | | |
124 | | /* All modules must export db_close */ |
125 | 0 | if (dbf->close == 0) { |
126 | 0 | LM_ERR("module %s does not export db_close function\n", mname); |
127 | 0 | goto error; |
128 | 0 | } |
129 | | |
130 | 0 | if (dbf->query) { |
131 | 0 | dbf->cap |= DB_CAP_QUERY; |
132 | 0 | } |
133 | |
|
134 | 0 | if (dbf->fetch_result) { |
135 | 0 | dbf->cap |= DB_CAP_FETCH; |
136 | 0 | } |
137 | |
|
138 | 0 | if (dbf->raw_query) { |
139 | 0 | dbf->cap |= DB_CAP_RAW_QUERY; |
140 | 0 | } |
141 | | |
142 | | /* Free result must be exported if DB_CAP_QUERY or |
143 | | * DB_CAP_RAW_QUERY is set */ |
144 | 0 | if ((dbf->cap & (DB_CAP_QUERY|DB_CAP_RAW_QUERY)) && (dbf->free_result==0)) { |
145 | 0 | LM_ERR("module %s supports queries but does not export free_result\n", |
146 | 0 | mname); |
147 | 0 | goto error; |
148 | 0 | } |
149 | | |
150 | 0 | if (dbf->insert) { |
151 | 0 | dbf->cap |= DB_CAP_INSERT; |
152 | 0 | } |
153 | |
|
154 | 0 | if (dbf->delete) { |
155 | 0 | dbf->cap |= DB_CAP_DELETE; |
156 | 0 | } |
157 | |
|
158 | 0 | if (dbf->update) { |
159 | 0 | dbf->cap |= DB_CAP_UPDATE; |
160 | 0 | } |
161 | |
|
162 | 0 | if (dbf->replace) { |
163 | 0 | dbf->cap |= DB_CAP_REPLACE; |
164 | 0 | } |
165 | |
|
166 | 0 | if (dbf->last_inserted_id) { |
167 | 0 | dbf->cap |= DB_CAP_LAST_INSERTED_ID; |
168 | 0 | } |
169 | |
|
170 | 0 | if (dbf->insert_update) { |
171 | 0 | dbf->cap |= DB_CAP_INSERT_UPDATE; |
172 | 0 | } |
173 | |
|
174 | 0 | if (dbf->async_raw_query || dbf->async_resume || dbf->async_free_result) { |
175 | 0 | if (!dbf->async_raw_query || !dbf->async_resume || !dbf->async_free_result) { |
176 | 0 | LM_BUG("NULL async raw_query | resume | free_result in %s", mname); |
177 | 0 | return -1; |
178 | 0 | } |
179 | | |
180 | 0 | dbf->cap |= DB_CAP_ASYNC_RAW_QUERY; |
181 | 0 | } |
182 | | |
183 | 0 | return 0; |
184 | 0 | error: |
185 | 0 | return -1; |
186 | 0 | } |
187 | | |
188 | | /* fills mydbf with the corresponding db module callbacks |
189 | | * returns 0 on success, -1 on error |
190 | | * on error mydbf will contain only 0s */ |
191 | | int db_bind_mod(const str* mod, db_func_t* mydbf) |
192 | 0 | { |
193 | 0 | char *name, *tmp, *p; |
194 | 0 | int len; |
195 | 0 | db_func_t dbf; |
196 | 0 | db_bind_api_f dbind; |
197 | |
|
198 | 0 | if (!mod || !mod->s) { |
199 | 0 | LM_CRIT("null database module name\n"); |
200 | 0 | return -1; |
201 | 0 | } |
202 | 0 | if (mydbf==0) { |
203 | 0 | LM_CRIT("null dbf parameter\n"); |
204 | 0 | return -1; |
205 | 0 | } |
206 | 0 | if (mod->len > MAX_URL_LENGTH) |
207 | 0 | { |
208 | 0 | LM_ERR("SQL URL too long\n"); |
209 | 0 | return 0; |
210 | 0 | } |
211 | | // add the prefix |
212 | 0 | name = pkg_malloc(mod->len + 4); |
213 | 0 | if (!name) { |
214 | 0 | LM_ERR("no private memory left\n"); |
215 | 0 | return -1; |
216 | 0 | } |
217 | 0 | memcpy(name, "db_", 3); |
218 | 0 | memcpy(name+3, mod->s, mod->len); |
219 | 0 | name[mod->len+3] = 0; |
220 | | |
221 | | /* for safety we initialize mydbf with 0 (this will cause |
222 | | * a segfault immediately if someone tries to call a function |
223 | | * from it without checking the return code from bind_dbmod */ |
224 | 0 | memset((void*)mydbf, 0, sizeof(db_func_t)); |
225 | |
|
226 | 0 | p = strchr(name, ':'); |
227 | 0 | if (p) { |
228 | 0 | len = p - name; |
229 | 0 | tmp = (char*)pkg_malloc(len + 4); |
230 | 0 | if (!tmp) { |
231 | 0 | LM_ERR("no private memory left\n"); |
232 | 0 | pkg_free(name); |
233 | 0 | return -1; |
234 | 0 | } |
235 | 0 | memcpy(tmp, name, len); |
236 | 0 | tmp[len] = '\0'; |
237 | 0 | pkg_free(name); |
238 | 0 | } else { |
239 | 0 | tmp = name; |
240 | 0 | } |
241 | | |
242 | 0 | dbind = (db_bind_api_f)find_mod_export(tmp, "db_bind_api", 0); |
243 | 0 | if(dbind != NULL) |
244 | 0 | { |
245 | 0 | LM_DBG("using db bind api for %s\n", tmp); |
246 | 0 | if(dbind(mod, &dbf)<0) |
247 | 0 | { |
248 | 0 | LM_ERR("db_bind_api returned error for module %s\n", tmp); |
249 | 0 | goto error; |
250 | 0 | } |
251 | 0 | } else { |
252 | 0 | memset(&dbf, 0, sizeof(db_func_t)); |
253 | 0 | LM_DBG("using export interface to bind %s\n", tmp); |
254 | 0 | dbf.use_table = (db_use_table_f)find_mod_export(tmp, |
255 | 0 | "db_use_table", 0); |
256 | 0 | dbf.init = (db_init_f)find_mod_export(tmp, "db_init", 0); |
257 | 0 | dbf.close = (db_close_f)find_mod_export(tmp, "db_close", 0); |
258 | 0 | dbf.query = (db_query_f)find_mod_export(tmp, "db_query", 0); |
259 | 0 | dbf.fetch_result = (db_fetch_result_f)find_mod_export(tmp, |
260 | 0 | "db_fetch_result", 0); |
261 | 0 | dbf.raw_query = (db_raw_query_f)find_mod_export(tmp, |
262 | 0 | "db_raw_query", 0); |
263 | 0 | dbf.free_result = (db_free_result_f)find_mod_export(tmp, |
264 | 0 | "db_free_result", 0); |
265 | 0 | dbf.insert = (db_insert_f)find_mod_export(tmp, "db_insert", 0); |
266 | 0 | dbf.delete = (db_delete_f)find_mod_export(tmp, "db_delete", 0); |
267 | 0 | dbf.update = (db_update_f)find_mod_export(tmp, "db_update", 0); |
268 | 0 | dbf.replace = (db_replace_f)find_mod_export(tmp, "db_replace", 0); |
269 | 0 | dbf.last_inserted_id= (db_last_inserted_id_f)find_mod_export(tmp, |
270 | 0 | "db_last_inserted_id", 0); |
271 | 0 | dbf.insert_update = (db_insert_update_f)find_mod_export(tmp, |
272 | 0 | "db_insert_update", 0); |
273 | 0 | } |
274 | | /* check if the module pre-populated the capabilities, or we need to |
275 | | * compute them ourselves - we check for the INSERT capability, because |
276 | | * it's the only one that should be exported by all modules */ |
277 | 0 | if(!DB_CAPABILITY(dbf, DB_CAP_INSERT) && db_check_api(&dbf, tmp)!=0) |
278 | 0 | goto error; |
279 | | |
280 | 0 | *mydbf=dbf; /* copy */ |
281 | 0 | pkg_free(tmp); |
282 | 0 | return 0; |
283 | | |
284 | 0 | error: |
285 | 0 | pkg_free(tmp); |
286 | 0 | return -1; |
287 | 0 | } |
288 | | |
289 | | |
290 | | /* |
291 | | * Initialize database module |
292 | | * No function should be called before this |
293 | | */ |
294 | | db_con_t* db_do_init(const str* url, void* (*new_connection)(const struct db_id *)) |
295 | 0 | { |
296 | 0 | struct db_id *id = NULL; |
297 | 0 | struct pool_con *con = NULL; |
298 | 0 | db_con_t *res = NULL; |
299 | 0 | int con_size = 0; |
300 | |
|
301 | 0 | if (!url || !url->s || !new_connection) { |
302 | 0 | LM_ERR("invalid parameter value\n"); |
303 | 0 | return 0; |
304 | 0 | } |
305 | | |
306 | 0 | con_size = sizeof(db_con_t) + sizeof(void *) + url->len; |
307 | 0 | if (url->len > MAX_URL_LENGTH) |
308 | 0 | { |
309 | 0 | LM_ERR("SQL URL too long\n"); |
310 | 0 | return 0; |
311 | 0 | } |
312 | | |
313 | | /* this is the root memory for this database connection. */ |
314 | 0 | res = (db_con_t*)pkg_malloc(con_size); |
315 | 0 | if (!res) { |
316 | 0 | LM_ERR("no private memory left\n"); |
317 | 0 | return 0; |
318 | 0 | } |
319 | | |
320 | 0 | memset(res, 0, con_size); |
321 | | |
322 | | /* fill in the URL info */ |
323 | 0 | res->url.s = (char *)res + sizeof(db_con_t) + sizeof(void *); |
324 | 0 | res->url.len = url->len; |
325 | 0 | memcpy(res->url.s,url->s,url->len); |
326 | |
|
327 | 0 | id = new_db_id(url); |
328 | 0 | if (!id) { |
329 | 0 | LM_ERR("cannot parse URL '%.*s'\n", url->len, url->s); |
330 | 0 | goto err; |
331 | 0 | } |
332 | | |
333 | | /* Find the connection in the pool */ |
334 | 0 | con = pool_get(id); |
335 | 0 | if (!con) { |
336 | 0 | LM_DBG("connection %p not found in pool\n", id); |
337 | | /* Not in the pool yet */ |
338 | 0 | con = (struct pool_con *)new_connection(id); |
339 | 0 | if (!con) { |
340 | 0 | LM_ERR("could not add connection to the pool\n"); |
341 | 0 | goto err; |
342 | 0 | } |
343 | 0 | pool_insert(con); |
344 | 0 | LM_DBG("connection %p inserted in pool as %p\n", id,con); |
345 | 0 | } else { |
346 | 0 | LM_DBG("connection %p found in pool as %p\n", id,con); |
347 | 0 | free_db_id(id); |
348 | 0 | } |
349 | | |
350 | 0 | if (!con->transfers) { |
351 | 0 | con->transfers = pkg_malloc(db_max_async_connections * |
352 | 0 | sizeof *con->transfers); |
353 | 0 | if (!con->transfers) { |
354 | 0 | LM_ERR("no more pkg\n"); |
355 | 0 | goto err; |
356 | 0 | } |
357 | 0 | } |
358 | | |
359 | 0 | res->tail = (unsigned long)con; |
360 | 0 | return res; |
361 | | |
362 | 0 | err: |
363 | 0 | if (id) free_db_id(id); |
364 | 0 | if (res) pkg_free(res); |
365 | 0 | return 0; |
366 | 0 | } |
367 | | |
368 | | |
369 | | /* |
370 | | * Shut down database module |
371 | | * No function should be called after this |
372 | | */ |
373 | | void db_do_close(db_con_t* _h, void (*free_connection)(struct pool_con*)) |
374 | 0 | { |
375 | 0 | struct pool_con* con; |
376 | |
|
377 | 0 | if (!_h) { |
378 | 0 | LM_ERR("invalid parameter value\n"); |
379 | 0 | return; |
380 | 0 | } |
381 | | |
382 | 0 | con = (struct pool_con*)_h->tail; |
383 | 0 | if (pool_remove(con) == 1) { |
384 | 0 | free_connection(con); |
385 | 0 | } |
386 | |
|
387 | 0 | pkg_free(_h); |
388 | 0 | } |
389 | | |
390 | | |
391 | | |
392 | | /* |
393 | | * Get version of a table |
394 | | * If there is no row for the given table, return version 0 |
395 | | */ |
396 | | int db_table_version(const db_func_t* dbf, db_con_t* connection, const str* table) |
397 | 0 | { |
398 | 0 | db_key_t key[1], col[1]; |
399 | 0 | db_val_t val[1]; |
400 | 0 | db_res_t* res = NULL; |
401 | 0 | db_val_t* ver = 0; |
402 | 0 | str version; |
403 | 0 | int ret; |
404 | | |
405 | |
|
406 | 0 | if (!dbf||!connection || !table || !table->s) { |
407 | 0 | LM_CRIT("invalid parameter value\n"); |
408 | 0 | return -1; |
409 | 0 | } |
410 | | |
411 | 0 | version.s = db_version_table; |
412 | 0 | version.len = strlen(version.s); |
413 | |
|
414 | 0 | if (dbf->use_table(connection, &version) < 0) { |
415 | 0 | LM_ERR("error while changing table\n"); |
416 | 0 | return -1; |
417 | 0 | } |
418 | 0 | str tmp1 = str_init(TABLENAME_COLUMN); |
419 | 0 | key[0] = &tmp1; |
420 | |
|
421 | 0 | VAL_TYPE(val) = DB_STR; |
422 | 0 | VAL_NULL(val) = 0; |
423 | 0 | VAL_STR(val) = *table; |
424 | |
|
425 | 0 | str tmp2 = str_init(VERSION_COLUMN); |
426 | 0 | col[0] = &tmp2; |
427 | |
|
428 | 0 | if (dbf->query(connection, key, 0, val, col, 1, 1, 0, &res) < 0) { |
429 | 0 | LM_ERR("error in db_query\n"); |
430 | 0 | return -1; |
431 | 0 | } |
432 | | |
433 | 0 | if (RES_ROW_N(res) == 0) { |
434 | 0 | LM_DBG("no row for table %.*s found\n", |
435 | 0 | table->len, ZSW(table->s)); |
436 | 0 | return 0; |
437 | 0 | } |
438 | | |
439 | 0 | if (RES_ROW_N(res) != 1) { |
440 | 0 | LM_ERR("invalid number of rows received:" |
441 | 0 | " %d, %.*s\n", RES_ROW_N(res), table->len, ZSW(table->s)); |
442 | 0 | dbf->free_result(connection, res); |
443 | 0 | return -1; |
444 | 0 | } |
445 | | |
446 | 0 | ver = ROW_VALUES(RES_ROWS(res)); |
447 | 0 | if ((VAL_TYPE(ver)!=DB_INT && VAL_TYPE(ver)!=DB_BIGINT) || VAL_NULL(ver)) { |
448 | 0 | LM_ERR("invalid type (%d) or nul (%d) version " |
449 | 0 | "columns for %.*s\n", VAL_TYPE(ver), VAL_NULL(ver), |
450 | 0 | table->len, ZSW(table->s)); |
451 | 0 | dbf->free_result(connection, res); |
452 | 0 | return -1; |
453 | 0 | } |
454 | | |
455 | 0 | if (VAL_TYPE(ver)==DB_INT) |
456 | 0 | ret = VAL_INT(ver); |
457 | 0 | else |
458 | 0 | ret = (int)VAL_BIGINT(ver); |
459 | |
|
460 | 0 | dbf->free_result(connection, res); |
461 | 0 | return ret; |
462 | 0 | } |
463 | | |
464 | | /* |
465 | | * Check the table version |
466 | | * 0 means ok, -1 means an error occurred |
467 | | */ |
468 | | int db_check_table_version(db_func_t* dbf, db_con_t* dbh, const str* table, const unsigned int version) |
469 | 0 | { |
470 | 0 | int ver; |
471 | | |
472 | | /* if DB does not support QUERY, return TRUE */ |
473 | 0 | if (!DB_CAPABILITY(*dbf, DB_CAP_QUERY)) |
474 | 0 | return 0; |
475 | | |
476 | 0 | ver = db_table_version(dbf, dbh, table); |
477 | 0 | if (ver < 0) { |
478 | 0 | LM_ERR("querying version for table %.*s\n", table->len, table->s); |
479 | 0 | return -1; |
480 | 0 | } else if (ver != version) { |
481 | 0 | LM_ERR("invalid version %d for table %.*s found, expected %d\n", |
482 | 0 | ver, table->len, table->s, version); |
483 | 0 | return -1; |
484 | 0 | } |
485 | 0 | return 0; |
486 | 0 | } |
487 | | |
488 | | /* |
489 | | * Store name of table that will be used by |
490 | | * subsequent database functions |
491 | | */ |
492 | | int db_use_table(db_con_t* _h, const str* _t) |
493 | 0 | { |
494 | 0 | if (!_h || !_t || !_t->s) { |
495 | 0 | LM_ERR("invalid parameter value %p, %p\n", _h, _t); |
496 | 0 | return -1; |
497 | 0 | } |
498 | | |
499 | 0 | CON_TABLE(_h) = _t; |
500 | 0 | return 0; |
501 | 0 | } |