/* * Copyright (C) 2001-2003 FhG Fokus * Copyright (C) 2007-2008 1&1 Internet AG * * This file is part of Kamailio, a free SIP server. * * Kamailio is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version * * Kamailio is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** * \file lib/srdb1/db.c * \ingroup db1 * \brief Generic Database Interface * */ /*! \defgroup db DB: The Kamailio generic database interface * This is a generic database interface for modules that need to utilize a * database. The interface should be used by all modules that access database. * The interface will be independent of the underlying database server. * Notes: * If possible, use the predefined macros if you need to access any structure * attributes. * For additional description, see the comments in the sources of mysql module. * * If you want to see more complicated examples of how the API could be used, * take a look at the sources of the usrloc or auth modules. * - \ref usrloc * - \ref auth * * Implemented modules * - \ref ../modules/db_berkeley * - \ref ../modules/db_flatstore * - \ref ../modules/db_text * - \ref ../modules/db_mysql * - \ref ../modules/db_oracle * - \ref ../modules/db_postgres * - \ref ../modules/db_unixodbc */ #include "../../core/dprint.h" #include "../../core/sr_module.h" #include "../../core/mem/mem.h" #include "../../core/ut.h" #include "../../core/globals.h" #include "db_cap.h" #include "db_id.h" #include "db_pool.h" #include "db_query.h" #include "db.h" static unsigned int MAX_URL_LENGTH = 1023; /*!< maximum length of a SQL URL */ int db_check_api(db_func_t* dbf, char *mname) { if(dbf==NULL) return -1; /* All modules must export db_use_table */ if (dbf->use_table == 0) { LM_ERR("module %s does not export db_use_table function. Please check if module is loaded.\n", mname); goto error; } /* All modules must export db_init */ if (dbf->init == 0) { LM_ERR("module %s does not export db_init function. Please check if module is loaded.\n", mname); goto error; } /* All modules must export db_close */ if (dbf->close == 0) { LM_ERR("module %s does not export db_close function. Please check if module is loaded.\n", mname); goto error; } if (dbf->query) { dbf->cap |= DB_CAP_QUERY; } if (dbf->fetch_result) { dbf->cap |= DB_CAP_FETCH; } if (dbf->raw_query) { dbf->cap |= DB_CAP_RAW_QUERY; } /* Free result must be exported if DB_CAP_QUERY or * DB_CAP_RAW_QUERY is set */ if ((dbf->cap & (DB_CAP_QUERY|DB_CAP_RAW_QUERY)) && (dbf->free_result==0)) { LM_ERR("module %s supports queries but does not export free_result\n", mname); goto error; } if (dbf->insert) { dbf->cap |= DB_CAP_INSERT; } if (dbf->delete) { dbf->cap |= DB_CAP_DELETE; } if (dbf->update) { dbf->cap |= DB_CAP_UPDATE; } if (dbf->replace) { dbf->cap |= DB_CAP_REPLACE; } if (dbf->last_inserted_id) { dbf->cap |= DB_CAP_LAST_INSERTED_ID; } if (dbf->insert_update) { dbf->cap |= DB_CAP_INSERT_UPDATE; } if (dbf->insert_delayed) { dbf->cap |= DB_CAP_INSERT_DELAYED; } if (dbf->affected_rows) { dbf->cap |= DB_CAP_AFFECTED_ROWS; } return 0; error: return -1; } /*! \brief fills mydbf with the corresponding db module callbacks * \param mod * \param mydbf * \return returns 0 on success, -1 on error * \note on error mydbf will contain only 0s */ int db_bind_mod(const str* mod, db_func_t* mydbf) { char *name, *tmp, *p; int len; db_func_t dbf; db_bind_api_f dbind; if (!mod || !mod->s) { LM_CRIT("null database module name\n"); return -1; } if (mydbf==0) { LM_CRIT("null dbf parameter\n"); return -1; } /* for safety we initialize mydbf with 0 (this will cause * a segfault immediately if someone tries to call a function * from it without checking the return code from bind_dbmod */ memset((void*)mydbf, 0, sizeof(db_func_t)); if (mod->len > MAX_URL_LENGTH) { LM_ERR("SQL URL too long\n"); return -1; } // add the prefix name = pkg_malloc(mod->len + 4); if (!name) { PKG_MEM_ERROR; return -1; } memcpy(name, "db_", 3); memcpy(name+3, mod->s, mod->len); name[mod->len+3] = 0; p = strchr(name, ':'); if (p) { len = p - name; tmp = (char*)pkg_malloc(len + 4); if (!tmp) { PKG_MEM_ERROR; pkg_free(name); return -1; } memcpy(tmp, name, len); tmp[len] = '\0'; pkg_free(name); } else { tmp = name; } if (!find_module_by_name(tmp)) { LM_ERR("Module %s not found. Missing loadmodule? \n", tmp); goto error; } dbind = (db_bind_api_f)find_mod_export(tmp, "db_bind_api", 0, 0); if(dbind != NULL) { LM_DBG("using db bind api for %s\n", tmp); if(dbind(&dbf)<0) { LM_ERR("db_bind_api returned error for module %s\n", tmp); goto error; } } else { memset(&dbf, 0, sizeof(db_func_t)); LM_DBG("using export interface to bind %s\n", tmp); dbf.use_table = (db_use_table_f)find_mod_export(tmp, "db_use_table", 2, 0); dbf.init = (db_init_f)find_mod_export(tmp, "db_init", 1, 0); dbf.init2 = (db_init2_f)find_mod_export(tmp, "db_init2", 1, 0); dbf.close = (db_close_f)find_mod_export(tmp, "db_close", 2, 0); dbf.query = (db_query_f)find_mod_export(tmp, "db_query", 2, 0); dbf.fetch_result = (db_fetch_result_f)find_mod_export(tmp, "db_fetch_result", 2, 0); dbf.raw_query = (db_raw_query_f)find_mod_export(tmp, "db_raw_query", 2, 0); dbf.free_result = (db_free_result_f)find_mod_export(tmp, "db_free_result", 2, 0); dbf.insert = (db_insert_f)find_mod_export(tmp, "db_insert", 2, 0); dbf.delete = (db_delete_f)find_mod_export(tmp, "db_delete", 2, 0); dbf.update = (db_update_f)find_mod_export(tmp, "db_update", 2, 0); dbf.replace = (db_replace_f)find_mod_export(tmp, "db_replace", 2, 0); dbf.last_inserted_id= (db_last_inserted_id_f)find_mod_export(tmp, "db_last_inserted_id", 1, 0); dbf.affected_rows = (db_affected_rows_f)find_mod_export(tmp, "db_affected_rows", 1, 0); dbf.insert_update = (db_insert_update_f)find_mod_export(tmp, "db_insert_update", 2, 0); dbf.insert_delayed = (db_insert_delayed_f)find_mod_export(tmp, "db_insert_delayed", 2, 0); dbf.start_transaction = (db_start_transaction_f)find_mod_export(tmp, "db_start_transaction", 2, 0); dbf.end_transaction = (db_end_transaction_f)find_mod_export(tmp, "db_end_transaction", 1, 0); dbf.abort_transaction = (db_abort_transaction_f)find_mod_export(tmp, "db_abort_transaction", 1, 0); dbf.query_lock = (db_query_f)find_mod_export(tmp, "db_query_lock", 2, 0); } if(db_check_api(&dbf, tmp)!=0) goto error; *mydbf=dbf; /* copy */ pkg_free(tmp); return 0; error: pkg_free(tmp); return -1; } /*! \brief * Initialize database module * \note No function should be called before this */ db1_con_t* db_do_init(const str* url, void* (*new_connection)()) { return db_do_init2(url, *new_connection, DB_POOLING_PERMITTED); } /*! \brief * Initialize database module * \note No function should be called before this */ db1_con_t* db_do_init2(const str* url, void* (*new_connection)(), db_pooling_t pooling) { struct db_id* id; void* con; db1_con_t* res; int con_size = sizeof(db1_con_t) + sizeof(void *); id = 0; res = 0; if (!url || !url->s || !new_connection) { LM_ERR("invalid parameter value\n"); return 0; } if (url->len > MAX_URL_LENGTH) { LM_ERR("The configured db_url is too long\n"); return 0; } /* this is the root memory for this database connection. */ res = (db1_con_t*)pkg_malloc(con_size); if (!res) { PKG_MEM_ERROR; return 0; } memset(res, 0, con_size); id = new_db_id(url, pooling); if (!id) { LM_ERR("cannot parse URL '%.*s'\n", url->len, url->s); goto err; } /* Find the connection in the pool */ con = pool_get(id); if (!con) { LM_DBG("connection %p not found in pool\n", id); /* Not in the pool yet */ con = new_connection(id); if (!con) { LM_ERR("could not add connection to the pool\n"); goto err; } pool_insert((struct pool_con*)con); } else { LM_DBG("connection %p found in pool\n", id); free_db_id(id); // free the new id, as we use the pool instead id = 0; } res->tail = (unsigned long)con; return res; err: if (id) free_db_id(id); if (res) pkg_free(res); return 0; } /*! \brief * Shut down database module * \note No function should be called after this */ void db_do_close(db1_con_t* _h, void (*free_connection)()) { struct pool_con* con; if (!_h || !_h->tail) { LM_ERR("invalid parameter value\n"); return; } con = (struct pool_con*)_h->tail; if (pool_remove(con) == 1) { free_connection(con); } pkg_free(_h); } /** * \brief Get the version of a table. * * Returns the version number of a given table from the version table. * Instead of this function you should use db_check_table_version! * \see db_check_table_version * \param dbf database module callbacks * \param con database connection handle * \param table checked table * \return the version number if present, 0 if no version data available, < 0 on error */ int db_table_version(const db_func_t* dbf, db1_con_t* connection, const str* table) { db_key_t key[1], col[1]; db_val_t val[1]; db1_res_t* res = NULL; db_val_t* ver = 0; str *version = &version_table; str tmp1 = str_init(TABLENAME_COLUMN); str tmp2 = str_init(VERSION_COLUMN); int ret = 0; int val_type; if (!dbf||!connection || !table || !table->s) { LM_CRIT("invalid parameter value\n"); return -1; } if (dbf->use_table(connection, version) < 0) { LM_ERR("error while changing table\n"); return -1; } key[0] = &tmp1; VAL_TYPE(val) = DB1_STR; VAL_NULL(val) = 0; VAL_STR(val) = *table; col[0] = &tmp2; if (dbf->query(connection, key, 0, val, col, 1, 1, 0, &res) < 0) { LM_ERR("error in db_query\n"); return -1; } if (RES_ROW_N(res) == 0) { LM_DBG("no row for table %.*s found\n", table->len, ZSW(table->s)); return 0; } if (RES_ROW_N(res) != 1) { LM_ERR("invalid number of rows received:" " %d, %.*s\n", RES_ROW_N(res), table->len, ZSW(table->s)); dbf->free_result(connection, res); return -1; } ver = ROW_VALUES(RES_ROWS(res)); val_type = VAL_TYPE(ver); if ( (val_type!=DB1_INT && val_type!=DB1_DOUBLE && val_type!=DB1_BIGINT && val_type!=DB1_UINT && val_type!=DB1_UBIGINT) || VAL_NULL(ver) ) { LM_ERR("invalid type (%d) or nul (%d) version " "columns for %.*s\n", VAL_TYPE(ver), VAL_NULL(ver), table->len, ZSW(table->s)); dbf->free_result(connection, res); return -1; } if (val_type == DB1_INT) { ret = VAL_INT(ver); } else if (val_type == DB1_UINT) { ret = (int)VAL_UINT(ver); } else if (val_type == DB1_BIGINT) { ret = (int)VAL_BIGINT(ver); } else if (val_type == DB1_UBIGINT) { ret = (int)VAL_UBIGINT(ver); } else if (val_type == DB1_DOUBLE) { ret = (int)VAL_DOUBLE(ver); } dbf->free_result(connection, res); return ret; } /** * \brief Check the table version, including user error logging. * * Small helper function to check the table version, including user error logging. * \param dbf database module callbacks * \param dbh database connection handle * \param table checked table * \param version checked version * \return 0 means ok, -1 means an error occurred */ int db_check_table_version(db_func_t* dbf, db1_con_t* dbh, const str* table, const unsigned int version) { int ver = db_table_version(dbf, dbh, table); if (ver < 0) { LM_ERR("querying version for table %.*s\n", table->len, table->s); return -1; } else if (ver != (int)version) { LM_ERR("invalid version %d for table %.*s found, expected %u" " (check table structure and table \"version\")\n", ver, table->len, table->s, version); return -1; } return 0; } /*! \brief * Store name of table that will be used by * subsequent database functions */ int db_use_table(db1_con_t* _h, const str* _t) { if (!_h) { LM_ERR("invalid connection parameter\n"); return -1; } if (!_t || !_t->s) { LM_ERR("invalid table parameter value\n"); return -1; } CON_TABLE(_h) = _t; return 0; } /*! \brief Generic query helper for load bulk data * * Generic query helper method for load bulk data, e.g. lcr tables * \param binding database module binding * \param handle database connection * \param name database table name * \param cols queried columns * \param count number of queried columns * \param strict if set to 1 an error is returned when no data could be loaded, otherwise just a warning is logged * \param res database result, unchanged on failure and if no data could be found * \return 0 if the query was run successful, -1 otherwise */ int db_load_bulk_data(db_func_t* binding, db1_con_t* handle, str* name, db_key_t* cols, unsigned int count, unsigned int strict, db1_res_t* res) { if (binding == NULL) { LM_ERR("invalid database module binding\n"); return -1; } if(handle == NULL) { LM_ERR("invalid database handle\n"); return -1; } if (binding->use_table(handle, name) < 0) { LM_ERR("error in use_table for database\n"); return -1; } /* select the whole table and all the columns */ if(binding->query(handle, 0, 0, 0, cols, 0, count, 0, &res) < 0) { LM_ERR("error while querying database\n"); return -1; } if(RES_ROW_N(res) == 0) { binding->free_result(handle, res); if (strict == 1) { LM_ERR("no data in the database table %.*s\n", name->len, name->s); return -1; } else { LM_WARN("no data in the database table %.*s, use an empty set\n", name->len, name->s); return 0; } } return 0; } /** * \brief DB API init function. * * This function must be executed by DB connector modules at load time to * initialize the internals of DB API library. * \return returns 0 on successful initialization, -1 on error. */ int db_api_init(void) { if(db_query_init()<0) return -1; return 0; }