]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
res_config_odbc/res_odbc: Fix handling of non-text columns updates with empty values.
authorMatthew Jordan <mjordan@digium.com>
Fri, 28 Mar 2014 04:27:02 +0000 (04:27 +0000)
committerMatthew Jordan <mjordan@digium.com>
Fri, 28 Mar 2014 04:27:02 +0000 (04:27 +0000)
This patch fixes setting nullable integer columns to NULL instead of an empty
string, which fails for PostgreSQL, for example. The current code is supposed
to do so, but the check is broken. The patch also allows the first column in
the list to be a nullable integer.

This patch also adds a compatibility setting in res_odbc.conf,
allow_empty_string_in_nontext. It is enabled by default. It should be disabled
for database backends (such as PostgreSQL) that require NULL instead of an
empty string for Integer columns.

Review: https://reviewboard.asterisk.org/r/3375

(issue ASTERISK-23459)
Reported by: zvision
patches:
  res_config_odbc.diff uploaded by zvision (License 5755)
........

Merged revisions 411399 from http://svn.asterisk.org/svn/asterisk/branches/1.8

git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/11@411408 65c4cc65-6c06-0410-ace0-fbb531ad65f3

UPGRADE.txt
configs/res_odbc.conf.sample
include/asterisk/res_odbc.h
res/res_config_odbc.c
res/res_odbc.c
res/res_odbc.exports.in

index 5250793484708f2f9f3fb5a393cd62ac3766aff4..8092140f042a716b0a8b12c3436905b422d682c6 100644 (file)
@@ -19,6 +19,7 @@
 === UPGRADE-10.txt -- Upgrade info for 1.8 to 10
 ===
 ===========================================================
+
 from 11.8 to 11.9
 * res_fax now returns the correct rates for V.27ter (4800 or 9600 bit/s).
   Because of this the default settings would not load, so the minrate (minimum
@@ -29,6 +30,14 @@ from 11.8 to 11.9
   and the fix involved using a different method to achieve the same goal. The
   new method to achieve this functionality is by using sound_begin to play
   a sound to the conference when waitmarked users are moved into the conference.
+* A compatibility setting, allow_empty_string_in_nontext, has been added to
+  res_odbc.conf. When enabled (default behavior), empty column values are
+  stored as empty strings during realtime updates. Disabling this option
+  causes empty column values to be stored as NULLs for non-text columns.
+
+  Disable it for PostgreSQL backends in order to avoid errors caused by
+  updating integer columns with an empty string instead of NULL
+  (sipppeers,sipregs)
 
 From 11.7 to 11.8:
 * The per console verbose level feature as previously implemented caused a
index 66659ae42ea2a8588146ca3b2860ecb055116283..581389fda14e74382a49b23e52557c05520c563b 100644 (file)
@@ -64,6 +64,14 @@ pre-connect => yes
 ; MS SQL Server, the answer is no.
 ;backslash_is_escape => yes
 ;
+; When enabled (default behavior), empty column values are stored as empty strings
+; during realtime updates. Disabling this option causes empty column values to be
+; stored as NULLs for non-text columns.
+; Disable it for PostgreSQL backend in order to avoid errors caused by updating
+; integer columns with an empty string instead of NULL (sipppeers,sipregs)
+; NOTE: This option will be removed in asterisk 13. At that point, it will always behave as if it was set to 'no'.
+;allow_empty_string_in_nontext => yes
+;
 ; How long (in seconds) should we attempt to connect before considering the
 ; connection dead?  The default is 10 seconds, but you may wish to reduce it,
 ; to increase responsiveness.
index 76d57f74c13e8d77a08964472bf0bb24434ba4c3..2f63ec54a4696338b805eaf5d86b2a363570d332 100644 (file)
@@ -221,4 +221,10 @@ int ast_odbc_clear_cache(const char *database, const char *tablename);
  */
 SQLRETURN ast_odbc_ast_str_SQLGetData(struct ast_str **buf, int pmaxlen, SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, SQLLEN *StrLen_or_Ind);
 
+/*! \brief Checks if the database natively supports implicit conversion from an empty string to a number (0).
+ * \param obj The ODBC object
+ * \return Returns 1 if the implicit conversion is valid and non-text columns can take empty strings, 0 if the conversion is not valid and NULLs should be used instead '\'
+ */
+int ast_odbc_allow_empty_string_in_nontext(struct odbc_obj *obj);
+
 #endif /* _ASTERISK_RES_ODBC_H */
index 66e15a02be17a30568c6e15dedb45b67615f7e76..0f522e5ccfd60b2a741cb7f6542831484b2a6abe 100644 (file)
@@ -69,6 +69,12 @@ static void decode_chunk(char *chunk)
        }
 }
 
+static inline int is_text(const struct odbc_cache_columns *column)
+{
+       return column->type == SQL_CHAR || column->type == SQL_VARCHAR || column->type == SQL_LONGVARCHAR
+               || column->type == SQL_WCHAR || column->type == SQL_WVARCHAR || column->type == SQL_WLONGVARCHAR;
+}
+
 static SQLHSTMT custom_prepare(struct odbc_obj *obj, void *data)
 {
        int res, x = 1, count = 0;
@@ -484,15 +490,15 @@ static int update_odbc(const char *database, const char *table, const char *keyf
        SQLHSTMT stmt;
        char sql[256];
        SQLLEN rowcount=0;
-       const char *newparam;
-       int res, count = 1;
+       const char *newparam, *newval;
+       int res, count = 0, paramcount = 0;
        va_list aq;
        struct custom_prepare_struct cps = { .sql = sql, .extra = lookup };
        struct odbc_cache_tables *tableptr;
        struct odbc_cache_columns *column = NULL;
        struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
 
-       if (!table) {
+       if (!table || !keyfield) {
                return -1;
        }
 
@@ -507,39 +513,31 @@ 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;
+       if (tableptr && !ast_odbc_find_column(tableptr, keyfield)) {
+               ast_log(LOG_WARNING, "Key field '%s' does not exist in table '%s@%s'.  Update will fail\n", keyfield, table, database);
        }
-       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);
-       }
+       va_copy(aq, ap);
 
-       snprintf(sql, sizeof(sql), "UPDATE %s SET %s=?", table, newparam);
+       snprintf(sql, sizeof(sql), "UPDATE %s SET ", table);
        while((newparam = va_arg(aq, const char *))) {
-               va_arg(aq, const char *);
-               if ((tableptr && (column = ast_odbc_find_column(tableptr, newparam))) || count > 63) {
-                       /* NULL test for integer-based columns */
-                       if (ast_strlen_zero(newparam) && 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);
+               newval = va_arg(aq, const char *);
+               if ((tableptr && (column = ast_odbc_find_column(tableptr, newparam))) || count >= 64) {
+                       if (paramcount++) {
+                               snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", ");
+                       }
+                       /* NULL test for non-text columns */
+                       if (count < 64 && ast_strlen_zero(newval) && column->nullable && !is_text(column) && !ast_odbc_allow_empty_string_in_nontext(obj)) {
+                               snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=NULL", newparam);
                                cps.skip |= (1LL << count);
                        } else {
-                               snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s=?", newparam);
+                               /* Value is not an empty string, or column accepts empty strings, or we couldn't fit any more into cps.skip (count >= 64 ?!). */
+                               snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=?", newparam);
                        }
                } else { /* the column does not exist in the table */
                        cps.skip |= (1LL << count);
                }
-               count++;
+               ++count;
        }
        va_end(aq);
        snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s=?", keyfield);
index 797f31ece89f945ebc527d084f20f00071652d58..3de4737be5984045ce31952fa6f0e5decba51542 100644 (file)
@@ -128,6 +128,7 @@ struct odbc_class
        unsigned int delme:1;                /*!< Purge the class */
        unsigned int backslash_is_escape:1;  /*!< On this database, the backslash is a native escape sequence */
        unsigned int forcecommit:1;          /*!< Should uncommitted transactions be auto-committed on handle release? */
+       unsigned int allow_empty_strings:1;  /*!< Implicit conversion from an empty string to a number is valid for this database */
        unsigned int isolation;              /*!< Flags for how the DB should deal with data in other, uncommitted transactions */
        unsigned int limit;                  /*!< Maximum number of database handles we will allow */
        int count;                           /*!< Running count of pooled connections */
@@ -772,7 +773,7 @@ static int load_odbc_config(void)
        struct ast_variable *v;
        char *cat;
        const char *dsn, *username, *password, *sanitysql;
-       int enabled, pooling, limit, bse, conntimeout, forcecommit, isolation;
+       int enabled, pooling, limit, bse, conntimeout, forcecommit, isolation, allow_empty_strings;
        struct timeval ncache = { 0, 0 };
        unsigned int idlecheck;
        int preconnect = 0, res = 0;
@@ -801,6 +802,7 @@ static int load_odbc_config(void)
                        bse = 1;
                        conntimeout = 10;
                        forcecommit = 0;
+                       allow_empty_strings = 1;
                        isolation = SQL_TXN_READ_COMMITTED;
                        for (v = ast_variable_browse(config, cat); v; v = v->next) {
                                if (!strcasecmp(v->name, "pooling")) {
@@ -836,6 +838,8 @@ static int load_odbc_config(void)
                                        sanitysql = v->value;
                                } else if (!strcasecmp(v->name, "backslash_is_escape")) {
                                        bse = ast_true(v->value);
+                               } else if (!strcasecmp(v->name, "allow_empty_string_in_nontext")) {
+                                       allow_empty_strings = ast_true(v->value);
                                } else if (!strcasecmp(v->name, "connect_timeout")) {
                                        if (sscanf(v->value, "%d", &conntimeout) != 1 || conntimeout < 1) {
                                                ast_log(LOG_WARNING, "connect_timeout must be a positive integer\n");
@@ -897,6 +901,7 @@ static int load_odbc_config(void)
                                new->idlecheck = idlecheck;
                                new->conntimeout = conntimeout;
                                new->negative_connection_cache = ncache;
+                               new->allow_empty_strings = allow_empty_strings ? 1 : 0;
 
                                if (cat)
                                        ast_copy_string(new->name, cat, sizeof(new->name));
@@ -1114,6 +1119,11 @@ int ast_odbc_backslash_is_escape(struct odbc_obj *obj)
        return obj->parent->backslash_is_escape;
 }
 
+int ast_odbc_allow_empty_string_in_nontext(struct odbc_obj *obj)
+{
+       return obj->parent->allow_empty_strings;
+}
+
 static int commit_exec(struct ast_channel *chan, const char *data)
 {
        struct odbc_txn_frame *tx;
index ad674beb178d57ccbe0ecae42a7c2e19f358bee3..991daccaf6e0f36aed6fba22a73ed5d56f05524f 100644 (file)
@@ -2,6 +2,7 @@
        global:
                LINKER_SYMBOL_PREFIXast_odbc_ast_str_SQLGetData;
                LINKER_SYMBOL_PREFIXast_odbc_backslash_is_escape;
+               LINKER_SYMBOL_PREFIXast_odbc_allow_empty_string_in_nontext;
                LINKER_SYMBOL_PREFIXast_odbc_clear_cache;
                LINKER_SYMBOL_PREFIXast_odbc_direct_execute;
                LINKER_SYMBOL_PREFIXast_odbc_find_column;