Skip to Content.
Sympa Menu

freetds - Re: [freetds] MS query notifications patch

freetds AT lists.ibiblio.org

Subject: FreeTDS Development Group

List archive

Chronological Thread  
  • From: Frediano Ziglio <freddy77 AT gmail.com>
  • To: Richard Hughes <richard.freetds AT rhughes.net>, FreeTDS Development Group <freetds AT lists.ibiblio.org>
  • Subject: Re: [freetds] MS query notifications patch
  • Date: Fri, 18 Apr 2014 09:39:11 +0100

2014-04-16 19:12 GMT+01:00 Richard Hughes <cyreve AT gmail.com>:
> 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




Archive powered by MHonArc 2.6.24.

Top of Page