Re: MS query notifications patch

Richard Hughes <[email protected]>
Newsgroups gmane.comp.db.tds.freetds
Message-ID <[email protected]>
On 2014-05-10 at 09:55, Frediano Ziglio wrote:
> 2014-04-23 18:50 GMT+01:00 Richard Hughes <[email protected]>:
>> On 2014-04-18 at 09:39, Frediano Ziglio wrote:
>>> 2014-04-16 19:12 GMT+01:00 Richard Hughes <[email protected]>:
>>>> I needed support for SQL Server 2005's Query Notifications feature, so
>>>> here's a patch against tip.

> Sorry for late reply. I was on holiday and actually I'm without my own computer.

This entire thread seems to be plagued with late replies - work got
busy for me :-)

> I like the idea of splitting in query.c the code that handle tds7 from
> other protocol however I would not expose all these definitions
> outside it and instead all function calls should add the head
> parameter.

I had ruled out that option because I didn't think I was allowed to
change the TDS API. If that breakage is allowed then absolutely adding
a new parameter is cleaner.

This version of the patch does, however, undo the splitting-out of
tds7. I find it difficult to manage and review patches (even my own)
which combine cosmetic changes with functional changes. Once we're
happy with the functional stuff then either you or I can do the split
as a separate task.

...
> It make sense but perhaps here would
> be more sensible to add a tds_start_query_head (or similar) function.

Done.

>>>> @@ -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.
>>
>> Yes - the _WITH_INFO part should have been a clue that I needed to
>> return some info. MS Native Client returns HY000 for the first case
>> and still executes the query, but I don't have a pre-2005 server to
>> find out what should happen in the second case so I just left it as a
>> generic error.
>>
>
> I'll try with a former server.

If it's inconvenient for you, I can get all the installation media for
2000 - I just need to find the time to spin up a virtual machine and
get it running and tested.

Richard.

_______________________________________________
FreeTDS mailing list
[email protected]
http://lists.ibiblio.org/mailman/listinfo/freetds
freetds-mssql-query-notifications-v3.diff (application/octet-stream, 24 KB)
diff --git a/include/freetds/odbc.h b/include/freetds/odbc.h
index 4413b61..17ee0f4 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;
+	SQLUINTEGER qn_timeout;
 };
 
 typedef enum
diff --git a/include/freetds/tds.h b/include/freetds/tds.h
index 413f1d8..79d5d83 100644
--- a/include/freetds/tds.h
+++ b/include/freetds/tds.h
@@ -587,6 +587,14 @@ typedef struct tds_login
 	unsigned int valid_configuration:1;
 } TDSLOGIN;
 
+typedef struct tds_headers
+{
+	const char *qn_options;
+	const char *qn_msgtext;
+	TDS_INT qn_timeout;
+	/* TDS 7.4+: trace activity ID char[20] */
+} TDSHEADERS;
+
 typedef struct tds_locale
 {
 	char *language;
@@ -1274,10 +1282,10 @@ int tds_connect_and_login(TDSSOCKET * tds, TDSLOGIN * login);
 
 /* query.c */
 TDSRET tds_submit_query(TDSSOCKET * tds, const char *query);
-TDSRET tds_submit_query_params(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params);
+TDSRET tds_submit_query_params(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params, TDSHEADERS * head);
 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 tds_submit_execdirect(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params, TDSHEADERS * head);
 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);
@@ -1285,7 +1293,7 @@ const char *tds_next_placeholder(const char *start);
 int tds_count_placeholders(const char *query);
 int tds_needs_unprepare(TDSSOCKET * tds, TDSDYNAMIC * dyn);
 TDSRET tds_submit_unprepare(TDSSOCKET * tds, TDSDYNAMIC * dyn);
-TDSRET tds_submit_rpc(TDSSOCKET * tds, const char *rpc_name, TDSPARAMINFO * params);
+TDSRET tds_submit_rpc(TDSSOCKET * tds, const char *rpc_name, TDSPARAMINFO * params, TDSHEADERS * head);
 TDSRET tds_submit_optioncmd(TDSSOCKET * tds, TDS_OPTION_CMD command, TDS_OPTION option, TDS_OPTION_ARG *param, TDS_INT param_size);
 TDSRET tds_submit_begin_tran(TDSSOCKET *tds);
 TDSRET tds_submit_rollback(TDSSOCKET *tds, int cont);
@@ -1312,7 +1320,7 @@ TDSRET tds_cursor_dealloc(TDSSOCKET * tds, TDSCURSOR * cursor);
 TDSRET tds_cursor_update(TDSSOCKET * tds, TDSCURSOR * cursor, TDS_CURSOR_OPERATION op, TDS_INT i_row, TDSPARAMINFO * params);
 TDSRET tds_cursor_setname(TDSSOCKET * tds, TDSCURSOR * cursor);
 
-TDSRET tds_multiple_init(TDSSOCKET *tds, TDSMULTIPLE *multiple, TDS_MULTIPLE_TYPE type);
+TDSRET tds_multiple_init(TDSSOCKET *tds, TDSMULTIPLE *multiple, TDS_MULTIPLE_TYPE type, TDSHEADERS * head);
 TDSRET tds_multiple_done(TDSSOCKET *tds, TDSMULTIPLE *multiple);
 TDSRET tds_multiple_query(TDSSOCKET *tds, TDSMULTIPLE *multiple, const char *query, TDSPARAMINFO * params);
 TDSRET tds_multiple_execute(TDSSOCKET *tds, TDSMULTIPLE *multiple, TDSDYNAMIC * dyn);
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/ctlib/ct.c b/src/ctlib/ct.c
index 38a51cf..722de95 100644
--- a/src/ctlib/ct.c
+++ b/src/ctlib/ct.c
@@ -949,7 +949,7 @@ ct_send(CS_COMMAND * cmd)
 		}
 
 		pparam_info = paraminfoalloc(tds, rpc->param_list);
-		ret = tds_submit_rpc(tds, rpc->name, pparam_info);
+		ret = tds_submit_rpc(tds, rpc->name, pparam_info, NULL);
 
 		tds_free_param_results(pparam_info);
 
@@ -967,7 +967,7 @@ ct_send(CS_COMMAND * cmd)
 		ret = TDS_FAIL;
 		if (cmd->input_params) {
 			pparam_info = paraminfoalloc(tds, cmd->input_params);
-			ret = tds_submit_query_params(tds, cmd->query, pparam_info);
+			ret = tds_submit_query_params(tds, cmd->query, pparam_info, NULL);
 			tds_free_param_results(pparam_info);
 		} else {
 			ret = tds_submit_query(tds, cmd->query);
diff --git a/src/dblib/rpc.c b/src/dblib/rpc.c
index 6b35bca..5e322d5 100644
--- a/src/dblib/rpc.c
+++ b/src/dblib/rpc.c
@@ -301,7 +301,7 @@ dbrpcsend(DBPROCESS * dbproc)
 			if (!pparam_info)
 				return FAIL;
 		}
-		erc = tds_submit_rpc(dbproc->tds_socket, dbproc->rpc->name, pparam_info);
+		erc = tds_submit_rpc(dbproc->tds_socket, dbproc->rpc->name, pparam_info, NULL);
 		tds_free_param_results(pparam_info);
 		if (erc == TDS_FAIL) {
 			tdsdump_log(TDS_DBG_INFO1, "returning FAIL: tds_submit_rpc() failed\n");
diff --git a/src/odbc/native.c b/src/odbc/native.c
index decfcb9..6581bb0 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)) && !IS_TDS72_PLUS(stmt->dbc->tds_socket->conn)) {
+		odbc_errs_add(&stmt->errs, "HY000", "Feature is not supported by this server");
+		return SQL_SUCCESS_WITH_INFO;
+	}
+
 	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..c3b2e99 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);
 }
@@ -3236,6 +3240,19 @@ odbc_cursor_execute(TDS_STMT * stmt)
 	return ret;
 }
 
+static TDSHEADERS *
+odbc_init_headers(TDS_STMT * stmt, TDSHEADERS * head)
+{
+	if (tds_dstr_isempty(&stmt->attr.qn_msgtext) || tds_dstr_isempty(&stmt->attr.qn_options))
+		return NULL;
+
+	memset(head, 0, sizeof(*head));
+	head->qn_timeout = stmt->attr.qn_timeout;
+	head->qn_msgtext = tds_dstr_cstr(&stmt->attr.qn_msgtext);
+	head->qn_options = tds_dstr_cstr(&stmt->attr.qn_options);
+	return head;
+}
+
 static SQLRETURN
 _SQLExecute(TDS_STMT * stmt)
 {
@@ -3247,6 +3264,7 @@ _SQLExecute(TDS_STMT * stmt)
 	SQLUSMALLINT param_status;
 	int found_info = 0, found_error = 0;
 	SQLLEN total_rows = TDS_NO_COUNT;
+	TDSHEADERS head;
 
 	tdsdump_log(TDS_DBG_FUNC, "_SQLExecute(%p)\n", 
 			stmt);
@@ -3300,7 +3318,7 @@ _SQLExecute(TDS_STMT * stmt)
 		stmt->prepared_pos = end;
 		tmp = *end;
 		*end = 0;
-		ret = tds_submit_rpc(tds, name, stmt->params);
+		ret = tds_submit_rpc(tds, name, stmt->params, odbc_init_headers(stmt, &head));
 		*end = tmp;
 	} else if (stmt->attr.cursor_type != SQL_CURSOR_FORWARD_ONLY || stmt->attr.concurrency != SQL_CONCUR_READ_ONLY) {
 		ret = odbc_cursor_execute(stmt);
@@ -3310,15 +3328,15 @@ _SQLExecute(TDS_STMT * stmt)
 		/* SQLExecDirect */
 		if (stmt->num_param_rows <= 1) {
 			if (!stmt->params) {
-				ret = tds_submit_query(tds, stmt->query);
+				ret = tds_submit_query_params(tds, stmt->query, NULL, odbc_init_headers(stmt, &head));
 			} else {
-				ret = tds_submit_execdirect(tds, stmt->query, stmt->params);
+				ret = tds_submit_execdirect(tds, stmt->query, stmt->params, odbc_init_headers(stmt, &head));
 			}
 		} else {
 			/* pack multiple submit using language */
 			TDSMULTIPLE multiple;
 
-			ret = tds_multiple_init(tds, &multiple, TDS_MULTIPLE_QUERY);
+			ret = tds_multiple_init(tds, &multiple, TDS_MULTIPLE_QUERY, odbc_init_headers(stmt, &head));
 			for (stmt->curr_param_row = 0; TDS_SUCCEED(ret); ) {
 				/* submit a query */
 				ret = tds_multiple_query(tds, &multiple, stmt->query, stmt->params);
@@ -3381,7 +3399,7 @@ _SQLExecute(TDS_STMT * stmt)
 		} else {
 			TDSMULTIPLE multiple;
 
-			ret = tds_multiple_init(tds, &multiple, TDS_MULTIPLE_EXECUTE);
+			ret = tds_multiple_init(tds, &multiple, TDS_MULTIPLE_EXECUTE, NULL);
 			for (stmt->curr_param_row = 0; TDS_SUCCEED(ret); ) {
 				dyn = stmt->dyn;
 				tds_free_input_params(dyn);
@@ -3480,6 +3498,10 @@ _SQLExecute(TDS_STMT * stmt)
 	}
 	if ((found_info || found_error) && stmt->errs.lastrc != SQL_ERROR)
 		stmt->errs.lastrc = SQL_SUCCESS_WITH_INFO;
+	if (tds_dstr_isempty(&stmt->attr.qn_msgtext) != tds_dstr_isempty(&stmt->attr.qn_options)) {
+		odbc_errs_add(&stmt->errs, "HY000", "Attribute ignored");
+		stmt->errs.lastrc = SQL_SUCCESS_WITH_INFO;
+	}
 	if (found_error && stmt->num_param_rows <= 1)
 		stmt->errs.lastrc = SQL_ERROR;
 	if (stmt->curr_param_row < stmt->num_param_rows) {
@@ -4203,6 +4225,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 +4307,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 +4462,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 +4496,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 +4506,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 +4517,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 +6291,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 +6563,33 @@ _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 = (SQLUINTEGER)*ulp;
+		break;
+	case SQL_SOPT_SS_QUERYNOTIFICATION_MSGTEXT:
+		if (!IS_VALID_LEN(StringLength)) {
+			odbc_errs_add(&stmt->errs, "HY090", NULL);
+			break;
+		}
+		if (!odbc_dstr_copy_oct(stmt->dbc, &stmt->attr.qn_msgtext, StringLength, (ODBC_CHAR*) ValuePtr)) {
+			odbc_errs_add(&stmt->errs, "HY001", NULL);
+			break;
+		}
+		break;
+	case SQL_SOPT_SS_QUERYNOTIFICATION_OPTIONS:
+		if (!IS_VALID_LEN(StringLength)) {
+			odbc_errs_add(&stmt->errs, "HY090", NULL);
+			break;
+		}
+		if (!odbc_dstr_copy_oct(stmt->dbc, &stmt->attr.qn_options, StringLength, (ODBC_CHAR*) ValuePtr)) {
+			odbc_errs_add(&stmt->errs, "HY001", NULL);
+			break;
+		}
+		break;
 	default:
 		odbc_errs_add(&stmt->errs, "HY092", NULL);
 		break;
@@ -6539,7 +6604,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 +6614,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 +6625,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 +7334,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..8f72345 100644
--- a/src/tds/query.c
+++ b/src/tds/query.c
@@ -208,7 +208,7 @@ tds_set_cur_dyn(TDSSOCKET *tds, TDSDYNAMIC *dyn)
 TDSRET
 tds_submit_query(TDSSOCKET * tds, const char *query)
 {
-	return tds_submit_query_params(tds, query, NULL);
+	return tds_submit_query_params(tds, query, NULL, NULL);
 }
 
 /**
@@ -280,20 +280,62 @@ tds_put_data(TDSSOCKET * tds, TDSCOLUMN * curcol)
 }
 
 /**
- * Start of TDS 7.2+ query packet.
+ * Start query packet of a given type
+ * \tds
+ * \param packet_type  packet type
+ * \param head         extra information to put in a TDS7 header
  */
-static const TDS_UCHAR tds72_query_start[] = {
-	/* total length */
-	0x16, 0, 0, 0,
-	/* length */
-	0x12, 0, 0, 0,
-	/* type */
-	0x02, 0,
-	/* transaction */
-	0, 0, 0, 0, 0, 0, 0, 0,
-	/* request count */
-	1, 0, 0, 0
-};
+static TDSRET
+tds_start_query_head(TDSSOCKET *tds, unsigned char packet_type, TDSHEADERS * head)
+{
+	tds->out_flag = packet_type;
+	if (IS_TDS72_PLUS(tds->conn)) {
+		int qn_len = 0;
+		const char *converted_msgtext = NULL;
+		const char *converted_options = NULL;
+		size_t converted_msgtext_len = 0;
+		size_t converted_options_len = 0;
+
+		if (head && head->qn_msgtext && head->qn_options) {
+			converted_msgtext = tds_convert_string(tds, tds->conn->char_convs[client2ucs2], head->qn_msgtext, (int)strlen(head->qn_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], head->qn_options, (int)strlen(head->qn_options), &converted_options_len);
+			if (!converted_options) {
+				tds_convert_string_free(head->qn_msgtext, converted_msgtext);
+				tds_set_state(tds, TDS_IDLE);
+				return TDS_FAIL;
+			}
+
+			qn_len = 6 + 2 + converted_msgtext_len + 2 + converted_options_len;
+			if (head->qn_timeout != 0)
+				qn_len += 4;
+		}
+
+		tds_put_int(tds, 4 + 18 + 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 */
+		if (qn_len != 0) {
+			tds_put_int(tds, 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);
+			if (head->qn_timeout != 0)
+				tds_put_int(tds, head->qn_timeout);        /* timeout */
+
+			tds_convert_string_free(head->qn_options, converted_options);
+			tds_convert_string_free(head->qn_msgtext, converted_msgtext);
+		}
+	}
+	return TDS_SUCCESS;
+}
 
 /**
  * Start query packet of a given type
@@ -303,12 +345,9 @@ static const TDS_UCHAR tds72_query_start[] = {
 static void
 tds_start_query(TDSSOCKET *tds, unsigned char packet_type)
 {
-	tds->out_flag = packet_type;
-	if (IS_TDS72_PLUS(tds->conn)) {
-		tds_put_n(tds, tds72_query_start, 10);
-		tds_put_n(tds, tds->conn->tds72_transaction, 8);
-		tds_put_n(tds, tds72_query_start + 10 + 8, 4);
-	}
+	/* no need to check return value here because tds_start_query_head() cannot
+	fail when given a NULL head parameter */
+	tds_start_query_head(tds, packet_type, NULL);
 }
 
 /**
@@ -322,7 +361,7 @@ tds_start_query(TDSSOCKET *tds, unsigned char packet_type)
  * \return TDS_FAIL or TDS_SUCCESS
  */
 TDSRET
-tds_submit_query_params(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params)
+tds_submit_query_params(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params, TDSHEADERS * head)
 {
 	size_t query_len;
 	int num_params = params ? params->num_cols : 0;
@@ -400,7 +439,11 @@ tds_submit_query_params(TDSSOCKET * tds, const char *query, TDSPARAMINFO * param
 			}
 		}
  
-		tds_start_query(tds, TDS_RPC);
+		if (tds_start_query_head(tds, TDS_RPC, head) != TDS_SUCCESS) {
+			tds_convert_string_free(query, converted_query);
+			free(param_definition);
+			return TDS_FAIL;
+		}
 		/* procedure name */
 		if (IS_TDS71_PLUS(tds->conn)) {
 			tds_put_smallint(tds, -1);
@@ -1266,7 +1309,7 @@ failure:
  * \return TDS_FAIL or TDS_SUCCESS
  */
 TDSRET
-tds_submit_execdirect(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params)
+tds_submit_execdirect(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params, TDSHEADERS * head)
 {
 	size_t query_len;
 	TDSCOLUMN *param;
@@ -1303,7 +1346,11 @@ tds_submit_execdirect(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params)
 			return TDS_FAIL;
 		}
 
-		tds_start_query(tds, TDS_RPC);
+		if (tds_start_query_head(tds, TDS_RPC, head) != TDS_SUCCESS) {
+			tds_convert_string_free(query, converted_query);
+			free(param_definition);
+			return TDS_FAIL;
+		}
 		/* procedure name */
 		if (IS_TDS71_PLUS(tds->conn)) {
 			tds_put_smallint(tds, -1);
@@ -1946,7 +1993,7 @@ tds_send_emulated_rpc(TDSSOCKET * tds, const char *rpc_name, TDSPARAMINFO * para
  * \param params   parameters informations. NULL for no parameters
  */
 TDSRET
-tds_submit_rpc(TDSSOCKET * tds, const char *rpc_name, TDSPARAMINFO * params)
+tds_submit_rpc(TDSSOCKET * tds, const char *rpc_name, TDSPARAMINFO * params, TDSHEADERS * head)
 {
 	TDSCOLUMN *param;
 	int rpc_name_len, i;
@@ -1976,7 +2023,10 @@ tds_submit_rpc(TDSSOCKET * tds, const char *rpc_name, TDSPARAMINFO * params)
 			tds_set_state(tds, TDS_IDLE);
 			return TDS_FAIL;
 		}
-		tds_start_query(tds, TDS_RPC);
+		if (tds_start_query_head(tds, TDS_RPC, head) != TDS_SUCCESS) {
+			tds_convert_string_free(rpc_name, converted_name);
+			return TDS_FAIL;
+		}
 
 		TDS_PUT_SMALLINT(tds, converted_name_len / 2);
 		tds_put_n(tds, converted_name, (int)converted_name_len);
@@ -3222,7 +3272,7 @@ tds_send_emulated_execute(TDSSOCKET * tds, const char *query, TDSPARAMINFO * par
 enum { MUL_STARTED = 1 };
 
 TDSRET
-tds_multiple_init(TDSSOCKET *tds, TDSMULTIPLE *multiple, TDS_MULTIPLE_TYPE type)
+tds_multiple_init(TDSSOCKET *tds, TDSMULTIPLE *multiple, TDS_MULTIPLE_TYPE type, TDSHEADERS * head)
 {
 	unsigned char packet_type;
 	multiple->type = type;
@@ -3241,7 +3291,8 @@ tds_multiple_init(TDSSOCKET *tds, TDSMULTIPLE *multiple, TDS_MULTIPLE_TYPE type)
 			packet_type = TDS_RPC;
 		break;
 	}
-	tds_start_query(tds, packet_type);
+	if (tds_start_query_head(tds, packet_type, head) != TDS_SUCCESS)
+		return TDS_FAIL;
 
 	return TDS_SUCCESS;
 }
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.