res_config_odbc: Prevent Realtime fallback on record-not-found (SQL_NO_DATA)

This patch fixes an issue in the ODBC Realtime engine where Asterisk incorrectly
falls back to the next configured backend when the current one returns
SQL_NO_DATA (i.e., no record found).
This is a logical error and performance risk in multi-backend configurations.

Solution:
Introduced CONFIG_RT_NOT_FOUND ((void *)-1) as a special return marker.
ODBC Realtime backend now return CONFIG_RT_NOT_FOUND when no data is found.
Core engine stops iterating on this marker, avoiding unnecessary fallback.

Notes:
Other Realtime backends (PostgreSQL, LDAP, etc.) can be updated similarly.
This patch only covers ODBC.

Fixes: #1305
This commit is contained in:
Alexei Gradinari
2025-07-15 17:56:39 -04:00
parent 0ba07ac7c3
commit 9e3ea1c8ac
3 changed files with 27 additions and 4 deletions

View File

@@ -126,6 +126,12 @@ typedef int realtime_require(const char *database, const char *table, va_list ap
*/ */
typedef int realtime_unload(const char *database, const char *table); typedef int realtime_unload(const char *database, const char *table);
/*! Special return value indicating a successful query that returned no data.
* Used by realtime backends to signal "not found" vs an actual backend failure.
* This allows the core engine to differentiate and avoid unnecessary failover.
*/
#define CONFIG_RT_NOT_FOUND (void *)-1
/*! \brief Configuration engine structure, used to define realtime drivers */ /*! \brief Configuration engine structure, used to define realtime drivers */
struct ast_config_engine { struct ast_config_engine {
char *name; char *name;

View File

@@ -3639,8 +3639,20 @@ struct ast_variable *ast_load_realtime_all_fields(const char *family, const stru
for (i = 1; ; i++) { for (i = 1; ; i++) {
if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
if (eng->realtime_func && (res = eng->realtime_func(db, table, fields))) { if (eng->realtime_func) {
return res; res = eng->realtime_func(db, table, fields);
/* If a backend returns CONFIG_RT_NOT_FOUND, stop iteration and return NULL,
* indicating that the requested record does not exist and no failover should occur.
* Only continue iteration if the result is NULL and not CONFIG_RT_NOT_FOUND,
* which signals a backend failure.
*/
if (res == CONFIG_RT_NOT_FOUND) {
return NULL;
}
if (res) {
return res;
}
} }
} else { } else {
return NULL; return NULL;

View File

@@ -167,7 +167,8 @@ static SQLHSTMT custom_prepare(struct odbc_obj *obj, void *data)
* Sub-in the values to the prepared statement and execute it. Return results * Sub-in the values to the prepared statement and execute it. Return results
* as a ast_variable list. * as a ast_variable list.
* *
* \return var on success * \return var on success (data found)
* \return CONFIG_RT_NOT_FOUND on success but no record
* \retval NULL on failure * \retval NULL on failure
*/ */
static struct ast_variable *realtime_odbc(const char *database, const char *table, const struct ast_variable *fields) static struct ast_variable *realtime_odbc(const char *database, const char *table, const struct ast_variable *fields)
@@ -237,9 +238,13 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl
res = SQLFetch(stmt); res = SQLFetch(stmt);
if (res == SQL_NO_DATA) { if (res == SQL_NO_DATA) {
/* SQL_NO_DATA indicates that the query was valid but no record was found.
* Instead of returning NULL (which signals a backend error to the core),
* return CONFIG_RT_NOT_FOUND to prevent incorrect failover.
*/
SQLFreeHandle (SQL_HANDLE_STMT, stmt); SQLFreeHandle (SQL_HANDLE_STMT, stmt);
ast_odbc_release_obj(obj); ast_odbc_release_obj(obj);
return NULL; return CONFIG_RT_NOT_FOUND;
} }
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
ast_log(LOG_WARNING, "SQL Fetch error! [%s]\n", ast_str_buffer(sql)); ast_log(LOG_WARNING, "SQL Fetch error! [%s]\n", ast_str_buffer(sql));