Re: MS query notifications patch

Frediano Ziglio <[email protected]>
Newsgroups gmane.comp.db.tds.freetds
Message-ID <CAHt6W4d0VvOj7GkARvM6ju4aPOBcU49-e7FKrudSfNJd2G-epQ@mail.gmail.com>
2014-04-16 19:12 GMT+01:00 Richard Hughes <[email protected]>:
> Hi all,
>
> I needed support for SQL Server 2005's Query Notifications feature, so
> here's a patch against tip.
>
> I've only given the patch a light sprinkling of testing so far, but
> could I ask somebody to review it and confirm that I'm on the right
> track so that I can create a v2 patch that is (hopefully) mergeable?
> Both the Microsoft API and the TDS implementation feel extremely
> kludgy to me and hence some of that kludginess needed to be reflected
> in the patch; sorry about that, but don't shoot the messenger.
>
> About Query Notifications:
>
> Query Notifications are designed to support client-side caching of
> datasets by providing a mechanism to allow the application to be
> notified whenever its cache may have become out of date (i.e. because
> somebody updated the underlying table(s)). Fundamentally the feature
> works by delivering a query to the server and, rather than executing
> it, the server does internal magic to register that query and a
> user-specified cookie. When the result set that the query would have
> returned (had it actually been executed) changes, the cookie is
> delivered back to the caller through a WAITFOR/RECEIVE statement.
>
> User-level doc: http://technet.microsoft.com/en-us/library/ms130764.aspx
> Protocol doc: http://msdn.microsoft.com/en-us/library/dd304949.aspx
>
> Only the registering of the query and cookie with the server needs
> FreeTDS changes. The WAITFOR statement is just executed as a normal
> statement with a normal result set.
>
> Richard.

The patch is really good and fit very well with FreeTDS coding standards.

The big concern is that I think that this feature works also for every
queries not for just sql ones. I'll add some comments on your code. I
think we should test what happens for prepared statements or RPCs.

> diff --git a/include/freetds/odbc.h b/include/freetds/odbc.h
> index 4413b61..1a75080 100644
> --- a/include/freetds/odbc.h
> +++ b/include/freetds/odbc.h
> @@ -379,6 +379,9 @@ struct _hsattr
>      /* SQLGetStmtAttr only */
>  /*    TDS_DESC *imp_row_desc; */
>  /*    TDS_DESC *imp_param_desc; */
> +    DSTR qn_msgtext;
> +    DSTR qn_options;
> +    SQLLEN qn_timeout;

I think should be SQLINTEGER here. Protocol and OLEDB use 32 bit,
SQLLEN is 64 on 64 bit platforms. This change ABI in SQLSetStmtAttr
and similars.

>  };
>
>  typedef enum
> diff --git a/include/freetds/tds.h b/include/freetds/tds.h
> index 413f1d8..d9d0fc5 100644
> --- a/include/freetds/tds.h
> +++ b/include/freetds/tds.h
> @@ -1278,6 +1278,7 @@ TDSRET tds_submit_query_params(TDSSOCKET * tds, const char *query, TDSPARAMINFO
>  TDSRET tds_submit_queryf(TDSSOCKET * tds, const char *queryf, ...);
>  TDSRET tds_submit_prepare(TDSSOCKET * tds, const char *query, const char *id, TDSDYNAMIC ** dyn_out, TDSPARAMINFO * params);
>  TDSRET tds_submit_execdirect(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params);
> +TDSRET tds72_submit_query_notification(TDSSOCKET * tds, const char *query, const char *msgtext, const char *options, TDS_INT timeout);
>  TDSRET tds71_submit_prepexec(TDSSOCKET * tds, const char *query, const char *id, TDSDYNAMIC ** dyn_out, TDSPARAMINFO * params);
>  TDSRET tds_submit_execute(TDSSOCKET * tds, TDSDYNAMIC * dyn);
>  TDSRET tds_send_cancel(TDSSOCKET * tds);
> diff --git a/include/odbcss.h b/include/odbcss.h
> index 6582559..bb5f5b3 100644
> --- a/include/odbcss.h
> +++ b/include/odbcss.h
> @@ -22,3 +22,6 @@
>  #define SQL_DIAG_SS_MSGSTATE    (-1150)
>  #define SQL_DIAG_SS_LINE    (-1154)
>
> +#define SQL_SOPT_SS_QUERYNOTIFICATION_TIMEOUT  1233
> +#define SQL_SOPT_SS_QUERYNOTIFICATION_MSGTEXT  1234
> +#define SQL_SOPT_SS_QUERYNOTIFICATION_OPTIONS  1235
> diff --git a/src/odbc/native.c b/src/odbc/native.c
> index decfcb9..eb8f6d1 100644
> --- a/src/odbc/native.c
> +++ b/src/odbc/native.c
> @@ -36,6 +36,7 @@
>  #include <assert.h>
>
>  #include <freetds/odbc.h>
> +#include <freetds/string.h>
>
>  #ifdef DMALLOC
>  #include <dmalloc.h>
> @@ -228,6 +229,11 @@ prepare_call(struct _hstmt * stmt)
>      else
>          return SQL_ERROR;
>
> +    if (tds_dstr_isempty(&stmt->attr.qn_msgtext) != tds_dstr_isempty(&stmt->attr.qn_options))
> +        return SQL_SUCCESS_WITH_INFO;
> +    if (!tds_dstr_isempty(&stmt->attr.qn_msgtext) && !IS_TDS72_PLUS(stmt->dbc->tds_socket->conn))
> +        return SQL_SUCCESS_WITH_INFO;
> +

I'm not sure this is the right place. Also I think MS add some error
to the statement. I didn't find from documentation which error should
be reported.

>      if ((rc = to_native(stmt->dbc, stmt, buf)) != SQL_SUCCESS)
>          return rc;
>
> diff --git a/src/odbc/odbc.c b/src/odbc/odbc.c
> index 2f57ac3..613b17e 100644
> --- a/src/odbc/odbc.c
> +++ b/src/odbc/odbc.c
> @@ -55,6 +55,7 @@
>  #include <freetds/convert.h>
>  #include "replacements.h"
>  #include "sqlwparams.h"
> +#include <odbcss.h>
>
>  #ifdef DMALLOC
>  #include <dmalloc.h>
> @@ -71,9 +72,9 @@ static SQLRETURN _SQLFreeEnv(SQLHENV henv);
>  static SQLRETURN _SQLFreeStmt(SQLHSTMT hstmt, SQLUSMALLINT fOption, int force);
>  static SQLRETURN _SQLFreeDesc(SQLHDESC hdesc);
>  static SQLRETURN _SQLExecute(TDS_STMT * stmt);
> -static SQLRETURN _SQLSetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength);
> +static SQLRETURN _SQLSetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength WIDE);
>  static SQLRETURN _SQLGetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER Value, SQLINTEGER BufferLength,
> -                     SQLINTEGER * StringLength);
> +                     SQLINTEGER * StringLength WIDE);
>  static SQLRETURN _SQLColAttribute(SQLHSTMT hstmt, SQLUSMALLINT icol, SQLUSMALLINT fDescType, SQLPOINTER rgbDesc,
>                        SQLSMALLINT cbDescMax, SQLSMALLINT FAR * pcbDesc, SQLLEN FAR * pfDesc _WIDE);
>  static SQLRETURN _SQLFetch(TDS_STMT * stmt, SQLSMALLINT FetchOrientation, SQLLEN FetchOffset);
> @@ -1070,10 +1071,10 @@ SQLParamOptions(SQLHSTMT hstmt, SQLULEN crow, SQLULEN FAR * pirow)
>      tdsdump_log(TDS_DBG_FUNC, "SQLParamOptions(%p, %lu, %p)\n", hstmt, (unsigned long int)crow, pirow);
>
>      /* emulate for ODBC 2 DM */
> -    res = _SQLSetStmtAttr(hstmt, SQL_ATTR_PARAMS_PROCESSED_PTR, pirow, 0);
> +    res = _SQLSetStmtAttr(hstmt, SQL_ATTR_PARAMS_PROCESSED_PTR, pirow, 0 _wide0);
>      if (res != SQL_SUCCESS)
>          return res;
> -    return _SQLSetStmtAttr(hstmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) (TDS_INTPTR) crow, 0);
> +    return _SQLSetStmtAttr(hstmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) (TDS_INTPTR) crow, 0 _wide0);
>  }
>
>  ODBC_FUNC(SQLPrimaryKeys, (P(SQLHSTMT,hstmt), PCHARIN(CatalogName,SQLSMALLINT),
> @@ -1744,6 +1745,9 @@ _SQLAllocStmt(SQLHDBC hdbc, SQLHSTMT FAR * phstmt)
>      assert(stmt->ird->header.sql_desc_rows_processed_ptr == NULL);
>      stmt->attr.simulate_cursor = SQL_SC_NON_UNIQUE;
>      stmt->attr.use_bookmarks = SQL_UB_OFF;
> +    tds_dstr_init(&stmt->attr.qn_msgtext);
> +    tds_dstr_init(&stmt->attr.qn_options);
> +    stmt->attr.qn_timeout = 432000;
>
>      stmt->sql_rowset_size = 1;
>
> @@ -1760,7 +1764,7 @@ _SQLAllocStmt(SQLHDBC hdbc, SQLHSTMT FAR * phstmt)
>      *phstmt = (SQLHSTMT) stmt;
>
>      if (dbc->attr.cursor_type != SQL_CURSOR_FORWARD_ONLY)
> -        _SQLSetStmtAttr(stmt, SQL_CURSOR_TYPE, (SQLPOINTER) (TDS_INTPTR) dbc->attr.cursor_type, SQL_IS_INTEGER);
> +        _SQLSetStmtAttr(stmt, SQL_CURSOR_TYPE, (SQLPOINTER) (TDS_INTPTR) dbc->attr.cursor_type, SQL_IS_INTEGER _wide0);
>
>      ODBC_EXIT_(dbc);
>  }
> @@ -3281,7 +3285,12 @@ _SQLExecute(TDS_STMT * stmt)
>
>      stmt->row_count = TDS_NO_COUNT;
>
> -    if (stmt->prepared_query_is_rpc) {
> +    if (!tds_dstr_isempty(&stmt->attr.qn_msgtext)) {
> +        char *name = stmt->query;
> +        if (!name)
> +            name = stmt->prepared_query;

Here you use prepared query but this usually came with some required
parameters you don't use.

> +        ret = tds72_submit_query_notification(tds, stmt->query, tds_dstr_cstr(&stmt->attr.qn_msgtext), tds_dstr_cstr(&stmt->attr.qn_options), stmt->attr.qn_timeout);
> +    } else if (stmt->prepared_query_is_rpc) {
>          /* TODO support stmt->apd->header.sql_desc_array_size for RPC */
>          /* get rpc name */
>          /* TODO change method */
> @@ -4203,6 +4212,8 @@ _SQLFreeStmt(SQLHSTMT hstmt, SQLUSMALLINT fOption, int force)
>          odbc_errs_reset(&stmt->errs);
>          odbc_unlock_statement(stmt);
>          tds_dstr_free(&stmt->cursor_name);
> +        tds_dstr_free(&stmt->attr.qn_msgtext);
> +        tds_dstr_free(&stmt->attr.qn_options);
>          desc_free(stmt->ird);
>          desc_free(stmt->ipd);
>          desc_free(stmt->orig_ard);
> @@ -4283,7 +4294,7 @@ _SQLFreeDesc(SQLHDESC hdesc)
>  }
>
>  static SQLRETURN
> -_SQLGetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER Value, SQLINTEGER BufferLength, SQLINTEGER * StringLength)
> +_SQLGetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER Value, SQLINTEGER BufferLength, SQLINTEGER * StringLength WIDE)
>  {
>      void *src;
>      size_t size;
> @@ -4438,6 +4449,20 @@ _SQLGetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER Value, SQLINTEG
>          size = sizeof(stmt->sql_rowset_size);
>          src = &stmt->sql_rowset_size;
>          break;
> +    case SQL_SOPT_SS_QUERYNOTIFICATION_TIMEOUT:
> +        size = sizeof(stmt->attr.qn_timeout);
> +        src = &stmt->attr.qn_timeout;
> +        break;
> +    case SQL_SOPT_SS_QUERYNOTIFICATION_MSGTEXT:
> +        {
> +            SQLRETURN rc = odbc_set_string_oct(stmt->dbc, Value, BufferLength, StringLength, tds_dstr_cstr(&stmt->attr.qn_msgtext), tds_dstr_len(&stmt->attr.qn_msgtext));
> +            ODBC_EXIT(stmt, rc);
> +        }
> +    case SQL_SOPT_SS_QUERYNOTIFICATION_OPTIONS:
> +        {
> +            SQLRETURN rc = odbc_set_string_oct(stmt->dbc, Value, BufferLength, StringLength, tds_dstr_cstr(&stmt->attr.qn_options), tds_dstr_len(&stmt->attr.qn_options));
> +            ODBC_EXIT(stmt, rc);
> +        }
>          /* TODO SQL_COLUMN_SEARCHABLE, although ODBC2 */
>      default:
>          odbc_errs_add(&stmt->errs, "HY092", NULL);
> @@ -4458,7 +4483,7 @@ SQLGetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER Value, SQLINTEGE
>      tdsdump_log(TDS_DBG_FUNC, "SQLGetStmtAttr(%p, %d, %p, %d, %p)\n",
>              hstmt, (int)Attribute, Value, (int)BufferLength, StringLength);
>
> -    return _SQLGetStmtAttr(hstmt, Attribute, Value, BufferLength, StringLength);
> +    return _SQLGetStmtAttr(hstmt, Attribute, Value, BufferLength, StringLength _wide0);
>  }
>
>  #ifdef ENABLE_ODBC_WIDE
> @@ -4468,7 +4493,7 @@ SQLGetStmtAttrW(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER Value, SQLINTEG
>      tdsdump_log(TDS_DBG_FUNC, "SQLGetStmtAttr(%p, %d, %p, %d, %p)\n",
>              hstmt, (int)Attribute, Value, (int)BufferLength, StringLength);
>
> -    return _SQLGetStmtAttr(hstmt, Attribute, Value, BufferLength, StringLength);
> +    return _SQLGetStmtAttr(hstmt, Attribute, Value, BufferLength, StringLength, 1);
>  }
>  #endif
>  #endif
> @@ -4479,7 +4504,7 @@ SQLGetStmtOption(SQLHSTMT hstmt, SQLUSMALLINT fOption, SQLPOINTER pvParam)
>      tdsdump_log(TDS_DBG_FUNC, "SQLGetStmtOption(%p, %d, %p)\n",
>              hstmt, fOption, pvParam);
>
> -    return _SQLGetStmtAttr(hstmt, (SQLINTEGER) fOption, pvParam, SQL_MAX_OPTION_STRING_LENGTH, NULL);
> +    return _SQLGetStmtAttr(hstmt, (SQLINTEGER) fOption, pvParam, SQL_MAX_OPTION_STRING_LENGTH, NULL _wide0);
>  }
>
>  SQLRETURN ODBC_PUBLIC ODBC_API
> @@ -6253,7 +6278,7 @@ SQLSetConnectOptionW(SQLHDBC hdbc, SQLUSMALLINT fOption, SQLULEN vParam)
>  #endif
>
>  static SQLRETURN
> -_SQLSetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength)
> +_SQLSetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength WIDE)
>  {
>      SQLULEN ui = (SQLULEN) (TDS_INTPTR) ValuePtr;
>      SQLUSMALLINT *usip = (SQLUSMALLINT *) ValuePtr;
> @@ -6525,6 +6550,47 @@ _SQLSetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLIN
>          }
>          stmt->sql_rowset_size = ui;
>          break;
> +    case SQL_SOPT_SS_QUERYNOTIFICATION_TIMEOUT:
> +        if (*lp < 1) {
> +            odbc_errs_add(&stmt->errs, "HY024", NULL);
> +            break;
> +        }
> +        stmt->attr.qn_timeout = *lp;
> +        break;
> +    case SQL_SOPT_SS_QUERYNOTIFICATION_MSGTEXT:
> +        if (!IS_VALID_LEN(StringLength)) {
> +            odbc_errs_add(&stmt->errs, "HY090", NULL);
> +            break;
> +        }
> +        {
> +            DSTR s;
> +
> +            tds_dstr_init(&s);
> +            if (!odbc_dstr_copy_oct(stmt->dbc, &s, StringLength, (ODBC_CHAR*) ValuePtr)) {
> +                odbc_errs_add(&stmt->errs, "HY001", NULL);
> +                break;
> +            }
> +            tds_dstr_free(&stmt->attr.qn_msgtext);
> +            stmt->attr.qn_msgtext = s;
> +        }

I don't think setting on a temporary is needed. odbc_dstr_copy_oct
should take care of it.

> +        break;
> +    case SQL_SOPT_SS_QUERYNOTIFICATION_OPTIONS:
> +        if (!IS_VALID_LEN(StringLength)) {
> +            odbc_errs_add(&stmt->errs, "HY090", NULL);
> +            break;
> +        }
> +        {
> +            DSTR s;
> +
> +            tds_dstr_init(&s);
> +            if (!odbc_dstr_copy_oct(stmt->dbc, &s, StringLength, (ODBC_CHAR*) ValuePtr)) {
> +                odbc_errs_add(&stmt->errs, "HY001", NULL);
> +                break;
> +            }
> +            tds_dstr_free(&stmt->attr.qn_options);
> +            stmt->attr.qn_options = s;
> +        }
> +        break;
>      default:
>          odbc_errs_add(&stmt->errs, "HY092", NULL);
>          break;
> @@ -6539,7 +6605,7 @@ SQLSetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINT
>      tdsdump_log(TDS_DBG_FUNC, "SQLSetStmtAttr(%p, %d, %p, %d)\n",
>              hstmt, (int)Attribute, ValuePtr, (int)StringLength);
>
> -    return _SQLSetStmtAttr(hstmt, Attribute, ValuePtr, StringLength);
> +    return _SQLSetStmtAttr(hstmt, Attribute, ValuePtr, StringLength _wide0);
>  }
>
>  #ifdef ENABLE_ODBC_WIDE
> @@ -6549,7 +6615,7 @@ SQLSetStmtAttrW(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLIN
>      tdsdump_log(TDS_DBG_FUNC, "SQLSetStmtAttr(%p, %d, %p, %d)\n",
>              hstmt, (int)Attribute, ValuePtr, (int)StringLength);
>
> -    return _SQLSetStmtAttr(hstmt, Attribute, ValuePtr, StringLength);
> +    return _SQLSetStmtAttr(hstmt, Attribute, ValuePtr, StringLength, 1);
>  }
>  #endif
>  #endif
> @@ -6560,7 +6626,7 @@ SQLSetStmtOption(SQLHSTMT hstmt, SQLUSMALLINT fOption, SQLULEN vParam)
>      tdsdump_log(TDS_DBG_FUNC, "SQLSetStmtOption(%p, %u, %u)\n", hstmt, fOption, (unsigned)vParam);
>
>      /* XXX: Lost precision */
> -    return _SQLSetStmtAttr(hstmt, (SQLINTEGER) fOption, (SQLPOINTER) (TDS_INTPTR) vParam, SQL_NTS);
> +    return _SQLSetStmtAttr(hstmt, (SQLINTEGER) fOption, (SQLPOINTER) (TDS_INTPTR) vParam, SQL_NTS _wide0);
>  }
>
>  ODBC_FUNC(SQLSpecialColumns, (P(SQLHSTMT,hstmt), P(SQLUSMALLINT,fColType), PCHARIN(CatalogName,SQLSMALLINT),
> @@ -7269,10 +7335,10 @@ SQLSetScrollOptions(SQLHSTMT hstmt, SQLUSMALLINT fConcurrency, SQLLEN crowKeyset
>          ODBC_EXIT_(stmt);
>      }
>
> -    _SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER) (TDS_INTPTR) cursor_type, 0);
> -    _SQLSetStmtAttr(hstmt, SQL_ATTR_CONCURRENCY, (SQLPOINTER) (TDS_INTPTR) fConcurrency, 0);
> -    _SQLSetStmtAttr(hstmt, SQL_ATTR_KEYSET_SIZE, (SQLPOINTER) (TDS_INTPTR) crowKeyset, 0);
> -    _SQLSetStmtAttr(hstmt, SQL_ROWSET_SIZE, (SQLPOINTER) (TDS_INTPTR) crowRowset, 0);
> +    _SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER) (TDS_INTPTR) cursor_type, 0 _wide0);
> +    _SQLSetStmtAttr(hstmt, SQL_ATTR_CONCURRENCY, (SQLPOINTER) (TDS_INTPTR) fConcurrency, 0 _wide0);
> +    _SQLSetStmtAttr(hstmt, SQL_ATTR_KEYSET_SIZE, (SQLPOINTER) (TDS_INTPTR) crowKeyset, 0 _wide0);
> +    _SQLSetStmtAttr(hstmt, SQL_ROWSET_SIZE, (SQLPOINTER) (TDS_INTPTR) crowRowset, 0 _wide0);
>
>      ODBC_EXIT_(stmt);
>  }
> diff --git a/src/tds/query.c b/src/tds/query.c
> index a076255..2c1ac3e 100644
> --- a/src/tds/query.c
> +++ b/src/tds/query.c
> @@ -312,6 +312,76 @@ tds_start_query(TDSSOCKET *tds, unsigned char packet_type)
>  }
>
>  /**
> + * tds72_submit_query_notification() sends a request for the server to call
> + * us back when the results of the query would have changed; although this
> + * involves sending the query to the server, the server does not execute it.
> + * \tds
> + * \param query   query to analyze for change detection. There are specific
> + *                limitations on the statement which depend on the server;
> + *                consult your server's documentation
> + * \param msgtext arbitrary extra data to be passed along with the
> + *                notification request and which will be delivered back to
> + *                the client when the notification happens
> + * \param options specification of how the notification is to be delivered.
> + *                Consult your server's documentatin for syntax
> + * \param timeout number of seconds to keep the notification request active
> + *                before automatically expiring it (with a notification)
> + * \return TDS_FAIL or TDS_SUCCESS
> + */
> +TDSRET
> +tds72_submit_query_notification(TDSSOCKET * tds, const char *query, const char *msgtext, const char *options, TDS_INT timeout)
> +{
> +    size_t query_len;
> +    int qn_len;
> +    const char *converted_msgtext;
> +    const char *converted_options;
> +    size_t converted_msgtext_len;
> +    size_t converted_options_len;
> +
> +    CHECK_TDS_EXTRA(tds);
> +    if (!query || !msgtext || !options || !IS_TDS72_PLUS(tds->conn))
> +        return TDS_FAIL;
> +
> +    if (tds_set_state(tds, TDS_QUERYING) != TDS_QUERYING)
> +        return TDS_FAIL;
> +
> +    converted_msgtext = tds_convert_string(tds, tds->conn->char_convs[client2ucs2], msgtext, (int)strlen(msgtext), &converted_msgtext_len);
> +    if (!converted_msgtext) {
> +        tds_set_state(tds, TDS_IDLE);
> +        return TDS_FAIL;
> +    }
> +
> +    converted_options = tds_convert_string(tds, tds->conn->char_convs[client2ucs2], options, (int)strlen(options), &converted_options_len);
> +    if (!converted_options) {
> +        tds_convert_string_free(query, converted_msgtext);
> +        tds_set_state(tds, TDS_IDLE);
> +        return TDS_FAIL;
> +    }
> +    qn_len = 2 + converted_msgtext_len + 2 + converted_options_len + 4;
> +
> +    query_len = strlen(query);
> +    tds->out_flag = TDS_QUERY;
> +    tds_put_int(tds, 4 + 18 + 6 + qn_len);         /* total length */
> +    tds_put_int(tds, 18);                          /* length: transaction descriptor */
> +    tds_put_smallint(tds, 2);                      /* type: transaction descriptor */
> +    tds_put_n(tds, tds->conn->tds72_transaction, 8);  /* transaction */
> +    tds_put_int(tds, 1);                           /* request count */
> +    tds_put_int(tds, 6 + qn_len);                  /* length: query notification */
> +    tds_put_smallint(tds, 1);                      /* type: query notification */
> +    tds_put_smallint(tds, converted_msgtext_len);  /* notifyid */
> +    tds_put_n(tds, converted_msgtext, converted_msgtext_len);
> +    tds_put_smallint(tds, converted_options_len);  /* ssbdeployment */
> +    tds_put_n(tds, converted_options, converted_options_len);
> +    tds_put_int(tds, timeout);                     /* timeout */
> +    tds_put_string(tds, query, (int)query_len);
> +
> +    tds_convert_string_free(query, converted_msgtext);
> +    tds_convert_string_free(query, converted_options);
> +
> +    return tds_query_flush_packet(tds);
> +}
> +
> +/**
>   * tds_submit_query_params() sends a language string to the database server for
>   * processing.  TDS 4.2 is a plain text message with a packet type of 0x01,
>   * TDS 7.0 is a unicode string with packet type 0x01, and TDS 5.0 uses a

I think we should send this header not only for normal query, perhaps
tds_start_query is the better place to put it.

Frediano
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.