Re: MS query notifications patch

Richard Hughes <[email protected]>
Newsgroups gmane.comp.db.tds.freetds
Message-ID <[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.
>
> 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.

You're right. I was too stuck in my particular use-case to consider
that RPC might be possible. I actually can't think of realistic design
for an client application that would set up parameterized query
notifications, however the server does support it so I've gone ahead
and implemented it. Unfortunately this makes the patch a whole lot
bigger, and a large part of my delay in getting back to you was spent
changing my mind several times about the neatest way to squeeze it in.
You may wish to read the diff with ignore-whitespace, because I ended
up pulling a load of if statements out into their own functions.

>> @@ -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.

All your other comments have been swept up in to the big restructuring
I needed to do to get RPC working.

Richard.

_______________________________________________
FreeTDS mailing list
[email protected]
http://lists.ibiblio.org/mailman/listinfo/freetds
freetds-mssql-query-notifications-v2.diff (application/octet-stream, 38 KB)
diff --git a/include/freetds/odbc.h b/include/freetds/odbc.h
index 4413b61..17ee0f4
--- 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..6615245
--- 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;
@@ -1275,9 +1283,11 @@ 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 tds7_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 tds7_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);
@@ -1286,6 +1296,7 @@ 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 tds7_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);
@@ -1313,6 +1324,7 @@ TDSRET tds_cursor_update(TDSSOCKET * tds, TDSCURSOR * cursor, TDS_CURSOR_OPERATI
 TDSRET tds_cursor_setname(TDSSOCKET * tds, TDSCURSOR * cursor);
 
 TDSRET tds_multiple_init(TDSSOCKET *tds, TDSMULTIPLE *multiple, TDS_MULTIPLE_TYPE type);
+TDSRET tds7_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/odbc/native.c b/src/odbc/native.c
index decfcb9..6581bb0
--- 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..59ffb36
--- 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,15 @@ odbc_cursor_execute(TDS_STMT * stmt)
 	return ret;
 }
 
+static void
+query_notification_init_headers(TDS_STMT * stmt, TDSHEADERS * head)
+{
+	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);
+}
+
 static SQLRETURN
 _SQLExecute(TDS_STMT * stmt)
 {
@@ -3300,7 +3313,13 @@ _SQLExecute(TDS_STMT * stmt)
 		stmt->prepared_pos = end;
 		tmp = *end;
 		*end = 0;
-		ret = tds_submit_rpc(tds, name, stmt->params);
+		if (!tds_dstr_isempty(&stmt->attr.qn_msgtext) && !tds_dstr_isempty(&stmt->attr.qn_options)) {
+			TDSHEADERS head;
+			query_notification_init_headers(stmt, &head);
+			ret = tds7_submit_rpc(tds, name, stmt->params, &head);
+		} else {
+			ret = tds_submit_rpc(tds, name, stmt->params);
+		}
 		*end = tmp;
 	} else if (stmt->attr.cursor_type != SQL_CURSOR_FORWARD_ONLY || stmt->attr.concurrency != SQL_CONCUR_READ_ONLY) {
 		ret = odbc_cursor_execute(stmt);
@@ -3309,16 +3328,32 @@ _SQLExecute(TDS_STMT * stmt)
 		/* TODO cursor change way of calling */
 		/* SQLExecDirect */
 		if (stmt->num_param_rows <= 1) {
-			if (!stmt->params) {
-				ret = tds_submit_query(tds, stmt->query);
+			if (!tds_dstr_isempty(&stmt->attr.qn_msgtext) && !tds_dstr_isempty(&stmt->attr.qn_options)) {
+				TDSHEADERS head;
+				query_notification_init_headers(stmt, &head);
+				if (!stmt->params) {
+					ret = tds7_submit_query_params(tds, stmt->query, NULL, &head);
+				} else {
+					ret = tds7_submit_execdirect(tds, stmt->query, stmt->params, &head);
+				}
 			} else {
-				ret = tds_submit_execdirect(tds, stmt->query, stmt->params);
+				if (!stmt->params) {
+					ret = tds_submit_query(tds, stmt->query);
+				} else {
+					ret = tds_submit_execdirect(tds, stmt->query, stmt->params);
+				}
 			}
 		} else {
 			/* pack multiple submit using language */
 			TDSMULTIPLE multiple;
 
-			ret = tds_multiple_init(tds, &multiple, TDS_MULTIPLE_QUERY);
+			if (!tds_dstr_isempty(&stmt->attr.qn_msgtext) && !tds_dstr_isempty(&stmt->attr.qn_options)) {
+				TDSHEADERS head;
+				query_notification_init_headers(stmt, &head);
+				ret = tds7_multiple_init(tds, &multiple, TDS_MULTIPLE_QUERY, &head);
+			} else {
+				ret = tds_multiple_init(tds, &multiple, TDS_MULTIPLE_QUERY);
+			}
 			for (stmt->curr_param_row = 0; TDS_SUCCEED(ret); ) {
 				/* submit a query */
 				ret = tds_multiple_query(tds, &multiple, stmt->query, stmt->params);
@@ -3480,6 +3515,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 +4242,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 +4324,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 +4479,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 +4513,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 +4523,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 +4534,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 +6308,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 +6580,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 +6621,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 +6631,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 +6642,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 +7351,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..9224989
--- a/src/tds/query.c
+++ b/src/tds/query.c
@@ -280,35 +280,58 @@ tds_put_data(TDSSOCKET * tds, TDSCOLUMN * curcol)
 }
 
 /**
- * Start of TDS 7.2+ query packet.
- */
-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
-};
-
-/**
  * Start query packet of a given type
  * \tds
  * \param packet_type  packet type
  */
-static void
-tds_start_query(TDSSOCKET *tds, unsigned char packet_type)
+static TDSRET
+tds_start_query(TDSSOCKET *tds, unsigned char packet_type, TDSHEADERS * head)
 {
 	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);
+		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) {
+				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);
+				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;
 }
 
 /**
@@ -325,7 +348,6 @@ TDSRET
 tds_submit_query_params(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params)
 {
 	size_t query_len;
-	int num_params = params ? params->num_cols : 0;
  
 	CHECK_TDS_EXTRA(tds);
 	if (params)
@@ -362,82 +384,109 @@ 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);
+		tds_start_query(tds, TDS_QUERY, NULL);
 		tds_put_string(tds, query, (int)query_len);
 	} else {
-		TDSCOLUMN *param;
-		size_t definition_len;
-		int count, i;
-		char *param_definition;
-		size_t converted_query_len;
-		const char *converted_query;
+		tds_set_state(tds, TDS_IDLE);
+		return tds7_submit_query_params(tds, query, params, NULL);
+	}
+	return tds_query_flush_packet(tds);
+}
+
+TDSRET
+tds7_submit_query_params(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params, TDSHEADERS * head)
+{
+	TDSCOLUMN *param;
+	size_t definition_len;
+	int count, i;
+	char *param_definition;
+	size_t converted_query_len;
+	const char *converted_query;
+	size_t query_len;
+	int num_params = params ? params->num_cols : 0;
  
-		converted_query = tds_convert_string(tds, tds->conn->char_convs[client2ucs2], query, (int)query_len, &converted_query_len);
-		if (!converted_query) {
-			tds_set_state(tds, TDS_IDLE);
-			return TDS_FAIL;
-		}
+	CHECK_TDS_EXTRA(tds);
+	if (params)
+		CHECK_PARAMINFO_EXTRA(params);
+ 
+	if (!query || !IS_TDS7_PLUS(tds->conn))
+		return TDS_FAIL;
+ 
+	if (tds_set_state(tds, TDS_QUERYING) != TDS_QUERYING)
+		return TDS_FAIL;
+ 
+	query_len = strlen(query);
+ 
+	converted_query = tds_convert_string(tds, tds->conn->char_convs[client2ucs2], query, (int)query_len, &converted_query_len);
+	if (!converted_query) {
+		tds_set_state(tds, TDS_IDLE);
+		return TDS_FAIL;
+	}
 
-		count = tds_count_placeholders_ucs2le(converted_query, converted_query + converted_query_len);
+	count = tds_count_placeholders_ucs2le(converted_query, converted_query + converted_query_len);
  
-		if (!count) {
-			param_definition = tds7_build_param_def_from_params(tds, converted_query, converted_query_len, params, &definition_len);
-			if (!param_definition) {
-				tds_convert_string_free(query, converted_query);
-				tds_set_state(tds, TDS_IDLE);
-				return TDS_FAIL;
-			}
-		} else {
-			/*
-			 * TODO perhaps functions that calls tds7_build_param_def_from_query
-			 * should call also tds7_build_param_def_from_params ??
-			 */
-			param_definition = tds7_build_param_def_from_query(tds, converted_query, converted_query_len, params, &definition_len);
-			if (!param_definition) {
-				tds_convert_string_free(query, converted_query);
-				tds_set_state(tds, TDS_IDLE);
-				return TDS_FAIL;
-			}
+	if (!count) {
+		param_definition = tds7_build_param_def_from_params(tds, converted_query, converted_query_len, params, &definition_len);
+		if (!param_definition) {
+			tds_convert_string_free(query, converted_query);
+			tds_set_state(tds, TDS_IDLE);
+			return TDS_FAIL;
 		}
- 
-		tds_start_query(tds, TDS_RPC);
-		/* procedure name */
-		if (IS_TDS71_PLUS(tds->conn)) {
-			tds_put_smallint(tds, -1);
-			tds_put_smallint(tds, TDS_SP_EXECUTESQL);
-		} else {
-			tds_put_smallint(tds, 13);
-			TDS_PUT_N_AS_UCS2(tds, "sp_executesql");
+	} else {
+		/*
+			* TODO perhaps functions that calls tds7_build_param_def_from_query
+			* should call also tds7_build_param_def_from_params ??
+			*/
+		param_definition = tds7_build_param_def_from_query(tds, converted_query, converted_query_len, params, &definition_len);
+		if (!param_definition) {
+			tds_convert_string_free(query, converted_query);
+			tds_set_state(tds, TDS_IDLE);
+			return TDS_FAIL;
 		}
-		tds_put_smallint(tds, 0);
+	}
  
-		/* string with sql statement */
-		if (!count) {
-			tds_put_byte(tds, 0);
-			tds_put_byte(tds, 0);
-			tds_put_byte(tds, SYBNTEXT);	/* must be Ntype */
-			TDS_PUT_INT(tds, converted_query_len);
-			if (IS_TDS71_PLUS(tds->conn))
-				tds_put_n(tds, tds->conn->collation, 5);
-			TDS_PUT_INT(tds, converted_query_len);
-			tds_put_n(tds, converted_query, converted_query_len);
-		} else {
-			tds7_put_query_params(tds, converted_query, converted_query_len);
-		}
+	if (tds_start_query(tds, TDS_RPC, head) != TDS_SUCCESS) {
 		tds_convert_string_free(query, converted_query);
- 
-		tds7_put_params_definition(tds, param_definition, definition_len);
 		free(param_definition);
+		tds_set_state(tds, TDS_IDLE);
+		return TDS_FAIL;
+	}
+	/* procedure name */
+	if (IS_TDS71_PLUS(tds->conn)) {
+		tds_put_smallint(tds, -1);
+		tds_put_smallint(tds, TDS_SP_EXECUTESQL);
+	} else {
+		tds_put_smallint(tds, 13);
+		TDS_PUT_N_AS_UCS2(tds, "sp_executesql");
+	}
+	tds_put_smallint(tds, 0);
  
-		for (i = 0; i < num_params; i++) {
-			param = params->columns[i];
-			/* TODO check error */
-			tds_put_data_info(tds, param, 0);
-			if (tds_put_data(tds, param) != TDS_SUCCESS)
-				return TDS_FAIL;
-		}
-		tds->current_op = TDS_OP_EXECUTESQL;
+	/* string with sql statement */
+	if (!count) {
+		tds_put_byte(tds, 0);
+		tds_put_byte(tds, 0);
+		tds_put_byte(tds, SYBNTEXT);	/* must be Ntype */
+		TDS_PUT_INT(tds, converted_query_len);
+		if (IS_TDS71_PLUS(tds->conn))
+			tds_put_n(tds, tds->conn->collation, 5);
+		TDS_PUT_INT(tds, converted_query_len);
+		tds_put_n(tds, converted_query, converted_query_len);
+	} else {
+		tds7_put_query_params(tds, converted_query, converted_query_len);
+	}
+	tds_convert_string_free(query, converted_query);
+ 
+	tds7_put_params_definition(tds, param_definition, definition_len);
+	free(param_definition);
+ 
+	for (i = 0; i < num_params; i++) {
+		param = params->columns[i];
+		/* TODO check error */
+		tds_put_data_info(tds, param, 0);
+		if (tds_put_data(tds, param) != TDS_SUCCESS)
+			return TDS_FAIL;
 	}
+	tds->current_op = TDS_OP_EXECUTESQL;
 	return tds_query_flush_packet(tds);
 }
 
@@ -1184,7 +1233,7 @@ tds_submit_prepare(TDSSOCKET * tds, const char *query, const char *id, TDSDYNAMI
 			goto failure;
 		}
 
-		tds_start_query(tds, TDS_RPC);
+		tds_start_query(tds, TDS_RPC, NULL);
 		/* procedure name */
 		if (IS_TDS71_PLUS(tds->conn)) {
 			tds_put_smallint(tds, -1);
@@ -1266,74 +1315,99 @@ failure:
  * \return TDS_FAIL or TDS_SUCCESS
  */
 TDSRET
-tds_submit_execdirect(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params)
+tds7_submit_execdirect(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params, TDSHEADERS * head)
 {
 	size_t query_len;
 	TDSCOLUMN *param;
-	TDSDYNAMIC *dyn;
-	size_t id_len;
+	size_t definition_len = 0;
+	int i;
+	char *param_definition = NULL;
+	size_t converted_query_len;
+	const char *converted_query;
 
 	CHECK_TDS_EXTRA(tds);
 	CHECK_PARAMINFO_EXTRA(params);
 
-	if (!query)
+	if (!query || !IS_TDS7_PLUS(tds->conn))
 		return TDS_FAIL;
 	query_len = strlen(query);
 
-	if (IS_TDS7_PLUS(tds->conn)) {
-		size_t definition_len = 0;
-		int i;
-		char *param_definition = NULL;
-		size_t converted_query_len;
-		const char *converted_query;
-
-		if (tds_set_state(tds, TDS_QUERYING) != TDS_QUERYING)
-			return TDS_FAIL;
-
-		converted_query = tds_convert_string(tds, tds->conn->char_convs[client2ucs2], query, (int)query_len, &converted_query_len);
-		if (!converted_query) {
-			tds_set_state(tds, TDS_IDLE);
-			return TDS_FAIL;
-		}
+	if (tds_set_state(tds, TDS_QUERYING) != TDS_QUERYING)
+		return TDS_FAIL;
 
-		param_definition = tds7_build_param_def_from_query(tds, converted_query, converted_query_len, params, &definition_len);
-		if (!param_definition) {
-			tds_convert_string_free(query, converted_query);
-			tds_set_state(tds, TDS_IDLE);
-			return TDS_FAIL;
-		}
+	converted_query = tds_convert_string(tds, tds->conn->char_convs[client2ucs2], query, (int)query_len, &converted_query_len);
+	if (!converted_query) {
+		tds_set_state(tds, TDS_IDLE);
+		return TDS_FAIL;
+	}
 
-		tds_start_query(tds, TDS_RPC);
-		/* procedure name */
-		if (IS_TDS71_PLUS(tds->conn)) {
-			tds_put_smallint(tds, -1);
-			tds_put_smallint(tds, TDS_SP_EXECUTESQL);
-		} else {
-			tds_put_smallint(tds, 13);
-			TDS_PUT_N_AS_UCS2(tds, "sp_executesql");
-		}
-		tds_put_smallint(tds, 0);
+	param_definition = tds7_build_param_def_from_query(tds, converted_query, converted_query_len, params, &definition_len);
+	if (!param_definition) {
+		tds_convert_string_free(query, converted_query);
+		tds_set_state(tds, TDS_IDLE);
+		return TDS_FAIL;
+	}
 
-		tds7_put_query_params(tds, converted_query, converted_query_len);
-		tds7_put_params_definition(tds, param_definition, definition_len);
+	if (tds_start_query(tds, TDS_RPC, head) != TDS_SUCCESS) {
 		tds_convert_string_free(query, converted_query);
 		free(param_definition);
+		tds_set_state(tds, TDS_IDLE);
+		return TDS_FAIL;
+	}
+	/* procedure name */
+	if (IS_TDS71_PLUS(tds->conn)) {
+		tds_put_smallint(tds, -1);
+		tds_put_smallint(tds, TDS_SP_EXECUTESQL);
+	} else {
+		tds_put_smallint(tds, 13);
+		TDS_PUT_N_AS_UCS2(tds, "sp_executesql");
+	}
+	tds_put_smallint(tds, 0);
 
-		for (i = 0; i < params->num_cols; i++) {
-			TDSRET ret;
+	tds7_put_query_params(tds, converted_query, converted_query_len);
+	tds7_put_params_definition(tds, param_definition, definition_len);
+	tds_convert_string_free(query, converted_query);
+	free(param_definition);
 
-			param = params->columns[i];
-			/* TODO check error */
-			tds_put_data_info(tds, param, 0);
-			ret = tds_put_data(tds, param);
-			if (TDS_FAILED(ret))
-				return ret;
-		}
+	for (i = 0; i < params->num_cols; i++) {
+		TDSRET ret;
 
-		tds->current_op = TDS_OP_EXECUTESQL;
-		return tds_query_flush_packet(tds);
+		param = params->columns[i];
+		/* TODO check error */
+		tds_put_data_info(tds, param, 0);
+		ret = tds_put_data(tds, param);
+		if (TDS_FAILED(ret))
+			return ret;
 	}
 
+	tds->current_op = TDS_OP_EXECUTESQL;
+	return tds_query_flush_packet(tds);
+}
+
+/**
+ * Submit a prepared query with parameters
+ * \param tds     state information for the socket and the TDS protocol
+ * \param query   language query with given placeholders (?)
+ * \param params  parameters to send
+ * \return TDS_FAIL or TDS_SUCCESS
+ */
+TDSRET
+tds_submit_execdirect(TDSSOCKET * tds, const char *query, TDSPARAMINFO * params)
+{
+	size_t query_len;
+	TDSDYNAMIC *dyn;
+	size_t id_len;
+
+	if (IS_TDS7_PLUS(tds->conn))
+		return tds7_submit_execdirect(tds, query, params, NULL);
+
+	CHECK_TDS_EXTRA(tds);
+	CHECK_PARAMINFO_EXTRA(params);
+
+	if (!query)
+		return TDS_FAIL;
+	query_len = strlen(query);
+
 	/* allocate a structure for this thing */
 	dyn = tds_alloc_dynamic(tds->conn, NULL);
 
@@ -1451,7 +1525,7 @@ tds71_submit_prepexec(TDSSOCKET * tds, const char *query, const char *id, TDSDYN
 		goto failure;
 	}
 
-	tds_start_query(tds, TDS_RPC);
+	tds_start_query(tds, TDS_RPC, NULL);
 	/* procedure name */
 	if (IS_TDS71_PLUS(tds->conn)) {
 		tds_put_smallint(tds, -1);
@@ -1707,7 +1781,7 @@ tds_submit_execute(TDSSOCKET * tds, TDSDYNAMIC * dyn)
 		}
 
 		/* RPC on sp_execute */
-		tds_start_query(tds, TDS_RPC);
+		tds_start_query(tds, TDS_RPC, NULL);
 
 		tds7_send_execute(tds, dyn);
 
@@ -1831,7 +1905,7 @@ tds_submit_unprepare(TDSSOCKET * tds, TDSDYNAMIC * dyn)
 
 	if (IS_TDS7_PLUS(tds->conn)) {
 		/* RPC on sp_execute */
-		tds_start_query(tds, TDS_RPC);
+		tds_start_query(tds, TDS_RPC, NULL);
 
 		/* procedure name */
 		if (IS_TDS71_PLUS(tds->conn)) {
@@ -1857,7 +1931,7 @@ tds_submit_unprepare(TDSSOCKET * tds, TDSDYNAMIC * dyn)
 	}
 
 	if (dyn->emulated) {
-		tds_start_query(tds, TDS_QUERY);
+		tds_start_query(tds, TDS_QUERY, NULL);
 
 		/* just a dummy select to return some data */
 		tds_put_string(tds, "select 1 where 0=1", -1);
@@ -1946,11 +2020,13 @@ 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)
+tds7_submit_rpc(TDSSOCKET * tds, const char *rpc_name, TDSPARAMINFO * params, TDSHEADERS * head)
 {
 	TDSCOLUMN *param;
 	int rpc_name_len, i;
 	int num_params = params ? params->num_cols : 0;
+	const char *converted_name;
+	size_t converted_name_len;
 
 	CHECK_TDS_EXTRA(tds);
 	if (params)
@@ -1959,6 +2035,9 @@ tds_submit_rpc(TDSSOCKET * tds, const char *rpc_name, TDSPARAMINFO * params)
 	assert(tds);
 	assert(rpc_name);
 
+	if (!IS_TDS7_PLUS(tds->conn))
+		return TDS_FAIL;
+
 	if (tds_set_state(tds, TDS_QUERYING) != TDS_QUERYING)
 		return TDS_FAIL;
 
@@ -1966,41 +2045,72 @@ tds_submit_rpc(TDSSOCKET * tds, const char *rpc_name, TDSPARAMINFO * params)
 	tds_release_cur_dyn(tds);
 
 	rpc_name_len = (int)strlen(rpc_name);
-	if (IS_TDS7_PLUS(tds->conn)) {
-		const char *converted_name;
-		size_t converted_name_len;
 
-		/* procedure name */
-		converted_name = tds_convert_string(tds, tds->conn->char_convs[client2ucs2], rpc_name, rpc_name_len, &converted_name_len);
-		if (!converted_name) {
-			tds_set_state(tds, TDS_IDLE);
-			return TDS_FAIL;
-		}
-		tds_start_query(tds, TDS_RPC);
-
-		TDS_PUT_SMALLINT(tds, converted_name_len / 2);
-		tds_put_n(tds, converted_name, (int)converted_name_len);
+	/* procedure name */
+	converted_name = tds_convert_string(tds, tds->conn->char_convs[client2ucs2], rpc_name, rpc_name_len, &converted_name_len);
+	if (!converted_name) {
+		tds_set_state(tds, TDS_IDLE);
+		return TDS_FAIL;
+	}
+	if (tds_start_query(tds, TDS_RPC, head) != TDS_SUCCESS) {
 		tds_convert_string_free(rpc_name, converted_name);
+		tds_set_state(tds, TDS_IDLE);
+		return TDS_FAIL;
+	}
 
-		/*
-		 * TODO support flags
-		 * bit 0 (1 as flag) in TDS7/TDS5 is "recompile"
-		 * bit 1 (2 as flag) in TDS7+ is "no metadata" bit 
-		 * (I don't know meaning of "no metadata")
-		 */
-		tds_put_smallint(tds, 0);
+	TDS_PUT_SMALLINT(tds, converted_name_len / 2);
+	tds_put_n(tds, converted_name, (int)converted_name_len);
+	tds_convert_string_free(rpc_name, converted_name);
 
-		for (i = 0; i < num_params; i++) {
-			param = params->columns[i];
-			/* TODO check error */
-			tds_put_data_info(tds, param, TDS_PUT_DATA_USE_NAME);
-			/* FIXME handle error */
-			tds_put_data(tds, param);
-		}
+	/*
+		* TODO support flags
+		* bit 0 (1 as flag) in TDS7/TDS5 is "recompile"
+		* bit 1 (2 as flag) in TDS7+ is "no metadata" bit 
+		* (I don't know meaning of "no metadata")
+		*/
+	tds_put_smallint(tds, 0);
 
-		return tds_query_flush_packet(tds);
+	for (i = 0; i < num_params; i++) {
+		param = params->columns[i];
+		/* TODO check error */
+		tds_put_data_info(tds, param, TDS_PUT_DATA_USE_NAME);
+		/* FIXME handle error */
+		tds_put_data(tds, param);
 	}
 
+	return tds_query_flush_packet(tds);
+}
+
+/**
+ * tds_submit_rpc() call a RPC from server. Output parameters will be stored in tds->param_info
+ * \param tds      state information for the socket and the TDS protocol
+ * \param rpc_name name of RPC
+ * \param params   parameters informations. NULL for no parameters
+ */
+TDSRET
+tds_submit_rpc(TDSSOCKET * tds, const char *rpc_name, TDSPARAMINFO * params)
+{
+	int rpc_name_len;
+	int num_params = params ? params->num_cols : 0;
+
+	if (IS_TDS7_PLUS(tds->conn))
+		return tds7_submit_rpc(tds, rpc_name, params, NULL);
+
+	CHECK_TDS_EXTRA(tds);
+	if (params)
+		CHECK_PARAMINFO_EXTRA(params);
+
+	assert(tds);
+	assert(rpc_name);
+
+	if (tds_set_state(tds, TDS_QUERYING) != TDS_QUERYING)
+		return TDS_FAIL;
+
+	/* distinguish from dynamic query  */
+	tds_release_cur_dyn(tds);
+
+	rpc_name_len = (int)strlen(rpc_name);
+
 	if (IS_TDS50(tds->conn)) {
 		tds->out_flag = TDS_NORMAL;
 
@@ -2359,7 +2469,7 @@ tds_cursor_open(TDSSOCKET * tds, TDSCURSOR * cursor, TDSPARAMINFO *params, int *
 		}
 
 		/* RPC call to sp_cursoropen */
-		tds_start_query(tds, TDS_RPC);
+		tds_start_query(tds, TDS_RPC, NULL);
 
 		/* procedure identifier by number */
 
@@ -2603,7 +2713,7 @@ tds_cursor_fetch(TDSSOCKET * tds, TDSCURSOR * cursor, TDS_CURSOR_FETCH fetch_typ
 			0x20  /* TDS_CURSOR_FETCH_RELATIVE */
 		};
 
-		tds_start_query(tds, TDS_RPC);
+		tds_start_query(tds, TDS_RPC, NULL);
 
 		/* TODO enum for 2 ... */
 		if (cursor->type == 2 && fetch_type == TDS_CURSOR_FETCH_ABSOLUTE) {
@@ -2653,7 +2763,7 @@ tds_cursor_get_cursor_info(TDSSOCKET *tds, TDSCURSOR *cursor, TDS_UINT *prow_num
 		tds_set_cur_cursor(tds, cursor);
 
 		/* General initialization of server command */
-		tds_start_query(tds, TDS_RPC);
+		tds_start_query(tds, TDS_RPC, NULL);
 
 		/* Create and send query to server */
 		if (IS_TDS71_PLUS(tds->conn)) {
@@ -2775,7 +2885,7 @@ tds_cursor_close(TDSSOCKET * tds, TDSCURSOR * cursor)
 	if (IS_TDS7_PLUS(tds->conn)) {
 
 		/* RPC call to sp_cursorclose */
-		tds_start_query(tds, TDS_RPC);
+		tds_start_query(tds, TDS_RPC, NULL);
 
 		if (IS_TDS71_PLUS(tds->conn)) {
 			tds_put_smallint(tds, -1);
@@ -2824,7 +2934,7 @@ tds_cursor_setname(TDSSOCKET * tds, TDSCURSOR * cursor)
 	tds_set_cur_cursor(tds, cursor);
 
 	/* RPC call to sp_cursoroption */
-	tds_start_query(tds, TDS_RPC);
+	tds_start_query(tds, TDS_RPC, NULL);
 
 	if (IS_TDS71_PLUS(tds->conn)) {
 		tds_put_smallint(tds, -1);
@@ -2898,7 +3008,7 @@ tds_cursor_update(TDSSOCKET * tds, TDSCURSOR * cursor, TDS_CURSOR_OPERATION op,
 	if (IS_TDS7_PLUS(tds->conn)) {
 
 		/* RPC call to sp_cursorclose */
-		tds_start_query(tds, TDS_RPC);
+		tds_start_query(tds, TDS_RPC, NULL);
 
 		if (IS_TDS71_PLUS(tds->conn)) {
 			tds_put_smallint(tds, -1);
@@ -3198,7 +3308,7 @@ tds_send_emulated_execute(TDSSOCKET * tds, const char *query, TDSPARAMINFO * par
 	 * NOTE: even for TDS5 we use this packet so to avoid computing 
 	 * entire sql command
 	 */
-	tds_start_query(tds, TDS_QUERY);
+	tds_start_query(tds, TDS_QUERY, NULL);
 	if (!num_placeholders) {
 		tds_put_string(tds, query, -1);
 		return TDS_SUCCESS;
@@ -3241,7 +3351,37 @@ tds_multiple_init(TDSSOCKET *tds, TDSMULTIPLE *multiple, TDS_MULTIPLE_TYPE type)
 			packet_type = TDS_RPC;
 		break;
 	}
-	tds_start_query(tds, packet_type);
+	tds_start_query(tds, packet_type, NULL);
+
+	return TDS_SUCCESS;
+}
+
+TDSRET
+tds7_multiple_init(TDSSOCKET *tds, TDSMULTIPLE *multiple, TDS_MULTIPLE_TYPE type, TDSHEADERS * head)
+{
+	unsigned char packet_type;
+	multiple->type = type;
+	multiple->flags = 0;
+
+	if (!IS_TDS7_PLUS(tds->conn))
+		return TDS_FAIL;
+
+	if (tds_set_state(tds, TDS_QUERYING) != TDS_QUERYING)
+		return TDS_FAIL;
+
+	packet_type = TDS_QUERY;
+	switch (type) {
+	case TDS_MULTIPLE_QUERY:
+		break;
+	case TDS_MULTIPLE_EXECUTE:
+	case TDS_MULTIPLE_RPC:
+		packet_type = TDS_RPC;
+		break;
+	}
+	if (tds_start_query(tds, packet_type, head) != TDS_SUCCESS) {
+		tds_set_state(tds, TDS_IDLE);
+		return TDS_FAIL;
+	}
 
 	return TDS_SUCCESS;
 }
@@ -3589,7 +3729,7 @@ tds_submit_begin_tran(TDSSOCKET *tds)
 	if (tds_set_state(tds, TDS_QUERYING) != TDS_QUERYING)
 		return TDS_FAIL;
 
-	tds_start_query(tds, TDS7_TRANS);
+	tds_start_query(tds, TDS7_TRANS, NULL);
 
 	/* begin transaction */
 	tds_put_smallint(tds, 5);
@@ -3617,7 +3757,7 @@ tds_submit_rollback(TDSSOCKET *tds, int cont)
 	if (tds_set_state(tds, TDS_QUERYING) != TDS_QUERYING)
 		return TDS_FAIL;
 
-	tds_start_query(tds, TDS7_TRANS);
+	tds_start_query(tds, TDS7_TRANS, NULL);
 	tds_put_smallint(tds, 8);	/* rollback */
 	tds_put_byte(tds, 0);	/* name */
 	if (cont) {
@@ -3648,7 +3788,7 @@ tds_submit_commit(TDSSOCKET *tds, int cont)
 	if (tds_set_state(tds, TDS_QUERYING) != TDS_QUERYING)
 		return TDS_FAIL;
 
-	tds_start_query(tds, TDS7_TRANS);
+	tds_start_query(tds, TDS7_TRANS, NULL);
 	tds_put_smallint(tds, 7);	/* commit */
 	tds_put_byte(tds, 0);	/* name */
 	if (cont) {
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.