From 230fa5804014cbe3aac236b6b35b6f1b6b5e9178 Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Thu, 13 Aug 2015 19:08:34 +0300 Subject: [PATCH 01/15] sql_cacher: full caching in mod_init --- modules/sql_cacher/sql_cacher.c | 629 ++++++++++++++++++++++++++++++++ modules/sql_cacher/sql_cacher.h | 56 +++ 2 files changed, 685 insertions(+) create mode 100644 modules/sql_cacher/sql_cacher.c create mode 100644 modules/sql_cacher/sql_cacher.h diff --git a/modules/sql_cacher/sql_cacher.c b/modules/sql_cacher/sql_cacher.c new file mode 100644 index 00000000000..c34b5dd278c --- /dev/null +++ b/modules/sql_cacher/sql_cacher.c @@ -0,0 +1,629 @@ +#include "../../sr_module.h" +#include "../../dprint.h" +#include "../../mem/mem.h" +#include "../../ut.h" + +#include "sql_cacher.h" + +static int mod_init(void); +static void destroy(void); +static int child_init(int rank); + +static int cache_new_table(unsigned int type, void *val); + +static str delimiter; + +static cache_entry_t **entry_list; +static struct parse_entry *to_parse_list = NULL; + +/* module parameters */ +static param_export_t mod_params[] = { + {"delimiter", STR_PARAM, &delimiter.s}, + {"cache_table", STR_PARAM|USE_FUNC_PARAM, (void *)&cache_new_table}, + {0,0,0} +}; + +/** + * module exports + */ +struct module_exports exports = { + "sql_cacher", /* module name */ + MOD_TYPE_DEFAULT, /* class of this module */ + MODULE_VERSION, + DEFAULT_DLFLAGS, /* dlopen flags */ + NULL, /* OpenSIPS module dependencies */ + 0, /* exported functions */ + 0, /* exported async functions */ + mod_params, /* exported parameters */ + 0, /* exported statistics */ + 0, /* exported MI functions */ + 0, /* exported pseudo-variables */ + 0, /* extra processes */ + mod_init, /* module initialization function */ + 0, /* response handling function */ + destroy, /* destroy function */ + child_init /* per-child init function */ +}; + +static int cache_new_table(unsigned int type, void *val) { + struct parse_entry *new_entry; + + new_entry = pkg_malloc(sizeof(struct parse_entry)); + if (!new_entry) { + LM_ERR("No more memory for to_parse list entry\n"); + return -1; + } + + new_entry->next = NULL; + new_entry->to_parse_str.len = strlen((char *)val); + new_entry->to_parse_str.s = pkg_malloc(new_entry->to_parse_str.len); + if (!new_entry->to_parse_str.s) { + LM_ERR("No more pkg memory\n"); + return -1; + } + memcpy(new_entry->to_parse_str.s, (char *)val, new_entry->to_parse_str.len); + + if (!to_parse_list) { + to_parse_list = new_entry; + } else { + new_entry->next = to_parse_list; + to_parse_list = new_entry; + } + + return 0; +} + +static void insert_cache_entry(cache_entry_t *new_entry) { + new_entry->next = NULL; + + if (*entry_list != NULL) { + new_entry->next = *entry_list; + } + + *entry_list = new_entry; +} + +static int parse_cache_entries(void) { + cache_entry_t *new_entry; + struct parse_entry *it; + char *p1, *p2, *tmp, *c_tmp1, *c_tmp2; + int col_idx; + int rc = -1; + + for (it = to_parse_list; it != NULL; it = it->next) { + + new_entry = shm_malloc(sizeof(cache_entry_t)); + if (!new_entry) { + LM_ERR("No more memory for cache entry struct\n"); + return -1; + } + + new_entry->columns = NULL; + new_entry->nr_columns = 0; + new_entry->on_demand = 0; + new_entry->expire = DEFAULT_CACHEDB_EXPIRE; + new_entry->nr_ints = 0; + new_entry->nr_strs = 0; + new_entry->db_con = 0; + new_entry->cdbcon = 0; + + /* parse the id */ + p1 = it->to_parse_str.s; + p2 = memchr(p1, '=', it->to_parse_str.len); + if (!p2) + goto parse_err; + if (!memcmp(p1, ID_STR, ID_STR_LEN)) { + tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - p1)); + if (!tmp) { + goto parse_err; + } + new_entry->id.len = tmp - p2 - 1; + if (new_entry->id.len <= 0) + goto parse_err; + new_entry->id.s = shm_malloc(new_entry->id.len); + memcpy(new_entry->id.s, p2 + 1, new_entry->id.len); + } else { + goto parse_err; + } + + /* parse the db_url */ + p1 = tmp + 1; + p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); + if (!p2) + goto parse_err; + if (!memcmp(p1, DB_URL_STR, DB_URL_LEN)) { + tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + if (!tmp) + goto parse_err; + new_entry->db_url.len = tmp - p2 - 1; + if (new_entry->db_url.len <= 0) + goto parse_err; + new_entry->db_url.s = shm_malloc(new_entry->db_url.len); + memcpy(new_entry->db_url.s, p2 + 1, new_entry->db_url.len); + } else + goto parse_err; + + /* parse the cachedb_url */ + p1 = tmp + 1; + p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); + if (!p2) + goto parse_err; + if (!memcmp(p1, CACHEDB_URL_STR, CACHEDB_URL_LEN)) { + tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + if (!tmp) + goto parse_err; + new_entry->cachedb_url.len = tmp - p2 - 1; + if (new_entry->cachedb_url.len <= 0) + goto parse_err; + new_entry->cachedb_url.s = shm_malloc(new_entry->cachedb_url.len); + memcpy(new_entry->cachedb_url.s, p2 + 1, new_entry->cachedb_url.len); + } else + goto parse_err; + + /* parse the table name */ + p1 = tmp + 1; + p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); + if (!p2) + goto parse_err; + if (!memcmp(p1, TABLE_STR, TABLE_STR_LEN)) { + tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + if (!tmp) + goto parse_err; + new_entry->table.len = tmp - p2 - 1; + if (new_entry->table.len <= 0) + goto parse_err; + new_entry->table.s = shm_malloc(new_entry->table.len); + memcpy(new_entry->table.s, p2 + 1, new_entry->table.len); + } else + goto parse_err; + + /* parse the key column name */ + p1 = tmp + 1; + p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); + if (!p2) + goto parse_err; + if (!memcmp(p1, KEY_STR, KEY_STR_LEN)) { + tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + if (!tmp) /* delimiter not found, reached the end of the string to parse */ + new_entry->key.len = it->to_parse_str.len - (p2 - it->to_parse_str.s + 1); + else + new_entry->key.len = tmp - p2 - 1; + + if (new_entry->key.len <= 0) + goto parse_err; + + new_entry->key.s = shm_malloc(new_entry->key.len); + memcpy(new_entry->key.s, p2 + 1, new_entry->key.len); + + if (!tmp) + goto end_parsing; + } else + goto parse_err; + + /* parse the required column names if present */ + p1 = tmp + 1; + p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); + if (!p2) + goto parse_err; + if (!memcmp(p1, COLUMNS_STR, COLUMNS_STR_LEN)) { + col_idx = 0; + tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + + /* just count how many columns there are */ + new_entry->nr_columns = 1; + c_tmp1 = memchr(p2 + 1, COLUMN_NAMES_DELIM, it->to_parse_str.len - (p2 - it->to_parse_str.s + 1)); + while (c_tmp1) { + new_entry->nr_columns++; + c_tmp1 = memchr(c_tmp1 + 1, COLUMN_NAMES_DELIM, it->to_parse_str.len - (c_tmp1 - it->to_parse_str.s + 1)); + } + + /* allocate array of columns and actually parse */ + new_entry->columns = shm_malloc(new_entry->nr_columns * sizeof(str)); + + c_tmp1 = p2 + 1; + c_tmp2 = memchr(p2 + 1, COLUMN_NAMES_DELIM, it->to_parse_str.len - (p2 - it->to_parse_str.s + 1)); + while (c_tmp2) { + new_entry->columns[col_idx].len = c_tmp2 - c_tmp1; + if (new_entry->columns[col_idx].len <= 0) + goto parse_err; + new_entry->columns[col_idx].s = shm_malloc(new_entry->columns[col_idx].len); + memcpy(new_entry->columns[col_idx].s, c_tmp1, new_entry->columns[col_idx].len); + + c_tmp1 = c_tmp2 + 1; + c_tmp2 = memchr(c_tmp1, COLUMN_NAMES_DELIM, it->to_parse_str.len - (c_tmp1 - it->to_parse_str.s + 1)); + col_idx++; + } + + if (!tmp) + new_entry->columns[col_idx].len = it->to_parse_str.len - (p2 - c_tmp1 + 1); + else + new_entry->columns[col_idx].len = tmp - c_tmp1; + + if (new_entry->columns[col_idx].len <= 0) + goto parse_err; + new_entry->columns[col_idx].s = shm_malloc(new_entry->columns[col_idx].len); + memcpy(new_entry->columns[col_idx].s, c_tmp1, new_entry->columns[col_idx].len); + + if (!tmp) { /* delimiter not found, reached the end of the string to parse */ + goto end_parsing; + } else { + p1 = tmp + 1; + p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); + if (!p2) + goto parse_err; + } + } + + /* parse on demand parameter */ + if (!memcmp(p1, ONDEMAND_STR, ONDEMAND_STR_LEN)) { + tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + str str_val; + if (!tmp) { /* delimiter not found, reached the end of the string to parse */ + str_val.len = it->to_parse_str.len - (p2 - it->to_parse_str.s + 1); + } else { + str_val.len = tmp - p2 - 1; + } + + if (str_val.len <= 0) + goto parse_err; + str_val.s = p2 + 1; + if(str2int(&str_val, &new_entry->on_demand)) + goto parse_err; + + if (!tmp) { /* delimiter not found, reached the end of the string to parse */ + goto end_parsing; + } else { + p1 = tmp + 1; + p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); + if (!p2) + goto parse_err; + } + } + + /* parse expire parameter */ + if (!memcmp(p1, EXPIRE_STR, EXPIRE_STR_LEN)) { + str str_val; + str_val.len = it->to_parse_str.len - (p2 - it->to_parse_str.s + 1); + if (str_val.len <= 0) + goto parse_err; + str_val.s = p2 + 1; + if(str2int(&str_val, &new_entry->expire)) + goto parse_err; + + goto end_parsing; + } + + goto end_parsing; + +parse_err: + LM_ERR("Invalid cache entry specification\n"); + if (new_entry->columns) + free(new_entry->columns); + free(new_entry); + continue; +end_parsing: + insert_cache_entry(new_entry); + rc = 0; + } + + return rc; +} + +/* count the number of integers and strings from the db */ +static int get_column_types(cache_entry_t *c_entry, db_val_t *values, int nr_columns) { + unsigned int i; + db_type_t val_type; + + for (i = 0; i < nr_columns; i++) { + val_type = VAL_TYPE(values + i); + switch (val_type) { + case DB_INT: + case DB_BIGINT: + case DB_DOUBLE: + c_entry->nr_ints++; + break; + case DB_STRING: + case DB_STR: + c_entry->nr_strs++; + break; + default: + return -1; + } + } + + return 0; +} + +/* returns the total length of the actual value which will be stored in the cachedb*/ +static unsigned int cdb_val_total_len(cache_entry_t *c_entry, db_val_t *values, int nr_columns) { + unsigned int i, len = 0; + db_type_t val_type; + + /* length of the integer values and the offsets of the string values(also stored as integers) */ + len = c_entry->nr_ints * INT_B64_ENC_LEN + c_entry->nr_strs * INT_B64_ENC_LEN; + /* length of the actual string values*/ + for (i = 0; i < nr_columns; i++) { + val_type = VAL_TYPE(values + i); + switch (val_type) { + case DB_STRING: + len += strlen(VAL_STRING(values + i)); + break; + case DB_STR: + len += VAL_STR(values + i).len; + break; + default: continue; + } + } + + return len; +} + +static int insert_in_cachedb(cache_entry_t *c_entry, db_val_t *key, db_val_t *values, int nr_columns) { + unsigned int i, offset = 0, strs_offset = 0; + int int_val; + int int_key_len = 0; + char int_buf[4], int_enc_buf[INT_B64_ENC_LEN]; + char *int_key_buf = NULL; + str str_val; + db_type_t val_type; + str cdb_val; + str cdb_key; + + cdb_val.len = cdb_val_total_len(c_entry, values, nr_columns); + cdb_val.s = pkg_malloc(cdb_val.len); + + /* store the integer values first (base64 encoded) */ + for (i = 0; i < nr_columns; i++) { + int_val = 0; + val_type = VAL_TYPE(values + i); + + switch (val_type) { + case DB_INT: + int_val = VAL_INT(values + i); + break; + case DB_BIGINT: + int_val = (int)VAL_BIGINT(values + i); + break; + case DB_DOUBLE: + int_val = (int)VAL_DOUBLE(values + i); + break; + default: continue; + } + + memcpy(int_buf, &int_val, 4); + base64encode((unsigned char *)int_enc_buf, (unsigned char *)int_buf, 4); + memcpy(cdb_val.s + offset, int_enc_buf, INT_B64_ENC_LEN); + + memset(int_enc_buf, 0, INT_B64_ENC_LEN); + offset += INT_B64_ENC_LEN; + } + + /* store the string values and their offsets as integers (base64 encoded) */ + strs_offset = offset + c_entry->nr_strs * INT_B64_ENC_LEN; + + for (i = 0; i < nr_columns; i++) { + val_type = VAL_TYPE(values + i); + + switch (val_type) { + case DB_STRING: + str_val.s = (char *)VAL_STRING(values + i); + str_val.len = strlen(str_val.s); + break; + case DB_STR: + str_val = VAL_STR(values + i); + break; + default: continue; + } + + int_val = strs_offset; + memcpy(int_buf, &int_val, 4); + base64encode((unsigned char *)int_enc_buf, (unsigned char *)int_buf, 4); + memcpy(cdb_val.s + offset, int_enc_buf, INT_B64_ENC_LEN); + + memset(int_enc_buf, 0, INT_B64_ENC_LEN); + offset += INT_B64_ENC_LEN; + + memcpy(cdb_val.s + strs_offset, str_val.s, str_val.len); + strs_offset += str_val.len; + } + + /* make sure the key is string */ + val_type = VAL_TYPE(key); + switch (val_type) { + case DB_STRING: + cdb_key.s = (char *)VAL_STRING(key); + cdb_key.len = strlen(cdb_key.s); + break; + case DB_STR: + cdb_key = VAL_STR(key); + break; + case DB_INT: + int_key_buf = sint2str(VAL_INT(key), &int_key_len); + break; + case DB_BIGINT: + int_val = (int)VAL_BIGINT(key); + int_key_buf = sint2str(int_val, &int_key_len); + break; + case DB_DOUBLE: + int_val = (int)VAL_DOUBLE(key); + int_key_buf = sint2str(int_val, &int_key_len); + break; + default: + LM_ERR("Unsupported type for SQL DB key column\n"); + free(cdb_val.s); + return -1; + } + + if (int_key_len) { + cdb_key.s = int_key_buf; + cdb_key.len = int_key_len; + } + + if (c_entry->cdbf.set(c_entry->cdbcon, &cdb_key, &cdb_val, c_entry->expire) < 0) { + LM_ERR("Failed to insert in cachedb\n"); + free(cdb_val.s); + return -1; + } + + return 0; +} + +static int mod_init(void) { + cache_entry_t *c_entry; + str test_query_key_str = str_init(TEST_QUERY_STR); + str cdb_test_key = str_init(CDB_TEST_KEY_STR); + str cdb_test_val = str_init(CDB_TEST_VAL_STR); + db_key_t query_key_col; + db_key_t *query_cols = NULL; + db_val_t query_key_val; + db_res_t *sql_res; + db_row_t *rows; + db_val_t *values; + str cachedb_res; + int i, nr_rows; + + LM_NOTICE("initializing module......\n"); + + if (!delimiter.s) { + delimiter.s = pkg_malloc(sizeof(char)); + if (!delimiter.s) { + LM_ERR("No more memory for delimiter\n"); + return -1; + } + delimiter.s[0] = DEFAULT_DELIM; + delimiter.len = 1; + } else + delimiter.len = strlen(delimiter.s); + + entry_list = shm_malloc(sizeof(cache_entry_t*)); + *entry_list = NULL; + + if (!entry_list) { + LM_ERR("No more memory for cache entries list\n"); + return -1; + } + + if (parse_cache_entries() < 0) { + LM_ERR("Unable to parse any cache entry"); + return -1; + } + + VAL_NULL(&query_key_val) = 0; + VAL_TYPE(&query_key_val) = DB_STR; + VAL_STR(&query_key_val) = test_query_key_str; + + for (c_entry = *entry_list; c_entry != NULL; c_entry = c_entry->next) { + /* NoSQL DB init, test connection and query */ + if (cachedb_bind_mod(&c_entry->cachedb_url, &c_entry->cdbf) < 0) { + LM_ERR("Unable to bind to a cache database driver\n"); + continue; + } + /* open a test connection */ + c_entry->cdbcon = c_entry->cdbf.init(&c_entry->cachedb_url); + if (c_entry->cdbcon == NULL) { + LM_ERR("Cannot init connection to cache DB\n"); + continue; + } + + /* setting and geting a test key in cachedb */ + if (c_entry->cdbf.set(c_entry->cdbcon, &cdb_test_key, &cdb_test_val, 0) < 0) { + LM_ERR("Failed to set test cachedb key\n"); + continue; + } + if (c_entry->cdbf.get(c_entry->cdbcon, &cdb_test_key, &cachedb_res) < 0) { + LM_ERR("Failed to get cachedb key\n"); + continue; + } + if (str_strcmp(&cachedb_res, &cdb_test_val) != 0) { + LM_ERR("Cachedb inconsistent test key\n"); + continue; + } + + /* SQL DB init, test connection and query */ + if (db_bind_mod(&c_entry->db_url, &c_entry->db_funcs) < 0){ + LM_ERR("Unable to bind to a SQL database driver\n"); + continue; + } + /* open a test connection */ + if ((c_entry->db_con = c_entry->db_funcs.init(&c_entry->db_url)) == 0) { + LM_ERR("Cannot init connection to SQL DB\n"); + continue; + } + + if (c_entry->db_funcs.use_table(c_entry->db_con, &c_entry->table) < 0) { + LM_ERR("Invalid table name\n"); + continue; + } + /* verify the column names by running a test query with a bogus key */ + query_key_col = &c_entry->key; + query_cols = pkg_malloc((c_entry->nr_columns + 1) * sizeof(db_key_t)); + if (!query_cols) { + LM_ERR("No more memory for test query columns array\n"); + continue; + } + query_cols[0] = &(c_entry->key); + for (i = 0; i < c_entry->nr_columns; i++) { + query_cols[i+1] = &(c_entry->columns[i]); + } + + if (c_entry->db_funcs.query(c_entry->db_con, &query_key_col, 0, &query_key_val, + query_cols + 1, 1, c_entry->nr_columns, 0, &sql_res) != 0) { + LM_ERR("Failure to issuse test query to SQL DB\n"); + continue; + } + + c_entry->db_funcs.free_result(c_entry->db_con, sql_res); + + /* cache the entire table if on demand is not set*/ + if (!c_entry->on_demand) { + c_entry->expire = 0; + + if (c_entry->db_funcs.query(c_entry->db_con, NULL, 0, NULL, + query_cols, 0, c_entry->nr_columns + 1, 0, &sql_res) != 0) { + LM_ERR("Failure to issue query to SQL DB\n"); + continue; + } + + nr_rows = RES_ROW_N(sql_res); + if (nr_rows == 0) { + LM_DBG("Table is empty!\n"); + } + rows = RES_ROWS(sql_res); + + values = ROW_VALUES(rows); + if (get_column_types(c_entry, values + 1, ROW_N(rows) - 1) < 0) { + LM_ERR("SQL column has unsupported type\n"); + c_entry->db_funcs.free_result(c_entry->db_con, sql_res); + continue; + } + + for (i = 0; i < nr_rows; i++) { + values = ROW_VALUES(rows + i); + if (!VAL_NULL(values)) { + insert_in_cachedb(c_entry, values ,values + 1, ROW_N(rows + i) - 1); + } + } + + LM_DBG("Cached the entire table\n"); + c_entry->db_funcs.free_result(c_entry->db_con, sql_res); + } + + c_entry->db_funcs.close(c_entry->db_con); + c_entry->db_con = 0; + + c_entry->cdbf.destroy(c_entry->cdbcon); + c_entry->cdbcon = 0; + } + + return 0; +} + +static int child_init(int rank) { + LM_NOTICE("initializing child......\n"); + return 0; +} + +static void destroy(void) +{ + LM_NOTICE("destroy module ...\n"); +} \ No newline at end of file diff --git a/modules/sql_cacher/sql_cacher.h b/modules/sql_cacher/sql_cacher.h new file mode 100644 index 00000000000..a9255ca73f6 --- /dev/null +++ b/modules/sql_cacher/sql_cacher.h @@ -0,0 +1,56 @@ +#ifndef _SQL_CACHER_H_ +#define _SQL_CACHER_H_ + +#include "../../db/db.h" +#include "../../cachedb/cachedb.h" + +#define DEFAULT_DELIM ' ' +#define COLUMN_NAMES_DELIM ',' + +#define ID_STR "id" +#define ID_STR_LEN 2 +#define DB_URL_STR "db_url" +#define DB_URL_LEN 6 +#define CACHEDB_URL_STR "cachedb_url" +#define CACHEDB_URL_LEN 6 +#define TABLE_STR "table" +#define TABLE_STR_LEN 5 +#define KEY_STR "key" +#define KEY_STR_LEN 3 +#define COLUMNS_STR "columns" +#define COLUMNS_STR_LEN 7 +#define ONDEMAND_STR "on_demand" +#define ONDEMAND_STR_LEN 9 +#define EXPIRE_STR "expire" +#define EXPIRE_STR_LEN 6 + +#define DEFAULT_CACHEDB_EXPIRE 30 +#define TEST_QUERY_STR "sql_cacher_test_query_key" +#define CDB_TEST_KEY_STR "sql_cacher_cdb_test_key" +#define CDB_TEST_VAL_STR "sql_cacher_cdb_test_val" +#define INT_B64_ENC_LEN 8 + +typedef struct _cache_entry { + str id; + str db_url; + str cachedb_url; + str table; + str key; + str *columns; + unsigned int nr_columns; + unsigned int on_demand; + unsigned int expire; + unsigned int nr_ints, nr_strs; + db_func_t db_funcs; + db_con_t *db_con; + cachedb_funcs cdbf; + cachedb_con *cdbcon; + struct _cache_entry *next; +} cache_entry_t; + +struct parse_entry { + str to_parse_str; + struct parse_entry *next; +}; + +#endif \ No newline at end of file From e7faa3e67247ca9b4db1db12104f6cdb39302965 Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Tue, 18 Aug 2015 17:31:58 +0300 Subject: [PATCH 02/15] sql_cacher: Full caching improvements -use fetch_result for queries if sql db supports -fix cachedb encoding for null sql db values -caching id added in cachedb key --- modules/sql_cacher/sql_cacher.c | 319 ++++++++++++++++++++------------ modules/sql_cacher/sql_cacher.h | 3 +- 2 files changed, 205 insertions(+), 117 deletions(-) diff --git a/modules/sql_cacher/sql_cacher.c b/modules/sql_cacher/sql_cacher.c index c34b5dd278c..84725d7a232 100644 --- a/modules/sql_cacher/sql_cacher.c +++ b/modules/sql_cacher/sql_cacher.c @@ -15,10 +15,12 @@ static str delimiter; static cache_entry_t **entry_list; static struct parse_entry *to_parse_list = NULL; +static int fetch_nr_rows = DEFAULT_FETCH_NR_ROWS; /* module parameters */ static param_export_t mod_params[] = { {"delimiter", STR_PARAM, &delimiter.s}, + {"sql_fetch_nr_rows", INT_PARAM, &fetch_nr_rows}, {"cache_table", STR_PARAM|USE_FUNC_PARAM, (void *)&cache_new_table}, {0,0,0} }; @@ -309,7 +311,7 @@ static int parse_cache_entries(void) { return rc; } -/* count the number of integers and strings from the db */ +/* count the number of integers and strings from the SQL db query */ static int get_column_types(cache_entry_t *c_entry, db_val_t *values, int nr_columns) { unsigned int i; db_type_t val_type; @@ -366,11 +368,16 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_val_t *key, db_val_t *va char *int_key_buf = NULL; str str_val; db_type_t val_type; + str str_key; str cdb_val; str cdb_key; cdb_val.len = cdb_val_total_len(c_entry, values, nr_columns); cdb_val.s = pkg_malloc(cdb_val.len); + if (!cdb_val.s) { + LM_ERR("No more pkg memory\n"); + return -1; + } /* store the integer values first (base64 encoded) */ for (i = 0; i < nr_columns; i++) { @@ -390,8 +397,13 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_val_t *key, db_val_t *va default: continue; } - memcpy(int_buf, &int_val, 4); - base64encode((unsigned char *)int_enc_buf, (unsigned char *)int_buf, 4); + if (VAL_NULL(values + i)) { + memset(int_enc_buf, 0, INT_B64_ENC_LEN); + } else { + memcpy(int_buf, &int_val, 4); + base64encode((unsigned char *)int_enc_buf, (unsigned char *)int_buf, 4); + } + memcpy(cdb_val.s + offset, int_enc_buf, INT_B64_ENC_LEN); memset(int_enc_buf, 0, INT_B64_ENC_LEN); @@ -400,7 +412,7 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_val_t *key, db_val_t *va /* store the string values and their offsets as integers (base64 encoded) */ strs_offset = offset + c_entry->nr_strs * INT_B64_ENC_LEN; - + for (i = 0; i < nr_columns; i++) { val_type = VAL_TYPE(values + i); @@ -415,7 +427,11 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_val_t *key, db_val_t *va default: continue; } - int_val = strs_offset; + if (VAL_NULL(values + i) || !str_val.len) + int_val = 0; + else + int_val = strs_offset; + memcpy(int_buf, &int_val, 4); base64encode((unsigned char *)int_enc_buf, (unsigned char *)int_buf, 4); memcpy(cdb_val.s + offset, int_enc_buf, INT_B64_ENC_LEN); @@ -431,11 +447,11 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_val_t *key, db_val_t *va val_type = VAL_TYPE(key); switch (val_type) { case DB_STRING: - cdb_key.s = (char *)VAL_STRING(key); - cdb_key.len = strlen(cdb_key.s); + str_key.s = (char *)VAL_STRING(key); + str_key.len = strlen(str_key.s); break; case DB_STR: - cdb_key = VAL_STR(key); + str_key = VAL_STR(key); break; case DB_INT: int_key_buf = sint2str(VAL_INT(key), &int_key_len); @@ -450,26 +466,31 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_val_t *key, db_val_t *va break; default: LM_ERR("Unsupported type for SQL DB key column\n"); - free(cdb_val.s); return -1; } - if (int_key_len) { - cdb_key.s = int_key_buf; - cdb_key.len = int_key_len; + str_key.s = int_key_buf; + str_key.len = int_key_len; } + cdb_key.len = c_entry->id.len + str_key.len; + cdb_key.s = pkg_malloc(cdb_key.len); + if (!cdb_key.s) { + LM_ERR("No more pkg memory\n"); + return -1; + } + memcpy(cdb_key.s, c_entry->id.s, c_entry->id.len); + memcpy(cdb_key.s + c_entry->id.len, str_key.s, str_key.len); + if (c_entry->cdbf.set(c_entry->cdbcon, &cdb_key, &cdb_val, c_entry->expire) < 0) { LM_ERR("Failed to insert in cachedb\n"); - free(cdb_val.s); return -1; } return 0; } -static int mod_init(void) { - cache_entry_t *c_entry; +static int db_init_test_conn(cache_entry_t *c_entry) { str test_query_key_str = str_init(TEST_QUERY_STR); str cdb_test_key = str_init(CDB_TEST_KEY_STR); str cdb_test_val = str_init(CDB_TEST_VAL_STR); @@ -477,10 +498,166 @@ static int mod_init(void) { db_key_t *query_cols = NULL; db_val_t query_key_val; db_res_t *sql_res; - db_row_t *rows; - db_val_t *values; str cachedb_res; - int i, nr_rows; + unsigned int i; + + /* cachedb init and test connection */ + if (cachedb_bind_mod(&c_entry->cachedb_url, &c_entry->cdbf) < 0) { + LM_ERR("Unable to bind to a cachedb database driver\n"); + return -1; + } + /* open a test connection */ + c_entry->cdbcon = c_entry->cdbf.init(&c_entry->cachedb_url); + if (c_entry->cdbcon == NULL) { + LM_ERR("Cannot init connection to cachedb\n"); + return -1; + } + /* setting and geting a test key in cachedb */ + if (c_entry->cdbf.set(c_entry->cdbcon, &cdb_test_key, &cdb_test_val, 0) < 0) { + LM_ERR("Failed to set test key in cachedb\n"); + c_entry->cdbf.destroy(c_entry->cdbcon); + c_entry->cdbcon = 0; + return -1; + } + if (c_entry->cdbf.get(c_entry->cdbcon, &cdb_test_key, &cachedb_res) < 0) { + LM_ERR("Failed to get test key from cachedb\n"); + c_entry->cdbf.destroy(c_entry->cdbcon); + c_entry->cdbcon = 0; + return -1; + } + if (str_strcmp(&cachedb_res, &cdb_test_val) != 0) { + LM_ERR("Cachedb inconsistent test key\n"); + c_entry->cdbf.destroy(c_entry->cdbcon); + c_entry->cdbcon = 0; + return -1; + } + + /* SQL DB init and test connection */ + if (db_bind_mod(&c_entry->db_url, &c_entry->db_funcs) < 0){ + LM_ERR("Unable to bind to a SQL database driver\n"); + return -1; + } + /* open a test connection */ + if ((c_entry->db_con = c_entry->db_funcs.init(&c_entry->db_url)) == 0) { + LM_ERR("Cannot init connection to SQL DB\n"); + return -1; + } + + /* verify the column names by running a test query with a bogus key */ + if (c_entry->db_funcs.use_table(c_entry->db_con, &c_entry->table) < 0) { + LM_ERR("Invalid table name\n"); + c_entry->db_funcs.close(c_entry->db_con); + c_entry->db_con = 0; + return -1; + } + + VAL_NULL(&query_key_val) = 0; + VAL_TYPE(&query_key_val) = DB_STR; + VAL_STR(&query_key_val) = test_query_key_str; + + query_key_col = &c_entry->key; + + query_cols = pkg_malloc(c_entry->nr_columns * sizeof(db_key_t)); + if (!query_cols) { + LM_ERR("No more pkg memory\n"); + c_entry->db_funcs.close(c_entry->db_con); + c_entry->db_con = 0; + return -1; + } + + for (i = 0; i < c_entry->nr_columns; i++) + query_cols[i] = &(c_entry->columns[i]); + + if (c_entry->db_funcs.query(c_entry->db_con, &query_key_col, 0, &query_key_val, + query_cols, 1, c_entry->nr_columns, 0, &sql_res) != 0) { + LM_ERR("Failure to issuse test query to SQL DB\n"); + c_entry->db_funcs.close(c_entry->db_con); + c_entry->db_con = 0; + return -1; + } + + c_entry->db_funcs.free_result(c_entry->db_con, sql_res); + return 0; +} + +static int load_entire_table(cache_entry_t *c_entry) { + db_key_t *query_cols = NULL; + db_res_t *sql_res = NULL; + db_row_t *row; + db_val_t *values; + int i; + + query_cols = pkg_malloc((c_entry->nr_columns + 1) * sizeof(db_key_t)); + if (!query_cols) { + LM_ERR("No more pkg memory\n"); + return -1; + } + query_cols[0] = &(c_entry->key); + for (i=0; i < c_entry->nr_columns; i++) { + query_cols[i+1] = &(c_entry->columns[i]); + } + + /* query the entire table */ + if (DB_CAPABILITY(c_entry->db_funcs, DB_CAP_FETCH)) { + if (c_entry->db_funcs.query(c_entry->db_con, NULL, 0, NULL, + query_cols, 0, c_entry->nr_columns + 1, 0, 0) != 0) { + LM_ERR("Failure to issue query to SQL DB\n"); + goto error; + } + + if (c_entry->db_funcs.fetch_result(c_entry->db_con,&sql_res,fetch_nr_rows)<0) { + LM_ERR("Error fetching rows\n"); + goto error; + } + } else { + if (c_entry->db_funcs.query(c_entry->db_con, NULL, 0, NULL, + query_cols, 0, c_entry->nr_columns + 1, 0, &sql_res) != 0) { + LM_ERR("Failure to issue query to SQL DB\n"); + goto error; + } + } + + if (RES_ROW_N(sql_res) == 0) { + LM_DBG("Table is empty!\n"); + goto error; + } + row = RES_ROWS(sql_res); + values = ROW_VALUES(row); + if (get_column_types(c_entry, values + 1, ROW_N(row) - 1) < 0) { + LM_ERR("SQL column has unsupported type\n"); + goto error; + } + + /* load the rows into the cahchedb */ + do { + for (i=0; i < RES_ROW_N(sql_res); i++) { + row = RES_ROWS(sql_res) + i; + values = ROW_VALUES(row); + if (!VAL_NULL(values)) { + insert_in_cachedb(c_entry, values ,values + 1, ROW_N(row) - 1); + } + } + + if (DB_CAPABILITY(c_entry->db_funcs, DB_CAP_FETCH)) { + if (c_entry->db_funcs.fetch_result(c_entry->db_con,&sql_res,fetch_nr_rows)<0) { + LM_ERR("Error fetching rows (1)\n"); + goto error; + } + } else { + break; + } + } while (RES_ROW_N(sql_res) > 0); + + c_entry->db_funcs.free_result(c_entry->db_con, sql_res); + return 0; +error: + if (sql_res) + c_entry->db_funcs.free_result(c_entry->db_con, sql_res); + return -1; +} + +static int mod_init(void) { + cache_entry_t *c_entry; LM_NOTICE("initializing module......\n"); @@ -496,121 +673,32 @@ static int mod_init(void) { delimiter.len = strlen(delimiter.s); entry_list = shm_malloc(sizeof(cache_entry_t*)); - *entry_list = NULL; - if (!entry_list) { LM_ERR("No more memory for cache entries list\n"); return -1; } + *entry_list = NULL; if (parse_cache_entries() < 0) { - LM_ERR("Unable to parse any cache entry"); + LM_ERR("Unable to parse any cache entry\n"); return -1; } - VAL_NULL(&query_key_val) = 0; - VAL_TYPE(&query_key_val) = DB_STR; - VAL_STR(&query_key_val) = test_query_key_str; - for (c_entry = *entry_list; c_entry != NULL; c_entry = c_entry->next) { - /* NoSQL DB init, test connection and query */ - if (cachedb_bind_mod(&c_entry->cachedb_url, &c_entry->cdbf) < 0) { - LM_ERR("Unable to bind to a cache database driver\n"); - continue; - } - /* open a test connection */ - c_entry->cdbcon = c_entry->cdbf.init(&c_entry->cachedb_url); - if (c_entry->cdbcon == NULL) { - LM_ERR("Cannot init connection to cache DB\n"); - continue; - } - - /* setting and geting a test key in cachedb */ - if (c_entry->cdbf.set(c_entry->cdbcon, &cdb_test_key, &cdb_test_val, 0) < 0) { - LM_ERR("Failed to set test cachedb key\n"); - continue; - } - if (c_entry->cdbf.get(c_entry->cdbcon, &cdb_test_key, &cachedb_res) < 0) { - LM_ERR("Failed to get cachedb key\n"); - continue; - } - if (str_strcmp(&cachedb_res, &cdb_test_val) != 0) { - LM_ERR("Cachedb inconsistent test key\n"); - continue; - } - - /* SQL DB init, test connection and query */ - if (db_bind_mod(&c_entry->db_url, &c_entry->db_funcs) < 0){ - LM_ERR("Unable to bind to a SQL database driver\n"); + if (db_init_test_conn(c_entry) < 0) continue; - } - /* open a test connection */ - if ((c_entry->db_con = c_entry->db_funcs.init(&c_entry->db_url)) == 0) { - LM_ERR("Cannot init connection to SQL DB\n"); - continue; - } - - if (c_entry->db_funcs.use_table(c_entry->db_con, &c_entry->table) < 0) { - LM_ERR("Invalid table name\n"); - continue; - } - /* verify the column names by running a test query with a bogus key */ - query_key_col = &c_entry->key; - query_cols = pkg_malloc((c_entry->nr_columns + 1) * sizeof(db_key_t)); - if (!query_cols) { - LM_ERR("No more memory for test query columns array\n"); - continue; - } - query_cols[0] = &(c_entry->key); - for (i = 0; i < c_entry->nr_columns; i++) { - query_cols[i+1] = &(c_entry->columns[i]); - } - - if (c_entry->db_funcs.query(c_entry->db_con, &query_key_col, 0, &query_key_val, - query_cols + 1, 1, c_entry->nr_columns, 0, &sql_res) != 0) { - LM_ERR("Failure to issuse test query to SQL DB\n"); - continue; - } - - c_entry->db_funcs.free_result(c_entry->db_con, sql_res); /* cache the entire table if on demand is not set*/ if (!c_entry->on_demand) { c_entry->expire = 0; - - if (c_entry->db_funcs.query(c_entry->db_con, NULL, 0, NULL, - query_cols, 0, c_entry->nr_columns + 1, 0, &sql_res) != 0) { - LM_ERR("Failure to issue query to SQL DB\n"); - continue; - } - - nr_rows = RES_ROW_N(sql_res); - if (nr_rows == 0) { - LM_DBG("Table is empty!\n"); - } - rows = RES_ROWS(sql_res); - - values = ROW_VALUES(rows); - if (get_column_types(c_entry, values + 1, ROW_N(rows) - 1) < 0) { - LM_ERR("SQL column has unsupported type\n"); - c_entry->db_funcs.free_result(c_entry->db_con, sql_res); - continue; - } - - for (i = 0; i < nr_rows; i++) { - values = ROW_VALUES(rows + i); - if (!VAL_NULL(values)) { - insert_in_cachedb(c_entry, values ,values + 1, ROW_N(rows + i) - 1); - } - } - - LM_DBG("Cached the entire table\n"); - c_entry->db_funcs.free_result(c_entry->db_con, sql_res); + if (load_entire_table(c_entry) < 0) + LM_ERR("Failed to cache the entire table %s\n", c_entry->table.s); + else + LM_DBG("Cached the entire table %s\n", c_entry->table.s); } c_entry->db_funcs.close(c_entry->db_con); c_entry->db_con = 0; - c_entry->cdbf.destroy(c_entry->cdbcon); c_entry->cdbcon = 0; } @@ -623,7 +711,6 @@ static int child_init(int rank) { return 0; } -static void destroy(void) -{ +static void destroy(void) { LM_NOTICE("destroy module ...\n"); } \ No newline at end of file diff --git a/modules/sql_cacher/sql_cacher.h b/modules/sql_cacher/sql_cacher.h index a9255ca73f6..311027f0b3b 100644 --- a/modules/sql_cacher/sql_cacher.h +++ b/modules/sql_cacher/sql_cacher.h @@ -24,7 +24,8 @@ #define EXPIRE_STR "expire" #define EXPIRE_STR_LEN 6 -#define DEFAULT_CACHEDB_EXPIRE 30 +#define DEFAULT_CACHEDB_EXPIRE 60 +#define DEFAULT_FETCH_NR_ROWS 100 #define TEST_QUERY_STR "sql_cacher_test_query_key" #define CDB_TEST_KEY_STR "sql_cacher_cdb_test_key" #define CDB_TEST_VAL_STR "sql_cacher_cdb_test_val" From e7065585ae5e4b063eee75429e2196aaea8cea08 Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Wed, 2 Sep 2015 12:29:11 +0300 Subject: [PATCH 03/15] sql_cacher: add sql_cached_value pseudo-variable; parse pvar name and optimize for next pvar get --- modules/sql_cacher/sql_cacher.c | 245 +++++++++++++++++++++++++++++--- modules/sql_cacher/sql_cacher.h | 14 +- 2 files changed, 239 insertions(+), 20 deletions(-) diff --git a/modules/sql_cacher/sql_cacher.c b/modules/sql_cacher/sql_cacher.c index 84725d7a232..858fc73199c 100644 --- a/modules/sql_cacher/sql_cacher.c +++ b/modules/sql_cacher/sql_cacher.c @@ -2,6 +2,7 @@ #include "../../dprint.h" #include "../../mem/mem.h" #include "../../ut.h" +#include "../../pvar.h" #include "sql_cacher.h" @@ -11,20 +12,32 @@ static int child_init(int rank); static int cache_new_table(unsigned int type, void *val); -static str delimiter; +int pv_parse_name(pv_spec_p sp, str *in); +int pv_init_param(pv_spec_p sp, int param); +int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t *res); + +static str spec_delimiter; +static str pvar_delimiter; +static int fetch_nr_rows = DEFAULT_FETCH_NR_ROWS; static cache_entry_t **entry_list; static struct parse_entry *to_parse_list = NULL; -static int fetch_nr_rows = DEFAULT_FETCH_NR_ROWS; /* module parameters */ static param_export_t mod_params[] = { - {"delimiter", STR_PARAM, &delimiter.s}, + {"spec_delimiter", STR_PARAM, &spec_delimiter.s}, + {"pvar_delimiter", STR_PARAM, &pvar_delimiter.s}, {"sql_fetch_nr_rows", INT_PARAM, &fetch_nr_rows}, {"cache_table", STR_PARAM|USE_FUNC_PARAM, (void *)&cache_new_table}, {0,0,0} }; +static pv_export_t mod_items[] = { + {{"sql_cached_value", sizeof("sql_cached_value") - 1}, 1000, + pv_get_sql_cached_value, 0, pv_parse_name, 0, 0, 0}, + { {0, 0}, 0, 0, 0, 0, 0, 0, 0 } +}; + /** * module exports */ @@ -39,7 +52,7 @@ struct module_exports exports = { mod_params, /* exported parameters */ 0, /* exported statistics */ 0, /* exported MI functions */ - 0, /* exported pseudo-variables */ + mod_items, /* exported pseudo-variables */ 0, /* extra processes */ mod_init, /* module initialization function */ 0, /* response handling function */ @@ -106,6 +119,7 @@ static int parse_cache_entries(void) { new_entry->expire = DEFAULT_CACHEDB_EXPIRE; new_entry->nr_ints = 0; new_entry->nr_strs = 0; + new_entry->column_types = 0; new_entry->db_con = 0; new_entry->cdbcon = 0; @@ -115,7 +129,7 @@ static int parse_cache_entries(void) { if (!p2) goto parse_err; if (!memcmp(p1, ID_STR, ID_STR_LEN)) { - tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - p1)); + tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - p1)); if (!tmp) { goto parse_err; } @@ -134,7 +148,7 @@ static int parse_cache_entries(void) { if (!p2) goto parse_err; if (!memcmp(p1, DB_URL_STR, DB_URL_LEN)) { - tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); if (!tmp) goto parse_err; new_entry->db_url.len = tmp - p2 - 1; @@ -151,7 +165,7 @@ static int parse_cache_entries(void) { if (!p2) goto parse_err; if (!memcmp(p1, CACHEDB_URL_STR, CACHEDB_URL_LEN)) { - tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); if (!tmp) goto parse_err; new_entry->cachedb_url.len = tmp - p2 - 1; @@ -168,7 +182,7 @@ static int parse_cache_entries(void) { if (!p2) goto parse_err; if (!memcmp(p1, TABLE_STR, TABLE_STR_LEN)) { - tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); if (!tmp) goto parse_err; new_entry->table.len = tmp - p2 - 1; @@ -185,7 +199,7 @@ static int parse_cache_entries(void) { if (!p2) goto parse_err; if (!memcmp(p1, KEY_STR, KEY_STR_LEN)) { - tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); if (!tmp) /* delimiter not found, reached the end of the string to parse */ new_entry->key.len = it->to_parse_str.len - (p2 - it->to_parse_str.s + 1); else @@ -209,7 +223,7 @@ static int parse_cache_entries(void) { goto parse_err; if (!memcmp(p1, COLUMNS_STR, COLUMNS_STR_LEN)) { col_idx = 0; - tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); /* just count how many columns there are */ new_entry->nr_columns = 1; @@ -219,6 +233,11 @@ static int parse_cache_entries(void) { c_tmp1 = memchr(c_tmp1 + 1, COLUMN_NAMES_DELIM, it->to_parse_str.len - (c_tmp1 - it->to_parse_str.s + 1)); } + if (new_entry->nr_columns > sizeof(long long)) { + LM_ERR("Too many columns, maximum number is %ld\n", sizeof(long long)); + goto parse_err; + } + /* allocate array of columns and actually parse */ new_entry->columns = shm_malloc(new_entry->nr_columns * sizeof(str)); @@ -258,7 +277,7 @@ static int parse_cache_entries(void) { /* parse on demand parameter */ if (!memcmp(p1, ONDEMAND_STR, ONDEMAND_STR_LEN)) { - tmp = memchr(p2 + 1, delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); str str_val; if (!tmp) { /* delimiter not found, reached the end of the string to parse */ str_val.len = it->to_parse_str.len - (p2 - it->to_parse_str.s + 1); @@ -314,6 +333,7 @@ static int parse_cache_entries(void) { /* count the number of integers and strings from the SQL db query */ static int get_column_types(cache_entry_t *c_entry, db_val_t *values, int nr_columns) { unsigned int i; + long long one = 1; db_type_t val_type; for (i = 0; i < nr_columns; i++) { @@ -323,10 +343,12 @@ static int get_column_types(cache_entry_t *c_entry, db_val_t *values, int nr_col case DB_BIGINT: case DB_DOUBLE: c_entry->nr_ints++; + c_entry->column_types &= ~(one << i); break; case DB_STRING: case DB_STR: c_entry->nr_strs++; + c_entry->column_types |= (one << i); break; default: return -1; @@ -661,16 +683,24 @@ static int mod_init(void) { LM_NOTICE("initializing module......\n"); - if (!delimiter.s) { - delimiter.s = pkg_malloc(sizeof(char)); - if (!delimiter.s) { - LM_ERR("No more memory for delimiter\n"); + if (!spec_delimiter.s) { + spec_delimiter.s = pkg_malloc(sizeof(char)); + if (!spec_delimiter.s) { + LM_ERR("No more memory for spec_delimiter\n"); return -1; } - delimiter.s[0] = DEFAULT_DELIM; - delimiter.len = 1; + spec_delimiter.s[0] = DEFAULT_SPEC_DELIM; + } + + if (!pvar_delimiter.s) { + pvar_delimiter.s = pkg_malloc(sizeof(char)); + if (!pvar_delimiter.s) { + LM_ERR("No more memory for pvar_delimiter\n"); + return -1; + } + pvar_delimiter.s[0] = DEFAULT_PVAR_DELIM; } else - delimiter.len = strlen(delimiter.s); + pvar_delimiter.len = strlen(pvar_delimiter.s); entry_list = shm_malloc(sizeof(cache_entry_t*)); if (!entry_list) { @@ -707,10 +737,187 @@ static int mod_init(void) { } static int child_init(int rank) { - LM_NOTICE("initializing child......\n"); + cache_entry_t *c_entry; + + for (c_entry = *entry_list; c_entry != NULL; c_entry = c_entry->next) { + c_entry->cdbcon = c_entry->cdbf.init(&c_entry->cachedb_url); + if (c_entry->cdbcon == NULL) { + LM_ERR("Cannot connect to cachedb from child\n"); + return -1; + } + + if (c_entry->on_demand && + (c_entry->db_con = c_entry->db_funcs.init(&c_entry->db_url)) == 0) { + LM_ERR("Cannot connect to SQL DB from child\n"); + return -1; + } + } + return 0; } +static int parse_pv_name_s(pv_name_fix_t *pv_name, str *name_s) { + char *p1 = NULL, *p2 = NULL; + char last; + +#define PARSE_TOKEN(_ptr1, _ptr2, type, delim) \ + do { \ + (_ptr2) = memchr((_ptr1), (delim), \ + name_s->len - ((_ptr1) - name_s->s) + 1); \ + if (!(_ptr2)) { \ + LM_ERR("Invalid syntax for pvar name\n"); \ + return -1; \ + } \ + int prev_len = pv_name->type.len; \ + pv_name->type.len = (_ptr2) - (_ptr1); \ + if (!pv_name->type.s) { \ + pv_name->type.s = pkg_malloc(pv_name->type.len); \ + if (!pv_name->type.s) { \ + LM_ERR("No more pkg memory\n"); \ + return -1; \ + } \ + memcpy(pv_name->type.s, (_ptr1), pv_name->type.len); \ + } else if (memcmp(pv_name->type.s, (_ptr1), pv_name->type.len)) { \ + if (prev_len != pv_name->type.len) { \ + pv_name->type.s = pkg_realloc(pv_name->type.s, pv_name->type.len); \ + if (!pv_name->type.s) { \ + LM_ERR("No more pkg memory\n"); \ + return -1; \ + } \ + } \ + memcpy(pv_name->type.s, (_ptr1), pv_name->type.len); \ + } \ + } while (0) + + last = name_s->s[name_s->len]; + p1 = name_s->s; + PARSE_TOKEN(p1, p2, id, DEFAULT_PVAR_DELIM); + p1 = p2 + 1; + PARSE_TOKEN(p1, p2, col, DEFAULT_PVAR_DELIM); + p1 = p2 + 1; + PARSE_TOKEN(p1, p2, key, last); + +#undef PARSE_TOKEN + + return 0; +} + +int pv_parse_name(pv_spec_p sp, str *in) { + pv_elem_t *model = NULL, *it; + pv_name_fix_t *pv_name; + + if (in == NULL || in->s == NULL || sp == NULL) + return -1; + + pv_name = pkg_malloc(sizeof(pv_name_fix_t)); + if (!pv_name) { + LM_ERR("No more pkg memory\n"); + return -1; + } + pv_name->id.s = NULL; + pv_name->id.len = 0; + pv_name->col.s = NULL; + pv_name->col.len = 0; + pv_name->key.s = NULL; + pv_name->key.len = 0; + pv_name->c_entry = NULL; + pv_name->pv_elem_list = NULL; + pv_name->col_offset = -1; + + sp->pvp.pvn.type = PV_NAME_PVAR; + sp->pvp.pvn.u.dname = (void *)pv_name; + + if (pv_parse_format(in, &model) < 0) { + LM_ERR("Wrong format for pvar name\n"); + return -1; + } + + for (it = model; it != NULL; it = it->next) { + if (it->spec.type != PVT_NONE) + break; + } + if (it != NULL) { /* if there are variables in the name, parse later */ + pv_name->pv_elem_list = model; + } else { + if (parse_pv_name_s(pv_name, &(model->text)) < 0) + return -1; + } + + return 0; +} + +int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t *res) { + pv_name_fix_t *pv_name; + str name_s; + cache_entry_t *it; + int i, j, prev_cols; + char col_type1, col_type2; + long long one = 1; + + if (param == NULL || param->pvn.type != PV_NAME_PVAR || + param->pvn.u.dname == NULL) { + LM_CRIT("Bad pvar get function parameters\n"); + return -1; + } + + pv_name = (pv_name_fix_t *)param->pvn.u.dname; + if (!pv_name) { + LM_ERR("Unable to get name struct from dname\n"); + return -1; + } + + if (pv_name->pv_elem_list) { + /* there are variables in the name which need to be evaluated, then parse */ + if (pv_printf_s(msg, pv_name->pv_elem_list, &name_s) != 0 || + name_s.len == 0 || name_s.s == NULL) { + LM_ERR("Unable to evaluate variables in pv name"); + return pv_get_null(msg, param, res); + } + if (parse_pv_name_s(pv_name, &name_s) < 0) + return pv_get_null(msg, param, res); + } else { + /* already parsed */ + name_s = pv_name->id; + } + + /* save pointer to cache entries list and collumn offset in the pv spec */ + if (!pv_name->c_entry) { + for (it = *entry_list; it != NULL; it = it->next) { + if (!memcmp(it->id.s, pv_name->id.s, pv_name->id.len)) { + pv_name->c_entry = it; + + for (i = 0; i < it->nr_columns; i++) { + if (!memcmp(it->columns[i].s, pv_name->col.s, pv_name->col.len)) { + prev_cols = 0; + col_type1 = ((it->column_types & (one << i)) != 0); + for (j = 0; j < i; j++) { + col_type2 = ((it->column_types & (one << j)) != 0); + if (col_type1 == col_type2) + prev_cols++; + } + if (col_type1) + pv_name->col_offset = it->nr_ints * INT_B64_ENC_LEN + + prev_cols * INT_B64_ENC_LEN; + else + pv_name->col_offset = prev_cols * INT_B64_ENC_LEN; + break; + } + } + if (i == it->nr_columns) + pv_name->col_offset = -1; + break; + } + } + + if (!it) { + LM_ERR("Unknown caching id\n"); + return pv_get_null(msg, param, res); + } + } + + return pv_get_null(msg, param, res); +} + static void destroy(void) { LM_NOTICE("destroy module ...\n"); } \ No newline at end of file diff --git a/modules/sql_cacher/sql_cacher.h b/modules/sql_cacher/sql_cacher.h index 311027f0b3b..ca8369c362d 100644 --- a/modules/sql_cacher/sql_cacher.h +++ b/modules/sql_cacher/sql_cacher.h @@ -4,8 +4,9 @@ #include "../../db/db.h" #include "../../cachedb/cachedb.h" -#define DEFAULT_DELIM ' ' +#define DEFAULT_SPEC_DELIM ' ' #define COLUMN_NAMES_DELIM ',' +#define DEFAULT_PVAR_DELIM ':' #define ID_STR "id" #define ID_STR_LEN 2 @@ -42,6 +43,7 @@ typedef struct _cache_entry { unsigned int on_demand; unsigned int expire; unsigned int nr_ints, nr_strs; + long long column_types; db_func_t db_funcs; db_con_t *db_con; cachedb_funcs cdbf; @@ -54,4 +56,14 @@ struct parse_entry { struct parse_entry *next; }; +typedef struct _pv_name_fix +{ + str id; + str col; + str key; + cache_entry_t *c_entry; + pv_elem_t *pv_elem_list; + int col_offset; +} pv_name_fix_t; + #endif \ No newline at end of file From 6c22820fe89260c2c0dd8680262c0e9fda1c94ae Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Wed, 2 Sep 2015 13:15:04 +0300 Subject: [PATCH 04/15] remove duplicate code by using macro in parse_cache_entries function --- modules/sql_cacher/sql_cacher.c | 85 ++++++++++----------------------- 1 file changed, 26 insertions(+), 59 deletions(-) diff --git a/modules/sql_cacher/sql_cacher.c b/modules/sql_cacher/sql_cacher.c index 858fc73199c..5d7e83e4b96 100644 --- a/modules/sql_cacher/sql_cacher.c +++ b/modules/sql_cacher/sql_cacher.c @@ -112,7 +112,6 @@ static int parse_cache_entries(void) { LM_ERR("No more memory for cache entry struct\n"); return -1; } - new_entry->columns = NULL; new_entry->nr_columns = 0; new_entry->on_demand = 0; @@ -123,75 +122,43 @@ static int parse_cache_entries(void) { new_entry->db_con = 0; new_entry->cdbcon = 0; +#define PARSE_TOKEN(_ptr1, _ptr2, field, field_name_str, field_name_len) \ + do { \ + (_ptr2) = memchr((_ptr1), '=', it->to_parse_str.len - \ + ((_ptr1) - it->to_parse_str.s)); \ + if (!(_ptr2)) \ + goto parse_err; \ + if (!memcmp((_ptr1), (field_name_str), (field_name_len))) { \ + tmp = memchr((_ptr2) + 1, spec_delimiter.s[0], it->to_parse_str.len - \ + ((_ptr2) - it->to_parse_str.s)); \ + if (!tmp) \ + goto parse_err; \ + new_entry->field.len = tmp - (_ptr2) - 1; \ + if (new_entry->field.len <= 0) \ + goto parse_err; \ + new_entry->field.s = shm_malloc(new_entry->field.len); \ + memcpy(new_entry->field.s, p2 + 1, new_entry->field.len); \ + } else \ + goto parse_err; \ + } while (0) + /* parse the id */ p1 = it->to_parse_str.s; - p2 = memchr(p1, '=', it->to_parse_str.len); - if (!p2) - goto parse_err; - if (!memcmp(p1, ID_STR, ID_STR_LEN)) { - tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - p1)); - if (!tmp) { - goto parse_err; - } - new_entry->id.len = tmp - p2 - 1; - if (new_entry->id.len <= 0) - goto parse_err; - new_entry->id.s = shm_malloc(new_entry->id.len); - memcpy(new_entry->id.s, p2 + 1, new_entry->id.len); - } else { - goto parse_err; - } + PARSE_TOKEN(p1, p2, id, ID_STR, ID_STR_LEN); /* parse the db_url */ p1 = tmp + 1; - p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); - if (!p2) - goto parse_err; - if (!memcmp(p1, DB_URL_STR, DB_URL_LEN)) { - tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); - if (!tmp) - goto parse_err; - new_entry->db_url.len = tmp - p2 - 1; - if (new_entry->db_url.len <= 0) - goto parse_err; - new_entry->db_url.s = shm_malloc(new_entry->db_url.len); - memcpy(new_entry->db_url.s, p2 + 1, new_entry->db_url.len); - } else - goto parse_err; + PARSE_TOKEN(p1, p2, db_url, DB_URL_STR, DB_URL_LEN); /* parse the cachedb_url */ p1 = tmp + 1; - p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); - if (!p2) - goto parse_err; - if (!memcmp(p1, CACHEDB_URL_STR, CACHEDB_URL_LEN)) { - tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); - if (!tmp) - goto parse_err; - new_entry->cachedb_url.len = tmp - p2 - 1; - if (new_entry->cachedb_url.len <= 0) - goto parse_err; - new_entry->cachedb_url.s = shm_malloc(new_entry->cachedb_url.len); - memcpy(new_entry->cachedb_url.s, p2 + 1, new_entry->cachedb_url.len); - } else - goto parse_err; + PARSE_TOKEN(p1, p2, cachedb_url, CACHEDB_URL_STR, CACHEDB_URL_LEN); /* parse the table name */ p1 = tmp + 1; - p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); - if (!p2) - goto parse_err; - if (!memcmp(p1, TABLE_STR, TABLE_STR_LEN)) { - tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); - if (!tmp) - goto parse_err; - new_entry->table.len = tmp - p2 - 1; - if (new_entry->table.len <= 0) - goto parse_err; - new_entry->table.s = shm_malloc(new_entry->table.len); - memcpy(new_entry->table.s, p2 + 1, new_entry->table.len); - } else - goto parse_err; + PARSE_TOKEN(p1, p2, table, TABLE_STR, TABLE_STR_LEN); + +#undef PARSE_TOKEN /* parse the key column name */ p1 = tmp + 1; From 4cc712a692680b2f5e5664f8052045ab0a8b2339 Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Mon, 7 Sep 2015 16:39:09 +0300 Subject: [PATCH 05/15] add function to get value from cachedb; full caching completed --- modules/sql_cacher/sql_cacher.c | 142 +++++++++++++++++++++++++++++--- modules/sql_cacher/sql_cacher.h | 2 + 2 files changed, 131 insertions(+), 13 deletions(-) diff --git a/modules/sql_cacher/sql_cacher.c b/modules/sql_cacher/sql_cacher.c index 5d7e83e4b96..e050d85b4ca 100644 --- a/modules/sql_cacher/sql_cacher.c +++ b/modules/sql_cacher/sql_cacher.c @@ -286,8 +286,8 @@ static int parse_cache_entries(void) { parse_err: LM_ERR("Invalid cache entry specification\n"); if (new_entry->columns) - free(new_entry->columns); - free(new_entry); + shm_free(new_entry->columns); + shm_free(new_entry); continue; end_parsing: insert_cache_entry(new_entry); @@ -416,8 +416,9 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_val_t *key, db_val_t *va default: continue; } - if (VAL_NULL(values + i) || !str_val.len) + if (VAL_NULL(values + i)) { int_val = 0; + } else int_val = strs_offset; @@ -723,6 +724,85 @@ static int child_init(int rank) { return 0; } +/* return value: +* 0 - succes +* 1 - succes, null value in db +* -1 - error +* -2 - not found +*/ +static int get_from_cachedb(pv_name_fix_t *pv_name, str *str_res, int *int_res) { + str cdb_res, cdb_key; + long long one = 1; + int int_val, next_str_off, i, rc; + char int_buf[4]; + const char zeroes[INT_B64_ENC_LEN] = {0}; + + cdb_key.len = pv_name->id.len + pv_name->key.len; + cdb_key.s = pkg_malloc(cdb_key.len); + if (!cdb_key.s) { + LM_ERR("No more pkg memory\n"); + return -1; + } + memcpy(cdb_key.s, pv_name->id.s, pv_name->id.len); + memcpy(cdb_key.s + pv_name->id.len, pv_name->key.s, pv_name->key.len); + + rc = pv_name->c_entry->cdbf.get(pv_name->c_entry->cdbcon, &cdb_key, &cdb_res); + if (rc == -1) + goto error; + if (rc == -2) + return -2; + + if (pv_name->col_offset == -1) { + LM_WARN("Unknown column %.*s\n", pv_name->col.len, pv_name->col.s); + return -1; + } + + /* null integer value in db */ + if (!memcmp(cdb_res.s + pv_name->col_offset, zeroes, INT_B64_ENC_LEN)) + return 1; + + /* decode the integer value or the offset of the string value */ + if (base64decode((unsigned char *)int_buf, + (unsigned char *)(cdb_res.s + pv_name->col_offset), INT_B64_ENC_LEN) != 4) + goto error; + memcpy(&int_val, int_buf, 4); + + if ((pv_name->c_entry->column_types & (one << pv_name->col_nr)) != 0) { + /* null string value in db */ + if (int_val == 0) + return 1; + + str_res->s = cdb_res.s + int_val; + if (pv_name->last_str) + str_res->len = cdb_res.len - int_val; + else { + /* calculate the length of the current string using the offset of the next not null string */ + i = 1; + do { + rc = base64decode((unsigned char *)int_buf, (unsigned char *)(cdb_res.s + + pv_name->col_offset + i * INT_B64_ENC_LEN), INT_B64_ENC_LEN); + if (rc != 4) + goto error; + memcpy(&next_str_off, int_buf, 4); + i++; + } while (next_str_off == 0 && pv_name->col_offset + i*INT_B64_ENC_LEN < + pv_name->c_entry->nr_columns * INT_B64_ENC_LEN); + + if (next_str_off == 0) + str_res->len = cdb_res.len - int_val; + else + str_res->len = next_str_off - int_val; + } + } else { + *int_res = int_val; + } + + return 0; +error: + LM_ERR("Failed to get value from cachedb\n"); + return -1; +} + static int parse_pv_name_s(pv_name_fix_t *pv_name, str *name_s) { char *p1 = NULL, *p2 = NULL; char last; @@ -817,9 +897,11 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t pv_name_fix_t *pv_name; str name_s; cache_entry_t *it; - int i, j, prev_cols; - char col_type1, col_type2; + int i, j, prev_cols, rc; + char col_type1, col_type2, *ch = NULL; long long one = 1; + int int_res = 0, l = 0; + str str_res = {NULL, 0}; if (param == NULL || param->pvn.type != PV_NAME_PVAR || param->pvn.u.dname == NULL) { @@ -842,12 +924,9 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t } if (parse_pv_name_s(pv_name, &name_s) < 0) return pv_get_null(msg, param, res); - } else { - /* already parsed */ - name_s = pv_name->id; } - /* save pointer to cache entries list and collumn offset in the pv spec */ + /* save pointer to cache entries list and column offset in the pv spec */ if (!pv_name->c_entry) { for (it = *entry_list; it != NULL; it = it->next) { if (!memcmp(it->id.s, pv_name->id.s, pv_name->id.len)) { @@ -855,6 +934,7 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t for (i = 0; i < it->nr_columns; i++) { if (!memcmp(it->columns[i].s, pv_name->col.s, pv_name->col.len)) { + pv_name->col_nr = i; prev_cols = 0; col_type1 = ((it->column_types & (one << i)) != 0); for (j = 0; j < i; j++) { @@ -862,27 +942,63 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t if (col_type1 == col_type2) prev_cols++; } - if (col_type1) + if (col_type1) { pv_name->col_offset = it->nr_ints * INT_B64_ENC_LEN + prev_cols * INT_B64_ENC_LEN; + if (prev_cols == it->nr_strs - 1) + pv_name->last_str = 1; + else + pv_name->last_str = 0; + } else pv_name->col_offset = prev_cols * INT_B64_ENC_LEN; + break; } } - if (i == it->nr_columns) + if (i == it->nr_columns) { + pv_name->col_nr = -1; pv_name->col_offset = -1; + } + break; } } if (!it) { - LM_ERR("Unknown caching id\n"); + LM_WARN("Unknown caching id %.*s\n", pv_name->id.len, pv_name->id.s); return pv_get_null(msg, param, res); } } - return pv_get_null(msg, param, res); + rc = get_from_cachedb(pv_name, &str_res, &int_res); + switch (rc) { + case -1: + return pv_get_null(msg, param, res); + break; + case -2: + LM_DBG("Key: %.*s not found\n", pv_name->key.len, pv_name->key.s); + return pv_get_null(msg, param, res); + break; + case 1: + LM_DBG("NULL value in SQL db\n"); + return pv_get_null(msg, param, res); + break; + case 0: + if ((pv_name->c_entry->column_types & (one << pv_name->col_nr)) != 0) { + res->flags = PV_VAL_STR; + res->rs.s = str_res.s; + res->rs.len = str_res.len; + } else { + res->ri = int_res; + ch = int2str(int_res, &l); + res->rs.s = ch; + res->rs.len = l; + res->flags = PV_VAL_STR|PV_VAL_INT|PV_TYPE_INT; + } + } + + return 0; } static void destroy(void) { diff --git a/modules/sql_cacher/sql_cacher.h b/modules/sql_cacher/sql_cacher.h index ca8369c362d..f9a16a637bc 100644 --- a/modules/sql_cacher/sql_cacher.h +++ b/modules/sql_cacher/sql_cacher.h @@ -64,6 +64,8 @@ typedef struct _pv_name_fix cache_entry_t *c_entry; pv_elem_t *pv_elem_list; int col_offset; + int col_nr; + char last_str; } pv_name_fix_t; #endif \ No newline at end of file From 501236533f049b2a660d42ddc2414ec9b4eb00ff Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Wed, 16 Sep 2015 18:23:28 +0300 Subject: [PATCH 06/15] sql_cacher: immplement on demand caching --- modules/sql_cacher/sql_cacher.c | 439 +++++++++++++++++++++++++------- modules/sql_cacher/sql_cacher.h | 9 +- 2 files changed, 351 insertions(+), 97 deletions(-) diff --git a/modules/sql_cacher/sql_cacher.c b/modules/sql_cacher/sql_cacher.c index e050d85b4ca..d9c0f1db57f 100644 --- a/modules/sql_cacher/sql_cacher.c +++ b/modules/sql_cacher/sql_cacher.c @@ -3,6 +3,7 @@ #include "../../mem/mem.h" #include "../../ut.h" #include "../../pvar.h" +#include "../../locking.h" #include "sql_cacher.h" @@ -22,6 +23,9 @@ static int fetch_nr_rows = DEFAULT_FETCH_NR_ROWS; static cache_entry_t **entry_list; static struct parse_entry *to_parse_list = NULL; +static struct queried_key **queries_in_progress; + +gen_lock_t *queries_lock; /* module parameters */ static param_export_t mod_params[] = { @@ -88,16 +92,6 @@ static int cache_new_table(unsigned int type, void *val) { return 0; } -static void insert_cache_entry(cache_entry_t *new_entry) { - new_entry->next = NULL; - - if (*entry_list != NULL) { - new_entry->next = *entry_list; - } - - *entry_list = new_entry; -} - static int parse_cache_entries(void) { cache_entry_t *new_entry; struct parse_entry *it; @@ -290,14 +284,18 @@ static int parse_cache_entries(void) { shm_free(new_entry); continue; end_parsing: - insert_cache_entry(new_entry); + new_entry->next = NULL; + if (*entry_list != NULL) + new_entry->next = *entry_list; + *entry_list = new_entry; + rc = 0; } return rc; } -/* count the number of integers and strings from the SQL db query */ +/* get the column types from the sql query result */ static int get_column_types(cache_entry_t *c_entry, db_val_t *values, int nr_columns) { unsigned int i; long long one = 1; @@ -588,6 +586,12 @@ static int load_entire_table(cache_entry_t *c_entry) { } /* query the entire table */ + if (c_entry->db_funcs.use_table(c_entry->db_con, &c_entry->table) < 0) { + LM_ERR("Invalid table name\n"); + c_entry->db_funcs.close(c_entry->db_con); + c_entry->db_con = 0; + return -1; + } if (DB_CAPABILITY(c_entry->db_funcs, DB_CAP_FETCH)) { if (c_entry->db_funcs.query(c_entry->db_con, NULL, 0, NULL, query_cols, 0, c_entry->nr_columns + 1, 0, 0) != 0) { @@ -677,6 +681,23 @@ static int mod_init(void) { } *entry_list = NULL; + queries_in_progress = shm_malloc(sizeof(struct queried_key *)); + if (!queries_in_progress) { + LM_ERR("No more memory for queries_in_progress list\n"); + return -1; + } + *queries_in_progress = NULL; + + queries_lock = lock_alloc(); + if (!queries_lock) { + LM_ERR("No more memory for queries_lock\n"); + return -1; + } + if (!lock_init(queries_lock)) { + LM_ERR("Failed to init queries_lock\n"); + return -1; + } + if (parse_cache_entries() < 0) { LM_ERR("Unable to parse any cache entry\n"); return -1; @@ -724,18 +745,13 @@ static int child_init(int rank) { return 0; } -/* return value: -* 0 - succes -* 1 - succes, null value in db -* -1 - error -* -2 - not found -*/ -static int get_from_cachedb(pv_name_fix_t *pv_name, str *str_res, int *int_res) { - str cdb_res, cdb_key; - long long one = 1; - int int_val, next_str_off, i, rc; - char int_buf[4]; - const char zeroes[INT_B64_ENC_LEN] = {0}; +/* return: + * 1 - if found + * -2 - if not found + * -1 - if error + */ +static int cdb_fetch(pv_name_fix_t *pv_name, str *cdb_res) { + str cdb_key; cdb_key.len = pv_name->id.len + pv_name->key.len; cdb_key.s = pkg_malloc(cdb_key.len); @@ -746,11 +762,19 @@ static int get_from_cachedb(pv_name_fix_t *pv_name, str *str_res, int *int_res) memcpy(cdb_key.s, pv_name->id.s, pv_name->id.len); memcpy(cdb_key.s + pv_name->id.len, pv_name->key.s, pv_name->key.len); - rc = pv_name->c_entry->cdbf.get(pv_name->c_entry->cdbcon, &cdb_key, &cdb_res); - if (rc == -1) - goto error; - if (rc == -2) - return -2; + return pv_name->c_entry->cdbf.get(pv_name->c_entry->cdbcon, &cdb_key, cdb_res); +} + +/* return: +* 0 - succes +* 1 - succes, null value in db +* -1 - error +*/ +static int cdb_val_decode(pv_name_fix_t *pv_name, str *cdb_val, str *str_res, int *int_res) { + long long one = 1; + int int_val, next_str_off, i, rc; + char int_buf[4]; + const char zeroes[INT_B64_ENC_LEN] = {0}; if (pv_name->col_offset == -1) { LM_WARN("Unknown column %.*s\n", pv_name->col.len, pv_name->col.s); @@ -758,12 +782,12 @@ static int get_from_cachedb(pv_name_fix_t *pv_name, str *str_res, int *int_res) } /* null integer value in db */ - if (!memcmp(cdb_res.s + pv_name->col_offset, zeroes, INT_B64_ENC_LEN)) + if (!memcmp(cdb_val->s + pv_name->col_offset, zeroes, INT_B64_ENC_LEN)) return 1; /* decode the integer value or the offset of the string value */ if (base64decode((unsigned char *)int_buf, - (unsigned char *)(cdb_res.s + pv_name->col_offset), INT_B64_ENC_LEN) != 4) + (unsigned char *)(cdb_val->s + pv_name->col_offset), INT_B64_ENC_LEN) != 4) goto error; memcpy(&int_val, int_buf, 4); @@ -772,14 +796,14 @@ static int get_from_cachedb(pv_name_fix_t *pv_name, str *str_res, int *int_res) if (int_val == 0) return 1; - str_res->s = cdb_res.s + int_val; + str_res->s = cdb_val->s + int_val; if (pv_name->last_str) - str_res->len = cdb_res.len - int_val; + str_res->len = cdb_val->len - int_val; else { /* calculate the length of the current string using the offset of the next not null string */ i = 1; do { - rc = base64decode((unsigned char *)int_buf, (unsigned char *)(cdb_res.s + + rc = base64decode((unsigned char *)int_buf, (unsigned char *)(cdb_val->s + pv_name->col_offset + i * INT_B64_ENC_LEN), INT_B64_ENC_LEN); if (rc != 4) goto error; @@ -789,7 +813,7 @@ static int get_from_cachedb(pv_name_fix_t *pv_name, str *str_res, int *int_res) pv_name->c_entry->nr_columns * INT_B64_ENC_LEN); if (next_str_off == 0) - str_res->len = cdb_res.len - int_val; + str_res->len = cdb_val->len - int_val; else str_res->len = next_str_off - int_val; } @@ -799,7 +823,231 @@ static int get_from_cachedb(pv_name_fix_t *pv_name, str *str_res, int *int_res) return 0; error: - LM_ERR("Failed to get value from cachedb\n"); + LM_ERR("Failed to decode value from cachedb\n"); + return -1; +} + +static void optimize_cdb_decode(pv_name_fix_t *pv_name) { + int i, j, prev_cols; + char col_type1, col_type2; + long long one = 1; + + for (i = 0; i < pv_name->c_entry->nr_columns; i++) + if (!memcmp(pv_name->c_entry->columns[i].s, pv_name->col.s, pv_name->col.len)) { + pv_name->col_nr = i; + + prev_cols = 0; + col_type1 = ((pv_name->c_entry->column_types & (one << i)) != 0); + for (j = 0; j < i; j++) { + col_type2 = ((pv_name->c_entry->column_types & (one << j)) != 0); + if (col_type1 == col_type2) + prev_cols++; + } + if (col_type1) { + pv_name->col_offset = pv_name->c_entry->nr_ints * INT_B64_ENC_LEN + + prev_cols * INT_B64_ENC_LEN; + if (prev_cols == pv_name->c_entry->nr_strs - 1) + pv_name->last_str = 1; + else + pv_name->last_str = 0; + } else + pv_name->col_offset = prev_cols * INT_B64_ENC_LEN; + + break; + } + + if (i == pv_name->c_entry->nr_columns) + pv_name->col_offset = -1; +} + +/* return: +* 0 - succes +* 1 - succes, null value in db +* -1 - error +*/ +static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, int *int_res) { + struct queried_key *it, *prev = NULL, *tmp, *new_key; + str src_key; + db_key_t *query_cols = NULL, key_col; + db_res_t *sql_res = NULL; + db_row_t *row; + db_val_t *values, key_val; + db_type_t val_type; + int i; + + src_key.len = pv_name->id.len + pv_name->key.len; + src_key.s = shm_malloc(src_key.len); + if (!src_key.s) { + LM_ERR("No more shm memory\n"); + return -1; + } + memcpy(src_key.s, pv_name->id.s, pv_name->id.len); + memcpy(src_key.s + pv_name->id.len, pv_name->key.s, pv_name->key.len); + + lock_get(queries_lock); + + it = *queries_in_progress; + while (it != NULL) { + if (!memcmp(it->key.s, src_key.s, src_key.len)) { /* key is in list */ + it->nr_waiting_procs++; + lock_release(queries_lock); + /* wait for the query to complete */ + lock_get(it->wait_sql_query); + lock_get(queries_lock); + if (it->nr_waiting_procs == 1) { + lock_release(it->wait_sql_query); + lock_destroy(it->wait_sql_query); + lock_dealloc(it->wait_sql_query); + /* if this is the last process waiting, delete key from list */ + if (prev) + prev->next = it->next; + else + *queries_in_progress = it->next; + tmp = it; + it = it->next; + shm_free(tmp); + } else if (it->nr_waiting_procs > 1) { + it->nr_waiting_procs--; + lock_release(it->wait_sql_query); + } + lock_release(queries_lock); + + /* reload key from cachedb */ + if (cdb_fetch(pv_name, cdb_res) < 0) { + LM_ERR("Error or missing value on retrying fetch from cachedb\n"); + return -1; + } + + if (pv_name->last_str == -1) + optimize_cdb_decode(pv_name); + + return cdb_val_decode(pv_name, cdb_res, str_res, int_res); + } else { + it = it->next; + } + prev = it; + } + + if (!it) { /* if key not found in list */ + /* insert key in list */ + new_key = shm_malloc(sizeof(struct queried_key)); + if (!new_key) { + return -1; + } + new_key->key = src_key; + new_key->nr_waiting_procs = 0; + new_key->wait_sql_query = lock_alloc(); + if (!new_key->wait_sql_query) { + LM_ERR("No more memory for wait_sql_query lock\n"); + return -1; + } + if (!lock_init(new_key->wait_sql_query)) { + LM_ERR("Failed to init wait_sql_query lock\n"); + return -1; + } + + new_key->next = NULL; + if (*queries_in_progress != NULL) + new_key->next = *queries_in_progress; + *queries_in_progress = new_key; + + lock_get(new_key->wait_sql_query); + + lock_release(queries_lock); + + /* load key from sql and insert in cachedb */ + query_cols = pkg_malloc(pv_name->c_entry->nr_columns * sizeof(db_key_t)); + if (!query_cols) { + LM_ERR("No more pkg memory\n"); + return -1; + } + for (i=0; i < pv_name->c_entry->nr_columns; i++) + query_cols[i] = &(pv_name->c_entry->columns[i]); + key_col = &(pv_name->c_entry->key); + VAL_NULL(&key_val) = 0; + VAL_TYPE(&key_val) = DB_STR; + VAL_STR(&key_val) = pv_name->key; + + if (pv_name->c_entry->db_funcs.use_table(pv_name->c_entry->db_con, &pv_name->c_entry->table) < 0) { + LM_ERR("Invalid table name\n"); + pv_name->c_entry->db_funcs.close(pv_name->c_entry->db_con); + pv_name->c_entry->db_con = 0; + return -1; + } + + if (pv_name->c_entry->db_funcs.query(pv_name->c_entry->db_con, + &key_col, 0, &key_val, query_cols, 1, + pv_name->c_entry->nr_columns, 0, &sql_res) != 0) { + LM_ERR("Failure to issue query to SQL DB\n"); + goto sql_error; + } + + if (RES_ROW_N(sql_res) != 1) { + LM_ERR("Only one row should be loaded!\n"); + goto sql_error; + } + row = RES_ROWS(sql_res); + values = ROW_VALUES(row); + if (pv_name->c_entry->nr_ints + pv_name->c_entry->nr_strs == 0 && + get_column_types(pv_name->c_entry, values, ROW_N(row)) < 0) { + LM_ERR("SQL column has unsupported type\n"); + goto sql_error; + } + + insert_in_cachedb(pv_name->c_entry, &key_val, values, ROW_N(row)); + + lock_get(queries_lock); + + lock_release(new_key->wait_sql_query); + + /* delete key from list */ + if (new_key->nr_waiting_procs == 0) { + lock_destroy(new_key->wait_sql_query); + lock_dealloc(new_key->wait_sql_query); + *queries_in_progress = new_key->next; + shm_free(new_key); + } + + lock_release(queries_lock); + + for (i = 0; i < pv_name->c_entry->nr_columns; i++) + if (!memcmp(pv_name->c_entry->columns[i].s, pv_name->col.s, pv_name->col.len)) + pv_name->col_nr = i; + + if (VAL_NULL(values + pv_name->col_nr)) + return 1; + + val_type = VAL_TYPE(values + pv_name->col_nr); + switch (val_type) { + case DB_STRING: + str_res->s = (char *)VAL_STRING(values + pv_name->col_nr); + str_res->len = strlen(str_res->s); + break; + case DB_STR: + str_res = &(VAL_STR(values + pv_name->col_nr)); + break; + case DB_INT: + *int_res = VAL_INT(values + pv_name->col_nr); + break; + case DB_BIGINT: + *int_res = (int)VAL_BIGINT(values + pv_name->col_nr); + break; + case DB_DOUBLE: + *int_res = (int)VAL_DOUBLE(values + pv_name->col_nr); + break; + default: + LM_ERR("Unsupported type for SQL column\n"); + return -1; + } + + pv_name->c_entry->db_funcs.free_result(pv_name->c_entry->db_con, sql_res); + + return 0; + } + +sql_error: + if (sql_res) + pv_name->c_entry->db_funcs.free_result(pv_name->c_entry->db_con, sql_res); return -1; } @@ -870,6 +1118,7 @@ int pv_parse_name(pv_spec_p sp, str *in) { pv_name->c_entry = NULL; pv_name->pv_elem_list = NULL; pv_name->col_offset = -1; + pv_name->last_str = -1; sp->pvp.pvn.type = PV_NAME_PVAR; sp->pvp.pvn.u.dname = (void *)pv_name; @@ -897,11 +1146,12 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t pv_name_fix_t *pv_name; str name_s; cache_entry_t *it; - int i, j, prev_cols, rc; - char col_type1, col_type2, *ch = NULL; + int rc, rc2; + char *ch = NULL; long long one = 1; int int_res = 0, l = 0; str str_res = {NULL, 0}; + str cdb_res; if (param == NULL || param->pvn.type != PV_NAME_PVAR || param->pvn.u.dname == NULL) { @@ -926,81 +1176,78 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t return pv_get_null(msg, param, res); } - /* save pointer to cache entries list and column offset in the pv spec */ if (!pv_name->c_entry) { - for (it = *entry_list; it != NULL; it = it->next) { + for (it = *entry_list; it != NULL; it = it->next) if (!memcmp(it->id.s, pv_name->id.s, pv_name->id.len)) { pv_name->c_entry = it; - - for (i = 0; i < it->nr_columns; i++) { - if (!memcmp(it->columns[i].s, pv_name->col.s, pv_name->col.len)) { - pv_name->col_nr = i; - prev_cols = 0; - col_type1 = ((it->column_types & (one << i)) != 0); - for (j = 0; j < i; j++) { - col_type2 = ((it->column_types & (one << j)) != 0); - if (col_type1 == col_type2) - prev_cols++; - } - if (col_type1) { - pv_name->col_offset = it->nr_ints * INT_B64_ENC_LEN + - prev_cols * INT_B64_ENC_LEN; - if (prev_cols == it->nr_strs - 1) - pv_name->last_str = 1; - else - pv_name->last_str = 0; - } - else - pv_name->col_offset = prev_cols * INT_B64_ENC_LEN; - - break; - } - } - if (i == it->nr_columns) { - pv_name->col_nr = -1; - pv_name->col_offset = -1; - } - break; } - } - if (!it) { LM_WARN("Unknown caching id %.*s\n", pv_name->id.len, pv_name->id.s); return pv_get_null(msg, param, res); } } - rc = get_from_cachedb(pv_name, &str_res, &int_res); - switch (rc) { - case -1: - return pv_get_null(msg, param, res); - break; - case -2: - LM_DBG("Key: %.*s not found\n", pv_name->key.len, pv_name->key.s); - return pv_get_null(msg, param, res); - break; - case 1: - LM_DBG("NULL value in SQL db\n"); + rc = cdb_fetch(pv_name, &cdb_res); + if (rc == -1) { + LM_ERR("Error fetching from cachedb\n"); + return pv_get_null(msg, param, res); + } + + if (!pv_name->c_entry->on_demand) { + if (rc == -2) { + LM_DBG("key: %.*s not found\n", pv_name->key.len, pv_name->key.s); return pv_get_null(msg, param, res); - break; - case 0: - if ((pv_name->c_entry->column_types & (one << pv_name->col_nr)) != 0) { - res->flags = PV_VAL_STR; - res->rs.s = str_res.s; - res->rs.len = str_res.len; - } else { - res->ri = int_res; - ch = int2str(int_res, &l); - res->rs.s = ch; - res->rs.len = l; - res->flags = PV_VAL_STR|PV_VAL_INT|PV_TYPE_INT; + } else { + if (pv_name->last_str == -1) + optimize_cdb_decode(pv_name); + rc2 = cdb_val_decode(pv_name, &cdb_res, &str_res, &int_res); + if (rc2 == -1) + return pv_get_null(msg, param, res); + if (rc2 == 1) { + LM_WARN("NULL value in SQL db\n"); + return pv_get_null(msg, param, res); + } + } + } else { + if (rc == -2) { + rc2 = on_demand_load(pv_name, &cdb_res, &str_res, &int_res); + if (rc2 == -1) + return pv_get_null(msg, param, res); + if (rc2 == 1) { + LM_WARN("NULL value in SQL db\n"); + return pv_get_null(msg, param, res); } + } else { + if (pv_name->last_str == -1) + optimize_cdb_decode(pv_name); + rc2 = cdb_val_decode(pv_name, &cdb_res, &str_res, &int_res); + if (rc2 == -1) + return pv_get_null(msg, param, res); + if (rc2 == 1) { + LM_WARN("NULL value in SQL db\n"); + return pv_get_null(msg, param, res); + } + } + } + + if ((pv_name->c_entry->column_types & (one << pv_name->col_nr)) != 0) { + res->flags = PV_VAL_STR; + res->rs.s = str_res.s; + res->rs.len = str_res.len; + } else { + res->ri = int_res; + ch = int2str(int_res, &l); + res->rs.s = ch; + res->rs.len = l; + res->flags = PV_VAL_STR|PV_VAL_INT|PV_TYPE_INT; } return 0; } static void destroy(void) { - LM_NOTICE("destroy module ...\n"); + LM_NOTICE("destroying module ...\n"); + lock_destroy(queries_lock); + lock_dealloc(queries_lock); } \ No newline at end of file diff --git a/modules/sql_cacher/sql_cacher.h b/modules/sql_cacher/sql_cacher.h index f9a16a637bc..6079d9577ff 100644 --- a/modules/sql_cacher/sql_cacher.h +++ b/modules/sql_cacher/sql_cacher.h @@ -25,7 +25,7 @@ #define EXPIRE_STR "expire" #define EXPIRE_STR_LEN 6 -#define DEFAULT_CACHEDB_EXPIRE 60 +#define DEFAULT_CACHEDB_EXPIRE 3600 #define DEFAULT_FETCH_NR_ROWS 100 #define TEST_QUERY_STR "sql_cacher_test_query_key" #define CDB_TEST_KEY_STR "sql_cacher_cdb_test_key" @@ -56,6 +56,13 @@ struct parse_entry { struct parse_entry *next; }; +struct queried_key { + str key; + int nr_waiting_procs; + gen_lock_t *wait_sql_query; + struct queried_key *next; +}; + typedef struct _pv_name_fix { str id; From 604b67a916d007716e5e747da57e7791f8f47610 Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Thu, 17 Sep 2015 18:48:04 +0300 Subject: [PATCH 07/15] add per process db handlers list --- modules/sql_cacher/sql_cacher.c | 184 ++++++++++++++++++-------------- modules/sql_cacher/sql_cacher.h | 10 +- 2 files changed, 110 insertions(+), 84 deletions(-) diff --git a/modules/sql_cacher/sql_cacher.c b/modules/sql_cacher/sql_cacher.c index d9c0f1db57f..5ad4d541755 100644 --- a/modules/sql_cacher/sql_cacher.c +++ b/modules/sql_cacher/sql_cacher.c @@ -4,7 +4,6 @@ #include "../../ut.h" #include "../../pvar.h" #include "../../locking.h" - #include "sql_cacher.h" static int mod_init(void); @@ -24,6 +23,8 @@ static int fetch_nr_rows = DEFAULT_FETCH_NR_ROWS; static cache_entry_t **entry_list; static struct parse_entry *to_parse_list = NULL; static struct queried_key **queries_in_progress; +/* per process db handlers corresponding to cache entries in entry_list */ +static db_handlers_t *db_hdls_list = NULL; gen_lock_t *queries_lock; @@ -113,8 +114,6 @@ static int parse_cache_entries(void) { new_entry->nr_ints = 0; new_entry->nr_strs = 0; new_entry->column_types = 0; - new_entry->db_con = 0; - new_entry->cdbcon = 0; #define PARSE_TOKEN(_ptr1, _ptr2, field, field_name_str, field_name_len) \ do { \ @@ -347,7 +346,7 @@ static unsigned int cdb_val_total_len(cache_entry_t *c_entry, db_val_t *values, return len; } -static int insert_in_cachedb(cache_entry_t *c_entry, db_val_t *key, db_val_t *values, int nr_columns) { +static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_val_t *key, db_val_t *values, int nr_columns) { unsigned int i, offset = 0, strs_offset = 0; int int_val; int int_key_len = 0; @@ -470,7 +469,7 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_val_t *key, db_val_t *va memcpy(cdb_key.s, c_entry->id.s, c_entry->id.len); memcpy(cdb_key.s + c_entry->id.len, str_key.s, str_key.len); - if (c_entry->cdbf.set(c_entry->cdbcon, &cdb_key, &cdb_val, c_entry->expire) < 0) { + if (db_hdls->cdbf.set(db_hdls->cdbcon, &cdb_key, &cdb_val, c_entry->expire) < 0) { LM_ERR("Failed to insert in cachedb\n"); return -1; } @@ -478,7 +477,8 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_val_t *key, db_val_t *va return 0; } -static int db_init_test_conn(cache_entry_t *c_entry) { +static db_handlers_t *db_init_test_conn(cache_entry_t *c_entry) { + db_handlers_t *new_db_hdls; str test_query_key_str = str_init(TEST_QUERY_STR); str cdb_test_key = str_init(CDB_TEST_KEY_STR); str cdb_test_val = str_init(CDB_TEST_VAL_STR); @@ -489,54 +489,65 @@ static int db_init_test_conn(cache_entry_t *c_entry) { str cachedb_res; unsigned int i; + new_db_hdls = pkg_malloc(sizeof(db_handlers_t)); + if (!new_db_hdls) { + LM_ERR("No more pkg memory for db handlers\n"); + return NULL; + } + new_db_hdls->c_entry = c_entry; + new_db_hdls->db_con = 0; + new_db_hdls->cdbcon = 0; + new_db_hdls->next = db_hdls_list; + db_hdls_list = new_db_hdls; + /* cachedb init and test connection */ - if (cachedb_bind_mod(&c_entry->cachedb_url, &c_entry->cdbf) < 0) { + if (cachedb_bind_mod(&c_entry->cachedb_url, &new_db_hdls->cdbf) < 0) { LM_ERR("Unable to bind to a cachedb database driver\n"); - return -1; + return NULL; } /* open a test connection */ - c_entry->cdbcon = c_entry->cdbf.init(&c_entry->cachedb_url); - if (c_entry->cdbcon == NULL) { + new_db_hdls->cdbcon = new_db_hdls->cdbf.init(&c_entry->cachedb_url); + if (new_db_hdls->cdbcon == NULL) { LM_ERR("Cannot init connection to cachedb\n"); - return -1; + return NULL; } /* setting and geting a test key in cachedb */ - if (c_entry->cdbf.set(c_entry->cdbcon, &cdb_test_key, &cdb_test_val, 0) < 0) { + if (new_db_hdls->cdbf.set(new_db_hdls->cdbcon, &cdb_test_key, &cdb_test_val, 0) < 0) { LM_ERR("Failed to set test key in cachedb\n"); - c_entry->cdbf.destroy(c_entry->cdbcon); - c_entry->cdbcon = 0; - return -1; + new_db_hdls->cdbf.destroy(new_db_hdls->cdbcon); + new_db_hdls->cdbcon = 0; + return NULL; } - if (c_entry->cdbf.get(c_entry->cdbcon, &cdb_test_key, &cachedb_res) < 0) { + if (new_db_hdls->cdbf.get(new_db_hdls->cdbcon, &cdb_test_key, &cachedb_res) < 0) { LM_ERR("Failed to get test key from cachedb\n"); - c_entry->cdbf.destroy(c_entry->cdbcon); - c_entry->cdbcon = 0; - return -1; + new_db_hdls->cdbf.destroy(new_db_hdls->cdbcon); + new_db_hdls->cdbcon = 0; + return NULL; } if (str_strcmp(&cachedb_res, &cdb_test_val) != 0) { LM_ERR("Cachedb inconsistent test key\n"); - c_entry->cdbf.destroy(c_entry->cdbcon); - c_entry->cdbcon = 0; - return -1; + new_db_hdls->cdbf.destroy(new_db_hdls->cdbcon); + new_db_hdls->cdbcon = 0; + return NULL; } /* SQL DB init and test connection */ - if (db_bind_mod(&c_entry->db_url, &c_entry->db_funcs) < 0){ + if (db_bind_mod(&c_entry->db_url, &new_db_hdls->db_funcs) < 0){ LM_ERR("Unable to bind to a SQL database driver\n"); - return -1; + return NULL; } /* open a test connection */ - if ((c_entry->db_con = c_entry->db_funcs.init(&c_entry->db_url)) == 0) { + if ((new_db_hdls->db_con = new_db_hdls->db_funcs.init(&c_entry->db_url)) == 0) { LM_ERR("Cannot init connection to SQL DB\n"); - return -1; + return NULL; } /* verify the column names by running a test query with a bogus key */ - if (c_entry->db_funcs.use_table(c_entry->db_con, &c_entry->table) < 0) { + if (new_db_hdls->db_funcs.use_table(new_db_hdls->db_con, &c_entry->table) < 0) { LM_ERR("Invalid table name\n"); - c_entry->db_funcs.close(c_entry->db_con); - c_entry->db_con = 0; - return -1; + new_db_hdls->db_funcs.close(new_db_hdls->db_con); + new_db_hdls->db_con = 0; + return NULL; } VAL_NULL(&query_key_val) = 0; @@ -548,27 +559,27 @@ static int db_init_test_conn(cache_entry_t *c_entry) { query_cols = pkg_malloc(c_entry->nr_columns * sizeof(db_key_t)); if (!query_cols) { LM_ERR("No more pkg memory\n"); - c_entry->db_funcs.close(c_entry->db_con); - c_entry->db_con = 0; - return -1; + new_db_hdls->db_funcs.close(new_db_hdls->db_con); + new_db_hdls->db_con = 0; + return NULL; } for (i = 0; i < c_entry->nr_columns; i++) query_cols[i] = &(c_entry->columns[i]); - if (c_entry->db_funcs.query(c_entry->db_con, &query_key_col, 0, &query_key_val, + if (new_db_hdls->db_funcs.query(new_db_hdls->db_con, &query_key_col, 0, &query_key_val, query_cols, 1, c_entry->nr_columns, 0, &sql_res) != 0) { LM_ERR("Failure to issuse test query to SQL DB\n"); - c_entry->db_funcs.close(c_entry->db_con); - c_entry->db_con = 0; - return -1; + new_db_hdls->db_funcs.close(new_db_hdls->db_con); + new_db_hdls->db_con = 0; + return NULL; } - c_entry->db_funcs.free_result(c_entry->db_con, sql_res); - return 0; + new_db_hdls->db_funcs.free_result(new_db_hdls->db_con, sql_res); + return new_db_hdls; } -static int load_entire_table(cache_entry_t *c_entry) { +static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls) { db_key_t *query_cols = NULL; db_res_t *sql_res = NULL; db_row_t *row; @@ -586,25 +597,25 @@ static int load_entire_table(cache_entry_t *c_entry) { } /* query the entire table */ - if (c_entry->db_funcs.use_table(c_entry->db_con, &c_entry->table) < 0) { + if (db_hdls->db_funcs.use_table(db_hdls->db_con, &c_entry->table) < 0) { LM_ERR("Invalid table name\n"); - c_entry->db_funcs.close(c_entry->db_con); - c_entry->db_con = 0; + db_hdls->db_funcs.close(db_hdls->db_con); + db_hdls->db_con = 0; return -1; } - if (DB_CAPABILITY(c_entry->db_funcs, DB_CAP_FETCH)) { - if (c_entry->db_funcs.query(c_entry->db_con, NULL, 0, NULL, + if (DB_CAPABILITY(db_hdls->db_funcs, DB_CAP_FETCH)) { + if (db_hdls->db_funcs.query(db_hdls->db_con, NULL, 0, NULL, query_cols, 0, c_entry->nr_columns + 1, 0, 0) != 0) { LM_ERR("Failure to issue query to SQL DB\n"); goto error; } - if (c_entry->db_funcs.fetch_result(c_entry->db_con,&sql_res,fetch_nr_rows)<0) { + if (db_hdls->db_funcs.fetch_result(db_hdls->db_con,&sql_res,fetch_nr_rows)<0) { LM_ERR("Error fetching rows\n"); goto error; } } else { - if (c_entry->db_funcs.query(c_entry->db_con, NULL, 0, NULL, + if (db_hdls->db_funcs.query(db_hdls->db_con, NULL, 0, NULL, query_cols, 0, c_entry->nr_columns + 1, 0, &sql_res) != 0) { LM_ERR("Failure to issue query to SQL DB\n"); goto error; @@ -628,12 +639,12 @@ static int load_entire_table(cache_entry_t *c_entry) { row = RES_ROWS(sql_res) + i; values = ROW_VALUES(row); if (!VAL_NULL(values)) { - insert_in_cachedb(c_entry, values ,values + 1, ROW_N(row) - 1); + insert_in_cachedb(c_entry, db_hdls, values ,values + 1, ROW_N(row) - 1); } } - if (DB_CAPABILITY(c_entry->db_funcs, DB_CAP_FETCH)) { - if (c_entry->db_funcs.fetch_result(c_entry->db_con,&sql_res,fetch_nr_rows)<0) { + if (DB_CAPABILITY(db_hdls->db_funcs, DB_CAP_FETCH)) { + if (db_hdls->db_funcs.fetch_result(db_hdls->db_con,&sql_res,fetch_nr_rows)<0) { LM_ERR("Error fetching rows (1)\n"); goto error; } @@ -642,16 +653,17 @@ static int load_entire_table(cache_entry_t *c_entry) { } } while (RES_ROW_N(sql_res) > 0); - c_entry->db_funcs.free_result(c_entry->db_con, sql_res); + db_hdls->db_funcs.free_result(db_hdls->db_con, sql_res); return 0; error: if (sql_res) - c_entry->db_funcs.free_result(c_entry->db_con, sql_res); + db_hdls->db_funcs.free_result(db_hdls->db_con, sql_res); return -1; } static int mod_init(void) { cache_entry_t *c_entry; + db_handlers_t *db_hdls; LM_NOTICE("initializing module......\n"); @@ -704,39 +716,41 @@ static int mod_init(void) { } for (c_entry = *entry_list; c_entry != NULL; c_entry = c_entry->next) { - if (db_init_test_conn(c_entry) < 0) + if ((db_hdls = db_init_test_conn(c_entry)) == NULL) continue; /* cache the entire table if on demand is not set*/ if (!c_entry->on_demand) { c_entry->expire = 0; - if (load_entire_table(c_entry) < 0) + if (load_entire_table(c_entry, db_hdls) < 0) LM_ERR("Failed to cache the entire table %s\n", c_entry->table.s); else LM_DBG("Cached the entire table %s\n", c_entry->table.s); } - c_entry->db_funcs.close(c_entry->db_con); - c_entry->db_con = 0; - c_entry->cdbf.destroy(c_entry->cdbcon); - c_entry->cdbcon = 0; + db_hdls->db_funcs.close(db_hdls->db_con); + db_hdls->db_con = 0; + db_hdls->cdbf.destroy(db_hdls->cdbcon); + db_hdls->cdbcon = 0; } return 0; } static int child_init(int rank) { + db_handlers_t *db_hdls; cache_entry_t *c_entry; - for (c_entry = *entry_list; c_entry != NULL; c_entry = c_entry->next) { - c_entry->cdbcon = c_entry->cdbf.init(&c_entry->cachedb_url); - if (c_entry->cdbcon == NULL) { + for (db_hdls = db_hdls_list, c_entry = *entry_list; db_hdls != NULL; + db_hdls = db_hdls->next, c_entry = c_entry->next) { + db_hdls->cdbcon = db_hdls->cdbf.init(&c_entry->cachedb_url); + if (db_hdls->cdbcon == NULL) { LM_ERR("Cannot connect to cachedb from child\n"); return -1; } if (c_entry->on_demand && - (c_entry->db_con = c_entry->db_funcs.init(&c_entry->db_url)) == 0) { + (db_hdls->db_con = db_hdls->db_funcs.init(&c_entry->db_url)) == 0) { LM_ERR("Cannot connect to SQL DB from child\n"); return -1; } @@ -762,7 +776,7 @@ static int cdb_fetch(pv_name_fix_t *pv_name, str *cdb_res) { memcpy(cdb_key.s, pv_name->id.s, pv_name->id.len); memcpy(cdb_key.s + pv_name->id.len, pv_name->key.s, pv_name->key.len); - return pv_name->c_entry->cdbf.get(pv_name->c_entry->cdbcon, &cdb_key, cdb_res); + return pv_name->db_hdls->cdbf.get(pv_name->db_hdls->cdbcon, &cdb_key, cdb_res); } /* return: @@ -920,7 +934,6 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in if (pv_name->last_str == -1) optimize_cdb_decode(pv_name); - return cdb_val_decode(pv_name, cdb_res, str_res, int_res); } else { it = it->next; @@ -945,7 +958,6 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in LM_ERR("Failed to init wait_sql_query lock\n"); return -1; } - new_key->next = NULL; if (*queries_in_progress != NULL) new_key->next = *queries_in_progress; @@ -968,14 +980,13 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in VAL_TYPE(&key_val) = DB_STR; VAL_STR(&key_val) = pv_name->key; - if (pv_name->c_entry->db_funcs.use_table(pv_name->c_entry->db_con, &pv_name->c_entry->table) < 0) { + if (pv_name->db_hdls->db_funcs.use_table(pv_name->db_hdls->db_con, &pv_name->c_entry->table) < 0) { LM_ERR("Invalid table name\n"); - pv_name->c_entry->db_funcs.close(pv_name->c_entry->db_con); - pv_name->c_entry->db_con = 0; + pv_name->db_hdls->db_funcs.close(pv_name->db_hdls->db_con); + pv_name->db_hdls->db_con = 0; return -1; } - - if (pv_name->c_entry->db_funcs.query(pv_name->c_entry->db_con, + if (pv_name->db_hdls->db_funcs.query(pv_name->db_hdls->db_con, &key_col, 0, &key_val, query_cols, 1, pv_name->c_entry->nr_columns, 0, &sql_res) != 0) { LM_ERR("Failure to issue query to SQL DB\n"); @@ -988,13 +999,13 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in } row = RES_ROWS(sql_res); values = ROW_VALUES(row); + if (pv_name->c_entry->nr_ints + pv_name->c_entry->nr_strs == 0 && get_column_types(pv_name->c_entry, values, ROW_N(row)) < 0) { LM_ERR("SQL column has unsupported type\n"); goto sql_error; } - - insert_in_cachedb(pv_name->c_entry, &key_val, values, ROW_N(row)); + insert_in_cachedb(pv_name->c_entry, pv_name->db_hdls, &key_val, values, ROW_N(row)); lock_get(queries_lock); @@ -1016,7 +1027,6 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in if (VAL_NULL(values + pv_name->col_nr)) return 1; - val_type = VAL_TYPE(values + pv_name->col_nr); switch (val_type) { case DB_STRING: @@ -1040,14 +1050,13 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in return -1; } - pv_name->c_entry->db_funcs.free_result(pv_name->c_entry->db_con, sql_res); + pv_name->db_hdls->db_funcs.free_result(pv_name->db_hdls->db_con, sql_res); return 0; } - sql_error: if (sql_res) - pv_name->c_entry->db_funcs.free_result(pv_name->c_entry->db_con, sql_res); + pv_name->db_hdls->db_funcs.free_result(pv_name->db_hdls->db_con, sql_res); return -1; } @@ -1145,7 +1154,8 @@ int pv_parse_name(pv_spec_p sp, str *in) { int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t *res) { pv_name_fix_t *pv_name; str name_s; - cache_entry_t *it; + cache_entry_t *it_entries; + db_handlers_t *it_db; int rc, rc2; char *ch = NULL; long long one = 1; @@ -1177,12 +1187,14 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t } if (!pv_name->c_entry) { - for (it = *entry_list; it != NULL; it = it->next) - if (!memcmp(it->id.s, pv_name->id.s, pv_name->id.len)) { - pv_name->c_entry = it; + for (it_entries = *entry_list, it_db = db_hdls_list; it_entries != NULL; + it_entries = it_entries->next, it_db = it_db->next) + if (!memcmp(it_entries->id.s, pv_name->id.s, pv_name->id.len)) { + pv_name->c_entry = it_entries; + pv_name->db_hdls = it_db; break; } - if (!it) { + if (!it_entries) { LM_WARN("Unknown caching id %.*s\n", pv_name->id.len, pv_name->id.s); return pv_get_null(msg, param, res); } @@ -1247,7 +1259,15 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t } static void destroy(void) { - LM_NOTICE("destroying module ...\n"); + db_handlers_t *db_hdls; + + for(db_hdls = db_hdls_list; db_hdls != NULL; db_hdls = db_hdls->next) { + if (db_hdls->cdbcon) + db_hdls->cdbf.destroy(db_hdls->cdbcon); + if (db_hdls->db_con) + db_hdls->db_funcs.close(db_hdls->db_con); + } + lock_destroy(queries_lock); lock_dealloc(queries_lock); } \ No newline at end of file diff --git a/modules/sql_cacher/sql_cacher.h b/modules/sql_cacher/sql_cacher.h index 6079d9577ff..95e6997c68d 100644 --- a/modules/sql_cacher/sql_cacher.h +++ b/modules/sql_cacher/sql_cacher.h @@ -44,12 +44,17 @@ typedef struct _cache_entry { unsigned int expire; unsigned int nr_ints, nr_strs; long long column_types; + struct _cache_entry *next; +} cache_entry_t; + +typedef struct _db_handlers { + cache_entry_t *c_entry; db_func_t db_funcs; db_con_t *db_con; cachedb_funcs cdbf; cachedb_con *cdbcon; - struct _cache_entry *next; -} cache_entry_t; + struct _db_handlers *next; +} db_handlers_t; struct parse_entry { str to_parse_str; @@ -69,6 +74,7 @@ typedef struct _pv_name_fix str col; str key; cache_entry_t *c_entry; + db_handlers_t *db_hdls; pv_elem_t *pv_elem_list; int col_offset; int col_nr; From f59d4bb43275f4161240b81bfd271741ed28fdcc Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Tue, 22 Sep 2015 18:41:14 +0300 Subject: [PATCH 08/15] improve on demand loading --- modules/sql_cacher/sql_cacher.c | 59 +++++++++++++++++++++++++++------ modules/sql_cacher/sql_cacher.h | 1 + 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/modules/sql_cacher/sql_cacher.c b/modules/sql_cacher/sql_cacher.c index 5ad4d541755..acbbbde021b 100644 --- a/modules/sql_cacher/sql_cacher.c +++ b/modules/sql_cacher/sql_cacher.c @@ -496,6 +496,7 @@ static db_handlers_t *db_init_test_conn(cache_entry_t *c_entry) { } new_db_hdls->c_entry = c_entry; new_db_hdls->db_con = 0; + new_db_hdls->query_ps = NULL; new_db_hdls->cdbcon = 0; new_db_hdls->next = db_hdls_list; db_hdls_list = new_db_hdls; @@ -766,6 +767,7 @@ static int child_init(int rank) { */ static int cdb_fetch(pv_name_fix_t *pv_name, str *cdb_res) { str cdb_key; + int rc; cdb_key.len = pv_name->id.len + pv_name->key.len; cdb_key.s = pkg_malloc(cdb_key.len); @@ -776,7 +778,9 @@ static int cdb_fetch(pv_name_fix_t *pv_name, str *cdb_res) { memcpy(cdb_key.s, pv_name->id.s, pv_name->id.len); memcpy(cdb_key.s + pv_name->id.len, pv_name->key.s, pv_name->key.len); - return pv_name->db_hdls->cdbf.get(pv_name->db_hdls->cdbcon, &cdb_key, cdb_res); + rc = pv_name->db_hdls->cdbf.get(pv_name->db_hdls->cdbcon, &cdb_key, cdb_res); + pkg_free(cdb_key.s); + return rc; } /* return: @@ -878,10 +882,11 @@ static void optimize_cdb_decode(pv_name_fix_t *pv_name) { * 0 - succes * 1 - succes, null value in db * -1 - error +* -2 - not found in sql db */ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, int *int_res) { struct queried_key *it, *prev = NULL, *tmp, *new_key; - str src_key; + str src_key, null_val; db_key_t *query_cols = NULL, key_col; db_res_t *sql_res = NULL; db_row_t *row; @@ -889,6 +894,16 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in db_type_t val_type; int i; + for (i = 0; i < pv_name->c_entry->nr_columns; i++) + if (!memcmp(pv_name->c_entry->columns[i].s, pv_name->col.s, pv_name->col.len)) { + pv_name->col_nr = i; + break; + } + if (i == pv_name->c_entry->nr_columns) { + LM_WARN("Unknown column %.*s\n", pv_name->col.len, pv_name->col.s); + return -1; + } + src_key.len = pv_name->id.len + pv_name->key.len; src_key.s = shm_malloc(src_key.len); if (!src_key.s) { @@ -908,6 +923,7 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in /* wait for the query to complete */ lock_get(it->wait_sql_query); lock_get(queries_lock); + shm_free(src_key.s); if (it->nr_waiting_procs == 1) { lock_release(it->wait_sql_query); lock_destroy(it->wait_sql_query); @@ -945,6 +961,8 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in /* insert key in list */ new_key = shm_malloc(sizeof(struct queried_key)); if (!new_key) { + LM_ERR("No more shm memory\n"); + lock_release(queries_lock); return -1; } new_key->key = src_key; @@ -952,10 +970,12 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in new_key->wait_sql_query = lock_alloc(); if (!new_key->wait_sql_query) { LM_ERR("No more memory for wait_sql_query lock\n"); + lock_release(queries_lock); return -1; } if (!lock_init(new_key->wait_sql_query)) { LM_ERR("Failed to init wait_sql_query lock\n"); + lock_release(queries_lock); return -1; } new_key->next = NULL; @@ -971,6 +991,7 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in query_cols = pkg_malloc(pv_name->c_entry->nr_columns * sizeof(db_key_t)); if (!query_cols) { LM_ERR("No more pkg memory\n"); + lock_release(new_key->wait_sql_query); return -1; } for (i=0; i < pv_name->c_entry->nr_columns; i++) @@ -984,19 +1005,32 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in LM_ERR("Invalid table name\n"); pv_name->db_hdls->db_funcs.close(pv_name->db_hdls->db_con); pv_name->db_hdls->db_con = 0; + lock_release(new_key->wait_sql_query); return -1; } + CON_PS_REFERENCE(pv_name->db_hdls->db_con) = &pv_name->db_hdls->query_ps; if (pv_name->db_hdls->db_funcs.query(pv_name->db_hdls->db_con, &key_col, 0, &key_val, query_cols, 1, pv_name->c_entry->nr_columns, 0, &sql_res) != 0) { LM_ERR("Failure to issue query to SQL DB\n"); goto sql_error; } - - if (RES_ROW_N(sql_res) != 1) { - LM_ERR("Only one row should be loaded!\n"); + pkg_free(query_cols); + + if (RES_ROW_N(sql_res) == 0) { + LM_DBG("key %.*s not found in SQL db\n", pv_name->key.len, pv_name->key.s); + null_val.len = 0; + null_val.s = NULL; + if (pv_name->db_hdls->cdbf.set(pv_name->db_hdls->cdbcon, &src_key, &null_val, pv_name->c_entry->expire) < 0) { + LM_ERR("Failed to insert null in cachedb\n"); + goto sql_error; + } + return -2; + } else if (RES_ROW_N(sql_res) > 1) { + LM_ERR("To many columns returned\n"); goto sql_error; } + row = RES_ROWS(sql_res); values = ROW_VALUES(row); @@ -1016,15 +1050,12 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in lock_destroy(new_key->wait_sql_query); lock_dealloc(new_key->wait_sql_query); *queries_in_progress = new_key->next; + shm_free(new_key->key.s); shm_free(new_key); } lock_release(queries_lock); - for (i = 0; i < pv_name->c_entry->nr_columns; i++) - if (!memcmp(pv_name->c_entry->columns[i].s, pv_name->col.s, pv_name->col.len)) - pv_name->col_nr = i; - if (VAL_NULL(values + pv_name->col_nr)) return 1; val_type = VAL_TYPE(values + pv_name->col_nr); @@ -1057,6 +1088,7 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in sql_error: if (sql_res) pv_name->db_hdls->db_funcs.free_result(pv_name->db_hdls->db_con, sql_res); + lock_release(new_key->wait_sql_query); return -1; } @@ -1208,7 +1240,7 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t if (!pv_name->c_entry->on_demand) { if (rc == -2) { - LM_DBG("key: %.*s not found\n", pv_name->key.len, pv_name->key.s); + LM_DBG("key %.*s not found in SQL db\n", pv_name->key.len, pv_name->key.s); return pv_get_null(msg, param, res); } else { if (pv_name->last_str == -1) @@ -1224,13 +1256,18 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t } else { if (rc == -2) { rc2 = on_demand_load(pv_name, &cdb_res, &str_res, &int_res); - if (rc2 == -1) + if (rc2 == -1 || rc2 == -2) return pv_get_null(msg, param, res); if (rc2 == 1) { LM_WARN("NULL value in SQL db\n"); return pv_get_null(msg, param, res); } } else { + if (!cdb_res.len || !cdb_res.s) { + LM_DBG("key %.*s already searched and not found in SQL db\n", pv_name->key.len, pv_name->key.s); + return pv_get_null(msg, param, res); + } + if (pv_name->last_str == -1) optimize_cdb_decode(pv_name); rc2 = cdb_val_decode(pv_name, &cdb_res, &str_res, &int_res); diff --git a/modules/sql_cacher/sql_cacher.h b/modules/sql_cacher/sql_cacher.h index 95e6997c68d..c623f477da1 100644 --- a/modules/sql_cacher/sql_cacher.h +++ b/modules/sql_cacher/sql_cacher.h @@ -51,6 +51,7 @@ typedef struct _db_handlers { cache_entry_t *c_entry; db_func_t db_funcs; db_con_t *db_con; + db_ps_t query_ps; cachedb_funcs cdbf; cachedb_con *cdbcon; struct _db_handlers *next; From 0f8540ed26bb772f226e66c04a08f8aa026b006a Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Fri, 25 Sep 2015 18:53:47 +0300 Subject: [PATCH 09/15] add reload version int in cachedb for full caching reload --- modules/sql_cacher/sql_cacher.c | 181 +++++++++++++++++++++++--------- modules/sql_cacher/sql_cacher.h | 4 +- 2 files changed, 137 insertions(+), 48 deletions(-) diff --git a/modules/sql_cacher/sql_cacher.c b/modules/sql_cacher/sql_cacher.c index acbbbde021b..3e2b7dce018 100644 --- a/modules/sql_cacher/sql_cacher.c +++ b/modules/sql_cacher/sql_cacher.c @@ -4,6 +4,7 @@ #include "../../ut.h" #include "../../pvar.h" #include "../../locking.h" +#include "../../timer.h" #include "sql_cacher.h" static int mod_init(void); @@ -19,6 +20,8 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t static str spec_delimiter; static str pvar_delimiter; static int fetch_nr_rows = DEFAULT_FETCH_NR_ROWS; +static int full_caching_expire = DEFAULT_FULL_CACHING_EXPIRE; +static int reload_interval = DEFAULT_RELOAD_INTERVAL; static cache_entry_t **entry_list; static struct parse_entry *to_parse_list = NULL; @@ -33,6 +36,8 @@ static param_export_t mod_params[] = { {"spec_delimiter", STR_PARAM, &spec_delimiter.s}, {"pvar_delimiter", STR_PARAM, &pvar_delimiter.s}, {"sql_fetch_nr_rows", INT_PARAM, &fetch_nr_rows}, + {"full_caching_expire", INT_PARAM, &full_caching_expire}, + {"reload_interval", INT_PARAM, &reload_interval}, {"cache_table", STR_PARAM|USE_FUNC_PARAM, (void *)&cache_new_table}, {0,0,0} }; @@ -110,7 +115,7 @@ static int parse_cache_entries(void) { new_entry->columns = NULL; new_entry->nr_columns = 0; new_entry->on_demand = 0; - new_entry->expire = DEFAULT_CACHEDB_EXPIRE; + new_entry->expire = DEFAULT_ON_DEMAND_EXPIRE; new_entry->nr_ints = 0; new_entry->nr_strs = 0; new_entry->column_types = 0; @@ -327,8 +332,8 @@ static unsigned int cdb_val_total_len(cache_entry_t *c_entry, db_val_t *values, unsigned int i, len = 0; db_type_t val_type; - /* length of the integer values and the offsets of the string values(also stored as integers) */ - len = c_entry->nr_ints * INT_B64_ENC_LEN + c_entry->nr_strs * INT_B64_ENC_LEN; + /* reload version + integer values + offsets of the string values */ + len = INT_B64_ENC_LEN + c_entry->nr_ints*INT_B64_ENC_LEN + c_entry->nr_strs*INT_B64_ENC_LEN; /* length of the actual string values*/ for (i = 0; i < nr_columns; i++) { val_type = VAL_TYPE(values + i); @@ -346,7 +351,7 @@ static unsigned int cdb_val_total_len(cache_entry_t *c_entry, db_val_t *values, return len; } -static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_val_t *key, db_val_t *values, int nr_columns) { +static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_val_t *key, db_val_t *values, int reload_version, int nr_columns) { unsigned int i, offset = 0, strs_offset = 0; int int_val; int int_key_len = 0; @@ -365,7 +370,14 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_ return -1; } - /* store the integer values first (base64 encoded) */ + /* store the reload version (base64 encoded) */ + memcpy(int_buf, &reload_version, 4); + base64encode((unsigned char *)int_enc_buf, (unsigned char *)int_buf, 4); + memcpy(cdb_val.s, int_enc_buf, INT_B64_ENC_LEN); + + offset += INT_B64_ENC_LEN; + + /* store the integer values (base64 encoded) */ for (i = 0; i < nr_columns; i++) { int_val = 0; val_type = VAL_TYPE(values + i); @@ -382,7 +394,6 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_ break; default: continue; } - if (VAL_NULL(values + i)) { memset(int_enc_buf, 0, INT_B64_ENC_LEN); } else { @@ -392,7 +403,6 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_ memcpy(cdb_val.s + offset, int_enc_buf, INT_B64_ENC_LEN); - memset(int_enc_buf, 0, INT_B64_ENC_LEN); offset += INT_B64_ENC_LEN; } @@ -401,7 +411,6 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_ for (i = 0; i < nr_columns; i++) { val_type = VAL_TYPE(values + i); - switch (val_type) { case DB_STRING: str_val.s = (char *)VAL_STRING(values + i); @@ -412,7 +421,6 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_ break; default: continue; } - if (VAL_NULL(values + i)) { int_val = 0; } @@ -423,7 +431,6 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_ base64encode((unsigned char *)int_enc_buf, (unsigned char *)int_buf, 4); memcpy(cdb_val.s + offset, int_enc_buf, INT_B64_ENC_LEN); - memset(int_enc_buf, 0, INT_B64_ENC_LEN); offset += INT_B64_ENC_LEN; memcpy(cdb_val.s + strs_offset, str_val.s, str_val.len); @@ -580,7 +587,7 @@ static db_handlers_t *db_init_test_conn(cache_entry_t *c_entry) { return new_db_hdls; } -static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls) { +static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int reload_version) { db_key_t *query_cols = NULL; db_res_t *sql_res = NULL; db_row_t *row; @@ -640,7 +647,7 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls) { row = RES_ROWS(sql_res) + i; values = ROW_VALUES(row); if (!VAL_NULL(values)) { - insert_in_cachedb(c_entry, db_hdls, values ,values + 1, ROW_N(row) - 1); + insert_in_cachedb(c_entry, db_hdls, values ,values + 1, reload_version, ROW_N(row) - 1); } } @@ -662,9 +669,16 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls) { return -1; } +void reload_timer(unsigned int ticks, void *param) { + return; +} + static int mod_init(void) { cache_entry_t *c_entry; db_handlers_t *db_hdls; + char use_timer = 0, entry_success = 0; + str rld_vers_key; + int reload_version = -1; LM_NOTICE("initializing module......\n"); @@ -676,7 +690,6 @@ static int mod_init(void) { } spec_delimiter.s[0] = DEFAULT_SPEC_DELIM; } - if (!pvar_delimiter.s) { pvar_delimiter.s = pkg_malloc(sizeof(char)); if (!pvar_delimiter.s) { @@ -687,6 +700,15 @@ static int mod_init(void) { } else pvar_delimiter.len = strlen(pvar_delimiter.s); + if (full_caching_expire <= 0) { + full_caching_expire = DEFAULT_FULL_CACHING_EXPIRE; + LM_WARN("Invalid full_caching_expire parameter, setting default value: %d sec\n", DEFAULT_FULL_CACHING_EXPIRE); + } + if (reload_interval <= 0 || reload_interval >= full_caching_expire) { + reload_interval = DEFAULT_RELOAD_INTERVAL; + LM_WARN("Invalid reload_interval parameter, setting default value: %d sec\n", DEFAULT_RELOAD_INTERVAL); + } + entry_list = shm_malloc(sizeof(cache_entry_t*)); if (!entry_list) { LM_ERR("No more memory for cache entries list\n"); @@ -722,12 +744,32 @@ static int mod_init(void) { /* cache the entire table if on demand is not set*/ if (!c_entry->on_demand) { - c_entry->expire = 0; - if (load_entire_table(c_entry, db_hdls) < 0) + use_timer = 1; + c_entry->expire = full_caching_expire; + if (load_entire_table(c_entry, db_hdls, 0) < 0) LM_ERR("Failed to cache the entire table %s\n", c_entry->table.s); - else + else { + /* set up reload version counter for this entry in cachedb */ + rld_vers_key.len = c_entry->id.len + 5; + rld_vers_key.s = pkg_malloc(rld_vers_key.len); + if (!rld_vers_key.s) { + LM_ERR("No more pkg memory\n"); + return -1; + } + memcpy(rld_vers_key.s, c_entry->id.s, c_entry->id.len); + memcpy(rld_vers_key.s + c_entry->id.len, "_vers", 5); + + db_hdls->cdbf.add(db_hdls->cdbcon, &rld_vers_key, 1, 0, &reload_version); + db_hdls->cdbf.sub(db_hdls->cdbcon, &rld_vers_key, 1, 0, &reload_version); + if (reload_version != 0) + LM_ERR("Failed to set up reload version counter in cahchedb for " + "entry %.*s\n", c_entry->id.len, c_entry->id.s); + else + entry_success = 1; LM_DBG("Cached the entire table %s\n", c_entry->table.s); - } + } + } else + entry_success = 1; db_hdls->db_funcs.close(db_hdls->db_con); db_hdls->db_con = 0; @@ -735,6 +777,16 @@ static int mod_init(void) { db_hdls->cdbcon = 0; } + if (!entry_success) { + LM_ERR("Unable to use any cache entry\n"); + } + + if (use_timer && register_timer("sql_cacher_reload-timer", reload_timer, NULL, + full_caching_expire - reload_interval, TIMER_FLAG_DELAY_ON_DELAY) < 0) { + LM_ERR("failed to register timer\n"); + return -1; + } + return 0; } @@ -765,8 +817,9 @@ static int child_init(int rank) { * -2 - if not found * -1 - if error */ -static int cdb_fetch(pv_name_fix_t *pv_name, str *cdb_res) { +static int cdb_fetch(pv_name_fix_t *pv_name, str *cdb_res, int *entry_rld_vers) { str cdb_key; + str rld_vers_key; int rc; cdb_key.len = pv_name->id.len + pv_name->key.len; @@ -778,17 +831,35 @@ static int cdb_fetch(pv_name_fix_t *pv_name, str *cdb_res) { memcpy(cdb_key.s, pv_name->id.s, pv_name->id.len); memcpy(cdb_key.s + pv_name->id.len, pv_name->key.s, pv_name->key.len); + if (!pv_name->c_entry->on_demand) { + rld_vers_key.len = pv_name->id.len + 5; + rld_vers_key.s = pkg_malloc(rld_vers_key.len); + if (!rld_vers_key.s) { + LM_ERR("No more pkg memory\n"); + return -1; + } + memcpy(rld_vers_key.s, pv_name->id.s, pv_name->id.len); + memcpy(rld_vers_key.s + pv_name->id.len, "_vers", 5); + + if(pv_name->db_hdls->cdbf.get_counter(pv_name->db_hdls->cdbcon, + &rld_vers_key, entry_rld_vers) < 0) + return -1; + pkg_free(rld_vers_key.s); + } else + *entry_rld_vers = 0; + rc = pv_name->db_hdls->cdbf.get(pv_name->db_hdls->cdbcon, &cdb_key, cdb_res); pkg_free(cdb_key.s); return rc; } -/* return: -* 0 - succes -* 1 - succes, null value in db -* -1 - error -*/ -static int cdb_val_decode(pv_name_fix_t *pv_name, str *cdb_val, str *str_res, int *int_res) { +/* return: + * 0 - succes + * 1 - succes, null value in db + * -1 - error + * -2 - does not match reload version (old value) + */ +static int cdb_val_decode(pv_name_fix_t *pv_name, str *cdb_val, int reload_version, str *str_res, int *int_res) { long long one = 1; int int_val, next_str_off, i, rc; char int_buf[4]; @@ -799,6 +870,17 @@ static int cdb_val_decode(pv_name_fix_t *pv_name, str *cdb_val, str *str_res, in return -1; } + if (!pv_name->c_entry->on_demand) { + /* decode the reload version */ + if (base64decode((unsigned char *)int_buf, + (unsigned char *)(cdb_val->s), INT_B64_ENC_LEN) != 4) + goto error; + memcpy(&int_val, int_buf, 4); + + if (reload_version != int_val) + return -2; + } + /* null integer value in db */ if (!memcmp(cdb_val->s + pv_name->col_offset, zeroes, INT_B64_ENC_LEN)) return 1; @@ -828,7 +910,7 @@ static int cdb_val_decode(pv_name_fix_t *pv_name, str *cdb_val, str *str_res, in memcpy(&next_str_off, int_buf, 4); i++; } while (next_str_off == 0 && pv_name->col_offset + i*INT_B64_ENC_LEN < - pv_name->c_entry->nr_columns * INT_B64_ENC_LEN); + (pv_name->c_entry->nr_columns + 1) * INT_B64_ENC_LEN); if (next_str_off == 0) str_res->len = cdb_val->len - int_val; @@ -850,7 +932,7 @@ static void optimize_cdb_decode(pv_name_fix_t *pv_name) { char col_type1, col_type2; long long one = 1; - for (i = 0; i < pv_name->c_entry->nr_columns; i++) + for (i = 0; i < pv_name->c_entry->nr_columns; i++) { if (!memcmp(pv_name->c_entry->columns[i].s, pv_name->col.s, pv_name->col.len)) { pv_name->col_nr = i; @@ -862,28 +944,28 @@ static void optimize_cdb_decode(pv_name_fix_t *pv_name) { prev_cols++; } if (col_type1) { - pv_name->col_offset = pv_name->c_entry->nr_ints * INT_B64_ENC_LEN + - prev_cols * INT_B64_ENC_LEN; + pv_name->col_offset = INT_B64_ENC_LEN + + pv_name->c_entry->nr_ints*INT_B64_ENC_LEN + prev_cols*INT_B64_ENC_LEN; if (prev_cols == pv_name->c_entry->nr_strs - 1) pv_name->last_str = 1; else pv_name->last_str = 0; } else - pv_name->col_offset = prev_cols * INT_B64_ENC_LEN; + pv_name->col_offset = INT_B64_ENC_LEN + prev_cols*INT_B64_ENC_LEN; break; } - + } if (i == pv_name->c_entry->nr_columns) pv_name->col_offset = -1; } -/* return: -* 0 - succes -* 1 - succes, null value in db -* -1 - error -* -2 - not found in sql db -*/ +/* return: + * 0 - succes + * 1 - succes, null value in db + * -1 - error + * -2 - not found in sql db + */ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, int *int_res) { struct queried_key *it, *prev = NULL, *tmp, *new_key; str src_key, null_val; @@ -892,7 +974,7 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in db_row_t *row; db_val_t *values, key_val; db_type_t val_type; - int i; + int i, rld_vers_dummy; for (i = 0; i < pv_name->c_entry->nr_columns; i++) if (!memcmp(pv_name->c_entry->columns[i].s, pv_name->col.s, pv_name->col.len)) { @@ -943,14 +1025,14 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in lock_release(queries_lock); /* reload key from cachedb */ - if (cdb_fetch(pv_name, cdb_res) < 0) { + if (cdb_fetch(pv_name, cdb_res, &rld_vers_dummy) < 0) { LM_ERR("Error or missing value on retrying fetch from cachedb\n"); return -1; } if (pv_name->last_str == -1) optimize_cdb_decode(pv_name); - return cdb_val_decode(pv_name, cdb_res, str_res, int_res); + return cdb_val_decode(pv_name, cdb_res, 0, str_res, int_res); } else { it = it->next; } @@ -1039,7 +1121,7 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in LM_ERR("SQL column has unsupported type\n"); goto sql_error; } - insert_in_cachedb(pv_name->c_entry, pv_name->db_hdls, &key_val, values, ROW_N(row)); + insert_in_cachedb(pv_name->c_entry, pv_name->db_hdls, &key_val, values, 0, ROW_N(row)); lock_get(queries_lock); @@ -1188,12 +1270,11 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t str name_s; cache_entry_t *it_entries; db_handlers_t *it_db; - int rc, rc2; + int rc, rc2, int_res = 0, l = 0; char *ch = NULL; long long one = 1; - int int_res = 0, l = 0; - str str_res = {NULL, 0}; - str cdb_res; + str str_res = {NULL, 0}, cdb_res; + int entry_rld_vers; if (param == NULL || param->pvn.type != PV_NAME_PVAR || param->pvn.u.dname == NULL) { @@ -1232,7 +1313,7 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t } } - rc = cdb_fetch(pv_name, &cdb_res); + rc = cdb_fetch(pv_name, &cdb_res, &entry_rld_vers); if (rc == -1) { LM_ERR("Error fetching from cachedb\n"); return pv_get_null(msg, param, res); @@ -1245,9 +1326,14 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t } else { if (pv_name->last_str == -1) optimize_cdb_decode(pv_name); - rc2 = cdb_val_decode(pv_name, &cdb_res, &str_res, &int_res); + + rc2 = cdb_val_decode(pv_name, &cdb_res, entry_rld_vers, &str_res, &int_res); if (rc2 == -1) return pv_get_null(msg, param, res); + if (rc2 == -2) { + LM_DBG("key %.*s not found in SQL db\n", pv_name->key.len, pv_name->key.s); + return pv_get_null(msg, param, res); + } if (rc2 == 1) { LM_WARN("NULL value in SQL db\n"); return pv_get_null(msg, param, res); @@ -1270,7 +1356,8 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t if (pv_name->last_str == -1) optimize_cdb_decode(pv_name); - rc2 = cdb_val_decode(pv_name, &cdb_res, &str_res, &int_res); + + rc2 = cdb_val_decode(pv_name, &cdb_res, entry_rld_vers, &str_res, &int_res); if (rc2 == -1) return pv_get_null(msg, param, res); if (rc2 == 1) { diff --git a/modules/sql_cacher/sql_cacher.h b/modules/sql_cacher/sql_cacher.h index c623f477da1..62c7684fd74 100644 --- a/modules/sql_cacher/sql_cacher.h +++ b/modules/sql_cacher/sql_cacher.h @@ -25,7 +25,9 @@ #define EXPIRE_STR "expire" #define EXPIRE_STR_LEN 6 -#define DEFAULT_CACHEDB_EXPIRE 3600 +#define DEFAULT_ON_DEMAND_EXPIRE 3600 +#define DEFAULT_FULL_CACHING_EXPIRE 86400 /* 24h */ +#define DEFAULT_RELOAD_INTERVAL 5 #define DEFAULT_FETCH_NR_ROWS 100 #define TEST_QUERY_STR "sql_cacher_test_query_key" #define CDB_TEST_KEY_STR "sql_cacher_cdb_test_key" From 2646f0a2d1dc733c400ee9747dfb00e8326a195b Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Thu, 8 Oct 2015 12:02:42 +0300 Subject: [PATCH 10/15] add MI reload function --- modules/sql_cacher/sql_cacher.c | 430 +++++++++++++++++++++++++------- modules/sql_cacher/sql_cacher.h | 30 ++- 2 files changed, 367 insertions(+), 93 deletions(-) diff --git a/modules/sql_cacher/sql_cacher.c b/modules/sql_cacher/sql_cacher.c index 3e2b7dce018..5d7cdeb5a95 100644 --- a/modules/sql_cacher/sql_cacher.c +++ b/modules/sql_cacher/sql_cacher.c @@ -1,9 +1,35 @@ +/** + * + * Copyright (C) 2015 OpenSIPS Foundation + * + * This file is part of opensips, a free SIP server. + * + * opensips 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 + * + * opensips 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 + * + * History + * ------- + * 2015-09-xx initial version (Vlad Patrascu) +*/ + #include "../../sr_module.h" #include "../../dprint.h" #include "../../mem/mem.h" #include "../../ut.h" #include "../../pvar.h" #include "../../locking.h" +#include "../../rw_locking.h" #include "../../timer.h" #include "sql_cacher.h" @@ -17,6 +43,8 @@ int pv_parse_name(pv_spec_p sp, str *in); int pv_init_param(pv_spec_p sp, int param); int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t *res); +static struct mi_root* mi_reload(struct mi_root *cmd_tree, void *param); + static str spec_delimiter; static str pvar_delimiter; static int fetch_nr_rows = DEFAULT_FETCH_NR_ROWS; @@ -26,6 +54,7 @@ static int reload_interval = DEFAULT_RELOAD_INTERVAL; static cache_entry_t **entry_list; static struct parse_entry *to_parse_list = NULL; static struct queried_key **queries_in_progress; + /* per process db handlers corresponding to cache entries in entry_list */ static db_handlers_t *db_hdls_list = NULL; @@ -48,6 +77,11 @@ static pv_export_t mod_items[] = { { {0, 0}, 0, 0, 0, 0, 0, 0, 0 } }; +static mi_export_t mi_cmds[] = { + { "sql_cacher_reload", "reload the SQL database into the cache", mi_reload, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0} +}; + /** * module exports */ @@ -61,7 +95,7 @@ struct module_exports exports = { 0, /* exported async functions */ mod_params, /* exported parameters */ 0, /* exported statistics */ - 0, /* exported MI functions */ + mi_cmds, /* exported MI functions */ mod_items, /* exported pseudo-variables */ 0, /* extra processes */ mod_init, /* module initialization function */ @@ -88,9 +122,9 @@ static int cache_new_table(unsigned int type, void *val) { } memcpy(new_entry->to_parse_str.s, (char *)val, new_entry->to_parse_str.len); - if (!to_parse_list) { + if (!to_parse_list) to_parse_list = new_entry; - } else { + else { new_entry->next = to_parse_list; to_parse_list = new_entry; } @@ -119,6 +153,7 @@ static int parse_cache_entries(void) { new_entry->nr_ints = 0; new_entry->nr_strs = 0; new_entry->column_types = 0; + new_entry->ref_lock = NULL; #define PARSE_TOKEN(_ptr1, _ptr2, field, field_name_str, field_name_len) \ do { \ @@ -230,9 +265,9 @@ static int parse_cache_entries(void) { new_entry->columns[col_idx].s = shm_malloc(new_entry->columns[col_idx].len); memcpy(new_entry->columns[col_idx].s, c_tmp1, new_entry->columns[col_idx].len); - if (!tmp) { /* delimiter not found, reached the end of the string to parse */ + if (!tmp) /* delimiter not found, reached the end of the string to parse */ goto end_parsing; - } else { + else { p1 = tmp + 1; p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); if (!p2) @@ -244,11 +279,10 @@ static int parse_cache_entries(void) { if (!memcmp(p1, ONDEMAND_STR, ONDEMAND_STR_LEN)) { tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); str str_val; - if (!tmp) { /* delimiter not found, reached the end of the string to parse */ + if (!tmp) /* delimiter not found, reached the end of the string to parse */ str_val.len = it->to_parse_str.len - (p2 - it->to_parse_str.s + 1); - } else { + else str_val.len = tmp - p2 - 1; - } if (str_val.len <= 0) goto parse_err; @@ -256,9 +290,9 @@ static int parse_cache_entries(void) { if(str2int(&str_val, &new_entry->on_demand)) goto parse_err; - if (!tmp) { /* delimiter not found, reached the end of the string to parse */ + if (!tmp) /* delimiter not found, reached the end of the string to parse */ goto end_parsing; - } else { + else { p1 = tmp + 1; p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); if (!p2) @@ -305,6 +339,10 @@ static int get_column_types(cache_entry_t *c_entry, db_val_t *values, int nr_col long long one = 1; db_type_t val_type; + c_entry->nr_ints = 0; + c_entry->nr_strs = 0; + c_entry->column_types = 0; + for (i = 0; i < nr_columns; i++) { val_type = VAL_TYPE(values + i); switch (val_type) { @@ -394,9 +432,9 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_ break; default: continue; } - if (VAL_NULL(values + i)) { + if (VAL_NULL(values + i)) memset(int_enc_buf, 0, INT_B64_ENC_LEN); - } else { + else { memcpy(int_buf, &int_val, 4); base64encode((unsigned char *)int_enc_buf, (unsigned char *)int_buf, 4); } @@ -421,9 +459,8 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_ break; default: continue; } - if (VAL_NULL(values + i)) { + if (VAL_NULL(values + i)) int_val = 0; - } else int_val = strs_offset; @@ -540,7 +577,7 @@ static db_handlers_t *db_init_test_conn(cache_entry_t *c_entry) { } /* SQL DB init and test connection */ - if (db_bind_mod(&c_entry->db_url, &new_db_hdls->db_funcs) < 0){ + if (db_bind_mod(&c_entry->db_url, &new_db_hdls->db_funcs) < 0) { LM_ERR("Unable to bind to a SQL database driver\n"); return NULL; } @@ -600,9 +637,8 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int return -1; } query_cols[0] = &(c_entry->key); - for (i=0; i < c_entry->nr_columns; i++) { + for (i=0; i < c_entry->nr_columns; i++) query_cols[i+1] = &(c_entry->columns[i]); - } /* query the entire table */ if (db_hdls->db_funcs.use_table(db_hdls->db_con, &c_entry->table) < 0) { @@ -646,9 +682,8 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int for (i=0; i < RES_ROW_N(sql_res); i++) { row = RES_ROWS(sql_res) + i; values = ROW_VALUES(row); - if (!VAL_NULL(values)) { + if (!VAL_NULL(values)) insert_in_cachedb(c_entry, db_hdls, values ,values + 1, reload_version, ROW_N(row) - 1); - } } if (DB_CAPABILITY(db_hdls->db_funcs, DB_CAP_FETCH)) { @@ -669,8 +704,230 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int return -1; } +/* return: + * 0 - succes + * -1 - error + * -2 - not found in sql db + */ +static int load_key(cache_entry_t *c_entry, db_handlers_t *db_hdls, str key, db_val_t **values, db_res_t **sql_res) { + db_key_t *query_cols = NULL, key_col; + db_row_t *row; + db_val_t key_val; + str src_key, null_val; + int i; + + src_key.len = c_entry->id.len + key.len; + src_key.s = pkg_malloc(src_key.len); + if (!src_key.s) { + LM_ERR("No more shm memory\n"); + return -1; + } + memcpy(src_key.s, c_entry->id.s, c_entry->id.len); + memcpy(src_key.s + c_entry->id.len, key.s, key.len); + + query_cols = pkg_malloc(c_entry->nr_columns * sizeof(db_key_t)); + if (!query_cols) { + LM_ERR("No more pkg memory\n"); + return -1; + } + for (i=0; i < c_entry->nr_columns; i++) + query_cols[i] = &(c_entry->columns[i]); + key_col = &(c_entry->key); + VAL_NULL(&key_val) = 0; + VAL_TYPE(&key_val) = DB_STR; + VAL_STR(&key_val) = key; + + if (db_hdls->db_funcs.use_table(db_hdls->db_con, &c_entry->table) < 0) { + LM_ERR("Invalid table name\n"); + db_hdls->db_funcs.close(db_hdls->db_con); + db_hdls->db_con = 0; + return -1; + } + CON_PS_REFERENCE(db_hdls->db_con) = &db_hdls->query_ps; + if (db_hdls->db_funcs.query(db_hdls->db_con, + &key_col, 0, &key_val, query_cols, 1, + c_entry->nr_columns, 0, sql_res) != 0) { + LM_ERR("Failure to issue query to SQL DB\n"); + goto sql_error; + } + + pkg_free(query_cols); + + if (RES_ROW_N(*sql_res) == 0) { + LM_DBG("key %.*s not found in SQL db\n", key.len, key.s); + null_val.len = 0; + null_val.s = NULL; + if (db_hdls->cdbf.set(db_hdls->cdbcon, &src_key, &null_val, c_entry->expire) < 0) { + LM_ERR("Failed to insert null in cachedb\n"); + pkg_free(src_key.s); + goto sql_error; + } + pkg_free(src_key.s); + + db_hdls->db_funcs.free_result(db_hdls->db_con, *sql_res); + return -2; + + } else if (RES_ROW_N(*sql_res) > 1) { + LM_ERR("To many columns returned\n"); + goto sql_error; + } + + row = RES_ROWS(*sql_res); + *values = ROW_VALUES(row); + + if (c_entry->nr_ints + c_entry->nr_strs == 0 && + get_column_types(c_entry, *values, ROW_N(row)) < 0) { + LM_ERR("SQL column has unsupported type\n"); + goto sql_error; + } + insert_in_cachedb(c_entry, db_hdls, &key_val, *values, 0, ROW_N(row)); + + return 0; + +sql_error: + if (*sql_res) + db_hdls->db_funcs.free_result(db_hdls->db_con, *sql_res); + return -1; +} + void reload_timer(unsigned int ticks, void *param) { - return; + cache_entry_t *c_entry; + db_handlers_t *db_hdls; + str rld_vers_key; + int rld_vers = 0; + + for (c_entry = *entry_list, db_hdls = db_hdls_list; c_entry != NULL; + c_entry = c_entry->next, db_hdls = db_hdls->next) { + if (c_entry->on_demand) + continue; + + rld_vers_key.len = c_entry->id.len + 23; + rld_vers_key.s = pkg_malloc(rld_vers_key.len); + if (!rld_vers_key.s) { + LM_ERR("No more pkg memory\n"); + return; + } + memcpy(rld_vers_key.s, c_entry->id.s, c_entry->id.len); + memcpy(rld_vers_key.s + c_entry->id.len, "_sql_cacher_reload_vers", 23); + + lock_start_write(c_entry->ref_lock); + + if(db_hdls->cdbf.get_counter(db_hdls->cdbcon, + &rld_vers_key, &rld_vers) < 0) { + LM_ERR("Failed to get reload version integer from cachedb\n"); + pkg_free(rld_vers_key.s); + continue; + } + + if (load_entire_table(c_entry, db_hdls, rld_vers) < 0) + LM_ERR("Failed to reload table %.*s\n", c_entry->table.len, c_entry->table.s); + + lock_stop_write(c_entry->ref_lock); + + pkg_free(rld_vers_key.s); + } +} + +static struct mi_root* mi_reload(struct mi_root *root, void *param) { + struct mi_node *node; + cache_entry_t *c_entry; + db_handlers_t *db_hdls; + db_val_t *values; + db_res_t *sql_res = NULL; + struct queried_key *it; + str entry_id, key, src_key; + str rld_vers_key; + int rld_vers = 0, rc; + + /* cache entry id */ + node = root->node.kids; + if (!node || !node->value.len || !node->value.s) { + LM_ERR("no parameters received\n"); + return init_mi_tree(400, MI_SSTR(MI_MISSING_PARM)); + } + entry_id = node->value; + + for (c_entry = *entry_list, db_hdls = db_hdls_list; c_entry != NULL; + c_entry = c_entry->next, db_hdls = db_hdls->next) + if (!memcmp(entry_id.s, c_entry->id.s, entry_id.len)) + break; + if (!c_entry) { + LM_ERR("Entry %.*s not found\n", entry_id.len, entry_id.s); + return init_mi_tree(500, MI_SSTR("ERROR Cache entry not found\n")); + } + + /* key */ + node = node->next; + if (c_entry->on_demand) { + if (!node || !node->value.len || !node->value.s) { + LM_ERR("missing key parameter\n"); + return init_mi_tree(400, MI_SSTR(MI_MISSING_PARM)); + } + key = node->value; + } + + if (c_entry->on_demand) { + src_key.len = c_entry->id.len + key.len; + src_key.s = pkg_malloc(src_key.len); + if (!src_key.s) { + LM_ERR("No more shm memory\n"); + return NULL; + } + memcpy(src_key.s, c_entry->id.s, c_entry->id.len); + memcpy(src_key.s + c_entry->id.len, key.s, key.len); + + lock_get(queries_lock); + + for (it = *queries_in_progress; it != NULL; it = it->next) + if (!memcmp(it->key.s, src_key.s, src_key.len)) + break; + pkg_free(src_key.s); + if (it) { /* key is in list */ + lock_release(queries_lock); + lock_get(it->wait_sql_query); + } + + rc = load_key(c_entry, db_hdls, key, &values, &sql_res); + if (rc == 0) + db_hdls->db_funcs.free_result(db_hdls->db_con, sql_res); + + if (it) + lock_release(it->wait_sql_query); + else + lock_release(queries_lock); + + if (rc == -1) + return init_mi_tree(500, MI_SSTR("ERROR Reloading key from SQL database\n")); + else if (rc == -2) + return init_mi_tree(500, MI_SSTR("ERROR Reloading key from SQL database, key not found\n")); + + } else { + rld_vers_key.len = c_entry->id.len + 23; + rld_vers_key.s = pkg_malloc(rld_vers_key.len); + if (!rld_vers_key.s) { + LM_ERR("No more pkg memory\n"); + return NULL; + } + memcpy(rld_vers_key.s, c_entry->id.s, c_entry->id.len); + memcpy(rld_vers_key.s + c_entry->id.len, "_sql_cacher_reload_vers", 23); + + lock_start_write(c_entry->ref_lock); + + if (db_hdls->cdbf.add(db_hdls->cdbcon, &rld_vers_key, 1, 0, &rld_vers) < 0) { + LM_DBG("Failed to increment reload version integer from cachedb\n"); + return init_mi_tree(500, MI_SSTR("ERROR Reloading SQL database\n")); + } + pkg_free(rld_vers_key.s); + + if (load_entire_table(c_entry, db_hdls, rld_vers) < 0) { + LM_DBG("Failed to reload table\n"); + return init_mi_tree(500, MI_SSTR("ERROR Reloading SQL database\n")); + } + + lock_stop_write(c_entry->ref_lock); + } + + return init_mi_tree(200, MI_SSTR(MI_OK_S)); } static int mod_init(void) { @@ -680,8 +937,6 @@ static int mod_init(void) { str rld_vers_key; int reload_version = -1; - LM_NOTICE("initializing module......\n"); - if (!spec_delimiter.s) { spec_delimiter.s = pkg_malloc(sizeof(char)); if (!spec_delimiter.s) { @@ -746,18 +1001,24 @@ static int mod_init(void) { if (!c_entry->on_demand) { use_timer = 1; c_entry->expire = full_caching_expire; + c_entry->ref_lock = lock_init_rw(); + if (!c_entry->ref_lock) { + LM_ERR("Failed to init readers-writers lock\n"); + continue; + } + if (load_entire_table(c_entry, db_hdls, 0) < 0) LM_ERR("Failed to cache the entire table %s\n", c_entry->table.s); else { /* set up reload version counter for this entry in cachedb */ - rld_vers_key.len = c_entry->id.len + 5; + rld_vers_key.len = c_entry->id.len + 23; rld_vers_key.s = pkg_malloc(rld_vers_key.len); if (!rld_vers_key.s) { LM_ERR("No more pkg memory\n"); return -1; } memcpy(rld_vers_key.s, c_entry->id.s, c_entry->id.len); - memcpy(rld_vers_key.s + c_entry->id.len, "_vers", 5); + memcpy(rld_vers_key.s + c_entry->id.len, "_sql_cacher_reload_vers", 23); db_hdls->cdbf.add(db_hdls->cdbcon, &rld_vers_key, 1, 0, &reload_version); db_hdls->cdbf.sub(db_hdls->cdbcon, &rld_vers_key, 1, 0, &reload_version); @@ -778,7 +1039,8 @@ static int mod_init(void) { } if (!entry_success) { - LM_ERR("Unable to use any cache entry\n"); + LM_ERR("Failed to set up any cache entry\n"); + return -1; } if (use_timer && register_timer("sql_cacher_reload-timer", reload_timer, NULL, @@ -802,8 +1064,7 @@ static int child_init(int rank) { return -1; } - if (c_entry->on_demand && - (db_hdls->db_con = db_hdls->db_funcs.init(&c_entry->db_url)) == 0) { + if ((db_hdls->db_con = db_hdls->db_funcs.init(&c_entry->db_url)) == 0) { LM_ERR("Cannot connect to SQL DB from child\n"); return -1; } @@ -832,14 +1093,14 @@ static int cdb_fetch(pv_name_fix_t *pv_name, str *cdb_res, int *entry_rld_vers) memcpy(cdb_key.s + pv_name->id.len, pv_name->key.s, pv_name->key.len); if (!pv_name->c_entry->on_demand) { - rld_vers_key.len = pv_name->id.len + 5; + rld_vers_key.len = pv_name->id.len + 23; rld_vers_key.s = pkg_malloc(rld_vers_key.len); if (!rld_vers_key.s) { LM_ERR("No more pkg memory\n"); return -1; } memcpy(rld_vers_key.s, pv_name->id.s, pv_name->id.len); - memcpy(rld_vers_key.s + pv_name->id.len, "_vers", 5); + memcpy(rld_vers_key.s + pv_name->id.len, "_sql_cacher_reload_vers", 23); if(pv_name->db_hdls->cdbf.get_counter(pv_name->db_hdls->cdbcon, &rld_vers_key, entry_rld_vers) < 0) @@ -968,13 +1229,11 @@ static void optimize_cdb_decode(pv_name_fix_t *pv_name) { */ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, int *int_res) { struct queried_key *it, *prev = NULL, *tmp, *new_key; - str src_key, null_val; - db_key_t *query_cols = NULL, key_col; + str src_key; db_res_t *sql_res = NULL; - db_row_t *row; - db_val_t *values, key_val; + db_val_t *values; db_type_t val_type; - int i, rld_vers_dummy; + int i, rld_vers_dummy, rc; for (i = 0; i < pv_name->c_entry->nr_columns; i++) if (!memcmp(pv_name->c_entry->columns[i].s, pv_name->col.s, pv_name->col.len)) { @@ -1069,59 +1328,12 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in lock_release(queries_lock); - /* load key from sql and insert in cachedb */ - query_cols = pkg_malloc(pv_name->c_entry->nr_columns * sizeof(db_key_t)); - if (!query_cols) { - LM_ERR("No more pkg memory\n"); - lock_release(new_key->wait_sql_query); - return -1; - } - for (i=0; i < pv_name->c_entry->nr_columns; i++) - query_cols[i] = &(pv_name->c_entry->columns[i]); - key_col = &(pv_name->c_entry->key); - VAL_NULL(&key_val) = 0; - VAL_TYPE(&key_val) = DB_STR; - VAL_STR(&key_val) = pv_name->key; - - if (pv_name->db_hdls->db_funcs.use_table(pv_name->db_hdls->db_con, &pv_name->c_entry->table) < 0) { - LM_ERR("Invalid table name\n"); - pv_name->db_hdls->db_funcs.close(pv_name->db_hdls->db_con); - pv_name->db_hdls->db_con = 0; - lock_release(new_key->wait_sql_query); - return -1; - } - CON_PS_REFERENCE(pv_name->db_hdls->db_con) = &pv_name->db_hdls->query_ps; - if (pv_name->db_hdls->db_funcs.query(pv_name->db_hdls->db_con, - &key_col, 0, &key_val, query_cols, 1, - pv_name->c_entry->nr_columns, 0, &sql_res) != 0) { - LM_ERR("Failure to issue query to SQL DB\n"); - goto sql_error; - } - pkg_free(query_cols); + rc = load_key(pv_name->c_entry, pv_name->db_hdls, pv_name->key, &values, &sql_res); - if (RES_ROW_N(sql_res) == 0) { - LM_DBG("key %.*s not found in SQL db\n", pv_name->key.len, pv_name->key.s); - null_val.len = 0; - null_val.s = NULL; - if (pv_name->db_hdls->cdbf.set(pv_name->db_hdls->cdbcon, &src_key, &null_val, pv_name->c_entry->expire) < 0) { - LM_ERR("Failed to insert null in cachedb\n"); - goto sql_error; - } - return -2; - } else if (RES_ROW_N(sql_res) > 1) { - LM_ERR("To many columns returned\n"); - goto sql_error; - } - - row = RES_ROWS(sql_res); - values = ROW_VALUES(row); - - if (pv_name->c_entry->nr_ints + pv_name->c_entry->nr_strs == 0 && - get_column_types(pv_name->c_entry, values, ROW_N(row)) < 0) { - LM_ERR("SQL column has unsupported type\n"); - goto sql_error; + if (rc) { + lock_release(new_key->wait_sql_query); + return rc; } - insert_in_cachedb(pv_name->c_entry, pv_name->db_hdls, &key_val, values, 0, ROW_N(row)); lock_get(queries_lock); @@ -1140,6 +1352,7 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in if (VAL_NULL(values + pv_name->col_nr)) return 1; + val_type = VAL_TYPE(values + pv_name->col_nr); switch (val_type) { case DB_STRING: @@ -1167,10 +1380,7 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in return 0; } -sql_error: - if (sql_res) - pv_name->db_hdls->db_funcs.free_result(pv_name->db_hdls->db_con, sql_res); - lock_release(new_key->wait_sql_query); + return -1; } @@ -1313,6 +1523,9 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t } } + if (!pv_name->c_entry->on_demand) + lock_start_read(pv_name->c_entry->ref_lock); + rc = cdb_fetch(pv_name, &cdb_res, &entry_rld_vers); if (rc == -1) { LM_ERR("Error fetching from cachedb\n"); @@ -1322,12 +1535,16 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t if (!pv_name->c_entry->on_demand) { if (rc == -2) { LM_DBG("key %.*s not found in SQL db\n", pv_name->key.len, pv_name->key.s); + lock_stop_read(pv_name->c_entry->ref_lock); return pv_get_null(msg, param, res); } else { if (pv_name->last_str == -1) optimize_cdb_decode(pv_name); rc2 = cdb_val_decode(pv_name, &cdb_res, entry_rld_vers, &str_res, &int_res); + + lock_stop_read(pv_name->c_entry->ref_lock); + if (rc2 == -1) return pv_get_null(msg, param, res); if (rc2 == -2) { @@ -1350,14 +1567,14 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t } } else { if (!cdb_res.len || !cdb_res.s) { - LM_DBG("key %.*s already searched and not found in SQL db\n", pv_name->key.len, pv_name->key.s); + LM_DBG("key %.*s not found in SQL db\n", pv_name->key.len, pv_name->key.s); return pv_get_null(msg, param, res); } if (pv_name->last_str == -1) optimize_cdb_decode(pv_name); - rc2 = cdb_val_decode(pv_name, &cdb_res, entry_rld_vers, &str_res, &int_res); + rc2 = cdb_val_decode(pv_name, &cdb_res, 0, &str_res, &int_res); if (rc2 == -1) return pv_get_null(msg, param, res); if (rc2 == 1) { @@ -1384,6 +1601,9 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t static void destroy(void) { db_handlers_t *db_hdls; + struct queried_key *q_it, *q_tmp; + cache_entry_t *c_it, *c_tmp; + int i; for(db_hdls = db_hdls_list; db_hdls != NULL; db_hdls = db_hdls->next) { if (db_hdls->cdbcon) @@ -1392,6 +1612,34 @@ static void destroy(void) { db_hdls->db_funcs.close(db_hdls->db_con); } + q_it = *queries_in_progress; + while (q_it) { + q_tmp = q_it; + q_it = q_it->next; + lock_destroy(q_tmp->wait_sql_query); + lock_dealloc(q_tmp->wait_sql_query); + shm_free(q_tmp->key.s); + shm_free(q_tmp); + } + shm_free(queries_in_progress); + + c_it = *entry_list; + while (c_it) { + c_tmp = c_it; + c_it = c_it->next; + shm_free(c_tmp->id.s); + shm_free(c_tmp->db_url.s); + shm_free(c_tmp->cachedb_url.s); + shm_free(c_tmp->table.s); + shm_free(c_tmp->key.s); + for (i = 0; i < c_tmp->nr_columns; i++) + shm_free(c_tmp->columns[i].s); + shm_free(c_tmp->columns); + lock_destroy_rw(c_tmp->ref_lock); + shm_free(c_tmp); + } + shm_free(entry_list); + lock_destroy(queries_lock); lock_dealloc(queries_lock); } \ No newline at end of file diff --git a/modules/sql_cacher/sql_cacher.h b/modules/sql_cacher/sql_cacher.h index 62c7684fd74..ae38a9a7108 100644 --- a/modules/sql_cacher/sql_cacher.h +++ b/modules/sql_cacher/sql_cacher.h @@ -1,3 +1,28 @@ +/** + * + * Copyright (C) 2015 OpenSIPS Foundation + * + * This file is part of opensips, a free SIP server. + * + * opensips 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 + * + * opensips 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 + * + * History + * ------- + * 2015-09-xx initial version (Vlad Patrascu) +*/ + #ifndef _SQL_CACHER_H_ #define _SQL_CACHER_H_ @@ -26,8 +51,8 @@ #define EXPIRE_STR_LEN 6 #define DEFAULT_ON_DEMAND_EXPIRE 3600 -#define DEFAULT_FULL_CACHING_EXPIRE 86400 /* 24h */ -#define DEFAULT_RELOAD_INTERVAL 5 +#define DEFAULT_FULL_CACHING_EXPIRE 86400 /* 24h */ +#define DEFAULT_RELOAD_INTERVAL 60 #define DEFAULT_FETCH_NR_ROWS 100 #define TEST_QUERY_STR "sql_cacher_test_query_key" #define CDB_TEST_KEY_STR "sql_cacher_cdb_test_key" @@ -46,6 +71,7 @@ typedef struct _cache_entry { unsigned int expire; unsigned int nr_ints, nr_strs; long long column_types; + rw_lock_t *ref_lock; struct _cache_entry *next; } cache_entry_t; From 238d108a75d4bfc505f36760076335bc394d6713 Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Fri, 9 Oct 2015 17:46:00 +0300 Subject: [PATCH 11/15] sql_cacher: add documentation --- modules/sql_cacher/README | 232 +++++++++++++++ modules/sql_cacher/doc/sql_cacher.xml | 39 +++ modules/sql_cacher/doc/sql_cacher_admin.xml | 300 ++++++++++++++++++++ 3 files changed, 571 insertions(+) create mode 100644 modules/sql_cacher/README create mode 100644 modules/sql_cacher/doc/sql_cacher.xml create mode 100644 modules/sql_cacher/doc/sql_cacher_admin.xml diff --git a/modules/sql_cacher/README b/modules/sql_cacher/README new file mode 100644 index 00000000000..9796ad87b58 --- /dev/null +++ b/modules/sql_cacher/README @@ -0,0 +1,232 @@ +sql_cacher Module + +Robert-Vladut Patrascu + + OpenSIPS Solutions + + Copyright © 2015 www.opensips-solutions.com + __________________________________________________________ + + Table of Contents + + 1. Admin Guide + + 1.1. Overview + 1.2. Dependencies + 1.3. Exported Parameters + + 1.3.1. cache_table (string) + 1.3.2. spec_delimiter (string) + 1.3.3. pvar_delimiter (string) + 1.3.4. sql_fetch_nr_rows (integer) + 1.3.5. full_caching_expire (integer) + 1.3.6. reload_interval (integer) + + 1.4. Exported Functions + 1.5. MI Commands + + 1.5.1. sql_cacher_reload + + 1.6. Exported pseudo-variables + + 1.6.1. $sql_cached_value(id{sep}col{sep}key) + + List of Examples + + 1.1. cache_table parameter usage + 1.2. spec_delimiter parameter usage + 1.3. pvar_delimiter parameter usage + 1.4. sql_fetch_nr_rows parameter usage + 1.5. full_caching_expire parameter usage + 1.6. reload_interval parameter usage + 1.7. sql_cacher_reload usage + 1.8. sql_cached_value(id{sep}col{sep}key) pseudo-variable usage + +Chapter 1. Admin Guide + +1.1. Overview + + The sql_cacher module introduces the possibility to cache data + from a SQL-based database (using different OpenSIPS modules + which implement the DB API) into a cache system implemented in + OpenSIPS through the CacheDB Interface. This is done by + specifying the databases URLs, SQL table to be used, required + columns and other details in the OpenSIPS configuration script. + + The cached data is available in the script through the + read-only pseudovariable “$sql_cached_value” similar to a + Key-Value system. A specified column from the SQL table has the + role of “key” therefore the value of this column along with the + name of the desired column are provided as "parameters" to the + pseudovariable returning the appropriate value of the column. + + There are two types of caching available: + * full caching - the entire SQL table is loaded into the + cache at OpenSIPS startup; + * on demand - the rows of the SQL table are loaded at runtime + when certain columns from those rows are requested. + + For on demand caching, the stored values have a configurable + expire period after which they are permanently removed unless + an MI reload function is called for a specifc key. In the case + of full caching the data is automatically reloaded at a + configurable interval. Consequently if the data in the SQL + database changes and a MI reload function is called, the old + data remains in cache only until it expires. + +1.2. Dependencies + + The following modules must be loaded before this module: + * The OpenSIPS modules that offer actual database back-end + connection + +1.3. Exported Parameters + +1.3.1. cache_table (string) + + This parameter can be set multiple times in order to cache + multiple SQL tables or even the same table but with a different + configuration. The module distinguishes those different entries + by an “id” string. + + The caching entry is specified via this parameter that has it's + own subparameters. Each of those parameters are separted by a + configurable delimiter and have the following format: + + param_name=param_value + + The parameters are: + * id : cache entry id + * db_url : the URL of the SQL database + * cachedb_url : the URL of the CacheDB database + * table : SQL database table name + * key : SQL database column name of the key column + * columns : names of the desired columns from the SQL + database separated by “,” + * on_demand : specifies the type of caching: + + 0 : full caching + + 1 : on demand + If not present, default value is “0” + * expire : expire period for the values stored in the cache + for the on demand caching type in seconds + If not present, default value is “1 hour” + + Overall, the parameter does not have a default value, it must + be set at least once. + + Example 1.1. cache_table parameter usage + +modparam("sql_cacher", "cache_table", +"id=caching_name +db_url=mysql://root:opensips@localhost/opensips_2_2 +cachedb_url=mongodb:mycluster://127.0.0.1:27017/db.col +table=table_name +key=column_name_0 +columns=column_name_1,column_name_2,column_name_3 +on_demand=0") + + +1.3.2. spec_delimiter (string) + + The delimiter to be used in the caching entry specification + provided in the cache_table parameter to separate the + subparameters. It must be a single character. + + The default value is “ ”(space). + + Example 1.2. spec_delimiter parameter usage + +modparam("sql_cacher", "spec_delimiter", "\n") + + +1.3.3. pvar_delimiter (string) + + The delimiter to be used in the “$sql_cached_value” + pseudovariable to separate the value of the key and the desired + column name. It must be a single character. + + The default value is “:”. + + Example 1.3. pvar_delimiter parameter usage + +modparam("sql_cacher", "pvar_delimiter", ";") + + +1.3.4. sql_fetch_nr_rows (integer) + + The number of rows to be fetched into OpenSIPS private memory + in one chunk from the SQL database driver. When querying large + tables, adjust this parameter accordingly to avoid the filling + of OpenSIPS private memory. + + The default value is “100”. + + Example 1.4. sql_fetch_nr_rows parameter usage + +modparam("sql_cacher", "sql_fetch_nr_rows", "1000") + + +1.3.5. full_caching_expire (integer) + + Expire period for the values stored in cache for the full + caching type in seconds. This is the longest time that deleted + or modified data remains in cache. + + The default value is “24 hours”. + + Example 1.5. full_caching_expire parameter usage + +modparam("sql_cacher", "full_caching_expire", "3600") + + +1.3.6. reload_interval (integer) + + This parameter represents how many seconds before the data + expires (for full caching) the automatic reloading is triggerd. + + The default value is “60 s”. + + Example 1.6. reload_interval parameter usage + +modparam("sql_cacher", "reload_interval", "5") + + +1.4. Exported Functions + + No function exported to be used from configuration file. + +1.5. MI Commands + +1.5.1. sql_cacher_reload + + Reloads the entire SQL table in cache for full caching or the + specified key for on demand caching. + + The first parameter is the caching id. + + The second parameter must be a value of the key column from the + SQL table. For full caching this parameter is not needed. + + Example 1.7. sql_cacher_reload usage +... +$ opensipsctl fifo sql_cacher_reload caching_name +... +$ opensipsctl fifo sql_cacher_reload caching_name key +... + +1.6. Exported pseudo-variables + +1.6.1. $sql_cached_value(id{sep}col{sep}key) + + The cached data is available through this read-only PV.The + format is the following: + * sep : separator configured by “pvar_delimiter” parameter + * id : cache entry id + * col : name of the column + * key : value of the key column from the SQL table + + Example 1.8. sql_cached_value(id{sep}col{sep}key) + pseudo-variable usage +... +$avp(a) = $sql_cached_value(caching_name:column_name_1:key1); +... diff --git a/modules/sql_cacher/doc/sql_cacher.xml b/modules/sql_cacher/doc/sql_cacher.xml new file mode 100644 index 00000000000..dc19d3d8a79 --- /dev/null +++ b/modules/sql_cacher/doc/sql_cacher.xml @@ -0,0 +1,39 @@ + + + + + +%docentities; + +]> + + + + sql_cacher Module + &osipsname; + + + Robert-Vladut + Patrascu + OpenSIPS Solutions +
+ rvlad.patrascu@gmail.com + + http://www.opensips.org + +
+
+
+ + 2015 + &osipssol; + +
+ + + &admin; + +
\ No newline at end of file diff --git a/modules/sql_cacher/doc/sql_cacher_admin.xml b/modules/sql_cacher/doc/sql_cacher_admin.xml new file mode 100644 index 00000000000..fb6f37de54a --- /dev/null +++ b/modules/sql_cacher/doc/sql_cacher_admin.xml @@ -0,0 +1,300 @@ + + + + + &adminguide; + +
+ Overview + + The sql_cacher module introduces the possibility to cache data from a + SQL-based database (using different &osips; modules which implement the DB API) + into a cache system implemented in &osips; through the CacheDB Interface. + This is done by specifying the databases URLs, SQL table to be used, required + columns and other details in the &osips; configuration script. + + + The cached data is available in the script through the read-only pseudovariable + $sql_cached_value similar to a Key-Value system. A specified + column from the SQL table has the role of key therefore the value + of this column along with the name of the desired column are provided as + "parameters" to the pseudovariable returning the appropriate value of the column. + + + There are two types of caching available: + + + full caching - the entire SQL table is loaded into the + cache at &osips; startup; + + + on demand - the rows of the SQL table are loaded at runtime + when certain columns from those rows are requested. + + + + + For on demand caching, the stored values have a configurable expire period after + which they are permanently removed unless an MI reload function is called for a + specifc key. In the case of full caching the data is automatically reloaded at + a configurable interval. Consequently if the data in the SQL database changes + and a MI reload function is called, the old data remains in cache only + until it expires. + +
+
+ Dependencies + + The following modules must be loaded before this module: + + + The &osips; modules that offer actual database back-end + connection + + + +
+
+ Exported Parameters +
+ <varname>cache_table</varname> (string) + + This parameter can be set multiple times in order to cache multiple SQL + tables or even the same table but with a different configuration. The module + distinguishes those different entries by an id string. + + + The caching entry is specified via this parameter that has it's own + subparameters. Each of those parameters are separted by a configurable + delimiter and have the following format: + param_name=param_value + The parameters are: + + + id : cache entry id + + + db_url : the URL of the SQL database + + + cachedb_url : the URL of the CacheDB database + + + table : SQL database table name + + + key : SQL database column name of the key column + + + columns : names of the desired columns from the + SQL database separated by , + + + on_demand : specifies the type of caching: + + + 0 : full caching + + + 1 : on demand + + + If not present, default value is 0 + + + expire : expire period for the values stored + in the cache for the on demand caching type in seconds + If not present, default value is 1 hour + + + + + Overall, the parameter does not have a default value, it must be set at least once. + + + <varname>cache_table</varname> parameter usage + + +modparam("sql_cacher", "cache_table", +"id=caching_name +db_url=mysql://root:opensips@localhost/opensips_2_2 +cachedb_url=mongodb:mycluster://127.0.0.1:27017/db.col +table=table_name +key=column_name_0 +columns=column_name_1,column_name_2,column_name_3 +on_demand=0") + + + +
+ +
+ <varname>spec_delimiter</varname> (string) + + The delimiter to be used in the caching entry specification provided in the + cache_table parameter to separate the subparameters. It + must be a single character. + + + The default value is (space). + + + <varname>spec_delimiter</varname> parameter usage + + +modparam("sql_cacher", "spec_delimiter", "\n") + + + +
+ +
+ <varname>pvar_delimiter</varname> (string) + + The delimiter to be used in the $sql_cached_value + pseudovariable to separate the value of the key and the desired column name. It + must be a single character. + + + The default value is :. + + + <varname>pvar_delimiter</varname> parameter usage + + +modparam("sql_cacher", "pvar_delimiter", ";") + + + +
+ +
+ <varname>sql_fetch_nr_rows</varname> (integer) + + The number of rows to be fetched into &osips; private memory in one chunk from + the SQL database driver. When querying large tables, adjust this parameter + accordingly to avoid the filling of &osips; private memory. + + + The default value is 100. + + + <varname>sql_fetch_nr_rows</varname> parameter usage + + +modparam("sql_cacher", "sql_fetch_nr_rows", "1000") + + + +
+ +
+ <varname>full_caching_expire</varname> (integer) + + Expire period for the values stored in cache for the full caching type + in seconds. This is the longest time that deleted or modified data remains + in cache. + + + The default value is 24 hours. + + + <varname>full_caching_expire</varname> parameter usage + + +modparam("sql_cacher", "full_caching_expire", "3600") + + + +
+ +
+ <varname>reload_interval</varname> (integer) + + This parameter represents how many seconds before the data expires (for full caching) the + automatic reloading is triggerd. + + + The default value is 60 s. + + + <varname>reload_interval</varname> parameter usage + + +modparam("sql_cacher", "reload_interval", "5") + + + +
+
+ +
+ Exported Functions + + No function exported to be used from configuration file. + +
+ +
+ <acronym>MI</acronym> Commands +
+ <function moreinfo="none">sql_cacher_reload</function> + + Reloads the entire SQL table in cache for full caching or the specified + key for on demand caching. + + + The first parameter is the caching id. + + + The second parameter must be a value of the key column from the SQL table. + For full caching this parameter is not needed. + + + <function moreinfo="none">sql_cacher_reload</function> usage + +... +$ opensipsctl fifo sql_cacher_reload caching_name +... +$ opensipsctl fifo sql_cacher_reload caching_name key +... + + +
+
+ +
+ Exported pseudo-variables +
+ <varname>$sql_cached_value(id{sep}col{sep}key)</varname> + + The cached data is available through this read-only PV.The format + is the following: + + + sep : separator configured by + pvar_delimiter parameter + + + id : cache entry id + + + col : name of the column + + + key : value of the key column from the SQL table + + + + + <function moreinfo="none">sql_cached_value(id{sep}col{sep}key) pseudo-variable</function> usage + +... +$avp(a) = $sql_cached_value(caching_name:column_name_1:key1); +... + + +
+ +
+
\ No newline at end of file From fbccbfdd0aaa7568fef75ff2898e0b27ec20b250 Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Wed, 14 Oct 2015 13:39:21 +0300 Subject: [PATCH 12/15] add Makefile --- modules/sql_cacher/Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 modules/sql_cacher/Makefile diff --git a/modules/sql_cacher/Makefile b/modules/sql_cacher/Makefile new file mode 100644 index 00000000000..0351bf9acd0 --- /dev/null +++ b/modules/sql_cacher/Makefile @@ -0,0 +1,8 @@ +# WARNING: do not run this directly, it should be run by the master Makefile + +include ../../Makefile.defs +auto_gen= +NAME=sql_cacher.so +LIBS= + +include ../../Makefile.modules From f897e8973771c4d3354c6aa40adb1ee192a713eb Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Thu, 12 Nov 2015 16:20:56 +0200 Subject: [PATCH 13/15] sql_cacher: small improvements -improved coding style -more verbose error messages -trim whitespaces for cache entry specification -fix memory leak: free temporary cache entries list used for parsing --- modules/sql_cacher/sql_cacher.c | 194 +++++++++++++++++++------------- modules/sql_cacher/sql_cacher.h | 2 +- 2 files changed, 117 insertions(+), 79 deletions(-) diff --git a/modules/sql_cacher/sql_cacher.c b/modules/sql_cacher/sql_cacher.c index 5d7cdeb5a95..c5c7a0eef7f 100644 --- a/modules/sql_cacher/sql_cacher.c +++ b/modules/sql_cacher/sql_cacher.c @@ -27,6 +27,7 @@ #include "../../dprint.h" #include "../../mem/mem.h" #include "../../ut.h" +#include "../../trim.h" #include "../../pvar.h" #include "../../locking.h" #include "../../rw_locking.h" @@ -52,11 +53,11 @@ static int full_caching_expire = DEFAULT_FULL_CACHING_EXPIRE; static int reload_interval = DEFAULT_RELOAD_INTERVAL; static cache_entry_t **entry_list; -static struct parse_entry *to_parse_list = NULL; +static struct parse_entry *to_parse_list; static struct queried_key **queries_in_progress; /* per process db handlers corresponding to cache entries in entry_list */ -static db_handlers_t *db_hdls_list = NULL; +static db_handlers_t *db_hdls_list; gen_lock_t *queries_lock; @@ -104,7 +105,8 @@ struct module_exports exports = { child_init /* per-child init function */ }; -static int cache_new_table(unsigned int type, void *val) { +static int cache_new_table(unsigned int type, void *val) +{ struct parse_entry *new_entry; new_entry = pkg_malloc(sizeof(struct parse_entry)); @@ -132,14 +134,17 @@ static int cache_new_table(unsigned int type, void *val) { return 0; } -static int parse_cache_entries(void) { +static int parse_cache_entries(void) +{ cache_entry_t *new_entry; - struct parse_entry *it; + struct parse_entry *it, *next; char *p1, *p2, *tmp, *c_tmp1, *c_tmp2; int col_idx; int rc = -1; + str parse_str_copy; - for (it = to_parse_list; it != NULL; it = it->next) { + for (it = to_parse_list; it; it = next) { + next = it->next; new_entry = shm_malloc(sizeof(cache_entry_t)); if (!new_entry) { @@ -175,6 +180,9 @@ static int parse_cache_entries(void) { goto parse_err; \ } while (0) + parse_str_copy = it->to_parse_str; + trim(&it->to_parse_str); + /* parse the id */ p1 = it->to_parse_str.s; PARSE_TOKEN(p1, p2, id, ID_STR, ID_STR_LEN); @@ -316,17 +324,22 @@ static int parse_cache_entries(void) { goto end_parsing; parse_err: - LM_ERR("Invalid cache entry specification\n"); + LM_ERR("Invalid cache entry specification: %.*s\n", it->to_parse_str.len, it->to_parse_str.s); if (new_entry->columns) shm_free(new_entry->columns); shm_free(new_entry); + + pkg_free(parse_str_copy.s); + pkg_free(it); continue; end_parsing: new_entry->next = NULL; - if (*entry_list != NULL) + if (*entry_list) new_entry->next = *entry_list; *entry_list = new_entry; + pkg_free(parse_str_copy.s); + pkg_free(it); rc = 0; } @@ -334,7 +347,8 @@ static int parse_cache_entries(void) { } /* get the column types from the sql query result */ -static int get_column_types(cache_entry_t *c_entry, db_val_t *values, int nr_columns) { +static int get_column_types(cache_entry_t *c_entry, db_val_t *values, int nr_columns) +{ unsigned int i; long long one = 1; db_type_t val_type; @@ -365,8 +379,9 @@ static int get_column_types(cache_entry_t *c_entry, db_val_t *values, int nr_col return 0; } -/* returns the total length of the actual value which will be stored in the cachedb*/ -static unsigned int cdb_val_total_len(cache_entry_t *c_entry, db_val_t *values, int nr_columns) { +/* returns the total size of the actual value which will be stored in the cachedb*/ +static unsigned int get_cdb_val_size(cache_entry_t *c_entry, db_val_t *values, int nr_columns) +{ unsigned int i, len = 0; db_type_t val_type; @@ -389,7 +404,8 @@ static unsigned int cdb_val_total_len(cache_entry_t *c_entry, db_val_t *values, return len; } -static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_val_t *key, db_val_t *values, int reload_version, int nr_columns) { +static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_val_t *key, db_val_t *values, int reload_version, int nr_columns) +{ unsigned int i, offset = 0, strs_offset = 0; int int_val; int int_key_len = 0; @@ -401,7 +417,7 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_ str cdb_val; str cdb_key; - cdb_val.len = cdb_val_total_len(c_entry, values, nr_columns); + cdb_val.len = get_cdb_val_size(c_entry, values, nr_columns); cdb_val.s = pkg_malloc(cdb_val.len); if (!cdb_val.s) { LM_ERR("No more pkg memory\n"); @@ -514,14 +530,15 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_ memcpy(cdb_key.s + c_entry->id.len, str_key.s, str_key.len); if (db_hdls->cdbf.set(db_hdls->cdbcon, &cdb_key, &cdb_val, c_entry->expire) < 0) { - LM_ERR("Failed to insert in cachedb\n"); + LM_ERR("Failed to insert the values for key: %.*s in cachedb\n", str_key.len, str_key.s); return -1; } return 0; } -static db_handlers_t *db_init_test_conn(cache_entry_t *c_entry) { +static db_handlers_t *db_init_test_conn(cache_entry_t *c_entry) +{ db_handlers_t *new_db_hdls; str test_query_key_str = str_init(TEST_QUERY_STR); str cdb_test_key = str_init(CDB_TEST_KEY_STR); @@ -547,30 +564,35 @@ static db_handlers_t *db_init_test_conn(cache_entry_t *c_entry) { /* cachedb init and test connection */ if (cachedb_bind_mod(&c_entry->cachedb_url, &new_db_hdls->cdbf) < 0) { - LM_ERR("Unable to bind to a cachedb database driver\n"); + LM_ERR("Unable to bind to a cachedb database driver for URL: %.*s\n", + c_entry->cachedb_url.len, c_entry->cachedb_url.s); return NULL; } /* open a test connection */ new_db_hdls->cdbcon = new_db_hdls->cdbf.init(&c_entry->cachedb_url); - if (new_db_hdls->cdbcon == NULL) { - LM_ERR("Cannot init connection to cachedb\n"); + if (!new_db_hdls->cdbcon) { + LM_ERR("Cannot init connection to cachedb: %.*s\n", + c_entry->cachedb_url.len, c_entry->cachedb_url.s); return NULL; } /* setting and geting a test key in cachedb */ if (new_db_hdls->cdbf.set(new_db_hdls->cdbcon, &cdb_test_key, &cdb_test_val, 0) < 0) { - LM_ERR("Failed to set test key in cachedb\n"); + LM_ERR("Failed to set test key in cachedb: %.*s\n", + c_entry->cachedb_url.len, c_entry->cachedb_url.s); new_db_hdls->cdbf.destroy(new_db_hdls->cdbcon); new_db_hdls->cdbcon = 0; return NULL; } if (new_db_hdls->cdbf.get(new_db_hdls->cdbcon, &cdb_test_key, &cachedb_res) < 0) { - LM_ERR("Failed to get test key from cachedb\n"); + LM_ERR("Failed to get test key from cachedb: %.*s\n", + c_entry->cachedb_url.len, c_entry->cachedb_url.s); new_db_hdls->cdbf.destroy(new_db_hdls->cdbcon); new_db_hdls->cdbcon = 0; return NULL; } if (str_strcmp(&cachedb_res, &cdb_test_val) != 0) { - LM_ERR("Cachedb inconsistent test key\n"); + LM_ERR("Inconsistent test key for cachedb: %.*s\n", + c_entry->cachedb_url.len, c_entry->cachedb_url.s); new_db_hdls->cdbf.destroy(new_db_hdls->cdbcon); new_db_hdls->cdbcon = 0; return NULL; @@ -578,18 +600,20 @@ static db_handlers_t *db_init_test_conn(cache_entry_t *c_entry) { /* SQL DB init and test connection */ if (db_bind_mod(&c_entry->db_url, &new_db_hdls->db_funcs) < 0) { - LM_ERR("Unable to bind to a SQL database driver\n"); + LM_ERR("Unable to bind to a SQL database driver for URL: %.*s\n", + c_entry->db_url.len, c_entry->db_url.s); return NULL; } /* open a test connection */ if ((new_db_hdls->db_con = new_db_hdls->db_funcs.init(&c_entry->db_url)) == 0) { - LM_ERR("Cannot init connection to SQL DB\n"); + LM_ERR("Cannot init connection to SQL DB: %.*s\n", + c_entry->db_url.len, c_entry->db_url.s); return NULL; } /* verify the column names by running a test query with a bogus key */ if (new_db_hdls->db_funcs.use_table(new_db_hdls->db_con, &c_entry->table) < 0) { - LM_ERR("Invalid table name\n"); + LM_ERR("Invalid table name: %.*s\n", c_entry->table.len, c_entry->table.s); new_db_hdls->db_funcs.close(new_db_hdls->db_con); new_db_hdls->db_con = 0; return NULL; @@ -614,7 +638,8 @@ static db_handlers_t *db_init_test_conn(cache_entry_t *c_entry) { if (new_db_hdls->db_funcs.query(new_db_hdls->db_con, &query_key_col, 0, &query_key_val, query_cols, 1, c_entry->nr_columns, 0, &sql_res) != 0) { - LM_ERR("Failure to issuse test query to SQL DB\n"); + LM_ERR("Failure to issuse test query to SQL DB: %.*s\n", + c_entry->db_url.len, c_entry->db_url.s); new_db_hdls->db_funcs.close(new_db_hdls->db_con); new_db_hdls->db_con = 0; return NULL; @@ -624,7 +649,8 @@ static db_handlers_t *db_init_test_conn(cache_entry_t *c_entry) { return new_db_hdls; } -static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int reload_version) { +static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int reload_version) +{ db_key_t *query_cols = NULL; db_res_t *sql_res = NULL; db_row_t *row; @@ -642,7 +668,7 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int /* query the entire table */ if (db_hdls->db_funcs.use_table(db_hdls->db_con, &c_entry->table) < 0) { - LM_ERR("Invalid table name\n"); + LM_ERR("Invalid table name: %.*s\n", c_entry->table.len, c_entry->table.s); db_hdls->db_funcs.close(db_hdls->db_con); db_hdls->db_con = 0; return -1; @@ -650,30 +676,33 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int if (DB_CAPABILITY(db_hdls->db_funcs, DB_CAP_FETCH)) { if (db_hdls->db_funcs.query(db_hdls->db_con, NULL, 0, NULL, query_cols, 0, c_entry->nr_columns + 1, 0, 0) != 0) { - LM_ERR("Failure to issue query to SQL DB\n"); + LM_ERR("Failure to issue query to SQL DB: %.*s\n", + c_entry->db_url.len, c_entry->db_url.s); goto error; } if (db_hdls->db_funcs.fetch_result(db_hdls->db_con,&sql_res,fetch_nr_rows)<0) { - LM_ERR("Error fetching rows\n"); + LM_ERR("Error fetching rows from SQL DB: %.*s\n", + c_entry->db_url.len, c_entry->db_url.s); goto error; } } else { if (db_hdls->db_funcs.query(db_hdls->db_con, NULL, 0, NULL, query_cols, 0, c_entry->nr_columns + 1, 0, &sql_res) != 0) { - LM_ERR("Failure to issue query to SQL DB\n"); + LM_ERR("Failure to issue query to SQL DB: %.*s\n", + c_entry->db_url.len, c_entry->db_url.s); goto error; } } if (RES_ROW_N(sql_res) == 0) { - LM_DBG("Table is empty!\n"); + LM_DBG("Table: %.*s is empty!\n", c_entry->table.len, c_entry->table.s); goto error; } row = RES_ROWS(sql_res); values = ROW_VALUES(row); if (get_column_types(c_entry, values + 1, ROW_N(row) - 1) < 0) { - LM_ERR("SQL column has unsupported type\n"); + LM_ERR("One ore more SQL columns have an unsupported type\n"); goto error; } @@ -688,7 +717,8 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int if (DB_CAPABILITY(db_hdls->db_funcs, DB_CAP_FETCH)) { if (db_hdls->db_funcs.fetch_result(db_hdls->db_con,&sql_res,fetch_nr_rows)<0) { - LM_ERR("Error fetching rows (1)\n"); + LM_ERR("Error fetching rows (1) from SQL DB: %.*s\n", + c_entry->db_url.len, c_entry->db_url.s); goto error; } } else { @@ -709,7 +739,8 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int * -1 - error * -2 - not found in sql db */ -static int load_key(cache_entry_t *c_entry, db_handlers_t *db_hdls, str key, db_val_t **values, db_res_t **sql_res) { +static int load_key(cache_entry_t *c_entry, db_handlers_t *db_hdls, str key, db_val_t **values, db_res_t **sql_res) +{ db_key_t *query_cols = NULL, key_col; db_row_t *row; db_val_t key_val; @@ -754,7 +785,7 @@ static int load_key(cache_entry_t *c_entry, db_handlers_t *db_hdls, str key, db_ pkg_free(query_cols); if (RES_ROW_N(*sql_res) == 0) { - LM_DBG("key %.*s not found in SQL db\n", key.len, key.s); + LM_WARN("key %.*s not found in SQL db\n", key.len, key.s); null_val.len = 0; null_val.s = NULL; if (db_hdls->cdbf.set(db_hdls->cdbcon, &src_key, &null_val, c_entry->expire) < 0) { @@ -790,13 +821,14 @@ static int load_key(cache_entry_t *c_entry, db_handlers_t *db_hdls, str key, db_ return -1; } -void reload_timer(unsigned int ticks, void *param) { +void reload_timer(unsigned int ticks, void *param) +{ cache_entry_t *c_entry; db_handlers_t *db_hdls; str rld_vers_key; int rld_vers = 0; - for (c_entry = *entry_list, db_hdls = db_hdls_list; c_entry != NULL; + for (c_entry = *entry_list, db_hdls = db_hdls_list; c_entry; c_entry = c_entry->next, db_hdls = db_hdls->next) { if (c_entry->on_demand) continue; @@ -828,7 +860,8 @@ void reload_timer(unsigned int ticks, void *param) { } } -static struct mi_root* mi_reload(struct mi_root *root, void *param) { +static struct mi_root* mi_reload(struct mi_root *root, void *param) +{ struct mi_node *node; cache_entry_t *c_entry; db_handlers_t *db_hdls; @@ -847,7 +880,7 @@ static struct mi_root* mi_reload(struct mi_root *root, void *param) { } entry_id = node->value; - for (c_entry = *entry_list, db_hdls = db_hdls_list; c_entry != NULL; + for (c_entry = *entry_list, db_hdls = db_hdls_list; c_entry; c_entry = c_entry->next, db_hdls = db_hdls->next) if (!memcmp(entry_id.s, c_entry->id.s, entry_id.len)) break; @@ -878,7 +911,7 @@ static struct mi_root* mi_reload(struct mi_root *root, void *param) { lock_get(queries_lock); - for (it = *queries_in_progress; it != NULL; it = it->next) + for (it = *queries_in_progress; it; it = it->next) if (!memcmp(it->key.s, src_key.s, src_key.len)) break; pkg_free(src_key.s); @@ -930,21 +963,17 @@ static struct mi_root* mi_reload(struct mi_root *root, void *param) { return init_mi_tree(200, MI_SSTR(MI_OK_S)); } -static int mod_init(void) { +static int mod_init(void) +{ cache_entry_t *c_entry; db_handlers_t *db_hdls; char use_timer = 0, entry_success = 0; str rld_vers_key; int reload_version = -1; - if (!spec_delimiter.s) { - spec_delimiter.s = pkg_malloc(sizeof(char)); - if (!spec_delimiter.s) { - LM_ERR("No more memory for spec_delimiter\n"); - return -1; - } - spec_delimiter.s[0] = DEFAULT_SPEC_DELIM; - } + if (!spec_delimiter.s) + spec_delimiter.s = DEFAULT_SPEC_DELIM; + if (!pvar_delimiter.s) { pvar_delimiter.s = pkg_malloc(sizeof(char)); if (!pvar_delimiter.s) { @@ -993,7 +1022,7 @@ static int mod_init(void) { return -1; } - for (c_entry = *entry_list; c_entry != NULL; c_entry = c_entry->next) { + for (c_entry = *entry_list; c_entry; c_entry = c_entry->next) { if ((db_hdls = db_init_test_conn(c_entry)) == NULL) continue; @@ -1008,7 +1037,7 @@ static int mod_init(void) { } if (load_entire_table(c_entry, db_hdls, 0) < 0) - LM_ERR("Failed to cache the entire table %s\n", c_entry->table.s); + LM_ERR("Failed to cache the entire table: %s\n", c_entry->table.s); else { /* set up reload version counter for this entry in cachedb */ rld_vers_key.len = c_entry->id.len + 23; @@ -1052,14 +1081,15 @@ static int mod_init(void) { return 0; } -static int child_init(int rank) { +static int child_init(int rank) +{ db_handlers_t *db_hdls; cache_entry_t *c_entry; - for (db_hdls = db_hdls_list, c_entry = *entry_list; db_hdls != NULL; + for (db_hdls = db_hdls_list, c_entry = *entry_list; db_hdls; db_hdls = db_hdls->next, c_entry = c_entry->next) { db_hdls->cdbcon = db_hdls->cdbf.init(&c_entry->cachedb_url); - if (db_hdls->cdbcon == NULL) { + if (!db_hdls->cdbcon) { LM_ERR("Cannot connect to cachedb from child\n"); return -1; } @@ -1078,7 +1108,8 @@ static int child_init(int rank) { * -2 - if not found * -1 - if error */ -static int cdb_fetch(pv_name_fix_t *pv_name, str *cdb_res, int *entry_rld_vers) { +static int cdb_fetch(pv_name_fix_t *pv_name, str *cdb_res, int *entry_rld_vers) +{ str cdb_key; str rld_vers_key; int rc; @@ -1120,7 +1151,8 @@ static int cdb_fetch(pv_name_fix_t *pv_name, str *cdb_res, int *entry_rld_vers) * -1 - error * -2 - does not match reload version (old value) */ -static int cdb_val_decode(pv_name_fix_t *pv_name, str *cdb_val, int reload_version, str *str_res, int *int_res) { +static int cdb_val_decode(pv_name_fix_t *pv_name, str *cdb_val, int reload_version, str *str_res, int *int_res) +{ long long one = 1; int int_val, next_str_off, i, rc; char int_buf[4]; @@ -1184,11 +1216,12 @@ static int cdb_val_decode(pv_name_fix_t *pv_name, str *cdb_val, int reload_versi return 0; error: - LM_ERR("Failed to decode value from cachedb\n"); + LM_ERR("Failed to decode value: %.*s from cachedb\n", cdb_val->len, cdb_val->s); return -1; } -static void optimize_cdb_decode(pv_name_fix_t *pv_name) { +static void optimize_cdb_decode(pv_name_fix_t *pv_name) +{ int i, j, prev_cols; char col_type1, col_type2; long long one = 1; @@ -1227,7 +1260,8 @@ static void optimize_cdb_decode(pv_name_fix_t *pv_name) { * -1 - error * -2 - not found in sql db */ -static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, int *int_res) { +static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, int *int_res) +{ struct queried_key *it, *prev = NULL, *tmp, *new_key; str src_key; db_res_t *sql_res = NULL; @@ -1257,7 +1291,7 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in lock_get(queries_lock); it = *queries_in_progress; - while (it != NULL) { + while (it) { if (!memcmp(it->key.s, src_key.s, src_key.len)) { /* key is in list */ it->nr_waiting_procs++; lock_release(queries_lock); @@ -1320,7 +1354,7 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in return -1; } new_key->next = NULL; - if (*queries_in_progress != NULL) + if (*queries_in_progress) new_key->next = *queries_in_progress; *queries_in_progress = new_key; @@ -1384,7 +1418,8 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in return -1; } -static int parse_pv_name_s(pv_name_fix_t *pv_name, str *name_s) { +static int parse_pv_name_s(pv_name_fix_t *pv_name, str *name_s) +{ char *p1 = NULL, *p2 = NULL; char last; @@ -1396,7 +1431,7 @@ static int parse_pv_name_s(pv_name_fix_t *pv_name, str *name_s) { LM_ERR("Invalid syntax for pvar name\n"); \ return -1; \ } \ - int prev_len = pv_name->type.len; \ + int _prev_len = pv_name->type.len; \ pv_name->type.len = (_ptr2) - (_ptr1); \ if (!pv_name->type.s) { \ pv_name->type.s = pkg_malloc(pv_name->type.len); \ @@ -1406,7 +1441,7 @@ static int parse_pv_name_s(pv_name_fix_t *pv_name, str *name_s) { } \ memcpy(pv_name->type.s, (_ptr1), pv_name->type.len); \ } else if (memcmp(pv_name->type.s, (_ptr1), pv_name->type.len)) { \ - if (prev_len != pv_name->type.len) { \ + if (_prev_len != pv_name->type.len) { \ pv_name->type.s = pkg_realloc(pv_name->type.s, pv_name->type.len); \ if (!pv_name->type.s) { \ LM_ERR("No more pkg memory\n"); \ @@ -1430,11 +1465,12 @@ static int parse_pv_name_s(pv_name_fix_t *pv_name, str *name_s) { return 0; } -int pv_parse_name(pv_spec_p sp, str *in) { +int pv_parse_name(pv_spec_p sp, str *in) +{ pv_elem_t *model = NULL, *it; pv_name_fix_t *pv_name; - if (in == NULL || in->s == NULL || sp == NULL) + if (!in || !in->s || !sp) return -1; pv_name = pkg_malloc(sizeof(pv_name_fix_t)); @@ -1461,11 +1497,11 @@ int pv_parse_name(pv_spec_p sp, str *in) { return -1; } - for (it = model; it != NULL; it = it->next) { + for (it = model; it; it = it->next) { if (it->spec.type != PVT_NONE) break; } - if (it != NULL) { /* if there are variables in the name, parse later */ + if (it) { /* if there are variables in the name, parse later */ pv_name->pv_elem_list = model; } else { if (parse_pv_name_s(pv_name, &(model->text)) < 0) @@ -1475,7 +1511,8 @@ int pv_parse_name(pv_spec_p sp, str *in) { return 0; } -int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t *res) { +int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t *res) +{ pv_name_fix_t *pv_name; str name_s; cache_entry_t *it_entries; @@ -1486,8 +1523,8 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t str str_res = {NULL, 0}, cdb_res; int entry_rld_vers; - if (param == NULL || param->pvn.type != PV_NAME_PVAR || - param->pvn.u.dname == NULL) { + if (!param || param->pvn.type != PV_NAME_PVAR || + !param->pvn.u.dname) { LM_CRIT("Bad pvar get function parameters\n"); return -1; } @@ -1501,7 +1538,7 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t if (pv_name->pv_elem_list) { /* there are variables in the name which need to be evaluated, then parse */ if (pv_printf_s(msg, pv_name->pv_elem_list, &name_s) != 0 || - name_s.len == 0 || name_s.s == NULL) { + name_s.len == 0 || !name_s.s) { LM_ERR("Unable to evaluate variables in pv name"); return pv_get_null(msg, param, res); } @@ -1510,7 +1547,7 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t } if (!pv_name->c_entry) { - for (it_entries = *entry_list, it_db = db_hdls_list; it_entries != NULL; + for (it_entries = *entry_list, it_db = db_hdls_list; it_entries; it_entries = it_entries->next, it_db = it_db->next) if (!memcmp(it_entries->id.s, pv_name->id.s, pv_name->id.len)) { pv_name->c_entry = it_entries; @@ -1534,7 +1571,7 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t if (!pv_name->c_entry->on_demand) { if (rc == -2) { - LM_DBG("key %.*s not found in SQL db\n", pv_name->key.len, pv_name->key.s); + LM_WARN("key %.*s not found in SQL db\n", pv_name->key.len, pv_name->key.s); lock_stop_read(pv_name->c_entry->ref_lock); return pv_get_null(msg, param, res); } else { @@ -1548,7 +1585,7 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t if (rc2 == -1) return pv_get_null(msg, param, res); if (rc2 == -2) { - LM_DBG("key %.*s not found in SQL db\n", pv_name->key.len, pv_name->key.s); + LM_WARN("key %.*s not found in SQL db\n", pv_name->key.len, pv_name->key.s); return pv_get_null(msg, param, res); } if (rc2 == 1) { @@ -1567,7 +1604,7 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t } } else { if (!cdb_res.len || !cdb_res.s) { - LM_DBG("key %.*s not found in SQL db\n", pv_name->key.len, pv_name->key.s); + LM_WARN("key %.*s not found in SQL db\n", pv_name->key.len, pv_name->key.s); return pv_get_null(msg, param, res); } @@ -1599,13 +1636,14 @@ int pv_get_sql_cached_value(struct sip_msg *msg, pv_param_t *param, pv_value_t return 0; } -static void destroy(void) { +static void destroy(void) +{ db_handlers_t *db_hdls; struct queried_key *q_it, *q_tmp; cache_entry_t *c_it, *c_tmp; int i; - for(db_hdls = db_hdls_list; db_hdls != NULL; db_hdls = db_hdls->next) { + for(db_hdls = db_hdls_list; db_hdls; db_hdls = db_hdls->next) { if (db_hdls->cdbcon) db_hdls->cdbf.destroy(db_hdls->cdbcon); if (db_hdls->db_con) diff --git a/modules/sql_cacher/sql_cacher.h b/modules/sql_cacher/sql_cacher.h index ae38a9a7108..8afe5b017ab 100644 --- a/modules/sql_cacher/sql_cacher.h +++ b/modules/sql_cacher/sql_cacher.h @@ -29,7 +29,7 @@ #include "../../db/db.h" #include "../../cachedb/cachedb.h" -#define DEFAULT_SPEC_DELIM ' ' +#define DEFAULT_SPEC_DELIM " " #define COLUMN_NAMES_DELIM ',' #define DEFAULT_PVAR_DELIM ':' From db7465540d4c485187e11a69deffffd4c133ad7f Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Thu, 3 Dec 2015 02:13:49 +0200 Subject: [PATCH 14/15] sql_cacher: further improvements -if table is empty don't return error when loading entire table -improve error reporting for cache_table parameter -cache entire table if no columns are specified -don't allocate new array of columns for sql query function call -fix memory leaks in insert_in_cachedb() -add columns_delimiter module parameter -update README --- modules/sql_cacher/README | 88 +++--- modules/sql_cacher/doc/sql_cacher_admin.xml | 61 +++-- modules/sql_cacher/sql_cacher.c | 283 +++++++++++++------- modules/sql_cacher/sql_cacher.h | 24 +- 4 files changed, 291 insertions(+), 165 deletions(-) diff --git a/modules/sql_cacher/README b/modules/sql_cacher/README index 9796ad87b58..a33802a4531 100644 --- a/modules/sql_cacher/README +++ b/modules/sql_cacher/README @@ -18,9 +18,10 @@ Robert-Vladut Patrascu 1.3.1. cache_table (string) 1.3.2. spec_delimiter (string) 1.3.3. pvar_delimiter (string) - 1.3.4. sql_fetch_nr_rows (integer) - 1.3.5. full_caching_expire (integer) - 1.3.6. reload_interval (integer) + 1.3.4. columns_delimiter (string) + 1.3.5. sql_fetch_nr_rows (integer) + 1.3.6. full_caching_expire (integer) + 1.3.7. reload_interval (integer) 1.4. Exported Functions 1.5. MI Commands @@ -36,11 +37,12 @@ Robert-Vladut Patrascu 1.1. cache_table parameter usage 1.2. spec_delimiter parameter usage 1.3. pvar_delimiter parameter usage - 1.4. sql_fetch_nr_rows parameter usage - 1.5. full_caching_expire parameter usage - 1.6. reload_interval parameter usage - 1.7. sql_cacher_reload usage - 1.8. sql_cached_value(id{sep}col{sep}key) pseudo-variable usage + 1.4. spec_delimiter parameter usage + 1.5. sql_fetch_nr_rows parameter usage + 1.6. full_caching_expire parameter usage + 1.7. reload_interval parameter usage + 1.8. sql_cacher_reload usage + 1.9. sql_cached_value(id{sep}col{sep}key) pseudo-variable usage Chapter 1. Admin Guide @@ -50,21 +52,22 @@ Chapter 1. Admin Guide from a SQL-based database (using different OpenSIPS modules which implement the DB API) into a cache system implemented in OpenSIPS through the CacheDB Interface. This is done by - specifying the databases URLs, SQL table to be used, required - columns and other details in the OpenSIPS configuration script. + specifying the databases URLs, SQL table to be used, desired + columns to be cached and other details in the OpenSIPS + configuration script. The cached data is available in the script through the read-only pseudovariable “$sql_cached_value” similar to a Key-Value system. A specified column from the SQL table has the role of “key” therefore the value of this column along with the - name of the desired column are provided as "parameters" to the + name of a required column are provided as "parameters" to the pseudovariable returning the appropriate value of the column. There are two types of caching available: - * full caching - the entire SQL table is loaded into the - cache at OpenSIPS startup; + * full caching - the entire SQL table (all the rows) is + loaded into the cache at OpenSIPS startup; * on demand - the rows of the SQL table are loaded at runtime - when certain columns from those rows are requested. + when appropriate keys are requested. For on demand caching, the stored values have a configurable expire period after which they are permanently removed unless @@ -100,9 +103,11 @@ Chapter 1. Admin Guide * db_url : the URL of the SQL database * cachedb_url : the URL of the CacheDB database * table : SQL database table name - * key : SQL database column name of the key column - * columns : names of the desired columns from the SQL - database separated by “,” + * key : SQL database column name of the “key” column + * columns : names of the columns to be cached from the SQL + database, separated by a configurable delimiter + If not present, all the columns from the table will be + cached * on_demand : specifies the type of caching: + 0 : full caching + 1 : on demand @@ -111,8 +116,11 @@ Chapter 1. Admin Guide for the on demand caching type in seconds If not present, default value is “1 hour” + The parameters must be given in the exact order specified + above. + Overall, the parameter does not have a default value, it must - be set at least once. + be set at least once in order to cache any table. Example 1.1. cache_table parameter usage @@ -122,7 +130,7 @@ db_url=mysql://root:opensips@localhost/opensips_2_2 cachedb_url=mongodb:mycluster://127.0.0.1:27017/db.col table=table_name key=column_name_0 -columns=column_name_1,column_name_2,column_name_3 +columns=column_name_1 column_name_2 column_name_3 on_demand=0") @@ -132,7 +140,7 @@ on_demand=0") provided in the cache_table parameter to separate the subparameters. It must be a single character. - The default value is “ ”(space). + The default value is newline. Example 1.2. spec_delimiter parameter usage @@ -142,17 +150,31 @@ modparam("sql_cacher", "spec_delimiter", "\n") 1.3.3. pvar_delimiter (string) The delimiter to be used in the “$sql_cached_value” - pseudovariable to separate the value of the key and the desired - column name. It must be a single character. + pseudovariable to separate the caching id, the desired column + name and the value of the key. It must be a single character. The default value is “:”. Example 1.3. pvar_delimiter parameter usage -modparam("sql_cacher", "pvar_delimiter", ";") +modparam("sql_cacher", "pvar_delimiter", " ") + + +1.3.4. columns_delimiter (string) + + The delimiter to be used in the columns subparameter of the + caching entry specification provided in the cache_table + parameter to separate the desired columns names. It must be a + single character. + + The default value is “ ”(space). + + Example 1.4. spec_delimiter parameter usage + +modparam("sql_cacher", "spec_delimiter", ",") -1.3.4. sql_fetch_nr_rows (integer) +1.3.5. sql_fetch_nr_rows (integer) The number of rows to be fetched into OpenSIPS private memory in one chunk from the SQL database driver. When querying large @@ -161,12 +183,12 @@ modparam("sql_cacher", "pvar_delimiter", ";") The default value is “100”. - Example 1.4. sql_fetch_nr_rows parameter usage + Example 1.5. sql_fetch_nr_rows parameter usage modparam("sql_cacher", "sql_fetch_nr_rows", "1000") -1.3.5. full_caching_expire (integer) +1.3.6. full_caching_expire (integer) Expire period for the values stored in cache for the full caching type in seconds. This is the longest time that deleted @@ -174,19 +196,19 @@ modparam("sql_cacher", "sql_fetch_nr_rows", "1000") The default value is “24 hours”. - Example 1.5. full_caching_expire parameter usage + Example 1.6. full_caching_expire parameter usage modparam("sql_cacher", "full_caching_expire", "3600") -1.3.6. reload_interval (integer) +1.3.7. reload_interval (integer) This parameter represents how many seconds before the data expires (for full caching) the automatic reloading is triggerd. The default value is “60 s”. - Example 1.6. reload_interval parameter usage + Example 1.7. reload_interval parameter usage modparam("sql_cacher", "reload_interval", "5") @@ -207,7 +229,7 @@ modparam("sql_cacher", "reload_interval", "5") The second parameter must be a value of the key column from the SQL table. For full caching this parameter is not needed. - Example 1.7. sql_cacher_reload usage + Example 1.8. sql_cacher_reload usage ... $ opensipsctl fifo sql_cacher_reload caching_name ... @@ -222,10 +244,10 @@ $ opensipsctl fifo sql_cacher_reload caching_name key format is the following: * sep : separator configured by “pvar_delimiter” parameter * id : cache entry id - * col : name of the column - * key : value of the key column from the SQL table + * col : name of the required column + * key : value of the “key” column - Example 1.8. sql_cached_value(id{sep}col{sep}key) + Example 1.9. sql_cached_value(id{sep}col{sep}key) pseudo-variable usage ... $avp(a) = $sql_cached_value(caching_name:column_name_1:key1); diff --git a/modules/sql_cacher/doc/sql_cacher_admin.xml b/modules/sql_cacher/doc/sql_cacher_admin.xml index fb6f37de54a..02a840c7415 100644 --- a/modules/sql_cacher/doc/sql_cacher_admin.xml +++ b/modules/sql_cacher/doc/sql_cacher_admin.xml @@ -10,26 +10,26 @@ The sql_cacher module introduces the possibility to cache data from a SQL-based database (using different &osips; modules which implement the DB API) into a cache system implemented in &osips; through the CacheDB Interface. - This is done by specifying the databases URLs, SQL table to be used, required - columns and other details in the &osips; configuration script. + This is done by specifying the databases URLs, SQL table to be used, desired + columns to be cached and other details in the &osips; configuration script. The cached data is available in the script through the read-only pseudovariable $sql_cached_value similar to a Key-Value system. A specified column from the SQL table has the role of key therefore the value - of this column along with the name of the desired column are provided as + of this column along with the name of a required column are provided as "parameters" to the pseudovariable returning the appropriate value of the column. There are two types of caching available: - full caching - the entire SQL table is loaded into the - cache at &osips; startup; + full caching - the entire SQL table (all the rows) is loaded + into the cache at &osips; startup; on demand - the rows of the SQL table are loaded at runtime - when certain columns from those rows are requested. + when appropriate keys are requested. @@ -83,11 +83,12 @@ table : SQL database table name - key : SQL database column name of the key column + key : SQL database column name of the key column - columns : names of the desired columns from the - SQL database separated by , + columns : names of the columns to be cached from the + SQL database, separated by a configurable delimiter + If not present, all the columns from the table will be cached on_demand : specifies the type of caching: @@ -109,7 +110,11 @@ - Overall, the parameter does not have a default value, it must be set at least once. + The parameters must be given in the exact order specified above. + + + Overall, the parameter does not have a default value, it must be set + at least once in order to cache any table. <varname>cache_table</varname> parameter usage @@ -121,7 +126,7 @@ db_url=mysql://root:opensips@localhost/opensips_2_2 cachedb_url=mongodb:mycluster://127.0.0.1:27017/db.col table=table_name key=column_name_0 -columns=column_name_1,column_name_2,column_name_3 +columns=column_name_1 column_name_2 column_name_3 on_demand=0") @@ -136,7 +141,7 @@ on_demand=0") must be a single character. - The default value is (space). + The default value is newline. <varname>spec_delimiter</varname> parameter usage @@ -152,8 +157,8 @@ modparam("sql_cacher", "spec_delimiter", "\n") <varname>pvar_delimiter</varname> (string) The delimiter to be used in the $sql_cached_value - pseudovariable to separate the value of the key and the desired column name. It - must be a single character. + pseudovariable to separate the caching id, the desired column name + and the value of the key. It must be a single character. The default value is :. @@ -162,7 +167,27 @@ modparam("sql_cacher", "spec_delimiter", "\n") <varname>pvar_delimiter</varname> parameter usage -modparam("sql_cacher", "pvar_delimiter", ";") +modparam("sql_cacher", "pvar_delimiter", " ") + + + + + +
+ <varname>columns_delimiter</varname> (string) + + The delimiter to be used in the columns subparameter of + the caching entry specification provided in the cache_table + parameter to separate the desired columns names. It must be a single character. + + + The default value is (space). + + + <varname>spec_delimiter</varname> parameter usage + + +modparam("sql_cacher", "spec_delimiter", ",") @@ -276,13 +301,13 @@ $ opensipsctl fifo sql_cacher_reload caching_name key pvar_delimiter parameter - id : cache entry id + id : cache entry id - col : name of the column + col : name of the required column - key : value of the key column from the SQL table + key : value of the key column diff --git a/modules/sql_cacher/sql_cacher.c b/modules/sql_cacher/sql_cacher.c index c5c7a0eef7f..2a07bcde303 100644 --- a/modules/sql_cacher/sql_cacher.c +++ b/modules/sql_cacher/sql_cacher.c @@ -48,6 +48,7 @@ static struct mi_root* mi_reload(struct mi_root *cmd_tree, void *param); static str spec_delimiter; static str pvar_delimiter; +static str columns_delimiter; static int fetch_nr_rows = DEFAULT_FETCH_NR_ROWS; static int full_caching_expire = DEFAULT_FULL_CACHING_EXPIRE; static int reload_interval = DEFAULT_RELOAD_INTERVAL; @@ -65,6 +66,7 @@ gen_lock_t *queries_lock; static param_export_t mod_params[] = { {"spec_delimiter", STR_PARAM, &spec_delimiter.s}, {"pvar_delimiter", STR_PARAM, &pvar_delimiter.s}, + {"columns_delimiter", STR_PARAM, &columns_delimiter.s}, {"sql_fetch_nr_rows", INT_PARAM, &fetch_nr_rows}, {"full_caching_expire", INT_PARAM, &full_caching_expire}, {"reload_interval", INT_PARAM, &reload_interval}, @@ -141,6 +143,7 @@ static int parse_cache_entries(void) char *p1, *p2, *tmp, *c_tmp1, *c_tmp2; int col_idx; int rc = -1; + int i; str parse_str_copy; for (it = to_parse_list; it; it = next) { @@ -151,6 +154,7 @@ static int parse_cache_entries(void) LM_ERR("No more memory for cache entry struct\n"); return -1; } + new_entry->id.s = NULL; new_entry->columns = NULL; new_entry->nr_columns = 0; new_entry->on_demand = 0; @@ -164,20 +168,34 @@ static int parse_cache_entries(void) do { \ (_ptr2) = memchr((_ptr1), '=', it->to_parse_str.len - \ ((_ptr1) - it->to_parse_str.s)); \ - if (!(_ptr2)) \ + if (!(_ptr2)) { \ + LM_ERR("expected: '=' after %.*s\n", (field_name_len), (field_name_str)); \ goto parse_err; \ + } \ if (!memcmp((_ptr1), (field_name_str), (field_name_len))) { \ + if (*((_ptr1)+(field_name_len)) != '=') { \ + LM_ERR("expected: '=' after %.*s\n", (field_name_len), (field_name_str)); \ + goto parse_err; \ + } \ tmp = memchr((_ptr2) + 1, spec_delimiter.s[0], it->to_parse_str.len - \ ((_ptr2) - it->to_parse_str.s)); \ - if (!tmp) \ + if (!tmp) { \ + LM_ERR("expected: %c after value of %.*s\n", spec_delimiter.s[0], \ + (field_name_len), (field_name_str)); \ goto parse_err; \ + } \ new_entry->field.len = tmp - (_ptr2) - 1; \ - if (new_entry->field.len <= 0) \ + if (new_entry->field.len <= 0) { \ + LM_ERR("expected value of: %.*s\n", (field_name_len), (field_name_str)); \ goto parse_err; \ + } \ new_entry->field.s = shm_malloc(new_entry->field.len); \ memcpy(new_entry->field.s, p2 + 1, new_entry->field.len); \ - } else \ + } else { \ + LM_ERR("expected: %.*s instead of: %.*s\n", (field_name_len), (field_name_str), \ + (field_name_len), (_ptr1)); \ goto parse_err; \ + } \ } while (0) parse_str_copy = it->to_parse_str; @@ -186,125 +204,163 @@ static int parse_cache_entries(void) /* parse the id */ p1 = it->to_parse_str.s; PARSE_TOKEN(p1, p2, id, ID_STR, ID_STR_LEN); - /* parse the db_url */ p1 = tmp + 1; PARSE_TOKEN(p1, p2, db_url, DB_URL_STR, DB_URL_LEN); - /* parse the cachedb_url */ p1 = tmp + 1; PARSE_TOKEN(p1, p2, cachedb_url, CACHEDB_URL_STR, CACHEDB_URL_LEN); - /* parse the table name */ p1 = tmp + 1; PARSE_TOKEN(p1, p2, table, TABLE_STR, TABLE_STR_LEN); - #undef PARSE_TOKEN /* parse the key column name */ p1 = tmp + 1; p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); - if (!p2) + if (!p2) { + LM_ERR("expected: '=' after %.*s\n", KEY_STR_LEN, KEY_STR); goto parse_err; + } if (!memcmp(p1, KEY_STR, KEY_STR_LEN)) { - tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + if (*(p1+KEY_STR_LEN) != '=') { \ + LM_ERR("expected: '=' after %.*s\n", KEY_STR_LEN, KEY_STR); + goto parse_err; + } + + tmp = memchr(p2 + 1, spec_delimiter.s[0], + it->to_parse_str.len - (p2 - it->to_parse_str.s)); if (!tmp) /* delimiter not found, reached the end of the string to parse */ new_entry->key.len = it->to_parse_str.len - (p2 - it->to_parse_str.s + 1); else new_entry->key.len = tmp - p2 - 1; - if (new_entry->key.len <= 0) + if (new_entry->key.len <= 0) { + LM_ERR("expected value of: %.*s\n", KEY_STR_LEN, KEY_STR); goto parse_err; + } new_entry->key.s = shm_malloc(new_entry->key.len); memcpy(new_entry->key.s, p2 + 1, new_entry->key.len); if (!tmp) goto end_parsing; - } else + } else { + LM_ERR("expected: %.*s instead of: %.*s\n", (KEY_STR_LEN), (KEY_STR), \ + KEY_STR_LEN, p1); goto parse_err; + } /* parse the required column names if present */ p1 = tmp + 1; p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); - if (!p2) + if (!p2) { + LM_ERR("expected: '='\n"); goto parse_err; + } if (!memcmp(p1, COLUMNS_STR, COLUMNS_STR_LEN)) { + if (*(p1+COLUMNS_STR_LEN) != '=') { \ + LM_ERR("expected: '=' after: %.*s\n", COLUMNS_STR_LEN, COLUMNS_STR); + goto parse_err; + } col_idx = 0; - tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); - + tmp = memchr(p2 + 1, spec_delimiter.s[0], + it->to_parse_str.len - (p2 - it->to_parse_str.s)); /* just count how many columns there are */ new_entry->nr_columns = 1; - c_tmp1 = memchr(p2 + 1, COLUMN_NAMES_DELIM, it->to_parse_str.len - (p2 - it->to_parse_str.s + 1)); + c_tmp1 = memchr(p2 + 1, columns_delimiter.s[0], + it->to_parse_str.len - (p2 - it->to_parse_str.s + 1)); while (c_tmp1) { new_entry->nr_columns++; - c_tmp1 = memchr(c_tmp1 + 1, COLUMN_NAMES_DELIM, it->to_parse_str.len - (c_tmp1 - it->to_parse_str.s + 1)); + c_tmp1 = memchr(c_tmp1 + 1, columns_delimiter.s[0], + it->to_parse_str.len - (c_tmp1 - it->to_parse_str.s + 1)); } if (new_entry->nr_columns > sizeof(long long)) { - LM_ERR("Too many columns, maximum number is %ld\n", sizeof(long long)); + LM_WARN("Too many columns, maximum number is %ld\n", sizeof(long long)); goto parse_err; } - /* allocate array of columns and actually parse */ - new_entry->columns = shm_malloc(new_entry->nr_columns * sizeof(str)); + new_entry->columns = shm_malloc(new_entry->nr_columns * sizeof(str*)); c_tmp1 = p2 + 1; - c_tmp2 = memchr(p2 + 1, COLUMN_NAMES_DELIM, it->to_parse_str.len - (p2 - it->to_parse_str.s + 1)); + c_tmp2 = memchr(p2 + 1, columns_delimiter.s[0], + it->to_parse_str.len - (p2 - it->to_parse_str.s + 1)); while (c_tmp2) { - new_entry->columns[col_idx].len = c_tmp2 - c_tmp1; - if (new_entry->columns[col_idx].len <= 0) + new_entry->columns[col_idx] = shm_malloc(sizeof(str)); + (*new_entry->columns[col_idx]).len = c_tmp2 - c_tmp1; + if ((*new_entry->columns[col_idx]).len <= 0) { + LM_ERR("expected name of column\n"); goto parse_err; - new_entry->columns[col_idx].s = shm_malloc(new_entry->columns[col_idx].len); - memcpy(new_entry->columns[col_idx].s, c_tmp1, new_entry->columns[col_idx].len); + } + (*new_entry->columns[col_idx]).s = shm_malloc((*new_entry->columns[col_idx]).len); + memcpy((*new_entry->columns[col_idx]).s, c_tmp1, (*new_entry->columns[col_idx]).len); c_tmp1 = c_tmp2 + 1; - c_tmp2 = memchr(c_tmp1, COLUMN_NAMES_DELIM, it->to_parse_str.len - (c_tmp1 - it->to_parse_str.s + 1)); + c_tmp2 = memchr(c_tmp1, columns_delimiter.s[0], + it->to_parse_str.len - (c_tmp1 - it->to_parse_str.s + 1)); col_idx++; } + new_entry->columns[col_idx] = shm_malloc(sizeof(str)); if (!tmp) - new_entry->columns[col_idx].len = it->to_parse_str.len - (p2 - c_tmp1 + 1); + (*new_entry->columns[col_idx]).len = it->to_parse_str.len - (p2 - c_tmp1 + 1); else - new_entry->columns[col_idx].len = tmp - c_tmp1; + (*new_entry->columns[col_idx]).len = tmp - c_tmp1; - if (new_entry->columns[col_idx].len <= 0) - goto parse_err; - new_entry->columns[col_idx].s = shm_malloc(new_entry->columns[col_idx].len); - memcpy(new_entry->columns[col_idx].s, c_tmp1, new_entry->columns[col_idx].len); + if ((*new_entry->columns[col_idx]).len <= 0) { + LM_ERR("expected name of column\n"); + goto parse_err; + } + (*new_entry->columns[col_idx]).s = shm_malloc((*new_entry->columns[col_idx]).len); + memcpy((*new_entry->columns[col_idx]).s, c_tmp1, (*new_entry->columns[col_idx]).len); if (!tmp) /* delimiter not found, reached the end of the string to parse */ goto end_parsing; else { p1 = tmp + 1; p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); - if (!p2) + if (!p2) { + LM_ERR("expected: '='\n"); goto parse_err; + } } } /* parse on demand parameter */ if (!memcmp(p1, ONDEMAND_STR, ONDEMAND_STR_LEN)) { - tmp = memchr(p2 + 1, spec_delimiter.s[0], it->to_parse_str.len - (p2 - it->to_parse_str.s)); + if (*(p1+ONDEMAND_STR_LEN) != '=') { \ + LM_ERR("expected: '=' after: %.*s\n", ONDEMAND_STR_LEN, ONDEMAND_STR); + goto parse_err; + } + tmp = memchr(p2 + 1, spec_delimiter.s[0], + it->to_parse_str.len - (p2 - it->to_parse_str.s)); str str_val; if (!tmp) /* delimiter not found, reached the end of the string to parse */ str_val.len = it->to_parse_str.len - (p2 - it->to_parse_str.s + 1); else str_val.len = tmp - p2 - 1; - if (str_val.len <= 0) + if (str_val.len <= 0) { + LM_ERR("expected value of: %.*s\n", ONDEMAND_STR_LEN, ONDEMAND_STR); goto parse_err; + } str_val.s = p2 + 1; - if(str2int(&str_val, &new_entry->on_demand)) + if(str2int(&str_val, &new_entry->on_demand)) { + LM_ERR("expected integer value for: %.*s instead of: %.*s\n", + ONDEMAND_STR_LEN, ONDEMAND_STR, str_val.len, str_val.s); goto parse_err; + } if (!tmp) /* delimiter not found, reached the end of the string to parse */ goto end_parsing; else { p1 = tmp + 1; p2 = memchr(p1, '=', it->to_parse_str.len - (p1 - it->to_parse_str.s)); - if (!p2) + if (!p2) { + LM_ERR("expected: '='\n"); goto parse_err; + } } } @@ -312,26 +368,22 @@ static int parse_cache_entries(void) if (!memcmp(p1, EXPIRE_STR, EXPIRE_STR_LEN)) { str str_val; str_val.len = it->to_parse_str.len - (p2 - it->to_parse_str.s + 1); - if (str_val.len <= 0) + if (str_val.len <= 0) { + LM_ERR("expected value of: %.*s\n", EXPIRE_STR_LEN, EXPIRE_STR); goto parse_err; + } str_val.s = p2 + 1; - if(str2int(&str_val, &new_entry->expire)) + if(str2int(&str_val, &new_entry->expire)) { + LM_ERR("expected integer value for: %.*s instead of: %.*s\n", + EXPIRE_STR_LEN, EXPIRE_STR, str_val.len, str_val.s); goto parse_err; - - goto end_parsing; + } + } else { + LM_ERR("unknown parameter: %.*s\n", + (int)(it->to_parse_str.len - (p1 - it->to_parse_str.s)), p1); + goto parse_err; } - goto end_parsing; - -parse_err: - LM_ERR("Invalid cache entry specification: %.*s\n", it->to_parse_str.len, it->to_parse_str.s); - if (new_entry->columns) - shm_free(new_entry->columns); - shm_free(new_entry); - - pkg_free(parse_str_copy.s); - pkg_free(it); - continue; end_parsing: new_entry->next = NULL; if (*entry_list) @@ -341,6 +393,27 @@ static int parse_cache_entries(void) pkg_free(parse_str_copy.s); pkg_free(it); rc = 0; + continue; +parse_err: + if (!new_entry->id.s) + LM_WARN("invalid cache entry specification: %.*s\n", + it->to_parse_str.len, it->to_parse_str.s); + else + LM_WARN("invalid cache entry specification for id: %.*s\n", + new_entry->id.len, new_entry->id.s); + + if (new_entry->columns) { + for (i=0; i < new_entry->nr_columns; i++) + if (new_entry->columns[i]) { + if ((*new_entry->columns[i]).s) + shm_free((*new_entry->columns[i]).s); + shm_free(new_entry->columns[i]); + } + shm_free(new_entry->columns); + } + shm_free(new_entry); + pkg_free(parse_str_copy.s); + pkg_free(it); } return rc; @@ -530,9 +603,12 @@ static int insert_in_cachedb(cache_entry_t *c_entry, db_handlers_t *db_hdls, db_ memcpy(cdb_key.s + c_entry->id.len, str_key.s, str_key.len); if (db_hdls->cdbf.set(db_hdls->cdbcon, &cdb_key, &cdb_val, c_entry->expire) < 0) { - LM_ERR("Failed to insert the values for key: %.*s in cachedb\n", str_key.len, str_key.s); + LM_ERR("Failed to insert the values for key: %.*s in cachedb\n", + str_key.len, str_key.s); return -1; } + pkg_free(cdb_key.s); + pkg_free(cdb_val.s); return 0; } @@ -544,7 +620,6 @@ static db_handlers_t *db_init_test_conn(cache_entry_t *c_entry) str cdb_test_key = str_init(CDB_TEST_KEY_STR); str cdb_test_val = str_init(CDB_TEST_VAL_STR); db_key_t query_key_col; - db_key_t *query_cols = NULL; db_val_t query_key_val; db_res_t *sql_res; str cachedb_res; @@ -625,19 +700,8 @@ static db_handlers_t *db_init_test_conn(cache_entry_t *c_entry) query_key_col = &c_entry->key; - query_cols = pkg_malloc(c_entry->nr_columns * sizeof(db_key_t)); - if (!query_cols) { - LM_ERR("No more pkg memory\n"); - new_db_hdls->db_funcs.close(new_db_hdls->db_con); - new_db_hdls->db_con = 0; - return NULL; - } - - for (i = 0; i < c_entry->nr_columns; i++) - query_cols[i] = &(c_entry->columns[i]); - if (new_db_hdls->db_funcs.query(new_db_hdls->db_con, &query_key_col, 0, &query_key_val, - query_cols, 1, c_entry->nr_columns, 0, &sql_res) != 0) { + c_entry->columns, 1, c_entry->nr_columns, 0, &sql_res) != 0) { LM_ERR("Failure to issuse test query to SQL DB: %.*s\n", c_entry->db_url.len, c_entry->db_url.s); new_db_hdls->db_funcs.close(new_db_hdls->db_con); @@ -645,6 +709,18 @@ static db_handlers_t *db_init_test_conn(cache_entry_t *c_entry) return NULL; } + /* no columns specified in cache entry -> cache entire table and get column names from the sql result */ + if (!c_entry->columns) { + c_entry->nr_columns = RES_COL_N(sql_res); + c_entry->columns = shm_malloc(c_entry->nr_columns * sizeof(str*)); + for (i = 0; i < c_entry->nr_columns; i++) { + c_entry->columns[i] = shm_malloc(sizeof(str)); + (*c_entry->columns[i]).len = RES_NAMES(sql_res)[i]->len; + (*c_entry->columns[i]).s = shm_malloc((*c_entry->columns[i]).len); + memcpy((*c_entry->columns[i]).s, RES_NAMES(sql_res)[i]->s, (*c_entry->columns[i]).len); + } + } + new_db_hdls->db_funcs.free_result(new_db_hdls->db_con, sql_res); return new_db_hdls; } @@ -664,7 +740,7 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int } query_cols[0] = &(c_entry->key); for (i=0; i < c_entry->nr_columns; i++) - query_cols[i+1] = &(c_entry->columns[i]); + query_cols[i+1] = &((*c_entry->columns[i])); /* query the entire table */ if (db_hdls->db_funcs.use_table(db_hdls->db_con, &c_entry->table) < 0) { @@ -695,9 +771,12 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int } } + pkg_free(query_cols); + if (RES_ROW_N(sql_res) == 0) { - LM_DBG("Table: %.*s is empty!\n", c_entry->table.len, c_entry->table.s); - goto error; + LM_WARN("Table: %.*s is empty!\n", c_entry->table.len, c_entry->table.s); + db_hdls->db_funcs.free_result(db_hdls->db_con, sql_res); + return 0; } row = RES_ROWS(sql_res); values = ROW_VALUES(row); @@ -712,7 +791,9 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int row = RES_ROWS(sql_res) + i; values = ROW_VALUES(row); if (!VAL_NULL(values)) - insert_in_cachedb(c_entry, db_hdls, values ,values + 1, reload_version, ROW_N(row) - 1); + if (insert_in_cachedb(c_entry, db_hdls, values ,values + 1, + reload_version, ROW_N(row) - 1) < 0) + return -1; } if (DB_CAPABILITY(db_hdls->db_funcs, DB_CAP_FETCH)) { @@ -741,11 +822,10 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls, int */ static int load_key(cache_entry_t *c_entry, db_handlers_t *db_hdls, str key, db_val_t **values, db_res_t **sql_res) { - db_key_t *query_cols = NULL, key_col; + db_key_t key_col; db_row_t *row; db_val_t key_val; str src_key, null_val; - int i; src_key.len = c_entry->id.len + key.len; src_key.s = pkg_malloc(src_key.len); @@ -756,34 +836,26 @@ static int load_key(cache_entry_t *c_entry, db_handlers_t *db_hdls, str key, db_ memcpy(src_key.s, c_entry->id.s, c_entry->id.len); memcpy(src_key.s + c_entry->id.len, key.s, key.len); - query_cols = pkg_malloc(c_entry->nr_columns * sizeof(db_key_t)); - if (!query_cols) { - LM_ERR("No more pkg memory\n"); - return -1; - } - for (i=0; i < c_entry->nr_columns; i++) - query_cols[i] = &(c_entry->columns[i]); key_col = &(c_entry->key); VAL_NULL(&key_val) = 0; VAL_TYPE(&key_val) = DB_STR; VAL_STR(&key_val) = key; if (db_hdls->db_funcs.use_table(db_hdls->db_con, &c_entry->table) < 0) { - LM_ERR("Invalid table name\n"); + LM_ERR("Invalid table name: %.*s\n", c_entry->table.len, c_entry->table.s); db_hdls->db_funcs.close(db_hdls->db_con); db_hdls->db_con = 0; return -1; } CON_PS_REFERENCE(db_hdls->db_con) = &db_hdls->query_ps; if (db_hdls->db_funcs.query(db_hdls->db_con, - &key_col, 0, &key_val, query_cols, 1, + &key_col, 0, &key_val, c_entry->columns, 1, c_entry->nr_columns, 0, sql_res) != 0) { - LM_ERR("Failure to issue query to SQL DB\n"); + LM_ERR("Failure to issue query to SQL DB: %.*s\n", + c_entry->db_url.len, c_entry->db_url.s); goto sql_error; } - pkg_free(query_cols); - if (RES_ROW_N(*sql_res) == 0) { LM_WARN("key %.*s not found in SQL db\n", key.len, key.s); null_val.len = 0; @@ -811,7 +883,9 @@ static int load_key(cache_entry_t *c_entry, db_handlers_t *db_hdls, str key, db_ LM_ERR("SQL column has unsupported type\n"); goto sql_error; } - insert_in_cachedb(c_entry, db_hdls, &key_val, *values, 0, ROW_N(row)); + + if (insert_in_cachedb(c_entry, db_hdls, &key_val, *values, 0, ROW_N(row)) < 0) + return -1; return 0; @@ -974,23 +1048,21 @@ static int mod_init(void) if (!spec_delimiter.s) spec_delimiter.s = DEFAULT_SPEC_DELIM; - if (!pvar_delimiter.s) { - pvar_delimiter.s = pkg_malloc(sizeof(char)); - if (!pvar_delimiter.s) { - LM_ERR("No more memory for pvar_delimiter\n"); - return -1; - } - pvar_delimiter.s[0] = DEFAULT_PVAR_DELIM; - } else - pvar_delimiter.len = strlen(pvar_delimiter.s); + if (!pvar_delimiter.s) + pvar_delimiter.s = DEFAULT_PVAR_DELIM; + + if (!columns_delimiter.s) + columns_delimiter.s = DEFAULT_COLUMNS_DELIM; if (full_caching_expire <= 0) { full_caching_expire = DEFAULT_FULL_CACHING_EXPIRE; - LM_WARN("Invalid full_caching_expire parameter, setting default value: %d sec\n", DEFAULT_FULL_CACHING_EXPIRE); + LM_WARN("Invalid full_caching_expire parameter, " + "setting default value: %d sec\n", DEFAULT_FULL_CACHING_EXPIRE); } if (reload_interval <= 0 || reload_interval >= full_caching_expire) { reload_interval = DEFAULT_RELOAD_INTERVAL; - LM_WARN("Invalid reload_interval parameter, setting default value: %d sec\n", DEFAULT_RELOAD_INTERVAL); + LM_WARN("Invalid reload_interval parameter, " + "setting default value: %d sec\n", DEFAULT_RELOAD_INTERVAL); } entry_list = shm_malloc(sizeof(cache_entry_t*)); @@ -1017,6 +1089,11 @@ static int mod_init(void) return -1; } + if (!to_parse_list) { + LM_WARN("No table to cache\n"); + return 0; + } + if (parse_cache_entries() < 0) { LM_ERR("Unable to parse any cache entry\n"); return -1; @@ -1227,7 +1304,7 @@ static void optimize_cdb_decode(pv_name_fix_t *pv_name) long long one = 1; for (i = 0; i < pv_name->c_entry->nr_columns; i++) { - if (!memcmp(pv_name->c_entry->columns[i].s, pv_name->col.s, pv_name->col.len)) { + if (!memcmp((*pv_name->c_entry->columns[i]).s, pv_name->col.s, pv_name->col.len)) { pv_name->col_nr = i; prev_cols = 0; @@ -1270,7 +1347,7 @@ static int on_demand_load(pv_name_fix_t *pv_name, str *cdb_res, str *str_res, in int i, rld_vers_dummy, rc; for (i = 0; i < pv_name->c_entry->nr_columns; i++) - if (!memcmp(pv_name->c_entry->columns[i].s, pv_name->col.s, pv_name->col.len)) { + if (!memcmp((*pv_name->c_entry->columns[i]).s, pv_name->col.s, pv_name->col.len)) { pv_name->col_nr = i; break; } @@ -1454,9 +1531,9 @@ static int parse_pv_name_s(pv_name_fix_t *pv_name, str *name_s) last = name_s->s[name_s->len]; p1 = name_s->s; - PARSE_TOKEN(p1, p2, id, DEFAULT_PVAR_DELIM); + PARSE_TOKEN(p1, p2, id, pvar_delimiter.s[0]); p1 = p2 + 1; - PARSE_TOKEN(p1, p2, col, DEFAULT_PVAR_DELIM); + PARSE_TOKEN(p1, p2, col, pvar_delimiter.s[0]); p1 = p2 + 1; PARSE_TOKEN(p1, p2, key, last); @@ -1670,8 +1747,10 @@ static void destroy(void) shm_free(c_tmp->cachedb_url.s); shm_free(c_tmp->table.s); shm_free(c_tmp->key.s); - for (i = 0; i < c_tmp->nr_columns; i++) - shm_free(c_tmp->columns[i].s); + for (i = 0; i < c_tmp->nr_columns; i++) { + shm_free((*c_tmp->columns[i]).s); + shm_free(c_tmp->columns[i]); + } shm_free(c_tmp->columns); lock_destroy_rw(c_tmp->ref_lock); shm_free(c_tmp); diff --git a/modules/sql_cacher/sql_cacher.h b/modules/sql_cacher/sql_cacher.h index 8afe5b017ab..fe415bef60d 100644 --- a/modules/sql_cacher/sql_cacher.h +++ b/modules/sql_cacher/sql_cacher.h @@ -29,26 +29,26 @@ #include "../../db/db.h" #include "../../cachedb/cachedb.h" -#define DEFAULT_SPEC_DELIM " " -#define COLUMN_NAMES_DELIM ',' -#define DEFAULT_PVAR_DELIM ':' +#define DEFAULT_SPEC_DELIM "\n" +#define DEFAULT_COLUMNS_DELIM " " +#define DEFAULT_PVAR_DELIM ":" #define ID_STR "id" -#define ID_STR_LEN 2 +#define ID_STR_LEN ((int)(sizeof(ID_STR) - 1)) #define DB_URL_STR "db_url" -#define DB_URL_LEN 6 +#define DB_URL_LEN ((int)(sizeof(DB_URL_STR) - 1)) #define CACHEDB_URL_STR "cachedb_url" -#define CACHEDB_URL_LEN 6 +#define CACHEDB_URL_LEN ((int)(sizeof(CACHEDB_URL_STR) - 1)) #define TABLE_STR "table" -#define TABLE_STR_LEN 5 +#define TABLE_STR_LEN ((int)(sizeof(TABLE_STR) - 1)) #define KEY_STR "key" -#define KEY_STR_LEN 3 +#define KEY_STR_LEN ((int)(sizeof(KEY_STR) - 1)) #define COLUMNS_STR "columns" -#define COLUMNS_STR_LEN 7 +#define COLUMNS_STR_LEN ((int)(sizeof(COLUMNS_STR) - 1)) #define ONDEMAND_STR "on_demand" -#define ONDEMAND_STR_LEN 9 +#define ONDEMAND_STR_LEN ((int)(sizeof(ONDEMAND_STR) - 1)) #define EXPIRE_STR "expire" -#define EXPIRE_STR_LEN 6 +#define EXPIRE_STR_LEN ((int)(sizeof(EXPIRE_STR) - 1)) #define DEFAULT_ON_DEMAND_EXPIRE 3600 #define DEFAULT_FULL_CACHING_EXPIRE 86400 /* 24h */ @@ -65,7 +65,7 @@ typedef struct _cache_entry { str cachedb_url; str table; str key; - str *columns; + str **columns; unsigned int nr_columns; unsigned int on_demand; unsigned int expire; From e095586710302402e9b45d9ecdc276701c036418 Mon Sep 17 00:00:00 2001 From: rvlad-patrascu Date: Thu, 3 Dec 2015 15:53:45 +0200 Subject: [PATCH 15/15] sql_cacher: update documentation with usage example --- modules/sql_cacher/README | 63 +++++++++++++++++-- modules/sql_cacher/doc/sql_cacher.xml | 2 +- modules/sql_cacher/doc/sql_cacher_admin.xml | 67 ++++++++++++++++++++- 3 files changed, 124 insertions(+), 8 deletions(-) diff --git a/modules/sql_cacher/README b/modules/sql_cacher/README index a33802a4531..a3b866f2e88 100644 --- a/modules/sql_cacher/README +++ b/modules/sql_cacher/README @@ -1,4 +1,4 @@ -sql_cacher Module +SQL Cacher Module Robert-Vladut Patrascu @@ -32,17 +32,22 @@ Robert-Vladut Patrascu 1.6.1. $sql_cached_value(id{sep}col{sep}key) + 1.7. Usage Example + List of Examples 1.1. cache_table parameter usage 1.2. spec_delimiter parameter usage 1.3. pvar_delimiter parameter usage - 1.4. spec_delimiter parameter usage + 1.4. columns_delimiter parameter usage 1.5. sql_fetch_nr_rows parameter usage 1.6. full_caching_expire parameter usage 1.7. reload_interval parameter usage 1.8. sql_cacher_reload usage 1.9. sql_cached_value(id{sep}col{sep}key) pseudo-variable usage + 1.10. Example database content - carrierfailureroute table + 1.11. Setting the cache_table parameter + 1.12. Accessing cached values Chapter 1. Admin Guide @@ -169,9 +174,9 @@ modparam("sql_cacher", "pvar_delimiter", " ") The default value is “ ”(space). - Example 1.4. spec_delimiter parameter usage + Example 1.4. columns_delimiter parameter usage -modparam("sql_cacher", "spec_delimiter", ",") +modparam("sql_cacher", "columns_delimiter", ",") 1.3.5. sql_fetch_nr_rows (integer) @@ -252,3 +257,53 @@ $ opensipsctl fifo sql_cacher_reload caching_name key ... $avp(a) = $sql_cached_value(caching_name:column_name_1:key1); ... + +1.7. Usage Example + + This section provides an usage example for the caching of an + SQL table. + + Suppose one in interested in caching the columns: “host_name”, + “reply_code”, “flags” and “next_domain” from the + “carrierfailureroute” table of the OpenSIPS database. + + Example 1.10. Example database content - carrierfailureroute + table +... ++----+---------+-----------+------------+--------+-----+-------------+ +| id | domain | host_name | reply_code | flags | mask | next_domain | ++----+---------+-----------+------------+-------+------+-------------+ +| 1 | 99 | | 408 | 16 | 16 | | +| 2 | 99 | gw1 | 404 | 0 | 0 | 100 | +| 3 | 99 | gw2 | 50. | 0 | 0 | 100 | +| 4 | 99 | | 404 | 2048 | 2112 | asterisk-1 | ++----+---------+-----------+------------+-------+------+-------------+ +... + + In the first place, the details of the caching must be provided + by setting the module parameter “cache_table” in the OpenSIPS + configuration script. + + Example 1.11. Setting the cache_table parameter +modparam("sql_cacher", "cache_table", +"id=carrier_fr_caching +db_url=mysql://root:opensips@localhost/opensips +cachedb_url=mongodb:mycluster://127.0.0.1:27017/my_db.col +table=carrierfailureroute +key=id +columns=host_name reply_code flags next_domain") + + Next, the values of the cached columns ca be accessed through + the “$sql_cached_value” PV. + + Example 1.12. Accessing cached values +... +$avp(rc1) = $sql_cached_value(carrier_fr_caching:reply_code:1); +$avp(rc2) = $sql_cached_value(carrier_fr_caching:reply_code:2); +... +var(some_id)=4; +$avp(nd) = $sql_cached_value(carrier_fr_caching:next_domain:$var(some_id +)); +... +xlog("host name is: $sql_cached_value(carrier_fr_caching:host_name:2)"); +... diff --git a/modules/sql_cacher/doc/sql_cacher.xml b/modules/sql_cacher/doc/sql_cacher.xml index dc19d3d8a79..06a64b448a7 100644 --- a/modules/sql_cacher/doc/sql_cacher.xml +++ b/modules/sql_cacher/doc/sql_cacher.xml @@ -12,7 +12,7 @@ - sql_cacher Module + SQL Cacher Module &osipsname; diff --git a/modules/sql_cacher/doc/sql_cacher_admin.xml b/modules/sql_cacher/doc/sql_cacher_admin.xml index 02a840c7415..00897778cfe 100644 --- a/modules/sql_cacher/doc/sql_cacher_admin.xml +++ b/modules/sql_cacher/doc/sql_cacher_admin.xml @@ -1,4 +1,4 @@ - + @@ -184,10 +184,10 @@ modparam("sql_cacher", "pvar_delimiter", " ") The default value is (space). - <varname>spec_delimiter</varname> parameter usage + <varname>columns_delimiter</varname> parameter usage -modparam("sql_cacher", "spec_delimiter", ",") +modparam("sql_cacher", "columns_delimiter", ",") @@ -322,4 +322,65 @@ $avp(a) = $sql_cached_value(caching_name:column_name_1:key1);
+ +
+ Usage Example + + This section provides an usage example for the caching of an SQL table. + + + Suppose one in interested in caching the columns: host_name, + reply_code, flags and next_domain + from the carrierfailureroute table of the &osips; database. + + + Example database content - carrierfailureroute table + +... ++----+---------+-----------+------------+--------+-----+-------------+ +| id | domain | host_name | reply_code | flags | mask | next_domain | ++----+---------+-----------+------------+-------+------+-------------+ +| 1 | 99 | | 408 | 16 | 16 | | +| 2 | 99 | gw1 | 404 | 0 | 0 | 100 | +| 3 | 99 | gw2 | 50. | 0 | 0 | 100 | +| 4 | 99 | | 404 | 2048 | 2112 | asterisk-1 | ++----+---------+-----------+------------+-------+------+-------------+ +... + + + + In the first place, the details of the caching must be provided by setting + the module parameter cache_table in the &osips; configuration script. + + + Setting the <varname>cache_table</varname> parameter + +modparam("sql_cacher", "cache_table", +"id=carrier_fr_caching +db_url=mysql://root:opensips@localhost/opensips +cachedb_url=mongodb:mycluster://127.0.0.1:27017/my_db.col +table=carrierfailureroute +key=id +columns=host_name reply_code flags next_domain") + + + + Next, the values of the cached columns ca be accessed through the $sql_cached_value PV. + + + Accessing cached values + +... +$avp(rc1) = $sql_cached_value(carrier_fr_caching:reply_code:1); +$avp(rc2) = $sql_cached_value(carrier_fr_caching:reply_code:2); +... +var(some_id)=4; +$avp(nd) = $sql_cached_value(carrier_fr_caching:next_domain:$var(some_id)); +... +xlog("host name is: $sql_cached_value(carrier_fr_caching:host_name:2)"); +... + + +
+ \ No newline at end of file