diff options
-rw-r--r-- | CHANGES | 6 | ||||
-rw-r--r-- | addons/res_config_mysql.c | 151 | ||||
-rw-r--r-- | include/asterisk/config.h | 111 | ||||
-rw-r--r-- | main/config.c | 172 | ||||
-rw-r--r-- | main/sorcery.c | 2 | ||||
-rw-r--r-- | res/res_config_curl.c | 132 | ||||
-rw-r--r-- | res/res_config_ldap.c | 125 | ||||
-rw-r--r-- | res/res_config_odbc.c | 228 | ||||
-rw-r--r-- | res/res_config_pgsql.c | 149 | ||||
-rw-r--r-- | res/res_config_sqlite3.c | 82 | ||||
-rw-r--r-- | res/res_sorcery_realtime.c | 252 | ||||
-rw-r--r-- | tests/test_sorcery_realtime.c | 791 |
12 files changed, 1665 insertions, 536 deletions
@@ -147,6 +147,12 @@ XMPP If no resources exist or all are unavailable the device state is considered to be unavailable. +Sorcery +------------------ + * All future modules which utilize Sorcery for object persistence must have a + column named "id" within their schema when using the Sorcery realtime module. + This column must be able to contain a string of up to 128 characters in length. + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 10 to Asterisk 11 -------------------- ------------------------------------------------------------------------------ diff --git a/addons/res_config_mysql.c b/addons/res_config_mysql.c index 71f21db46..f9201698c 100644 --- a/addons/res_config_mysql.c +++ b/addons/res_config_mysql.c @@ -316,7 +316,7 @@ static char *decode_chunk(char *chunk) return orig; } -static struct ast_variable *realtime_mysql(const char *database, const char *table, va_list ap) +static struct ast_variable *realtime_mysql(const char *database, const char *table, const struct ast_variable *rt_fields) { struct mysql_conn *dbh; MYSQL_RES *result; @@ -328,7 +328,7 @@ static struct ast_variable *realtime_mysql(const char *database, const char *tab char *stringp; char *chunk; char *op; - const char *newparam, *newval; + const struct ast_variable *field = rt_fields; struct ast_variable *var=NULL, *prev=NULL; if (!(dbh = find_database(database, 0))) { @@ -343,7 +343,7 @@ static struct ast_variable *realtime_mysql(const char *database, const char *tab } /* Get the first parameter and first value in our list of passed paramater/value pairs */ - if (!(newparam = va_arg(ap, const char *)) || !(newval = va_arg(ap, const char *))) { + if (!field) { ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n"); release_database(dbh); return NULL; @@ -358,21 +358,20 @@ static struct ast_variable *realtime_mysql(const char *database, const char *tab /* Create the first part of the query using the first parameter/value pairs we just extracted If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ - if (!strchr(newparam, ' ')) + if (!strchr(field->name, ' ')) op = " ="; else op = ""; - ESCAPE_STRING(buf, newval); - ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(buf)); - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - if (!strchr(newparam, ' ')) + ESCAPE_STRING(buf, field->value); + ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, field->name, op, ast_str_buffer(buf)); + while ((field = field->next)) { + if (!strchr(field->name, ' ')) op = " ="; else op = ""; - ESCAPE_STRING(buf, newval); - ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(buf)); + ESCAPE_STRING(buf, field->value); + ast_str_append(&sql, 0, " AND %s%s '%s'", field->name, op, ast_str_buffer(buf)); } ast_debug(1, "MySQL RealTime: Retrieve SQL: %s\n", ast_str_buffer(sql)); @@ -417,7 +416,7 @@ static struct ast_variable *realtime_mysql(const char *database, const char *tab return var; } -static struct ast_config *realtime_multi_mysql(const char *database, const char *table, va_list ap) +static struct ast_config *realtime_multi_mysql(const char *database, const char *table, const struct ast_variable *rt_fields) { struct mysql_conn *dbh; MYSQL_RES *result; @@ -430,7 +429,7 @@ static struct ast_config *realtime_multi_mysql(const char *database, const char char *stringp; char *chunk; char *op; - const char *newparam, *newval; + const struct ast_variable *field = rt_fields; struct ast_variable *var = NULL; struct ast_config *cfg = NULL; struct ast_category *cat = NULL; @@ -454,14 +453,14 @@ static struct ast_config *realtime_multi_mysql(const char *database, const char } /* Get the first parameter and first value in our list of passed paramater/value pairs */ - if (!(newparam = va_arg(ap, const char *)) || !(newval = va_arg(ap, const char *))) { + if (!field) { ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n"); ast_config_destroy(cfg); release_database(dbh); return NULL; } - initfield = ast_strdupa(newparam); + initfield = ast_strdupa(field->name); if ((op = strchr(initfield, ' '))) { *op = '\0'; } @@ -476,18 +475,17 @@ static struct ast_config *realtime_multi_mysql(const char *database, const char /* Create the first part of the query using the first parameter/value pairs we just extracted If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ - if (!strchr(newparam, ' ')) + if (!strchr(field->name, ' ')) op = " ="; else op = ""; - ESCAPE_STRING(buf, newval); - ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(buf)); - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - if (!strchr(newparam, ' ')) op = " ="; else op = ""; - ESCAPE_STRING(buf, newval); - ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(buf)); + ESCAPE_STRING(buf, field->value); + ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, field->name, op, ast_str_buffer(buf)); + while ((field = field->next)) { + if (!strchr(field->name, ' ')) op = " ="; else op = ""; + ESCAPE_STRING(buf, field->value); + ast_str_append(&sql, 0, " AND %s%s '%s'", field->name, op, ast_str_buffer(buf)); } if (initfield) { @@ -540,11 +538,11 @@ static struct ast_config *realtime_multi_mysql(const char *database, const char return cfg; } -static int update_mysql(const char *database, const char *tablename, const char *keyfield, const char *lookup, va_list ap) +static int update_mysql(const char *database, const char *tablename, const char *keyfield, const char *lookup, const struct ast_variable *rt_fields) { struct mysql_conn *dbh; my_ulonglong numrows; - const char *newparam, *newval; + const struct ast_variable *field = rt_fields; struct ast_str *sql = ast_str_thread_get(&sql_buf, 100), *buf = ast_str_thread_get(&scratch_buf, 100); struct tables *table; struct columns *column = NULL; @@ -574,7 +572,7 @@ static int update_mysql(const char *database, const char *tablename, const char } /* Get the first parameter and first value in our list of passed paramater/value pairs */ - if (!(newparam = va_arg(ap, const char *)) || !(newval = va_arg(ap, const char *))) { + if (!field) { ast_log(LOG_WARNING, "MySQL RealTime: Realtime update requires at least 1 parameter and 1 value to update.\n"); release_table(table); release_database(dbh); @@ -582,8 +580,8 @@ static int update_mysql(const char *database, const char *tablename, const char } /* Check that the column exists in the table */ - if (!(column = find_column(table, newparam))) { - ast_log(LOG_ERROR, "MySQL RealTime: Updating column '%s', but that column does not exist within the table '%s' (first pair MUST exist)!\n", newparam, tablename); + if (!(column = find_column(table, field->name))) { + ast_log(LOG_ERROR, "MySQL RealTime: Updating column '%s', but that column does not exist within the table '%s' (first pair MUST exist)!\n", field->name, tablename); release_table(table); release_database(dbh); return -1; @@ -599,29 +597,27 @@ static int update_mysql(const char *database, const char *tablename, const char /* Create the first part of the query using the first parameter/value pairs we just extracted If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ - ESCAPE_STRING(buf, newval); - ast_str_set(&sql, 0, "UPDATE %s SET `%s` = '%s'", tablename, newparam, ast_str_buffer(buf)); + ESCAPE_STRING(buf, field->value); + ast_str_set(&sql, 0, "UPDATE %s SET `%s` = '%s'", tablename, field->name, ast_str_buffer(buf)); /* If the column length isn't long enough, give a chance to lengthen it. */ if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) { - internal_require(database, tablename, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL); + internal_require(database, tablename, field->name, RQ_CHAR, ast_str_strlen(buf), SENTINEL); } - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - + while ((field = field->next)) { /* If the column is not within the table, then skip it */ - if (!(column = find_column(table, newparam))) { - ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename); + if (!(column = find_column(table, field->name))) { + ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", field->name, tablename); continue; } - ESCAPE_STRING(buf, newval); - ast_str_append(&sql, 0, ", `%s` = '%s'", newparam, ast_str_buffer(buf)); + ESCAPE_STRING(buf, field->value); + ast_str_append(&sql, 0, ", `%s` = '%s'", field->value, ast_str_buffer(buf)); /* If the column length isn't long enough, give a chance to lengthen it. */ if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) { - internal_require(database, tablename, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL); + internal_require(database, tablename, field->name, RQ_CHAR, ast_str_strlen(buf), SENTINEL); } } @@ -653,12 +649,12 @@ static int update_mysql(const char *database, const char *tablename, const char return (int)numrows; } -static int update2_mysql(const char *database, const char *tablename, va_list ap) +static int update2_mysql(const char *database, const char *tablename, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields) { struct mysql_conn *dbh; my_ulonglong numrows; int first; - const char *newparam, *newval; + const struct ast_variable *field; struct ast_str *sql = ast_str_thread_get(&sql_buf, 100), *buf = ast_str_thread_get(&scratch_buf, 100); struct ast_str *where = ast_str_thread_get(&sql2_buf, 100); struct tables *table; @@ -697,51 +693,38 @@ static int update2_mysql(const char *database, const char *tablename, va_list ap } first = 1; - while ((newparam = va_arg(ap, const char *))) { - if (!(column = find_column(table, newparam))) { - ast_log(LOG_ERROR, "Updating on column '%s', but that column does not exist within the table '%s'!\n", newparam, tablename); + for (field = lookup_fields; field; field = field->next) { + if (!(column = find_column(table, field->name))) { + ast_log(LOG_ERROR, "Updating on column '%s', but that column does not exist within the table '%s'!\n", field->name, tablename); release_table(table); release_database(dbh); return -1; } - if (!(newval = va_arg(ap, const char *))) { - ast_log(LOG_ERROR, "Invalid arguments: no value specified for column '%s' on '%s@%s'\n", newparam, tablename, database); - release_table(table); - release_database(dbh); - return -1; - } - ESCAPE_STRING(buf, newval); - ast_str_append(&where, 0, "%s `%s` = '%s'", first ? "" : " AND", newparam, ast_str_buffer(buf)); + ESCAPE_STRING(buf, field->value); + ast_str_append(&where, 0, "%s `%s` = '%s'", first ? "" : " AND", field->name, ast_str_buffer(buf)); first = 0; /* If the column length isn't long enough, give a chance to lengthen it. */ if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) { - internal_require(database, tablename, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL); + internal_require(database, tablename, field->name, RQ_CHAR, ast_str_strlen(buf), SENTINEL); } } first = 1; - while ((newparam = va_arg(ap, const char *))) { - if (!(newval = va_arg(ap, const char *))) { - ast_log(LOG_ERROR, "Invalid arguments: no value specified for column '%s' on '%s@%s'\n", newparam, tablename, database); - release_table(table); - release_database(dbh); - return -1; - } - + for (field = update_fields; field; field = field->next) { /* If the column is not within the table, then skip it */ - if (!(column = find_column(table, newparam))) { - ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename); + if (!(column = find_column(table, field->name))) { + ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", field->name, tablename); continue; } - ESCAPE_STRING(buf, newval); - ast_str_append(&sql, 0, "%s `%s` = '%s'", first ? "" : ",", newparam, ast_str_buffer(buf)); + ESCAPE_STRING(buf, field->value); + ast_str_append(&sql, 0, "%s `%s` = '%s'", first ? "" : ",", field->name, ast_str_buffer(buf)); first = 0; /* If the column length isn't long enough, give a chance to lengthen it. */ if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) { - internal_require(database, tablename, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL); + internal_require(database, tablename, field->name, RQ_CHAR, ast_str_strlen(buf), SENTINEL); } } @@ -773,14 +756,14 @@ static int update2_mysql(const char *database, const char *tablename, va_list ap return (int)numrows; } -static int store_mysql(const char *database, const char *table, va_list ap) +static int store_mysql(const char *database, const char *table, const struct ast_variable *rt_fields) { struct mysql_conn *dbh; my_ulonglong insertid; struct ast_str *sql = ast_str_thread_get(&sql_buf, 16); struct ast_str *sql2 = ast_str_thread_get(&sql2_buf, 16); struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16); - const char *newparam, *newval; + const struct ast_variable *field = rt_fields; if (!(dbh = find_database(database, 1))) { ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database); @@ -793,7 +776,7 @@ static int store_mysql(const char *database, const char *table, va_list ap) return -1; } /* Get the first parameter and first value in our list of passed paramater/value pairs */ - if (!(newparam = va_arg(ap, const char *)) || !(newval = va_arg(ap, const char *))) { + if (!field) { ast_log(LOG_WARNING, "MySQL RealTime: Realtime storage requires at least 1 parameter and 1 value to search on.\n"); release_database(dbh); return -1; @@ -805,20 +788,17 @@ static int store_mysql(const char *database, const char *table, va_list ap) } /* Create the first part of the query using the first parameter/value pairs we just extracted If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ - ESCAPE_STRING(buf, newval); - ast_str_set(&sql, 0, "INSERT INTO %s (`%s`", table, newparam); + ESCAPE_STRING(buf, field->value); + ast_str_set(&sql, 0, "INSERT INTO %s (`%s`", table, field->name); ast_str_set(&sql2, 0, ") VALUES ('%s'", ast_str_buffer(buf)); - internal_require(database, table, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL); + internal_require(database, table, field->name, RQ_CHAR, ast_str_strlen(buf), SENTINEL); - while ((newparam = va_arg(ap, const char *))) { - if ((newval = va_arg(ap, const char *))) { - ESCAPE_STRING(buf, newval); - } else { - ast_str_reset(buf); - } - if (internal_require(database, table, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL) == 0) { - ast_str_append(&sql, 0, ", `%s`", newparam); + while ((field = field->next)) { + ESCAPE_STRING(buf, field->value); + + if (internal_require(database, table, field->name, RQ_CHAR, ast_str_strlen(buf), SENTINEL) == 0) { + ast_str_append(&sql, 0, ", `%s`", field->name); ast_str_append(&sql2, 0, ", '%s'", ast_str_buffer(buf)); } } @@ -846,13 +826,13 @@ static int store_mysql(const char *database, const char *table, va_list ap) return (int)insertid; } -static int destroy_mysql(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap) +static int destroy_mysql(const char *database, const char *table, const char *keyfield, const char *lookup, const struct ast_variable *rt_fields) { struct mysql_conn *dbh; my_ulonglong numrows; struct ast_str *sql = ast_str_thread_get(&sql_buf, 16); struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16); - const char *newparam, *newval; + const struct ast_variable *field; if (!(dbh = find_database(database, 1))) { ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database); @@ -884,10 +864,9 @@ static int destroy_mysql(const char *database, const char *table, const char *ke If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ ESCAPE_STRING(buf, lookup); ast_str_set(&sql, 0, "DELETE FROM %s WHERE `%s` = '%s'", table, keyfield, ast_str_buffer(buf)); - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - ESCAPE_STRING(buf, newval); - ast_str_append(&sql, 0, " AND `%s` = '%s'", newparam, ast_str_buffer(buf)); + for (field = rt_fields; field; field = field->next) { + ESCAPE_STRING(buf, field->value); + ast_str_append(&sql, 0, " AND `%s` = '%s'", field->name, ast_str_buffer(buf)); } ast_debug(1, "MySQL RealTime: Delete SQL: %s\n", ast_str_buffer(sql)); diff --git a/include/asterisk/config.h b/include/asterisk/config.h index cd29bb362..d669e7a81 100644 --- a/include/asterisk/config.h +++ b/include/asterisk/config.h @@ -98,12 +98,12 @@ struct ast_variable { }; typedef struct ast_config *config_load_func(const char *database, const char *table, const char *configfile, struct ast_config *config, struct ast_flags flags, const char *suggested_include_file, const char *who_asked); -typedef struct ast_variable *realtime_var_get(const char *database, const char *table, va_list ap); -typedef struct ast_config *realtime_multi_get(const char *database, const char *table, va_list ap); -typedef int realtime_update(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap); -typedef int realtime_update2(const char *database, const char *table, va_list ap); -typedef int realtime_store(const char *database, const char *table, va_list ap); -typedef int realtime_destroy(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap); +typedef struct ast_variable *realtime_var_get(const char *database, const char *table, const struct ast_variable *fields); +typedef struct ast_config *realtime_multi_get(const char *database, const char *table, const struct ast_variable *fields); +typedef int realtime_update(const char *database, const char *table, const char *keyfield, const char *entity, const struct ast_variable *fields); +typedef int realtime_update2(const char *database, const char *table, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields); +typedef int realtime_store(const char *database, const char *table, const struct ast_variable *fields); +typedef int realtime_destroy(const char *database, const char *table, const char *keyfield, const char *entity, const struct ast_variable *fields); /*! * \brief Function pointer called to ensure database schema is properly configured for realtime use @@ -305,7 +305,9 @@ int ast_category_exist(const struct ast_config *config, const char *category_nam * You should use the constant SENTINEL to terminate arguments, in * order to preserve cross-platform compatibility. */ +struct ast_variable *ast_load_realtime_fields(const char *family, const struct ast_variable *fields); struct ast_variable *ast_load_realtime(const char *family, ...) attribute_sentinel; +struct ast_variable *ast_load_realtime_all_fields(const char *family, const struct ast_variable *fields); struct ast_variable *ast_load_realtime_all(const char *family, ...) attribute_sentinel; /*! @@ -363,6 +365,24 @@ int ast_realtime_require_field(const char *family, ...) attribute_sentinel; * \brief Retrieve realtime configuration * * \param family which family/config to lookup + * \param fields list of fields + * + * \details + * This will use builtin configuration backends to look up a particular + * entity in realtime and return a variable list of its parameters. Unlike + * the ast_load_realtime, this function can return more than one entry and + * is thus stored inside a traditional ast_config structure rather than + * just returning a linked list of variables. + * + * \return An ast_config with one or more results + * \retval NULL Error or no results returned + */ +struct ast_config *ast_load_realtime_multientry_fields(const char *family, const struct ast_variable *fields); + +/*! + * \brief Retrieve realtime configuration + * + * \param family which family/config to lookup * * \details * This will use builtin configuration backends to look up a particular @@ -385,6 +405,21 @@ struct ast_config *ast_load_realtime_multientry(const char *family, ...) attribu * \param family which family/config to be updated * \param keyfield which field to use as the key * \param lookup which value to look for in the key field to match the entry. + * \param fields fields to update + * + * \details + * This function is used to update a parameter in realtime configuration space. + * + * \return Number of rows affected, or -1 on error. + */ +int ast_update_realtime_fields(const char *family, const char *keyfield, const char *lookup, const struct ast_variable *fields); + +/*! + * \brief Update realtime configuration + * + * \param family which family/config to be updated + * \param keyfield which field to use as the key + * \param lookup which value to look for in the key field to match the entry. * * \details * This function is used to update a parameter in realtime configuration space. @@ -400,6 +435,23 @@ int ast_update_realtime(const char *family, const char *keyfield, const char *lo * \brief Update realtime configuration * * \param family which family/config to be updated + * \param lookup_fields fields used to look up entries + * \param update_fields fields to update + * + * \details + * This function is used to update a parameter in realtime configuration space. + * It includes the ability to lookup a row based upon multiple key criteria. + * As a result, this function includes two sentinel values, one to terminate + * lookup values and the other to terminate the listing of fields to update. + * + * \return Number of rows affected, or -1 on error. + */ +int ast_update2_realtime_fields(const char *family, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields); + +/*! + * \brief Update realtime configuration + * + * \param family which family/config to be updated * * \details * This function is used to update a parameter in realtime configuration space. @@ -418,6 +470,24 @@ int ast_update2_realtime(const char *family, ...) attribute_sentinel; * \brief Create realtime configuration * * \param family which family/config to be created + * \param fields fields themselves + * + * \details + * This function is used to create a parameter in realtime configuration space. + * + * \return Number of rows affected, or -1 on error. + * + * \note + * On the MySQL engine only, for reasons of backwards compatibility, the return + * value is the insert ID. This value is nonportable and may be changed in a + * future version to match the other engines. + */ +int ast_store_realtime_fields(const char *family, const struct ast_variable *fields); + +/*! + * \brief Create realtime configuration + * + * \param family which family/config to be created * * \details * This function is used to create a parameter in realtime configuration space. @@ -440,6 +510,22 @@ int ast_store_realtime(const char *family, ...) attribute_sentinel; * \param family which family/config to be destroyed * \param keyfield which field to use as the key * \param lookup which value to look for in the key field to match the entry. + * \param fields fields themselves + * + * \details + * This function is used to destroy an entry in realtime configuration space. + * Additional params are used as keys. + * + * \return Number of rows affected, or -1 on error. + */ +int ast_destroy_realtime_fields(const char *family, const char *keyfield, const char *lookup, const struct ast_variable *fields); + +/*! + * \brief Destroy realtime configuration + * + * \param family which family/config to be destroyed + * \param keyfield which field to use as the key + * \param lookup which value to look for in the key field to match the entry. * * \details * This function is used to destroy an entry in realtime configuration space. @@ -503,6 +589,19 @@ int ast_config_engine_deregister(struct ast_config_engine *del); */ int ast_realtime_is_mapping_defined(const char *family); +#ifdef TEST_FRAMEWORK +/*! + * \brief Add an explicit mapping for a family + * + * \param name Family name + * \param driver Driver to use + * \param database Database to access + * \param table Table to use + * \param priority Priority of this mapping + */ +int ast_realtime_append_mapping(const char *name, const char *driver, const char *database, const char *table, int priority); +#endif + /*! * \brief Exposed initialization method for core process * diff --git a/main/config.c b/main/config.c index e1c40644e..5220679a2 100644 --- a/main/config.c +++ b/main/config.c @@ -2220,7 +2220,11 @@ static void clear_config_maps(void) } } -static int append_mapping(const char *name, const char *driver, const char *database, const char *table, int priority) +#ifdef TEST_FRAMEWORK +int ast_realtime_append_mapping(const char *name, const char *driver, const char *database, const char *table, int priority) +#else +static int ast_realtime_append_mapping(const char *name, const char *driver, const char *database, const char *table, int priority) +#endif { struct ast_config_map *map; char *dst; @@ -2323,13 +2327,13 @@ int read_config_maps(void) continue; if (!strcasecmp(v->name, "sipfriends")) { ast_log(LOG_WARNING, "The 'sipfriends' table is obsolete, update your config to use sippeers instead.\n"); - append_mapping("sippeers", driver, database, table ? table : "sipfriends", pri); + ast_realtime_append_mapping("sippeers", driver, database, table ? table : "sipfriends", pri); } else if (!strcasecmp(v->name, "iaxfriends")) { ast_log(LOG_WARNING, "The 'iaxfriends' table is obsolete, update your config to use iaxusers and iaxpeers, though they can point to the same table.\n"); - append_mapping("iaxusers", driver, database, table ? table : "iaxfriends", pri); - append_mapping("iaxpeers", driver, database, table ? table : "iaxfriends", pri); + ast_realtime_append_mapping("iaxusers", driver, database, table ? table : "iaxfriends", pri); + ast_realtime_append_mapping("iaxpeers", driver, database, table ? table : "iaxfriends", pri); } else - append_mapping(v->name, driver, database, table, pri); + ast_realtime_append_mapping(v->name, driver, database, table, pri); } ast_config_destroy(config); @@ -2517,7 +2521,36 @@ struct ast_config *ast_config_load2(const char *filename, const char *who_asked, return result; } -static struct ast_variable *ast_load_realtime_helper(const char *family, va_list ap) +static struct ast_variable *realtime_arguments_to_fields(va_list ap) +{ + struct ast_variable *first, *fields = NULL; + const char *newparam = va_arg(ap, const char *), *newval = va_arg(ap, const char *); + + if (!(first = ast_variable_new(newparam, newval, ""))) { + return NULL; + } + + while ((newparam = va_arg(ap, const char *))) { + struct ast_variable *field; + + newval = va_arg(ap, const char *); + if (!(field = ast_variable_new(newparam, newval, ""))) { + ast_variables_destroy(fields); + ast_variables_destroy(first); + return NULL; + } + + field->next = fields; + fields = field; + } + + first->next = fields; + fields = first; + + return fields; +} + +struct ast_variable *ast_load_realtime_all_fields(const char *family, const struct ast_variable *fields) { struct ast_config_engine *eng; char db[256]; @@ -2527,7 +2560,7 @@ static struct ast_variable *ast_load_realtime_helper(const char *family, va_list for (i = 1; ; i++) { if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { - if (eng->realtime_func && (res = eng->realtime_func(db, table, ap))) { + if (eng->realtime_func && (res = eng->realtime_func(db, table, fields))) { return res; } } else { @@ -2540,26 +2573,28 @@ static struct ast_variable *ast_load_realtime_helper(const char *family, va_list struct ast_variable *ast_load_realtime_all(const char *family, ...) { - struct ast_variable *res; + RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy); + struct ast_variable *res = NULL; va_list ap; va_start(ap, family); - res = ast_load_realtime_helper(family, ap); + fields = realtime_arguments_to_fields(ap); va_end(ap); + if (fields) { + res = ast_load_realtime_all_fields(family, fields); + } + return res; } -struct ast_variable *ast_load_realtime(const char *family, ...) +struct ast_variable *ast_load_realtime_fields(const char *family, const struct ast_variable *fields) { struct ast_variable *res; struct ast_variable *cur; struct ast_variable **prev; - va_list ap; - va_start(ap, family); - res = ast_load_realtime_helper(family, ap); - va_end(ap); + res = ast_load_realtime_all_fields(family, fields); /* Filter the list. */ prev = &res; @@ -2588,6 +2623,18 @@ struct ast_variable *ast_load_realtime(const char *family, ...) return res; } +struct ast_variable *ast_load_realtime(const char *family, ...) +{ + RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy); + va_list ap; + + va_start(ap, family); + fields = realtime_arguments_to_fields(ap); + va_end(ap); + + return ast_load_realtime_fields(family, fields); +} + /*! \brief Check if realtime engine is configured for family */ int ast_check_realtime(const char *family) { @@ -2652,19 +2699,17 @@ int ast_unload_realtime(const char *family) return res; } -struct ast_config *ast_load_realtime_multientry(const char *family, ...) +struct ast_config *ast_load_realtime_multientry_fields(const char *family, const struct ast_variable *fields) { struct ast_config_engine *eng; char db[256]; char table[256]; struct ast_config *res = NULL; - va_list ap; int i; - va_start(ap, family); for (i = 1; ; i++) { if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { - if (eng->realtime_multi_func && (res = eng->realtime_multi_func(db, table, ap))) { + if (eng->realtime_multi_func && (res = eng->realtime_multi_func(db, table, fields))) { /* If we were returned an empty cfg, destroy it and return NULL */ if (!res->root) { ast_config_destroy(res); @@ -2676,105 +2721,154 @@ struct ast_config *ast_load_realtime_multientry(const char *family, ...) break; } } - va_end(ap); return res; } -int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...) +struct ast_config *ast_load_realtime_multientry(const char *family, ...) +{ + RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy); + va_list ap; + + va_start(ap, family); + fields = realtime_arguments_to_fields(ap); + va_end(ap); + + return ast_load_realtime_multientry_fields(family, fields); +} + +int ast_update_realtime_fields(const char *family, const char *keyfield, const char *lookup, const struct ast_variable *fields) { struct ast_config_engine *eng; int res = -1, i; char db[256]; char table[256]; - va_list ap; - va_start(ap, lookup); for (i = 1; ; i++) { if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { /* If the update succeeds, it returns 0. */ - if (eng->update_func && !(res = eng->update_func(db, table, keyfield, lookup, ap))) { + if (eng->update_func && !(res = eng->update_func(db, table, keyfield, lookup, fields))) { break; } } else { break; } } - va_end(ap); return res; } -int ast_update2_realtime(const char *family, ...) +int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...) +{ + RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy); + va_list ap; + + va_start(ap, lookup); + fields = realtime_arguments_to_fields(ap); + va_end(ap); + + return ast_update_realtime_fields(family, keyfield, lookup, fields); +} + +int ast_update2_realtime_fields(const char *family, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields) { struct ast_config_engine *eng; int res = -1, i; char db[256]; char table[256]; - va_list ap; - va_start(ap, family); for (i = 1; ; i++) { if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { - if (eng->update2_func && !(res = eng->update2_func(db, table, ap))) { + if (eng->update2_func && !(res = eng->update2_func(db, table, lookup_fields, update_fields))) { break; } } else { break; } } - va_end(ap); return res; } -int ast_store_realtime(const char *family, ...) +int ast_update2_realtime(const char *family, ...) +{ + RAII_VAR(struct ast_variable *, lookup_fields, NULL, ast_variables_destroy); + RAII_VAR(struct ast_variable *, update_fields, NULL, ast_variables_destroy); + va_list ap; + + va_start(ap, family); + lookup_fields = realtime_arguments_to_fields(ap); + update_fields = realtime_arguments_to_fields(ap); + va_end(ap); + + return ast_update2_realtime_fields(family, lookup_fields, update_fields); +} + +int ast_store_realtime_fields(const char *family, const struct ast_variable *fields) { struct ast_config_engine *eng; int res = -1, i; char db[256]; char table[256]; - va_list ap; - va_start(ap, family); for (i = 1; ; i++) { if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { /* If the store succeeds, it returns 0. */ - if (eng->store_func && !(res = eng->store_func(db, table, ap))) { + if (eng->store_func && !(res = eng->store_func(db, table, fields))) { break; } } else { break; } } - va_end(ap); return res; } -int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...) +int ast_store_realtime(const char *family, ...) +{ + RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy); + va_list ap; + + va_start(ap, family); + fields = realtime_arguments_to_fields(ap); + va_end(ap); + + return ast_store_realtime_fields(family, fields); +} + +int ast_destroy_realtime_fields(const char *family, const char *keyfield, const char *lookup, const struct ast_variable *fields) { struct ast_config_engine *eng; int res = -1, i; char db[256]; char table[256]; - va_list ap; - va_start(ap, lookup); for (i = 1; ; i++) { if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { - if (eng->destroy_func && !(res = eng->destroy_func(db, table, keyfield, lookup, ap))) { + if (eng->destroy_func && !(res = eng->destroy_func(db, table, keyfield, lookup, fields))) { break; } } else { break; } } - va_end(ap); return res; } +int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...) +{ + RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy); + va_list ap; + + va_start(ap, lookup); + fields = realtime_arguments_to_fields(ap); + va_end(ap); + + return ast_destroy_realtime_fields(family, keyfield, lookup, fields); +} + char *ast_realtime_decode_chunk(char *chunk) { char *orig = chunk; diff --git a/main/sorcery.c b/main/sorcery.c index 44e247a38..d0aee4a92 100644 --- a/main/sorcery.c +++ b/main/sorcery.c @@ -540,7 +540,7 @@ int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char int pos; va_list args; - if (!object_type || !object_type->type.item_alloc) { + if (!strcmp(type, "id") || !object_type || !object_type->type.item_alloc) { return -1; } diff --git a/res/res_config_curl.c b/res/res_config_curl.c index 81ad23467..9dfcb0d9d 100644 --- a/res/res_config_curl.c +++ b/res/res_config_curl.c @@ -53,18 +53,18 @@ AST_THREADSTORAGE(result_buf); * \brief Execute a curl query and return ast_variable list * \param url The base URL from which to retrieve data * \param unused Not currently used - * \param ap list containing one or more field/operator/value set. + * \param fields list containing one or more field/operator/value set. * * \retval var on success * \retval NULL on failure */ -static struct ast_variable *realtime_curl(const char *url, const char *unused, va_list ap) +static struct ast_variable *realtime_curl(const char *url, const char *unused, const struct ast_variable *fields) { struct ast_str *query, *buffer; char buf1[256], buf2[256]; - const char *newparam, *newval; + const struct ast_variable *field; char *stringp, *pair, *key; - int i; + unsigned int start = 1; struct ast_variable *var = NULL, *prev = NULL; if (!ast_custom_function_find("CURL")) { @@ -82,11 +82,11 @@ static struct ast_variable *realtime_curl(const char *url, const char *unused, v ast_str_set(&query, 0, "${CURL(%s/single,", url); - for (i = 0; (newparam = va_arg(ap, const char *)); i++) { - newval = va_arg(ap, const char *); - ast_uri_encode(newparam, buf1, sizeof(buf1), ast_uri_http); - ast_uri_encode(newval, buf2, sizeof(buf2), ast_uri_http); - ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2); + for (field = fields; field; field = field->next) { + ast_uri_encode(field->name, buf1, sizeof(buf1), ast_uri_http); + ast_uri_encode(field->value, buf2, sizeof(buf2), ast_uri_http); + ast_str_append(&query, 0, "%s%s=%s", !start ? "&" : "", buf1, buf2); + start = 0; } ast_str_append(&query, 0, ")}"); @@ -124,18 +124,18 @@ static struct ast_variable *realtime_curl(const char *url, const char *unused, v * \brief Excute an Select query and return ast_config list * \param url * \param unused - * \param ap list containing one or more field/operator/value set. + * \param fields list containing one or more field/operator/value set. * * \retval struct ast_config pointer on success * \retval NULL on failure */ -static struct ast_config *realtime_multi_curl(const char *url, const char *unused, va_list ap) +static struct ast_config *realtime_multi_curl(const char *url, const char *unused, const struct ast_variable *fields) { struct ast_str *query, *buffer; char buf1[256], buf2[256]; - const char *newparam, *newval; + const struct ast_variable *field; char *stringp, *line, *pair, *key, *initfield = NULL; - int i; + int start = 1; struct ast_variable *var = NULL; struct ast_config *cfg = NULL; struct ast_category *cat = NULL; @@ -155,17 +155,17 @@ static struct ast_config *realtime_multi_curl(const char *url, const char *unuse ast_str_set(&query, 0, "${CURL(%s/multi,", url); - for (i = 0; (newparam = va_arg(ap, const char *)); i++) { - newval = va_arg(ap, const char *); - if (i == 0) { + for (field = fields; field; field = field->next) { + if (start) { char *op; - initfield = ast_strdupa(newparam); + initfield = ast_strdupa(field->name); if ((op = strchr(initfield, ' '))) *op = '\0'; } - ast_uri_encode(newparam, buf1, sizeof(buf1), ast_uri_http); - ast_uri_encode(newval, buf2, sizeof(buf2), ast_uri_http); - ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2); + ast_uri_encode(field->name, buf1, sizeof(buf1), ast_uri_http); + ast_uri_encode(field->value, buf2, sizeof(buf2), ast_uri_http); + ast_str_append(&query, 0, "%s%s=%s", !start ? "&" : "", buf1, buf2); + start = 0; } ast_str_append(&query, 0, ")}"); @@ -216,7 +216,7 @@ static struct ast_config *realtime_multi_curl(const char *url, const char *unuse * \param unused * \param keyfield where clause field * \param lookup value of field for where clause - * \param ap list containing one or more field/value set(s). + * \param fields list containing one or more field/value set(s). * * Update a database table, prepare the sql statement using keyfield and lookup * control the number of records to change. All values to be changed are stored in ap list. @@ -225,13 +225,13 @@ static struct ast_config *realtime_multi_curl(const char *url, const char *unuse * \retval number of rows affected * \retval -1 on failure */ -static int update_curl(const char *url, const char *unused, const char *keyfield, const char *lookup, va_list ap) +static int update_curl(const char *url, const char *unused, const char *keyfield, const char *lookup, const struct ast_variable *fields) { struct ast_str *query, *buffer; char buf1[256], buf2[256]; - const char *newparam, *newval; + const struct ast_variable *field; char *stringp; - int i, rowcount = -1; + int start = 1, rowcount = -1; if (!ast_custom_function_find("CURL")) { ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n"); @@ -250,11 +250,11 @@ static int update_curl(const char *url, const char *unused, const char *keyfield ast_uri_encode(lookup, buf2, sizeof(buf2), ast_uri_http); ast_str_set(&query, 0, "${CURL(%s/update?%s=%s,", url, buf1, buf2); - for (i = 0; (newparam = va_arg(ap, const char *)); i++) { - newval = va_arg(ap, const char *); - ast_uri_encode(newparam, buf1, sizeof(buf1), ast_uri_http); - ast_uri_encode(newval, buf2, sizeof(buf2), ast_uri_http); - ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2); + for (field = fields; field; field = field->next) { + ast_uri_encode(field->name, buf1, sizeof(buf1), ast_uri_http); + ast_uri_encode(field->value, buf2, sizeof(buf2), ast_uri_http); + ast_str_append(&query, 0, "%s%s=%s", !start ? "&" : "", buf1, buf2); + start = 0; } ast_str_append(&query, 0, ")}"); @@ -274,13 +274,14 @@ static int update_curl(const char *url, const char *unused, const char *keyfield return -1; } -static int update2_curl(const char *url, const char *unused, va_list ap) +static int update2_curl(const char *url, const char *unused, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields) { struct ast_str *query, *buffer; char buf1[200], buf2[200]; - const char *newparam, *newval; + const struct ast_variable *field; char *stringp; - int rowcount = -1, lookup = 1, first = 1; + unsigned int start = 1; + int rowcount = -1; if (!ast_custom_function_find("CURL")) { ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n"); @@ -296,23 +297,20 @@ static int update2_curl(const char *url, const char *unused, va_list ap) ast_str_set(&query, 0, "${CURL(%s/update?", url); - for (;;) { - if ((newparam = va_arg(ap, const char *)) == SENTINEL) { - if (lookup) { - lookup = 0; - ast_str_append(&query, 0, ","); - /* Back to the first parameter; we don't need a starting '&' */ - first = 1; - continue; - } else { - break; - } - } - newval = va_arg(ap, const char *); - ast_uri_encode(newparam, buf1, sizeof(buf1), ast_uri_http); - ast_uri_encode(newval, buf2, sizeof(buf2), ast_uri_http); - ast_str_append(&query, 0, "%s%s=%s", first ? "" : "&", buf1, buf2); - first = 0; + for (field = lookup_fields; field; field = field->next) { + ast_uri_encode(field->name, buf1, sizeof(buf1), ast_uri_http); + ast_uri_encode(field->value, buf2, sizeof(buf2), ast_uri_http); + ast_str_append(&query, 0, "%s%s=%s", !start ? "" : "&", buf1, buf2); + start = 0; + } + ast_str_append(&query, 0, ","); + start = 1; + + for (field = update_fields; field; field = field->next) { + ast_uri_encode(field->name, buf1, sizeof(buf1), ast_uri_http); + ast_uri_encode(field->value, buf2, sizeof(buf2), ast_uri_http); + ast_str_append(&query, 0, "%s%s=%s", !start ? "" : "&", buf1, buf2); + start = 0; } ast_str_append(&query, 0, ")}"); @@ -340,7 +338,7 @@ static int update2_curl(const char *url, const char *unused, va_list ap) * \brief Execute an INSERT query * \param url * \param unused - * \param ap list containing one or more field/value set(s) + * \param fields list containing one or more field/value set(s) * * Insert a new record into database table, prepare the sql statement. * All values to be changed are stored in ap list. @@ -349,13 +347,13 @@ static int update2_curl(const char *url, const char *unused, va_list ap) * \retval number of rows affected * \retval -1 on failure */ -static int store_curl(const char *url, const char *unused, va_list ap) +static int store_curl(const char *url, const char *unused, const struct ast_variable *fields) { struct ast_str *query, *buffer; char buf1[256], buf2[256]; - const char *newparam, *newval; + const struct ast_variable *field; char *stringp; - int i, rowcount = -1; + int start = 1, rowcount = -1; if (!ast_custom_function_find("CURL")) { ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n"); @@ -372,11 +370,11 @@ static int store_curl(const char *url, const char *unused, va_list ap) ast_str_set(&query, 0, "${CURL(%s/store,", url); - for (i = 0; (newparam = va_arg(ap, const char *)); i++) { - newval = va_arg(ap, const char *); - ast_uri_encode(newparam, buf1, sizeof(buf1), ast_uri_http); - ast_uri_encode(newval, buf2, sizeof(buf2), ast_uri_http); - ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2); + for (field = fields; field; field = field->next) { + ast_uri_encode(field->name, buf1, sizeof(buf1), ast_uri_http); + ast_uri_encode(field->value, buf2, sizeof(buf2), ast_uri_http); + ast_str_append(&query, 0, "%s%s=%s", !start ? "&" : "", buf1, buf2); + start = 0; } ast_str_append(&query, 0, ")}"); @@ -401,7 +399,7 @@ static int store_curl(const char *url, const char *unused, va_list ap) * \param unused * \param keyfield where clause field * \param lookup value of field for where clause - * \param ap list containing one or more field/value set(s) + * \param fields list containing one or more field/value set(s) * * Delete a row from a database table, prepare the sql statement using keyfield and lookup * control the number of records to change. Additional params to match rows are stored in ap list. @@ -410,13 +408,13 @@ static int store_curl(const char *url, const char *unused, va_list ap) * \retval number of rows affected * \retval -1 on failure */ -static int destroy_curl(const char *url, const char *unused, const char *keyfield, const char *lookup, va_list ap) +static int destroy_curl(const char *url, const char *unused, const char *keyfield, const char *lookup, const struct ast_variable *fields) { struct ast_str *query, *buffer; char buf1[200], buf2[200]; - const char *newparam, *newval; + const struct ast_variable *field; char *stringp; - int i, rowcount = -1; + int start = 1, rowcount = -1; if (!ast_custom_function_find("CURL")) { ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n"); @@ -435,11 +433,11 @@ static int destroy_curl(const char *url, const char *unused, const char *keyfiel ast_uri_encode(lookup, buf2, sizeof(buf2), ast_uri_http); ast_str_set(&query, 0, "${CURL(%s/destroy,%s=%s&", url, buf1, buf2); - for (i = 0; (newparam = va_arg(ap, const char *)); i++) { - newval = va_arg(ap, const char *); - ast_uri_encode(newparam, buf1, sizeof(buf1), ast_uri_http); - ast_uri_encode(newval, buf2, sizeof(buf2), ast_uri_http); - ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2); + for (field = fields; field; field = field->next) { + ast_uri_encode(field->name, buf1, sizeof(buf1), ast_uri_http); + ast_uri_encode(field->value, buf2, sizeof(buf2), ast_uri_http); + ast_str_append(&query, 0, "%s%s=%s", !start ? "&" : "", buf1, buf2); + start = 0; } ast_str_append(&query, 0, ")}"); diff --git a/res/res_config_ldap.c b/res/res_config_ldap.c index b44adf4bc..79dc987eb 100644 --- a/res/res_config_ldap.c +++ b/res/res_config_ldap.c @@ -762,14 +762,13 @@ static void append_var_and_value_to_filter(struct ast_str **filter, * \param entries_count_ptr is a pointer to found entries count (can be NULL) * \param basedn is the base DN * \param table_name is the table_name (used dor attribute convertion and additional filter) - * \param ap contains null terminated list of pairs name/value + * \param fields contains list of pairs name/value */ static struct ast_variable **realtime_ldap_base_ap(unsigned int *entries_count_ptr, - const char *basedn, const char *table_name, va_list ap) + const char *basedn, const char *table_name, const struct ast_variable *fields) { struct ast_variable **vars = NULL; - const char *newparam = NULL; - const char *newval = NULL; + const struct ast_variable *field = fields; struct ldap_table_config *table_config = NULL; char *clean_basedn = cleaned_basedn(NULL, basedn); struct ast_str *filter = NULL; @@ -789,11 +788,7 @@ static struct ast_variable **realtime_ldap_base_ap(unsigned int *entries_count_p return NULL; } - /* Get the first parameter and first value in our list of passed paramater/value pairs */ - newparam = va_arg(ap, const char *); - newval = va_arg(ap, const char *); - - if (!newparam || !newval) { + if (!field) { ast_log(LOG_ERROR, "Realtime retrieval requires at least 1 parameter" " and 1 value to search on.\n"); ast_free(filter); @@ -834,10 +829,9 @@ static struct ast_variable **realtime_ldap_base_ap(unsigned int *entries_count_p * If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ - append_var_and_value_to_filter(&filter, table_config, newparam, newval); - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - append_var_and_value_to_filter(&filter, table_config, newparam, newval); + append_var_and_value_to_filter(&filter, table_config, field->name, field->value); + while ((field = field->next)) { + append_var_and_value_to_filter(&filter, table_config, field->name, field->value); } ast_str_append(&filter, 0, ")"); @@ -947,18 +941,42 @@ static struct ast_variable **realtime_ldap_base_ap(unsigned int *entries_count_p return vars; } +static struct ast_variable *realtime_arguments_to_fields(va_list ap) +{ + struct ast_variable *fields = NULL; + const char *newparam, *newval; + + while ((newparam = va_arg(ap, const char *))) { + struct ast_variable *field; + + newval = va_arg(ap, const char *); + if (!(field = ast_variable_new(newparam, newval, ""))) { + ast_variables_destroy(fields); + return NULL; + } + + field->next = fields; + fields = field; + } + + return fields; +} + /*! \brief same as realtime_ldap_base_ap but take variable arguments count list */ static struct ast_variable **realtime_ldap_base(unsigned int *entries_count_ptr, const char *basedn, const char *table_name, ...) { + RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy); struct ast_variable **vars = NULL; va_list ap; va_start(ap, table_name); - vars = realtime_ldap_base_ap(entries_count_ptr, basedn, table_name, ap); + fields = realtime_arguments_to_fields(ap); va_end(ap); + vars = realtime_ldap_base_ap(entries_count_ptr, basedn, table_name, fields); + return vars; } @@ -967,9 +985,9 @@ static struct ast_variable **realtime_ldap_base(unsigned int *entries_count_ptr, * For Realtime Dynamic(i.e., switch, queues, and directory) */ static struct ast_variable *realtime_ldap(const char *basedn, - const char *table_name, va_list ap) + const char *table_name, const struct ast_variable *fields) { - struct ast_variable **vars = realtime_ldap_base_ap(NULL, basedn, table_name, ap); + struct ast_variable **vars = realtime_ldap_base_ap(NULL, basedn, table_name, fields); struct ast_variable *var = NULL; if (vars) { @@ -1002,22 +1020,19 @@ static struct ast_variable *realtime_ldap(const char *basedn, * I think this function returns Realtime dynamic objects */ static struct ast_config *realtime_multi_ldap(const char *basedn, - const char *table_name, va_list ap) + const char *table_name, const struct ast_variable *fields) { char *op; const char *initfield = NULL; - const char *newparam, *newval; struct ast_variable **vars = - realtime_ldap_base_ap(NULL, basedn, table_name, ap); + realtime_ldap_base_ap(NULL, basedn, table_name, fields); struct ast_config *cfg = NULL; - newparam = va_arg(ap, const char *); - newval = va_arg(ap, const char *); - if (!newparam || !newval) { + if (!fields) { ast_log(LOG_WARNING, "realtime retrieval requires at least 1 parameter and 1 value to search on.\n"); return NULL; } - initfield = ast_strdupa(newparam); + initfield = ast_strdupa(fields->name); if ((op = strchr(initfield, ' '))) { *op = '\0'; } @@ -1207,13 +1222,13 @@ static struct ast_config *config_ldap(const char *basedn, const char *table_name /* \brief Function to update a set of values in ldap static mode */ static int update_ldap(const char *basedn, const char *table_name, const char *attribute, - const char *lookup, va_list ap) + const char *lookup, const struct ast_variable *fields) { int error = 0; LDAPMessage *ldap_entry = NULL; LDAPMod **ldap_mods; - const char *newparam = NULL; - const char *newval = NULL; + const char *newparam; + const struct ast_variable *field = fields; char *dn; int num_entries = 0; int i = 0; @@ -1270,10 +1285,8 @@ static int update_ldap(const char *basedn, const char *table_name, const char *a /* Create the modification array with the parameter/value pairs we were given, * if there are several parameters with the same name, we collect them into * one parameter/value pair and delimit them with a semicolon */ - newparam = va_arg(ap, const char *); - newparam = convert_attribute_name_to_ldap(table_config, newparam); - newval = va_arg(ap, const char *); - if (!newparam || !newval) { + newparam = convert_attribute_name_to_ldap(table_config, field->name); + if (!newparam) { ast_log(LOG_WARNING, "LINE(%d): need at least one parameter to modify.\n", __LINE__); return -1; } @@ -1286,19 +1299,18 @@ static int update_ldap(const char *basedn, const char *table_name, const char *a ldap_mods[0]->mod_type = ldap_strdup(newparam); ldap_mods[0]->mod_values = ast_calloc(sizeof(char *), 2); - ldap_mods[0]->mod_values[0] = ldap_strdup(newval); + ldap_mods[0]->mod_values[0] = ldap_strdup(field->value); - while ((newparam = va_arg(ap, const char *))) { - newparam = convert_attribute_name_to_ldap(table_config, newparam); - newval = va_arg(ap, const char *); + while ((field = field->next)) { + newparam = convert_attribute_name_to_ldap(table_config, field->name); mod_exists = 0; for (i = 0; i < mods_size - 1; i++) { if (ldap_mods[i]&& !strcmp(ldap_mods[i]->mod_type, newparam)) { /* We have the parameter allready, adding the value as a semicolon delimited value */ - ldap_mods[i]->mod_values[0] = ldap_memrealloc(ldap_mods[i]->mod_values[0], sizeof(char) * (strlen(ldap_mods[i]->mod_values[0]) + strlen(newval) + 2)); + ldap_mods[i]->mod_values[0] = ldap_memrealloc(ldap_mods[i]->mod_values[0], sizeof(char) * (strlen(ldap_mods[i]->mod_values[0]) + strlen(field->value) + 2)); strcat(ldap_mods[i]->mod_values[0], ";"); - strcat(ldap_mods[i]->mod_values[0], newval); + strcat(ldap_mods[i]->mod_values[0], field->value); mod_exists = 1; break; } @@ -1315,14 +1327,14 @@ static int update_ldap(const char *basedn, const char *table_name, const char *a ldap_mods[mods_size - 2]->mod_type = ldap_memcalloc(sizeof(char), strlen(newparam) + 1); strcpy(ldap_mods[mods_size - 2]->mod_type, newparam); - if (strlen(newval) == 0) { + if (strlen(field->value) == 0) { ldap_mods[mods_size - 2]->mod_op = LDAP_MOD_DELETE; } else { ldap_mods[mods_size - 2]->mod_op = LDAP_MOD_REPLACE; ldap_mods[mods_size - 2]->mod_values = ldap_memcalloc(sizeof(char *), 2); - ldap_mods[mods_size - 2]->mod_values[0] = ldap_memcalloc(sizeof(char), strlen(newval) + 1); - strcpy(ldap_mods[mods_size - 2]->mod_values[0], newval); + ldap_mods[mods_size - 2]->mod_values[0] = ldap_memcalloc(sizeof(char), strlen(field->value) + 1); + strcpy(ldap_mods[mods_size - 2]->mod_values[0], field->value); } } } @@ -1390,13 +1402,13 @@ static int update_ldap(const char *basedn, const char *table_name, const char *a return num_entries; } -static int update2_ldap(const char *basedn, const char *table_name, va_list ap) +static int update2_ldap(const char *basedn, const char *table_name, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields) { int error = 0; LDAPMessage *ldap_entry = NULL; LDAPMod **ldap_mods; - const char *newparam = NULL; - const char *newval = NULL; + const char *newparam; + const struct ast_variable *field; char *dn; int num_entries = 0; int i = 0; @@ -1448,19 +1460,17 @@ static int update2_ldap(const char *basedn, const char *table_name, va_list ap) } /* Get multiple lookup keyfields and values */ - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - append_var_and_value_to_filter(&filter, table_config, newparam, newval); + for (field = lookup_fields; field; field = field->next) { + append_var_and_value_to_filter(&filter, table_config, field->name, field->value); } ast_str_append(&filter, 0, ")"); /* Create the modification array with the parameter/value pairs we were given, * if there are several parameters with the same name, we collect them into * one parameter/value pair and delimit them with a semicolon */ - newparam = va_arg(ap, const char *); - newparam = convert_attribute_name_to_ldap(table_config, newparam); - newval = va_arg(ap, const char *); - if (!newparam || !newval) { + field = update_fields; + newparam = convert_attribute_name_to_ldap(table_config, field->name); + if (!newparam) { ast_log(LOG_WARNING, "LINE(%d): need at least one parameter to modify.\n", __LINE__); ast_free(filter); ast_free(clean_basedn); @@ -1476,20 +1486,19 @@ static int update2_ldap(const char *basedn, const char *table_name, va_list ap) strcpy(ldap_mods[0]->mod_type, newparam); ldap_mods[0]->mod_values = ast_calloc(sizeof(char), 2); - ldap_mods[0]->mod_values[0] = ast_calloc(sizeof(char), strlen(newval) + 1); - strcpy(ldap_mods[0]->mod_values[0], newval); + ldap_mods[0]->mod_values[0] = ast_calloc(sizeof(char), strlen(field->value) + 1); + strcpy(ldap_mods[0]->mod_values[0], field->value); - while ((newparam = va_arg(ap, const char *))) { - newparam = convert_attribute_name_to_ldap(table_config, newparam); - newval = va_arg(ap, const char *); + while ((field = field->next)) { + newparam = convert_attribute_name_to_ldap(table_config, field->name); mod_exists = 0; for (i = 0; i < mods_size - 1; i++) { if (ldap_mods[i]&& !strcmp(ldap_mods[i]->mod_type, newparam)) { /* We have the parameter allready, adding the value as a semicolon delimited value */ - ldap_mods[i]->mod_values[0] = ast_realloc(ldap_mods[i]->mod_values[0], sizeof(char) * (strlen(ldap_mods[i]->mod_values[0]) + strlen(newval) + 2)); + ldap_mods[i]->mod_values[0] = ast_realloc(ldap_mods[i]->mod_values[0], sizeof(char) * (strlen(ldap_mods[i]->mod_values[0]) + strlen(field->value) + 2)); strcat(ldap_mods[i]->mod_values[0], ";"); - strcat(ldap_mods[i]->mod_values[0], newval); + strcat(ldap_mods[i]->mod_values[0], field->value); mod_exists = 1; break; } @@ -1508,8 +1517,8 @@ static int update2_ldap(const char *basedn, const char *table_name, va_list ap) strcpy(ldap_mods[mods_size - 2]->mod_type, newparam); ldap_mods[mods_size - 2]->mod_values = ast_calloc(sizeof(char *), 2); - ldap_mods[mods_size - 2]->mod_values[0] = ast_calloc(sizeof(char), strlen(newval) + 1); - strcpy(ldap_mods[mods_size - 2]->mod_values[0], newval); + ldap_mods[mods_size - 2]->mod_values[0] = ast_calloc(sizeof(char), strlen(field->value) + 1); + strcpy(ldap_mods[mods_size - 2]->mod_values[0], field->value); } } /* freeing ldap_mods further down */ diff --git a/res/res_config_odbc.c b/res/res_config_odbc.c index 66e15a02b..524912a9e 100644 --- a/res/res_config_odbc.c +++ b/res/res_config_odbc.c @@ -55,7 +55,7 @@ struct custom_prepare_struct { AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(encoding)[256]; ); - va_list ap; + const struct ast_variable *fields; unsigned long long skip; }; @@ -73,10 +73,9 @@ static SQLHSTMT custom_prepare(struct odbc_obj *obj, void *data) { int res, x = 1, count = 0; struct custom_prepare_struct *cps = data; - const char *newparam, *newval; + const struct ast_variable *field; char encodebuf[1024]; SQLHSTMT stmt; - va_list ap; res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { @@ -93,14 +92,14 @@ static SQLHSTMT custom_prepare(struct odbc_obj *obj, void *data) return NULL; } - va_copy(ap, cps->ap); - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); + for (field = cps->fields; field; field = field->next) { + const char *newval = field->value; + if ((1LL << count++) & cps->skip) { - ast_debug(1, "Skipping field '%s'='%s' (%llo/%llo)\n", newparam, newval, 1LL << (count - 1), cps->skip); + ast_debug(1, "Skipping field '%s'='%s' (%llo/%llo)\n", field->name, newval, 1LL << (count - 1), cps->skip); continue; } - ast_debug(1, "Parameter %d ('%s') = '%s'\n", x, newparam, newval); + ast_debug(1, "Parameter %d ('%s') = '%s'\n", x, field->name, newval); if (strchr(newval, ';') || strchr(newval, '^')) { char *eptr = encodebuf; const char *vptr = newval; @@ -123,7 +122,6 @@ static SQLHSTMT custom_prepare(struct odbc_obj *obj, void *data) } SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL); } - va_end(ap); if (!ast_strlen_zero(cps->extra)) SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(cps->extra), 0, (void *)cps->extra, 0, NULL); @@ -143,7 +141,7 @@ static SQLHSTMT custom_prepare(struct odbc_obj *obj, void *data) * \retval var on success * \retval NULL on failure */ -static struct ast_variable *realtime_odbc(const char *database, const char *table, va_list ap) +static struct ast_variable *realtime_odbc(const char *database, const char *table, const struct ast_variable *fields) { struct odbc_obj *obj; SQLHSTMT stmt; @@ -151,7 +149,7 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl char coltitle[256]; char rowdata[2048]; char *op; - const char *newparam; + const struct ast_variable *field = fields; char *stringp; char *chunk; SQLSMALLINT collen; @@ -164,10 +162,13 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl SQLSMALLINT decimaldigits; SQLSMALLINT nullable; SQLLEN indicator; - va_list aq; - struct custom_prepare_struct cps = { .sql = sql }; + struct custom_prepare_struct cps = { .sql = sql, .fields = fields, }; struct ast_flags connected_flag = { RES_ODBC_CONNECTED }; + if (!fields) { + return NULL; + } + if (ast_string_field_init(&cps, 256)) { return NULL; } @@ -185,29 +186,16 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl return NULL; } - va_copy(aq, ap); - newparam = va_arg(aq, const char *); - if (!newparam) { - va_end(aq); - ast_odbc_release_obj(obj); - ast_string_field_free_memory(&cps); - return NULL; - } - va_arg(aq, const char *); - op = !strchr(newparam, ' ') ? " =" : ""; - snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?%s", table, newparam, op, - strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : ""); - while((newparam = va_arg(aq, const char *))) { - op = !strchr(newparam, ' ') ? " =" : ""; - snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?%s", newparam, op, - strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : ""); - va_arg(aq, const char *); + op = !strchr(field->name, ' ') ? " =" : ""; + snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?%s", table, field->name, op, + strcasestr(field->name, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : ""); + while ((field = field->next)) { + op = !strchr(field->name, ' ') ? " =" : ""; + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?%s", field->name, op, + strcasestr(field->name, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : ""); } - va_end(aq); - va_copy(cps.ap, ap); stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps); - va_end(cps.ap); if (!stmt) { ast_odbc_release_obj(obj); @@ -310,7 +298,7 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl * \retval var on success * \retval NULL on failure */ -static struct ast_config *realtime_multi_odbc(const char *database, const char *table, va_list ap) +static struct ast_config *realtime_multi_odbc(const char *database, const char *table, const struct ast_variable *fields) { struct odbc_obj *obj; SQLHSTMT stmt; @@ -319,7 +307,7 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char * char rowdata[2048]; const char *initfield; char *op; - const char *newparam; + const struct ast_variable *field = fields; char *stringp; char *chunk; SQLSMALLINT collen; @@ -335,51 +323,36 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char * SQLSMALLINT decimaldigits; SQLSMALLINT nullable; SQLLEN indicator; - struct custom_prepare_struct cps = { .sql = sql }; - va_list aq; + struct custom_prepare_struct cps = { .sql = sql, .fields = fields, }; - if (!table || ast_string_field_init(&cps, 256)) { + if (!table || !field || ast_string_field_init(&cps, 256)) { return NULL; } - obj = ast_odbc_request_obj2(database, connected_flag); if (!obj) { ast_string_field_free_memory(&cps); return NULL; } - va_copy(aq, ap); - newparam = va_arg(aq, const char *); - if (!newparam) { - va_end(aq); - ast_odbc_release_obj(obj); - ast_string_field_free_memory(&cps); - return NULL; - } - - initfield = ast_strdupa(newparam); + initfield = ast_strdupa(field->name); if ((op = strchr(initfield, ' '))) { *op = '\0'; } - va_arg(aq, const char *); - op = !strchr(newparam, ' ') ? " =" : ""; - snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?%s", table, newparam, op, - strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : ""); - while((newparam = va_arg(aq, const char *))) { - op = !strchr(newparam, ' ') ? " =" : ""; - snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?%s", newparam, op, - strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : ""); - va_arg(aq, const char *); + field = field->next; + op = !strchr(field->name, ' ') ? " =" : ""; + snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?%s", table, field->name, op, + strcasestr(field->name, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : ""); + while ((field = field->next)) { + op = !strchr(field->name, ' ') ? " =" : ""; + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?%s", field->name, op, + strcasestr(field->name, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : ""); } - va_end(aq); snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield); - va_copy(cps.ap, ap); stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps); - va_end(cps.ap); if (!stmt) { ast_odbc_release_obj(obj); @@ -478,21 +451,20 @@ next_sql_fetch:; * \retval number of rows affected * \retval -1 on failure */ -static int update_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap) +static int update_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, const struct ast_variable *fields) { struct odbc_obj *obj; SQLHSTMT stmt; char sql[256]; SQLLEN rowcount=0; - const char *newparam; + const struct ast_variable *field = fields; int res, count = 1; - va_list aq; - struct custom_prepare_struct cps = { .sql = sql, .extra = lookup }; + struct custom_prepare_struct cps = { .sql = sql, .extra = lookup, .fields = fields, }; struct odbc_cache_tables *tableptr; struct odbc_cache_columns *column = NULL; struct ast_flags connected_flag = { RES_ODBC_CONNECTED }; - if (!table) { + if (!table || !field) { return -1; } @@ -507,47 +479,32 @@ static int update_odbc(const char *database, const char *table, const char *keyf return -1; } - va_copy(aq, ap); - newparam = va_arg(aq, const char *); - if (!newparam) { - va_end(aq); - ast_odbc_release_obj(obj); - ast_odbc_release_table(tableptr); - ast_string_field_free_memory(&cps); - return -1; - } - va_arg(aq, const char *); - - if (tableptr && !ast_odbc_find_column(tableptr, newparam)) { - ast_log(LOG_WARNING, "Key field '%s' does not exist in table '%s@%s'. Update will fail\n", newparam, table, database); + if (tableptr && !ast_odbc_find_column(tableptr, field->name)) { + ast_log(LOG_WARNING, "Key field '%s' does not exist in table '%s@%s'. Update will fail\n", field->name, table, database); } - snprintf(sql, sizeof(sql), "UPDATE %s SET %s=?", table, newparam); - while((newparam = va_arg(aq, const char *))) { - va_arg(aq, const char *); - if ((tableptr && (column = ast_odbc_find_column(tableptr, newparam))) || count > 63) { + snprintf(sql, sizeof(sql), "UPDATE %s SET %s=?", table, field->name); + while ((field = field->next)) { + if ((tableptr && (column = ast_odbc_find_column(tableptr, field->name))) || count > 63) { /* NULL test for integer-based columns */ - if (ast_strlen_zero(newparam) && tableptr && column && column->nullable && count < 64 && + if (ast_strlen_zero(field->name) && tableptr && column && column->nullable && count < 64 && (column->type == SQL_INTEGER || column->type == SQL_BIGINT || column->type == SQL_SMALLINT || column->type == SQL_TINYINT || column->type == SQL_NUMERIC || column->type == SQL_DECIMAL)) { - snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s=NULL", newparam); + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s=NULL", field->name); cps.skip |= (1LL << count); } else { - snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s=?", newparam); + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s=?", field->name); } } else { /* the column does not exist in the table */ cps.skip |= (1LL << count); } count++; } - va_end(aq); snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s=?", keyfield); ast_odbc_release_table(tableptr); - va_copy(cps.ap, ap); stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps); - va_end(cps.ap); if (!stmt) { ast_odbc_release_obj(obj); @@ -575,17 +532,17 @@ static int update_odbc(const char *database, const char *table, const char *keyf struct update2_prepare_struct { const char *database; const char *table; - va_list ap; + const struct ast_variable *lookup_fields; + const struct ast_variable *update_fields; }; static SQLHSTMT update2_prepare(struct odbc_obj *obj, void *data) { int res, x = 1, first = 1; struct update2_prepare_struct *ups = data; - const char *newparam, *newval; + const struct ast_variable *field; struct ast_str *sql = ast_str_thread_get(&sql_buf, 16); SQLHSTMT stmt; - va_list ap; struct odbc_cache_tables *tableptr = ast_odbc_find_table(ups->database, ups->table); if (!sql) { @@ -609,45 +566,30 @@ static SQLHSTMT update2_prepare(struct odbc_obj *obj, void *data) ast_str_set(&sql, 0, "UPDATE %s SET ", ups->table); - /* Start by finding the second set of parameters */ - va_copy(ap, ups->ap); - - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - } - - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - if (ast_odbc_find_column(tableptr, newparam)) { - ast_str_append(&sql, 0, "%s%s=? ", first ? "" : ", ", newparam); - SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL); + for (field = ups->update_fields; field; field = field->next) { + if (ast_odbc_find_column(tableptr, field->name)) { + ast_str_append(&sql, 0, "%s%s=? ", first ? "" : ", ", field->name); + SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(field->name), 0, (void *)field->value, 0, NULL); first = 0; } else { - ast_log(LOG_NOTICE, "Not updating column '%s' in '%s@%s' because that column does not exist!\n", newparam, ups->table, ups->database); + ast_log(LOG_NOTICE, "Not updating column '%s' in '%s@%s' because that column does not exist!\n", field->name, ups->table, ups->database); } } - va_end(ap); ast_str_append(&sql, 0, "WHERE"); first = 1; - /* Restart search, because we need to add the search parameters */ - va_copy(ap, ups->ap); - - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - if (!ast_odbc_find_column(tableptr, newparam)) { - va_end(ap); - ast_log(LOG_ERROR, "One or more of the criteria columns '%s' on '%s@%s' for this update does not exist!\n", newparam, ups->table, ups->database); + for (field = ups->lookup_fields; field; field = field->next) { + if (!ast_odbc_find_column(tableptr, field->name)) { + ast_log(LOG_ERROR, "One or more of the criteria columns '%s' on '%s@%s' for this update does not exist!\n", field->name, ups->table, ups->database); ast_odbc_release_table(tableptr); SQLFreeHandle(SQL_HANDLE_STMT, stmt); return NULL; } - ast_str_append(&sql, 0, "%s %s=?", first ? "" : " AND", newparam); - SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL); + ast_str_append(&sql, 0, "%s %s=?", first ? "" : " AND", field->name); + SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(field->value), 0, (void *)field->value, 0, NULL); first = 0; } - va_end(ap); /* Done with the table metadata */ ast_odbc_release_table(tableptr); @@ -676,11 +618,11 @@ static SQLHSTMT update2_prepare(struct odbc_obj *obj, void *data) * \retval number of rows affected * \retval -1 on failure */ -static int update2_odbc(const char *database, const char *table, va_list ap) +static int update2_odbc(const char *database, const char *table, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields) { struct odbc_obj *obj; SQLHSTMT stmt; - struct update2_prepare_struct ups = { .database = database, .table = table, }; + struct update2_prepare_struct ups = { .database = database, .table = table, .lookup_fields = lookup_fields, .update_fields = update_fields, }; struct ast_str *sql; int res; SQLLEN rowcount = 0; @@ -689,13 +631,10 @@ static int update2_odbc(const char *database, const char *table, va_list ap) return -1; } - va_copy(ups.ap, ap); if (!(stmt = ast_odbc_prepare_and_execute(obj, update2_prepare, &ups))) { - va_end(ups.ap); ast_odbc_release_obj(obj); return -1; } - va_end(ups.ap); res = SQLRowCount(stmt, &rowcount); SQLFreeHandle(SQL_HANDLE_STMT, stmt); @@ -728,7 +667,7 @@ static int update2_odbc(const char *database, const char *table, va_list ap) * \retval number of rows affected * \retval -1 on failure */ -static int store_odbc(const char *database, const char *table, va_list ap) +static int store_odbc(const char *database, const char *table, const struct ast_variable *fields) { struct odbc_obj *obj; SQLHSTMT stmt; @@ -736,13 +675,12 @@ static int store_odbc(const char *database, const char *table, va_list ap) char keys[256]; char vals[256]; SQLLEN rowcount=0; - const char *newparam; + const struct ast_variable *field = fields; int res; - va_list aq; - struct custom_prepare_struct cps = { .sql = sql, .extra = NULL }; + struct custom_prepare_struct cps = { .sql = sql, .extra = NULL, .fields = fields, }; struct ast_flags connected_flag = { RES_ODBC_CONNECTED }; - if (!table) { + if (!table || !field) { return -1; } @@ -751,29 +689,15 @@ static int store_odbc(const char *database, const char *table, va_list ap) return -1; } - va_copy(aq, ap); - - newparam = va_arg(aq, const char *); - if (!newparam) { - va_end(aq); - ast_odbc_release_obj(obj); - return -1; - } - va_arg(aq, const char *); - snprintf(keys, sizeof(keys), "%s", newparam); + snprintf(keys, sizeof(keys), "%s", field->name); ast_copy_string(vals, "?", sizeof(vals)); - while ((newparam = va_arg(aq, const char *))) { - snprintf(keys + strlen(keys), sizeof(keys) - strlen(keys), ", %s", newparam); + while ((field = field->next)) { + snprintf(keys + strlen(keys), sizeof(keys) - strlen(keys), ", %s", field->name); snprintf(vals + strlen(vals), sizeof(vals) - strlen(vals), ", ?"); - va_arg(aq, const char *); } - va_end(aq); snprintf(sql, sizeof(sql), "INSERT INTO %s (%s) VALUES (%s)", table, keys, vals); - - va_copy(cps.ap, ap); stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps); - va_end(cps.ap); if (!stmt) { ast_odbc_release_obj(obj); @@ -810,16 +734,15 @@ static int store_odbc(const char *database, const char *table, va_list ap) * \retval number of rows affected * \retval -1 on failure */ -static int destroy_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap) +static int destroy_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, const struct ast_variable *fields) { struct odbc_obj *obj; SQLHSTMT stmt; char sql[256]; SQLLEN rowcount=0; - const char *newparam; + const struct ast_variable *field; int res; - va_list aq; - struct custom_prepare_struct cps = { .sql = sql, .extra = lookup }; + struct custom_prepare_struct cps = { .sql = sql, .extra = lookup, .fields = fields, }; struct ast_flags connected_flag = { RES_ODBC_CONNECTED }; if (!table) { @@ -833,17 +756,12 @@ static int destroy_odbc(const char *database, const char *table, const char *key snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE ", table); - va_copy(aq, ap); - while((newparam = va_arg(aq, const char *))) { - snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=? AND ", newparam); - va_arg(aq, const char *); + for (field = fields; field; field = field->next) { + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=? AND ", field->name); } - va_end(aq); snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=?", keyfield); - va_copy(cps.ap, ap); stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps); - va_end(cps.ap); if (!stmt) { ast_odbc_release_obj(obj); diff --git a/res/res_config_pgsql.c b/res/res_config_pgsql.c index e3b605812..5af753651 100644 --- a/res/res_config_pgsql.c +++ b/res/res_config_pgsql.c @@ -418,7 +418,7 @@ static struct columns *find_column(struct tables *t, const char *colname) return NULL; } -static struct ast_variable *realtime_pgsql(const char *database, const char *tablename, va_list ap) +static struct ast_variable *realtime_pgsql(const char *database, const char *tablename, const struct ast_variable *fields) { PGresult *result = NULL; int num_rows = 0, pgresult; @@ -427,7 +427,7 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab char *stringp; char *chunk; char *op; - const char *newparam, *newval; + const struct ast_variable *field = fields; struct ast_variable *var = NULL, *prev = NULL; /* @@ -442,9 +442,7 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab } /* Get the first parameter and first value in our list of passed paramater/value pairs */ - newparam = va_arg(ap, const char *); - newval = va_arg(ap, const char *); - if (!newparam || !newval) { + if (!field) { ast_log(LOG_WARNING, "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n"); if (pgsqlConn) { @@ -456,29 +454,28 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab /* Create the first part of the query using the first parameter/value pairs we just extracted If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ - op = strchr(newparam, ' ') ? "" : " ="; + op = strchr(field->name, ' ') ? "" : " ="; - ESCAPE_STRING(escapebuf, newval); + ESCAPE_STRING(escapebuf, field->value); if (pgresult) { - ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", newval); + ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value); return NULL; } - ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", tablename, newparam, op, ast_str_buffer(escapebuf)); - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - if (!strchr(newparam, ' ')) + ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", tablename, field->name, op, ast_str_buffer(escapebuf)); + while ((field = field->next)) { + if (!strchr(field->name, ' ')) op = " ="; else op = ""; - ESCAPE_STRING(escapebuf, newval); + ESCAPE_STRING(escapebuf, field->value); if (pgresult) { - ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", newval); + ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value); return NULL; } - ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(escapebuf)); + ast_str_append(&sql, 0, " AND %s%s '%s'", field->name, op, ast_str_buffer(escapebuf)); } /* We now have our complete statement; Lets connect to the server and execute it. */ @@ -536,17 +533,17 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab return var; } -static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, va_list ap) +static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, const struct ast_variable *fields) { PGresult *result = NULL; int num_rows = 0, pgresult; struct ast_str *sql = ast_str_thread_get(&sql_buf, 100); struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100); + const struct ast_variable *field = fields; const char *initfield = NULL; char *stringp; char *chunk; char *op; - const char *newparam, *newval; struct ast_variable *var = NULL; struct ast_config *cfg = NULL; struct ast_category *cat = NULL; @@ -566,9 +563,7 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char return NULL; /* Get the first parameter and first value in our list of passed paramater/value pairs */ - newparam = va_arg(ap, const char *); - newval = va_arg(ap, const char *); - if (!newparam || !newval) { + if (!field) { ast_log(LOG_WARNING, "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n"); if (pgsqlConn) { @@ -579,7 +574,7 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char return NULL; } - initfield = ast_strdupa(newparam); + initfield = ast_strdupa(field->name); if ((op = strchr(initfield, ' '))) { *op = '\0'; } @@ -587,34 +582,33 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char /* Create the first part of the query using the first parameter/value pairs we just extracted If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ - if (!strchr(newparam, ' ')) + if (!strchr(field->name, ' ')) op = " ="; else op = ""; - ESCAPE_STRING(escapebuf, newval); + ESCAPE_STRING(escapebuf, field->value); if (pgresult) { - ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", newval); + ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value); ast_config_destroy(cfg); return NULL; } - ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(escapebuf)); - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - if (!strchr(newparam, ' ')) + ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, field->name, op, ast_str_buffer(escapebuf)); + while ((field = field->next)) { + if (!strchr(field->name, ' ')) op = " ="; else op = ""; - ESCAPE_STRING(escapebuf, newval); + ESCAPE_STRING(escapebuf, field->value); if (pgresult) { - ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", newval); + ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value); ast_config_destroy(cfg); return NULL; } - ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(escapebuf)); + ast_str_append(&sql, 0, " AND %s%s '%s'", field->name, op, ast_str_buffer(escapebuf)); } if (initfield) { @@ -696,11 +690,11 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char } static int update_pgsql(const char *database, const char *tablename, const char *keyfield, - const char *lookup, va_list ap) + const char *lookup, const struct ast_variable *fields) { PGresult *result = NULL; int numrows = 0, pgresult; - const char *newparam, *newval; + const struct ast_variable *field = fields; struct ast_str *sql = ast_str_thread_get(&sql_buf, 100); struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100); struct tables *table; @@ -723,9 +717,7 @@ static int update_pgsql(const char *database, const char *tablename, const char } /* Get the first parameter and first value in our list of passed paramater/value pairs */ - newparam = va_arg(ap, const char *); - newval = va_arg(ap, const char *); - if (!newparam || !newval) { + if (!field) { ast_log(LOG_WARNING, "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n"); if (pgsqlConn) { @@ -738,13 +730,13 @@ static int update_pgsql(const char *database, const char *tablename, const char /* Check that the column exists in the table */ AST_LIST_TRAVERSE(&table->columns, column, list) { - if (strcmp(column->name, newparam) == 0) { + if (strcmp(column->name, field->name) == 0) { break; } } if (!column) { - ast_log(LOG_ERROR, "PostgreSQL RealTime: Updating on column '%s', but that column does not exist within the table '%s'!\n", newparam, tablename); + ast_log(LOG_ERROR, "PostgreSQL RealTime: Updating on column '%s', but that column does not exist within the table '%s'!\n", field->name, tablename); release_table(table); return -1; } @@ -752,30 +744,28 @@ static int update_pgsql(const char *database, const char *tablename, const char /* Create the first part of the query using the first parameter/value pairs we just extracted If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ - ESCAPE_STRING(escapebuf, newval); + ESCAPE_STRING(escapebuf, field->value); if (pgresult) { - ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", newval); + ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value); release_table(table); return -1; } - ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, newparam, ast_str_buffer(escapebuf)); + ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, field->name, ast_str_buffer(escapebuf)); - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - - if (!find_column(table, newparam)) { - ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename); + while ((field = field->next)) { + if (!find_column(table, field->name)) { + ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s', but column does not exist!\n", field->name, tablename); continue; } - ESCAPE_STRING(escapebuf, newval); + ESCAPE_STRING(escapebuf, field->value); if (pgresult) { - ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", newval); + ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value); release_table(table); return -1; } - ast_str_append(&sql, 0, ", %s = '%s'", newparam, ast_str_buffer(escapebuf)); + ast_str_append(&sql, 0, ", %s = '%s'", field->name, ast_str_buffer(escapebuf)); } release_table(table); @@ -828,12 +818,12 @@ static int update_pgsql(const char *database, const char *tablename, const char return -1; } -static int update2_pgsql(const char *database, const char *tablename, va_list ap) +static int update2_pgsql(const char *database, const char *tablename, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields) { PGresult *result = NULL; int numrows = 0, pgresult, first = 1; struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 16); - const char *newparam, *newval; + const struct ast_variable *field; struct ast_str *sql = ast_str_thread_get(&sql_buf, 100); struct ast_str *where = ast_str_thread_get(&where_buf, 100); struct tables *table; @@ -862,22 +852,21 @@ static int update2_pgsql(const char *database, const char *tablename, va_list ap ast_str_set(&sql, 0, "UPDATE %s SET ", tablename); ast_str_set(&where, 0, "WHERE"); - while ((newparam = va_arg(ap, const char *))) { - if (!find_column(table, newparam)) { - ast_log(LOG_ERROR, "Attempted to update based on criteria column '%s' (%s@%s), but that column does not exist!\n", newparam, tablename, database); + for (field = lookup_fields; field; field = field->next) { + if (!find_column(table, field->name)) { + ast_log(LOG_ERROR, "Attempted to update based on criteria column '%s' (%s@%s), but that column does not exist!\n", field->name, tablename, database); release_table(table); return -1; } - newval = va_arg(ap, const char *); - ESCAPE_STRING(escapebuf, newval); + ESCAPE_STRING(escapebuf, field->value); if (pgresult) { - ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", newval); + ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value); release_table(table); ast_free(sql); return -1; } - ast_str_append(&where, 0, "%s %s='%s'", first ? "" : " AND", newparam, ast_str_buffer(escapebuf)); + ast_str_append(&where, 0, "%s %s='%s'", first ? "" : " AND", field->name, ast_str_buffer(escapebuf)); first = 0; } @@ -894,24 +883,22 @@ static int update2_pgsql(const char *database, const char *tablename, va_list ap /* Now retrieve the columns to update */ first = 1; - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - + for (field = update_fields; field; field = field->next) { /* If the column is not within the table, then skip it */ - if (!find_column(table, newparam)) { - ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s@%s', but column does not exist!\n", newparam, tablename, database); + if (!find_column(table, field->name)) { + ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s@%s', but column does not exist!\n", field->name, tablename, database); continue; } - ESCAPE_STRING(escapebuf, newval); + ESCAPE_STRING(escapebuf, field->value); if (pgresult) { - ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", newval); + ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value); release_table(table); ast_free(sql); return -1; } - ast_str_append(&sql, 0, "%s %s='%s'", first ? "" : ",", newparam, ast_str_buffer(escapebuf)); + ast_str_append(&sql, 0, "%s %s='%s'", first ? "" : ",", field->name, ast_str_buffer(escapebuf)); } release_table(table); @@ -943,7 +930,7 @@ static int update2_pgsql(const char *database, const char *tablename, va_list ap return -1; } -static int store_pgsql(const char *database, const char *table, va_list ap) +static int store_pgsql(const char *database, const char *table, const struct ast_variable *fields) { PGresult *result = NULL; Oid insertid; @@ -951,7 +938,7 @@ static int store_pgsql(const char *database, const char *table, va_list ap) struct ast_str *sql1 = ast_str_thread_get(&sql_buf, 256); struct ast_str *sql2 = ast_str_thread_get(&where_buf, 256); int pgresult; - const char *newparam, *newval; + const struct ast_variable *field = fields; /* * Ignore database from the extconfig.conf since it was @@ -965,9 +952,7 @@ static int store_pgsql(const char *database, const char *table, va_list ap) } /* Get the first parameter and first value in our list of passed paramater/value pairs */ - newparam = va_arg(ap, const char *); - newval = va_arg(ap, const char *); - if (!newparam || !newval) { + if (!field) { ast_log(LOG_WARNING, "PostgreSQL RealTime: Realtime storage requires at least 1 parameter and 1 value to store.\n"); if (pgsqlConn) { @@ -986,15 +971,14 @@ static int store_pgsql(const char *database, const char *table, va_list ap) /* Create the first part of the query using the first parameter/value pairs we just extracted If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ - ESCAPE_STRING(buf, newparam); + ESCAPE_STRING(buf, field->name); ast_str_set(&sql1, 0, "INSERT INTO %s (%s", table, ast_str_buffer(buf)); - ESCAPE_STRING(buf, newval); + ESCAPE_STRING(buf, field->value); ast_str_set(&sql2, 0, ") VALUES ('%s'", ast_str_buffer(buf)); - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - ESCAPE_STRING(buf, newparam); + while ((field = field->next)) { + ESCAPE_STRING(buf, field->name); ast_str_append(&sql1, 0, ", %s", ast_str_buffer(buf)); - ESCAPE_STRING(buf, newval); + ESCAPE_STRING(buf, field->value); ast_str_append(&sql2, 0, ", '%s'", ast_str_buffer(buf)); } ast_str_append(&sql1, 0, "%s)", ast_str_buffer(sql2)); @@ -1024,14 +1008,14 @@ static int store_pgsql(const char *database, const char *table, va_list ap) return -1; } -static int destroy_pgsql(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap) +static int destroy_pgsql(const char *database, const char *table, const char *keyfield, const char *lookup, const struct ast_variable *fields) { PGresult *result = NULL; int numrows = 0; int pgresult; struct ast_str *sql = ast_str_thread_get(&sql_buf, 256); struct ast_str *buf1 = ast_str_thread_get(&where_buf, 60), *buf2 = ast_str_thread_get(&escapebuf_buf, 60); - const char *newparam, *newval; + const struct ast_variable *field; /* * Ignore database from the extconfig.conf since it was @@ -1072,10 +1056,9 @@ static int destroy_pgsql(const char *database, const char *table, const char *ke ESCAPE_STRING(buf1, keyfield); ESCAPE_STRING(buf2, lookup); ast_str_set(&sql, 0, "DELETE FROM %s WHERE %s = '%s'", table, ast_str_buffer(buf1), ast_str_buffer(buf2)); - while ((newparam = va_arg(ap, const char *))) { - newval = va_arg(ap, const char *); - ESCAPE_STRING(buf1, newparam); - ESCAPE_STRING(buf2, newval); + for (field = fields; field; field = field->next) { + ESCAPE_STRING(buf1, field->name); + ESCAPE_STRING(buf2, field->value); ast_str_append(&sql, 0, " AND %s = '%s'", ast_str_buffer(buf1), ast_str_buffer(buf2)); } diff --git a/res/res_config_sqlite3.c b/res/res_config_sqlite3.c index 4b2d36598..0a4b1acac 100644 --- a/res/res_config_sqlite3.c +++ b/res/res_config_sqlite3.c @@ -61,12 +61,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") ***/ static struct ast_config *realtime_sqlite3_load(const char *database, const char *table, const char *configfile, struct ast_config *config, struct ast_flags flags, const char *suggested_include_file, const char *who_asked); -static struct ast_variable *realtime_sqlite3(const char *database, const char *table, va_list ap); -static struct ast_config *realtime_sqlite3_multi(const char *database, const char *table, va_list ap); -static int realtime_sqlite3_update(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap); -static int realtime_sqlite3_update2(const char *database, const char *table, va_list ap); -static int realtime_sqlite3_store(const char *database, const char *table, va_list ap); -static int realtime_sqlite3_destroy(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap); +static struct ast_variable *realtime_sqlite3(const char *database, const char *table, const struct ast_variable *fields); +static struct ast_config *realtime_sqlite3_multi(const char *database, const char *table, const struct ast_variable *fields); +static int realtime_sqlite3_update(const char *database, const char *table, const char *keyfield, const char *entity, const struct ast_variable *fields); +static int realtime_sqlite3_update2(const char *database, const char *table, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields); +static int realtime_sqlite3_store(const char *database, const char *table, const struct ast_variable *fields); +static int realtime_sqlite3_destroy(const char *database, const char *table, const char *keyfield, const char *entity, const struct ast_variable *fields); static int realtime_sqlite3_require(const char *database, const char *table, va_list ap); static int realtime_sqlite3_unload(const char *database, const char *table); @@ -648,10 +648,10 @@ static struct ast_config *realtime_sqlite3_load(const char *database, const char } /*! \brief Helper function for single and multi-row realtime load functions */ -static int realtime_sqlite3_helper(const char *database, const char *table, va_list ap, int is_multi, void *arg) +static int realtime_sqlite3_helper(const char *database, const char *table, const struct ast_variable *fields, int is_multi, void *arg) { struct ast_str *sql; - const char *param, *value; + const struct ast_variable *field; int first = 1; if (ast_strlen_zero(table)) { @@ -663,14 +663,14 @@ static int realtime_sqlite3_helper(const char *database, const char *table, va_l return -1; } - while ((param = va_arg(ap, const char *)) && (value = va_arg(ap, const char *))) { + for (field = fields; field; field = field->next) { if (first) { ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s %s", sqlite3_escape_table(table), - sqlite3_escape_column_op(param), sqlite3_escape_value(value)); + sqlite3_escape_column_op(field->name), sqlite3_escape_value(field->value)); first = 0; } else { - ast_str_append(&sql, 0, " AND %s %s", sqlite3_escape_column_op(param), - sqlite3_escape_value(value)); + ast_str_append(&sql, 0, " AND %s %s", sqlite3_escape_column_op(field->name), + sqlite3_escape_value(field->value)); } } @@ -691,11 +691,11 @@ static int realtime_sqlite3_helper(const char *database, const char *table, va_l /*! \brief Realtime callback for a single row query * \return ast_variable list for single result on success, NULL on empty/failure */ -static struct ast_variable *realtime_sqlite3(const char *database, const char *table, va_list ap) +static struct ast_variable *realtime_sqlite3(const char *database, const char *table, const struct ast_variable *fields) { struct ast_variable *result_row = NULL; - realtime_sqlite3_helper(database, table, ap, 0, &result_row); + realtime_sqlite3_helper(database, table, fields, 0, &result_row); return result_row; } @@ -703,7 +703,7 @@ static struct ast_variable *realtime_sqlite3(const char *database, const char *t /*! \brief Realtime callback for a multi-row query * \return ast_config containing possibly many results on success, NULL on empty/failure */ -static struct ast_config *realtime_sqlite3_multi(const char *database, const char *table, va_list ap) +static struct ast_config *realtime_sqlite3_multi(const char *database, const char *table, const struct ast_variable *fields) { struct ast_config *cfg; @@ -711,7 +711,7 @@ static struct ast_config *realtime_sqlite3_multi(const char *database, const cha return NULL; } - if (realtime_sqlite3_helper(database, table, ap, 1, cfg)) { + if (realtime_sqlite3_helper(database, table, fields, 1, cfg)) { ast_config_destroy(cfg); return NULL; } @@ -722,10 +722,10 @@ static struct ast_config *realtime_sqlite3_multi(const char *database, const cha /*! \brief Realtime callback for updating a row based on a single criteria * \return Number of rows affected or -1 on error */ -static int realtime_sqlite3_update(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap) +static int realtime_sqlite3_update(const char *database, const char *table, const char *keyfield, const char *entity, const struct ast_variable *fields) { struct ast_str *sql; - const char *key, *value; + const struct ast_variable *field; int first = 1, res; if (ast_strlen_zero(table)) { @@ -737,13 +737,13 @@ static int realtime_sqlite3_update(const char *database, const char *table, cons return -1; } - while ((key = va_arg(ap, const char *)) && (value = va_arg(ap, const char *))) { + for (field = fields; field; field = field->next) { if (first) { ast_str_set(&sql, 0, "UPDATE %s SET %s = %s", - sqlite3_escape_table(table), sqlite3_escape_column(key), sqlite3_escape_value(value)); + sqlite3_escape_table(table), sqlite3_escape_column(field->name), sqlite3_escape_value(field->value)); first = 0; } else { - ast_str_append(&sql, 0, ", %s = %s", sqlite3_escape_column(key), sqlite3_escape_value(value)); + ast_str_append(&sql, 0, ", %s = %s", sqlite3_escape_column(field->name), sqlite3_escape_value(field->value)); } } @@ -758,11 +758,11 @@ static int realtime_sqlite3_update(const char *database, const char *table, cons /*! \brief Realtime callback for updating a row based on multiple criteria * \return Number of rows affected or -1 on error */ -static int realtime_sqlite3_update2(const char *database, const char *table, va_list ap) +static int realtime_sqlite3_update2(const char *database, const char *table, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields) { struct ast_str *sql; struct ast_str *where_clause; - const char *key, *value; + const struct ast_variable *field; int first = 1, res; if (ast_strlen_zero(table)) { @@ -779,22 +779,22 @@ static int realtime_sqlite3_update2(const char *database, const char *table, va_ return -1; } - while ((key = va_arg(ap, const char *)) && (value = va_arg(ap, const char *))) { + for (field = lookup_fields; field; field = field->next) { if (first) { - ast_str_set(&where_clause, 0, " WHERE %s %s", sqlite3_escape_column_op(key), sqlite3_escape_value(value)); + ast_str_set(&where_clause, 0, " WHERE %s %s", sqlite3_escape_column_op(field->name), sqlite3_escape_value(field->value)); first = 0; } else { - ast_str_append(&where_clause, 0, " AND %s %s", sqlite3_escape_column_op(key), sqlite3_escape_value(value)); + ast_str_append(&where_clause, 0, " AND %s %s", sqlite3_escape_column_op(field->name), sqlite3_escape_value(field->value)); } } first = 1; - while ((key = va_arg(ap, const char *)) && (value = va_arg(ap, const char *))) { + for (field = update_fields; field; field = field->next) { if (first) { - ast_str_set(&sql, 0, "UPDATE %s SET %s = %s", sqlite3_escape_table(table), sqlite3_escape_column(key), sqlite3_escape_value(value)); + ast_str_set(&sql, 0, "UPDATE %s SET %s = %s", sqlite3_escape_table(table), sqlite3_escape_column(field->name), sqlite3_escape_value(field->value)); first = 0; } else { - ast_str_append(&sql, 0, ", %s = %s", sqlite3_escape_column(key), sqlite3_escape_value(value)); + ast_str_append(&sql, 0, ", %s = %s", sqlite3_escape_column(field->name), sqlite3_escape_value(field->value)); } } @@ -811,10 +811,10 @@ static int realtime_sqlite3_update2(const char *database, const char *table, va_ /*! \brief Realtime callback for inserting a row * \return Number of rows affected or -1 on error */ -static int realtime_sqlite3_store(const char *database, const char *table, va_list ap) +static int realtime_sqlite3_store(const char *database, const char *table, const struct ast_variable *fields) { struct ast_str *sql, *values; - const char *column, *value; + const struct ast_variable *field; int first = 1, res; if (ast_strlen_zero(table)) { @@ -831,14 +831,14 @@ static int realtime_sqlite3_store(const char *database, const char *table, va_li return -1; } - while ((column = va_arg(ap, const char *)) && (value = va_arg(ap, const char *))) { + for (field = fields; field; field = field->next) { if (first) { - ast_str_set(&sql, 0, "INSERT INTO %s (%s", sqlite3_escape_table(table), sqlite3_escape_column(column)); - ast_str_set(&values, 0, ") VALUES (%s", sqlite3_escape_value(value)); + ast_str_set(&sql, 0, "INSERT INTO %s (%s", sqlite3_escape_table(table), sqlite3_escape_column(field->name)); + ast_str_set(&values, 0, ") VALUES (%s", sqlite3_escape_value(field->value)); first = 0; } else { - ast_str_append(&sql, 0, ", %s", sqlite3_escape_column(column)); - ast_str_append(&values, 0, ", %s", sqlite3_escape_value(value)); + ast_str_append(&sql, 0, ", %s", sqlite3_escape_column(field->name)); + ast_str_append(&values, 0, ", %s", sqlite3_escape_value(field->value)); } } @@ -855,10 +855,10 @@ static int realtime_sqlite3_store(const char *database, const char *table, va_li /*! \brief Realtime callback for deleting a row * \return Number of rows affected or -1 on error */ -static int realtime_sqlite3_destroy(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap) +static int realtime_sqlite3_destroy(const char *database, const char *table, const char *keyfield, const char *entity, const struct ast_variable *fields) { struct ast_str *sql; - const char *param, *value; + const struct ast_variable *field; int first = 1, res; if (ast_strlen_zero(table)) { @@ -870,13 +870,13 @@ static int realtime_sqlite3_destroy(const char *database, const char *table, con return -1; } - while ((param = va_arg(ap, const char *)) && (value = va_arg(ap, const char *))) { + for (field = fields; field; field = field->next) { if (first) { ast_str_set(&sql, 0, "DELETE FROM %s WHERE %s %s", sqlite3_escape_table(table), - sqlite3_escape_column_op(param), sqlite3_escape_value(value)); + sqlite3_escape_column_op(field->name), sqlite3_escape_value(field->value)); first = 0; } else { - ast_str_append(&sql, 0, " AND %s %s", sqlite3_escape_column_op(param), sqlite3_escape_value(value)); + ast_str_append(&sql, 0, " AND %s %s", sqlite3_escape_column_op(field->name), sqlite3_escape_value(field->value)); } } diff --git a/res/res_sorcery_realtime.c b/res/res_sorcery_realtime.c new file mode 100644 index 000000000..29517c80d --- /dev/null +++ b/res/res_sorcery_realtime.c @@ -0,0 +1,252 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * + * \brief Sorcery Realtime Object Wizard + * + * \author Joshua Colp <jcolp@digium.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <regex.h> + +#include "asterisk/module.h" +#include "asterisk/sorcery.h" + +/*! \brief They key field used to store the unique identifier for the object */ +#define UUID_FIELD "id" + +static void *sorcery_realtime_open(const char *data); +static int sorcery_realtime_create(const struct ast_sorcery *sorcery, void *data, void *object); +static void *sorcery_realtime_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id); +static void *sorcery_realtime_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type, const struct ast_variable *fields); +static void sorcery_realtime_retrieve_multiple(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, + const struct ast_variable *fields); +static void sorcery_realtime_retrieve_regex(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const char *regex); +static int sorcery_realtime_update(const struct ast_sorcery *sorcery, void *data, void *object); +static int sorcery_realtime_delete(const struct ast_sorcery *sorcery, void *data, void *object); +static void sorcery_realtime_close(void *data); + +static struct ast_sorcery_wizard realtime_object_wizard = { + .name = "realtime", + .open = sorcery_realtime_open, + .create = sorcery_realtime_create, + .retrieve_id = sorcery_realtime_retrieve_id, + .retrieve_fields = sorcery_realtime_retrieve_fields, + .retrieve_multiple = sorcery_realtime_retrieve_multiple, + .retrieve_regex = sorcery_realtime_retrieve_regex, + .update = sorcery_realtime_update, + .delete = sorcery_realtime_delete, + .close = sorcery_realtime_close, +}; + +static int sorcery_realtime_create(const struct ast_sorcery *sorcery, void *data, void *object) +{ + const char *family = data; + RAII_VAR(struct ast_variable *, fields, ast_sorcery_objectset_create(sorcery, object), ast_variables_destroy); + struct ast_variable *id = ast_variable_new(UUID_FIELD, ast_sorcery_object_get_id(object), ""); + + if (!fields || !id) { + ast_variables_destroy(id); + return -1; + } + + /* Place the identifier at the front for sanity sake */ + id->next = fields; + fields = id; + + return ast_store_realtime_fields(family, fields) ? -1 : 0; +} + +/*! \brief Internal helper function which returns a filtered objectset, basically removes the id field */ +static struct ast_variable *sorcery_realtime_filter_objectset(struct ast_variable *objectset, struct ast_variable **id) +{ + struct ast_variable *previous = NULL, *field; + + for (field = objectset; field; field = field->next) { + if (!strcmp(field->name, UUID_FIELD)) { + *id = field; + + if (previous) { + previous->next = field->next; + } else { + objectset = field->next; + } + + field->next = NULL; + break; + } + previous = field; + } + + return objectset; +} + +static void *sorcery_realtime_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type, const struct ast_variable *fields) +{ + const char *family = data; + RAII_VAR(struct ast_variable *, objectset, NULL, ast_variables_destroy); + RAII_VAR(struct ast_variable *, id, NULL, ast_variables_destroy); + void *object = NULL; + + if (!(objectset = ast_load_realtime_fields(family, fields))) { + return NULL; + } + + objectset = sorcery_realtime_filter_objectset(objectset, &id); + + if (!id || !(object = ast_sorcery_alloc(sorcery, type, id->value)) || ast_sorcery_objectset_apply(sorcery, object, objectset)) { + return NULL; + } + + return object; +} + +static void *sorcery_realtime_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id) +{ + RAII_VAR(struct ast_variable *, fields, ast_variable_new(UUID_FIELD, id, ""), ast_variables_destroy); + + return sorcery_realtime_retrieve_fields(sorcery, data, type, fields); +} + +static void sorcery_realtime_retrieve_multiple(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const struct ast_variable *fields) +{ + const char *family = data; + RAII_VAR(struct ast_config *, rows, NULL, ast_config_destroy); + RAII_VAR(struct ast_variable *, all, NULL, ast_variables_destroy); + char *row = NULL; + + if (!fields) { + char field[strlen(UUID_FIELD) + 6], value[2]; + + /* If no fields have been specified we want all rows, so trick realtime into doing it */ + snprintf(field, sizeof(field), "%s LIKE", UUID_FIELD); + snprintf(value, sizeof(value), "%%"); + + if (!(all = ast_variable_new(field, value, ""))) { + return; + } + + fields = all; + } + + if (!(rows = ast_load_realtime_multientry_fields(family, fields))) { + return; + } + + while ((row = ast_category_browse(rows, row))) { + struct ast_variable *objectset = ast_category_root(rows, row); + RAII_VAR(struct ast_variable *, id, NULL, ast_variables_destroy); + RAII_VAR(void *, object, NULL, ao2_cleanup); + + objectset = sorcery_realtime_filter_objectset(objectset, &id); + + if (id && (object = ast_sorcery_alloc(sorcery, type, id->value)) && !ast_sorcery_objectset_apply(sorcery, object, objectset)) { + ao2_link(objects, object); + } + + /* If the id is the root of the row it will be destroyed during ast_config_destroy */ + if (id == ast_category_root(rows, row)) { + id = NULL; + } + } +} + +static void sorcery_realtime_retrieve_regex(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const char *regex) +{ + char field[strlen(UUID_FIELD) + 6], value[strlen(regex) + 2]; + RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy); + + /* The realtime API provides no direct ability to do regex so for now we support a limited subset using pattern matching */ + if (regex[0] != '^') { + return; + } + + snprintf(field, sizeof(field), "%s LIKE", UUID_FIELD); + snprintf(value, sizeof(value), "%s%%", regex + 1); + + if (!(fields = ast_variable_new(field, value, ""))) { + return; + } + + sorcery_realtime_retrieve_multiple(sorcery, data, type, objects, fields); +} + +static int sorcery_realtime_update(const struct ast_sorcery *sorcery, void *data, void *object) +{ + const char *family = data; + RAII_VAR(struct ast_variable *, fields, ast_sorcery_objectset_create(sorcery, object), ast_variables_destroy); + + if (!fields) { + return -1; + } + + return (ast_update_realtime_fields(family, UUID_FIELD, ast_sorcery_object_get_id(object), fields) <= 0) ? -1 : 0; +} + +static int sorcery_realtime_delete(const struct ast_sorcery *sorcery, void *data, void *object) +{ + const char *family = data; + + return (ast_destroy_realtime_fields(family, UUID_FIELD, ast_sorcery_object_get_id(object), NULL) <= 0) ? -1 : 0; +} + +static void *sorcery_realtime_open(const char *data) +{ + /* We require a prefix for family string generation, or else stuff could mix together */ + if (ast_strlen_zero(data) || !ast_realtime_is_mapping_defined(data)) { + return NULL; + } + + return ast_strdup(data); +} + +static void sorcery_realtime_close(void *data) +{ + ast_free(data); +} + +static int load_module(void) +{ + if (ast_sorcery_wizard_register(&realtime_object_wizard)) { + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sorcery_wizard_unregister(&realtime_object_wizard); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Sorcery Realtime Object Wizard", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_REALTIME_DRIVER, +); diff --git a/tests/test_sorcery_realtime.c b/tests/test_sorcery_realtime.c new file mode 100644 index 000000000..6f4196841 --- /dev/null +++ b/tests/test_sorcery_realtime.c @@ -0,0 +1,791 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief Sorcery Unit Tests + * + * \author Joshua Colp <jcolp@digium.com> + * + */ + +/*** MODULEINFO + <depend>TEST_FRAMEWORK</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "") + +#include "asterisk/test.h" +#include "asterisk/module.h" +#include "asterisk/sorcery.h" +#include "asterisk/astdb.h" +#include "asterisk/logger.h" + +/*! \brief Configuration structure which contains all stored objects */ +static struct ast_config *realtime_objects; + +/*! \brief Helper function which finds a given variable */ +static const struct ast_variable *realtime_find_variable(const struct ast_variable *fields, const char *name) +{ + const struct ast_variable *variable; + + for (variable = fields; variable; variable = variable->next) { + if (!strcmp(variable->name, name)) { + return variable; + } + } + + return NULL; +} + +/*! \brief Helper function which returns if an object is matching or not */ +static int realtime_is_object_matching(const char *object_id, const struct ast_variable *fields) +{ + const struct ast_variable *field; + + for (field = fields; field; field = field->next) { + char *name = ast_strdupa(field->name), *like; + const char *value; + + /* If we are doing a pattern matching we need to remove the LIKE from the name */ + if ((like = strstr(name, " LIKE"))) { + char *pattern, *field_value = ast_strdupa(field->value); + + *like = '\0'; + + value = ast_strdupa(ast_variable_retrieve(realtime_objects, object_id, name)); + + if (!(pattern = strchr(field_value, '%'))) { + return 0; + } + + *pattern = '\0'; + + if (strncmp(value, field_value, strlen(field_value))) { + return 0; + } + } else { + value = ast_variable_retrieve(realtime_objects, object_id, name); + + if (ast_strlen_zero(value) || strcmp(value, field->value)) { + return 0; + } + } + } + + return 1; +} + +static struct ast_variable *realtime_sorcery(const char *database, const char *table, const struct ast_variable *fields) +{ + char *object_id = NULL; + + while ((object_id = ast_category_browse(realtime_objects, object_id))) { + if (!realtime_is_object_matching(object_id, fields)) { + continue; + } + + return ast_variables_dup(ast_category_root(realtime_objects, object_id)); + } + + return NULL; +} + +static struct ast_config *realtime_sorcery_multi(const char *database, const char *table, const struct ast_variable *fields) +{ + struct ast_config *objects; + char *object_id = NULL; + + if (!(objects = ast_config_new())) { + return NULL; + } + + while ((object_id = ast_category_browse(realtime_objects, object_id))) { + struct ast_category *object; + + if (!realtime_is_object_matching(object_id, fields)) { + continue; + } + + if (!(object = ast_category_new("", "", 0))) { + ast_config_destroy(objects); + return NULL; + } + + ast_variable_append(object, ast_variables_dup(ast_category_root(realtime_objects, object_id))); + ast_category_append(objects, object); + } + + return objects; +} + +static int realtime_sorcery_update(const char *database, const char *table, const char *keyfield, const char *entity, const struct ast_variable *fields) +{ + struct ast_category *object; + + if (!ast_category_exist(realtime_objects, entity)) { + return 0; + } else if (!(object = ast_category_new(entity, "", 0))) { + return -1; + } + + ast_category_delete(realtime_objects, entity); + ast_variable_append(object, ast_variables_dup((struct ast_variable*)fields)); + ast_variable_append(object, ast_variable_new(keyfield, entity, "")); + ast_category_append(realtime_objects, object); + + return 1; +} + +static int realtime_sorcery_store(const char *database, const char *table, const struct ast_variable *fields) +{ + /* The key field is explicit within res_sorcery_realtime */ + const struct ast_variable *keyfield = realtime_find_variable(fields, "id"); + struct ast_category *object; + + if (!keyfield || ast_category_exist(realtime_objects, keyfield->value) || !(object = ast_category_new(keyfield->value, "", 0))) { + return -1; + } + + ast_variable_append(object, ast_variables_dup((struct ast_variable*)fields)); + ast_category_append(realtime_objects, object); + + return 0; +} + +static int realtime_sorcery_destroy(const char *database, const char *table, const char *keyfield, const char *entity, const struct ast_variable *fields) +{ + if (!ast_category_exist(realtime_objects, entity)) { + return 0; + } + + ast_category_delete(realtime_objects, entity); + + return 1; +} + +struct ast_config_engine sorcery_config_engine = { + .name = "sorcery_realtime_test", + .realtime_func = realtime_sorcery, + .realtime_multi_func = realtime_sorcery_multi, + .update_func = realtime_sorcery_update, + .store_func = realtime_sorcery_store, + .destroy_func = realtime_sorcery_destroy, +}; + +/*! \brief Dummy sorcery object */ +struct test_sorcery_object { + SORCERY_OBJECT(details); + unsigned int bob; + unsigned int joe; +}; + +/*! \brief Internal function to allocate a test object */ +static void *test_sorcery_object_alloc(const char *id) +{ + return ao2_alloc(sizeof(struct test_sorcery_object), NULL); +} + +static struct ast_sorcery *alloc_and_initialize_sorcery(void) +{ + struct ast_sorcery *sorcery; + + if (!(sorcery = ast_sorcery_open())) { + return NULL; + } + + if (ast_sorcery_apply_default(sorcery, "test", "realtime", "sorcery_realtime_test") || + ast_sorcery_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL) || + !(realtime_objects = ast_config_new())) { + ast_sorcery_unref(sorcery); + return NULL; + } + + ast_sorcery_object_field_register(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob)); + ast_sorcery_object_field_register(sorcery, "test", "joe", "10", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, joe)); + + return sorcery; +} + +static void deinitialize_sorcery(struct ast_sorcery *sorcery) +{ + ast_config_destroy(realtime_objects); + realtime_objects = NULL; + ast_sorcery_unref(sorcery); +} + +AST_TEST_DEFINE(object_create) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_create"; + info->category = "/res/sorcery_realtime/"; + info->summary = "sorcery realtime object creation unit test"; + info->description = + "Test object creation in sorcery using realtime wizard"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = alloc_and_initialize_sorcery())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using realtime wizard\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_retrieve_id) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_retrieve_id"; + info->category = "/res/sorcery_realtime/"; + info->summary = "sorcery object retrieval using id unit test"; + info->description = + "Test object retrieval using id in sorcery with realtime wizard"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = alloc_and_initialize_sorcery())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using astdb wizard\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah2"))) { + ast_test_status_update(test, "Failed to allocate second instance of a known object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create second object using astdb wizard\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + if (!(obj = ast_sorcery_retrieve_by_id(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to retrieve properly created object using id of 'blah'\n"); + return AST_TEST_FAIL; + } else if (strcmp(ast_sorcery_object_get_id(obj), "blah")) { + ast_test_status_update(test, "Retrieved object does not have correct id\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_retrieve_field) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct ast_variable *, fields, ast_variable_new("joe", "42", ""), ast_variables_destroy); + + switch (cmd) { + case TEST_INIT: + info->name = "object_retrieve_field"; + info->category = "/res/sorcery_realtime/"; + info->summary = "sorcery object retrieval using a specific field unit test"; + info->description = + "Test object retrieval using a specific field in sorcery with realtime wizard"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!fields) { + ast_test_status_update(test, "Failed to create fields for object retrieval attempt\n"); + return AST_TEST_FAIL; + } + + if (!(sorcery = alloc_and_initialize_sorcery())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + obj->joe = 42; + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using realtime wizard\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + if (!(obj = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_DEFAULT, fields))) { + ast_test_status_update(test, "Failed to retrieve properly created object using 'joe' field\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + ast_variables_destroy(fields); + + if (!(fields = ast_variable_new("joe", "49", ""))) { + ast_test_status_update(test, "Failed to create fields for object retrieval attempt\n"); + return AST_TEST_FAIL; + } + + if ((obj = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_DEFAULT, fields))) { + ast_test_status_update(test, "Retrieved an object using a field with an in-correct value... that should not happen\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_retrieve_multiple_all) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_retrieve_multiple_all"; + info->category = "/res/sorcery_realtime/"; + info->summary = "sorcery multiple object retrieval unit test"; + info->description = + "Test multiple object retrieval in sorcery using realtime wizard"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = alloc_and_initialize_sorcery())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using realtime wizard\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah2"))) { + ast_test_status_update(test, "Failed to allocate second instance of a known object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create second object using realtime wizard\n"); + return AST_TEST_FAIL; + } + + if (!(objects = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL))) { + ast_test_status_update(test, "Failed to retrieve a container of all objects\n"); + return AST_TEST_FAIL; + } else if (ao2_container_count(objects) != 2) { + ast_test_status_update(test, "Received a container with no objects in it when there should be some\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_retrieve_multiple_field) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup); + RAII_VAR(struct ast_variable *, fields, ast_variable_new("joe", "6", ""), ast_variables_destroy); + + switch (cmd) { + case TEST_INIT: + info->name = "object_retrieve_multiple_field"; + info->category = "/res/sorcery_realtime/"; + info->summary = "sorcery multiple object retrieval unit test"; + info->description = + "Test multiple object retrieval in sorcery using fields using realtime wizard"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!fields) { + ast_test_status_update(test, "Failed to create fields for multiple retrieve\n"); + return AST_TEST_FAIL; + } + + if (!(sorcery = alloc_and_initialize_sorcery())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + obj->joe = 6; + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using realtime wizard\n"); + return AST_TEST_FAIL; + } + + if (!(objects = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_MULTIPLE, fields))) { + ast_test_status_update(test, "Failed to retrieve a container of all objects\n"); + return AST_TEST_FAIL; + } else if (ao2_container_count(objects) != 1) { + ast_test_status_update(test, "Received a container with no objects in it when there should be some\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(objects); + ast_variables_destroy(fields); + + if (!(fields = ast_variable_new("joe", "7", ""))) { + ast_test_status_update(test, "Failed to create fields for multiple retrieval\n"); + return AST_TEST_FAIL; + } else if (!(objects = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_MULTIPLE, fields))) { + ast_test_status_update(test, "Failed to retrieve an empty container when retrieving multiple\n"); + return AST_TEST_FAIL; + } else if (ao2_container_count(objects)) { + ast_test_status_update(test, "Received a container with objects when there should be none in it\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_retrieve_regex) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_retrieve_regex"; + info->category = "/res/sorcery_realtime/"; + info->summary = "sorcery multiple object retrieval using regex unit test"; + info->description = + "Test multiple object retrieval in sorcery using regular expression for matching using realtime wizard"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = alloc_and_initialize_sorcery())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah-98joe"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using realtime wizard\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah-93joe"))) { + ast_test_status_update(test, "Failed to allocate second instance of a known object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create second object using astdb wizard\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "neener-93joe"))) { + ast_test_status_update(test, "Failed to allocate third instance of a known object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create third object using astdb wizard\n"); + return AST_TEST_FAIL; + } + + if (!(objects = ast_sorcery_retrieve_by_regex(sorcery, "test", "^blah-"))) { + ast_test_status_update(test, "Failed to retrieve a container of objects\n"); + return AST_TEST_FAIL; + } else if (ao2_container_count(objects) != 2) { + ast_test_status_update(test, "Received a container with incorrect number of objects in it\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_update) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + RAII_VAR(struct test_sorcery_object *, obj2, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_update"; + info->category = "/res/sorcery_realtime/"; + info->summary = "sorcery object update unit test"; + info->description = + "Test object updating in sorcery using realtime wizard"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = alloc_and_initialize_sorcery())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using realtime wizard\n"); + return AST_TEST_FAIL; + } + + if (!(obj2 = ast_sorcery_copy(sorcery, obj))) { + ast_test_status_update(test, "Failed to allocate a known object type for updating\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + obj2->bob = 1000; + obj2->joe = 2000; + + if (ast_sorcery_update(sorcery, obj2)) { + ast_test_status_update(test, "Failed to update sorcery with new object\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_retrieve_by_id(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to retrieve properly updated object\n"); + return AST_TEST_FAIL; + } else if ((obj->bob != obj2->bob) || (obj->joe != obj2->joe)) { + ast_test_status_update(test, "Object retrieved is not the updated object\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_update_uncreated) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_update_uncreated"; + info->category = "/res/sorcery_realtime/"; + info->summary = "sorcery object update unit test"; + info->description = + "Test updating of an uncreated object in sorcery using realtime wizard"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = alloc_and_initialize_sorcery())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + if (!ast_sorcery_update(sorcery, obj)) { + ast_test_status_update(test, "Successfully updated an object which has not been created yet\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_delete) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_delete"; + info->category = "/res/sorcery_realtime/"; + info->summary = "sorcery object deletion unit test"; + info->description = + "Test object deletion in sorcery using realtime wizard"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = alloc_and_initialize_sorcery())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_create(sorcery, obj)) { + ast_test_status_update(test, "Failed to create object using realtime wizard\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_delete(sorcery, obj)) { + ast_test_status_update(test, "Failed to delete object using realtime wizard\n"); + return AST_TEST_FAIL; + } + + ao2_cleanup(obj); + + if ((obj = ast_sorcery_retrieve_by_id(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Retrieved deleted object that should not be there\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(object_delete_uncreated) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery); + RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_delete_uncreated"; + info->category = "/res/sorcery_realtime/"; + info->summary = "sorcery object deletion unit test"; + info->description = + "Test object deletion of an uncreated object in sorcery using realtime wizard"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (!(sorcery = alloc_and_initialize_sorcery())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + if (!ast_sorcery_delete(sorcery, obj)) { + ast_test_status_update(test, "Successfully deleted an object which was never created\n"); + return AST_TEST_FAIL; + } + + return AST_TEST_PASS; +} + +static int unload_module(void) +{ + ast_config_engine_deregister(&sorcery_config_engine); + AST_TEST_UNREGISTER(object_create); + AST_TEST_UNREGISTER(object_retrieve_id); + AST_TEST_UNREGISTER(object_retrieve_field); + AST_TEST_UNREGISTER(object_retrieve_multiple_all); + AST_TEST_UNREGISTER(object_retrieve_multiple_field); + AST_TEST_UNREGISTER(object_retrieve_regex); + AST_TEST_UNREGISTER(object_update); + AST_TEST_UNREGISTER(object_update_uncreated); + AST_TEST_UNREGISTER(object_delete); + AST_TEST_UNREGISTER(object_delete_uncreated); + + return 0; +} + +static int load_module(void) +{ + ast_config_engine_register(&sorcery_config_engine); + ast_realtime_append_mapping("sorcery_realtime_test", "sorcery_realtime_test", "test", "test", 1); + AST_TEST_REGISTER(object_create); + AST_TEST_REGISTER(object_retrieve_id); + AST_TEST_REGISTER(object_retrieve_field); + AST_TEST_REGISTER(object_retrieve_multiple_all); + AST_TEST_REGISTER(object_retrieve_multiple_field); + AST_TEST_REGISTER(object_retrieve_regex); + AST_TEST_REGISTER(object_update); + AST_TEST_REGISTER(object_update_uncreated); + AST_TEST_REGISTER(object_delete); + AST_TEST_REGISTER(object_delete_uncreated); + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Sorcery Realtime Wizard test module"); |