Re: MS query notifications patch

Richard Hughes <[email protected]>
Newsgroups gmane.comp.db.tds.freetds
Message-ID <[email protected]>
Here's a couple of smallish bug fixes over my previous patch.

On 2014-05-14 at 21:19, Frediano Ziglio wrote:
> Let see if this time I'm more quick. I have a test machine. Actually I
> committed to my master branch and launched the test. If there are no
> regression I'll commit. Just a request. Which mail address (your) can
> I use for the commit and which description for the commit do you want.
> The feature is really useful!
>
> Actually is just:
>
> "Add support for MS query notifications"
>
> a bit too few considering the importance can have.

You might like to steal the initial sentence of the 'About Query
Notifications' section from my initial post. I wrote that bit because
I've been using MSSQLServer for years and only just found the feature,
so I guessed that some of the readers of this mailing list would have
no idea what I was talking about.

Use my Reply-To address (richard.freetds@...) for the commit.

>>>>>> @@ -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.
>
> My testbox has an msde (free mssql 2000) I can use.
>
> I just need to code a new test. Oh.. if you have some code to test
> with is more than welcome. I try adding tests for all feature we have.

I'm afraid the code I've been using for basic testing heavily depends
on libraries which are owned by my employer.

Certainly it was my intent to look at writing a few tests within your
framework as my next task before we can consider the patch to be
complete. The code I wrote in SQLGetStmtAttr() has never been
executed, for example.

Richard.

_______________________________________________
FreeTDS mailing list
[email protected]
http://lists.ibiblio.org/mailman/listinfo/freetds
freetds-mssql-query-notifications-v4.diff (application/octet-stream, 24.3 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..8557cba 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 (ui < 1) {
+			odbc_errs_add(&stmt->errs, "HY024", NULL);
+			break;
+		}
+		stmt->attr.qn_timeout = ui;
+		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..0c05dbd 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;
@@ -362,7 +401,8 @@ tds_submit_query_params(TDSSOCKET * tds, const char *query, TDSPARAMINFO * param
 		}
 		free(new_query);
 	} else if (!IS_TDS7_PLUS(tds->conn) || !params || !params->num_cols) {
-		tds_start_query(tds, TDS_QUERY);
+		if (tds_start_query_head(tds, TDS_QUERY, head) != TDS_SUCCESS)
+			return TDS_FAIL;
 		tds_put_string(tds, query, (int)query_len);
 	} else {
 		TDSCOLUMN *param;
@@ -400,7 +440,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 +1310,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 +1347,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 +1994,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 +2024,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 +3273,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 +3292,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.