/* FreeTDS - Library of routines accessing Sybase and Microsoft databases
 * Copyright (C) 1998, 1999, 2000, 2001  Brian Bruns
 * Copyright (C) 2002-2012  Frediano Ziglio
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <config.h>

#include <stdarg.h>
#include <stdio.h>

#if HAVE_STDLIB_H
#include <stdlib.h>
#endif /* HAVE_STDLIB_H */

#if HAVE_STRING_H
#include <string.h>
#endif /* HAVE_STRING_H */

#include <assert.h>
#include <ctype.h>

#include <freetds/utils.h>
#include <freetds/odbc.h>
#include <freetds/iconv.h>
#include <freetds/utils/string.h>
#include <freetds/convert.h>
#include <freetds/encodings.h>
#include <freetds/replacements.h>
#include "sqlwparams.h"

/* Include odbcss.h with all bcp functions */
/* The define trick is to make inline functions calls internal
 * odbc_SQLSetConnectAttr instead of SQLSetConnectAttr */
ODBC_FUNC(SQLSetConnectAttr, (P(SQLHDBC,hdbc), P(SQLINTEGER,Attribute), P(SQLPOINTER,ValuePtr), P(SQLINTEGER,StringLength) WIDE));
#define TDSODBC_BCP
#undef SQLSetConnectAttr
#define SQLSetConnectAttr(h, n, p, t) odbc_SQLSetConnectAttr(h, n, p, t _wide0)
#include <odbcss.h>
#undef SQLSetConnectAttr

static SQLRETURN odbc_SQLAllocConnect(SQLHENV henv, SQLHDBC FAR * phdbc);
static SQLRETURN odbc_SQLAllocEnv(SQLHENV FAR * phenv, SQLINTEGER odbc_version);
static SQLRETURN odbc_SQLAllocStmt(SQLHDBC hdbc, SQLHSTMT FAR * phstmt);
static SQLRETURN odbc_SQLAllocDesc(SQLHDBC hdbc, SQLHDESC FAR * phdesc);
static SQLRETURN odbc_SQLFreeConnect(SQLHDBC hdbc);
static SQLRETURN odbc_SQLFreeEnv(SQLHENV henv);
static SQLRETURN odbc_SQLFreeStmt(SQLHSTMT hstmt, SQLUSMALLINT fOption, int force);
static SQLRETURN odbc_SQLFreeDesc(SQLHDESC hdesc);
static SQLRETURN odbc_SQLExecute(TDS_STMT * stmt);
static SQLRETURN odbc_SQLSetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength WIDE);
static SQLRETURN odbc_SQLGetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER Value, SQLINTEGER BufferLength,
				     SQLINTEGER * StringLength WIDE);
static SQLRETURN odbc_SQLColAttribute(SQLHSTMT hstmt, SQLUSMALLINT icol, SQLUSMALLINT fDescType, SQLPOINTER rgbDesc,
					  SQLSMALLINT cbDescMax, SQLSMALLINT FAR * pcbDesc, SQLLEN FAR * pfDesc _WIDE);
static SQLRETURN odbc_SQLFetch(TDS_STMT * stmt, SQLSMALLINT FetchOrientation, SQLLEN FetchOffset);
static SQLRETURN odbc_populate_ird(TDS_STMT * stmt);
static int odbc_errmsg_handler(const TDSCONTEXT * ctx, TDSSOCKET * tds, TDSMESSAGE * msg);
static void odbc_log_unimplemented_type(const char function_name[], int fType);
static void odbc_upper_column_names(TDS_STMT * stmt);
static void odbc_col_setname(TDS_STMT * stmt, int colpos, const char *name);
static SQLRETURN odbc_stat_execute(TDS_STMT * stmt _WIDE, const char *begin, int nparams, ...);
static SQLRETURN odbc_free_dynamic(TDS_STMT * stmt);
static SQLRETURN odbc_free_cursor(TDS_STMT * stmt);
static SQLRETURN odbc_update_ird(TDS_STMT *stmt, TDS_ERRS *errs);
static SQLRETURN odbc_prepare(TDS_STMT *stmt);
static SQLSMALLINT odbc_swap_datetime_sql_type(SQLSMALLINT sql_type, int version);
static int odbc_process_tokens(TDS_STMT * stmt, unsigned flag);
static bool odbc_lock_statement(TDS_STMT* stmt);
static void odbc_unlock_statement(TDS_STMT* stmt);
static bool read_params(TDS_STMT *stmt);

#if ENABLE_EXTRA_CHECKS
static void odbc_ird_check(TDS_STMT * stmt);

#define IRD_CHECK odbc_ird_check(stmt)
#else
#define IRD_CHECK
#endif

/**
 * \defgroup odbc_api ODBC API
 * Functions callable by \c ODBC client programs
 */


/* utils to check handles */
#define INIT_HANDLE(t, n) \
	TDS_##t *n = (TDS_##t*)h##n; \
	if (SQL_NULL_H##t  == h##n || (n)->htype != SQL_HANDLE_##t) return SQL_INVALID_HANDLE; \
	tds_mutex_lock(&n->mtx); \
	CHECK_##t##_EXTRA(n); \
	odbc_errs_reset(&n->errs);

#define ODBC_ENTER_HSTMT INIT_HANDLE(STMT, stmt)
#define ODBC_ENTER_HDBC  INIT_HANDLE(DBC,  dbc)
#define ODBC_ENTER_HENV  INIT_HANDLE(ENV,  env)
#define ODBC_ENTER_HDESC INIT_HANDLE(DESC, desc)

#define IS_VALID_LEN(len) ((len) >= 0 || (len) == SQL_NTS || (len) == SQL_NULL_DATA)

#define ODBC_SAFE_ERROR(stmt) \
	do { \
		if (!stmt->errs.num_errors) \
			odbc_errs_add(&stmt->errs, "HY000", "Unknown error"); \
	} while(0)

#define DEFAULT_QUERY_TIMEOUT (~((SQLUINTEGER) 0))

/*
 * Note: I *HATE* hungarian notation, it has to be the most idiotic thing
 * I've ever seen. So, you will note it is avoided other than in the function
 * declarations. "Gee, let's make our code totally hard to read and they'll
 * beg for GUI tools"
 * Bah!
 */

static const char *
odbc_prret(SQLRETURN ret, char *unknown, size_t unknown_size)
{
	switch (ret) {
	case SQL_ERROR:			return "SQL_ERROR";
	case SQL_INVALID_HANDLE:	return "SQL_INVALID_HANDLE";
	case SQL_SUCCESS:		return "SQL_SUCCESS";
	case SQL_SUCCESS_WITH_INFO:	return "SQL_SUCCESS_WITH_INFO";
#if ODBCVER >= 0x0300
	case SQL_NO_DATA:		return "SQL_NO_DATA";
#endif
	case SQL_STILL_EXECUTING:	return "SQL_STILL_EXECUTING";
	case SQL_NEED_DATA:		return "SQL_NEED_DATA";
	}

	snprintf(unknown, unknown_size, "unknown: %d", (int)ret);

	return unknown;
}
#define ODBC_PRRET_BUF	char unknown_prret_buf[24]
#define odbc_prret(ret) odbc_prret(ret, unknown_prret_buf, sizeof(unknown_prret_buf))

static void
odbc_col_setname(TDS_STMT * stmt, int colpos, const char *name)
{
#if ENABLE_EXTRA_CHECKS
	TDSRESULTINFO *resinfo;
#endif

	IRD_CHECK;

#if ENABLE_EXTRA_CHECKS
	if (colpos > 0 && stmt->tds != NULL && (resinfo = stmt->tds->current_results) != NULL) {
		if (colpos <= resinfo->num_cols) {
			/* no overflow possible, name is always shorter */
			if (!tds_dstr_copy(&resinfo->columns[colpos - 1]->column_name, name))
				odbc_errs_add(&stmt->errs, "HY001", NULL);
			tds_dstr_empty(&resinfo->columns[colpos - 1]->table_column_name);
		}
	}
#endif

	if (colpos > 0 && colpos <= stmt->ird->header.sql_desc_count) {
		--colpos;
		if (!tds_dstr_copy(&stmt->ird->records[colpos].sql_desc_label, name)
		    || !tds_dstr_copy(&stmt->ird->records[colpos].sql_desc_name, name))
			odbc_errs_add(&stmt->errs, "HY001", NULL);
	}
}

/* spinellia@acm.org : copied shamelessly from change_database */
static SQLRETURN
change_autocommit(TDS_DBC * dbc, int state)
{
	TDSSOCKET *tds = dbc->tds_socket;
	TDSRET ret;

	tds_mutex_check_owned(&dbc->mtx);
	if (dbc->attr.autocommit == state)
		return SQL_SUCCESS;

	/*
	 * We may not be connected yet and dbc->tds_socket
	 * may not initialized.
	 */
	if (tds) {
		/* TODO better idle check, not thread safe */
		if (tds->state == TDS_IDLE)
			tds->query_timeout = dbc->default_query_timeout;

		if (state == SQL_AUTOCOMMIT_ON)
			ret = tds_submit_rollback(tds, 0);
		else
			ret = tds_submit_begin_tran(tds);

		if (TDS_FAILED(ret)) {
			odbc_errs_add(&dbc->errs, "HY000", "Could not change transaction status");
			return SQL_ERROR;
		}
		if (TDS_FAILED(tds_process_simple_query(tds))) {
			odbc_errs_add(&dbc->errs, "HY000", "Could not change transaction status");
			return SQL_ERROR;
		}
		dbc->attr.autocommit = state;
	} else {
		/* if not connected we will change auto-commit after login */
		dbc->attr.autocommit = state;
	}
	ODBC_RETURN_(dbc);
}

static SQLRETURN
change_database(TDS_DBC * dbc, const char *database, int database_len)
{
	TDSSOCKET *tds = dbc->tds_socket;
	tds_mutex_check_owned(&dbc->mtx);

	/*
	 * We may not be connected yet and dbc->tds_socket
	 * may not initialized.
	 */
	if (tds) {
		/* build query */
		char *query = tds_new(char, 6 + tds_quote_id(tds, NULL, database, database_len));

		if (!query) {
			odbc_errs_add(&dbc->errs, "HY001", NULL);
			return SQL_ERROR;
		}
		strcpy(query, "USE ");
		tds_quote_id(tds, query + 4, database, database_len);


		tdsdump_log(TDS_DBG_INFO1, "change_database: executing %s\n", query);

		/* TODO better idle check, not thread safe */
		if (tds->state == TDS_IDLE)
			tds->query_timeout = dbc->default_query_timeout;
		if (TDS_FAILED(tds_submit_query(tds, query))) {
			free(query);
			odbc_errs_add(&dbc->errs, "HY000", "Could not change database");
			return SQL_ERROR;
		}
		free(query);
		if (TDS_FAILED(tds_process_simple_query(tds))) {
			odbc_errs_add(&dbc->errs, "HY000", "Could not change database");
			return SQL_ERROR;
		}
	} else {
		if (!tds_dstr_copyn(&dbc->attr.current_catalog, database, database_len)) {
			odbc_errs_add(&dbc->errs, "HY001", NULL);
			return SQL_ERROR;
		}
	}
	ODBC_RETURN_(dbc);
}

static SQLRETURN
change_txn(TDS_DBC * dbc, SQLUINTEGER txn_isolation)
{
	char query[64];
	const char *level;
	TDSSOCKET *tds = dbc->tds_socket;
	tds_mutex_check_owned(&dbc->mtx);

	switch (txn_isolation) {
	case SQL_TXN_READ_COMMITTED:
		level = "READ COMMITTED";
		break;
	case SQL_TXN_READ_UNCOMMITTED:
		level = "READ UNCOMMITTED";
		break;
	case SQL_TXN_REPEATABLE_READ:
		level = "REPEATABLE READ";
		break;
	case SQL_TXN_SERIALIZABLE:
		level = "SERIALIZABLE";
		break;
	default:
		odbc_errs_add(&dbc->errs, "HY024", NULL);
		return SQL_ERROR;
	}

	/* if not connected return success, will be set after connection */
	if (!tds)
		return SQL_SUCCESS;

	if (tds->state != TDS_IDLE) {
		odbc_errs_add(&dbc->errs, "HY011", NULL);
		return SQL_ERROR;
	}

	tds->query_timeout = dbc->default_query_timeout;
	sprintf(query, "SET TRANSACTION ISOLATION LEVEL %s", level);
	if (TDS_FAILED(tds_submit_query(tds, query))) {
		ODBC_SAFE_ERROR(dbc);
		return SQL_ERROR;
	}
	if (TDS_FAILED(tds_process_simple_query(tds))) {
		ODBC_SAFE_ERROR(dbc);
		return SQL_ERROR;
	}

	return SQL_SUCCESS;
}

static TDS_DBC*
odbc_get_dbc(TDSSOCKET *tds)
{
	TDS_CHK *chk = (TDS_CHK *) tds_get_parent(tds);
	if (!chk)
		return NULL;
	if (chk->htype == SQL_HANDLE_DBC)
		return (TDS_DBC *) chk;
	assert(chk->htype == SQL_HANDLE_STMT);
	return ((TDS_STMT *) chk)->dbc;
}

static TDS_STMT*
odbc_get_stmt(TDSSOCKET *tds)
{
	TDS_CHK *chk = (TDS_CHK *) tds_get_parent(tds);
	if (!chk || chk->htype != SQL_HANDLE_STMT)
		return NULL;
	return (TDS_STMT *) chk;
}


static void
odbc_env_change(TDSSOCKET * tds, int type, char *oldval TDS_UNUSED, char *newval)
{
	TDS_DBC *dbc;

	assert(tds);

	dbc = odbc_get_dbc(tds);
	if (!dbc)
		return;

	switch (type) {
	case TDS_ENV_DATABASE:
		tds_dstr_copy(&dbc->attr.current_catalog, newval);
		break;
	case TDS_ENV_PACKSIZE:
		dbc->attr.packet_size = atoi(newval);
		break;
	}
}

static SQLRETURN
odbc_connect(TDS_DBC * dbc, TDSLOGIN * login)
{
	TDS_ENV *env = dbc->env;
	tds_mutex_check_owned(&dbc->mtx);

#ifdef ENABLE_ODBC_WIDE
	dbc->mb_conv = NULL;
#endif
	dbc->tds_socket = tds_alloc_socket(env->tds_ctx, 512);
	if (!dbc->tds_socket)
		goto memory_error;

	dbc->tds_socket->conn->use_iconv = 0;
	tds_set_parent(dbc->tds_socket, (void *) dbc);

	/* Set up our environment change hook */
	dbc->tds_socket->env_chg_func = odbc_env_change;

	tds_fix_login(login);

	/* use connection timeout if set */
	if (dbc->attr.connection_timeout)
		login->connect_timeout = dbc->attr.connection_timeout;

	/* but override with login timeout, if set */
	if (dbc->attr.login_timeout)
		login->connect_timeout = dbc->attr.login_timeout;

	if (dbc->attr.mars_enabled != SQL_MARS_ENABLED_NO)
		login->mars = 1;
	if (dbc->attr.bulk_enabled != SQL_BCP_OFF)
		tds_set_bulk(login, true);

#ifdef ENABLE_ODBC_WIDE
	/* force utf-8 in order to support wide characters */
	dbc->original_charset_num = tds_canonical_charset(tds_dstr_cstr(&login->client_charset));
	if (dbc->original_charset_num < 0) {
		char *msg;

		tds_free_socket(dbc->tds_socket);
		dbc->tds_socket = NULL;

		if (asprintf(&msg, "Invalid \"%s\" character set specified", tds_dstr_cstr(&login->client_charset)) > 0) {
			odbc_errs_add(&dbc->errs, "HY024", msg);
			free(msg);
		} else {
			odbc_errs_add(&dbc->errs, "HY024", "Invalid character set specified");
		}
		ODBC_RETURN_(dbc);
	}
	if (!tds_dstr_copy(&login->client_charset, "UTF-8"))
		goto memory_error;
#endif

	/* replace password with old one */
	if (dbc->use_oldpwd) {
		if (!tds_dstr_dup(&login->new_password, &login->password)
		    || !tds_dstr_dup(&login->password, &dbc->oldpwd))
			goto memory_error;
		login->use_new_password = 1;
	}

	if (TDS_FAILED(tds_connect_and_login(dbc->tds_socket, login))) {
		tds_free_socket(dbc->tds_socket);
		dbc->tds_socket = NULL;
		odbc_errs_add(&dbc->errs, "08001", NULL);
		return SQL_ERROR;
	}
#ifdef ENABLE_ODBC_WIDE
	dbc->mb_conv = tds_iconv_get_info(dbc->tds_socket->conn, dbc->original_charset_num, TDS_CHARSET_UTF_8);
#endif

	dbc->default_query_timeout = dbc->tds_socket->query_timeout;

	if (IS_TDS7_PLUS(dbc->tds_socket->conn))
		dbc->cursor_support = 1;

#if ENABLE_ODBC_MARS
	/* check if mars is enabled */
	if (!IS_TDS72_PLUS(dbc->tds_socket->conn) || !dbc->tds_socket->conn->mars)
		dbc->attr.mars_enabled = SQL_MARS_ENABLED_NO;
#else
	dbc->attr.mars_enabled = SQL_MARS_ENABLED_NO;
#endif

	if (dbc->attr.txn_isolation != SQL_TXN_READ_COMMITTED) {
		if (!SQL_SUCCEEDED(change_txn(dbc, dbc->attr.txn_isolation)))
			ODBC_RETURN_(dbc);
	}

	if (dbc->attr.autocommit != SQL_AUTOCOMMIT_ON) {
		dbc->attr.autocommit = SQL_AUTOCOMMIT_ON;
		if (!SQL_SUCCEEDED(change_autocommit(dbc, SQL_AUTOCOMMIT_OFF)))
			ODBC_RETURN_(dbc);
	}

	/* this overwrite any error arrived (wanted behavior, Sybase return error for conversion errors) */
	ODBC_RETURN(dbc, SQL_SUCCESS);

memory_error:
	tds_free_socket(dbc->tds_socket);
	dbc->tds_socket = NULL;
	odbc_errs_add(&dbc->errs, "HY001", NULL);
	ODBC_RETURN_(dbc);
}

/**
 * Update IRD information.
 * This is needed if the IRD is not updated as requires query to be prepared.
 * For instance calling SQLNumResultCols after SQLPrepare we need to
 * know how many rows the query could return.
 */
static SQLRETURN
odbc_update_ird(TDS_STMT *stmt, TDS_ERRS *errs TDS_UNUSED)
{
	SQLRETURN res;

	if (!stmt->need_reprepare || stmt->prepared_query_is_rpc
	    || !stmt->dbc || !IS_TDS7_PLUS(stmt->dbc->tds_socket->conn)) {
		stmt->need_reprepare = 0;
		return SQL_SUCCESS;
	}

	/* FIXME where error are put ?? on stmt... */
	if (!odbc_lock_statement(stmt))
		ODBC_RETURN_(stmt);

	/* FIXME error */
	res = start_parse_prepared_query(stmt, false);
	if (res != SQL_SUCCESS) {
		/* prepare with dummy parameters just to fill IRD */
		tds_free_param_results(stmt->params);
		stmt->params = NULL;
		stmt->param_num = 0;
		/*
		 * TODO
		 * we need to prepare again with parameters but need_reprepare
		 * flag is reset by odbc_prepare... perhaps should be checked
		 * later, not calling describeCol or similar
		 * we need prepare to get dynamic and cause we change parameters
		 */
	}

	return odbc_prepare(stmt);
}

static SQLRETURN
odbc_prepare(TDS_STMT *stmt)
{
	TDSSOCKET *tds = stmt->tds;
	bool in_row = false;

	if (TDS_FAILED(tds_submit_prepare(tds, tds_dstr_cstr(&stmt->query), NULL, &stmt->dyn, stmt->params))) {
		ODBC_SAFE_ERROR(stmt);
		return SQL_ERROR;
	}

	/* try to go to the next recordset */
	/* TODO merge with similar code */
	desc_free_records(stmt->ird);
	stmt->row_status = PRE_NORMAL_ROW;
	for (;;) {
		TDS_INT result_type;
		int done_flags;

		switch (tds_process_tokens(tds, &result_type, &done_flags, TDS_RETURN_ROWFMT|TDS_RETURN_DONE)) {
		case TDS_SUCCESS:
			switch (result_type) {
			case TDS_DONE_RESULT:
			case TDS_DONEPROC_RESULT:
			case TDS_DONEINPROC_RESULT:
				stmt->row_count = tds->rows_affected;
				if (done_flags & TDS_DONE_ERROR && !stmt->dyn->emulated)
					stmt->errs.lastrc = SQL_ERROR;
				/* FIXME this row is used only as a flag for update binding,
				 * should be cleared if binding/result changed */
				stmt->row = 0;
				break;

			case TDS_ROWFMT_RESULT:
				/* store first row information */
				if (!in_row)
					odbc_populate_ird(stmt);
				stmt->row = 0;
				stmt->row_count = TDS_NO_COUNT;
				stmt->row_status = PRE_NORMAL_ROW;
				in_row = true;
				break;
			}
			continue;
		case TDS_NO_MORE_RESULTS:
			break;
		case TDS_CANCELLED:
			odbc_errs_add(&stmt->errs, "HY008", NULL);
		default:
			stmt->errs.lastrc = SQL_ERROR;
			break;
		}
		break;
	}

	if (stmt->errs.lastrc == SQL_ERROR && !stmt->dyn->emulated) {
		tds_release_dynamic(&stmt->dyn);
	}
	odbc_unlock_statement(stmt);
	stmt->need_reprepare = 0;
	ODBC_RETURN_(stmt);
}

ODBC_FUNC(SQLDriverConnect, (P(SQLHDBC,hdbc), P(SQLHWND,hwnd), PCHARIN(ConnStrIn,SQLSMALLINT),
	PCHAROUT(ConnStrOut,SQLSMALLINT), P(SQLUSMALLINT,fDriverCompletion) WIDE))
{
	TDSLOGIN *login;
	TDS_PARSED_PARAM params[ODBC_PARAM_SIZE];
	DSTR conn_str = DSTR_INITIALIZER;

	ODBC_ENTER_HDBC;

#ifdef TDS_NO_DM
	/* Check string length */
	if (!IS_VALID_LEN(cbConnStrIn) || cbConnStrIn == 0) {
		odbc_errs_add(&dbc->errs, "HY090", NULL);
		ODBC_EXIT_(dbc);
	}

	/* Check completion param */
	switch (fDriverCompletion) {
	case SQL_DRIVER_NOPROMPT:
	case SQL_DRIVER_COMPLETE:
	case SQL_DRIVER_PROMPT:
	case SQL_DRIVER_COMPLETE_REQUIRED:
		break;
	default:
		odbc_errs_add(&dbc->errs, "HY110", NULL);
		ODBC_EXIT_(dbc);
	}
#endif

	if (!odbc_dstr_copy(dbc, &conn_str, cbConnStrIn, szConnStrIn)) {
		odbc_errs_add(&dbc->errs, "HY001", NULL);
		ODBC_EXIT_(dbc);
	}

	login = tds_alloc_login(false);
	if (!login || !tds_init_login(login, dbc->env->tds_ctx->locale)) {
		tds_free_login(login);
		tds_dstr_free(&conn_str);
		odbc_errs_add(&dbc->errs, "HY001", NULL);
		ODBC_EXIT_(dbc);
	}

	if (!tds_dstr_isempty(&dbc->attr.current_catalog))
		if (!tds_dstr_dup(&login->database, &dbc->attr.current_catalog)) {
			tds_free_login(login);
			tds_dstr_free(&conn_str);
			odbc_errs_add(&dbc->errs, "HY001", NULL);
			ODBC_EXIT_(dbc);
		}

	/* parse the DSN string */
	if (!odbc_parse_connect_string(&dbc->errs, tds_dstr_buf(&conn_str), tds_dstr_buf(&conn_str) + tds_dstr_len(&conn_str),
				       login, params)) {
		tds_dstr_free(&conn_str);
		ODBC_EXIT_(dbc);
	}

	odbc_set_dstr(dbc, szConnStrOut, cbConnStrOutMax, pcbConnStrOut, &conn_str);
	tds_dstr_free(&conn_str);

	/* add login info */
	if (hwnd && fDriverCompletion != SQL_DRIVER_NOPROMPT
	    && (fDriverCompletion == SQL_DRIVER_PROMPT || (!params[ODBC_PARAM_UID].p && !params[ODBC_PARAM_Trusted_Connection].p)
		|| tds_dstr_isempty(&login->server_name))) {
#ifdef _WIN32
		char *out = NULL;

		/* prompt for login information */
		if (!get_login_info(hwnd, login)) {
			tds_free_login(login);
			odbc_errs_add(&dbc->errs, "08001", "User canceled login");
			ODBC_EXIT_(dbc);
		}
		if (tds_dstr_isempty(&login->user_name)) {
			params[ODBC_PARAM_UID].p = NULL;
			params[ODBC_PARAM_PWD].p = NULL;
			params[ODBC_PARAM_Trusted_Connection].p = "Yes";
			params[ODBC_PARAM_Trusted_Connection].len = 3;
		} else {
			params[ODBC_PARAM_UID].p   = tds_dstr_cstr(&login->user_name);
			params[ODBC_PARAM_UID].len = tds_dstr_len(&login->user_name);
			params[ODBC_PARAM_PWD].p   = tds_dstr_cstr(&login->password);
			params[ODBC_PARAM_PWD].len = tds_dstr_len(&login->password);
			params[ODBC_PARAM_Trusted_Connection].p = NULL;
		}
		if (!odbc_build_connect_string(&dbc->errs, params, &out))
			ODBC_EXIT_(dbc);

		odbc_set_string(dbc, szConnStrOut, cbConnStrOutMax, pcbConnStrOut, out, -1);
		tdsdump_log(TDS_DBG_INFO1, "connection string is now: %s\n", out);
		free(out);
#else
		/* we dont support a dialog box */
		odbc_errs_add(&dbc->errs, "HYC00", NULL);
#endif
	}

	if (tds_dstr_isempty(&login->server_name)) {
		tds_free_login(login);
		odbc_errs_add(&dbc->errs, "IM007", "Could not find Servername or server parameter");
		ODBC_EXIT_(dbc);
	}

	odbc_connect(dbc, login);

	tds_free_login(login);
	ODBC_EXIT_(dbc);
}

#if 0
SQLRETURN ODBC_PUBLIC ODBC_API
SQLBrowseConnect(SQLHDBC hdbc, SQLCHAR FAR * szConnStrIn, SQLSMALLINT cbConnStrIn, SQLCHAR FAR * szConnStrOut,
		 SQLSMALLINT cbConnStrOutMax, SQLSMALLINT FAR * pcbConnStrOut)
{
	ODBC_ENTER_HDBC;

	tdsdump_log(TDS_DBG_FUNC, "SQLBrowseConnect(%p, %s, %d, %p, %d, %p)\n",
			hdbc, szConnStrIn, cbConnStrIn, szConnStrOut, cbConnStrOutMax, pcbConnStrOut);
	odbc_errs_add(&dbc->errs, "HYC00", "SQLBrowseConnect: function not implemented");
	ODBC_EXIT_(dbc);
}
#endif


ODBC_FUNC(SQLColumnPrivileges, (P(SQLHSTMT,hstmt), PCHARIN(CatalogName,SQLSMALLINT), PCHARIN(SchemaName,SQLSMALLINT),
	PCHARIN(TableName,SQLSMALLINT), PCHARIN(ColumnName,SQLSMALLINT) WIDE))
{
	int retcode;

	ODBC_ENTER_HSTMT;

	retcode =
		odbc_stat_execute(stmt _wide, "sp_column_privileges", 4, "O@table_qualifier", szCatalogName, cbCatalogName,
				  "O@table_owner", szSchemaName, cbSchemaName, "O@table_name", szTableName, cbTableName,
				  "P@column_name", szColumnName, cbColumnName);
	if (SQL_SUCCEEDED(retcode) && stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3) {
		odbc_col_setname(stmt, 1, "TABLE_CAT");
		odbc_col_setname(stmt, 2, "TABLE_SCHEM");
	}
	ODBC_EXIT_(stmt);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLDescribeParam(SQLHSTMT hstmt, SQLUSMALLINT ipar, SQLSMALLINT FAR * pfSqlType, SQLULEN FAR * pcbParamDef,
		 SQLSMALLINT FAR * pibScale, SQLSMALLINT FAR * pfNullable)
{
	TDS_DESC *ipd;
	struct _drecord *drec;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLDescribeParam(%p, %d, %p, %p, %p, %p)\n",
			hstmt, ipar, pfSqlType, pcbParamDef, pibScale, pfNullable);

	/* Check if we need to ask server (not set params, we not asked before). */
	/* We need to reset set params resetting them. */
	if (!stmt->params_queried && !stmt->params_set && stmt->param_count > 0)
		read_params(stmt);

	/* return information from IPD */
	ipd = stmt->ipd;
	if (ipar < 1 || ipar > ipd->header.sql_desc_count) {
		odbc_errs_add(&stmt->errs, "07009", NULL);
		ODBC_EXIT_(stmt);
	}
	drec = &ipd->records[ipar - 1];

	if (pfSqlType)
		*pfSqlType = drec->sql_desc_type;
	if (pfNullable)
		*pfNullable = drec->sql_desc_nullable;
	switch (drec->sql_desc_type) {
	case SQL_DECIMAL:
	case SQL_NUMERIC:
		if (pcbParamDef)
			*pcbParamDef = drec->sql_desc_precision;
		if (pibScale)
			*pibScale = drec->sql_desc_scale;
		break;
	default:
		if (pcbParamDef)
			*pcbParamDef = drec->sql_desc_length;
		if (pibScale)
			*pibScale = 0;
		break;
	}
	ODBC_EXIT_(stmt);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLExtendedFetch(SQLHSTMT hstmt, SQLUSMALLINT fFetchType, SQLROWOFFSET irow,
		 SQLROWSETSIZE FAR * pcrow, SQLUSMALLINT FAR * rgfRowStatus)
{
	SQLRETURN ret;
	SQLULEN * tmp_rows;
	SQLUSMALLINT * tmp_status;
	SQLULEN tmp_size;
	SQLLEN * tmp_offset;
	SQLPOINTER tmp_bookmark;
	SQLULEN bookmark;
	SQLULEN out_len = 0;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLExtendedFetch(%p, %d, %d, %p, %p)\n",
			hstmt, fFetchType, (int)irow, pcrow, rgfRowStatus);

	if (fFetchType != SQL_FETCH_NEXT && !stmt->dbc->cursor_support) {
		odbc_errs_add(&stmt->errs, "HY106", NULL);
		ODBC_EXIT_(stmt);
	}

	/* save and change IRD/ARD state */
	tmp_rows = stmt->ird->header.sql_desc_rows_processed_ptr;
	stmt->ird->header.sql_desc_rows_processed_ptr = &out_len;
	tmp_status = stmt->ird->header.sql_desc_array_status_ptr;
	stmt->ird->header.sql_desc_array_status_ptr = rgfRowStatus;
	tmp_size = stmt->ard->header.sql_desc_array_size;
	stmt->ard->header.sql_desc_array_size = stmt->sql_rowset_size;
	tmp_offset = stmt->ard->header.sql_desc_bind_offset_ptr;
	stmt->ard->header.sql_desc_bind_offset_ptr = NULL;
	tmp_bookmark = stmt->attr.fetch_bookmark_ptr;

	/* SQL_FETCH_BOOKMARK different */
	if (fFetchType == SQL_FETCH_BOOKMARK) {
		bookmark = irow;
		irow = 0;
		stmt->attr.fetch_bookmark_ptr = &bookmark;
	}

	/* TODO errors are sligthly different ... perhaps it's better to leave DM do this job ?? */
	/* TODO check fFetchType can be converted to USMALLINT */
	ret = odbc_SQLFetch(stmt, fFetchType, irow);

	/* restore IRD/ARD */
	stmt->ird->header.sql_desc_rows_processed_ptr = tmp_rows;
	if (pcrow)
		*pcrow = out_len;
	stmt->ird->header.sql_desc_array_status_ptr = tmp_status;
	stmt->ard->header.sql_desc_array_size = tmp_size;
	stmt->ard->header.sql_desc_bind_offset_ptr = tmp_offset;
	stmt->attr.fetch_bookmark_ptr = tmp_bookmark;

	ODBC_EXIT(stmt, ret);
}

ODBC_FUNC(SQLForeignKeys, (P(SQLHSTMT,hstmt), PCHARIN(PkCatalogName,SQLSMALLINT),
	PCHARIN(PkSchemaName,SQLSMALLINT), PCHARIN(PkTableName,SQLSMALLINT),
	PCHARIN(FkCatalogName,SQLSMALLINT), PCHARIN(FkSchemaName,SQLSMALLINT),
	PCHARIN(FkTableName,SQLSMALLINT) WIDE))
{
	int retcode;

	ODBC_ENTER_HSTMT;

	retcode =
		odbc_stat_execute(stmt _wide, "sp_fkeys", 6,
				  "O@pktable_qualifier", szPkCatalogName, cbPkCatalogName,
				  "O@pktable_owner", szPkSchemaName, cbPkSchemaName,
				  "O@pktable_name", szPkTableName, cbPkTableName,
				  "O@fktable_qualifier", szFkCatalogName, cbFkCatalogName,
				  "O@fktable_owner", szFkSchemaName, cbFkSchemaName,
				  "O@fktable_name", szFkTableName, cbFkTableName);
	if (SQL_SUCCEEDED(retcode) && stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3) {
		odbc_col_setname(stmt, 1, "PKTABLE_CAT");
		odbc_col_setname(stmt, 2, "PKTABLE_SCHEM");
		odbc_col_setname(stmt, 5, "FKTABLE_CAT");
		odbc_col_setname(stmt, 6, "FKTABLE_SCHEM");
	}
	ODBC_EXIT_(stmt);
}

static bool
odbc_lock_statement(TDS_STMT* stmt)
{
#if ENABLE_ODBC_MARS
	TDSSOCKET *tds = stmt->tds;


	/* we already own a socket, just use it */
	if (!tds) {
		/* try with one saved into DBC */
		TDSSOCKET *dbc_tds = stmt->dbc->tds_socket;
		tds_mutex_lock(&stmt->dbc->mtx);

		if (stmt->dbc->current_statement == NULL
		    || stmt->dbc->current_statement == stmt) {
			tds = dbc_tds;
			stmt->dbc->current_statement = stmt;
		}

		/* try to grab current locked one */
		if (!tds && dbc_tds->state == TDS_IDLE) {
			stmt->dbc->current_statement->tds = NULL;
			tds = dbc_tds;
			stmt->dbc->current_statement = stmt;
		}
		tds_mutex_unlock(&stmt->dbc->mtx);

		/* try with MARS */
		if (!tds)
			tds = tds_alloc_additional_socket(dbc_tds->conn);
	}
	if (tds) {
		tds->query_timeout = (stmt->attr.query_timeout != DEFAULT_QUERY_TIMEOUT) ?
			stmt->attr.query_timeout : stmt->dbc->default_query_timeout;
		tds_set_parent(tds, stmt);
		stmt->tds = tds;
		return true;
	}
	odbc_errs_add(&stmt->errs, "24000", NULL);
	return false;
#else
	TDSSOCKET *tds = stmt->dbc->tds_socket;

	tds_mutex_lock(&stmt->dbc->mtx);
	if (stmt->dbc->current_statement != NULL
	    && stmt->dbc->current_statement != stmt) {
		if (!tds || tds->state != TDS_IDLE) {
			tds_mutex_unlock(&stmt->dbc->mtx);
			odbc_errs_add(&stmt->errs, "24000", NULL);
			return false;
		}
		stmt->dbc->current_statement->tds = NULL;
	}
	stmt->dbc->current_statement = stmt;
	if (tds) {
		tds->query_timeout = (stmt->attr.query_timeout != DEFAULT_QUERY_TIMEOUT) ?
			stmt->attr.query_timeout : stmt->dbc->default_query_timeout;
		tds_set_parent(tds, stmt);
		stmt->tds = tds;
	}
	tds_mutex_unlock(&stmt->dbc->mtx);
	return true;
#endif
}

static void
odbc_unlock_statement(TDS_STMT* stmt)
{
	TDSSOCKET * tds;

	tds_mutex_lock(&stmt->dbc->mtx);
	tds = stmt->tds;
	if (stmt->dbc->current_statement == stmt) {
		assert(tds == stmt->dbc->tds_socket);
		if (tds->state == TDS_IDLE || tds->state == TDS_DEAD) {
			stmt->dbc->current_statement = NULL;
			tds_set_parent(tds, stmt->dbc);
			stmt->tds = NULL;
		}
#if ENABLE_ODBC_MARS
	} else if (tds) {
		if (tds->state == TDS_IDLE || tds->state == TDS_DEAD) {
			assert(tds != stmt->dbc->tds_socket);
			tds_free_socket(tds);
			stmt->tds = NULL;
		}
#endif
	}
	tds_mutex_unlock(&stmt->dbc->mtx);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLMoreResults(SQLHSTMT hstmt)
{
	TDSSOCKET *tds;
	TDS_INT result_type;
	TDSRET tdsret;
	bool in_row = false;
	SQLUSMALLINT param_status;
	unsigned int token_flags;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLMoreResults(%p)\n", hstmt);

	tds = stmt->tds;

	/* We already read all results... */
	if (!tds)
		ODBC_EXIT(stmt, SQL_NO_DATA);

	stmt->row_count = TDS_NO_COUNT;
	stmt->special_row = ODBC_SPECIAL_NONE;

	/* TODO this code is TOO similar to odbc_SQLExecute, merge it - freddy77 */
	/* try to go to the next recordset */
	if (stmt->row_status == IN_COMPUTE_ROW) {
		/* FIXME doesn't seem so fine ... - freddy77 */
		tds_process_tokens(tds, &result_type, NULL, TDS_TOKEN_TRAILING);
		stmt->row_status = IN_COMPUTE_ROW;
		in_row = true;
	}

	param_status = SQL_PARAM_SUCCESS;
	token_flags = (TDS_TOKEN_RESULTS & (~TDS_STOPAT_COMPUTE)) | TDS_RETURN_COMPUTE;
	if (stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3)
		token_flags |= TDS_RETURN_MSG;
	for (;;) {
		result_type = odbc_process_tokens(stmt, token_flags);
		tdsdump_log(TDS_DBG_INFO1, "SQLMoreResults: result_type=%d, row_count=%" PRId64 ", lastrc=%d\n",
						result_type, stmt->row_count, stmt->errs.lastrc);
		switch (result_type) {
		case TDS_CMD_DONE:
#if 1 /* !UNIXODBC */
			tds_free_all_results(tds);
#endif
			odbc_populate_ird(stmt);
			odbc_unlock_statement(stmt);
			if (stmt->row_count == TDS_NO_COUNT && !in_row) {
				stmt->row_status = NOT_IN_ROW;
				tdsdump_log(TDS_DBG_INFO1, "SQLMoreResults: row_status=%d\n", stmt->row_status);
			}
			tdsdump_log(TDS_DBG_INFO1, "SQLMoreResults: row_count=%" PRId64 ", lastrc=%d\n", stmt->row_count,
				    stmt->errs.lastrc);
			if (stmt->row_count == TDS_NO_COUNT) {
				if (stmt->errs.lastrc == SQL_SUCCESS || stmt->errs.lastrc == SQL_SUCCESS_WITH_INFO)
					ODBC_EXIT(stmt, SQL_NO_DATA);
			}
			ODBC_EXIT_(stmt);

		case TDS_CMD_FAIL:
			ODBC_SAFE_ERROR(stmt);
			ODBC_EXIT_(stmt);

		case TDS_COMPUTE_RESULT:
			switch (stmt->row_status) {
			/* skip this recordset */
			case IN_COMPUTE_ROW:
				/* TODO here we should set current_results to normal results */
				in_row = true;
				/* fall through */
			/* in normal row, put in compute status */
			case AFTER_COMPUTE_ROW:
			case IN_NORMAL_ROW:
			case PRE_NORMAL_ROW:
				stmt->row_status = IN_COMPUTE_ROW;
				odbc_populate_ird(stmt);
				ODBC_EXIT_(stmt);
			case NOT_IN_ROW:
				/* this should never happen, protocol error */
				ODBC_SAFE_ERROR(stmt);
				ODBC_EXIT_(stmt);
				break;
			}
			break;

		case TDS_ROW_RESULT:
			if (in_row || (stmt->row_status != IN_NORMAL_ROW && stmt->row_status != PRE_NORMAL_ROW)) {
				stmt->row_status = PRE_NORMAL_ROW;
				odbc_populate_ird(stmt);
				ODBC_EXIT_(stmt);
			}
			/* Skipping current result set's rows to access next resultset or proc's retval */
			tdsret = tds_process_tokens(tds, &result_type, NULL, TDS_STOPAT_ROWFMT|TDS_RETURN_DONE|TDS_STOPAT_COMPUTE);
			/* TODO should we set in_row ?? */
			switch (tdsret) {
			case TDS_CANCELLED:
				odbc_errs_add(&stmt->errs, "HY008", NULL);
			default:
				if (TDS_FAILED(tdsret)) {
					ODBC_SAFE_ERROR(stmt);
					ODBC_EXIT_(stmt);
				}
			}
			break;

		case TDS_DONE_RESULT:
		case TDS_DONEPROC_RESULT:
			/* FIXME here ??? */
			if (!in_row)
				tds_free_all_results(tds);
			switch (stmt->errs.lastrc) {
			case SQL_ERROR:
				param_status = SQL_PARAM_ERROR;
				break;
			case SQL_SUCCESS_WITH_INFO:
				param_status = SQL_PARAM_SUCCESS_WITH_INFO;
				break;
			}
			if (stmt->curr_param_row < stmt->num_param_rows) {
				if (stmt->ipd->header.sql_desc_array_status_ptr)
					stmt->ipd->header.sql_desc_array_status_ptr[stmt->curr_param_row] = param_status;
				++stmt->curr_param_row;
				if (stmt->ipd->header.sql_desc_rows_processed_ptr)
					*stmt->ipd->header.sql_desc_rows_processed_ptr = stmt->curr_param_row;
			}
			if (stmt->curr_param_row < stmt->num_param_rows) {
#if 0
				if (stmt->errs.lastrc == SQL_SUCCESS_WITH_INFO)
					found_info = 1;
				if (stmt->errs.lastrc == SQL_ERROR)
					found_error = 1;
#endif
				stmt->errs.lastrc = SQL_SUCCESS;
				param_status = SQL_PARAM_SUCCESS;
				break;
			}
			odbc_populate_ird(stmt);
			ODBC_EXIT_(stmt);
			break;

			/*
			 * TODO test flags ? check error and change result ?
			 * see also other DONEINPROC handle (below)
			 */
		case TDS_DONEINPROC_RESULT:
			if (in_row) {
				odbc_populate_ird(stmt);
				ODBC_EXIT_(stmt);
			}
			/* TODO perhaps it can be a problem if SET NOCOUNT ON, test it */
			tds_free_all_results(tds);
			odbc_populate_ird(stmt);
			break;

			/* do not stop at metadata, an error can follow... */
		case TDS_ROWFMT_RESULT:
			if (in_row) {
				odbc_populate_ird(stmt);
				ODBC_EXIT_(stmt);
			}
			stmt->row = 0;
			stmt->row_count = TDS_NO_COUNT;
			/* we expect a row */
			stmt->row_status = PRE_NORMAL_ROW;
			in_row = true;
			break;

		case TDS_MSG_RESULT:
			if (!in_row) {
				tds_free_all_results(tds);
				odbc_populate_ird(stmt);
			}
			in_row = true;
			break;
		}
	}
	ODBC_EXIT(stmt, SQL_ERROR);
}

ODBC_FUNC(SQLNativeSql, (P(SQLHDBC,hdbc), PCHARIN(SqlStrIn,SQLINTEGER),
	PCHAROUT(SqlStr,SQLINTEGER) WIDE))
{
	SQLRETURN ret = SQL_SUCCESS;
	DSTR query = DSTR_INITIALIZER;

	ODBC_ENTER_HDBC;

#ifdef TDS_NO_DM
	if (!szSqlStrIn || !IS_VALID_LEN(cbSqlStrIn)) {
		odbc_errs_add(&dbc->errs, "HY009", NULL);
		ODBC_EXIT_(dbc);
	}
#endif

	if (!odbc_dstr_copy(dbc, &query, cbSqlStrIn, szSqlStrIn)) {
		odbc_errs_add(&dbc->errs, "HY001", NULL);
		ODBC_EXIT_(dbc);
	}

	native_sql(dbc, &query);

	/* FIXME if error set some kind of error */
	ret = odbc_set_dstr(dbc, szSqlStr, cbSqlStrMax, pcbSqlStr, &query);

	tds_dstr_free(&query);

	ODBC_EXIT(dbc, ret);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLNumParams(SQLHSTMT hstmt, SQLSMALLINT FAR * pcpar)
{
	ODBC_ENTER_HSTMT;
	tdsdump_log(TDS_DBG_FUNC, "SQLNumParams(%p, %p)\n", hstmt, pcpar);
	*pcpar = stmt->param_count;
	ODBC_EXIT_(stmt);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLParamOptions(SQLHSTMT hstmt, SQLULEN crow, SQLULEN FAR * pirow)
{
	SQLRETURN res;

	tdsdump_log(TDS_DBG_FUNC, "SQLParamOptions(%p, %lu, %p)\n", hstmt, (unsigned long int)crow, pirow);

	/* emulate for ODBC 2 DM */
	res = odbc_SQLSetStmtAttr(hstmt, SQL_ATTR_PARAMS_PROCESSED_PTR, pirow, 0 _wide0);
	if (res != SQL_SUCCESS)
		return res;
	return odbc_SQLSetStmtAttr(hstmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) (TDS_INTPTR) crow, 0 _wide0);
}

ODBC_FUNC(SQLPrimaryKeys, (P(SQLHSTMT,hstmt), PCHARIN(CatalogName,SQLSMALLINT),
	PCHARIN(SchemaName,SQLSMALLINT), PCHARIN(TableName,SQLSMALLINT) WIDE))
{
	int retcode;

	ODBC_ENTER_HSTMT;

	retcode =
		odbc_stat_execute(stmt _wide, "sp_pkeys", 3, "O@table_qualifier", szCatalogName, cbCatalogName, "O@table_owner",
				  szSchemaName, cbSchemaName, "O@table_name", szTableName, cbTableName);
	if (SQL_SUCCEEDED(retcode) && stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3) {
		odbc_col_setname(stmt, 1, "TABLE_CAT");
		odbc_col_setname(stmt, 2, "TABLE_SCHEM");
	}
	ODBC_EXIT_(stmt);
}

ODBC_FUNC(SQLProcedureColumns, (P(SQLHSTMT,hstmt), PCHARIN(CatalogName,SQLSMALLINT),
	PCHARIN(SchemaName,SQLSMALLINT), PCHARIN(ProcName,SQLSMALLINT), PCHARIN(ColumnName,SQLSMALLINT) WIDE))
{
	int retcode;

	ODBC_ENTER_HSTMT;

	retcode =
		odbc_stat_execute(stmt _wide, "sp_sproc_columns", TDS_IS_MSSQL(stmt->dbc->tds_socket) ? 5 : 4,
				  "O@procedure_qualifier", szCatalogName, cbCatalogName,
				  "P@procedure_owner", szSchemaName, cbSchemaName, "P@procedure_name", szProcName, cbProcName,
				  "P@column_name", szColumnName, cbColumnName, "V@ODBCVer", (char*) NULL, 0);
	if (SQL_SUCCEEDED(retcode) && stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3) {
		odbc_col_setname(stmt, 1, "PROCEDURE_CAT");
		odbc_col_setname(stmt, 2, "PROCEDURE_SCHEM");
		odbc_col_setname(stmt, 8, "COLUMN_SIZE");
		odbc_col_setname(stmt, 9, "BUFFER_LENGTH");
		odbc_col_setname(stmt, 10, "DECIMAL_DIGITS");
		odbc_col_setname(stmt, 11, "NUM_PREC_RADIX");
		if (TDS_IS_SYBASE(stmt->dbc->tds_socket))
			stmt->special_row = ODBC_SPECIAL_PROCEDURECOLUMNS;
	}
	ODBC_EXIT_(stmt);
}

ODBC_FUNC(SQLProcedures, (P(SQLHSTMT,hstmt), PCHARIN(CatalogName,SQLSMALLINT),
	PCHARIN(SchemaName,SQLSMALLINT), PCHARIN(ProcName,SQLSMALLINT) WIDE))
{
	int retcode;

	ODBC_ENTER_HSTMT;

	retcode =
		odbc_stat_execute(stmt _wide, "..sp_stored_procedures", 3,
				  "P@sp_name", szProcName, cbProcName,
				  "P@sp_owner", szSchemaName, cbSchemaName,
				  "O@sp_qualifier", szCatalogName, cbCatalogName);
	if (SQL_SUCCEEDED(retcode) && stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3) {
		odbc_col_setname(stmt, 1, "PROCEDURE_CAT");
		odbc_col_setname(stmt, 2, "PROCEDURE_SCHEM");
	}
	ODBC_EXIT_(stmt);
}

static TDSPARAMINFO*
odbc_build_update_params(TDS_STMT * stmt, unsigned int n_row)
{
	unsigned int n;
	TDSPARAMINFO * params = NULL;
	struct _drecord *drec_ird;

	for (n = 0; n < stmt->ird->header.sql_desc_count && n < stmt->ard->header.sql_desc_count; ++n) {
		TDSPARAMINFO *temp_params;
		TDSCOLUMN *curcol;

		drec_ird = &stmt->ird->records[n];

		if (drec_ird->sql_desc_updatable == SQL_FALSE)
			continue;

		/* we have certainly a parameter */
		if (!(temp_params = tds_alloc_param_result(params)))
			goto memory_error;
                params = temp_params;

		curcol = params->columns[params->num_cols - 1];
		if (!tds_dstr_dup(&curcol->column_name, &drec_ird->sql_desc_name))
			goto memory_error;

		/* TODO use all infos... */
		if (!tds_dstr_dup(&curcol->table_name, &drec_ird->sql_desc_base_table_name))
			goto memory_error;

		switch (odbc_sql2tds(stmt, drec_ird, &stmt->ard->records[n], curcol, true, stmt->ard, n_row)) {
		case SQL_NEED_DATA:
			goto memory_error;
		case SQL_ERROR:
			tds_free_param_results(params);
			return NULL;
		}
	}
	return params;

memory_error:
	tds_free_param_results(params);
	odbc_errs_add(&stmt->errs, "HY001", NULL);
	return NULL;
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLSetPos(SQLHSTMT hstmt, SQLSETPOSIROW irow, SQLUSMALLINT fOption, SQLUSMALLINT fLock)
{
	TDSRET ret;
	TDSSOCKET *tds;
	TDS_CURSOR_OPERATION op;
	TDSPARAMINFO *params = NULL;
	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLSetPos(%p, %ld, %d, %d)\n",
			hstmt, (long) irow, fOption, fLock);

	if (!stmt->dbc->cursor_support) {
		odbc_errs_add(&stmt->errs, "HYC00", "SQLSetPos: function not implemented");
		ODBC_EXIT_(stmt);
	}

	/* TODO handle irow == 0 (all rows) */

	if (!stmt->cursor) {
		odbc_errs_add(&stmt->errs, "HY109", NULL);
		ODBC_EXIT_(stmt);
	}

	switch (fOption) {
	case SQL_POSITION:
		op = TDS_CURSOR_POSITION;
		break;
	/* TODO cursor support */
	case SQL_REFRESH:
	default:
		odbc_errs_add(&stmt->errs, "HY092", NULL);
		ODBC_EXIT_(stmt);
		break;
	case SQL_UPDATE:
		op = TDS_CURSOR_UPDATE;
		/* prepare paremeters for update */
		/* scan all columns and build parameter list */
		params = odbc_build_update_params(stmt, irow >= 1 ? irow - 1 : 0);
		if (!params) {
			ODBC_SAFE_ERROR(stmt);
			ODBC_EXIT_(stmt);
		}
		break;
	case SQL_DELETE:
		op = TDS_CURSOR_DELETE;
		break;
	case SQL_ADD:
		op = TDS_CURSOR_INSERT;
		break;
	}

	if (!odbc_lock_statement(stmt)) {
		tds_free_param_results(params);
		ODBC_EXIT_(stmt);
	}

	tds = stmt->tds;

	if (TDS_FAILED(tds_cursor_update(tds, stmt->cursor, op, irow, params))) {
		tds_free_param_results(params);
		ODBC_SAFE_ERROR(stmt);
		ODBC_EXIT_(stmt);
	}
	tds_free_param_results(params);
	params = NULL;

	ret = tds_process_simple_query(tds);
	odbc_unlock_statement(stmt);
	if (TDS_FAILED(ret)) {
		ODBC_SAFE_ERROR(stmt);
		ODBC_EXIT_(stmt);
	}

	ODBC_EXIT_(stmt);
}

ODBC_FUNC(SQLTablePrivileges, (P(SQLHSTMT,hstmt), PCHARIN(CatalogName,SQLSMALLINT),
	PCHARIN(SchemaName,SQLSMALLINT), PCHARIN(TableName,SQLSMALLINT) WIDE))
{
	int retcode;

	ODBC_ENTER_HSTMT;

	retcode =
		odbc_stat_execute(stmt _wide, "sp_table_privileges", 3, "O@table_qualifier", szCatalogName, cbCatalogName,
				  "P@table_owner", szSchemaName, cbSchemaName, "P@table_name", szTableName, cbTableName);
	if (SQL_SUCCEEDED(retcode) && stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3) {
		odbc_col_setname(stmt, 1, "TABLE_CAT");
		odbc_col_setname(stmt, 2, "TABLE_SCHEM");
	}
	ODBC_EXIT_(stmt);
}

#if (ODBCVER >= 0x0300)
SQLRETURN ODBC_PUBLIC ODBC_API
SQLSetEnvAttr(SQLHENV henv, SQLINTEGER Attribute, SQLPOINTER Value, SQLINTEGER StringLength)
{
	SQLINTEGER i_val = (SQLINTEGER) (TDS_INTPTR) Value;

	ODBC_ENTER_HENV;

	tdsdump_log(TDS_DBG_FUNC, "SQLSetEnvAttr(%p, %d, %p, %d)\n", henv, (int)Attribute, Value, (int)StringLength);

	switch (Attribute) {
	case SQL_ATTR_CONNECTION_POOLING:
	case SQL_ATTR_CP_MATCH:
		odbc_errs_add(&env->errs, "HYC00", NULL);
		break;
	case SQL_ATTR_ODBC_VERSION:
		switch (i_val) {
		case SQL_OV_ODBC3:
		case SQL_OV_ODBC2:
			env->attr.odbc_version = i_val;
			break;
		default:
			odbc_errs_add(&env->errs, "HY024", NULL);
			break;
		}
		break;
	case SQL_ATTR_OUTPUT_NTS:
		/* TODO - Make this really work */
		/* env->attr.output_nts = i_val; */
		env->attr.output_nts = SQL_TRUE;
		break;
	default:
		odbc_errs_add(&env->errs, "HY092", NULL);
		break;
	}
	ODBC_EXIT_(env);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLGetEnvAttr(SQLHENV henv, SQLINTEGER Attribute, SQLPOINTER Value, SQLINTEGER BufferLength, SQLINTEGER * StringLength)
{
	static const SQLINTEGER unicode_type =
		sizeof(SQLWCHAR) == 4 ? SQL_DM_CP_UCS4 : SQL_DM_CP_UTF16;
	size_t size;
	const void *src;

	ODBC_ENTER_HENV;

	tdsdump_log(TDS_DBG_FUNC, "SQLGetEnvAttr(%p, %d, %p, %d, %p)\n",
			henv, (int)Attribute, Value, (int)BufferLength, StringLength);

	switch (Attribute) {
	case SQL_ATTR_CONNECTION_POOLING:
		src = &env->attr.connection_pooling;
		size = sizeof(env->attr.connection_pooling);
		break;
	case SQL_ATTR_CP_MATCH:
		src = &env->attr.cp_match;
		size = sizeof(env->attr.cp_match);
		break;
	case SQL_ATTR_ODBC_VERSION:
		src = &env->attr.odbc_version;
		size = sizeof(env->attr.odbc_version);
		break;
	case SQL_ATTR_OUTPUT_NTS:
		/* TODO handle output_nts flags */
		env->attr.output_nts = SQL_TRUE;
		src = &env->attr.output_nts;
		size = sizeof(env->attr.output_nts);
		break;
	case SQL_ATTR_DRIVER_UNICODE_TYPE:
		src = &unicode_type;
		size = sizeof(unicode_type);
		break;
	default:
		odbc_errs_add(&env->errs, "HY092", NULL);
		ODBC_EXIT_(env);
		break;
	}

	if (StringLength) {
		*StringLength = size;
	}
	memcpy(Value, src, size);

	ODBC_EXIT_(env);
}

#endif

#define IRD_UPDATE(desc, errs, exit) \
	do { \
		if (desc->type == DESC_IRD && ((TDS_STMT*)desc->parent)->need_reprepare && \
		    odbc_update_ird((TDS_STMT*)desc->parent, errs) != SQL_SUCCESS) \
			exit; \
	} while(0)

static SQLRETURN
odbc_SQLBindParameter(SQLHSTMT hstmt, SQLUSMALLINT ipar, SQLSMALLINT fParamType, SQLSMALLINT fCType, SQLSMALLINT fSqlType,
		  SQLULEN cbColDef, SQLSMALLINT ibScale, SQLPOINTER rgbValue, SQLLEN cbValueMax, SQLLEN FAR * pcbValue)
{
	TDS_DESC *apd, *ipd;
	struct _drecord *drec;
	SQLSMALLINT orig_apd_size, orig_ipd_size;
	bool is_numeric = false;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLBindParameter(%p, %u, %d, %d, %d, %u, %d, %p, %d, %p)\n",
			hstmt, (unsigned)ipar, (int)fParamType, (int)fCType, (int)fSqlType, (unsigned int)cbColDef,
			(int)ibScale, rgbValue, (int)cbValueMax, pcbValue);

#ifdef TDS_NO_DM
	/* TODO - more error checking ...  XXX smurph */

	/* Check param type */
	switch (fParamType) {
	case SQL_PARAM_INPUT:
	case SQL_PARAM_INPUT_OUTPUT:
	case SQL_PARAM_OUTPUT:
		break;
	default:
		odbc_errs_add(&stmt->errs, "HY105", NULL);
		ODBC_EXIT_(stmt);
	}

	/* Check max buffer length */
	if (cbValueMax < 0 && cbValueMax != SQL_NTS) {
		odbc_errs_add(&stmt->errs, "HY090", NULL);
		ODBC_EXIT_(stmt);
	}
#endif

	/* check cbColDef and ibScale */
	if (fSqlType == SQL_DECIMAL || fSqlType == SQL_NUMERIC) {
		is_numeric = true;
		if (cbColDef < 1 || cbColDef > 38) {
			odbc_errs_add(&stmt->errs, "HY104", "Invalid precision value");
			ODBC_EXIT_(stmt);
		}
		if (ibScale < 0 || ibScale > cbColDef) {
			odbc_errs_add(&stmt->errs, "HY104", "Invalid scale value");
			ODBC_EXIT_(stmt);
		}
	}

	/* Check parameter number */
	if (ipar <= 0 || ipar > 4000) {
		odbc_errs_add(&stmt->errs, "07009", NULL);
		ODBC_EXIT_(stmt);
	}

	/* special checks for table type */
	if (fSqlType == SQL_SS_TABLE) {
		/* Table types are strictly read-only */
		if (fParamType != SQL_PARAM_INPUT) {
			odbc_errs_add(&stmt->errs, "HY105", NULL);
			ODBC_EXIT_(stmt);
		}

		/* Only SQL_C_DEFAULT is accepted */
		if (fCType != SQL_C_DEFAULT) {
			odbc_errs_add(&stmt->errs, "07006", NULL);
			ODBC_EXIT_(stmt);
		}
		fCType = SQL_C_BINARY;
	}

	apd = stmt->apd;
	ipd = stmt->ipd;

	/* If the parameter focus is set to 0, bind values as arguments */
	/* Otherwise, bind values as the columns of a table-valued parameter */
	if (stmt->attr.param_focus != 0) {
		SQLTVP *tvp;

		/* a table type cannot contain a table type */
		if (fSqlType == SQL_SS_TABLE) {
			odbc_errs_add(&stmt->errs, "HY004", NULL);
			ODBC_EXIT_(stmt);
		}

		/* Columns of table types are strictly read-only */
		if (fParamType != SQL_PARAM_INPUT) {
			odbc_errs_add(&stmt->errs, "HY105", NULL);
			ODBC_EXIT_(stmt);
		}

		/* check index */
		if (stmt->attr.param_focus > ipd->header.sql_desc_count) {
			odbc_errs_add(&stmt->errs, "IM020", NULL);
			ODBC_EXIT_(stmt);
		}

		drec = &ipd->records[stmt->attr.param_focus - 1];

		/* check ipd type */
		if (drec->sql_desc_concise_type != SQL_SS_TABLE) {
			odbc_errs_add(&stmt->errs, "IM020", NULL);
			ODBC_EXIT_(stmt);
		}

		tvp = (SQLTVP *) drec->sql_desc_data_ptr;
		apd = tvp->apd;
		ipd = tvp->ipd;
	}

	/* fill APD related fields */
	orig_apd_size = apd->header.sql_desc_count;
	if (ipar > apd->header.sql_desc_count && desc_alloc_records(apd, ipar) != SQL_SUCCESS) {
		odbc_errs_add(&stmt->errs, "HY001", NULL);
		ODBC_EXIT_(stmt);
	}
	drec = &apd->records[ipar - 1];

	if (odbc_set_concise_c_type(fCType, drec, 0) != SQL_SUCCESS) {
		desc_alloc_records(apd, orig_apd_size);
		odbc_errs_add(&stmt->errs, "HY004", NULL);
		ODBC_EXIT_(stmt);
	}

	stmt->need_reprepare = 1;

	/* TODO other types ?? handle SQL_C_DEFAULT */
	if (drec->sql_desc_type == SQL_C_CHAR || drec->sql_desc_type == SQL_C_WCHAR || drec->sql_desc_type == SQL_C_BINARY)
		drec->sql_desc_octet_length = cbValueMax;
	drec->sql_desc_indicator_ptr = pcbValue;
	drec->sql_desc_octet_length_ptr = pcbValue;
	drec->sql_desc_data_ptr = (char *) rgbValue;

	if (fSqlType == SQL_SS_TABLE)
		drec->sql_desc_length = 1;

	/* field IPD related fields */
	orig_ipd_size = ipd->header.sql_desc_count;
	if (ipar > ipd->header.sql_desc_count && desc_alloc_records(ipd, ipar) != SQL_SUCCESS) {
		desc_alloc_records(apd, orig_apd_size);
		odbc_errs_add(&stmt->errs, "HY001", NULL);
		ODBC_EXIT_(stmt);
	}
	drec = &ipd->records[ipar - 1];

	drec->sql_desc_parameter_type = fParamType;

	if (odbc_set_concise_sql_type(fSqlType, drec, 0) != SQL_SUCCESS) {
		desc_alloc_records(ipd, orig_ipd_size);
		desc_alloc_records(apd, orig_apd_size);
		odbc_errs_add(&stmt->errs, "HY004", NULL);
		ODBC_EXIT_(stmt);
	}
	if (is_numeric) {
		drec->sql_desc_precision = cbColDef;
		drec->sql_desc_scale = ibScale;
	} else {
		drec->sql_desc_length = cbColDef;
	}

	if (fSqlType == SQL_SS_TABLE) {
		SQLTVP *tvp;
		int wide = 1;

		tvp = tvp_alloc(stmt);
		if (tvp == NULL) {
			desc_alloc_records(ipd, orig_ipd_size);
			desc_alloc_records(apd, orig_apd_size);
			odbc_errs_add(&stmt->errs, "HY001", NULL);
			ODBC_EXIT_(stmt);
		}
		tvp->apd->header.sql_desc_array_size = cbColDef;

		if (!odbc_dstr_copy_oct(stmt->dbc, &tvp->type_name, cbValueMax, (ODBC_CHAR *) rgbValue)) {
			free(tvp);
			desc_alloc_records(ipd, orig_ipd_size);
			desc_alloc_records(apd, orig_apd_size);
			odbc_errs_add(&stmt->errs, "HY001", NULL);
			ODBC_EXIT_(stmt);
		}

		/* Use this the column type as a marker for us */
		/* to free up memory allocated with odbc_alloc_table() */
		drec->sql_desc_type = drec->sql_desc_concise_type = SQL_SS_TABLE;

		drec->sql_desc_data_ptr = tvp;

		drec->sql_desc_length = 0;
	}

	stmt->params_set = 1;
	ODBC_EXIT_(stmt);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLBindParameter(SQLHSTMT hstmt, SQLUSMALLINT ipar, SQLSMALLINT fParamType, SQLSMALLINT fCType, SQLSMALLINT fSqlType,
		 SQLULEN cbColDef, SQLSMALLINT ibScale, SQLPOINTER rgbValue, SQLLEN cbValueMax, SQLLEN FAR * pcbValue)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLBindParameter(%p, %u, %d, %d, %d, %u, %d, %p, %d, %p)\n",
		    hstmt, (unsigned)ipar, fParamType, fCType, (int)fSqlType, (unsigned)cbColDef, ibScale,
		    rgbValue, (int)cbValueMax, pcbValue);
	return odbc_SQLBindParameter(hstmt, ipar, fParamType, fCType, fSqlType, cbColDef, ibScale, rgbValue, cbValueMax, pcbValue);
}


/* compatibility with X/Open */
SQLRETURN ODBC_PUBLIC ODBC_API
SQLBindParam(SQLHSTMT hstmt, SQLUSMALLINT ipar, SQLSMALLINT fCType, SQLSMALLINT fSqlType, SQLULEN cbColDef, SQLSMALLINT ibScale,
	     SQLPOINTER rgbValue, SQLLEN FAR * pcbValue)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLBindParam(%p, %d, %d, %d, %u, %d, %p, %p)\n",
			hstmt, ipar, fCType, fSqlType, (unsigned)cbColDef, ibScale, rgbValue, pcbValue);
	return odbc_SQLBindParameter(hstmt, ipar, SQL_PARAM_INPUT, fCType, fSqlType, cbColDef, ibScale, rgbValue, 0, pcbValue);
}

#if (ODBCVER >= 0x0300)
SQLRETURN ODBC_PUBLIC ODBC_API
SQLAllocHandle(SQLSMALLINT HandleType, SQLHANDLE InputHandle, SQLHANDLE * OutputHandle)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLAllocHandle(%d, %p, %p)\n", HandleType, InputHandle, OutputHandle);

	switch (HandleType) {
	case SQL_HANDLE_STMT:
		return odbc_SQLAllocStmt(InputHandle, OutputHandle);
		break;
	case SQL_HANDLE_DBC:
		return odbc_SQLAllocConnect(InputHandle, OutputHandle);
		break;
	case SQL_HANDLE_ENV:
		return odbc_SQLAllocEnv(OutputHandle, SQL_OV_ODBC3);
		break;
	case SQL_HANDLE_DESC:
		return odbc_SQLAllocDesc(InputHandle, OutputHandle);
		break;
	}

	/*
	 * As the documentation puts it,
	 *	"There is no handle with which to associate additional diagnostic information."
	 *
	 * The DM must catch HY092 because the driver has no valid handle at this early stage in which
	 * to store the error for later retrieval by the application.
	 */
	tdsdump_log(TDS_DBG_FUNC, "SQLAllocHandle(): invalid HandleType, error HY092: should be caught by DM\n");
	return SQL_ERROR;
}
#endif

static SQLRETURN
odbc_SQLAllocConnect(SQLHENV henv, SQLHDBC FAR * phdbc)
{
	TDS_DBC *dbc;

	ODBC_ENTER_HENV;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLAllocConnect(%p, %p)\n", henv, phdbc);

	dbc = tds_new0(TDS_DBC, 1);
	if (!dbc) {
		odbc_errs_add(&env->errs, "HY001", NULL);
		ODBC_EXIT_(env);
	}

	dbc->htype = SQL_HANDLE_DBC;
	dbc->env = env;
	tds_dstr_init(&dbc->dsn);

	dbc->attr.cursor_type = SQL_CURSOR_FORWARD_ONLY;
	dbc->attr.access_mode = SQL_MODE_READ_WRITE;
	dbc->attr.async_enable = SQL_ASYNC_ENABLE_OFF;
	dbc->attr.auto_ipd = SQL_FALSE;
	/*
	 * spinellia@acm.org
	 * after login is enabled autocommit
	 */
	dbc->attr.autocommit = SQL_AUTOCOMMIT_ON;
	dbc->attr.connection_dead = SQL_CD_TRUE;	/* No connection yet */
	dbc->attr.connection_timeout = 0;
	/* This is set in the environment change function */
	tds_dstr_init(&dbc->attr.current_catalog);
	dbc->attr.login_timeout = 0;	/* TODO */
	dbc->attr.metadata_id = SQL_FALSE;
	dbc->attr.odbc_cursors = SQL_CUR_USE_IF_NEEDED;
	dbc->attr.packet_size = 0;
	dbc->attr.quite_mode = NULL;	/* We don't support GUI dialogs yet */
#ifdef TDS_NO_DM
	dbc->attr.trace = SQL_OPT_TRACE_OFF;
	tds_dstr_init(&dbc->attr.tracefile);
#endif
	tds_dstr_init(&dbc->attr.translate_lib);
#ifdef ENABLE_ODBC_WIDE
	dbc->original_charset_num = TDS_CHARSET_UTF_8;
#endif
	tds_dstr_init(&dbc->oldpwd);
	dbc->attr.translate_option = 0;
	dbc->attr.txn_isolation = SQL_TXN_READ_COMMITTED;
	dbc->attr.mars_enabled = SQL_MARS_ENABLED_NO;
	dbc->attr.bulk_enabled = SQL_BCP_OFF;

	tds_mutex_init(&dbc->mtx);
	*phdbc = (SQLHDBC) dbc;

	ODBC_EXIT_(env);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLAllocConnect(SQLHENV henv, SQLHDBC FAR * phdbc)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLAllocConnect(%p, %p)\n", henv, phdbc);

	return odbc_SQLAllocConnect(henv, phdbc);
}

static SQLRETURN
odbc_SQLAllocEnv(SQLHENV FAR * phenv, SQLINTEGER odbc_version)
{
	TDS_ENV *env;
	TDSCONTEXT *ctx;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLAllocEnv(%p, %d)\n",
			phenv, (int) odbc_version);

	env = tds_new0(TDS_ENV, 1);
	if (!env)
		return SQL_ERROR;

	env->htype = SQL_HANDLE_ENV;
	env->attr.odbc_version = odbc_version;
	/* TODO use it */
	env->attr.output_nts = SQL_TRUE;

	ctx = tds_alloc_context(env);
	if (!ctx) {
		free(env);
		return SQL_ERROR;
	}
	env->tds_ctx = ctx;
	ctx->msg_handler = odbc_errmsg_handler;
	ctx->err_handler = odbc_errmsg_handler;

	/* ODBC has its own format */
	free(ctx->locale->datetime_fmt);
	ctx->locale->datetime_fmt = strdup("%Y-%m-%d %H:%M:%S.%z");
	free(ctx->locale->date_fmt);
	ctx->locale->date_fmt = strdup("%Y-%m-%d");
	free(ctx->locale->time_fmt);
	ctx->locale->time_fmt = strdup("%H:%M:%S.%z");

	tds_mutex_init(&env->mtx);
	*phenv = (SQLHENV) env;

	return SQL_SUCCESS;
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLAllocEnv(SQLHENV FAR * phenv)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLAllocEnv(%p)\n", phenv);

	return odbc_SQLAllocEnv(phenv, SQL_OV_ODBC2);
}

static SQLRETURN
odbc_SQLAllocDesc(SQLHDBC hdbc, SQLHDESC FAR * phdesc)
{
	int i;

	ODBC_ENTER_HDBC;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLAllocDesc(%p, %p)\n", hdbc, phdesc);

	for (i = 0; ; ++i) {
		if (i >= TDS_MAX_APP_DESC) {
			odbc_errs_add(&dbc->errs, "HY014", NULL);
			break;
		}
		if (dbc->uad[i] == NULL) {
			TDS_DESC *desc = desc_alloc(dbc, DESC_ARD, SQL_DESC_ALLOC_USER);
			if (desc == NULL) {
				odbc_errs_add(&dbc->errs, "HY001", NULL);
				break;
			}
			dbc->uad[i] = desc;
			*phdesc = (SQLHDESC) desc;
			break;
		}
	}
	ODBC_EXIT_(dbc);
}

static SQLRETURN
odbc_SQLAllocStmt(SQLHDBC hdbc, SQLHSTMT FAR * phstmt)
{
	TDS_STMT *stmt;
	char *pstr;

	ODBC_ENTER_HDBC;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLAllocStmt(%p, %p)\n", hdbc, phstmt);

	stmt = tds_new0(TDS_STMT, 1);
	if (!stmt) {
		odbc_errs_add(&dbc->errs, "HY001", NULL);
		ODBC_EXIT_(dbc);
	}
	tds_dstr_init(&stmt->cursor_name);
	tds_dstr_init(&stmt->query);

	stmt->htype = SQL_HANDLE_STMT;
	stmt->dbc = dbc;
	stmt->num_param_rows = 1;
	pstr = NULL;
	/* TODO test initial cursor ... */
	if (asprintf(&pstr, "SQL_CUR%lx", (unsigned long) (TDS_UINTPTR) stmt) < 0 || !tds_dstr_set(&stmt->cursor_name, pstr)) {
		free(stmt);
		free(pstr);
		odbc_errs_add(&dbc->errs, "HY001", NULL);
		ODBC_EXIT_(dbc);
	}
	/* do not free pstr tds_dstr_set do it if necessary */

	/* allocate descriptors */
	stmt->ird = desc_alloc(stmt, DESC_IRD, SQL_DESC_ALLOC_AUTO);
	stmt->ard = desc_alloc(stmt, DESC_ARD, SQL_DESC_ALLOC_AUTO);
	stmt->ipd = desc_alloc(stmt, DESC_IPD, SQL_DESC_ALLOC_AUTO);
	stmt->apd = desc_alloc(stmt, DESC_APD, SQL_DESC_ALLOC_AUTO);
	if (!stmt->ird || !stmt->ard || !stmt->ipd || !stmt->apd) {
		tds_dstr_free(&stmt->cursor_name);
		desc_free(stmt->ird);
		desc_free(stmt->ard);
		desc_free(stmt->ipd);
		desc_free(stmt->apd);
		free(stmt);
		odbc_errs_add(&dbc->errs, "HY001", NULL);
		ODBC_EXIT_(dbc);
	}

	/* save original ARD and APD */
	stmt->orig_apd = stmt->apd;
	stmt->orig_ard = stmt->ard;

	/* set the default statement attributes */
/*	stmt->attr.app_param_desc = stmt->apd; */
/*	stmt->attr.app_row_desc = stmt->ard; */
	stmt->attr.async_enable = SQL_ASYNC_ENABLE_OFF;
	stmt->attr.concurrency = SQL_CONCUR_READ_ONLY;
	stmt->attr.cursor_scrollable = SQL_NONSCROLLABLE;
	stmt->attr.cursor_sensitivity = SQL_INSENSITIVE;
	stmt->attr.cursor_type = SQL_CURSOR_FORWARD_ONLY;
	/* TODO ?? why two attributes */
	stmt->attr.enable_auto_ipd = dbc->attr.auto_ipd = SQL_FALSE;
	stmt->attr.fetch_bookmark_ptr = NULL;
/*	stmt->attr.imp_param_desc = stmt->ipd; */
/*	stmt->attr.imp_row_desc = stmt->ird; */
	stmt->attr.keyset_size = 0;
	stmt->attr.max_length = 0;
	stmt->attr.max_rows = 0;
	stmt->attr.metadata_id = dbc->attr.metadata_id;
	/* TODO check this flag in prepare_call */
	stmt->attr.noscan = SQL_NOSCAN_OFF;
	assert(stmt->apd->header.sql_desc_bind_offset_ptr == NULL);
	assert(stmt->apd->header.sql_desc_bind_type == SQL_PARAM_BIND_BY_COLUMN);
	assert(stmt->apd->header.sql_desc_array_status_ptr == NULL);
	assert(stmt->ipd->header.sql_desc_array_status_ptr == NULL);
	assert(stmt->ipd->header.sql_desc_rows_processed_ptr == NULL);
	assert(stmt->apd->header.sql_desc_array_size == 1);
	stmt->attr.query_timeout = DEFAULT_QUERY_TIMEOUT;
	stmt->attr.retrieve_data = SQL_RD_ON;
	assert(stmt->ard->header.sql_desc_array_size == 1);
	assert(stmt->ard->header.sql_desc_bind_offset_ptr == NULL);
	assert(stmt->ard->header.sql_desc_bind_type == SQL_BIND_BY_COLUMN);
	stmt->attr.row_number = 0;
	assert(stmt->ard->header.sql_desc_array_status_ptr == NULL);
	assert(stmt->ird->header.sql_desc_array_status_ptr == NULL);
	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;

	stmt->row_count = TDS_NO_COUNT;
	stmt->row_status = NOT_IN_ROW;

	/* insert into list */
	stmt->next = dbc->stmt_list;
	if (dbc->stmt_list)
		dbc->stmt_list->prev = stmt;
	dbc->stmt_list = stmt;

	tds_mutex_init(&stmt->mtx);
	*phstmt = (SQLHSTMT) stmt;

	if (dbc->attr.cursor_type != SQL_CURSOR_FORWARD_ONLY)
		odbc_SQLSetStmtAttr(stmt, SQL_CURSOR_TYPE, (SQLPOINTER) (TDS_INTPTR) dbc->attr.cursor_type, SQL_IS_INTEGER _wide0);

	stmt->attr.param_focus = 0;

	ODBC_EXIT_(dbc);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLAllocStmt(SQLHDBC hdbc, SQLHSTMT FAR * phstmt)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLAllocStmt(%p, %p)\n", hdbc, phstmt);

	return odbc_SQLAllocStmt(hdbc, phstmt);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLBindCol(SQLHSTMT hstmt, SQLUSMALLINT icol, SQLSMALLINT fCType, SQLPOINTER rgbValue, SQLLEN cbValueMax, SQLLEN FAR * pcbValue)
{
	TDS_DESC *ard;
	struct _drecord *drec;
	SQLSMALLINT orig_ard_size;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLBindCol(%p, %d, %d, %p, %d, %p)\n",
			hstmt, icol, fCType, rgbValue, (int)cbValueMax, pcbValue);

	/* TODO - More error checking XXX smurph */

#ifdef TDS_NO_DM
	/* check conversion type */
	switch (fCType) {
	case SQL_C_CHAR:
	case SQL_C_WCHAR:
	case SQL_C_BINARY:
	case SQL_C_DEFAULT:
		/* check max buffer length */
		if (!IS_VALID_LEN(cbValueMax)) {
			odbc_errs_add(&stmt->errs, "HY090", NULL);
			ODBC_EXIT_(stmt);
		}
		break;
	}
#endif

	if (icol <= 0 || icol > 4000) {
		odbc_errs_add(&stmt->errs, "07009", NULL);
		ODBC_EXIT_(stmt);
	}

	ard = stmt->ard;
	orig_ard_size = ard->header.sql_desc_count;
	if (icol > ard->header.sql_desc_count && desc_alloc_records(ard, icol) != SQL_SUCCESS) {
		odbc_errs_add(&stmt->errs, "HY001", NULL);
		ODBC_EXIT_(stmt);
	}

	drec = &ard->records[icol - 1];

	if (odbc_set_concise_c_type(fCType, drec, 0) != SQL_SUCCESS) {
		desc_alloc_records(ard, orig_ard_size);
		odbc_errs_add(&stmt->errs, "HY003", NULL);
		ODBC_EXIT_(stmt);
	}
	drec->sql_desc_octet_length = cbValueMax;
	drec->sql_desc_octet_length_ptr = pcbValue;
	drec->sql_desc_indicator_ptr = pcbValue;
	drec->sql_desc_data_ptr = rgbValue;

	/* force rebind */
	stmt->row = 0;

	ODBC_EXIT_(stmt);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLCancel(SQLHSTMT hstmt)
{
	TDSSOCKET *tds;

	/*
	 * FIXME this function can be called from other thread, do not free
	 * errors for this function
	 * If function is called from another thread errors are not touched
	 */
	/* TODO some tests required */
	TDS_STMT *stmt = (TDS_STMT*)hstmt;
	if (SQL_NULL_HSTMT == hstmt || !IS_HSTMT(hstmt))
		return SQL_INVALID_HANDLE;

	tdsdump_log(TDS_DBG_FUNC, "SQLCancel(%p)\n", hstmt);

	tds = stmt->tds;

	/* cancelling an inactive statement */
	if (!tds)
		/* Just return success */
		/* Note that we don't own locks so don't use ODBC_EXIT here. */
		return SQL_SUCCESS;

	if (tds_mutex_trylock(&stmt->mtx) == 0) {
		CHECK_STMT_EXTRA(stmt);
		odbc_errs_reset(&stmt->errs);

		/* FIXME test current statement */
		/* FIXME here we are unlocked */

		if (TDS_FAILED(tds_send_cancel(tds))) {
			ODBC_SAFE_ERROR(stmt);
			ODBC_EXIT_(stmt);
		}

		if (TDS_FAILED(tds_process_cancel(tds))) {
			ODBC_SAFE_ERROR(stmt);
			ODBC_EXIT_(stmt);
		}

		/* only if we processed cancel reset statement */
		if (tds->state == TDS_IDLE)
			odbc_unlock_statement(stmt);

		ODBC_EXIT_(stmt);
	}

	/* don't access error here, just return error */
	if (TDS_FAILED(tds_send_cancel(tds)))
		return SQL_ERROR;
	return SQL_SUCCESS;
}

ODBC_FUNC(SQLConnect, (P(SQLHDBC,hdbc), PCHARIN(DSN,SQLSMALLINT), PCHARIN(UID,SQLSMALLINT),
	PCHARIN(AuthStr,SQLSMALLINT) WIDE))
{
	TDSLOGIN *login;
	DSTR *s;

	ODBC_ENTER_HDBC;

#ifdef TDS_NO_DM
	if (szDSN && !IS_VALID_LEN(cbDSN)) {
		odbc_errs_add(&dbc->errs, "HY090", "Invalid DSN buffer length");
		ODBC_EXIT_(dbc);
	}

	if (szUID && !IS_VALID_LEN(cbUID)) {
		odbc_errs_add(&dbc->errs, "HY090", "Invalid UID buffer length");
		ODBC_EXIT_(dbc);
	}

	if (szAuthStr && !IS_VALID_LEN(cbAuthStr)) {
		odbc_errs_add(&dbc->errs, "HY090", "Invalid PWD buffer length");
		ODBC_EXIT_(dbc);
	}
#endif

	login = tds_alloc_login(false);
	if (!login || !tds_init_login(login, dbc->env->tds_ctx->locale))
		goto memory_error;

	/* data source name */
	if (odbc_get_string_size(cbDSN, szDSN _wide))
		s = odbc_dstr_copy(dbc, &dbc->dsn, cbDSN, szDSN);
	else
		s = tds_dstr_copy(&dbc->dsn, "DEFAULT");
	if (!s)
		goto memory_error;


	if (!odbc_get_dsn_info(&dbc->errs, tds_dstr_cstr(&dbc->dsn), login)) {
		tds_free_login(login);
		ODBC_EXIT_(dbc);
	}

	if (!tds_dstr_isempty(&dbc->attr.current_catalog))
		if (!tds_dstr_dup(&login->database, &dbc->attr.current_catalog))
			goto memory_error;

	/*
	 * username/password are never saved to ini file,
	 * so you do not check in ini file
	 */
	/* user id */
	if (odbc_get_string_size(cbUID, szUID _wide)) {
		if (!odbc_dstr_copy(dbc, &login->user_name, cbUID, szUID))
			goto memory_error;
	}

	/* password */
	if (szAuthStr && !tds_dstr_isempty(&login->user_name)) {
		if (!odbc_dstr_copy(dbc, &login->password, cbAuthStr, szAuthStr))
			goto memory_error;
	}

	/* DO IT */
	odbc_connect(dbc, login);

	tds_free_login(login);
	ODBC_EXIT_(dbc);

memory_error:
	tds_free_login(login);
	odbc_errs_add(&dbc->errs, "HY001", NULL);
	ODBC_EXIT_(dbc);
}

ODBC_FUNC(SQLDescribeCol, (P(SQLHSTMT,hstmt), P(SQLUSMALLINT,icol), PCHAROUT(ColName,SQLSMALLINT),
	P(SQLSMALLINT FAR *,pfSqlType), P(SQLULEN FAR *,pcbColDef),
	P(SQLSMALLINT FAR *,pibScale), P(SQLSMALLINT FAR *,pfNullable) WIDE))
{
	TDS_DESC *ird;
	struct _drecord *drec;
	SQLRETURN result;

	ODBC_ENTER_HSTMT;

	ird = stmt->ird;
	IRD_UPDATE(ird, &stmt->errs, ODBC_EXIT(stmt, SQL_ERROR));

	if (icol <= 0 || icol > ird->header.sql_desc_count) {
		odbc_errs_add(&stmt->errs, "07009", "Column out of range");
		ODBC_EXIT_(stmt);
	}
	/* check name length */
	if (cbColNameMax < 0) {
		odbc_errs_add(&stmt->errs, "HY090", NULL);
		ODBC_EXIT_(stmt);
	}
	drec = &ird->records[icol - 1];

	/* cbColNameMax can be 0 (to retrieve name length) */
	if (szColName == NULL)
		cbColNameMax = 0;

	/* straight copy column name up to cbColNameMax */
	result = odbc_set_dstr(stmt->dbc, szColName, cbColNameMax, pcbColName, &drec->sql_desc_label);
	if (szColName && result == SQL_SUCCESS_WITH_INFO)
		odbc_errs_add(&stmt->errs, "01004", NULL);

	if (pfSqlType) {
		/* TODO sure ? check documentation for date and intervals */
		*pfSqlType = drec->sql_desc_concise_type;
	}

	if (pcbColDef) {
		if (drec->sql_desc_type == SQL_NUMERIC || drec->sql_desc_type == SQL_DECIMAL) {
			*pcbColDef = drec->sql_desc_precision;
		} else {
			*pcbColDef = drec->sql_desc_length;
		}
	}
	if (pibScale) {
		*pibScale = drec->sql_desc_scale;
	}
	if (pfNullable) {
		*pfNullable = drec->sql_desc_nullable;
	}
	ODBC_EXIT_(stmt);
}

static SQLRETURN
odbc_SQLColAttribute(SQLHSTMT hstmt, SQLUSMALLINT icol, SQLUSMALLINT fDescType, SQLPOINTER rgbDesc, SQLSMALLINT cbDescMax,
		 SQLSMALLINT FAR * pcbDesc, SQLLEN FAR * pfDesc _WIDE)
{
	TDS_DESC *ird;
	struct _drecord *drec;
	SQLRETURN result = SQL_SUCCESS;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLColAttribute(%p, %u, %u, %p, %d, %p, %p)\n",
			hstmt, icol, fDescType, rgbDesc, cbDescMax, pcbDesc, pfDesc);

	ird = stmt->ird;

#define COUT(src) result = odbc_set_string_oct(stmt->dbc, rgbDesc, cbDescMax, pcbDesc, src ? src : "", -1);
#define SOUT(src) result = odbc_set_dstr_oct(stmt->dbc, rgbDesc, cbDescMax, pcbDesc, &src);

/* SQLColAttribute returns always attributes using SQLINTEGER */
#if ENABLE_EXTRA_CHECKS
#define IOUT(type, src) do { \
	/* trick warning if type wrong */ \
	type *p_test = &src; p_test = p_test; \
	*pfDesc = src; } while(0)
#else
#define IOUT(type, src) *pfDesc = src
#endif

	IRD_UPDATE(ird, &stmt->errs, ODBC_EXIT(stmt, SQL_ERROR));

	/* dont check column index for these */
	switch (fDescType) {
#if SQL_COLUMN_COUNT != SQL_DESC_COUNT
	case SQL_COLUMN_COUNT:
#endif
	case SQL_DESC_COUNT:
		IOUT(SQLSMALLINT, ird->header.sql_desc_count);
		ODBC_EXIT(stmt, SQL_SUCCESS);
		break;
	}

	if (!ird->header.sql_desc_count) {
		odbc_errs_add(&stmt->errs, "07005", NULL);
		ODBC_EXIT_(stmt);
	}

	if (icol <= 0 || icol > ird->header.sql_desc_count) {
		odbc_errs_add(&stmt->errs, "07009", "Column out of range");
		ODBC_EXIT_(stmt);
	}
	drec = &ird->records[icol - 1];

	tdsdump_log(TDS_DBG_INFO1, "SQLColAttribute: fDescType is %d\n", fDescType);

	switch (fDescType) {
	case SQL_DESC_AUTO_UNIQUE_VALUE:
		IOUT(SQLUINTEGER, drec->sql_desc_auto_unique_value);
		break;
	case SQL_DESC_BASE_COLUMN_NAME:
		SOUT(drec->sql_desc_base_column_name);
		break;
	case SQL_DESC_BASE_TABLE_NAME:
		SOUT(drec->sql_desc_base_table_name);
		break;
	case SQL_DESC_CASE_SENSITIVE:
		IOUT(SQLINTEGER, drec->sql_desc_case_sensitive);
		break;
	case SQL_DESC_CATALOG_NAME:
		SOUT(drec->sql_desc_catalog_name);
		break;
#if SQL_COLUMN_TYPE != SQL_DESC_CONCISE_TYPE
	case SQL_COLUMN_TYPE:
#endif
	case SQL_DESC_CONCISE_TYPE:
		/* special case, get ODBC 2 type, not ODBC 3 SQL_DESC_CONCISE_TYPE (different for datetime) */
		if (stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3) {
			IOUT(SQLSMALLINT, drec->sql_desc_concise_type);
			break;
		}

		/* get type and convert it to ODBC 2 type */
		{
			SQLSMALLINT type = drec->sql_desc_concise_type;

			switch (type) {
			case SQL_TYPE_DATE:
				type = SQL_DATE;
				break;
			case SQL_TYPE_TIME:
				type = SQL_TIME;
				break;
			case SQL_TYPE_TIMESTAMP:
				type = SQL_TIMESTAMP;
				break;
			}
			IOUT(SQLSMALLINT, type);
		}
		break;
	case SQL_DESC_DISPLAY_SIZE:
		IOUT(SQLLEN, drec->sql_desc_display_size);
		break;
	case SQL_DESC_FIXED_PREC_SCALE:
		IOUT(SQLSMALLINT, drec->sql_desc_fixed_prec_scale);
		break;
	case SQL_DESC_LABEL:
		SOUT(drec->sql_desc_label);
		break;
		/* FIXME special cases for SQL_COLUMN_LENGTH */
	case SQL_COLUMN_LENGTH:
		IOUT(SQLLEN, drec->sql_desc_octet_length);
		break;
	case SQL_DESC_LENGTH:
		IOUT(SQLULEN, drec->sql_desc_length);
		break;
	case SQL_DESC_LITERAL_PREFIX:
		COUT(drec->sql_desc_literal_prefix);
		break;
	case SQL_DESC_LITERAL_SUFFIX:
		COUT(drec->sql_desc_literal_suffix);
		break;
	case SQL_DESC_LOCAL_TYPE_NAME:
		SOUT(drec->sql_desc_local_type_name);
		break;
#if SQL_COLUMN_NAME != SQL_DESC_NAME
	case SQL_COLUMN_NAME:
#endif
	case SQL_DESC_NAME:
		SOUT(drec->sql_desc_name);
		break;
#if SQL_COLUMN_NULLABLE != SQL_DESC_NULLABLE
	case SQL_COLUMN_NULLABLE:
#endif
	case SQL_DESC_NULLABLE:
		IOUT(SQLSMALLINT, drec->sql_desc_nullable);
		break;
	case SQL_DESC_NUM_PREC_RADIX:
		IOUT(SQLINTEGER, drec->sql_desc_num_prec_radix);
		break;
	case SQL_DESC_OCTET_LENGTH:
		IOUT(SQLLEN, drec->sql_desc_octet_length);
		break;
		/* FIXME special cases for SQL_COLUMN_PRECISION */
	case SQL_COLUMN_PRECISION:
		if (drec->sql_desc_concise_type == SQL_REAL) {
			*pfDesc = 7;
			break;
		}
		if (drec->sql_desc_concise_type == SQL_DOUBLE) {
			*pfDesc = 15;
			break;
		}
		if (drec->sql_desc_concise_type == SQL_TYPE_TIMESTAMP || drec->sql_desc_concise_type == SQL_TIMESTAMP) {
			*pfDesc = drec->sql_desc_precision ? 23 : 16;
			break;
		}
	case SQL_DESC_PRECISION:	/* this section may be wrong */
		if (drec->sql_desc_concise_type == SQL_NUMERIC || drec->sql_desc_concise_type == SQL_DECIMAL
		    || drec->sql_desc_concise_type == SQL_TYPE_TIMESTAMP
		    || drec->sql_desc_concise_type == SQL_TYPE_DATE
		    || drec->sql_desc_concise_type == SQL_TYPE_TIME
		    || drec->sql_desc_concise_type == SQL_TIMESTAMP
		    || drec->sql_desc_concise_type == SQL_SS_TIME2
		    || drec->sql_desc_concise_type == SQL_SS_TIMESTAMPOFFSET)
			IOUT(SQLSMALLINT, drec->sql_desc_precision);
		else
			*pfDesc = drec->sql_desc_length;
		break;
		/* FIXME special cases for SQL_COLUMN_SCALE */
	case SQL_COLUMN_SCALE:
	case SQL_DESC_SCALE:	/* this section may be wrong */
		if (drec->sql_desc_concise_type == SQL_NUMERIC || drec->sql_desc_concise_type == SQL_DECIMAL
		    || drec->sql_desc_concise_type == SQL_TYPE_TIMESTAMP
		    || drec->sql_desc_concise_type == SQL_TYPE_DATE
		    || drec->sql_desc_concise_type == SQL_TYPE_TIME
		    || drec->sql_desc_concise_type == SQL_TIMESTAMP
		    || drec->sql_desc_concise_type == SQL_FLOAT
		    || drec->sql_desc_concise_type == SQL_SS_TIME2
		    || drec->sql_desc_concise_type == SQL_SS_TIMESTAMPOFFSET)
			IOUT(SQLSMALLINT, drec->sql_desc_scale);
		else
			*pfDesc = 0;
		break;
	case SQL_DESC_SCHEMA_NAME:
		SOUT(drec->sql_desc_schema_name);
		break;
	case SQL_DESC_SEARCHABLE:
		IOUT(SQLSMALLINT, drec->sql_desc_searchable);
		break;
	case SQL_DESC_TABLE_NAME:
		SOUT(drec->sql_desc_table_name);
		break;
	case SQL_DESC_TYPE:
		IOUT(SQLSMALLINT, drec->sql_desc_type);
		break;
	case SQL_DESC_TYPE_NAME:
		COUT(drec->sql_desc_type_name);
		break;
	case SQL_DESC_UNNAMED:
		IOUT(SQLSMALLINT, drec->sql_desc_unnamed);
		break;
	case SQL_DESC_UNSIGNED:
		IOUT(SQLSMALLINT, drec->sql_desc_unsigned);
		break;
	case SQL_DESC_UPDATABLE:
		IOUT(SQLSMALLINT, drec->sql_desc_updatable);
		break;
	default:
		tdsdump_log(TDS_DBG_INFO2, "SQLColAttribute: fDescType %d not catered for...\n", fDescType);
		odbc_errs_add(&stmt->errs, "HY091", NULL);
		ODBC_EXIT_(stmt);
		break;
	}

	if (result == SQL_SUCCESS_WITH_INFO)
		odbc_errs_add(&stmt->errs, "01004", NULL);

	ODBC_EXIT(stmt, result);

#undef COUT
#undef SOUT
#undef IOUT
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLColAttributes(SQLHSTMT hstmt, SQLUSMALLINT icol, SQLUSMALLINT fDescType,
		 SQLPOINTER rgbDesc, SQLSMALLINT cbDescMax, SQLSMALLINT FAR * pcbDesc, SQLLEN FAR * pfDesc)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLColAttributes(%p, %d, %d, %p, %d, %p, %p)\n",
			hstmt, icol, fDescType, rgbDesc, cbDescMax, pcbDesc, pfDesc);

	return odbc_SQLColAttribute(hstmt, icol, fDescType, rgbDesc, cbDescMax, pcbDesc, pfDesc _wide0);
}

#if (ODBCVER >= 0x0300)
SQLRETURN ODBC_PUBLIC ODBC_API
SQLColAttribute(SQLHSTMT hstmt, SQLUSMALLINT icol, SQLUSMALLINT fDescType,
		SQLPOINTER rgbDesc, SQLSMALLINT cbDescMax, SQLSMALLINT FAR * pcbDesc,
#ifdef TDS_SQLCOLATTRIBUTE_SQLLEN
		SQLLEN FAR * pfDesc
#else
		SQLPOINTER pfDesc
#endif
	)
{

	return odbc_SQLColAttribute(hstmt, icol, fDescType, rgbDesc, cbDescMax, pcbDesc, pfDesc _wide0);
}

#ifdef ENABLE_ODBC_WIDE
SQLRETURN ODBC_PUBLIC ODBC_API
SQLColAttributeW(SQLHSTMT hstmt, SQLUSMALLINT icol, SQLUSMALLINT fDescType,
		SQLPOINTER rgbDesc, SQLSMALLINT cbDescMax, SQLSMALLINT FAR * pcbDesc,
#ifdef TDS_SQLCOLATTRIBUTE_SQLLEN
		SQLLEN FAR * pfDesc
#else
		SQLPOINTER pfDesc
#endif
	)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLColAttributeW(%p, %u, %u, %p, %d, %p, %p)\n",
			hstmt, icol, fDescType, rgbDesc, cbDescMax, pcbDesc, pfDesc);

	return odbc_SQLColAttribute(hstmt, icol, fDescType, rgbDesc, cbDescMax, pcbDesc, pfDesc, 1);
}
#endif
#endif

SQLRETURN ODBC_PUBLIC ODBC_API
SQLDisconnect(SQLHDBC hdbc)
{
	int i;

	ODBC_ENTER_HDBC;

	tdsdump_log(TDS_DBG_FUNC, "SQLDisconnect(%p)\n", hdbc);

	/* free all associated statements */
	while (dbc->stmt_list) {
		tds_mutex_unlock(&dbc->mtx);
		odbc_SQLFreeStmt(dbc->stmt_list, SQL_DROP, 1);
		tds_mutex_lock(&dbc->mtx);
	}

	/* free all associated descriptors */
	for (i = 0; i < TDS_MAX_APP_DESC; ++i) {
		if (dbc->uad[i]) {
			desc_free(dbc->uad[i]);
			dbc->uad[i] = NULL;
		}
	}

#ifdef ENABLE_ODBC_WIDE
	dbc->mb_conv = NULL;
#endif
	tds_close_socket(dbc->tds_socket);
	tds_free_socket(dbc->tds_socket);
	dbc->tds_socket = NULL;
	dbc->cursor_support = 0;

	ODBC_EXIT_(dbc);
}

static int
odbc_errmsg_handler(const TDSCONTEXT * ctx, TDSSOCKET * tds, TDSMESSAGE * msg)
{
	struct _sql_errors *errs = NULL;
	TDS_DBC *dbc = NULL;
	TDS_STMT *stmt = NULL;

	tdsdump_log(TDS_DBG_INFO1, "msgno %d %d\n", (int) msg->msgno, TDSETIME);

	if (msg->msgno == TDSETIME) {

		tdsdump_log(TDS_DBG_INFO1, "in timeout\n");
		if (!tds)
			return TDS_INT_CANCEL;

		if ((stmt = odbc_get_stmt(tds)) != NULL) {
			/* first time, try to send a cancel */
			if (!tds->in_cancel) {
				odbc_errs_add(&stmt->errs, "HYT00", "Timeout expired");
				tdsdump_log(TDS_DBG_INFO1, "returning from timeout\n");
				return TDS_INT_TIMEOUT;
			}
		} else if ((dbc = odbc_get_dbc(tds)) != NULL) {
			odbc_errs_add(&dbc->errs, "HYT00", "Timeout expired");
		}

		tds_close_socket(tds);
		tdsdump_log(TDS_DBG_INFO1, "returning cancel from timeout\n");
		return TDS_INT_CANCEL;
	}

	if (tds && (dbc = odbc_get_dbc(tds)) != NULL) {
		errs = &dbc->errs;
		stmt = odbc_get_stmt(tds);
		if (stmt)
			errs = &stmt->errs;
	} else if (ctx->parent) {
		errs = &((TDS_ENV *) ctx->parent)->errs;
	}
	if (errs) {
		int severity = msg->severity;
		const char * state = msg->sql_state;

		/* fix severity for Sybase */
		if (severity <= 10 && dbc && !TDS_IS_MSSQL(dbc->tds_socket) && msg->sql_state && msg->sql_state[0]
		    && strncmp(msg->sql_state, "00", 2) != 0) {
			if (strncmp(msg->sql_state, "01", 2) != 0 && strncmp(msg->sql_state, "IM", 2) != 0)
				severity = 11;
		}

		/* compute state if not available */
		if (!state)
			state = severity <= 10 ? "01000" : "42000";
		/* add error, do not overwrite connection timeout error */
		if (msg->msgno != TDSEFCON || errs->lastrc != SQL_ERROR || errs->num_errors < 1)
			odbc_errs_add_rdbms(errs, msg->msgno, state, msg->message, msg->line_number, msg->severity,
					    msg->server, stmt ? stmt->curr_param_row + 1 : 0);

		/* set lastrc according */
		if (severity <= 10) {
			if (errs->lastrc == SQL_SUCCESS)
				errs->lastrc = SQL_SUCCESS_WITH_INFO;
		} else {
			errs->lastrc = SQL_ERROR;
		}
	}
	return TDS_INT_CANCEL;
}

/* TODO optimize, change only if some data change (set same value should not set this flag) */
#define DESC_SET_NEED_REPREPARE \
	do {\
		if (desc->type == DESC_IPD) {\
			assert(IS_HSTMT(desc->parent));\
			((TDS_STMT *) desc->parent)->need_reprepare = 1;\
		 }\
	} while(0)

SQLRETURN ODBC_PUBLIC ODBC_API
SQLSetDescRec(SQLHDESC hdesc, SQLSMALLINT nRecordNumber, SQLSMALLINT nType, SQLSMALLINT nSubType, SQLLEN nLength,
	      SQLSMALLINT nPrecision, SQLSMALLINT nScale, SQLPOINTER pData, SQLLEN FAR * pnStringLength, SQLLEN FAR * pnIndicator)
{
	struct _drecord *drec;
	SQLSMALLINT concise_type;

	ODBC_ENTER_HDESC;

	tdsdump_log(TDS_DBG_FUNC, "SQLSetDescRec(%p, %d, %d, %d, %d, %d, %d, %p, %p, %p)\n",
		    hdesc, nRecordNumber, nType, nSubType, (int)nLength, nPrecision, nScale,
		    pData, pnStringLength, pnIndicator);

	if (desc->type == DESC_IRD) {
		odbc_errs_add(&desc->errs, "HY016", NULL);
		ODBC_EXIT_(desc);
	}

	if (nRecordNumber > desc->header.sql_desc_count || nRecordNumber <= 0) {
		odbc_errs_add(&desc->errs, "07009", NULL);
		ODBC_EXIT_(desc);
	}

	drec = &desc->records[nRecordNumber - 1];

	/* check for valid types and return "HY021" if not */
	if (desc->type == DESC_IPD) {
		DESC_SET_NEED_REPREPARE;
		concise_type = odbc_get_concise_sql_type(nType, nSubType);
	} else {
		concise_type = odbc_get_concise_c_type(nType, nSubType);
	}
	if (nType == SQL_INTERVAL || nType == SQL_DATETIME) {
		if (!concise_type) {
			odbc_errs_add(&desc->errs, "HY021", NULL);
			ODBC_EXIT_(desc);
		}
	} else {
		if (concise_type != nType) {
			odbc_errs_add(&desc->errs, "HY021", NULL);
			ODBC_EXIT_(desc);
		}
		nSubType = 0;
	}
	drec->sql_desc_concise_type = concise_type;
	drec->sql_desc_type = nType;
	drec->sql_desc_datetime_interval_code = nSubType;

	drec->sql_desc_octet_length = nLength;
	drec->sql_desc_precision = nPrecision;
	drec->sql_desc_scale = nScale;
	drec->sql_desc_data_ptr = pData;
	drec->sql_desc_octet_length_ptr = pnStringLength;
	drec->sql_desc_indicator_ptr = pnIndicator;

	ODBC_EXIT_(desc);
}

ODBC_FUNC(SQLGetDescRec, (P(SQLHDESC,hdesc), P(SQLSMALLINT,RecordNumber), PCHAROUT(Name,SQLSMALLINT),
	P(SQLSMALLINT *,Type), P(SQLSMALLINT *,SubType), P(SQLLEN FAR *,Length),
	P(SQLSMALLINT *,Precision), P(SQLSMALLINT *,Scale), P(SQLSMALLINT *,Nullable) WIDE))
{
	struct _drecord *drec = NULL;
	SQLRETURN rc = SQL_SUCCESS;

	ODBC_ENTER_HDESC;

	if (RecordNumber <= 0) {
		odbc_errs_add(&desc->errs, "07009", NULL);
		ODBC_EXIT_(desc);
	}

	IRD_UPDATE(desc, &desc->errs, ODBC_EXIT(desc, SQL_ERROR));
	if (RecordNumber > desc->header.sql_desc_count)
		ODBC_EXIT(desc, SQL_NO_DATA);

	if (desc->type == DESC_IRD && !desc->header.sql_desc_count) {
		odbc_errs_add(&desc->errs, "HY007", NULL);
		ODBC_EXIT_(desc);
	}

	drec = &desc->records[RecordNumber - 1];

	if ((rc = odbc_set_dstr(desc_get_dbc(desc), szName, cbNameMax, pcbName, &drec->sql_desc_name)) != SQL_SUCCESS)
		odbc_errs_add(&desc->errs, "01004", NULL);

	if (Type)
		*Type = drec->sql_desc_type;
	if (Length)
		*Length = drec->sql_desc_octet_length;
	if (Precision)
		*Precision = drec->sql_desc_precision;
	if (Scale)
		*Scale = drec->sql_desc_scale;
	if (SubType)
		*SubType = drec->sql_desc_datetime_interval_code;
	if (Nullable)
		*Nullable = drec->sql_desc_nullable;

	ODBC_EXIT(desc, rc);
}

static TDS_DESC*
desc_get_focused(TDS_DESC *desc)
{
	struct _drecord *drec;
	int focus = desc->focus;
	bool was_apd = false;

	if (focus <= 0)
		return desc;
	if (desc->type != DESC_IPD && IS_HSTMT(desc->parent)) {
		desc = ((TDS_STMT *) desc->parent)->ipd;
		was_apd = true;
	}
	if (desc->type == DESC_IPD) {
		if (focus > desc->header.sql_desc_count)
			return desc;

		drec = &desc->records[focus - 1];
		if (drec->sql_desc_concise_type != SQL_SS_TABLE)
			return desc;
		if (was_apd)
			return ((SQLTVP *) drec->sql_desc_data_ptr)->apd;
		return ((SQLTVP *) drec->sql_desc_data_ptr)->ipd;
	}
	return desc;
}

ODBC_FUNC(SQLGetDescField, (P(SQLHDESC,hdesc), P(SQLSMALLINT,icol), P(SQLSMALLINT,fDescType), P(SQLPOINTER,Value),
	P(SQLINTEGER,BufferLength), P(SQLINTEGER *,StringLength) WIDE))
{
	struct _drecord *drec;
	SQLRETURN result = SQL_SUCCESS;
	SQLINTEGER dummy_size;
	TDS_DESC *fdesc;

	ODBC_ENTER_HDESC;

#define COUT(src) result = odbc_set_string_oct(desc_get_dbc(desc), Value, BufferLength, StringLength, src, -1);
#define SOUT(src) result = odbc_set_dstr_oct(desc_get_dbc(desc), Value, BufferLength, StringLength, &src);

#if ENABLE_EXTRA_CHECKS
#define IOUT(type, src) do { \
	/* trick warning if type wrong */ \
	type *p_test = &src; p_test = p_test; \
	*((type *)Value) = src; \
	*StringLength = sizeof(type); } while(0)
#else
#define IOUT(type, src) do { \
	*((type *)Value) = src; \
	*StringLength = sizeof(type); } while(0)
#endif

	if (!StringLength)
		StringLength = &dummy_size;

	fdesc = desc_get_focused(desc);

	/* dont check column index for these */
	switch (fDescType) {
	case SQL_DESC_ALLOC_TYPE:
		IOUT(SQLSMALLINT, fdesc->header.sql_desc_alloc_type);
		ODBC_EXIT_(desc);
		break;
	case SQL_DESC_ARRAY_SIZE:
		IOUT(SQLULEN, fdesc->header.sql_desc_array_size);
		ODBC_EXIT_(desc);
		break;
	case SQL_DESC_ARRAY_STATUS_PTR:
		IOUT(SQLUSMALLINT *, fdesc->header.sql_desc_array_status_ptr);
		ODBC_EXIT_(desc);
		break;
	case SQL_DESC_BIND_OFFSET_PTR:
		IOUT(SQLLEN *, fdesc->header.sql_desc_bind_offset_ptr);
		ODBC_EXIT_(desc);
		break;
	case SQL_DESC_BIND_TYPE:
		IOUT(SQLINTEGER, fdesc->header.sql_desc_bind_type);
		ODBC_EXIT_(desc);
		break;
	case SQL_DESC_COUNT:
		IRD_UPDATE(desc, &desc->errs, ODBC_EXIT(desc, SQL_ERROR));
		IOUT(SQLSMALLINT, fdesc->header.sql_desc_count);
		ODBC_EXIT_(desc);
		break;
	case SQL_DESC_ROWS_PROCESSED_PTR:
		IOUT(SQLULEN *, fdesc->header.sql_desc_rows_processed_ptr);
		ODBC_EXIT_(desc);
		break;
	}

	IRD_UPDATE(desc, &desc->errs, ODBC_EXIT(desc, SQL_ERROR));
	if (!fdesc->header.sql_desc_count) {
		odbc_errs_add(&desc->errs, "07005", NULL);
		ODBC_EXIT_(desc);
	}

	if (icol < 1) {
		odbc_errs_add(&desc->errs, "07009", "Column out of range");
		ODBC_EXIT_(desc);
	}
	if (icol > fdesc->header.sql_desc_count)
		ODBC_EXIT(desc, SQL_NO_DATA);
	drec = &fdesc->records[icol - 1];

	tdsdump_log(TDS_DBG_INFO1, "SQLGetDescField: fDescType is %d\n", fDescType);

	switch (fDescType) {
	case SQL_DESC_AUTO_UNIQUE_VALUE:
		IOUT(SQLUINTEGER, drec->sql_desc_auto_unique_value);
		break;
	case SQL_DESC_BASE_COLUMN_NAME:
		SOUT(drec->sql_desc_base_column_name);
		break;
	case SQL_DESC_BASE_TABLE_NAME:
		SOUT(drec->sql_desc_base_table_name);
		break;
	case SQL_DESC_CASE_SENSITIVE:
		IOUT(SQLINTEGER, drec->sql_desc_case_sensitive);
		break;
	case SQL_DESC_CATALOG_NAME:
		SOUT(drec->sql_desc_catalog_name);
		break;
	case SQL_DESC_CONCISE_TYPE:
		IOUT(SQLSMALLINT, drec->sql_desc_concise_type);
		break;
	case SQL_DESC_DATA_PTR:
		IOUT(SQLPOINTER, drec->sql_desc_data_ptr);
		break;
	case SQL_DESC_DATETIME_INTERVAL_CODE:
		IOUT(SQLSMALLINT, drec->sql_desc_datetime_interval_code);
		break;
	case SQL_DESC_DATETIME_INTERVAL_PRECISION:
		IOUT(SQLINTEGER, drec->sql_desc_datetime_interval_precision);
		break;
	case SQL_DESC_DISPLAY_SIZE:
		IOUT(SQLLEN, drec->sql_desc_display_size);
		break;
	case SQL_DESC_FIXED_PREC_SCALE:
		IOUT(SQLSMALLINT, drec->sql_desc_fixed_prec_scale);
		break;
	case SQL_DESC_INDICATOR_PTR:
		IOUT(SQLLEN *, drec->sql_desc_indicator_ptr);
		break;
	case SQL_DESC_LABEL:
		SOUT(drec->sql_desc_label);
		break;
	case SQL_DESC_LENGTH:
		IOUT(SQLULEN, drec->sql_desc_length);
		break;
	case SQL_DESC_LITERAL_PREFIX:
		COUT(drec->sql_desc_literal_prefix);
		break;
	case SQL_DESC_LITERAL_SUFFIX:
		COUT(drec->sql_desc_literal_suffix);
		break;
	case SQL_DESC_LOCAL_TYPE_NAME:
		SOUT(drec->sql_desc_local_type_name);
		break;
	case SQL_DESC_NAME:
		SOUT(drec->sql_desc_name);
		break;
	case SQL_DESC_NULLABLE:
		IOUT(SQLSMALLINT, drec->sql_desc_nullable);
		break;
	case SQL_DESC_NUM_PREC_RADIX:
		IOUT(SQLINTEGER, drec->sql_desc_num_prec_radix);
		break;
	case SQL_DESC_OCTET_LENGTH:
		IOUT(SQLLEN, drec->sql_desc_octet_length);
		break;
	case SQL_DESC_OCTET_LENGTH_PTR:
		IOUT(SQLLEN *, drec->sql_desc_octet_length_ptr);
		break;
	case SQL_DESC_PARAMETER_TYPE:
		IOUT(SQLSMALLINT, drec->sql_desc_parameter_type);
		break;
	case SQL_DESC_PRECISION:
		if (drec->sql_desc_concise_type == SQL_NUMERIC || drec->sql_desc_concise_type == SQL_DECIMAL
		    || drec->sql_desc_concise_type == SQL_TIMESTAMP
		    || drec->sql_desc_concise_type == SQL_TYPE_TIMESTAMP)
			IOUT(SQLSMALLINT, drec->sql_desc_precision);
		else
			/* TODO support date/time */
			*((SQLSMALLINT *) Value) = 0;
		break;
#ifdef SQL_DESC_ROWVER
	case SQL_DESC_ROWVER:
		IOUT(SQLSMALLINT, drec->sql_desc_rowver);
		break;
#endif
	case SQL_DESC_SCALE:
		if (drec->sql_desc_concise_type == SQL_NUMERIC || drec->sql_desc_concise_type == SQL_DECIMAL
		    || drec->sql_desc_concise_type == SQL_TYPE_TIMESTAMP
		    || drec->sql_desc_concise_type == SQL_TIMESTAMP
		    || drec->sql_desc_concise_type == SQL_FLOAT)
			IOUT(SQLSMALLINT, drec->sql_desc_scale);
		else
			*((SQLSMALLINT *) Value) = 0;
		break;
	case SQL_DESC_SCHEMA_NAME:
		SOUT(drec->sql_desc_schema_name);
		break;
	case SQL_DESC_SEARCHABLE:
		IOUT(SQLSMALLINT, drec->sql_desc_searchable);
		break;
	case SQL_DESC_TABLE_NAME:
		SOUT(drec->sql_desc_table_name);
		break;
	case SQL_DESC_TYPE:
		IOUT(SQLSMALLINT, drec->sql_desc_type);
		break;
	case SQL_DESC_TYPE_NAME:
		COUT(drec->sql_desc_type_name);
		break;
	case SQL_DESC_UNNAMED:
		IOUT(SQLSMALLINT, drec->sql_desc_unnamed);
		break;
	case SQL_DESC_UNSIGNED:
		IOUT(SQLSMALLINT, drec->sql_desc_unsigned);
		break;
	case SQL_DESC_UPDATABLE:
		IOUT(SQLSMALLINT, drec->sql_desc_updatable);
		break;
	default:
		odbc_errs_add(&desc->errs, "HY091", NULL);
		ODBC_EXIT_(desc);
		break;
	}

	if (result == SQL_SUCCESS_WITH_INFO)
		odbc_errs_add(&desc->errs, "01004", NULL);

	ODBC_EXIT(desc, result);

#undef COUT
#undef SOUT
#undef IOUT
}

ODBC_FUNC(SQLSetDescField, (P(SQLHDESC,hdesc), P(SQLSMALLINT,icol), P(SQLSMALLINT,fDescType),
	P(SQLPOINTER,Value), P(SQLINTEGER,BufferLength) WIDE))
{
	struct _drecord *drec;
	SQLRETURN result = SQL_SUCCESS;
	TDS_DESC *fdesc;

	ODBC_ENTER_HDESC;

#if ENABLE_EXTRA_CHECKS
#define IIN(type, dest) do { \
	/* trick warning if type wrong */ \
	type *p_test = &dest; p_test = p_test; \
	dest = (type)(TDS_INTPTR)Value; } while(0)
#define PIN(type, dest) do { \
	/* trick warning if type wrong */ \
	type *p_test = &dest; p_test = p_test; \
	dest = (type)Value; } while(0)
#else
#define IIN(type, dest) dest = (type)(TDS_INTPTR)Value
#define PIN(type, dest) dest = (type)Value
#endif

	/* special case for IRD */
	if (desc->type == DESC_IRD && fDescType != SQL_DESC_ARRAY_STATUS_PTR && fDescType != SQL_DESC_ROWS_PROCESSED_PTR) {
		odbc_errs_add(&desc->errs, "HY016", NULL);
		ODBC_EXIT_(desc);
	}

	fdesc = desc_get_focused(desc);

	/* dont check column index for these */
	switch (fDescType) {
	case SQL_DESC_ALLOC_TYPE:
		odbc_errs_add(&desc->errs, "HY091", "Descriptor type read only");
		ODBC_EXIT_(desc);
		break;
	case SQL_DESC_ARRAY_SIZE:
		IIN(SQLULEN, fdesc->header.sql_desc_array_size);
		ODBC_EXIT_(desc);
		break;
	case SQL_DESC_ARRAY_STATUS_PTR:
		PIN(SQLUSMALLINT *, fdesc->header.sql_desc_array_status_ptr);
		ODBC_EXIT_(desc);
		break;
	case SQL_DESC_ROWS_PROCESSED_PTR:
		PIN(SQLULEN *, fdesc->header.sql_desc_rows_processed_ptr);
		ODBC_EXIT_(desc);
		break;
	case SQL_DESC_BIND_TYPE:
		IIN(SQLINTEGER, fdesc->header.sql_desc_bind_type);
		ODBC_EXIT_(desc);
		break;
	case SQL_DESC_COUNT:
		{
			int n = (int) (TDS_INTPTR) Value;

			if (n < 0 || n > 4000) {
				odbc_errs_add(&desc->errs, "07009", NULL);
				ODBC_EXIT_(desc);
			}
			result = desc_alloc_records(fdesc, n);
			if (result == SQL_ERROR)
				odbc_errs_add(&desc->errs, "HY001", NULL);
			ODBC_EXIT(desc, result);
		}
		break;
	}

	if (!fdesc->header.sql_desc_count) {
		odbc_errs_add(&desc->errs, "07005", NULL);
		ODBC_EXIT_(desc);
	}

	if (icol <= 0 || icol > fdesc->header.sql_desc_count) {
		odbc_errs_add(&desc->errs, "07009", "Column out of range");
		ODBC_EXIT_(desc);
	}
	drec = &fdesc->records[icol - 1];

	tdsdump_log(TDS_DBG_INFO1, "SQLSetDescField: fDescType is %d\n", fDescType);

	switch (fDescType) {
	case SQL_DESC_AUTO_UNIQUE_VALUE:
	case SQL_DESC_BASE_COLUMN_NAME:
	case SQL_DESC_BASE_TABLE_NAME:
	case SQL_DESC_CASE_SENSITIVE:
	case SQL_DESC_CATALOG_NAME:
		odbc_errs_add(&desc->errs, "HY091", "Descriptor type read only");
		result = SQL_ERROR;
		break;
	case SQL_DESC_CONCISE_TYPE:
		DESC_SET_NEED_REPREPARE;
		if (fdesc->type == DESC_IPD)
			result = odbc_set_concise_sql_type((SQLSMALLINT) (TDS_INTPTR) Value, drec, 0);
		else
			result = odbc_set_concise_c_type((SQLSMALLINT) (TDS_INTPTR) Value, drec, 0);
		if (result != SQL_SUCCESS)
			odbc_errs_add(&desc->errs, "HY021", NULL);
		break;
	case SQL_DESC_DATA_PTR:
		PIN(SQLPOINTER, drec->sql_desc_data_ptr);
		break;
		/* TODO SQL_DESC_DATETIME_INTERVAL_CODE remember to check sql_desc_type */
		/* TODO SQL_DESC_DATETIME_INTERVAL_PRECISION */
	case SQL_DESC_DISPLAY_SIZE:
	case SQL_DESC_FIXED_PREC_SCALE:
		odbc_errs_add(&desc->errs, "HY091", "Descriptor type read only");
		result = SQL_ERROR;
		break;
	case SQL_DESC_INDICATOR_PTR:
		PIN(SQLLEN *, drec->sql_desc_indicator_ptr);
		break;
	case SQL_DESC_LABEL:
		odbc_errs_add(&desc->errs, "HY091", "Descriptor type read only");
		result = SQL_ERROR;
		break;
	case SQL_DESC_LENGTH:
		DESC_SET_NEED_REPREPARE;
		IIN(SQLULEN, drec->sql_desc_length);
		break;
	case SQL_DESC_LITERAL_PREFIX:
	case SQL_DESC_LITERAL_SUFFIX:
	case SQL_DESC_LOCAL_TYPE_NAME:
		odbc_errs_add(&desc->errs, "HY091", "Descriptor type read only");
		result = SQL_ERROR;
		break;
	case SQL_DESC_NAME:
		if (!odbc_dstr_copy_oct(desc_get_dbc(fdesc), &drec->sql_desc_name, BufferLength, (ODBC_CHAR*) Value)) {
			odbc_errs_add(&desc->errs, "HY001", NULL);
			result = SQL_ERROR;
		}
		break;
	case SQL_DESC_NULLABLE:
		odbc_errs_add(&desc->errs, "HY091", "Descriptor type read only");
		result = SQL_ERROR;
		break;
	case SQL_DESC_NUM_PREC_RADIX:
		IIN(SQLINTEGER, drec->sql_desc_num_prec_radix);
		break;
	case SQL_DESC_OCTET_LENGTH:
		DESC_SET_NEED_REPREPARE;
		IIN(SQLLEN, drec->sql_desc_octet_length);
		break;
	case SQL_DESC_OCTET_LENGTH_PTR:
		PIN(SQLLEN *, drec->sql_desc_octet_length_ptr);
		break;
	case SQL_DESC_PARAMETER_TYPE:
		DESC_SET_NEED_REPREPARE;
		IIN(SQLSMALLINT, drec->sql_desc_parameter_type);
		break;
	case SQL_DESC_PRECISION:
		DESC_SET_NEED_REPREPARE;
		/* TODO correct ?? */
		if (drec->sql_desc_concise_type == SQL_NUMERIC || drec->sql_desc_concise_type == SQL_DECIMAL)
			IIN(SQLSMALLINT, drec->sql_desc_precision);
		else
			IIN(SQLULEN, drec->sql_desc_length);
		break;
#ifdef SQL_DESC_ROWVER
	case SQL_DESC_ROWVER:
		odbc_errs_add(&desc->errs, "HY091", "Descriptor type read only");
		result = SQL_ERROR;
		break;
#endif
	case SQL_DESC_SCALE:
		DESC_SET_NEED_REPREPARE;
		if (drec->sql_desc_concise_type == SQL_NUMERIC || drec->sql_desc_concise_type == SQL_DECIMAL)
			IIN(SQLSMALLINT, drec->sql_desc_scale);
		else
			/* TODO even for datetime/money ?? */
			drec->sql_desc_scale = 0;
		break;
	case SQL_DESC_SCHEMA_NAME:
	case SQL_DESC_SEARCHABLE:
	case SQL_DESC_TABLE_NAME:
		odbc_errs_add(&desc->errs, "HY091", "Descriptor type read only");
		result = SQL_ERROR;
		break;
	case SQL_DESC_TYPE:
		DESC_SET_NEED_REPREPARE;
		IIN(SQLSMALLINT, drec->sql_desc_type);
		/* FIXME what happen for interval/datetime ?? */
		drec->sql_desc_concise_type = drec->sql_desc_type;
		break;
	case SQL_DESC_TYPE_NAME:
		odbc_errs_add(&desc->errs, "HY091", "Descriptor type read only");
		result = SQL_ERROR;
		break;
	case SQL_DESC_UNNAMED:
		IIN(SQLSMALLINT, drec->sql_desc_unnamed);
		break;
	case SQL_DESC_UNSIGNED:
	case SQL_DESC_UPDATABLE:
		odbc_errs_add(&desc->errs, "HY091", "Descriptor type read only");
		result = SQL_ERROR;
		break;
	default:
		odbc_errs_add(&desc->errs, "HY091", NULL);
		ODBC_EXIT_(desc);
		break;
	}

#undef IIN

	ODBC_EXIT(desc, result);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLCopyDesc(SQLHDESC hsrc, SQLHDESC hdesc)
{
	TDS_DESC *src;

	ODBC_ENTER_HDESC;

	tdsdump_log(TDS_DBG_FUNC, "SQLCopyDesc(%p, %p)\n",
			hsrc, hdesc);

	/* check source descriptor handle, destination one was already checked */
	if (SQL_NULL_HDESC == hsrc || !IS_HDESC(hsrc)) {
		tds_mutex_unlock(&desc->mtx);
		return SQL_INVALID_HANDLE;
	}

	src = (TDS_DESC *) hsrc;
	CHECK_DESC_EXTRA(src);

	/* do not write on IRD */
	if (desc->type == DESC_IRD) {
		odbc_errs_add(&desc->errs, "HY016", NULL);
		ODBC_EXIT_(desc);
	}
	IRD_UPDATE(src, &src->errs, ODBC_EXIT(desc, SQL_ERROR));

	ODBC_EXIT(desc, desc_copy(desc, src));
}

#if ENABLE_EXTRA_CHECKS
static void
odbc_ird_check(TDS_STMT * stmt TDS_UNUSED)
{
#if !ENABLE_ODBC_MARS
	TDS_DESC *ird = stmt->ird;
	TDSRESULTINFO *res_info = NULL;
	int cols = 0, i;

	if (!stmt->tds)
		return;
	if (stmt->tds->current_results) {
		res_info = stmt->tds->current_results;
		cols = res_info->num_cols;
	}
	if (stmt->cursor != NULL)
		return;

	/* check columns number */
	assert(ird->header.sql_desc_count <= cols || ird->header.sql_desc_count == 0);


	/* check all columns */
	for (i = 0; i < ird->header.sql_desc_count; ++i) {
		struct _drecord *drec = &ird->records[i];
		TDSCOLUMN *col = res_info->columns[i];

		assert(strcmp(tds_dstr_cstr(&drec->sql_desc_label), tds_dstr_cstr(&col->column_name)) == 0);
	}
#endif
}
#endif

static void
odbc_unquote(char *buf, size_t buf_len, const char *start, const char *end)
{
	char quote;
	assert(buf_len > 0);

	/* empty string */
	if (start >= end) {
		buf[0] = 0;
		return;
	}

	/* not quoted */
	if (*start != '[' && *start != '\"') {
		--buf_len;
		if (end < start + buf_len)
			buf_len = end - start;
		memcpy(buf, start, buf_len);
		buf[buf_len] = 0;
		return;
	}

	/* quoted... unquote */
	quote = (*start == '[') ? ']' : *start;
	++start;
	while (buf_len > 0 && start < end) {
		if (*start == quote)
			if (++start >= end)
				break;
		*buf++ = *start++;
		--buf_len;
	}
	*buf = 0;
}

/* FIXME check result !!! */
static SQLRETURN
odbc_populate_ird(TDS_STMT * stmt)
{
	TDS_DESC *ird = stmt->ird;
	struct _drecord *drec;
	TDSCOLUMN *col;
	TDSRESULTINFO *res_info;
	int num_cols;
	int i;

	desc_free_records(ird);
	if (!stmt->tds || !(res_info = stmt->tds->current_results))
		return SQL_SUCCESS;
	if (res_info == stmt->tds->param_info)
		return SQL_SUCCESS;
	num_cols = res_info->num_cols;

	/* ignore hidden columns... TODO correct? */
	while (num_cols > 0 && res_info->columns[num_cols - 1]->column_hidden == 1)
		--num_cols;

	if (desc_alloc_records(ird, num_cols) != SQL_SUCCESS)
		goto memory_error;

	for (i = 0; i < num_cols; i++) {
		drec = &ird->records[i];
		col = res_info->columns[i];
		drec->sql_desc_auto_unique_value = col->column_identity ? SQL_TRUE : SQL_FALSE;
		/* TODO SQL_FALSE ?? */
		drec->sql_desc_case_sensitive = SQL_TRUE;

		/*
		 * TODO how to handle when in datetime we change precision ??
		 * should we change display size too ??
		 * is formatting function correct ??
		 * we should not convert to string with invalid precision!
		 */
		odbc_set_sql_type_info(col, drec, stmt->dbc->env->attr.odbc_version);

		drec->sql_desc_fixed_prec_scale = (col->column_prec && col->column_scale) ? SQL_TRUE : SQL_FALSE;
		if (!tds_dstr_dup(&drec->sql_desc_label, &col->column_name))
			goto memory_error;

		if (tds_dstr_isempty(&col->table_column_name)) {
			if (!tds_dstr_dup(&drec->sql_desc_name, &col->column_name))
				goto memory_error;
		} else {
			if (!tds_dstr_dup(&drec->sql_desc_name, &col->table_column_name))
				goto memory_error;
			if (!tds_dstr_dup(&drec->sql_desc_base_column_name, &col->table_column_name))
				goto memory_error;
		}

		/* extract sql_desc_(catalog/schema/base_table)_name */
		/* TODO extract them dinamically (when needed) ? */
		/* TODO store in libTDS in different way (separately) ? */
		if (!tds_dstr_isempty(&col->table_name)) {
			struct {
				const char *start;
				const char *end;
			} partials[4];
			const char *p;
			char buf[256];
			int i;

			p = tds_dstr_cstr(&col->table_name);
			for (i = 0; ; ++i) {
				const char *pend;

				if (*p == '[' || *p == '\"') {
					pend = tds_skip_quoted(p);
				} else {
					pend = strchr(p, '.');
					if (!pend)
						pend = strchr(p, 0);
				}
				partials[i].start = p;
				partials[i].end = pend;
				p = pend;
				if (i == 3 || *p != '.')
					break;
				++p;
			}

			/* here i points to last element */
			odbc_unquote(buf, sizeof(buf), partials[i].start, partials[i].end);
			if (!tds_dstr_copy(&drec->sql_desc_base_table_name, buf))
				goto memory_error;

			--i;
			if (i >= 0) {
				odbc_unquote(buf, sizeof(buf), partials[i].start, partials[i].end);
				if (!tds_dstr_copy(&drec->sql_desc_schema_name, buf))
					goto memory_error;
			}

			--i;
			if (i >= 0) {
				odbc_unquote(buf, sizeof(buf), partials[i].start, partials[i].end);
				if (!tds_dstr_copy(&drec->sql_desc_catalog_name, buf))
					goto memory_error;
			}
		}

		drec->sql_desc_unnamed = tds_dstr_isempty(&drec->sql_desc_name) ? SQL_UNNAMED : SQL_NAMED;
		/* TODO use is_nullable_type ?? */
		drec->sql_desc_nullable = col->column_nullable ? SQL_TRUE : SQL_FALSE;

		drec->sql_desc_octet_length_ptr = NULL;
		/* TODO test timestamp from db, FOR BROWSE query */
		drec->sql_desc_rowver = SQL_FALSE;
		/* TODO seem not correct */
		drec->sql_desc_searchable = (drec->sql_desc_unnamed == SQL_NAMED) ? SQL_PRED_SEARCHABLE : SQL_UNSEARCHABLE;
		drec->sql_desc_updatable = col->column_writeable && !col->column_identity ? SQL_TRUE : SQL_FALSE;
	}
	return SQL_SUCCESS;

memory_error:
	odbc_errs_add(&stmt->errs, "HY001", NULL);
	return SQL_ERROR;
}

static TDSRET
odbc_cursor_execute(TDS_STMT * stmt)
{
	TDSSOCKET *tds = stmt->tds;
	int i;
	bool send = false;
	TDSRET ret;
	TDSCURSOR *cursor;
	TDSPARAMINFO *params = stmt->params;

	assert(tds);
	assert(stmt->attr.cursor_type != SQL_CURSOR_FORWARD_ONLY || stmt->attr.concurrency != SQL_CONCUR_READ_ONLY);

	tds_release_cursor(&stmt->cursor);
	cursor = tds_alloc_cursor(tds, tds_dstr_cstr(&stmt->cursor_name), tds_dstr_len(&stmt->cursor_name),
			tds_dstr_cstr(&stmt->query), tds_dstr_len(&stmt->query));
	if (!cursor) {
		odbc_unlock_statement(stmt);

		odbc_errs_add(&stmt->errs, "HY001", NULL);
		return TDS_FAIL;
	}
	stmt->cursor = cursor;

	/* TODO cursor add enums for tds7 */
	switch (stmt->attr.cursor_type) {
	default:
	case SQL_CURSOR_FORWARD_ONLY:
		i = TDS_CUR_TYPE_FORWARD;
		break;
	case SQL_CURSOR_STATIC:
		i = TDS_CUR_TYPE_STATIC;
		break;
	case SQL_CURSOR_KEYSET_DRIVEN:
		i = TDS_CUR_TYPE_KEYSET;
		break;
	case SQL_CURSOR_DYNAMIC:
		i = TDS_CUR_TYPE_DYNAMIC;
		break;
	}
	cursor->type = i;

	switch (stmt->attr.concurrency) {
	default:
	case SQL_CONCUR_READ_ONLY:
		i = TDS_CUR_CONCUR_READ_ONLY;
		break;
	case SQL_CONCUR_LOCK:
		i = TDS_CUR_CONCUR_SCROLL_LOCKS;
		break;
	case SQL_CONCUR_ROWVER:
		i = TDS_CUR_CONCUR_OPTIMISTIC;
		break;
	case SQL_CONCUR_VALUES:
		i = TDS_CUR_CONCUR_OPTIMISTIC_VALUES;
		break;
	}
	cursor->concurrency = 0x2000 | i;

	ret = tds_cursor_declare(tds, cursor, &send);
	if (TDS_FAILED(ret))
		return ret;
	ret = tds_cursor_open(tds, cursor, params, &send);
	if (TDS_FAILED(ret))
		return ret;
	/* TODO read results, set row count, check type and scroll returned */
	ret = tds_flush_packet(tds);
	tds_set_state(tds, TDS_PENDING);
	/* set cursor name for TDS7+ */
	if (TDS_SUCCEED(ret) && IS_TDS7_PLUS(tds->conn) && !tds_dstr_isempty(&stmt->cursor_name)) {
		ret = odbc_process_tokens(stmt, TDS_RETURN_DONE|TDS_STOPAT_ROW|TDS_STOPAT_COMPUTE);
		stmt->row_count = tds->rows_affected;
		if (ret == TDS_CMD_DONE && cursor->cursor_id != 0) {
			ret = tds_cursor_setname(tds, cursor);
			tds_set_state(tds, TDS_PENDING);
		} else {
			ret = (ret == TDS_CMD_FAIL) ? TDS_FAIL : TDS_SUCCESS;
		}
		if (!cursor->cursor_id) {
			tds_cursor_dealloc(tds, cursor);
			tds_release_cursor(&stmt->cursor);
		}
	}
	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
odbc_SQLExecute(TDS_STMT * stmt)
{
	TDSRET ret;
	TDSSOCKET *tds;
	TDS_INT result_type;
	TDS_INT done = 0;
	bool in_row = false;
	SQLUSMALLINT param_status;
	int found_info = 0, found_error = 0;
	TDS_INT8 total_rows = TDS_NO_COUNT;
	TDSHEADERS head;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLExecute(%p)\n",
			stmt);

	stmt->row = 0;


	/* check parameters are all OK */
	if (stmt->params && stmt->param_num <= stmt->param_count) {
		/* TODO what error ?? */
		ODBC_SAFE_ERROR(stmt);
		return SQL_ERROR;
	}

	if (!odbc_lock_statement(stmt))
		return SQL_ERROR;

	tds = stmt->tds;
	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLExecute() starting with state %d\n", tds->state);

	if (tds->state != TDS_IDLE) {
		if (tds->state == TDS_DEAD) {
			odbc_errs_add(&stmt->errs, "08S01", NULL);
		} else {
			odbc_errs_add(&stmt->errs, "24000", NULL);
		}
		return SQL_ERROR;
	}

	stmt->curr_param_row = 0;
	stmt->num_param_rows = ODBC_MAX(1, stmt->apd->header.sql_desc_array_size);

	stmt->row_count = TDS_NO_COUNT;

	if (stmt->prepared_query_is_rpc) {
		/* TODO support stmt->apd->header.sql_desc_array_size for RPC */
		/* get rpc name */
		/* TODO change method */
		/* TODO cursor change way of calling */
		char *name = tds_dstr_buf(&stmt->query);
		char *end, tmp;

		end = name;
		end = (char *) odbc_skip_rpc_name(end);
		stmt->prepared_pos = end - name;
		tmp = *end;
		*end = 0;
		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);
	} else if (!stmt->is_prepared_query) {
		/* not prepared query */
		/* TODO cursor change way of calling */
		/* SQLExecDirect */
		if (stmt->num_param_rows <= 1) {
			if (!stmt->params) {
				ret = tds_submit_query_params(tds, tds_dstr_cstr(&stmt->query), NULL,
							      odbc_init_headers(stmt, &head));
			} else {
				ret = tds_submit_execdirect(tds, tds_dstr_cstr(&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, odbc_init_headers(stmt, &head));
			for (stmt->curr_param_row = 0; TDS_SUCCEED(ret); ) {
				/* submit a query */
				ret = tds_multiple_query(tds, &multiple, tds_dstr_cstr(&stmt->query), stmt->params);
				if (++stmt->curr_param_row >= stmt->num_param_rows)
					break;
				/* than process others parameters */
				/* TODO handle all results*/
				if (start_parse_prepared_query(stmt, true) != SQL_SUCCESS)
					break;
			}
			if (TDS_SUCCEED(ret))
				ret = tds_multiple_done(tds, &multiple);
		}
	} else if (stmt->num_param_rows <= 1 && IS_TDS71_PLUS(tds->conn) && (!stmt->dyn || stmt->need_reprepare)) {
			if (stmt->dyn) {
				if (odbc_free_dynamic(stmt) != SQL_SUCCESS)
					ODBC_RETURN(stmt, SQL_ERROR);
			}
			stmt->need_reprepare = 0;
			ret = tds71_submit_prepexec(tds, tds_dstr_cstr(&stmt->query), NULL, &stmt->dyn, stmt->params);
	} else {
		/* TODO cursor change way of calling */
		/* SQLPrepare */
		TDSDYNAMIC *dyn;

		/* prepare dynamic query (only for first SQLExecute call) */
		if (!stmt->dyn || (stmt->need_reprepare && !stmt->dyn->emulated && IS_TDS7_PLUS(tds->conn))) {

			/* free previous prepared statement */
			if (stmt->dyn) {
				if (odbc_free_dynamic(stmt) != SQL_SUCCESS)
					ODBC_RETURN(stmt, SQL_ERROR);
			}
			stmt->need_reprepare = 0;

			tdsdump_log(TDS_DBG_INFO1, "Creating prepared statement\n");
			/* TODO use tds_submit_prepexec (mssql2k, tds71) */
			if (TDS_FAILED(tds_submit_prepare(tds, tds_dstr_cstr(&stmt->query), NULL, &stmt->dyn, stmt->params))) {
				/* TODO ?? tds_free_param_results(params); */
				ODBC_SAFE_ERROR(stmt);
				return SQL_ERROR;
			}
			if (TDS_FAILED(tds_process_simple_query(tds))) {
				tds_release_dynamic(&stmt->dyn);
				/* TODO ?? tds_free_param_results(params); */
				ODBC_SAFE_ERROR(stmt);
				return SQL_ERROR;
			}
		}
		stmt->row_count = TDS_NO_COUNT;
		if (stmt->num_param_rows <= 1) {
			dyn = stmt->dyn;
			tds_free_input_params(dyn);
			dyn->params = stmt->params;
			/* prevent double free */
			stmt->params = NULL;
			tdsdump_log(TDS_DBG_INFO1, "End prepare, execute\n");
			/* TODO return error to client */
			ret = tds_submit_execute(tds, dyn);
		} else {
			TDSMULTIPLE multiple;

			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);
				dyn->params = stmt->params;
				/* prevent double free */
				stmt->params = NULL;
				ret = tds_multiple_execute(tds, &multiple, dyn);
				if (++stmt->curr_param_row >= stmt->num_param_rows)
					break;
				/* than process others parameters */
				/* TODO handle all results*/
				if (start_parse_prepared_query(stmt, true) != SQL_SUCCESS)
					break;
			}
			if (TDS_SUCCEED(ret))
				ret = tds_multiple_done(tds, &multiple);
		}
	}
	if (TDS_FAILED(ret)) {
		ODBC_SAFE_ERROR(stmt);
		return SQL_ERROR;
	}
	/* catch all errors */
	if (!odbc_lock_statement(stmt))
		ODBC_RETURN_(stmt);

	stmt->row_status = PRE_NORMAL_ROW;

	stmt->curr_param_row = 0;
	param_status = SQL_PARAM_SUCCESS;

	/* TODO review this, ODBC return parameter in other way, for compute I don't know */
	/* TODO perhaps we should return SQL_NO_DATA if no data available... see old SQLExecute code */
	for (;;) {
		result_type = odbc_process_tokens(stmt, TDS_TOKEN_RESULTS);
		tdsdump_log(TDS_DBG_FUNC, "odbc_SQLExecute: odbc_process_tokens returned result_type %d\n", result_type);
		switch (result_type) {
		case TDS_CMD_FAIL:
		case TDS_CMD_DONE:
		case TDS_COMPUTE_RESULT:
		case TDS_ROW_RESULT:
			done = 1;
			break;

		case TDS_DONE_RESULT:
		case TDS_DONEPROC_RESULT:
			switch (stmt->errs.lastrc) {
			case SQL_ERROR:
				found_error = 1;
				param_status = SQL_PARAM_ERROR;
				break;
			case SQL_SUCCESS_WITH_INFO:
				found_info = 1;
				param_status = SQL_PARAM_SUCCESS_WITH_INFO;
				break;
			}
			if (stmt->curr_param_row < stmt->num_param_rows && stmt->ipd->header.sql_desc_array_status_ptr)
				stmt->ipd->header.sql_desc_array_status_ptr[stmt->curr_param_row] = param_status;
			param_status = SQL_PARAM_SUCCESS;
			++stmt->curr_param_row;
			/* actually is quite strange, if prepared always success with info or success
			 * if not prepared return last one
			 */
			if (stmt->curr_param_row < stmt->num_param_rows || result_type == TDS_DONEPROC_RESULT)
				stmt->errs.lastrc = SQL_SUCCESS;
			if (total_rows == TDS_NO_COUNT)
				total_rows = stmt->row_count;
			else if (stmt->row_count != TDS_NO_COUNT)
				total_rows += stmt->row_count;
			stmt->row_count = TDS_NO_COUNT;
			if (stmt->curr_param_row >= stmt->num_param_rows) {
				done = 1;
				break;
			}
			break;

		case TDS_DONEINPROC_RESULT:
			if (in_row)
				done = 1;
			break;

			/* ignore metadata, stop at done or row */
		case TDS_ROWFMT_RESULT:
			if (in_row) {
				done = 1;
				break;
			}
			stmt->row = 0;
			stmt->row_count = TDS_NO_COUNT;
			stmt->row_status = PRE_NORMAL_ROW;
			in_row = true;
			break;
		}
		if (done)
			break;
	}
	if (found_error) {
		/* if the application uses status array it can examine it to determine
		   which rows resulted in errors, but if it doesn't, it has no way to
		   do it and we must return an error and not success with info to it */
		if (stmt->ipd->header.sql_desc_array_status_ptr)
			stmt->errs.lastrc = SQL_SUCCESS_WITH_INFO;
		else
			stmt->errs.lastrc = SQL_ERROR;
	} else if (found_info && 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) {
		if (stmt->ipd->header.sql_desc_array_status_ptr)
			stmt->ipd->header.sql_desc_array_status_ptr[stmt->curr_param_row] = param_status;
		++stmt->curr_param_row;
		if (total_rows == TDS_NO_COUNT)
			total_rows = stmt->row_count;
		else if (stmt->row_count != TDS_NO_COUNT)
			total_rows += stmt->row_count;
		stmt->row_count = TDS_NO_COUNT;
	}
	if (stmt->ipd->header.sql_desc_rows_processed_ptr)
		*stmt->ipd->header.sql_desc_rows_processed_ptr = ODBC_MIN(stmt->curr_param_row, stmt->num_param_rows);

	if (total_rows != TDS_NO_COUNT)
		stmt->row_count = total_rows;

	odbc_populate_ird(stmt);
	switch (result_type) {
	case TDS_CMD_DONE:
		odbc_unlock_statement(stmt);
		if (stmt->errs.lastrc == SQL_SUCCESS && stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3
		    && stmt->row_count == TDS_NO_COUNT && !stmt->cursor && stmt->ird->header.sql_desc_count <= 0)
			ODBC_RETURN(stmt, SQL_NO_DATA);
		break;

	case TDS_CMD_FAIL:
		/* TODO test what happened, report correct error to client */
		tdsdump_log(TDS_DBG_INFO1, "SQLExecute: bad results\n");
		ODBC_SAFE_ERROR(stmt);
		return SQL_ERROR;
	}
	ODBC_RETURN_(stmt);
}

ODBC_FUNC(SQLExecDirect, (P(SQLHSTMT,hstmt), PCHARIN(SqlStr,SQLINTEGER) WIDE))
{
	SQLRETURN res;

	ODBC_ENTER_HSTMT;

	if (SQL_SUCCESS != odbc_set_stmt_query(stmt, szSqlStr, cbSqlStr _wide)) {
		odbc_errs_add(&stmt->errs, "HY001", NULL);
		ODBC_EXIT_(stmt);
	}

	/* count placeholders */
	/* note: szSqlStr can be no-null terminated, so first we set query and then count placeholders */
	stmt->param_count = tds_count_placeholders(tds_dstr_cstr(&stmt->query));
	stmt->param_data_called = 0;

	if (SQL_SUCCESS != prepare_call(stmt)) {
		/* TODO return another better error, prepare_call should set error ?? */
		odbc_errs_add(&stmt->errs, "HY000", "Could not prepare call");
		ODBC_EXIT_(stmt);
	}

	res = start_parse_prepared_query(stmt, true);
	if (SQL_SUCCESS != res)
		ODBC_EXIT(stmt, res);

	ODBC_EXIT(stmt, odbc_SQLExecute(stmt));
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLExecute(SQLHSTMT hstmt)
{
	ODBC_PRRET_BUF;
	SQLRETURN res;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLExecute(%p)\n", hstmt);

	if (!stmt->is_prepared_query) {
		/* TODO error report, only without DM ?? */
		tdsdump_log(TDS_DBG_FUNC, "SQLExecute returns SQL_ERROR (not prepared)\n");
		ODBC_EXIT(stmt, SQL_ERROR);
	}

	/* TODO rebuild should be done for every bindings change, not every time */
	/* TODO free previous parameters */
	/* build parameters list */
	stmt->param_data_called = 0;
	stmt->curr_param_row = 0;
	if ((res = start_parse_prepared_query(stmt, true)) != SQL_SUCCESS) {
		tdsdump_log(TDS_DBG_FUNC, "SQLExecute returns %s (start_parse_prepared_query failed)\n", odbc_prret(res));
		ODBC_EXIT(stmt, res);
	}

	/* TODO test if two SQLPrepare on a statement */
	/* TODO test unprepare on statement free or connection close */

	res = odbc_SQLExecute(stmt);

	tdsdump_log(TDS_DBG_FUNC, "SQLExecute returns %s\n", odbc_prret(res));

	ODBC_EXIT(stmt, res);
}

static int
odbc_process_tokens(TDS_STMT * stmt, unsigned flag)
{
	TDS_INT result_type;
	int done_flags = 0;
	TDSSOCKET * tds = stmt->tds;

	flag |= TDS_RETURN_DONE | TDS_RETURN_PROC;
	for (;;) {
		TDSRET retcode = tds_process_tokens(tds, &result_type, &done_flags, flag);
		tdsdump_log(TDS_DBG_FUNC, "odbc_process_tokens: tds_process_tokens returned %d\n", retcode);
		tdsdump_log(TDS_DBG_FUNC, "    result_type=%d, TDS_DONE_COUNT=%x, TDS_DONE_ERROR=%x\n",
						result_type, (done_flags & TDS_DONE_COUNT), (done_flags & TDS_DONE_ERROR));
		switch (retcode) {
		case TDS_SUCCESS:
			break;
		case TDS_NO_MORE_RESULTS:
			return TDS_CMD_DONE;
		case TDS_CANCELLED:
			odbc_errs_add(&stmt->errs, "HY008", NULL);
		default:
			return TDS_CMD_FAIL;
		}

		switch (result_type) {
		case TDS_STATUS_RESULT:
			odbc_set_return_status(stmt, ODBC_MIN(stmt->curr_param_row, stmt->num_param_rows - 1));
			break;
		case TDS_PARAM_RESULT:
			odbc_set_return_params(stmt, ODBC_MIN(stmt->curr_param_row, stmt->num_param_rows - 1));
			break;

		case TDS_DONE_RESULT:
		case TDS_DONEPROC_RESULT:
			if (stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3)
				flag |= TDS_STOPAT_MSG;
			if (done_flags & TDS_DONE_COUNT) {
				/* correct ?? overwrite ?? */
				if (stmt->row_count == TDS_NO_COUNT)
					stmt->row_count = tds->rows_affected;
			}
			if (done_flags & TDS_DONE_ERROR)
				stmt->errs.lastrc = SQL_ERROR;
			/* test for internal_sp not very fine, used for param set  -- freddy77 */
			if ((done_flags & (TDS_DONE_COUNT|TDS_DONE_ERROR)) != 0
			    || (stmt->errs.lastrc == SQL_SUCCESS_WITH_INFO && stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3)
			    || (result_type == TDS_DONEPROC_RESULT && tds->current_op == TDS_OP_EXECUTE)) {
				/* FIXME this row is used only as a flag for update binding,
				 * should be cleared if binding/result changed */
				stmt->row = 0;
#if 0
				tds_free_all_results(tds);
				odbc_populate_ird(stmt);
#endif
				tdsdump_log(TDS_DBG_FUNC, "odbc_process_tokens: row_count=%" PRId64 "\n", stmt->row_count);
				return result_type;
			}
			tdsdump_log(TDS_DBG_FUNC, "odbc_process_tokens: processed %s\n",
					result_type==TDS_DONE_RESULT? "TDS_DONE_RESULT" : "TDS_DONEPROC_RESULT");
			break;

		/*
		 * TODO test flags ? check error and change result ?
		 * see also other DONEINPROC handle (below)
		 */
		case TDS_DONEINPROC_RESULT:
			if (stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3)
				flag |= TDS_STOPAT_MSG;
			if (done_flags & TDS_DONE_COUNT) {
				stmt->row_count = tds->rows_affected;
			}
			if (done_flags & TDS_DONE_ERROR)
				stmt->errs.lastrc = SQL_ERROR;
			/* TODO perhaps it can be a problem if SET NOCOUNT ON, test it */
#if 0
			tds_free_all_results(tds);
			odbc_populate_ird(stmt);
#endif
			tdsdump_log(TDS_DBG_FUNC, "odbc_process_tokens: processed TDS_DONEINPROC_RESULT\n");
			if (stmt->row_status == PRE_NORMAL_ROW)
				return result_type;
			break;

		default:
			tdsdump_log(TDS_DBG_FUNC, "odbc_process_tokens: returning result_type %d\n", result_type);
			return result_type;
		}
	}
}

static void
odbc_fix_data_type_col(TDS_STMT *stmt, int idx)
{
	TDSSOCKET *tds = stmt->tds;
	TDSRESULTINFO *resinfo;
	TDSCOLUMN *colinfo;

	if (!tds)
		return;

	resinfo = tds->current_results;
	if (!resinfo || resinfo->num_cols <= idx)
		return;

	colinfo = resinfo->columns[idx];
	if (colinfo->column_cur_size < 0)
		return;

	switch (tds_get_conversion_type(colinfo->column_type, colinfo->column_size)) {
	case SYBINT2: {
		TDS_SMALLINT *data = (TDS_SMALLINT *) colinfo->column_data;
		*data = odbc_swap_datetime_sql_type(*data, 0);
		}
		break;
	case SYBINT4: {
		TDS_INT *data = (TDS_INT *) colinfo->column_data;
		*data = odbc_swap_datetime_sql_type(*data, 0);
		}
		break;
	default:
		break;
	}
}

static SQLUSMALLINT
copy_row(TDS_STMT * const stmt, const SQLLEN row_offset, const SQLULEN curr_row)
{
	const TDS_DESC *const ard = stmt->ard;
	TDSRESULTINFO *const resinfo = stmt->tds->current_results;
	int i;
	bool truncated = false;

#define AT_ROW(ptr, type) (row_offset ? (type*)(((char*)(ptr)) + row_offset) : &ptr[curr_row])

	for (i = 0; i < resinfo->num_cols; i++) {
		TDSCOLUMN *colinfo;
		struct _drecord *drec_ard;
		SQLLEN len;

		colinfo = resinfo->columns[i];
		colinfo->column_text_sqlgetdatapos = 0;
		colinfo->column_iconv_left = 0;
		drec_ard = (i < ard->header.sql_desc_count) ? &ard->records[i] : NULL;
		if (!drec_ard)
			continue;
		if (colinfo->column_cur_size < 0) {
			if (drec_ard->sql_desc_indicator_ptr) {
				*AT_ROW(drec_ard->sql_desc_indicator_ptr, SQLLEN) = SQL_NULL_DATA;
			} else if (drec_ard->sql_desc_data_ptr) {
				odbc_errs_add(&stmt->errs, "22002", NULL);
				return SQL_ROW_ERROR;
			}
			continue;
		}
		/* set indicator to 0 if data is not null */
		if (drec_ard->sql_desc_indicator_ptr)
			*AT_ROW(drec_ard->sql_desc_indicator_ptr, SQLLEN) = 0;

		/* TODO what happen to length if no data is returned (drec->sql_desc_data_ptr == NULL) ?? */
		len = 0;
		if (drec_ard->sql_desc_data_ptr) {
			int c_type;
			TDS_CHAR *data_ptr = (TDS_CHAR *) drec_ard->sql_desc_data_ptr;

			colinfo->column_text_sqlgetdatapos = 0;
			colinfo->column_iconv_left = 0;
			c_type = drec_ard->sql_desc_concise_type;
			if (c_type == SQL_C_DEFAULT)
				c_type = odbc_sql_to_c_type_default(stmt->ird->records[i].sql_desc_concise_type);
			if (row_offset || curr_row == 0) {
				data_ptr += row_offset;
			} else {
				data_ptr += odbc_get_octet_len(c_type, drec_ard) * curr_row;
			}
			len = odbc_tds2sql_col(stmt, colinfo, c_type, data_ptr, drec_ard->sql_desc_octet_length, drec_ard);
			if (len == SQL_NULL_DATA)
				return SQL_ROW_ERROR;

			if ((c_type == SQL_C_CHAR && len >= drec_ard->sql_desc_octet_length)
			    || (c_type == SQL_C_BINARY && len > drec_ard->sql_desc_octet_length)) {
				truncated = true;
				stmt->errs.lastrc = SQL_SUCCESS_WITH_INFO;
			}
		}
		if (drec_ard->sql_desc_octet_length_ptr)
			*AT_ROW(drec_ard->sql_desc_octet_length_ptr, SQLLEN) = len;
	}

	return truncated ? SQL_ROW_SUCCESS_WITH_INFO : SQL_ROW_SUCCESS;
#undef AT_ROW
}

/*
 * - handle correctly SQLGetData (for forward cursors accept only row_size == 1
 *   for other types application must use SQLSetPos)
 * - handle correctly results (SQL_SUCCESS_WITH_INFO if error on some rows,
 *   SQL_ERROR for all rows, see doc)
 */
static SQLRETURN
odbc_SQLFetch(TDS_STMT * stmt, SQLSMALLINT FetchOrientation, SQLLEN FetchOffset)
{
	TDSSOCKET *tds;
	TDSRESULTINFO *resinfo;
	int i;
	SQLULEN curr_row, num_rows;
	const TDS_DESC *const ard = stmt->ard;
	SQLULEN dummy, *fetched_ptr;
	SQLUSMALLINT *status_ptr, row_status = SQL_ROW_SUCCESS;
	TDS_INT result_type;
	bool truncated = false;

	SQLLEN row_offset = 0;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLFetch(%p, %d, %d)\n", stmt, (int)FetchOrientation, (int)FetchOffset);

	if (ard->header.sql_desc_bind_type != SQL_BIND_BY_COLUMN && ard->header.sql_desc_bind_offset_ptr)
		row_offset = *ard->header.sql_desc_bind_offset_ptr;

	tds = stmt->tds;
	num_rows = ard->header.sql_desc_array_size;

	/* TODO cursor check also type of cursor (scrollable, not forward) */
	if (FetchOrientation != SQL_FETCH_NEXT && (!stmt->cursor || !stmt->dbc->cursor_support)) {
		odbc_errs_add(&stmt->errs, "HY106", NULL);
		return SQL_ERROR;
	}

	/* handle cursors, fetch wanted rows */
	if (stmt->cursor && odbc_lock_statement(stmt)) {
		TDSCURSOR *cursor = stmt->cursor;
		TDS_CURSOR_FETCH fetch_type = TDS_CURSOR_FETCH_NEXT;

		tds = stmt->tds;

		switch (FetchOrientation) {
		case SQL_FETCH_NEXT:
			break;
		case SQL_FETCH_FIRST:
			fetch_type = TDS_CURSOR_FETCH_FIRST;
			break;
		case SQL_FETCH_LAST:
			fetch_type = TDS_CURSOR_FETCH_LAST;
			break;
		case SQL_FETCH_PRIOR:
			fetch_type = TDS_CURSOR_FETCH_PREV;
			break;
		case SQL_FETCH_ABSOLUTE:
			fetch_type = TDS_CURSOR_FETCH_ABSOLUTE;
			break;
		case SQL_FETCH_RELATIVE:
			fetch_type = TDS_CURSOR_FETCH_RELATIVE;
			break;
		/* TODO cursor bookmark */
		default:
			odbc_errs_add(&stmt->errs, "HYC00", NULL);
			return SQL_ERROR;
		}

		if (cursor->cursor_rows != num_rows) {
			bool send = false;
			cursor->cursor_rows = num_rows;
			/* TODO handle change rows (tds5) */
			/*
			 * TODO curerntly we support cursors only using tds7+
			 * so this function can't fail but remember to add error
			 * check when tds5 will be supported
			 */
			tds_cursor_setrows(tds, cursor, &send);
		}

		if (TDS_FAILED(tds_cursor_fetch(tds, cursor, fetch_type, FetchOffset))) {
			/* TODO what kind of error ?? */
			ODBC_SAFE_ERROR(stmt);
			return SQL_ERROR;
		}

		/* TODO handle errors in a better way */
		odbc_process_tokens(stmt, TDS_RETURN_ROW|TDS_STOPAT_COMPUTE|TDS_STOPAT_ROW);
		stmt->row_status = PRE_NORMAL_ROW;
	}

	if (!tds && stmt->row_status == PRE_NORMAL_ROW && stmt->ird->header.sql_desc_count > 0)
		ODBC_RETURN(stmt, SQL_NO_DATA);
	if (!tds || stmt->row_status == NOT_IN_ROW) {
		odbc_errs_add(&stmt->errs, "24000", NULL);
		return SQL_ERROR;
	}
	IRD_CHECK;

	if (stmt->ird->header.sql_desc_count <= 0) {
		odbc_errs_add(&stmt->errs, "24000", NULL);
		return SQL_ERROR;
	}

	stmt->row++;

	fetched_ptr = &dummy;
	if (stmt->ird->header.sql_desc_rows_processed_ptr)
		fetched_ptr = stmt->ird->header.sql_desc_rows_processed_ptr;
	*fetched_ptr = 0;

	status_ptr = stmt->ird->header.sql_desc_array_status_ptr;
	if (status_ptr) {
		for (i = 0; i < num_rows; ++i)
			*status_ptr++ = SQL_ROW_NOROW;
		status_ptr = stmt->ird->header.sql_desc_array_status_ptr;
	}

	curr_row = 0;
	do {
		row_status = SQL_ROW_SUCCESS;

		/* do not get compute row if we are not expecting a compute row */
		switch (stmt->row_status) {
		case AFTER_COMPUTE_ROW:
			/* handle done if needed */
			/* FIXME doesn't seem so fine ... - freddy77 */
			tds_process_tokens(tds, &result_type, NULL, TDS_TOKEN_TRAILING);
			goto all_done;

		case IN_COMPUTE_ROW:
			/* compute recorset contains only a row */
			/* we already fetched compute row in SQLMoreResults so do not fetch another one */
			num_rows = 1;
			stmt->row_status = AFTER_COMPUTE_ROW;
			break;

		default:
			/* FIXME stmt->row_count set correctly ?? TDS_DONE_COUNT not checked */
			switch (odbc_process_tokens(stmt, TDS_STOPAT_ROWFMT|TDS_RETURN_ROW|TDS_STOPAT_COMPUTE)) {
			case TDS_ROW_RESULT:
				break;
			default:
#if 0
				stmt->row_count = tds->rows_affected;
#endif
				/*
				 * NOTE do not set row_status to NOT_IN_ROW,
				 * if compute tds_process_tokens above returns TDS_NO_MORE_RESULTS
				 */
				stmt->row_status = PRE_NORMAL_ROW;
				stmt->special_row = ODBC_SPECIAL_NONE;
#if 0
				odbc_populate_ird(stmt);
#endif
				tdsdump_log(TDS_DBG_INFO1, "SQLFetch: NO_DATA_FOUND\n");
				goto all_done;
				break;
			case TDS_CMD_FAIL:
				ODBC_SAFE_ERROR(stmt);
				return SQL_ERROR;
				break;
			}

			stmt->row_status = IN_NORMAL_ROW;

			/* handle special row */
			switch (stmt->special_row) {
			case ODBC_SPECIAL_GETTYPEINFO:
				odbc_fix_data_type_col(stmt, 1);
				break;
			case ODBC_SPECIAL_COLUMNS:
				odbc_fix_data_type_col(stmt, 4);
				odbc_fix_data_type_col(stmt, 13); /* TODO sure ?? */
				break;
			case ODBC_SPECIAL_PROCEDURECOLUMNS:
				odbc_fix_data_type_col(stmt, 5);
				odbc_fix_data_type_col(stmt, 14); /* TODO sure ?? */
				break;
			case ODBC_SPECIAL_SPECIALCOLUMNS:
				odbc_fix_data_type_col(stmt, 2);
				break;
			case ODBC_SPECIAL_NONE:
				break;
			}
		}

		resinfo = tds->current_results;
		if (!resinfo) {
			tdsdump_log(TDS_DBG_INFO1, "SQLFetch: !resinfo\n");
			break;
		}

		/* we got a row, return a row readed even if error (for ODBC specifications) */
		++(*fetched_ptr);
		row_status = copy_row(stmt, row_offset, curr_row);
		if (row_status == SQL_ROW_SUCCESS_WITH_INFO)
			truncated = true;

		if (status_ptr)
			*status_ptr++ = truncated ? SQL_ROW_ERROR : row_status;
		if (row_status == SQL_ROW_ERROR) {
			stmt->errs.lastrc = SQL_ERROR;
			break;
		}
#if SQL_BIND_BY_COLUMN != 0
		if (ard->header.sql_desc_bind_type != SQL_BIND_BY_COLUMN)
#endif
			row_offset += ard->header.sql_desc_bind_type;
	} while (++curr_row < num_rows);

	if (truncated)
		odbc_errs_add(&stmt->errs, "01004", NULL);

      all_done:
	/* TODO cursor correct ?? */
	if (stmt->cursor) {
		tds_process_tokens(tds, &result_type, NULL, TDS_TOKEN_TRAILING);
		odbc_unlock_statement(stmt);
	}
	if (*fetched_ptr == 0 && (stmt->errs.lastrc == SQL_SUCCESS || stmt->errs.lastrc == SQL_SUCCESS_WITH_INFO))
		ODBC_RETURN(stmt, SQL_NO_DATA);
	if (stmt->errs.lastrc == SQL_ERROR && (*fetched_ptr > 1 || (*fetched_ptr == 1 && row_status != SQL_ROW_ERROR)))
		ODBC_RETURN(stmt, SQL_SUCCESS_WITH_INFO);
	ODBC_RETURN_(stmt);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLFetch(SQLHSTMT hstmt)
{
	SQLRETURN ret;
	struct {
		SQLULEN  array_size;
		SQLULEN *rows_processed_ptr;
		SQLUSMALLINT *array_status_ptr;
	} keep;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLFetch(%p)\n", hstmt);

	keep.array_size = stmt->ard->header.sql_desc_array_size;
	keep.rows_processed_ptr = stmt->ird->header.sql_desc_rows_processed_ptr;
	keep.array_status_ptr = stmt->ird->header.sql_desc_array_status_ptr;

	if (stmt->dbc->env->attr.odbc_version != SQL_OV_ODBC3) {
		stmt->ard->header.sql_desc_array_size = 1;
		stmt->ird->header.sql_desc_rows_processed_ptr = NULL;
		stmt->ird->header.sql_desc_array_status_ptr = NULL;
	}

	ret = odbc_SQLFetch(stmt, SQL_FETCH_NEXT, 0);

	if (stmt->dbc->env->attr.odbc_version != SQL_OV_ODBC3) {
		stmt->ard->header.sql_desc_array_size = keep.array_size;
		stmt->ird->header.sql_desc_rows_processed_ptr = keep.rows_processed_ptr;
		stmt->ird->header.sql_desc_array_status_ptr = keep.array_status_ptr;
	}

	ODBC_EXIT(stmt, ret);
}

#if (ODBCVER >= 0x0300)
SQLRETURN ODBC_PUBLIC ODBC_API
SQLFetchScroll(SQLHSTMT hstmt, SQLSMALLINT FetchOrientation, SQLLEN FetchOffset)
{
	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLFetchScroll(%p, %d, %d)\n", hstmt, FetchOrientation, (int)FetchOffset);

	if (FetchOrientation != SQL_FETCH_NEXT && !stmt->dbc->cursor_support) {
		odbc_errs_add(&stmt->errs, "HY106", NULL);
		ODBC_EXIT_(stmt);
	}

	ODBC_EXIT(stmt, odbc_SQLFetch(stmt, FetchOrientation, FetchOffset));
}
#endif


#if (ODBCVER >= 0x0300)
SQLRETURN ODBC_PUBLIC ODBC_API
SQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle)
{
	tdsdump_log(TDS_DBG_INFO1, "SQLFreeHandle(%d, %p)\n", HandleType, (void *) Handle);

	switch (HandleType) {
	case SQL_HANDLE_STMT:
		return odbc_SQLFreeStmt(Handle, SQL_DROP, 0);
		break;
	case SQL_HANDLE_DBC:
		return odbc_SQLFreeConnect(Handle);
		break;
	case SQL_HANDLE_ENV:
		return odbc_SQLFreeEnv(Handle);
		break;
	case SQL_HANDLE_DESC:
		return odbc_SQLFreeDesc(Handle);
		break;
	}
	return SQL_ERROR;
}

static SQLRETURN
odbc_SQLFreeConnect(SQLHDBC hdbc)
{
	int i;

	ODBC_ENTER_HDBC;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLFreeConnect(%p)\n",
			hdbc);

	tds_close_socket(dbc->tds_socket);

	/* TODO if connected return error */
	tds_free_socket(dbc->tds_socket);

	odbc_bcp_free_storage(dbc);
	/* free attributes */
#ifdef TDS_NO_DM
	tds_dstr_free(&dbc->attr.tracefile);
#endif
	tds_dstr_free(&dbc->attr.current_catalog);
	tds_dstr_free(&dbc->attr.translate_lib);
	tds_dstr_zero(&dbc->oldpwd);
	tds_dstr_free(&dbc->oldpwd);

	tds_dstr_free(&dbc->dsn);

	for (i = 0; i < TDS_MAX_APP_DESC; i++) {
		if (dbc->uad[i]) {
			desc_free(dbc->uad[i]);
		}
	}
	odbc_errs_reset(&dbc->errs);
	tds_mutex_unlock(&dbc->mtx);
	tds_mutex_free(&dbc->mtx);

	free(dbc);

	return SQL_SUCCESS;
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLFreeConnect(SQLHDBC hdbc)
{
	tdsdump_log(TDS_DBG_INFO2, "SQLFreeConnect(%p)\n", hdbc);
	return odbc_SQLFreeConnect(hdbc);
}
#endif

static SQLRETURN
odbc_SQLFreeEnv(SQLHENV henv)
{
	ODBC_ENTER_HENV;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLFreeEnv(%p)\n",
			henv);

	odbc_errs_reset(&env->errs);
	tds_free_context(env->tds_ctx);
	tds_mutex_unlock(&env->mtx);
	tds_mutex_free(&env->mtx);
	free(env);

	return SQL_SUCCESS;
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLFreeEnv(SQLHENV henv)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLFreeEnv(%p)\n", henv);

	return odbc_SQLFreeEnv(henv);
}

static SQLRETURN
odbc_SQLFreeStmt(SQLHSTMT hstmt, SQLUSMALLINT fOption, int force)
{
	TDSSOCKET *tds;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLFreeStmt(%p, %d, %d)\n", hstmt, fOption, force);

	/* check if option correct */
	if (fOption != SQL_DROP && fOption != SQL_CLOSE && fOption != SQL_UNBIND && fOption != SQL_RESET_PARAMS) {
		tdsdump_log(TDS_DBG_ERROR, "SQLFreeStmt: Unknown option %d\n", fOption);
		odbc_errs_add(&stmt->errs, "HY092", NULL);
		ODBC_EXIT_(stmt);
	}

	/* if we have bound columns, free the temporary list */
	if (fOption == SQL_DROP || fOption == SQL_UNBIND) {
		desc_free_records(stmt->ard);
	}

	/* do the same for bound parameters */
	if (fOption == SQL_DROP || fOption == SQL_RESET_PARAMS) {
		desc_free_records(stmt->apd);
		desc_free_records(stmt->ipd);
		stmt->params_set = 0;
	}

	/* close statement */
	if (fOption == SQL_DROP || fOption == SQL_CLOSE) {
		SQLRETURN retcode;

		tds = stmt->tds;
		/*
		 * FIXME -- otherwise make sure the current statement is complete
		 */
		/* do not close other running query ! */
		if (tds && tds->state != TDS_IDLE && tds->state != TDS_DEAD) {
			if (TDS_SUCCEED(tds_send_cancel(tds)))
				tds_process_cancel(tds);
		}

		/* free cursor */
		retcode = odbc_free_cursor(stmt);
		if (!force && retcode != SQL_SUCCESS)
			ODBC_EXIT(stmt, retcode);
	}

	/* free it */
	if (fOption == SQL_DROP) {
		SQLRETURN retcode;

		/* close prepared statement or add to connection */
		retcode = odbc_free_dynamic(stmt);
		if (!force && retcode != SQL_SUCCESS)
			ODBC_EXIT(stmt, retcode);

		/* detatch from list */
		tds_mutex_lock(&stmt->dbc->mtx);
		if (stmt->next)
			stmt->next->prev = stmt->prev;
		if (stmt->prev)
			stmt->prev->next = stmt->next;
		if (stmt->dbc->stmt_list == stmt)
			stmt->dbc->stmt_list = stmt->next;
		tds_mutex_unlock(&stmt->dbc->mtx);

		tds_dstr_free(&stmt->query);
		tds_free_param_results(stmt->params);
		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);
		desc_free(stmt->orig_apd);
		tds_mutex_unlock(&stmt->mtx);
		tds_mutex_free(&stmt->mtx);
		free(stmt);

		/* NOTE we freed stmt, do not use ODBC_EXIT */
		return SQL_SUCCESS;
	}
	ODBC_EXIT_(stmt);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLFreeStmt(SQLHSTMT hstmt, SQLUSMALLINT fOption)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLFreeStmt(%p, %d)\n", hstmt, fOption);
	return odbc_SQLFreeStmt(hstmt, fOption, 0);
}


SQLRETURN ODBC_PUBLIC ODBC_API
SQLCloseCursor(SQLHSTMT hstmt)
{
	/* TODO cursors */
	/*
	 * Basic implementation for when no driver manager is present.
	 *  - according to ODBC documentation SQLCloseCursor is more or less
	 *    equivalent to SQLFreeStmt(..., SQL_CLOSE).
	 *  - indeed that is what the DM does if it can't find the function
	 *    in the driver, so this is pretty close.
	 */
	/* TODO remember to close cursors really when get implemented */
	/* TODO read all results and discard them or use cancellation ?? test behaviour */

	tdsdump_log(TDS_DBG_FUNC, "SQLCloseCursor(%p)\n", hstmt);
	return odbc_SQLFreeStmt(hstmt, SQL_CLOSE, 0);
}

static SQLRETURN
odbc_SQLFreeDesc(SQLHDESC hdesc)
{
	ODBC_ENTER_HDESC;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLFreeDesc(%p)\n",
			hdesc);

	if (desc->header.sql_desc_alloc_type != SQL_DESC_ALLOC_USER) {
		odbc_errs_add(&desc->errs, "HY017", NULL);
		ODBC_EXIT_(desc);
	}

	if (IS_HDBC(desc->parent)) {
		TDS_DBC *dbc = (TDS_DBC *) desc->parent;
		TDS_STMT *stmt;
		int i;

		/* freeing descriptors associated to statements revert state of statements */
		tds_mutex_lock(&dbc->mtx);
		for (stmt = dbc->stmt_list; stmt != NULL; stmt = stmt->next) {
			if (stmt->ard == desc)
				stmt->ard = stmt->orig_ard;
			if (stmt->apd == desc)
				stmt->apd = stmt->orig_apd;
		}
		tds_mutex_unlock(&dbc->mtx);

		for (i = 0; i < TDS_MAX_APP_DESC; ++i) {
			if (dbc->uad[i] == desc) {
				dbc->uad[i] = NULL;
				tds_mutex_unlock(&desc->mtx);
				desc_free(desc);
				break;
			}
		}
	}
	return SQL_SUCCESS;
}

static SQLRETURN
odbc_SQLGetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER Value,
		    SQLINTEGER BufferLength, SQLINTEGER * StringLength WIDE)
{
	void *src;
	size_t size;

	ODBC_ENTER_HSTMT;

	/* TODO assign directly, use macro for size */
	switch (Attribute) {
	case SQL_ATTR_APP_PARAM_DESC:
		size = sizeof(stmt->apd);
		src = &stmt->apd;
		break;
	case SQL_ATTR_APP_ROW_DESC:
		size = sizeof(stmt->ard);
		src = &stmt->ard;
		break;
	case SQL_ATTR_ASYNC_ENABLE:
		size = sizeof(stmt->attr.async_enable);
		src = &stmt->attr.async_enable;
		break;
	case SQL_ATTR_CONCURRENCY:
		size = sizeof(stmt->attr.concurrency);
		src = &stmt->attr.concurrency;
		break;
	case SQL_ATTR_CURSOR_TYPE:
		size = sizeof(stmt->attr.cursor_type);
		src = &stmt->attr.cursor_type;
		break;
	case SQL_ATTR_ENABLE_AUTO_IPD:
		size = sizeof(stmt->attr.enable_auto_ipd);
		src = &stmt->attr.enable_auto_ipd;
		break;
	case SQL_ATTR_FETCH_BOOKMARK_PTR:
		size = sizeof(stmt->attr.fetch_bookmark_ptr);
		src = &stmt->attr.fetch_bookmark_ptr;
		break;
	case SQL_ATTR_KEYSET_SIZE:
		size = sizeof(stmt->attr.keyset_size);
		src = &stmt->attr.keyset_size;
		break;
	case SQL_ATTR_MAX_LENGTH:
		size = sizeof(stmt->attr.max_length);
		src = &stmt->attr.max_length;
		break;
	case SQL_ATTR_MAX_ROWS:
		size = sizeof(stmt->attr.max_rows);
		src = &stmt->attr.max_rows;
		break;
	case SQL_ATTR_METADATA_ID:
		size = sizeof(stmt->attr.metadata_id);
		src = &stmt->attr.metadata_id;
		break;
	case SQL_ATTR_NOSCAN:
		size = sizeof(stmt->attr.noscan);
		src = &stmt->attr.noscan;
		break;
	case SQL_ATTR_PARAM_BIND_OFFSET_PTR:
		size = sizeof(stmt->apd->header.sql_desc_bind_offset_ptr);
		src = &stmt->apd->header.sql_desc_bind_offset_ptr;
		break;
	case SQL_ATTR_PARAM_BIND_TYPE:
		size = sizeof(stmt->apd->header.sql_desc_bind_type);
		src = &stmt->apd->header.sql_desc_bind_type;
		break;
	case SQL_ATTR_PARAM_OPERATION_PTR:
		size = sizeof(stmt->apd->header.sql_desc_array_status_ptr);
		src = &stmt->apd->header.sql_desc_array_status_ptr;
		break;
	case SQL_ATTR_PARAM_STATUS_PTR:
		size = sizeof(stmt->ipd->header.sql_desc_array_status_ptr);
		src = &stmt->ipd->header.sql_desc_array_status_ptr;
		break;
	case SQL_ATTR_PARAMS_PROCESSED_PTR:
		size = sizeof(stmt->ipd->header.sql_desc_rows_processed_ptr);
		src = &stmt->ipd->header.sql_desc_rows_processed_ptr;
		break;
	case SQL_ATTR_PARAMSET_SIZE:
		size = sizeof(stmt->apd->header.sql_desc_array_size);
		src = &stmt->apd->header.sql_desc_array_size;
		break;
	case SQL_ATTR_QUERY_TIMEOUT:
		size = sizeof(stmt->attr.query_timeout);
		src = &stmt->attr.query_timeout;
		break;
	case SQL_ATTR_RETRIEVE_DATA:
		size = sizeof(stmt->attr.retrieve_data);
		src = &stmt->attr.retrieve_data;
		break;
	case SQL_ATTR_ROW_BIND_OFFSET_PTR:
		size = sizeof(stmt->ard->header.sql_desc_bind_offset_ptr);
		src = &stmt->ard->header.sql_desc_bind_offset_ptr;
		break;
#if SQL_BIND_TYPE != SQL_ATTR_ROW_BIND_TYPE
	case SQL_BIND_TYPE:	/* although this is ODBC2 we must support this attribute */
#endif
	case SQL_ATTR_ROW_BIND_TYPE:
		size = sizeof(stmt->ard->header.sql_desc_bind_type);
		src = &stmt->ard->header.sql_desc_bind_type;
		break;
	case SQL_ATTR_ROW_NUMBER:
		/* TODO do not get info every time, cache somewhere */
		if (stmt->cursor && odbc_lock_statement(stmt)) {
			TDS_UINT row_number, row_count;

			tds_cursor_get_cursor_info(stmt->tds, stmt->cursor, &row_number, &row_count);
			stmt->attr.row_number = row_number;
		}
		size = sizeof(stmt->attr.row_number);
		src = &stmt->attr.row_number;
		break;
	case SQL_ATTR_ROW_OPERATION_PTR:
		size = sizeof(stmt->ard->header.sql_desc_array_status_ptr);
		src = &stmt->ard->header.sql_desc_array_status_ptr;
		break;
	case SQL_ATTR_ROW_STATUS_PTR:
		size = sizeof(stmt->ird->header.sql_desc_array_status_ptr);
		src = &stmt->ird->header.sql_desc_array_status_ptr;
		break;
	case SQL_ATTR_ROWS_FETCHED_PTR:
		size = sizeof(stmt->ird->header.sql_desc_rows_processed_ptr);
		src = &stmt->ird->header.sql_desc_rows_processed_ptr;
		break;
	case SQL_ATTR_ROW_ARRAY_SIZE:
		size = sizeof(stmt->ard->header.sql_desc_array_size);
		src = &stmt->ard->header.sql_desc_array_size;
		break;
	case SQL_ATTR_SIMULATE_CURSOR:
		size = sizeof(stmt->attr.simulate_cursor);
		src = &stmt->attr.simulate_cursor;
		break;
	case SQL_ATTR_USE_BOOKMARKS:
		size = sizeof(stmt->attr.use_bookmarks);
		src = &stmt->attr.use_bookmarks;
		break;
	case SQL_ATTR_CURSOR_SCROLLABLE:
		size = sizeof(stmt->attr.cursor_scrollable);
		src = &stmt->attr.cursor_scrollable;
		break;
	case SQL_ATTR_CURSOR_SENSITIVITY:
		size = sizeof(stmt->attr.cursor_sensitivity);
		src = &stmt->attr.cursor_sensitivity;
		break;
	case SQL_ATTR_IMP_ROW_DESC:
		size = sizeof(stmt->ird);
		src = &stmt->ird;
		break;
	case SQL_ATTR_IMP_PARAM_DESC:
		size = sizeof(stmt->ipd);
		src = &stmt->ipd;
		break;
	case SQL_ROWSET_SIZE:	/* although this is ODBC2 we must support this attribute */
		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_dstr_oct(stmt->dbc, Value, BufferLength, StringLength, &stmt->attr.qn_msgtext);
			ODBC_EXIT(stmt, rc);
		}
	case SQL_SOPT_SS_QUERYNOTIFICATION_OPTIONS:
		{
			SQLRETURN rc = odbc_set_dstr_oct(stmt->dbc, Value, BufferLength, StringLength, &stmt->attr.qn_options);
			ODBC_EXIT(stmt, rc);
		}
		/* TODO SQL_COLUMN_SEARCHABLE, although ODBC2 */
	default:
		odbc_errs_add(&stmt->errs, "HY092", NULL);
		ODBC_EXIT_(stmt);
	}

	memcpy(Value, src, size);
	if (StringLength)
		*StringLength = size;

	ODBC_EXIT_(stmt);
}

#if (ODBCVER >= 0x0300)
SQLRETURN ODBC_PUBLIC ODBC_API
SQLGetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER Value, SQLINTEGER BufferLength, SQLINTEGER * StringLength)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLGetStmtAttr(%p, %d, %p, %d, %p)\n",
			hstmt, (int)Attribute, Value, (int)BufferLength, StringLength);

	return odbc_SQLGetStmtAttr(hstmt, Attribute, Value, BufferLength, StringLength _wide0);
}

#ifdef ENABLE_ODBC_WIDE
SQLRETURN ODBC_PUBLIC ODBC_API
SQLGetStmtAttrW(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER Value, SQLINTEGER BufferLength, SQLINTEGER * StringLength)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLGetStmtAttr(%p, %d, %p, %d, %p)\n",
			hstmt, (int)Attribute, Value, (int)BufferLength, StringLength);

	return odbc_SQLGetStmtAttr(hstmt, Attribute, Value, BufferLength, StringLength, 1);
}
#endif
#endif

SQLRETURN ODBC_PUBLIC ODBC_API
SQLGetStmtOption(SQLHSTMT hstmt, SQLUSMALLINT fOption, SQLPOINTER pvParam)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLGetStmtOption(%p, %d, %p)\n",
			hstmt, fOption, pvParam);

	return odbc_SQLGetStmtAttr(hstmt, (SQLINTEGER) fOption, pvParam, SQL_MAX_OPTION_STRING_LENGTH, NULL _wide0);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLNumResultCols(SQLHSTMT hstmt, SQLSMALLINT FAR * pccol)
{
	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLNumResultCols(%p, %p)\n",
			hstmt, pccol);

	/*
	 * 3/15/2001 bsb - DBD::ODBC calls SQLNumResultCols on non-result
	 * generating queries such as 'drop table'
	 */
#if 0
	if (stmt->row_status == NOT_IN_ROW) {
		odbc_errs_add(&stmt->errs, "24000", NULL);
		ODBC_EXIT_(stmt);
	}
#endif
	IRD_UPDATE(stmt->ird, &stmt->errs, ODBC_EXIT(stmt, SQL_ERROR));
	*pccol = stmt->ird->header.sql_desc_count;
	ODBC_EXIT_(stmt);
}

ODBC_FUNC(SQLPrepare, (P(SQLHSTMT,hstmt), PCHARIN(SqlStr,SQLINTEGER) WIDE))
{
	SQLRETURN retcode;

	ODBC_ENTER_HSTMT;

	/* try to free dynamic associated */
	retcode = odbc_free_dynamic(stmt);
	if (retcode != SQL_SUCCESS)
		ODBC_EXIT(stmt, retcode);

	if (SQL_SUCCESS != odbc_set_stmt_query(stmt, szSqlStr, cbSqlStr _wide))
		ODBC_EXIT(stmt, SQL_ERROR);
	stmt->is_prepared_query = 1;

	/* count parameters */
	stmt->param_count = tds_count_placeholders(tds_dstr_cstr(&stmt->query));

	/* reset IPD and APD */
	if (!stmt->params_set) {
		desc_alloc_records(stmt->ipd, 0);
		desc_alloc_records(stmt->apd, 0);
	}

	/* trasform to native (one time, not for every SQLExecute) */
	if (SQL_SUCCESS != prepare_call(stmt))
		ODBC_EXIT(stmt, SQL_ERROR);

	/* TODO needed ?? */
	tds_release_dynamic(&stmt->dyn);

	/* try to prepare query */
	/* TODO support getting info for RPC */
	if (!stmt->prepared_query_is_rpc
		 && (stmt->attr.cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->attr.concurrency == SQL_CONCUR_READ_ONLY)) {

		tds_free_param_results(stmt->params);
		stmt->params = NULL;
		stmt->param_num = 0;
		stmt->need_reprepare = 0;
		/*
		 * using TDS7+ we need parameters to prepare a query so try
		 * to get them
		 * TDS5 do not need parameters type and we have always to
		 * prepare sepatately so this is not an issue
		 */
		if (IS_TDS7_PLUS(stmt->dbc->tds_socket->conn)) {
			stmt->need_reprepare = 1;
			ODBC_EXIT_(stmt);
		}

		tdsdump_log(TDS_DBG_INFO1, "Creating prepared statement\n");
		if (odbc_lock_statement(stmt))
			odbc_prepare(stmt);
	}

	ODBC_EXIT_(stmt);
}

/* TDS_NO_COUNT must be -1 for SQLRowCount to return -1 when there is no rowcount */
#if TDS_NO_COUNT != -1
# error TDS_NO_COUNT != -1
#endif

SQLRETURN
odbc_SQLRowCount(SQLHSTMT hstmt, SQLLEN FAR * pcrow)
{
	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLRowCount(%p, %p),  %ld rows \n", hstmt, pcrow, (long)stmt->row_count);

	*pcrow = stmt->row_count;

	ODBC_EXIT_(stmt);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLRowCount(SQLHSTMT hstmt, SQLLEN FAR * pcrow)
{
	SQLRETURN rc = odbc_SQLRowCount(hstmt, pcrow);
	tdsdump_log(TDS_DBG_INFO1, "SQLRowCount returns %d, row count %ld\n", rc, (long int) *pcrow);
	return rc;
}

ODBC_FUNC(SQLSetCursorName, (P(SQLHSTMT,hstmt), PCHARIN(Cursor,SQLSMALLINT) WIDE))
{
	ODBC_ENTER_HSTMT;

	/* cursor already present, we cannot set name */
	if (stmt->cursor) {
		odbc_errs_add(&stmt->errs, "24000", NULL);
		ODBC_EXIT_(stmt);
	}

	if (!odbc_dstr_copy(stmt->dbc, &stmt->cursor_name, cbCursor, szCursor)) {
		odbc_errs_add(&stmt->errs, "HY001", NULL);
		ODBC_EXIT_(stmt);
	}
	ODBC_EXIT_(stmt);
}

ODBC_FUNC(SQLGetCursorName, (P(SQLHSTMT,hstmt), PCHAROUT(Cursor,SQLSMALLINT) WIDE))
{
	SQLRETURN rc;

	ODBC_ENTER_HSTMT;

	if ((rc = odbc_set_dstr(stmt->dbc, szCursor, cbCursorMax, pcbCursor, &stmt->cursor_name)))
		odbc_errs_add(&stmt->errs, "01004", NULL);

	ODBC_EXIT(stmt, rc);
}

/*
 * spinellia@acm.org : copied shamelessly from change_database
 * transaction support
 * 1 = commit, 0 = rollback
 */
static SQLRETURN
change_transaction(TDS_DBC * dbc, int state)
{
	TDSSOCKET *tds = dbc->tds_socket;
	int cont;
	TDSRET ret;

	tdsdump_log(TDS_DBG_INFO1, "change_transaction(0x%p,%d)\n", dbc, state);
	tds_mutex_check_owned(&dbc->mtx);

	if (dbc->attr.autocommit == SQL_AUTOCOMMIT_ON)
		return SQL_SUCCESS;
	cont = 1;

	/* if pending drop all recordset, don't issue cancel */
	if (tds->state == TDS_PENDING && dbc->current_statement != NULL) {
		/* TODO what happen on multiple errors ?? discard all ?? */
		if (TDS_FAILED(tds_process_simple_query(tds)))
			return SQL_ERROR;
	}

	/* TODO better idle check, not thread safe */
	if (tds->state == TDS_IDLE)
		tds->query_timeout = dbc->default_query_timeout;

	/* reset statement so errors will be reported on connection.
	 * Note that we own the connection lock */
	if (dbc->current_statement) {
		dbc->current_statement->tds = NULL;
		dbc->current_statement = NULL;
	}
	tds_set_parent(tds, dbc);

	if (state)
		ret = tds_submit_commit(tds, cont);
	else
		ret = tds_submit_rollback(tds, cont);

	if (TDS_FAILED(ret)) {
		odbc_errs_add(&dbc->errs, "HY000", "Could not perform COMMIT or ROLLBACK");
		return SQL_ERROR;
	}

	if (TDS_FAILED(tds_process_simple_query(tds)))
		return SQL_ERROR;

	/* TODO some query can collect errors so perhaps it's better to return SUCCES_WITH_INFO in such case... */
	return SQL_SUCCESS;
}

static SQLRETURN
odbc_SQLTransact(SQLHENV henv, SQLHDBC hdbc, SQLUSMALLINT fType)
{
	int op = (fType == SQL_COMMIT ? 1 : 0);

	/* I may live without a HENV */
	/*     CHECK_HENV; */
	/* ..but not without a HDBC! */
	ODBC_ENTER_HDBC;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLTransact(%p, %p, %d)\n",
			henv, hdbc, fType);

	ODBC_EXIT(dbc, change_transaction(dbc, op));
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLTransact(SQLHENV henv, SQLHDBC hdbc, SQLUSMALLINT fType)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLTransact(%p, %p, %d)\n", henv, hdbc, fType);

	return odbc_SQLTransact(henv, hdbc, fType);
}

#if ODBCVER >= 0x300
SQLRETURN ODBC_PUBLIC ODBC_API
SQLEndTran(SQLSMALLINT handleType, SQLHANDLE handle, SQLSMALLINT completionType)
{
	/*
	 * Do not call the exported SQLTransact(),
	 * because we may wind up calling a function with the same name implemented by the DM.
	 */
	tdsdump_log(TDS_DBG_FUNC, "SQLEndTran(%d, %p, %d)\n", handleType, handle, completionType);

	switch (handleType) {
	case SQL_HANDLE_ENV:
		return odbc_SQLTransact(handle, NULL, completionType);
	case SQL_HANDLE_DBC:
		return odbc_SQLTransact(NULL, handle, completionType);
	}
	return SQL_ERROR;
}
#endif

/* end of transaction support */

SQLRETURN ODBC_PUBLIC ODBC_API
SQLSetParam(SQLHSTMT hstmt, SQLUSMALLINT ipar, SQLSMALLINT fCType, SQLSMALLINT fSqlType, SQLULEN cbParamDef,
	    SQLSMALLINT ibScale, SQLPOINTER rgbValue, SQLLEN FAR * pcbValue)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLSetParam(%p, %d, %d, %d, %u, %d, %p, %p)\n",
			hstmt, ipar, fCType, fSqlType, (unsigned)cbParamDef, ibScale, rgbValue, pcbValue);

	return odbc_SQLBindParameter(hstmt, ipar, SQL_PARAM_INPUT_OUTPUT, fCType, fSqlType, cbParamDef, ibScale, rgbValue,
				 SQL_SETPARAM_VALUE_MAX, pcbValue);
}

/**
 * SQLColumns
 *
 * Return column information for a table or view. This is
 * mapped to a call to sp_columns which - lucky for us - returns
 * the exact result set we need.
 *
 * exec sp_columns [ @table_name = ] object
 *                 [ , [ @table_owner = ] owner ]
 *                 [ , [ @table_qualifier = ] qualifier ]
 *                 [ , [ @column_name = ] column ]
 *                 [ , [ @ODBCVer = ] ODBCVer ]
 *
 */
ODBC_FUNC(SQLColumns, (P(SQLHSTMT,hstmt), PCHARIN(CatalogName,SQLSMALLINT),	/* object_qualifier */
	PCHARIN(SchemaName,SQLSMALLINT),	/* object_owner */
	PCHARIN(TableName,SQLSMALLINT),	/* object_name */
	PCHARIN(ColumnName,SQLSMALLINT)	/* column_name */
	WIDE))
{
	int retcode;
	const char *proc = "sp_columns";

	ODBC_ENTER_HSTMT;

	if (odbc_get_string_size(cbCatalogName, szCatalogName _wide))
		proc = "..sp_columns";

	retcode =
		odbc_stat_execute(stmt _wide, proc, TDS_IS_MSSQL(stmt->dbc->tds_socket) ? 5 : 4,
				  "P@table_name", szTableName, cbTableName, "P@table_owner", szSchemaName,
				  cbSchemaName, "O@table_qualifier", szCatalogName, cbCatalogName, "P@column_name", szColumnName,
				  cbColumnName, "V@ODBCVer", (char*) NULL, 0);
	if (SQL_SUCCEEDED(retcode) && stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3) {
		odbc_col_setname(stmt, 1, "TABLE_CAT");
		odbc_col_setname(stmt, 2, "TABLE_SCHEM");
		odbc_col_setname(stmt, 7, "COLUMN_SIZE");
		odbc_col_setname(stmt, 8, "BUFFER_LENGTH");
		odbc_col_setname(stmt, 9, "DECIMAL_DIGITS");
		odbc_col_setname(stmt, 10, "NUM_PREC_RADIX");
		if (TDS_IS_SYBASE(stmt->dbc->tds_socket))
			stmt->special_row = ODBC_SPECIAL_COLUMNS;
	}
	ODBC_EXIT_(stmt);
}

ODBC_FUNC(SQLGetConnectAttr, (P(SQLHDBC,hdbc), P(SQLINTEGER,Attribute), P(SQLPOINTER,Value), P(SQLINTEGER,BufferLength),
	P(SQLINTEGER *,StringLength) WIDE))
{
	const char *p = NULL;

	ODBC_ENTER_HDBC;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLGetConnectAttr(%p, %d, %p, %d, %p)\n",
			hdbc, (int)Attribute, Value, (int)BufferLength, StringLength);

	switch (Attribute) {
	case SQL_ATTR_AUTOCOMMIT:
		*((SQLUINTEGER *) Value) = dbc->attr.autocommit;
		break;
#if defined(SQL_ATTR_CONNECTION_DEAD) && defined(SQL_CD_TRUE)
	case SQL_ATTR_CONNECTION_DEAD:
		*((SQLUINTEGER *) Value) = IS_TDSDEAD(dbc->tds_socket) ? SQL_CD_TRUE : SQL_CD_FALSE;
		break;
#endif
	case SQL_ATTR_CONNECTION_TIMEOUT:
		*((SQLUINTEGER *) Value) = dbc->attr.connection_timeout;
		break;
	case SQL_ATTR_ACCESS_MODE:
		*((SQLUINTEGER *) Value) = dbc->attr.access_mode;
		break;
	case SQL_ATTR_CURRENT_CATALOG:
		p = tds_dstr_cstr(&dbc->attr.current_catalog);
		break;
	case SQL_ATTR_LOGIN_TIMEOUT:
		*((SQLUINTEGER *) Value) = dbc->attr.login_timeout;
		break;
	case SQL_ATTR_ODBC_CURSORS:
		*((SQLUINTEGER *) Value) = dbc->attr.odbc_cursors;
		break;
	case SQL_ATTR_PACKET_SIZE:
		*((SQLUINTEGER *) Value) = dbc->attr.packet_size;
		break;
	case SQL_ATTR_QUIET_MODE:
		*((SQLHWND *) Value) = dbc->attr.quite_mode;
		break;
#ifdef TDS_NO_DM
	case SQL_ATTR_TRACE:
		*((SQLUINTEGER *) Value) = dbc->attr.trace;
		break;
	case SQL_ATTR_TRACEFILE:
		p = tds_dstr_cstr(&dbc->attr.tracefile);
		break;
#endif
	case SQL_ATTR_TXN_ISOLATION:
		*((SQLUINTEGER *) Value) = dbc->attr.txn_isolation;
		break;
	case SQL_COPT_SS_MARS_ENABLED:
		*((SQLUINTEGER *) Value) = dbc->attr.mars_enabled;
		break;
	case SQL_ATTR_TRANSLATE_LIB:
	case SQL_ATTR_TRANSLATE_OPTION:
		odbc_errs_add(&dbc->errs, "HYC00", NULL);
		break;
	case SQL_COPT_SS_BCP:
		*((SQLUINTEGER *) Value) = dbc->attr.bulk_enabled;
		break;
	default:
		odbc_errs_add(&dbc->errs, "HY092", NULL);
		break;
	}

	if (p) {
		SQLRETURN rc = odbc_set_string_oct(dbc, Value, BufferLength, StringLength, p, -1);
		ODBC_EXIT(dbc, rc);
	}
	ODBC_EXIT_(dbc);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLGetConnectOption(SQLHDBC hdbc, SQLUSMALLINT fOption, SQLPOINTER pvParam)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLGetConnectOption(%p, %u, %p)\n", hdbc, fOption, pvParam);

	return odbc_SQLGetConnectAttr(hdbc, (SQLINTEGER) fOption, pvParam, SQL_MAX_OPTION_STRING_LENGTH, NULL _wide0);
}

#ifdef ENABLE_ODBC_WIDE
SQLRETURN ODBC_PUBLIC ODBC_API
SQLGetConnectOptionW(SQLHDBC hdbc, SQLUSMALLINT fOption, SQLPOINTER pvParam)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLGetConnectOptionW(%p, %u, %p)\n", hdbc, fOption, pvParam);

	return odbc_SQLGetConnectAttr(hdbc, (SQLINTEGER) fOption, pvParam, SQL_MAX_OPTION_STRING_LENGTH, NULL, 1);
}
#endif

SQLRETURN ODBC_PUBLIC ODBC_API
SQLGetData(SQLHSTMT hstmt, SQLUSMALLINT icol, SQLSMALLINT fCType, SQLPOINTER rgbValue, SQLLEN cbValueMax, SQLLEN FAR * pcbValue)
{
	/* TODO cursors fetch row if needed ?? */
	TDSCOLUMN *colinfo;
	TDSRESULTINFO *resinfo;
	SQLLEN dummy_cb;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLGetData(%p, %u, %d, %p, %d, %p)\n",
			hstmt, icol, fCType, rgbValue, (int)cbValueMax, pcbValue);

	if (cbValueMax < 0) {
		odbc_errs_add(&stmt->errs, "HY090", NULL);
		ODBC_EXIT_(stmt);
	}

	/* read data from TDS only if current statement */
	if ((stmt->cursor == NULL && !stmt->tds)
		|| stmt->row_status == PRE_NORMAL_ROW
		|| stmt->row_status == NOT_IN_ROW)
	{
		odbc_errs_add(&stmt->errs, "24000", NULL);
		ODBC_EXIT_(stmt);
	}

	IRD_CHECK;

	if (!pcbValue)
		pcbValue = &dummy_cb;

	resinfo = stmt->cursor ? stmt->cursor->res_info : stmt->tds->current_results;
	if (!resinfo) {
		odbc_errs_add(&stmt->errs, "HY010", NULL);
		ODBC_EXIT_(stmt);
	}
	if (icol <= 0 || icol > resinfo->num_cols) {
		odbc_errs_add(&stmt->errs, "07009", "Column out of range");
		ODBC_EXIT_(stmt);
	}
	colinfo = resinfo->columns[icol - 1];

	if (colinfo->column_cur_size < 0) {
		/* TODO check what should happen if pcbValue was NULL */
		*pcbValue = SQL_NULL_DATA;
	} else {
		if (colinfo->column_text_sqlgetdatapos > 0
		    && colinfo->column_text_sqlgetdatapos >= colinfo->column_cur_size
		    && colinfo->column_iconv_left == 0)
			/* TODO check if SQL_SUCCESS instead !! */
			ODBC_EXIT(stmt, SQL_NO_DATA);

		if (!is_variable_type(colinfo->column_type)) {
			colinfo->column_text_sqlgetdatapos = 0;
			colinfo->column_iconv_left = 0;
		}

		if (fCType == SQL_C_DEFAULT)
			fCType = odbc_sql_to_c_type_default(stmt->ird->records[icol - 1].sql_desc_concise_type);
		if (fCType == SQL_ARD_TYPE) {
			if (icol > stmt->ard->header.sql_desc_count) {
				odbc_errs_add(&stmt->errs, "07009", NULL);
				ODBC_EXIT_(stmt);
			}
			fCType = stmt->ard->records[icol - 1].sql_desc_concise_type;
		}
		assert(fCType);

		*pcbValue = odbc_tds2sql_col(stmt, colinfo, fCType, (TDS_CHAR *) rgbValue, cbValueMax, NULL);
		if (*pcbValue == SQL_NULL_DATA)
			ODBC_EXIT(stmt, SQL_ERROR);

		if (is_variable_type(colinfo->column_type)
		    && (fCType == SQL_C_CHAR || fCType == SQL_C_WCHAR || fCType == SQL_C_BINARY)) {
			/* avoid infinite SQL_SUCCESS on empty strings */
			if (colinfo->column_text_sqlgetdatapos == 0 && cbValueMax > 0)
				++colinfo->column_text_sqlgetdatapos;

			if (colinfo->column_text_sqlgetdatapos < colinfo->column_cur_size || colinfo->column_iconv_left != 0) {
				/* not all read ?? */
				odbc_errs_add(&stmt->errs, "01004", "String data, right truncated");
				ODBC_EXIT_(stmt);
			}
		} else {
			int nSybType = tds_get_conversion_type(colinfo->on_server.column_type, colinfo->on_server.column_size);
			colinfo->column_text_sqlgetdatapos = colinfo->column_cur_size;
			if (is_fixed_type(nSybType) && (fCType == SQL_C_CHAR || fCType == SQL_C_WCHAR || fCType == SQL_C_BINARY)
			    && cbValueMax < *pcbValue) {
				odbc_errs_add(&stmt->errs, "22003", NULL);
				ODBC_EXIT_(stmt);
			}
		}
	}
	ODBC_EXIT_(stmt);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLGetFunctions(SQLHDBC hdbc, SQLUSMALLINT fFunction, SQLUSMALLINT FAR * pfExists)
{
	int i;

	ODBC_ENTER_HDBC;

	tdsdump_log(TDS_DBG_FUNC, "SQLGetFunctions: fFunction is %d\n", fFunction);
	switch (fFunction) {
#if (ODBCVER >= 0x0300)
	case SQL_API_ODBC3_ALL_FUNCTIONS:
		for (i = 0; i < SQL_API_ODBC3_ALL_FUNCTIONS_SIZE; ++i) {
			pfExists[i] = 0;
		}

		/*
		 * every api available are contained in a macro
		 * all these macro begin with API followed by 2 letter
		 * first letter mean pre ODBC 3 (_) or ODBC 3 (3)
		 * second letter mean implemented (X) or unimplemented (_)
		 * You should copy these macro 3 times... not very good
		 * but works. Perhaps best method is build the bit array statically
		 * and then use it but I don't know how to build it...
		 */
#undef ODBC_ALL_API
#undef ODBC_COLATTRIBUTE

#if SQL_API_SQLCOLATTRIBUTE != SQL_API_SQLCOLATTRIBUTES
#define ODBC_COLATTRIBUTE(s) s
#else
#define ODBC_COLATTRIBUTE(s)
#endif

#define ODBC_ALL_API \
	API_X(SQL_API_SQLALLOCCONNECT)\
	API_X(SQL_API_SQLALLOCENV)\
	API3X(SQL_API_SQLALLOCHANDLE)\
	API_X(SQL_API_SQLALLOCSTMT)\
	API_X(SQL_API_SQLBINDCOL)\
	API_X(SQL_API_SQLBINDPARAM)\
	API_X(SQL_API_SQLBINDPARAMETER)\
	API__(SQL_API_SQLBROWSECONNECT)\
	API3_(SQL_API_SQLBULKOPERATIONS)\
	API_X(SQL_API_SQLCANCEL)\
	API3X(SQL_API_SQLCLOSECURSOR)\
	ODBC_COLATTRIBUTE(API3X(SQL_API_SQLCOLATTRIBUTE))\
	API_X(SQL_API_SQLCOLATTRIBUTES)\
	API_X(SQL_API_SQLCOLUMNPRIVILEGES)\
	API_X(SQL_API_SQLCOLUMNS)\
	API_X(SQL_API_SQLCONNECT)\
	API3X(SQL_API_SQLCOPYDESC)\
	API_X(SQL_API_SQLDESCRIBECOL)\
	API_X(SQL_API_SQLDESCRIBEPARAM)\
	API_X(SQL_API_SQLDISCONNECT)\
	API_X(SQL_API_SQLDRIVERCONNECT)\
	API3X(SQL_API_SQLENDTRAN)\
	API_X(SQL_API_SQLERROR)\
	API_X(SQL_API_SQLEXECDIRECT)\
	API_X(SQL_API_SQLEXECUTE)\
	API_X(SQL_API_SQLEXTENDEDFETCH)\
	API_X(SQL_API_SQLFETCH)\
	API3X(SQL_API_SQLFETCHSCROLL)\
	API_X(SQL_API_SQLFOREIGNKEYS)\
	API_X(SQL_API_SQLFREECONNECT)\
	API_X(SQL_API_SQLFREEENV)\
	API3X(SQL_API_SQLFREEHANDLE)\
	API_X(SQL_API_SQLFREESTMT)\
	API3X(SQL_API_SQLGETCONNECTATTR)\
	API_X(SQL_API_SQLGETCONNECTOPTION)\
	API_X(SQL_API_SQLGETCURSORNAME)\
	API_X(SQL_API_SQLGETDATA)\
	API3X(SQL_API_SQLGETDESCFIELD)\
	API3X(SQL_API_SQLGETDESCREC)\
	API3X(SQL_API_SQLGETDIAGFIELD)\
	API3X(SQL_API_SQLGETDIAGREC)\
	API3X(SQL_API_SQLGETENVATTR)\
	API_X(SQL_API_SQLGETFUNCTIONS)\
	API_X(SQL_API_SQLGETINFO)\
	API3X(SQL_API_SQLGETSTMTATTR)\
	API_X(SQL_API_SQLGETSTMTOPTION)\
	API_X(SQL_API_SQLGETTYPEINFO)\
	API_X(SQL_API_SQLMORERESULTS)\
	API_X(SQL_API_SQLNATIVESQL)\
	API_X(SQL_API_SQLNUMPARAMS)\
	API_X(SQL_API_SQLNUMRESULTCOLS)\
	API_X(SQL_API_SQLPARAMDATA)\
	API_X(SQL_API_SQLPARAMOPTIONS)\
	API_X(SQL_API_SQLPREPARE)\
	API_X(SQL_API_SQLPRIMARYKEYS)\
	API_X(SQL_API_SQLPROCEDURECOLUMNS)\
	API_X(SQL_API_SQLPROCEDURES)\
	API_X(SQL_API_SQLPUTDATA)\
	API_X(SQL_API_SQLROWCOUNT)\
	API3X(SQL_API_SQLSETCONNECTATTR)\
	API_X(SQL_API_SQLSETCONNECTOPTION)\
	API_X(SQL_API_SQLSETCURSORNAME)\
	API3X(SQL_API_SQLSETDESCFIELD)\
	API3X(SQL_API_SQLSETDESCREC)\
	API3X(SQL_API_SQLSETENVATTR)\
	API_X(SQL_API_SQLSETPARAM)\
	API_X(SQL_API_SQLSETPOS)\
	API_X(SQL_API_SQLSETSCROLLOPTIONS)\
	API3X(SQL_API_SQLSETSTMTATTR)\
	API_X(SQL_API_SQLSETSTMTOPTION)\
	API_X(SQL_API_SQLSPECIALCOLUMNS)\
	API_X(SQL_API_SQLSTATISTICS)\
	API_X(SQL_API_SQLTABLEPRIVILEGES)\
	API_X(SQL_API_SQLTABLES)\
	API_X(SQL_API_SQLTRANSACT)

#define API_X(n) if (n >= 0 && n < (16*SQL_API_ODBC3_ALL_FUNCTIONS_SIZE)) pfExists[n/16] |= (1 << n%16);
#define API__(n)
#define API3X(n) if (n >= 0 && n < (16*SQL_API_ODBC3_ALL_FUNCTIONS_SIZE)) pfExists[n/16] |= (1 << n%16);
#define API3_(n)
		ODBC_ALL_API
#undef API_X
#undef API__
#undef API3X
#undef API3_
		break;
#endif

	case SQL_API_ALL_FUNCTIONS:
		tdsdump_log(TDS_DBG_FUNC, "SQLGetFunctions: " "fFunction is SQL_API_ALL_FUNCTIONS\n");
		for (i = 0; i < 100; ++i) {
			pfExists[i] = SQL_FALSE;
		}

#define API_X(n) if (n >= 0 && n < 100) pfExists[n] = SQL_TRUE;
#define API__(n)
#define API3X(n)
#define API3_(n)
		ODBC_ALL_API
#undef API_X
#undef API__
#undef API3X
#undef API3_
		break;
#define API_X(n) case n:
#define API__(n)
#if (ODBCVER >= 0x300)
#define API3X(n) case n:
#else
#define API3X(n)
#endif
#define API3_(n)
		ODBC_ALL_API
#undef API_X
#undef API__
#undef API3X
#undef API3_
		*pfExists = SQL_TRUE;
		break;
	default:
		*pfExists = SQL_FALSE;
		break;
	}
	ODBC_EXIT(dbc, SQL_SUCCESS);
#undef ODBC_ALL_API
}


static SQLRETURN
odbc_SQLGetInfo(TDS_DBC * dbc, SQLUSMALLINT fInfoType, SQLPOINTER rgbInfoValue, SQLSMALLINT cbInfoValueMax,
	    SQLSMALLINT FAR * pcbInfoValue _WIDE)
{
	const char *p = NULL;
	char buf[32];
	TDSSOCKET *tds;
	int is_ms = -1;
	unsigned int smajor = 6;
	SQLUINTEGER mssql7plus_mask = 0;
	int out_len = -1;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLGetInfo(%p, %u, %p, %d, %p)\n",
			dbc, fInfoType, rgbInfoValue, cbInfoValueMax, pcbInfoValue);

#define SIVAL out_len = sizeof(SQLSMALLINT), *((SQLSMALLINT *) rgbInfoValue)
#define USIVAL out_len = sizeof(SQLUSMALLINT), *((SQLUSMALLINT *) rgbInfoValue)
#define IVAL out_len = sizeof(SQLINTEGER), *((SQLINTEGER *) rgbInfoValue)
#define UIVAL out_len = sizeof(SQLUINTEGER), *((SQLUINTEGER *) rgbInfoValue)
#define ULVAL out_len = sizeof(SQLULEN), *((SQLULEN *) rgbInfoValue)

	if ((tds = dbc->tds_socket) != NULL) {
		is_ms = TDS_IS_MSSQL(tds);
		smajor = (tds->conn->product_version >> 24) & 0x7F;
		if (is_ms && smajor >= 7)
			mssql7plus_mask = ~((SQLUINTEGER) 0);
	}

	switch (fInfoType) {
	case SQL_ACCESSIBLE_PROCEDURES:
	case SQL_ACCESSIBLE_TABLES:
		p = "Y";
		break;
		/* SQL_ACTIVE_CONNECTIONS renamed to SQL_MAX_DRIVER_CONNECTIONS */
#if (ODBCVER >= 0x0300)
	case SQL_ACTIVE_ENVIRONMENTS:
		UIVAL = 0;
		break;
#endif /* ODBCVER >= 0x0300 */
#if (ODBCVER >= 0x0300)
	case SQL_AGGREGATE_FUNCTIONS:
		UIVAL = SQL_AF_ALL;
		break;
	case SQL_ALTER_DOMAIN:
		UIVAL = 0;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_ALTER_TABLE:
		UIVAL = SQL_AT_ADD_COLUMN | SQL_AT_ADD_COLUMN_DEFAULT | SQL_AT_ADD_COLUMN_SINGLE | SQL_AT_ADD_CONSTRAINT |
			SQL_AT_ADD_TABLE_CONSTRAINT | SQL_AT_CONSTRAINT_NAME_DEFINITION | SQL_AT_DROP_COLUMN_RESTRICT;
		break;
#if (ODBCVER >= 0x0300)
	case SQL_ASYNC_MODE:
		/* TODO we hope so in a not-too-far future */
		/* UIVAL = SQL_AM_STATEMENT; */
		UIVAL = SQL_AM_NONE;
		break;
	case SQL_BATCH_ROW_COUNT:
		UIVAL = SQL_BRC_EXPLICIT;
		break;
	case SQL_BATCH_SUPPORT:
		UIVAL = SQL_BS_ROW_COUNT_EXPLICIT | SQL_BS_ROW_COUNT_PROC | SQL_BS_SELECT_EXPLICIT | SQL_BS_SELECT_PROC;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_BOOKMARK_PERSISTENCE:
		/* TODO ??? */
		UIVAL = SQL_BP_DELETE | SQL_BP_SCROLL | SQL_BP_UPDATE;
		break;
	case SQL_CATALOG_LOCATION:
		SIVAL = SQL_CL_START;
		break;
#if (ODBCVER >= 0x0300)
	case SQL_CATALOG_NAME:
		p = "Y";
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_CATALOG_NAME_SEPARATOR:
		p = ".";
		break;
	case SQL_CATALOG_TERM:
		p = "database";
		break;
	case SQL_CATALOG_USAGE:
		UIVAL = SQL_CU_DML_STATEMENTS | SQL_CU_PROCEDURE_INVOCATION | SQL_CU_TABLE_DEFINITION;
		break;
		/* TODO */
#if 0
	case SQL_COLLATION_SEQ:
		break;
#endif
	case SQL_COLUMN_ALIAS:
		p = "Y";
		break;
	case SQL_CONCAT_NULL_BEHAVIOR:
		if (is_ms == -1)
			return SQL_ERROR;
		/* TODO a bit more complicate for mssql2k.. */
		SIVAL = (!is_ms || smajor < 7) ? SQL_CB_NON_NULL : SQL_CB_NULL;
		break;
		/* TODO SQL_CONVERT_BIGINT SQL_CONVERT_GUID SQL_CONVERT_DATE SQL_CONVERT_TIME */
		/* For Sybase/MSSql6.x we return 0 cause NativeSql is not so implemented */
	case SQL_CONVERT_BINARY:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_VARCHAR |
			 SQL_CVT_BINARY | SQL_CVT_VARBINARY | SQL_CVT_TINYINT | SQL_CVT_LONGVARBINARY | SQL_CVT_WCHAR |
			 SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
	case SQL_CONVERT_BIT:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_FLOAT |
			 SQL_CVT_REAL | SQL_CVT_VARCHAR | SQL_CVT_BINARY | SQL_CVT_VARBINARY | SQL_CVT_BIT | SQL_CVT_TINYINT |
			 SQL_CVT_WCHAR | SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
	case SQL_CONVERT_CHAR:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_FLOAT |
			 SQL_CVT_REAL | SQL_CVT_VARCHAR | SQL_CVT_LONGVARCHAR | SQL_CVT_BINARY | SQL_CVT_VARBINARY | SQL_CVT_BIT |
			 SQL_CVT_TINYINT | SQL_CVT_TIMESTAMP | SQL_CVT_LONGVARBINARY | SQL_CVT_WCHAR | SQL_CVT_WLONGVARCHAR |
			 SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
	case SQL_CONVERT_DECIMAL:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_FLOAT |
			 SQL_CVT_REAL | SQL_CVT_VARCHAR | SQL_CVT_BINARY | SQL_CVT_VARBINARY | SQL_CVT_BIT | SQL_CVT_TINYINT |
			 SQL_CVT_WCHAR | SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
	case SQL_CONVERT_FLOAT:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_FLOAT |
			 SQL_CVT_REAL | SQL_CVT_VARCHAR | SQL_CVT_BIT | SQL_CVT_TINYINT | SQL_CVT_WCHAR | SQL_CVT_WVARCHAR) &
			mssql7plus_mask;
		break;
	case SQL_CONVERT_INTEGER:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_FLOAT |
			 SQL_CVT_REAL | SQL_CVT_VARCHAR | SQL_CVT_BINARY | SQL_CVT_VARBINARY | SQL_CVT_BIT | SQL_CVT_TINYINT |
			 SQL_CVT_WCHAR | SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
	case SQL_CONVERT_LONGVARCHAR:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_VARCHAR | SQL_CVT_LONGVARCHAR | SQL_CVT_WCHAR | SQL_CVT_WLONGVARCHAR |
			 SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
	case SQL_CONVERT_NUMERIC:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_FLOAT |
			 SQL_CVT_REAL | SQL_CVT_VARCHAR | SQL_CVT_BINARY | SQL_CVT_VARBINARY | SQL_CVT_BIT | SQL_CVT_TINYINT |
			 SQL_CVT_WCHAR | SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
	case SQL_CONVERT_REAL:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_FLOAT |
			 SQL_CVT_REAL | SQL_CVT_VARCHAR | SQL_CVT_BIT | SQL_CVT_TINYINT | SQL_CVT_WCHAR | SQL_CVT_WVARCHAR) &
			mssql7plus_mask;
		break;
	case SQL_CONVERT_SMALLINT:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_FLOAT |
			 SQL_CVT_REAL | SQL_CVT_VARCHAR | SQL_CVT_BINARY | SQL_CVT_VARBINARY | SQL_CVT_BIT | SQL_CVT_TINYINT |
			 SQL_CVT_WCHAR | SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
	case SQL_CONVERT_TIMESTAMP:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_VARCHAR | SQL_CVT_BINARY | SQL_CVT_VARBINARY | SQL_CVT_TIMESTAMP | SQL_CVT_WCHAR |
			 SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
	case SQL_CONVERT_TINYINT:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_FLOAT |
			 SQL_CVT_REAL | SQL_CVT_VARCHAR | SQL_CVT_BINARY | SQL_CVT_VARBINARY | SQL_CVT_BIT | SQL_CVT_TINYINT |
			 SQL_CVT_WCHAR | SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
	case SQL_CONVERT_VARBINARY:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_VARCHAR |
			 SQL_CVT_BINARY | SQL_CVT_VARBINARY | SQL_CVT_TINYINT | SQL_CVT_LONGVARBINARY | SQL_CVT_WCHAR |
			 SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
	case SQL_CONVERT_VARCHAR:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_FLOAT |
			 SQL_CVT_REAL | SQL_CVT_VARCHAR | SQL_CVT_LONGVARCHAR | SQL_CVT_BINARY | SQL_CVT_VARBINARY | SQL_CVT_BIT |
			 SQL_CVT_TINYINT | SQL_CVT_TIMESTAMP | SQL_CVT_LONGVARBINARY | SQL_CVT_WCHAR | SQL_CVT_WLONGVARCHAR |
			 SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
	case SQL_CONVERT_LONGVARBINARY:
		UIVAL = (SQL_CVT_BINARY | SQL_CVT_LONGVARBINARY | SQL_CVT_VARBINARY) & mssql7plus_mask;
		break;
	case SQL_CONVERT_WLONGVARCHAR:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_VARCHAR | SQL_CVT_LONGVARCHAR | SQL_CVT_WCHAR | SQL_CVT_WLONGVARCHAR |
			 SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
#if (ODBCVER >= 0x0300)
	case SQL_CONVERT_WCHAR:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_FLOAT |
			 SQL_CVT_REAL | SQL_CVT_VARCHAR | SQL_CVT_LONGVARCHAR | SQL_CVT_BINARY | SQL_CVT_VARBINARY | SQL_CVT_BIT |
			 SQL_CVT_TINYINT | SQL_CVT_TIMESTAMP | SQL_CVT_LONGVARBINARY | SQL_CVT_WCHAR | SQL_CVT_WLONGVARCHAR |
			 SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
	case SQL_CONVERT_WVARCHAR:
		UIVAL = (SQL_CVT_CHAR | SQL_CVT_NUMERIC | SQL_CVT_DECIMAL | SQL_CVT_INTEGER | SQL_CVT_SMALLINT | SQL_CVT_FLOAT |
			 SQL_CVT_REAL | SQL_CVT_VARCHAR | SQL_CVT_LONGVARCHAR | SQL_CVT_BINARY | SQL_CVT_VARBINARY | SQL_CVT_BIT |
			 SQL_CVT_TINYINT | SQL_CVT_TIMESTAMP | SQL_CVT_LONGVARBINARY | SQL_CVT_WCHAR | SQL_CVT_WLONGVARCHAR |
			 SQL_CVT_WVARCHAR) & mssql7plus_mask;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_CONVERT_FUNCTIONS:
		/* TODO no CAST for Sybase ?? */
		UIVAL = SQL_FN_CVT_CONVERT | SQL_FN_CVT_CAST;
		break;
#if (ODBCVER >= 0x0300)
	case SQL_CREATE_ASSERTION:
	case SQL_CREATE_CHARACTER_SET:
	case SQL_CREATE_COLLATION:
	case SQL_CREATE_DOMAIN:
		UIVAL = 0;
		break;
	case SQL_CREATE_SCHEMA:
		UIVAL = SQL_CS_AUTHORIZATION | SQL_CS_CREATE_SCHEMA;
		break;
	case SQL_CREATE_TABLE:
		UIVAL = SQL_CT_CREATE_TABLE;
		break;
	case SQL_CREATE_TRANSLATION:
		UIVAL = 0;
		break;
	case SQL_CREATE_VIEW:
		UIVAL = SQL_CV_CHECK_OPTION | SQL_CV_CREATE_VIEW;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_CORRELATION_NAME:
		USIVAL = SQL_CN_ANY;
		break;
	case SQL_CURSOR_COMMIT_BEHAVIOR:
		/* currently cursors are not supported however sql server close automaticly cursors on commit */
		/* TODO cursors test what happen if rollback, cursors get properly deleted ?? */
		USIVAL = SQL_CB_CLOSE;
		break;
	case SQL_CURSOR_ROLLBACK_BEHAVIOR:
		USIVAL = SQL_CB_CLOSE;
		break;
	case SQL_CURSOR_SENSITIVITY:
		UIVAL = SQL_SENSITIVE;
		break;
	case SQL_DATABASE_NAME:
		p = tds_dstr_cstr(&dbc->attr.current_catalog);
		break;
	case SQL_DATA_SOURCE_NAME:
		p = tds_dstr_cstr(&dbc->dsn);
		break;
	case SQL_DATA_SOURCE_READ_ONLY:
		/*
		 * TODO: determine the right answer from connection
		 * attribute SQL_ATTR_ACCESS_MODE
		 */
		p = "N";	/* false, writable */
		break;
#if (ODBCVER >= 0x0300)
	case SQL_DATETIME_LITERALS:
		/* TODO ok ?? */
		UIVAL = 0;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_DBMS_NAME:
		p = tds ? tds->conn->product_name : NULL;
		break;
	case SQL_DBMS_VER:
		if (!dbc->tds_socket)
			return SQL_ERROR;
		odbc_rdbms_version(dbc->tds_socket, buf);
		p = buf;
		break;
#if (ODBCVER >= 0x0300)
	case SQL_DDL_INDEX:
		UIVAL = 0;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_DEFAULT_TXN_ISOLATION:
		UIVAL = SQL_TXN_READ_COMMITTED;
		break;
#if (ODBCVER >= 0x0300)
	case SQL_DESCRIBE_PARAMETER:
		/* TODO */
		p = "N";
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_DRIVER_HDBC:
		ULVAL = (SQLULEN) dbc;
		break;
	case SQL_DRIVER_HENV:
		ULVAL = (SQLULEN) dbc->env;
		break;
	case SQL_DRIVER_HSTMT: {
			void *ptr = *((void**) rgbInfoValue);
			if (SQL_NULL_HSTMT != ptr && !IS_HSTMT(ptr))
				ULVAL = SQL_NULL_HSTMT;
		}
		break;
	case SQL_DRIVER_HDESC: {
			void *ptr = *((void**) rgbInfoValue);
			if (SQL_NULL_HDESC != ptr && !IS_HDESC(ptr))
				ULVAL = SQL_NULL_HDESC;
		}
		break;
	case SQL_DRIVER_NAME:	/* ODBC 2.0 */
		p = "libtdsodbc.so";
		break;
	case SQL_DRIVER_ODBC_VER:
		p = "03.50";
		break;
	case SQL_DRIVER_VER:
		sprintf(buf, "%02d.%02d.%04d", TDS_VERSION_MAJOR,
			TDS_VERSION_MINOR, TDS_VERSION_SUBVERSION);
		p = buf;
		break;
#if (ODBCVER >= 0x0300)
	case SQL_DROP_ASSERTION:
	case SQL_DROP_CHARACTER_SET:
	case SQL_DROP_COLLATION:
	case SQL_DROP_DOMAIN:
	case SQL_DROP_SCHEMA:
		UIVAL = 0;
		break;
	case SQL_DROP_TABLE:
		UIVAL = SQL_DT_DROP_TABLE;
		break;
	case SQL_DROP_TRANSLATION:
		UIVAL = 0;
		break;
	case SQL_DROP_VIEW:
		UIVAL = SQL_DV_DROP_VIEW;
		break;
	case SQL_DYNAMIC_CURSOR_ATTRIBUTES1:
		if (dbc->cursor_support)
			/* TODO cursor SQL_CA1_BULK_ADD SQL_CA1_POS_REFRESH SQL_CA1_SELECT_FOR_UPDATE */
			UIVAL = SQL_CA1_ABSOLUTE | SQL_CA1_NEXT | SQL_CA1_RELATIVE |
				SQL_CA1_LOCK_NO_CHANGE |
				SQL_CA1_POS_DELETE | SQL_CA1_POS_POSITION | SQL_CA1_POS_UPDATE |
				SQL_CA1_POSITIONED_UPDATE | SQL_CA1_POSITIONED_DELETE;
		else
			UIVAL = 0;
		break;
	case SQL_DYNAMIC_CURSOR_ATTRIBUTES2:
		/* TODO cursors */
		/* Cursors not supported yet */
		/*
		 * Should be SQL_CA2_LOCK_CONCURRENCY SQL_CA2_MAX_ROWS_CATALOG SQL_CA2_MAX_ROWS_DELETE
 		 * SQL_CA2_MAX_ROWS_INSERT SQL_CA2_MAX_ROWS_SELECT SQL_CA2_MAX_ROWS_UPDATE SQL_CA2_OPT_ROWVER_CONCURRENCY
 		 * SQL_CA2_OPT_VALUES_CONCURRENCY SQL_CA2_READ_ONLY_CONCURRENCY SQL_CA2_SENSITIVITY_ADDITIONS
 		 * SQL_CA2_SENSITIVITY_UPDATES SQL_CA2_SIMULATE_UNIQUE
		 */
		UIVAL = 0;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_EXPRESSIONS_IN_ORDERBY:
		p = "Y";
		break;
	case SQL_FILE_USAGE:
		USIVAL = SQL_FILE_NOT_SUPPORTED;
		break;
	case SQL_FETCH_DIRECTION:
		if (dbc->cursor_support)
			/* TODO cursors SQL_FD_FETCH_BOOKMARK */
			UIVAL = SQL_FD_FETCH_ABSOLUTE|SQL_FD_FETCH_FIRST|SQL_FD_FETCH_LAST|SQL_FD_FETCH_NEXT
				|SQL_FD_FETCH_PRIOR|SQL_FD_FETCH_RELATIVE;
		else
			UIVAL = 0;
		break;
#if (ODBCVER >= 0x0300)
	case SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1:
		if (dbc->cursor_support)
			/* TODO cursors SQL_CA1_SELECT_FOR_UPDATE */
			UIVAL = SQL_CA1_NEXT|SQL_CA1_POSITIONED_DELETE|SQL_CA1_POSITIONED_UPDATE;
		else
			UIVAL = 0;
		break;
	case SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2:
		/* TODO cursors */
		/* Cursors not supported yet */
		/*
		 * Should be SQL_CA2_LOCK_CONCURRENCY SQL_CA2_MAX_ROWS_CATALOG SQL_CA2_MAX_ROWS_DELETE
 		 * SQL_CA2_MAX_ROWS_INSERT SQL_CA2_MAX_ROWS_SELECT SQL_CA2_MAX_ROWS_UPDATE SQL_CA2_OPT_ROWVER_CONCURRENCY
 		 * SQL_CA2_OPT_VALUES_CONCURRENCY SQL_CA2_READ_ONLY_CONCURRENCY
		 */
		UIVAL = 0;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_GETDATA_EXTENSIONS:
		/* TODO FreeTDS support more ?? */
		UIVAL = SQL_GD_BLOCK;
		break;
	case SQL_GROUP_BY:
		USIVAL = SQL_GB_GROUP_BY_CONTAINS_SELECT;
		break;
	case SQL_IDENTIFIER_CASE:
		/* TODO configuration dependend */
		USIVAL = SQL_IC_MIXED;
		break;
	case SQL_IDENTIFIER_QUOTE_CHAR:
		p = "\"";
		/* TODO workaround for Sybase, use SET QUOTED_IDENTIFIER ON and fix unwanted quote */
		if (!is_ms)
			p = "";
		break;
#if (ODBCVER >= 0x0300)
	case SQL_INDEX_KEYWORDS:
		UIVAL = SQL_IK_ASC | SQL_IK_DESC;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_INFO_SCHEMA_VIEWS:
		/* TODO finish */
		UIVAL = 0;
		break;
	case SQL_INSERT_STATEMENT:
		UIVAL = 0;
		break;
		/* renamed from SQL_ODBC_SQL_OPT_IEF */
	case SQL_INTEGRITY:
		p = "Y";
		break;
	case SQL_KEYSET_CURSOR_ATTRIBUTES1:
	case SQL_KEYSET_CURSOR_ATTRIBUTES2:
		/* TODO cursors */
		UIVAL = 0;
		break;
	case SQL_KEYWORDS:
		/* TODO ok for Sybase ? */
		p = "BREAK,BROWSE,BULK,CHECKPOINT,CLUSTERED,"
			"COMMITTED,COMPUTE,CONFIRM,CONTROLROW,DATABASE,"
			"DBCC,DISK,DISTRIBUTED,DUMMY,DUMP,ERRLVL,"
			"ERROREXIT,EXIT,FILE,FILLFACTOR,FLOPPY,HOLDLOCK,"
			"IDENTITY_INSERT,IDENTITYCOL,IF,KILL,LINENO,"
			"LOAD,MIRROREXIT,NONCLUSTERED,OFF,OFFSETS,ONCE,"
			"OVER,PERCENT,PERM,PERMANENT,PLAN,PRINT,PROC,"
			"PROCESSEXIT,RAISERROR,READ,READTEXT,RECONFIGURE,"
			"REPEATABLE,RETURN,ROWCOUNT,RULE,SAVE,SERIALIZABLE,"
			"SETUSER,SHUTDOWN,STATISTICS,TAPE,TEMP,TEXTSIZE,"
			"TRAN,TRIGGER,TRUNCATE,TSEQUEL,UNCOMMITTED," "UPDATETEXT,USE,WAITFOR,WHILE,WRITETEXT";
		break;
	case SQL_LIKE_ESCAPE_CLAUSE:
		p = "Y";
		break;
	case SQL_LOCK_TYPES:
		if (dbc->cursor_support)
			IVAL = SQL_LCK_NO_CHANGE;
		else
			IVAL = 0;
		break;
#if (ODBCVER >= 0x0300)
	case SQL_MAX_ASYNC_CONCURRENT_STATEMENTS:
		UIVAL = 1;
		break;
#endif /* ODBCVER >= 0x0300 */
		/* TODO same limits for Sybase ? */
	case SQL_MAX_BINARY_LITERAL_LEN:
		UIVAL = 131072;
		break;
	case SQL_MAX_CHAR_LITERAL_LEN:
		UIVAL = 131072;
		break;
	case SQL_MAX_CONCURRENT_ACTIVITIES:
		USIVAL = 1;
		break;
	case SQL_MAX_COLUMNS_IN_GROUP_BY:
	case SQL_MAX_COLUMNS_IN_INDEX:
	case SQL_MAX_COLUMNS_IN_ORDER_BY:
		/* TODO Sybase ? */
		USIVAL = 16;
		break;
	case SQL_MAX_COLUMNS_IN_SELECT:
		/* TODO Sybase ? */
		USIVAL = 4000;
		break;
	case SQL_MAX_COLUMNS_IN_TABLE:
		/* TODO Sybase ? */
		USIVAL = 250;
		break;
	case SQL_MAX_DRIVER_CONNECTIONS:
		USIVAL = 0;
		break;
	case SQL_MAX_IDENTIFIER_LEN:
		if (is_ms == -1)
			return SQL_ERROR;
		USIVAL = (!is_ms || smajor < 7) ? 30 : 128;
		break;
	case SQL_MAX_INDEX_SIZE:
		UIVAL = 127;
		break;
	case SQL_MAX_PROCEDURE_NAME_LEN:
		if (is_ms == -1)
			return SQL_ERROR;
		/* TODO Sybase ? */
		USIVAL = (is_ms && smajor >= 7) ? 134 : 36;
		break;
	case SQL_MAX_ROW_SIZE:
		if (is_ms == -1)
			return SQL_ERROR;
		/* TODO Sybase ? */
		UIVAL = (is_ms && smajor >= 7) ? 8062 : 1962;
		break;
	case SQL_MAX_ROW_SIZE_INCLUDES_LONG:
		p = "N";
		break;
	case SQL_MAX_STATEMENT_LEN:
		/* TODO Sybase ? */
		UIVAL = 131072;
		break;
	case SQL_MAX_TABLES_IN_SELECT:
		/* TODO Sybase ? */
		USIVAL = 16;
		break;
	case SQL_MAX_USER_NAME_LEN:
		/* TODO Sybase ? */
		if (is_ms == -1)
			return SQL_ERROR;
		USIVAL = (is_ms && smajor >= 7) ? 128 : 30;
		break;
	case SQL_MAX_COLUMN_NAME_LEN:
	case SQL_MAX_CURSOR_NAME_LEN:
	case SQL_MAX_SCHEMA_NAME_LEN:
	case SQL_MAX_CATALOG_NAME_LEN:
	case SQL_MAX_TABLE_NAME_LEN:
		/* TODO Sybase ? */
		if (is_ms == -1)
			return SQL_ERROR;
		USIVAL = (is_ms && smajor >= 7) ? 128 : 30;
		break;
	case SQL_MULT_RESULT_SETS:
		p = "Y";
		break;
	case SQL_MULTIPLE_ACTIVE_TXN:
		p = "Y";
		break;
	case SQL_NEED_LONG_DATA_LEN:
		/* current implementation do not require length, however future will, so is correct to return yes */
		p = "Y";
		break;
	case SQL_NON_NULLABLE_COLUMNS:
		USIVAL = SQL_NNC_NON_NULL;
		break;
	case SQL_NULL_COLLATION:
		USIVAL = SQL_NC_LOW;
		break;
	case SQL_NUMERIC_FUNCTIONS:
		UIVAL = (SQL_FN_NUM_ABS | SQL_FN_NUM_ACOS | SQL_FN_NUM_ASIN | SQL_FN_NUM_ATAN | SQL_FN_NUM_ATAN2 |
			 SQL_FN_NUM_CEILING | SQL_FN_NUM_COS | SQL_FN_NUM_COT | SQL_FN_NUM_DEGREES | SQL_FN_NUM_EXP |
			 SQL_FN_NUM_FLOOR | SQL_FN_NUM_LOG | SQL_FN_NUM_LOG10 | SQL_FN_NUM_MOD | SQL_FN_NUM_PI | SQL_FN_NUM_POWER |
			 SQL_FN_NUM_RADIANS | SQL_FN_NUM_RAND | SQL_FN_NUM_ROUND | SQL_FN_NUM_SIGN | SQL_FN_NUM_SIN |
			 SQL_FN_NUM_SQRT | SQL_FN_NUM_TAN) & mssql7plus_mask;
		break;
	case SQL_ODBC_API_CONFORMANCE:
		SIVAL = SQL_OAC_LEVEL2;
		break;
#if (ODBCVER >= 0x0300)
	case SQL_ODBC_INTERFACE_CONFORMANCE:
		UIVAL = SQL_OAC_LEVEL2;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_ODBC_SAG_CLI_CONFORMANCE:
		SIVAL = SQL_OSCC_NOT_COMPLIANT;
		break;
	case SQL_ODBC_SQL_CONFORMANCE:
		SIVAL = SQL_OSC_CORE;
		break;
#ifdef TDS_NO_DM
	case SQL_ODBC_VER:
		/* TODO check format ##.##.0000 */
		p = VERSION;
		break;
#endif
#if (ODBCVER >= 0x0300)
	case SQL_OJ_CAPABILITIES:
		UIVAL = SQL_OJ_ALL_COMPARISON_OPS | SQL_OJ_FULL | SQL_OJ_INNER | SQL_OJ_LEFT | SQL_OJ_NESTED | SQL_OJ_NOT_ORDERED |
			SQL_OJ_RIGHT;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_ORDER_BY_COLUMNS_IN_SELECT:
		p = "N";
		break;
	case SQL_OUTER_JOINS:
		p = "Y";
		break;
#if (ODBCVER >= 0x0300)
	case SQL_PARAM_ARRAY_ROW_COUNTS:
		UIVAL = SQL_PARC_BATCH;
		break;
	case SQL_PARAM_ARRAY_SELECTS:
		UIVAL = SQL_PAS_BATCH;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_POS_OPERATIONS:
		if (dbc->cursor_support)
			/* TODO cursors SQL_POS_ADD SQL_POS_REFRESH */
			/* test REFRESH, ADD under mssql, under Sybase I don't know how to do it but dblib do */
			IVAL = SQL_POS_DELETE | SQL_POS_POSITION | SQL_POS_UPDATE;
		else
			IVAL = 0;
		break;
	case SQL_POSITIONED_STATEMENTS:
		/* TODO cursors SQL_PS_SELECT_FOR_UPDATE */
		IVAL = SQL_PS_POSITIONED_DELETE | SQL_PS_POSITIONED_UPDATE;
		break;
	case SQL_PROCEDURE_TERM:
		p = "stored procedure";
		break;
	case SQL_PROCEDURES:
		p = "Y";
		break;
	case SQL_QUOTED_IDENTIFIER_CASE:
		/* TODO usually insensitive */
		USIVAL = SQL_IC_MIXED;
		break;
	case SQL_ROW_UPDATES:
		p = "N";
		break;
#if (ODBCVER >= 0x0300)
	case SQL_SCHEMA_TERM:
		p = "owner";
		break;
	case SQL_SCHEMA_USAGE:
		UIVAL = SQL_OU_DML_STATEMENTS | SQL_OU_INDEX_DEFINITION | SQL_OU_PRIVILEGE_DEFINITION | SQL_OU_PROCEDURE_INVOCATION
			| SQL_OU_TABLE_DEFINITION;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_SCROLL_CONCURRENCY:
		if (dbc->cursor_support)
			/* TODO cursors test them, Sybase support all ?  */
			IVAL = SQL_SCCO_LOCK | SQL_SCCO_OPT_ROWVER | SQL_SCCO_OPT_VALUES | SQL_SCCO_READ_ONLY;
		else
			IVAL = SQL_SCCO_READ_ONLY;
		break;
	case SQL_SCROLL_OPTIONS:
		if (dbc->cursor_support)
			/* TODO cursors test them, Sybase support all ?? */
			UIVAL = SQL_SO_DYNAMIC | SQL_SO_FORWARD_ONLY | SQL_SO_KEYSET_DRIVEN | SQL_SO_STATIC;
		else
			UIVAL = SQL_SO_FORWARD_ONLY | SQL_SO_STATIC;
		break;
	case SQL_SEARCH_PATTERN_ESCAPE:
		p = "\\";
		break;
	case SQL_SERVER_NAME:
		p = dbc->tds_socket->conn->server;
		break;
	case SQL_SPECIAL_CHARACTERS:
		/* TODO others ?? */
		p = "\'\"[]{}";
		break;
#if (ODBCVER >= 0x0300)
	case SQL_SQL_CONFORMANCE:
		UIVAL = SQL_SC_SQL92_ENTRY;
		break;
	case SQL_SQL92_DATETIME_FUNCTIONS:
	case SQL_SQL92_FOREIGN_KEY_DELETE_RULE:
	case SQL_SQL92_FOREIGN_KEY_UPDATE_RULE:
		UIVAL = 0;
		break;
	case SQL_SQL92_GRANT:
		UIVAL = SQL_SG_WITH_GRANT_OPTION;
		break;
	case SQL_SQL92_NUMERIC_VALUE_FUNCTIONS:
		UIVAL = 0;
		break;
	case SQL_SQL92_PREDICATES:
		/* TODO Sybase ?? */
		UIVAL = SQL_SP_EXISTS | SQL_SP_ISNOTNULL | SQL_SP_ISNULL;
		break;
	case SQL_SQL92_RELATIONAL_JOIN_OPERATORS:
		/* TODO Sybase ?? */
		UIVAL = SQL_SRJO_CROSS_JOIN | SQL_SRJO_FULL_OUTER_JOIN | SQL_SRJO_INNER_JOIN | SQL_SRJO_LEFT_OUTER_JOIN |
			SQL_SRJO_RIGHT_OUTER_JOIN | SQL_SRJO_UNION_JOIN;
		break;
	case SQL_SQL92_REVOKE:
		UIVAL = SQL_SR_GRANT_OPTION_FOR;
		break;
	case SQL_SQL92_ROW_VALUE_CONSTRUCTOR:
		UIVAL = SQL_SRVC_DEFAULT | SQL_SRVC_NULL | SQL_SRVC_ROW_SUBQUERY | SQL_SRVC_VALUE_EXPRESSION;
		break;
	case SQL_SQL92_STRING_FUNCTIONS:
		UIVAL = SQL_SSF_LOWER | SQL_SSF_UPPER;
		break;
	case SQL_SQL92_VALUE_EXPRESSIONS:
		/* TODO Sybase ? CAST supported ? */
		UIVAL = SQL_SVE_CASE | SQL_SVE_CAST | SQL_SVE_COALESCE | SQL_SVE_NULLIF;
		break;
	case SQL_STANDARD_CLI_CONFORMANCE:
		UIVAL = SQL_SCC_ISO92_CLI;
		break;
	case SQL_STATIC_SENSITIVITY:
		IVAL = 0;
		break;
	case SQL_STATIC_CURSOR_ATTRIBUTES1:
		if (dbc->cursor_support)
			/* TODO cursors SQL_CA1_BOOKMARK SQL_CA1_BULK_FETCH_BY_BOOKMARK SQL_CA1_POS_REFRESH */
			UIVAL = SQL_CA1_ABSOLUTE | SQL_CA1_LOCK_NO_CHANGE | SQL_CA1_NEXT | SQL_CA1_POS_POSITION | SQL_CA1_RELATIVE;
		else
			UIVAL = 0;
		break;
	case SQL_STATIC_CURSOR_ATTRIBUTES2:
		/* TODO cursors */
		UIVAL = 0;
		break;
#endif /* ODBCVER >= 0x0300 */
	case SQL_STRING_FUNCTIONS:
		UIVAL = (SQL_FN_STR_ASCII | SQL_FN_STR_BIT_LENGTH | SQL_FN_STR_CHAR | SQL_FN_STR_CONCAT | SQL_FN_STR_DIFFERENCE |
			 SQL_FN_STR_INSERT | SQL_FN_STR_LCASE | SQL_FN_STR_LEFT | SQL_FN_STR_LENGTH | SQL_FN_STR_LOCATE_2 |
			 SQL_FN_STR_LTRIM | SQL_FN_STR_OCTET_LENGTH | SQL_FN_STR_REPEAT | SQL_FN_STR_RIGHT | SQL_FN_STR_RTRIM |
			 SQL_FN_STR_SOUNDEX | SQL_FN_STR_SPACE | SQL_FN_STR_SUBSTRING | SQL_FN_STR_UCASE) & mssql7plus_mask;
		break;
	case SQL_SUBQUERIES:
		UIVAL = SQL_SQ_COMPARISON | SQL_SQ_CORRELATED_SUBQUERIES | SQL_SQ_EXISTS | SQL_SQ_IN | SQL_SQ_QUANTIFIED;
		break;
	case SQL_SYSTEM_FUNCTIONS:
		UIVAL = SQL_FN_SYS_DBNAME | SQL_FN_SYS_IFNULL | SQL_FN_SYS_USERNAME;
		break;
	case SQL_TABLE_TERM:
		p = "table";
		break;
	case SQL_TIMEDATE_ADD_INTERVALS:
		UIVAL = (SQL_FN_TSI_DAY | SQL_FN_TSI_FRAC_SECOND | SQL_FN_TSI_HOUR | SQL_FN_TSI_MINUTE | SQL_FN_TSI_MONTH |
			 SQL_FN_TSI_QUARTER | SQL_FN_TSI_SECOND | SQL_FN_TSI_WEEK | SQL_FN_TSI_YEAR) & mssql7plus_mask;
		break;
	case SQL_TIMEDATE_DIFF_INTERVALS:
		UIVAL = (SQL_FN_TSI_DAY | SQL_FN_TSI_FRAC_SECOND | SQL_FN_TSI_HOUR | SQL_FN_TSI_MINUTE | SQL_FN_TSI_MONTH |
			 SQL_FN_TSI_QUARTER | SQL_FN_TSI_SECOND | SQL_FN_TSI_WEEK | SQL_FN_TSI_YEAR) & mssql7plus_mask;
		break;
	case SQL_TIMEDATE_FUNCTIONS:
		UIVAL = (SQL_FN_TD_CURDATE | SQL_FN_TD_CURRENT_DATE | SQL_FN_TD_CURRENT_TIME | SQL_FN_TD_CURRENT_TIMESTAMP |
			 SQL_FN_TD_CURTIME | SQL_FN_TD_DAYNAME | SQL_FN_TD_DAYOFMONTH | SQL_FN_TD_DAYOFWEEK | SQL_FN_TD_DAYOFYEAR |
			 SQL_FN_TD_EXTRACT | SQL_FN_TD_HOUR | SQL_FN_TD_MINUTE | SQL_FN_TD_MONTH | SQL_FN_TD_MONTHNAME |
			 SQL_FN_TD_NOW | SQL_FN_TD_QUARTER | SQL_FN_TD_SECOND | SQL_FN_TD_TIMESTAMPADD | SQL_FN_TD_TIMESTAMPDIFF |
			 SQL_FN_TD_WEEK | SQL_FN_TD_YEAR) & mssql7plus_mask;
		break;
	case SQL_TXN_CAPABLE:
		/* transaction for DML and DDL */
		SIVAL = SQL_TC_ALL;
		break;
	case SQL_TXN_ISOLATION_OPTION:
		UIVAL = SQL_TXN_READ_COMMITTED | SQL_TXN_READ_UNCOMMITTED | SQL_TXN_REPEATABLE_READ | SQL_TXN_SERIALIZABLE;
		break;
	case SQL_UNION:
		UIVAL = SQL_U_UNION | SQL_U_UNION_ALL;
		break;
		/* TODO finish */
	case SQL_USER_NAME:
		/* TODO this is not correct username */
		/* p = tds_dstr_cstr(&dbc->tds_login->user_name); */
		/* make OpenOffice happy :) */
		p = "";
		break;
	case SQL_XOPEN_CLI_YEAR:
		/* TODO check specifications */
		p = "1995";
		break;
	case SQL_INFO_FREETDS_TDS_VERSION:
		if (!dbc->tds_socket)
			return SQL_ERROR;
		UIVAL = TDS_MAJOR(dbc->tds_socket->conn) << 16 | TDS_MINOR(dbc->tds_socket->conn);
		break;
	case SQL_INFO_FREETDS_SOCKET:
		if (IS_TDSDEAD(dbc->tds_socket))
			return SQL_ERROR;
		ULVAL = dbc->tds_socket->conn->s;
		break;
	default:
		odbc_log_unimplemented_type("SQLGetInfo", fInfoType);
		odbc_errs_add(&dbc->errs, "HY092", "Option not supported");
		return SQL_ERROR;
	}

	/* char data */
	if (p) {
		return odbc_set_string_oct(dbc, rgbInfoValue, cbInfoValueMax, pcbInfoValue, p, -1);
	} else {
		if (out_len > 0 && pcbInfoValue)
			*pcbInfoValue = out_len;
	}

	return SQL_SUCCESS;
#undef SIVAL
#undef USIVAL
#undef IVAL
#undef UIVAL
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLGetInfo(SQLHDBC hdbc, SQLUSMALLINT fInfoType, SQLPOINTER rgbInfoValue, SQLSMALLINT cbInfoValueMax,
	   SQLSMALLINT FAR * pcbInfoValue)
{
	ODBC_ENTER_HDBC;

	tdsdump_log(TDS_DBG_FUNC, "SQLGetInfo(%p, %d, %p, %d, %p)\n",
			hdbc, fInfoType, rgbInfoValue, cbInfoValueMax, pcbInfoValue);

	ODBC_EXIT(dbc, odbc_SQLGetInfo(dbc, fInfoType, rgbInfoValue, cbInfoValueMax, pcbInfoValue _wide0));
}

#ifdef ENABLE_ODBC_WIDE
SQLRETURN ODBC_PUBLIC ODBC_API
SQLGetInfoW(SQLHDBC hdbc, SQLUSMALLINT fInfoType, SQLPOINTER rgbInfoValue, SQLSMALLINT cbInfoValueMax,
	   SQLSMALLINT FAR * pcbInfoValue)
{
	ODBC_ENTER_HDBC;

	tdsdump_log(TDS_DBG_FUNC, "SQLGetInfoW(%p, %d, %p, %d, %p)\n",
			hdbc, fInfoType, rgbInfoValue, cbInfoValueMax, pcbInfoValue);

	ODBC_EXIT(dbc, odbc_SQLGetInfo(dbc, fInfoType, rgbInfoValue, cbInfoValueMax, pcbInfoValue, 1));
}
#endif

static void
tds_ascii_strupr(char *s)
{
	for (; *s; ++s)
		if ('a' <= *s && *s <= 'z')
			*s = *s & (~0x20);
}

static void
odbc_upper_column_names(TDS_STMT * stmt)
{
#if ENABLE_EXTRA_CHECKS
	TDSSOCKET *tds;
#endif
	int icol;
	TDS_DESC *ird;

	IRD_CHECK;

#if ENABLE_EXTRA_CHECKS
	tds = stmt->dbc->tds_socket;
	if (tds && tds->current_results) {
		TDSRESULTINFO *const resinfo = tds->current_results;

		for (icol = 0; icol < resinfo->num_cols; ++icol) {
			TDSCOLUMN *colinfo = resinfo->columns[icol];

			/* upper case */
			tds_ascii_strupr(tds_dstr_buf(&colinfo->column_name));
		}
	}
#endif

	ird = stmt->ird;
	for (icol = ird->header.sql_desc_count; --icol >= 0;) {
		struct _drecord *drec = &ird->records[icol];

		/* upper case */
		tds_ascii_strupr(tds_dstr_buf(&drec->sql_desc_label));
		tds_ascii_strupr(tds_dstr_buf(&drec->sql_desc_name));
	}
}

/**
 * Change sql datatype to a specific version
 * \param sql_type sql data type to change (any type)
 * \param version  ODBC version to change to, 2 for ODBC 2, 3 for ODBC 3, 0 swap
 */
static SQLSMALLINT
odbc_swap_datetime_sql_type(SQLSMALLINT sql_type, int version)
{
	switch (sql_type) {
	case SQL_TYPE_TIMESTAMP:
		if (version != 3)
			sql_type = SQL_TIMESTAMP;
		break;
	case SQL_TIMESTAMP:
		if (version != 2)
			sql_type = SQL_TYPE_TIMESTAMP;
		break;
	case SQL_TYPE_DATE:
		if (version != 3)
			sql_type = SQL_DATE;
		break;
	case SQL_DATE:
		if (version != 2)
			sql_type = SQL_TYPE_DATE;
		break;
	case SQL_TYPE_TIME:
		if (version != 3)
			sql_type = SQL_TIME;
		break;
	case SQL_TIME:
		if (version != 2)
			sql_type = SQL_TYPE_TIME;
		break;
	}
	return sql_type;
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLGetTypeInfo(SQLHSTMT hstmt, SQLSMALLINT fSqlType)
{
	SQLRETURN res;
	TDSSOCKET *tds;
	TDS_INT result_type;
	TDS_INT compute_id;
	int varchar_pos = -1, n;
	static const char sql_templ_default[] = "sp_datatype_info %d";
	const char *sql_templ = sql_templ_default;
	char sql[sizeof(sql_templ_default) + 36];
	int odbc3;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLGetTypeInfo(%p, %d)\n", hstmt, fSqlType);

	tds = stmt->dbc->tds_socket;
	odbc3 = (stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3);

	if (IS_TDS73_PLUS(tds->conn))
		sql_templ = "sp_datatype_info_100 %d";
	else if (IS_TDS72_PLUS(tds->conn))
		sql_templ = "sp_datatype_info_90 %d";

	/* For MSSQL6.5 and Sybase 11.9 sp_datatype_info work */
	/* TODO what about early Sybase products ? */
	/* TODO Does Sybase return all ODBC3 columns? Add them if not */
	/* TODO ODBC3 convert type to ODBC version 2 (date) */
	if (odbc3) {
		if (TDS_IS_SYBASE(tds)) {
			sprintf(sql, sql_templ, odbc_swap_datetime_sql_type(fSqlType, 0));
			stmt->special_row = ODBC_SPECIAL_GETTYPEINFO;
		} else {
			sprintf(sql, sql_templ, fSqlType);
			strcat(sql, ",3");
		}
	} else {
		/* MS ODBC translate SQL_TIMESTAMP to SQL_TYPE_TIMESTAMP even for ODBC 2 apps
		 * other DM just pass value straight.
		 * Convert always to version 2 to make version 2 datatype works with all DMs
		 */
		sprintf(sql, sql_templ, odbc_swap_datetime_sql_type(fSqlType, 2));
	}
	if (SQL_SUCCESS != odbc_set_stmt_query(stmt, (ODBC_CHAR*) sql, strlen(sql) _wide0))
		ODBC_EXIT(stmt, SQL_ERROR);

      redo:
	res = odbc_SQLExecute(stmt);

	odbc_upper_column_names(stmt);
	if (odbc3) {
		odbc_col_setname(stmt, 3, "COLUMN_SIZE");
		odbc_col_setname(stmt, 11, "FIXED_PREC_SCALE");
		odbc_col_setname(stmt, 12, "AUTO_UNIQUE_VALUE");
	}

	/* workaround for a mispelled column name in Sybase */
	if (TDS_IS_SYBASE(stmt->dbc->tds_socket) && !odbc3)
		odbc_col_setname(stmt, 3, "PRECISION");

	if (TDS_IS_MSSQL(stmt->dbc->tds_socket) || fSqlType != SQL_VARCHAR || res != SQL_SUCCESS)
		ODBC_EXIT(stmt, res);

	/*
	 * Sybase return first nvarchar for char... and without length !!!
	 * Some program use first entry so we discard all entry before varchar
	 */
	n = 0;
	while (tds->current_results) {
		TDSRESULTINFO *resinfo;
		TDSCOLUMN *colinfo;
		char *name;

		/* if next is varchar leave next for SQLFetch */
		if (n == (varchar_pos - 1))
			break;

		switch (tds_process_tokens(stmt->dbc->tds_socket, &result_type, &compute_id, TDS_STOPAT_ROWFMT|TDS_RETURN_ROW)) {
		case TDS_SUCCESS:
			if (result_type == TDS_ROW_RESULT)
				break;
		case TDS_NO_MORE_RESULTS:
			/* discard other tokens */
			tds_process_simple_query(tds);
			if (n >= varchar_pos && varchar_pos > 0)
				goto redo;
			break;
		case TDS_CANCELLED:
			odbc_errs_add(&stmt->errs, "HY008", NULL);
			res = SQL_ERROR;
			break;
		}
		if (!tds->current_results)
			break;
		++n;

		resinfo = tds->current_results;
		colinfo = resinfo->columns[0];
		name = (char *) colinfo->column_data;
		if (is_blob_col(colinfo))
			name = (char*) ((TDSBLOB *) name)->textvalue;
		/* skip nvarchar and sysname */
		if (colinfo->column_cur_size == 7 && memcmp("varchar", name, 7) == 0) {
			varchar_pos = n;
		}
	}
	ODBC_EXIT(stmt, res);
}

#ifdef ENABLE_ODBC_WIDE
SQLRETURN ODBC_PUBLIC ODBC_API
SQLGetTypeInfoW(SQLHSTMT hstmt, SQLSMALLINT fSqlType)
{
	return SQLGetTypeInfo(hstmt, fSqlType);
}
#endif

static SQLRETURN
odbc_SQLParamData(SQLHSTMT hstmt, SQLPOINTER FAR * prgbValue)
{
	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLParamData(%p, %p) [param_num %d, param_data_called = %d]\n",
					hstmt, prgbValue, stmt->param_num, stmt->param_data_called);

	if (stmt->params && stmt->param_num <= stmt->param_count) {
		SQLRETURN res;

		if (stmt->param_num <= 0 || stmt->param_num > stmt->apd->header.sql_desc_count) {
			tdsdump_log(TDS_DBG_FUNC, "SQLParamData: logic_error: parameter out of bounds: 0 <= %d < %d\n",
						   stmt->param_num, stmt->apd->header.sql_desc_count);
			ODBC_EXIT(stmt, SQL_ERROR);
		}

		/*
		 * TODO compute output value with this formula:
		 * Bound Address + Binding Offset + ((Row Number - 1) x Element Size)
		 * (see SQLParamData documentation)
		 * This is needed for updates using SQLBulkOperation (not currently supported)
		 */
		if (!stmt->param_data_called) {
			stmt->param_data_called = 1;
			*prgbValue = stmt->apd->records[stmt->param_num - 1].sql_desc_data_ptr;
			ODBC_EXIT(stmt, SQL_NEED_DATA);
		}
		++stmt->param_num;
		switch (res = parse_prepared_query(stmt, true)) {
		case SQL_NEED_DATA:
			*prgbValue = stmt->apd->records[stmt->param_num - 1].sql_desc_data_ptr;
			ODBC_EXIT(stmt, SQL_NEED_DATA);
		case SQL_SUCCESS:
			ODBC_EXIT(stmt, odbc_SQLExecute(stmt));
		}
		ODBC_EXIT(stmt, res);
	}

	odbc_errs_add(&stmt->errs, "HY010", NULL);
	ODBC_EXIT_(stmt);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLParamData(SQLHSTMT hstmt, SQLPOINTER FAR * prgbValue)
{
	ODBC_PRRET_BUF;
	SQLRETURN ret = odbc_SQLParamData(hstmt, prgbValue);

	tdsdump_log(TDS_DBG_FUNC, "SQLParamData returns %s\n", odbc_prret(ret));

	return ret;
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLPutData(SQLHSTMT hstmt, SQLPOINTER rgbValue, SQLLEN cbValue)
{
	ODBC_PRRET_BUF;
	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLPutData(%p, %p, %i)\n", hstmt, rgbValue, (int)cbValue);

	if (stmt->param_data_called) {
		SQLRETURN ret;
		const TDSCOLUMN *curcol = stmt->params->columns[stmt->param_num - (stmt->prepared_query_is_func ? 2 : 1)];
		/* TODO do some more tests before setting this flag */
		stmt->param_data_called = 1;
		ret = continue_parse_prepared_query(stmt, rgbValue, cbValue);
		tdsdump_log(TDS_DBG_FUNC, "SQLPutData returns %s, %d bytes left\n",
					  odbc_prret(ret), curcol->column_size - curcol->column_cur_size);
		ODBC_EXIT(stmt, ret);
	}

	odbc_errs_add(&stmt->errs, "HY010", NULL);
	ODBC_EXIT_(stmt);
}


ODBC_FUNC(SQLSetConnectAttr, (P(SQLHDBC,hdbc), P(SQLINTEGER,Attribute), P(SQLPOINTER,ValuePtr), P(SQLINTEGER,StringLength) WIDE))
{
	SQLULEN u_value = (SQLULEN) (TDS_INTPTR) ValuePtr;

	ODBC_ENTER_HDBC;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLSetConnectAttr(%p, %d, %p, %d)\n", hdbc, (int)Attribute, ValuePtr, (int)StringLength);

	switch (Attribute) {
	case SQL_ATTR_AUTOCOMMIT:
		/* spinellia@acm.org */
		change_autocommit(dbc, u_value);
		break;
	case SQL_ATTR_CONNECTION_TIMEOUT:
		dbc->attr.connection_timeout = u_value;
		break;
	case SQL_ATTR_ACCESS_MODE:
		dbc->attr.access_mode = u_value;
		break;
	case SQL_ATTR_CURRENT_CATALOG:
		if (!IS_VALID_LEN(StringLength)) {
			odbc_errs_add(&dbc->errs, "HY090", NULL);
			break;
		}
		{
			DSTR s = DSTR_INITIALIZER;

			if (!odbc_dstr_copy_oct(dbc, &s, StringLength, (ODBC_CHAR*) ValuePtr)) {
				odbc_errs_add(&dbc->errs, "HY001", NULL);
				break;
			}
			change_database(dbc, tds_dstr_cstr(&s), tds_dstr_len(&s));
			tds_dstr_free(&s);
		}
		break;
	case SQL_ATTR_CURSOR_TYPE:
		if (dbc->cursor_support)
			dbc->attr.cursor_type = u_value;
		break;
	case SQL_ATTR_LOGIN_TIMEOUT:
		dbc->attr.login_timeout = u_value;
		break;
	case SQL_ATTR_ODBC_CURSORS:
		/* TODO cursors */
		dbc->attr.odbc_cursors = u_value;
		break;
	case SQL_ATTR_PACKET_SIZE:
		dbc->attr.packet_size = u_value;
		break;
	case SQL_ATTR_QUIET_MODE:
		dbc->attr.quite_mode = (SQLHWND) (TDS_INTPTR) ValuePtr;
		break;
#ifdef TDS_NO_DM
	case SQL_ATTR_TRACE:
		dbc->attr.trace = u_value;
		break;
	case SQL_ATTR_TRACEFILE:
		if (!IS_VALID_LEN(StringLength)) {
			odbc_errs_add(&dbc->errs, "HY090", NULL);
			break;
		}
		if (!odbc_dstr_copy(dbc, &dbc->attr.tracefile, StringLength, (ODBC_CHAR *) ValuePtr))
			odbc_errs_add(&dbc->errs, "HY001", NULL);
		break;
#endif
	case SQL_ATTR_TXN_ISOLATION:
		if (u_value != dbc->attr.txn_isolation) {
			if (change_txn(dbc, u_value) == SQL_SUCCESS)
				dbc->attr.txn_isolation = u_value;
		}
		break;
	case SQL_COPT_SS_MARS_ENABLED:
		dbc->attr.mars_enabled = u_value;
		break;
	case SQL_ATTR_TRANSLATE_LIB:
	case SQL_ATTR_TRANSLATE_OPTION:
		odbc_errs_add(&dbc->errs, "HYC00", NULL);
		break;
	case SQL_COPT_SS_OLDPWD:
		if (!IS_VALID_LEN(StringLength)) {
			odbc_errs_add(&dbc->errs, "HY090", NULL);
			break;
		}
		if (!odbc_dstr_copy(dbc, &dbc->oldpwd, StringLength, (ODBC_CHAR *) ValuePtr))
			odbc_errs_add(&dbc->errs, "HY001", NULL);
		else
			dbc->use_oldpwd = 1;
		break;
	case SQL_COPT_SS_BCP:
		dbc->attr.bulk_enabled = u_value;
		break;
	case SQL_COPT_TDSODBC_IMPL_BCP_INITA:
		if (!ValuePtr)
			odbc_errs_add(&dbc->errs, "HY009", NULL);
		else {
			const struct tdsodbc_impl_bcp_init_params *params = (const struct tdsodbc_impl_bcp_init_params*)ValuePtr;
			odbc_bcp_init(dbc, (const ODBC_CHAR *) params->tblname, (const ODBC_CHAR *) params->hfile,
				      (const ODBC_CHAR *) params->errfile, params->direction _wide0);
		}
		break;
#ifdef ENABLE_ODBC_WIDE
	case SQL_COPT_TDSODBC_IMPL_BCP_INITW:
		if (!ValuePtr)
			odbc_errs_add(&dbc->errs, "HY009", NULL);
		else {
			const struct tdsodbc_impl_bcp_init_params *params = (const struct tdsodbc_impl_bcp_init_params*)ValuePtr;
			odbc_bcp_init(dbc, (const ODBC_CHAR *) params->tblname, (const ODBC_CHAR *) params->hfile,
				      (const ODBC_CHAR *) params->errfile, params->direction, 1);
		}
		break;
#endif
	case SQL_COPT_TDSODBC_IMPL_BCP_CONTROL_OLD:
		if (!ValuePtr)
			odbc_errs_add(&dbc->errs, "HY009", NULL);
		else {
			const struct tdsodbc_impl_bcp_control_params *params =
				(const struct tdsodbc_impl_bcp_control_params*)ValuePtr;
			const int field = params->field == BCPHINTS_OLD ? BCPHINTSA : params->field;
			odbc_bcp_control(dbc, field, params->value);
		}
		break;
	case SQL_COPT_TDSODBC_IMPL_BCP_CONTROL:
		if (!ValuePtr)
			odbc_errs_add(&dbc->errs, "HY009", NULL);
		else {
			const struct tdsodbc_impl_bcp_control_params *params =
				(const struct tdsodbc_impl_bcp_control_params*)ValuePtr;
			odbc_bcp_control(dbc, params->field, params->value);
		}
		break;
	case SQL_COPT_TDSODBC_IMPL_BCP_COLPTR:
		if (!ValuePtr)
			odbc_errs_add(&dbc->errs, "HY009", NULL);
		else {
			const struct tdsodbc_impl_bcp_colptr_params *params =
				(const struct tdsodbc_impl_bcp_colptr_params*)ValuePtr;
			odbc_bcp_colptr(dbc, params->colptr, params->table_column);
		}
		break;
	case SQL_COPT_TDSODBC_IMPL_BCP_SENDROW:
		if (ValuePtr)
			odbc_errs_add(&dbc->errs, "HY000", NULL);
		else
			odbc_bcp_sendrow(dbc);
		break;
	case SQL_COPT_TDSODBC_IMPL_BCP_BATCH:
		if (!ValuePtr)
			odbc_errs_add(&dbc->errs, "HY009", NULL);
		else {
			struct tdsodbc_impl_bcp_batch_params *params = (struct tdsodbc_impl_bcp_batch_params*)ValuePtr;
			params->rows = odbc_bcp_batch(dbc);
		}
		break;
	case SQL_COPT_TDSODBC_IMPL_BCP_DONE:
		if (!ValuePtr)
			odbc_errs_add(&dbc->errs, "HY009", NULL);
		else {
			struct tdsodbc_impl_bcp_done_params *params = (struct tdsodbc_impl_bcp_done_params*)ValuePtr;
			params->rows = odbc_bcp_done(dbc);
		}
		break;
	case SQL_COPT_TDSODBC_IMPL_BCP_BIND:
		if (!ValuePtr)
			odbc_errs_add(&dbc->errs, "HY009", NULL);
		else {
			const struct tdsodbc_impl_bcp_bind_params *params = (const struct tdsodbc_impl_bcp_bind_params*)ValuePtr;
			odbc_bcp_bind(dbc, params->varaddr, params->prefixlen, params->varlen,
				      params->terminator, params->termlen, params->vartype, params->table_column);
		}
		break;
	default:
		odbc_errs_add(&dbc->errs, "HY092", NULL);
		break;
	}
	ODBC_EXIT_(dbc);
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLSetConnectOption(SQLHDBC hdbc, SQLUSMALLINT fOption, SQLULEN vParam)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLSetConnectOption(%p, %d, %p)\n", hdbc, fOption, (SQLPOINTER) (TDS_INTPTR) vParam);

	return odbc_SQLSetConnectAttr(hdbc, (SQLINTEGER) fOption, (SQLPOINTER) (TDS_INTPTR) vParam, SQL_NTS _wide0);
}

#ifdef ENABLE_ODBC_WIDE
SQLRETURN ODBC_PUBLIC ODBC_API
SQLSetConnectOptionW(SQLHDBC hdbc, SQLUSMALLINT fOption, SQLULEN vParam)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLSetConnectOptionW(%p, %d, %p)\n", hdbc, fOption, (SQLPOINTER) (TDS_INTPTR) vParam);

	return odbc_SQLSetConnectAttr(hdbc, (SQLINTEGER) fOption, (SQLPOINTER) (TDS_INTPTR) vParam, SQL_NTS, 1);
}
#endif

static SQLRETURN
odbc_SQLSetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength WIDE)
{
	SQLULEN ui = (SQLULEN) (TDS_INTPTR) ValuePtr;
	SQLUSMALLINT *usip = (SQLUSMALLINT *) ValuePtr;
	SQLLEN *lp = (SQLLEN *) ValuePtr;
	SQLULEN *ulp = (SQLULEN *) ValuePtr;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "odbc_SQLSetStmtAttr(%p, %d, %p, %d)\n", hstmt, (int)Attribute, ValuePtr, (int)StringLength);

	/* TODO - error checking and real functionality :-) */
	/* TODO some setting set also other attribute, see documentation */
	switch (Attribute) {
		/* TODO check SQLFreeHandle on descriptor. Is possible to free an associated descriptor ? */
	case SQL_ATTR_APP_PARAM_DESC:
	case SQL_ATTR_APP_ROW_DESC:
		{
			TDS_DESC *orig;
			TDS_DESC **curr;
			TDS_DESC *val = (TDS_DESC *) ValuePtr;

			if (Attribute == SQL_ATTR_APP_PARAM_DESC) {
				orig = stmt->orig_apd;
				curr = &stmt->apd;
			} else {
				orig = stmt->orig_ard;
				curr = &stmt->ard;
			}
			/* if ValuePtr is NULL or original descriptor set original */
			if (!val || val == orig) {
				*curr = orig;
				break;
			}

			/* must be allocated by user, not implicit ones */
			if (val->header.sql_desc_alloc_type != SQL_DESC_ALLOC_USER) {
				odbc_errs_add(&stmt->errs, "HY017", NULL);
				break;
			}
			/* TODO check HDESC (not associated, from DBC HY024) */
			*curr = val;
		}
		break;
	case SQL_ATTR_ASYNC_ENABLE:
		if (stmt->attr.async_enable != ui) {
			odbc_errs_add(&stmt->errs, "HYC00", NULL);
			break;
		}
		stmt->attr.async_enable = ui;
		break;
	case SQL_ATTR_CONCURRENCY:
		if (stmt->attr.concurrency != ui && !stmt->dbc->cursor_support) {
			odbc_errs_add(&stmt->errs, "01S02", NULL);
			break;
		}

		if (stmt->cursor) {
			odbc_errs_add(&stmt->errs, "24000", NULL);
			break;
		}

		switch (ui) {
		case SQL_CONCUR_READ_ONLY:
			stmt->attr.cursor_sensitivity = SQL_INSENSITIVE;
			break;
		case SQL_CONCUR_LOCK:
		case SQL_CONCUR_ROWVER:
		case SQL_CONCUR_VALUES:
			stmt->attr.cursor_sensitivity = SQL_SENSITIVE;
			break;
		default:
			odbc_errs_add(&stmt->errs, "HY092", NULL);
			ODBC_EXIT_(stmt);
		}
		stmt->attr.concurrency = ui;
		break;
	case SQL_ATTR_CURSOR_SCROLLABLE:
		if (stmt->attr.cursor_scrollable != ui && !stmt->dbc->cursor_support) {
			odbc_errs_add(&stmt->errs, "HYC00", NULL);
			break;
		}

		switch (ui) {
		case SQL_SCROLLABLE:
			stmt->attr.cursor_type = SQL_CURSOR_KEYSET_DRIVEN;
			break;
		case SQL_NONSCROLLABLE:
			stmt->attr.cursor_type = SQL_CURSOR_FORWARD_ONLY;
			break;
		default:
			odbc_errs_add(&stmt->errs, "HY092", NULL);
			ODBC_EXIT_(stmt);
		}
		stmt->attr.cursor_scrollable = ui;
		break;
	case SQL_ATTR_CURSOR_SENSITIVITY:
		/* don't change anything */
		if (ui == SQL_UNSPECIFIED)
			break;
		if (stmt->attr.cursor_sensitivity != ui && !stmt->dbc->cursor_support) {
			odbc_errs_add(&stmt->errs, "HYC00", NULL);
			break;
		}
		switch (ui) {
		case SQL_INSENSITIVE:
			stmt->attr.concurrency = SQL_CONCUR_READ_ONLY;
			stmt->attr.cursor_type = SQL_CURSOR_STATIC;
			break;
		case SQL_SENSITIVE:
			stmt->attr.concurrency = SQL_CONCUR_ROWVER;
			break;
		}
		stmt->attr.cursor_sensitivity = ui;
		break;
	case SQL_ATTR_CURSOR_TYPE:
		if (stmt->attr.cursor_type != ui && !stmt->dbc->cursor_support) {
			odbc_errs_add(&stmt->errs, "01S02", NULL);
			break;
		}

		if (stmt->cursor) {
			odbc_errs_add(&stmt->errs, "24000", NULL);
			break;
		}

		switch (ui) {
		case SQL_CURSOR_DYNAMIC:
		case SQL_CURSOR_KEYSET_DRIVEN:
			if (stmt->attr.concurrency != SQL_CONCUR_READ_ONLY)
				stmt->attr.cursor_sensitivity = SQL_SENSITIVE;
			stmt->attr.cursor_scrollable = SQL_SCROLLABLE;
			break;
		case SQL_CURSOR_STATIC:
			if (stmt->attr.concurrency != SQL_CONCUR_READ_ONLY)
				stmt->attr.cursor_sensitivity = SQL_SENSITIVE;
			else
				stmt->attr.cursor_sensitivity = SQL_INSENSITIVE;
			stmt->attr.cursor_scrollable = SQL_SCROLLABLE;
			break;
		case SQL_CURSOR_FORWARD_ONLY:
			stmt->attr.cursor_scrollable = SQL_NONSCROLLABLE;
			break;
		default:
			odbc_errs_add(&stmt->errs, "HY092", NULL);
			ODBC_EXIT_(stmt);
		}
		stmt->attr.cursor_type = ui;
		break;
	case SQL_ATTR_ENABLE_AUTO_IPD:
		if (stmt->attr.enable_auto_ipd != ui) {
			odbc_errs_add(&stmt->errs, "HYC00", NULL);
			break;
		}
		stmt->attr.enable_auto_ipd = ui;
		break;
	case SQL_ATTR_FETCH_BOOKMARK_PTR:
		stmt->attr.fetch_bookmark_ptr = ValuePtr;
		break;
	case SQL_ATTR_IMP_ROW_DESC:
	case SQL_ATTR_IMP_PARAM_DESC:
		odbc_errs_add(&stmt->errs, "HY017", NULL);
		break;
		/* TODO what's is this ??? */
	case SQL_ATTR_KEYSET_SIZE:
		stmt->attr.keyset_size = ui;
		break;
		/* TODO max length of data returned. Use SQL TEXTSIZE or just truncate ?? */
	case SQL_ATTR_MAX_LENGTH:
		if (stmt->attr.max_length != ui) {
			odbc_errs_add(&stmt->errs, "01S02", NULL);
			break;
		}
		stmt->attr.max_length = ui;
		break;
		/* TODO max row returned. Use SET ROWCOUNT */
	case SQL_ATTR_MAX_ROWS:
		if (stmt->attr.max_rows != ui) {
			odbc_errs_add(&stmt->errs, "01S02", NULL);
			break;
		}
		stmt->attr.max_rows = ui;
		break;
	case SQL_ATTR_METADATA_ID:
		stmt->attr.metadata_id = ui;
		break;
		/* TODO use it !!! */
	case SQL_ATTR_NOSCAN:
		stmt->attr.noscan = ui;
		break;
	case SQL_ATTR_PARAM_BIND_OFFSET_PTR:
		stmt->apd->header.sql_desc_bind_offset_ptr = lp;
		break;
	case SQL_ATTR_PARAM_BIND_TYPE:
		stmt->apd->header.sql_desc_bind_type = ui;
		break;
	case SQL_ATTR_PARAM_OPERATION_PTR:
		stmt->apd->header.sql_desc_array_status_ptr = usip;
		break;
	case SQL_ATTR_PARAM_STATUS_PTR:
		stmt->ipd->header.sql_desc_array_status_ptr = usip;
		break;
	case SQL_ATTR_PARAMS_PROCESSED_PTR:
		stmt->ipd->header.sql_desc_rows_processed_ptr = ulp;
		break;
		/* allow to exec procedure multiple time */
	case SQL_ATTR_PARAMSET_SIZE:
		stmt->apd->header.sql_desc_array_size = ui;
		break;
	case SQL_ATTR_QUERY_TIMEOUT:
		/* Microsoft allows a maximum value of 65534 */
		if (ui > 65534) {
			odbc_errs_add(&stmt->errs, "01S02", NULL);
			ui = 65534;
		}
		stmt->attr.query_timeout = ui;
		break;
		/* retrieve data after positioning the cursor */
	case SQL_ATTR_RETRIEVE_DATA:
		/* TODO cursors */
		if (stmt->attr.retrieve_data != ui) {
			odbc_errs_add(&stmt->errs, "01S02", NULL);
			break;
		}
		stmt->attr.retrieve_data = ui;
		break;
	case SQL_ATTR_ROW_ARRAY_SIZE:
		stmt->ard->header.sql_desc_array_size = ui;
		break;
	case SQL_ATTR_ROW_BIND_OFFSET_PTR:
		/* TODO test what happen with column-wise and row-wise bindings and SQLGetData */
		stmt->ard->header.sql_desc_bind_offset_ptr = lp;
		break;
#if SQL_BIND_TYPE != SQL_ATTR_ROW_BIND_TYPE
	case SQL_BIND_TYPE:	/* although this is ODBC2 we must support this attribute */
#endif
	case SQL_ATTR_ROW_BIND_TYPE:
		stmt->ard->header.sql_desc_bind_type = ui;
		break;
	case SQL_ATTR_ROW_NUMBER:
		odbc_errs_add(&stmt->errs, "HY092", NULL);
		break;
	case SQL_ATTR_ROW_OPERATION_PTR:
		stmt->ard->header.sql_desc_array_status_ptr = usip;
		break;
	case SQL_ATTR_ROW_STATUS_PTR:
		stmt->ird->header.sql_desc_array_status_ptr = usip;
		break;
	case SQL_ATTR_ROWS_FETCHED_PTR:
		stmt->ird->header.sql_desc_rows_processed_ptr = ulp;
		break;
	case SQL_ATTR_SIMULATE_CURSOR:
		/* TODO cursors */
		if (stmt->cursor) {
			odbc_errs_add(&stmt->errs, "24000", NULL);
			break;
		}
		if (stmt->attr.simulate_cursor != ui) {
			odbc_errs_add(&stmt->errs, "01S02", NULL);
			break;
		}
		stmt->attr.simulate_cursor = ui;
		break;
	case SQL_ATTR_USE_BOOKMARKS:
		if (stmt->cursor) {
			odbc_errs_add(&stmt->errs, "24000", NULL);
			break;
		}
		stmt->attr.use_bookmarks = ui;
		break;
	case SQL_ROWSET_SIZE:	/* although this is ODBC2 we must support this attribute */
		if (((TDS_INTPTR) ValuePtr) < 1) {
			odbc_errs_add(&stmt->errs, "HY024", NULL);
			break;
		}
		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;
	case SQL_SOPT_SS_PARAM_FOCUS:
		if (ui > 0 && (ui > stmt->apd->header.sql_desc_count
			       || stmt->ipd->records[ui - 1].sql_desc_concise_type != SQL_SS_TABLE)) {
			odbc_errs_add(&stmt->errs, "IM020", NULL);
			break;
		}
		stmt->attr.param_focus = ui;
		stmt->orig_apd->focus = ui;
		stmt->ipd->focus = ui;
		break;
	default:
		odbc_errs_add(&stmt->errs, "HY092", NULL);
		break;
	}
	ODBC_EXIT_(stmt);
}

#if (ODBCVER >= 0x0300)
SQLRETURN ODBC_PUBLIC ODBC_API
SQLSetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLSetStmtAttr(%p, %d, %p, %d)\n",
			hstmt, (int)Attribute, ValuePtr, (int)StringLength);

	return odbc_SQLSetStmtAttr(hstmt, Attribute, ValuePtr, StringLength _wide0);
}

#ifdef ENABLE_ODBC_WIDE
SQLRETURN ODBC_PUBLIC ODBC_API
SQLSetStmtAttrW(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength)
{
	tdsdump_log(TDS_DBG_FUNC, "SQLSetStmtAttr(%p, %d, %p, %d)\n",
			hstmt, (int)Attribute, ValuePtr, (int)StringLength);

	return odbc_SQLSetStmtAttr(hstmt, Attribute, ValuePtr, StringLength, 1);
}
#endif
#endif

SQLRETURN ODBC_PUBLIC ODBC_API
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 odbc_SQLSetStmtAttr(hstmt, (SQLINTEGER) fOption, (SQLPOINTER) (TDS_INTPTR) vParam, SQL_NTS _wide0);
}

ODBC_FUNC(SQLSpecialColumns, (P(SQLHSTMT,hstmt), P(SQLUSMALLINT,fColType), PCHARIN(CatalogName,SQLSMALLINT),
	PCHARIN(SchemaName,SQLSMALLINT), PCHARIN(TableName,SQLSMALLINT),
	P(SQLUSMALLINT,fScope), P(SQLUSMALLINT,fNullable) WIDE))
{
	int retcode;
	char nullable, scope, col_type;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLSpecialColumns(%p, %d, %p, %d, %p, %d, %p, %d, %d, %d)\n",
		    hstmt, fColType, szCatalogName, cbCatalogName, szSchemaName, cbSchemaName, szTableName, cbTableName,
		    fScope, fNullable);

#ifdef TDS_NO_DM
	/* Check column type */
	if (fColType != SQL_BEST_ROWID && fColType != SQL_ROWVER) {
		odbc_errs_add(&stmt->errs, "HY097", NULL);
		ODBC_EXIT_(stmt);
	}

	/* check our buffer lengths */
	if (!IS_VALID_LEN(cbCatalogName) || !IS_VALID_LEN(cbSchemaName) || !IS_VALID_LEN(cbTableName)) {
		odbc_errs_add(&stmt->errs, "HY090", NULL);
		ODBC_EXIT_(stmt);
	}

	/* Check nullable */
	if (fNullable != SQL_NO_NULLS && fNullable != SQL_NULLABLE) {
		odbc_errs_add(&stmt->errs, "HY099", NULL);
		ODBC_EXIT_(stmt);
	}

	if (!odbc_get_string_size(cbTableName, szTableName _wide)) {
		odbc_errs_add(&stmt->errs, "HY009", "SQLSpecialColumns: The table name parameter is required");
		ODBC_EXIT_(stmt);
	}

	switch (fScope) {
	case SQL_SCOPE_CURROW:
	case SQL_SCOPE_TRANSACTION:
	case SQL_SCOPE_SESSION:
		break;
	default:
		odbc_errs_add(&stmt->errs, "HY098", NULL);
		ODBC_EXIT_(stmt);
	}
#endif

	if (fNullable == SQL_NO_NULLS)
		nullable = 'O';
	else
		nullable = 'U';

	if (fScope == SQL_SCOPE_CURROW)
		scope = 'C';
	else
		scope = 'T';

	if (fColType == SQL_BEST_ROWID)
		col_type = 'R';
	else
		col_type = 'V';

	retcode =
		odbc_stat_execute(stmt _wide, "sp_special_columns", TDS_IS_MSSQL(stmt->dbc->tds_socket) ? 7 : 4, "O", szTableName,
				  cbTableName, "O", szSchemaName, cbSchemaName, "O@qualifier", szCatalogName, cbCatalogName,
				  "!@col_type", &col_type, 1, "!@scope", &scope, 1, "!@nullable", &nullable, 1,
				  "V@ODBCVer", (char*) NULL, 0);
	if (SQL_SUCCEEDED(retcode) && stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3) {
		odbc_col_setname(stmt, 5, "COLUMN_SIZE");
		odbc_col_setname(stmt, 6, "BUFFER_LENGTH");
		odbc_col_setname(stmt, 7, "DECIMAL_DIGITS");
		if (TDS_IS_SYBASE(stmt->dbc->tds_socket))
			stmt->special_row = ODBC_SPECIAL_SPECIALCOLUMNS;
	}
	ODBC_EXIT_(stmt);
}

ODBC_FUNC(SQLStatistics, (P(SQLHSTMT,hstmt), PCHARIN(CatalogName,SQLSMALLINT),
	PCHARIN(SchemaName,SQLSMALLINT), PCHARIN(TableName,SQLSMALLINT), P(SQLUSMALLINT,fUnique),
	P(SQLUSMALLINT,fAccuracy) WIDE))
{
	int retcode;
	char unique, accuracy;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLStatistics(%p, %p, %d, %p, %d, %p, %d, %d, %d)\n",
		    hstmt, szCatalogName, cbCatalogName, szSchemaName, cbSchemaName, szTableName, cbTableName,
		    fUnique, fAccuracy);

#ifdef TDS_NO_DM
	/* check our buffer lengths */
	if (!IS_VALID_LEN(cbCatalogName) || !IS_VALID_LEN(cbSchemaName) || !IS_VALID_LEN(cbTableName)) {
		odbc_errs_add(&stmt->errs, "HY090", NULL);
		ODBC_EXIT_(stmt);
	}

	/* check our uniqueness value */
	if (fUnique != SQL_INDEX_UNIQUE && fUnique != SQL_INDEX_ALL) {
		odbc_errs_add(&stmt->errs, "HY100", NULL);
		ODBC_EXIT_(stmt);
	}

	/* check our accuracy value */
	if (fAccuracy != SQL_QUICK && fAccuracy != SQL_ENSURE) {
		odbc_errs_add(&stmt->errs, "HY101", NULL);
		ODBC_EXIT_(stmt);
	}

	if (!odbc_get_string_size(cbTableName, szTableName _wide)) {
		odbc_errs_add(&stmt->errs, "HY009", NULL);
		ODBC_EXIT_(stmt);
	}
#endif

	if (fAccuracy == SQL_ENSURE)
		accuracy = 'E';
	else
		accuracy = 'Q';

	if (fUnique == SQL_INDEX_UNIQUE)
		unique = 'Y';
	else
		unique = 'N';

	retcode =
		odbc_stat_execute(stmt _wide, "sp_statistics", TDS_IS_MSSQL(stmt->dbc->tds_socket) ? 5 : 4, "O@table_qualifier",
				  szCatalogName, cbCatalogName, "O@table_owner", szSchemaName, cbSchemaName, "O@table_name",
				  szTableName, cbTableName, "!@is_unique", &unique, 1, "!@accuracy", &accuracy, 1);
	if (SQL_SUCCEEDED(retcode) && stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3) {
		odbc_col_setname(stmt, 1, "TABLE_CAT");
		odbc_col_setname(stmt, 2, "TABLE_SCHEM");
		odbc_col_setname(stmt, 8, "ORDINAL_POSITION");
		odbc_col_setname(stmt, 10, "ASC_OR_DESC");
	}
	ODBC_EXIT_(stmt);
}

ODBC_FUNC(SQLTables, (P(SQLHSTMT,hstmt), PCHARIN(CatalogName,SQLSMALLINT),
	PCHARIN(SchemaName,SQLSMALLINT), PCHARIN(TableName,SQLSMALLINT), PCHARIN(TableType,SQLSMALLINT) WIDE))
{
	int retcode;
	const char *proc = NULL;
	int wildcards;
	TDSSOCKET *tds;
	DSTR schema_name  = DSTR_INITIALIZER;
	DSTR catalog_name = DSTR_INITIALIZER;
	DSTR table_name   = DSTR_INITIALIZER;
	DSTR table_type   = DSTR_INITIALIZER;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLTables(%p, %p, %d, %p, %d, %p, %d, %p, %d)\n",
		    hstmt, szCatalogName, cbCatalogName, szSchemaName, cbSchemaName, szTableName, cbTableName,
		    szTableType, cbTableType);

	tds = stmt->dbc->tds_socket;

	if (!odbc_dstr_copy(stmt->dbc, &catalog_name, cbCatalogName, szCatalogName)
	    || !odbc_dstr_copy(stmt->dbc, &schema_name, cbSchemaName, szSchemaName)
	    || !odbc_dstr_copy(stmt->dbc, &table_name, cbTableName, szTableName)
	    || !odbc_dstr_copy(stmt->dbc, &table_type, cbTableType, szTableType))
		goto memory_error;

	if (cbTableName == SQL_NTS)
		cbTableName = tds_dstr_len(&table_name);
	if (cbSchemaName == SQL_NTS)
		cbSchemaName = tds_dstr_len(&schema_name);

	/* support wildcards on catalog (only odbc 3) */
	wildcards = 0;
	if (stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3 && stmt->dbc->attr.metadata_id == SQL_FALSE &&
	    (strchr(tds_dstr_cstr(&catalog_name), '%') || strchr(tds_dstr_cstr(&catalog_name), '_')))
		wildcards = 1;

	proc = "sp_tables";
	if (!tds_dstr_isempty(&catalog_name)) {
		if (wildcards) {
			/* if catalog specified and wildcards use sp_tableswc under mssql2k */
			if (TDS_IS_MSSQL(tds) && tds->conn->product_version >= TDS_MS_VER(8,0,0)) {
				proc = "sp_tableswc";
				if (tds_dstr_isempty(&schema_name))
					if (!tds_dstr_copy(&schema_name, "%"))
						goto memory_error;
			}
			/*
			 * TODO support wildcards on catalog even for Sybase
			 * first execute a select name from master..sysdatabases where name like catalog quoted
			 * build a db1..sp_tables args db2..sp_tables args ... query
			 * collapse results in a single recordset (how??)
			 */
		} else {
			/* if catalog specified and not wildcards use catatog on name (catalog..sp_tables) */
			proc = "..sp_tables";
		}
	}

	/* fix type if needed quoting it */
	if (!tds_dstr_isempty(&table_type)) {
		int to_fix = 0;
		int elements = 0;
		const char *p = tds_dstr_cstr(&table_type);
		const char *const end = p + tds_dstr_len(&table_type);

		for (;;) {
			const char *begin = p;

			p = (const char*) memchr(p, ',', end - p);
			if (!p)
				p = end;
			++elements;
			if ((p - begin) < 2 || begin[0] != '\'' || p[-1] != '\'')
				to_fix = 1;
			if (p >= end)
				break;
			++p;
		}
		/* fix it */
		tdsdump_log(TDS_DBG_INFO1, "to_fix %d elements %d\n", to_fix, elements);
		if (to_fix) {
			char *dst, *type;

			tdsdump_log(TDS_DBG_INFO1, "fixing type elements\n");
			type = tds_new(char, tds_dstr_len(&table_type) + elements * 2 + 3);
			if (!type)
				goto memory_error;

			p = tds_dstr_cstr(&table_type);
			dst = type;
			for (;;) {
				const char *begin = p;

				p = (const char*) memchr(p, ',', end - p);
				if (!p)
					p = end;
				if ((p - begin) < 2 || begin[0] != '\'' || p[-1] != '\'') {
					*dst++ = '\'';
					memcpy(dst, begin, p - begin);
					dst += p - begin;
					*dst++ = '\'';
				} else {
					memcpy(dst, begin, p - begin);
					dst += p - begin;
				}
				if (p >= end)
					break;
				*dst++ = *p++;
			}
			*dst = 0;
			if (!tds_dstr_set(&table_type, type)) {
				free(type);
				goto memory_error;
			}
		}
	}

	/* special case for catalog list */
	if (strcmp(tds_dstr_cstr(&catalog_name), "%") == 0 && cbTableName <= 0 && cbSchemaName <= 0) {
		retcode =
			odbc_stat_execute(stmt _wide, "sp_tables", 3, "$!P@table_name", "", 0,
					  "$!P@table_owner", "", 0, "!P@table_qualifier", "%", 1);
	} else {
		retcode =
			odbc_stat_execute(stmt _wide, proc, 4,
				"!P@table_name", tds_dstr_cstr(&table_name), tds_dstr_len(&table_name),
				"!P@table_owner", tds_dstr_cstr(&schema_name), tds_dstr_len(&schema_name),
				"!P@table_qualifier", tds_dstr_cstr(&catalog_name), tds_dstr_len(&catalog_name),
				"!@table_type", tds_dstr_cstr(&table_type), tds_dstr_len(&table_type));
	}
	tds_dstr_free(&schema_name);
	tds_dstr_free(&catalog_name);
	tds_dstr_free(&table_name);
	tds_dstr_free(&table_type);
	if (SQL_SUCCEEDED(retcode) && stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3) {
		odbc_col_setname(stmt, 1, "TABLE_CAT");
		odbc_col_setname(stmt, 2, "TABLE_SCHEM");
	}
	ODBC_EXIT_(stmt);

memory_error:
	tds_dstr_free(&schema_name);
	tds_dstr_free(&catalog_name);
	tds_dstr_free(&table_name);
	tds_dstr_free(&table_type);
	odbc_errs_add(&stmt->errs, "HY001", NULL);
	ODBC_EXIT_(stmt);
}

/**
 * Log a useful message about unimplemented options
 * Defying belief, Microsoft defines mutually exclusive options that
 * some ODBC implementations #define as duplicate values (meaning, of course,
 * that they couldn't be implemented in the same function because they're
 * indistinguishable.
 *
 * Those duplicates are commented out below.
 */
static void
odbc_log_unimplemented_type(const char function_name[], int fType)
{
	const char *name, *category;

	switch (fType) {
#ifdef SQL_ALTER_SCHEMA
	case SQL_ALTER_SCHEMA:
		name = "SQL_ALTER_SCHEMA";
		category = "Supported SQL";
		break;
#endif
#ifdef SQL_ANSI_SQL_DATETIME_LITERALS
	case SQL_ANSI_SQL_DATETIME_LITERALS:
		name = "SQL_ANSI_SQL_DATETIME_LITERALS";
		category = "Supported SQL";
		break;
#endif
	case SQL_COLLATION_SEQ:
		name = "SQL_COLLATION_SEQ";
		category = "Data Source Information";
		break;
	case SQL_CONVERT_BIGINT:
		name = "SQL_CONVERT_BIGINT";
		category = "Conversion Information";
		break;
	case SQL_CONVERT_DATE:
		name = "SQL_CONVERT_DATE";
		category = "Conversion Information";
		break;
	case SQL_CONVERT_DOUBLE:
		name = "SQL_CONVERT_DOUBLE";
		category = "Conversion Information";
		break;
	case SQL_CONVERT_INTERVAL_DAY_TIME:
		name = "SQL_CONVERT_INTERVAL_DAY_TIME";
		category = "Conversion Information";
		break;
	case SQL_CONVERT_INTERVAL_YEAR_MONTH:
		name = "SQL_CONVERT_INTERVAL_YEAR_MONTH";
		category = "Conversion Information";
		break;
	case SQL_DM_VER:
		name = "SQL_DM_VER";
		category = "Added for ODBC 3.x";
		break;
	case SQL_DRIVER_HLIB:
		name = "SQL_DRIVER_HLIB";
		category = "Driver Information";
		break;
#ifdef SQL_ODBC_STANDARD_CLI_CONFORMANCE
	case SQL_ODBC_STANDARD_CLI_CONFORMANCE:
		name = "SQL_ODBC_STANDARD_CLI_CONFORMANCE";
		category = "Driver Information";
		break;
#endif
	case SQL_USER_NAME:
		name = "SQL_USER_NAME";
		category = "Data Source Information";
		break;
	/* TODO extension SQL_INFO_SS_NETLIB_NAME ?? */
	default:
		name = "unknown";
		category = "unknown";
		break;
	}

	tdsdump_log(TDS_DBG_INFO1, "not implemented: %s: option/type %d(%s) [category %s]\n", function_name, fType, name,
		    category);

	return;
}

static int
odbc_quote_metadata(TDS_DBC * dbc, char type, char *dest, DSTR * dstr)
{
	int unquote = 0;
	char prev, buf[1200], *dst;
	const char *s = tds_dstr_cstr(dstr);
	int len = tds_dstr_len(dstr);

	/* limit string/id lengths */
	if (len > 384)
		len = 384;

	if (!type || (type == 'O' && dbc->attr.metadata_id == SQL_FALSE)) {
		if (dest)
			memcpy(dest, s, len);
		return len;
	}

	/* where we can have ID or PV */
	assert(type == 'P' || (type == 'O' && dbc->attr.metadata_id != SQL_FALSE));

	/* ID ? */
	if (dbc->attr.metadata_id != SQL_FALSE) {
		/* strip leading and trailing spaces */
		while (len > 0 && *s == ' ')
			++s, --len;
		while (len > 0 && s[len - 1] == ' ')
			--len;
		/* unquote if necessary */
		if (len > 2 && *s == '\"' && s[len - 1] == '\"') {
			++s, len -= 2;
			unquote = 1;
		}
	}

	if (!dest)
		dest = buf;
	dst = dest;

	/*
	 * handle patterns
	 * "'" -> "''" (normal string quoting)
	 *
	 * if metadata_id is FALSE
	 * "\_" -> "[_]"
	 * "\%" -> "[%]"
	 * "[" -> "[[]"
	 *
	 * if metadata_id is TRUE
	 * "\"\"" -> "\"" (if unquote id)
	 * "_" -> "[_]"
	 * "%" -> "[%]"
	 * "[" -> "[[]"
	 */
	prev = 0;
	for (; --len >= 0; ++s) {
		switch (*s) {
		case '\"':
			if (unquote && prev == '\"') {
				prev = 0;	/* avoid "\"\"\"" -> "\"" */
				--dst;
				continue;
			}
			break;
		case '_':
		case '%':
			if (dbc->attr.metadata_id == SQL_FALSE) {
				if (prev != '\\')
					break;
				--dst;
			}
		case '[':
			if (type != 'P')
				break;
			/* quote search string */
			*dst++ = '[';
			*dst++ = *s;
			*dst++ = ']';
			prev = 0;
			continue;
		}
		*dst++ = prev = *s;
	}
	return dst - dest;
}

static TDSPARAMINFO*
odbc_add_char_param(TDSSOCKET *tds, TDSPARAMINFO *params, const char *name, const char *value, size_t len)
{
	TDSCOLUMN *col;

	params = tds_alloc_param_result(params);
	if (!params)
		return NULL;

	col = params->columns[params->num_cols-1];
	if (!tds_dstr_copy(&col->column_name, name))
		return NULL;
	tds_set_param_type(tds->conn, col, IS_TDS7_PLUS(tds->conn) ? XSYBNVARCHAR : SYBVARCHAR);

	col->column_size = len;
	if (!tds_alloc_param_data(col))
		return NULL;

	memcpy(col->column_data, value, len);
	col->column_cur_size = len;

	return params;
}

static TDSPARAMINFO*
odbc_add_int_param(TDSSOCKET *tds, TDSPARAMINFO *params, const char *name, int value)
{
	TDSCOLUMN *col;

	params = tds_alloc_param_result(params);
	if (!params)
		return NULL;

	col = params->columns[params->num_cols-1];
	if (!tds_dstr_copy(&col->column_name, name))
		return NULL;
	tds_set_param_type(tds->conn, col, SYBINT4);

	if (!tds_alloc_param_data(col))
		return NULL;

	*((TDS_INT*) col->column_data) = value;
	col->column_cur_size = sizeof(TDS_INT);

	return params;
}


static SQLRETURN
odbc_stat_execute(TDS_STMT * stmt _WIDE, const char *begin, int nparams, ...)
{
	int i, len, param_qualifier = -1;
	char *proc = NULL, *p;
	SQLRETURN retcode;
	va_list marker;
	DSTR qualifier = DSTR_INITIALIZER, value = DSTR_INITIALIZER;
	TDSPARAMINFO *params;

	/* read all params and calc len required */
	va_start(marker, nparams);
	len = strlen(begin) + 3;
	params = tds_alloc_results(0);
	if (!params)
		goto mem_error;
	for (i = 0; i < nparams; ++i) {
		int param_len;
		bool convert = true;
		bool add_always = false;
		DSTR *out;
		const char *name;
		char type;

		p = va_arg(marker, char *);

		for (; ; ++p) {
			switch (*p) {
			case '$':
				add_always = true;
				continue;
			case '!':
				convert = false;
				continue;
			case 'V':	/* ODBC version */
			case 'O':	/* ordinary arguments */
			case 'P':	/* pattern value arguments */
				type = *p++;
				break;
			default:
				type = 0;	/* ordinary type */
				break;
			}
			break;
		}
		name = p;

		p = va_arg(marker, char *);
		param_len = va_arg(marker, int);

		if (type == 'V') {
			int ver = (stmt->dbc->env->attr.odbc_version == SQL_OV_ODBC3) ? 3: 2;
			if (!odbc_add_int_param(stmt->dbc->tds_socket, params, name, ver))
				goto mem_error;
			continue;
		}

#ifdef ENABLE_ODBC_WIDE
		if (!convert)
			out = tds_dstr_copyn(&value, p, param_len);
		else
			out = odbc_dstr_copy(stmt->dbc, &value, param_len, (ODBC_CHAR *) p);
#else
		out = odbc_dstr_copy(stmt->dbc, &value, param_len, (ODBC_CHAR *) p);
#endif
		if (!out)
			goto mem_error;

		if (add_always || !tds_dstr_isempty(&value)) {
			char buf[1200];
			int l;

			l = odbc_quote_metadata(stmt->dbc, type, buf, &value);
			if (!odbc_add_char_param(stmt->dbc->tds_socket, params, name, buf, l))
				goto mem_error;

			if (begin[0] == '.' && strstr(name, "qualifier")) {
				if (!tds_dstr_dup(&qualifier, &value))
					goto mem_error;
				len += tds_quote_id_rpc(stmt->dbc->tds_socket, NULL,
							tds_dstr_cstr(&qualifier), tds_dstr_len(&qualifier));
				param_qualifier = i;
			}
		}
	}
	tds_dstr_free(&value);

	/* proc is neither mb or wide, is always utf encoded */
	retcode = odbc_set_stmt_query(stmt, (ODBC_CHAR *) "-", 1 _wide0);
	if (retcode != SQL_SUCCESS)
		goto error;
	stmt->prepared_query_is_rpc = 1;

	/* set params */
	tds_free_param_results(stmt->params);
	stmt->param_count = params->num_cols;
	stmt->param_num = 1 + stmt->param_count;
	stmt->params = params;
	params = NULL;

	/* allocate space for string */
	if (!tds_dstr_alloc(&stmt->query, len))
		goto mem_error;
	proc = tds_dstr_buf(&stmt->query);

	/* build query string */
	p = proc;
	if (param_qualifier >= 0)
		p += tds_quote_id_rpc(stmt->dbc->tds_socket, p, tds_dstr_cstr(&qualifier), tds_dstr_len(&qualifier));
	tds_dstr_free(&qualifier);
	strcpy(p, begin);
	p += strlen(begin);
	tds_dstr_setlen(&stmt->query, p - proc);
	assert(p + 1 <= proc + len);

	/* execute it */
	retcode = odbc_SQLExecute(stmt);
	if (SQL_SUCCEEDED(retcode))
		odbc_upper_column_names(stmt);

	va_end(marker);
	ODBC_RETURN(stmt, retcode);

mem_error:
	odbc_errs_add(&stmt->errs, "HY001", NULL);

error:
	tds_dstr_free(&value);
	tds_dstr_free(&qualifier);
	tds_free_results(params);
	va_end(marker);
	return SQL_ERROR;
}

static SQLRETURN
odbc_free_dynamic(TDS_STMT * stmt)
{
	TDSSOCKET *tds;

	if (!stmt->dyn)
		return TDS_SUCCESS;

	tds = stmt->dbc->tds_socket;
	if (!tds_needs_unprepare(tds->conn, stmt->dyn)) {
		tds_release_dynamic(&stmt->dyn);
		return SQL_SUCCESS;
	}

	if (odbc_lock_statement(stmt)) {
		if (TDS_SUCCEED(tds_submit_unprepare(stmt->tds, stmt->dyn))
		    && TDS_SUCCEED(tds_process_simple_query(stmt->tds))) {
			odbc_unlock_statement(stmt);
			tds_release_dynamic(&stmt->dyn);
			return SQL_SUCCESS;
		}
	}

	if (TDS_SUCCEED(tds_deferred_unprepare(tds->conn, stmt->dyn))) {
		tds_release_dynamic(&stmt->dyn);
		return SQL_SUCCESS;
	}

	ODBC_SAFE_ERROR(stmt);
	return SQL_ERROR;
}

/**
 * Close server cursors
 */
static SQLRETURN
odbc_free_cursor(TDS_STMT * stmt)
{
	TDSCURSOR *cursor = stmt->cursor;
	TDSSOCKET *tds;

	if (!cursor)
		return SQL_SUCCESS;

	/* if possible deallocate now */
	if (odbc_lock_statement(stmt)) {
		tds = stmt->tds;

		cursor->status.dealloc   = TDS_CURSOR_STATE_REQUESTED;
		if (TDS_SUCCEED(tds_cursor_close(tds, cursor))) {
			if (TDS_SUCCEED(tds_process_simple_query(tds))) {
				/* TODO check error */
				tds_cursor_dealloc(tds, cursor);
				tds_release_cursor(&stmt->cursor);
				return SQL_SUCCESS;
			}
		}
	}

	tds = stmt->dbc->tds_socket;
	if (TDS_SUCCEED(tds_deferred_cursor_dealloc(tds->conn, cursor))) {
		tds_release_cursor(&stmt->cursor);
		return SQL_SUCCESS;
	}

	ODBC_SAFE_ERROR(stmt);
	return SQL_ERROR;
}

SQLRETURN ODBC_PUBLIC ODBC_API
SQLSetScrollOptions(SQLHSTMT hstmt, SQLUSMALLINT fConcurrency, SQLLEN crowKeyset, SQLUSMALLINT crowRowset)
{
	SQLUSMALLINT info;
	SQLUINTEGER value, check;
	SQLUINTEGER cursor_type;

	ODBC_ENTER_HSTMT;

	tdsdump_log(TDS_DBG_FUNC, "SQLSetScrollOptions(%p, %u, %ld, %u)\n",
			hstmt, fConcurrency, (long int) crowKeyset, crowRowset);

	if (!stmt->dbc->cursor_support) {
		odbc_errs_add(&stmt->errs, "HYC00", NULL);
		ODBC_EXIT_(stmt);
	}

	if (stmt->cursor) {
		odbc_errs_add(&stmt->errs, "24000", NULL);
		ODBC_EXIT_(stmt);
	}

	switch (crowKeyset) {
	case SQL_SCROLL_FORWARD_ONLY:
		info = SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2;
		cursor_type = SQL_CURSOR_FORWARD_ONLY;
		break;
	case SQL_SCROLL_STATIC:
		info = SQL_STATIC_CURSOR_ATTRIBUTES2;
		cursor_type = SQL_CURSOR_STATIC;
		break;
	case SQL_SCROLL_KEYSET_DRIVEN:
		info = SQL_KEYSET_CURSOR_ATTRIBUTES2;
		cursor_type = SQL_CURSOR_KEYSET_DRIVEN;
		break;
	case SQL_SCROLL_DYNAMIC:
		info = SQL_DYNAMIC_CURSOR_ATTRIBUTES2;
		cursor_type = SQL_CURSOR_DYNAMIC;
		break;
	default:
		if (crowKeyset > crowRowset) {
			info = SQL_KEYSET_CURSOR_ATTRIBUTES2;
			cursor_type = SQL_CURSOR_KEYSET_DRIVEN;
			break;
		}

		odbc_errs_add(&stmt->errs, "HY107", NULL);
		ODBC_EXIT_(stmt);
	}

	switch (fConcurrency) {
	case SQL_CONCUR_READ_ONLY:
		check = SQL_CA2_READ_ONLY_CONCURRENCY;
		break;
	case SQL_CONCUR_LOCK:
		check = SQL_CA2_LOCK_CONCURRENCY;
		break;
	case SQL_CONCUR_ROWVER:
		check = SQL_CA2_OPT_ROWVER_CONCURRENCY;
		break;
	case SQL_CONCUR_VALUES:
		check = SQL_CA2_OPT_VALUES_CONCURRENCY;
		break;
	default:
		odbc_errs_add(&stmt->errs, "HY108", NULL);
		ODBC_EXIT_(stmt);
	}

	value = 0;
	odbc_SQLGetInfo(stmt->dbc, info, &value, sizeof(value), NULL _wide0);

	if ((value & check) == 0) {
		odbc_errs_add(&stmt->errs, "HYC00", NULL);
		ODBC_EXIT_(stmt);
	}

	odbc_SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER) (TDS_INTPTR) cursor_type, 0 _wide0);
	odbc_SQLSetStmtAttr(hstmt, SQL_ATTR_CONCURRENCY, (SQLPOINTER) (TDS_INTPTR) fConcurrency, 0 _wide0);
	odbc_SQLSetStmtAttr(hstmt, SQL_ATTR_KEYSET_SIZE, (SQLPOINTER) (TDS_INTPTR) crowKeyset, 0 _wide0);
	odbc_SQLSetStmtAttr(hstmt, SQL_ROWSET_SIZE, (SQLPOINTER) (TDS_INTPTR) crowRowset, 0 _wide0);

	ODBC_EXIT_(stmt);
}

static TDS_INT
get_int_col(TDSSOCKET *tds, TDSCOLUMN *col, TDS_INT default_value)
{
	int srctype = tds_get_conversion_type(col->on_server.column_type, col->on_server.column_size);
	TDS_CHAR *src = (TDS_CHAR *) col->column_data;
	TDS_INT srclen = col->column_cur_size;
	CONV_RESULT res;

	if (srclen < 0)
		return default_value;

	if (is_blob_col(col))
		src = ((TDSBLOB *) src)->textvalue;

	if (tds_convert(tds_get_ctx(tds), srctype, src, (TDS_UINT) srclen, SYBINT4, &res) < 0)
		return default_value;

	return res.i;
}

/**
 * Replace placeholders ('?') inside query with @PRMxxx name.
 * @param is_func Tell if query is a ODBC function call.
 * @returns Converted string, NULL on memory error.
 */
static char *
prepare_query_describing_params(const char *query, size_t *query_len, bool is_func)
{
#define PRM_FMT "@PRM%04d"
	int i = 0;
	size_t len, pos;
	const char *e, *s;
	size_t size = *query_len + 30;
	char *out;

	if (is_func)
		size += 10;

	out = tds_new(char, size);
	if (!out)
		goto memory_error;
	pos = 0;
	if (is_func)
		pos += sprintf(out + pos, "exec " PRM_FMT "=", ++i);

	s = query;
	for (;; ++i) {
		e = tds_next_placeholder(s);
		len = e ? e - s : strlen(s);
		if (pos + len + 12 >= size) {
			size = pos + len + 30;
			if (!TDS_RESIZE(out, size))
				goto memory_error;
		}
		memcpy(out + pos, s, len);
		pos += len;
		if (!e)
			break;
		pos += sprintf(out + pos, PRM_FMT, i + 1);

		s = e + 1;
	}
	out[pos] = 0;
	*query_len = pos;
	return out;

memory_error:
	free(out);
	return NULL;
#undef PRM_FMT
}

/**
 * Parse UTF-16 name for parameter name in the form @PRMxxxx.
 * @returns xxxx number or -1 if error.
 */
static int
param_index_from_name(const TDSCOLUMN *const col)
{
	const unsigned char *data = col->column_data;
	int i;
	unsigned n;
	char name[9];

	/* should be in format @PRMxxxx */
	if (col->column_cur_size != 8 * 2)
		return -1;

	/* convert to ASCII and lower the case */
	for (i = 0; i < 8; ++i) {
		name[i] = data[i * 2];
		if (data[i * 2 + 1])
			return -1;
	}
	name[1] |= 0x20;
	name[2] |= 0x20;
	name[3] |= 0x20;
	name[8] = 0;

	/* parse name */
	i = -1;
	if (sscanf(name, "@prm%u%n", &n, &i) < 1 || n < 1 || i != 8)
		return -1;

	return (int) (n - 1);
}

static bool
read_params(TDS_STMT *stmt)
{
	bool ret = false;
	TDSSOCKET *tds;
	TDSPARAMINFO *params = NULL;
	bool in_row = false;
	enum {
		COL_NAME,
		COL_PRECISION,
		COL_SCALE,
		COL_TYPE,
		COL_LENGTH,
		NUM_COLUMNS
	};
	static const char *const column_names[NUM_COLUMNS] = {
		"NAME",
		"SUGGESTED_PRECISION",
		"SUGGESTED_SCALE",
		"SUGGESTED_TDS_TYPE_ID",
		"SUGGESTED_TDS_LENGTH",
	};
	unsigned column_idx[NUM_COLUMNS];
	const char *query = tds_dstr_cstr(&stmt->query);
	size_t query_len = tds_dstr_len(&stmt->query);
	unsigned num_params = tds_count_placeholders(query);
	char *new_query = NULL;
	TDSCOLUMN col[1];

	/* there should be not so many parameters, avoid client to mess around */
	if (num_params >= 998) {
		stmt->params_queried = 1;
		return false;
	}

	/* allocate tds */
	if (!odbc_lock_statement(stmt)) {
		odbc_errs_reset(&stmt->errs);
		return false;
	}

	tds = stmt->tds;
	stmt->params_queried = 1;

	/* currently supported only by MSSQL 2012 */
	if (!TDS_IS_MSSQL(tds) || tds->conn->product_version < TDS_MS_VER(11,0,0)) {
		odbc_unlock_statement(stmt);
		return false;
	}

	/* replace parameters with names */
	if (stmt->prepared_query_is_func)
		++num_params;
	if (num_params) {
		new_query = prepare_query_describing_params(query, &query_len, stmt->prepared_query_is_func);
		if (!new_query)
			goto memory_error;
		query = new_query;
	}

	/* send query */
	params = odbc_add_char_param(tds, NULL, "", query, query_len);
	if (!params)
		goto memory_error;

	if (TDS_FAILED(tds_submit_rpc(tds, "sp_describe_undeclared_parameters", params, NULL)))
		goto error;
	tds_free_param_results(params);
	params = NULL;

	/* clear IPD and APD */
	desc_alloc_records(stmt->apd, 0);
	desc_alloc_records(stmt->ipd, 0);
	/* set IPD size */
	desc_alloc_records(stmt->ipd, num_params);

	ret = true;
	for (;;) {
		TDS_INT result_type;
		int done_flags;
		TDSRESULTINFO *res_info;
		ptrdiff_t i, j, num_cols;
		struct _drecord *drec;
		int idx, scale, precision, len, tds_type;

		switch (tds_process_tokens(tds, &result_type, &done_flags, TDS_RETURN_ROWFMT|TDS_RETURN_DONE|TDS_RETURN_ROW)) {
		case TDS_SUCCESS:
			switch (result_type) {
			case TDS_DONE_RESULT:
			case TDS_DONEPROC_RESULT:
			case TDS_DONEINPROC_RESULT:
				in_row = false;
				break;

			case TDS_ROWFMT_RESULT:
				/* parse results, scan all columns to find needed ones */
				/* extract all column indexes */
				res_info = tds->current_results;
				num_cols = res_info->num_cols;

				for (i = 0; i < NUM_COLUMNS; i++)
					column_idx[i] = ~0u;
				for (i = 0; i < num_cols; i++) {
					char *name;
					TDSCOLUMN *col;

					col = res_info->columns[i];
					name = tds_dstr_buf(&col->column_name);
					tds_ascii_strupr(name);
					for (j = 0; j < NUM_COLUMNS; j++)
						if (strcmp(name, column_names[j]) == 0)
							column_idx[j] = i;
				}
				in_row = true;
				for (i = 0; i < NUM_COLUMNS; i++)
					if (column_idx[i] == ~0u)
						in_row = false;
				break;
			case TDS_ROW_RESULT:
				if (!in_row)
					break;
				/*
				 * convert type to SQL.
				 * Set precision (fixed on some types) and scale.
				 * Set length (based on type and fields).
				 */
				res_info = tds->current_results;

				idx = param_index_from_name(res_info->columns[column_idx[COL_NAME]]);
				if (idx < 0 || idx >= num_params)
					break;
				drec = &stmt->ipd->records[idx];
				memset(col, 0, sizeof(col));
				tds_type = get_int_col(tds, res_info->columns[column_idx[COL_TYPE]], -2);
				len = get_int_col(tds, res_info->columns[column_idx[COL_LENGTH]], -2);
				precision = get_int_col(tds, res_info->columns[column_idx[COL_PRECISION]], -2);
				scale = get_int_col(tds, res_info->columns[column_idx[COL_SCALE]], -2);
				if (tds_type < -1 || len < -1 || precision < -1 || scale < -1)
					break;
				tds_set_column_type(tds->conn, col, tds_type);
				col->on_server.column_size = col->column_size = len;
				col->column_prec = precision;
				col->column_scale = scale;
				drec->sql_desc_nullable = SQL_NULLABLE;
				odbc_set_sql_type_info(col, drec, stmt->dbc->env->attr.odbc_version);
				break;
			}
			continue;
		case TDS_NO_MORE_RESULTS:
			break;
		case TDS_CANCELLED:
			odbc_errs_add(&stmt->errs, "HY008", NULL);
		default:
			stmt->errs.lastrc = SQL_ERROR;
			ret = false;
			break;
		}
		break;
	}
	odbc_unlock_statement(stmt);
	free(new_query);
	new_query = NULL;

	/* clear IPD on error */
	if (!ret)
		desc_alloc_records(stmt->ipd, 0);

	/* mark we have the information (we need to reset changing query) */
	stmt->params_queried = 1;
	return ret;

memory_error:
	free(new_query);
	tds_free_param_results(params);
	odbc_unlock_statement(stmt);
	odbc_errs_add(&stmt->errs, "HY001", NULL);
	return false;

error:
	free(new_query);
	tds_free_param_results(params);
	odbc_unlock_statement(stmt);
	return false;
}

#include "odbc_export.h"

/* Under Windows driver exports functions */
/* The win_ prefix is to avoid clash with inline function.
 * The prefix is removed from exported functions using definition file.
 */
#ifdef _WIN32
RETCODE SQL_API win_bcp_initA(HDBC hdbc, const char *tblname, const char *hfile,
	const char *errfile, int direction);
RETCODE SQL_API win_bcp_initW(HDBC hdbc, const SQLWCHAR *tblname, const SQLWCHAR *hfile,
	const SQLWCHAR *errfile, int direction);
RETCODE SQL_API win_bcp_control(HDBC hdbc, int field, void *value);
RETCODE SQL_API win_bcp_colptr(HDBC hdbc, const unsigned char * colptr, int table_column);
RETCODE SQL_API win_bcp_sendrow(HDBC hdbc);
int SQL_API win_bcp_batch(HDBC hdbc);
int SQL_API win_bcp_done(HDBC hdbc);
RETCODE SQL_API win_bcp_bind(HDBC hdbc, const unsigned char * varaddr, int prefixlen, int varlen,
	const unsigned char * terminator, int termlen, int vartype, int table_column);

RETCODE SQL_API
win_bcp_initA(HDBC hdbc, const char *tblname, const char *hfile, const char *errfile, int direction)
{
	return bcp_initA(hdbc, tblname, hfile, errfile, direction);
}

#ifdef ENABLE_ODBC_WIDE
RETCODE SQL_API
win_bcp_initW(HDBC hdbc, const SQLWCHAR *tblname, const SQLWCHAR *hfile, const SQLWCHAR *errfile, int direction)
{
	return bcp_initW(hdbc, tblname, hfile, errfile, direction);
}
#endif

RETCODE SQL_API
win_bcp_control(HDBC hdbc, int field, void *value)
{
	return bcp_control(hdbc, field, value);
}

RETCODE SQL_API
win_bcp_colptr(HDBC hdbc, const unsigned char * colptr, int table_column)
{
	return bcp_colptr(hdbc, colptr, table_column);
}

RETCODE SQL_API
win_bcp_sendrow(HDBC hdbc)
{
	return bcp_sendrow(hdbc);
}

int SQL_API
win_bcp_batch(HDBC hdbc)
{
	return bcp_batch(hdbc);
}

int SQL_API
win_bcp_done(HDBC hdbc)
{
	return bcp_done(hdbc);
}

RETCODE SQL_API
win_bcp_bind(HDBC hdbc, const unsigned char * varaddr, int prefixlen, int varlen,
	const unsigned char * terminator, int termlen, int vartype, int table_column)
{
	return bcp_bind(hdbc, varaddr, prefixlen, varlen, terminator, termlen, vartype, table_column);
}
#endif

