/* Author : sebastien.flaesch@4js.com Date : 2025/06/13 MS ODBC driver : Version 18.5.1.1 Linux Platform : Linux Debian 12 64bits SQL Server : Version 2025 CTP 2.0 Test : SELECT of VECTOR + SQLFetchScroll() fail Status : Considered as MS ODBC bug. Problem : SQLFetchScroll() fails with this error: SQL State: 42000 SQL code : 16926 Message : [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]sp_cursoroption: The column ID (2) does not correspond to a text, ntext, or image column. When fetching VECTOR data with CAST(), no error occurs. Notes: 1) We use SQLWCHAR and iconv() to convert to/from UTF-16 ... 2) No need to install unixODBC: we link directly with libmsodbcsql-18.so: gcc -o odbctest8.bin odbctest8.c \ -I /opt3/dbs/uxo/2.3/include -I /opt3/dbs/snc/18.5.1.1/include \ -L /opt3/dbs/snc/18.5.1.1/lib64 -lmsodbcsql-18 */ #include #include #include #include #include #include #include #include #include #include #define DBGMSG(l,s) fprintf(stderr,s); fputc('\n',stderr); #define DBGMSGINT(l,s,i) fprintf(stderr,s,i); fputc('\n',stderr); #define DBGMSGSTR(l,s,p) fprintf(stderr,s,p); fputc('\n',stderr); static const char * getErrorInfo(SQLSMALLINT sqlhdltype, SQLHANDLE sqlhandle); #define CHECK_RCODE(t,h,m) \ if ( rcode != SQL_NO_DATA \ && rcode != SQL_SUCCESS \ && rcode != SQL_SUCCESS_WITH_INFO \ && rcode != SQL_NEED_DATA ) { \ fprintf(stderr,"Error %d at: %s\n",rcode,m); \ getErrorInfo(t,h); \ exit(1); \ } static SQLHENV m_henv; static SQLHDBC m_hdbc; static char *convBuff; static int convBuffLen; static void ensureConvBuffCap(int sz) { if (convBuffLen < sz) { if (convBuff != NULL) free(convBuff); convBuff = (char *) malloc(sz); convBuffLen = sz; } } static iconv_t cdMBtoWC; static iconv_t cdWCtoMB; static int initIConv() { const char *cs; static int initialized; if (initialized) return 0; cs = nl_langinfo(CODESET); if ((cdMBtoWC = iconv_open("UTF-16LE", cs)) == (iconv_t) - 1) return -1; if ((cdWCtoMB = iconv_open(cs, "UTF-16LE")) == (iconv_t) - 1) return -2; initialized = 1; return 0; } static int iconvConvert(iconv_t cd, const char *src, int slen, char *dst, int dlen) { size_t r; char *inbuf = (char *) src; size_t inbytesleft = (size_t) slen; char *outbuf = dst; int sts = (cd == cdMBtoWC) ? 2 : 1; /* Room for string terminator */ size_t outbytesleft = (size_t) (dlen - sts); r = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft); if (r == E2BIG) { DBGMSG(1, "iconvConvert(): Missing out buffer space for conversion."); return -2; } if (r == (size_t) - 1) { DBGMSGINT(1, "iconvConvert(): Conversion error: %d", errno); return -1; } *outbuf++ = '\0'; if (cd == cdMBtoWC) *outbuf = '\0'; return (dlen - outbytesleft - sts); } /* MultiByte to WideChar (UTF-16) conversion: * Returns number of CHARS of the result string, without terminator. * WARNING: blen is provided as a number of bytes for WC buffer! */ static int myMBtoWC2(const char *s, int len, SQLWCHAR * buf, int blen) { int nb = iconvConvert(cdMBtoWC, s, len, (char *) buf, blen); return (nb / sizeof(SQLWCHAR)); } static int myMBtoWC(const char *s, SQLWCHAR * buf, int blen) { return myMBtoWC2(s, strlen(s), buf, blen); } /* Compute number of bytes of an UTF-16 string: Do not use wcslen()!!! */ static int utf16size(const SQLWCHAR * s) { const SQLWCHAR *s0 = s; while (*s++ != 0); return (s - s0) * sizeof(SQLWCHAR); } /* WideChar (UTF-16) to MultyByte conversion: * Returns number of BYTES of the result string, without terminator. */ static int myWCtoMB2(const SQLWCHAR * s, int len, char *buf, int blen) { return iconvConvert(cdWCtoMB, (char *) s, len, buf, blen); } static int myWCtoMB(const SQLWCHAR * s, char *buf, int blen) { return myWCtoMB2(s, utf16size(s), buf, blen); } static void myWCtoMB_ASCII(SQLWCHAR * s, char *d, int c) { int i; for (i = 0; i < c; i++) d[i] = (char) s[i]; d[c] = '\0'; } static SQLWCHAR *getWC(const char *s) { int nc; ensureConvBuffCap((int) ((strlen(s) + 1) * sizeof(SQLWCHAR))); nc = myMBtoWC(s, (SQLWCHAR *) convBuff, convBuffLen); return (nc >= 0) ? (SQLWCHAR *) convBuff : NULL; } static char *getMB(const SQLWCHAR *s) { int nc; ensureConvBuffCap((utf16size(s) + 1) * 6); nc = myWCtoMB(s, (char *) convBuff, convBuffLen); return (nc >= 0) ? (char *) convBuff : NULL; } static int getDiagRec(int warnings, SQLSMALLINT sqlhdltype, SQLHANDLE sqlhandle, SQLCHAR * sqlstate, SQLCHAR * msgtext, SQLSMALLINT msgtextl) { SQLRETURN r = 0; SQLINTEGER naterror = 0; SQLSMALLINT recnum, mlen; SQLWCHAR wcs_sqlstate[SQL_SQLSTATE_SIZE + 1]; SQLWCHAR wcs_msgtext[SQL_MAX_MESSAGE_LENGTH + 1]; for (recnum = 1, r = SQL_SUCCESS; r == SQL_SUCCESS; recnum++) { sqlstate[0] = '\0'; msgtext[0] = '\0'; r = SQLGetDiagRecW(sqlhdltype, sqlhandle, recnum, wcs_sqlstate, &naterror, wcs_msgtext, msgtextl, &mlen); myWCtoMB_ASCII(wcs_sqlstate, (char *) sqlstate, SQL_SQLSTATE_SIZE); wcs_msgtext[mlen] = 0; if (myWCtoMB(wcs_msgtext, (char *) msgtext, msgtextl) < 0) { DBGMSG(1, "Could not convert WC to MB for SQL error message."); } if (!warnings && sqlstate[0] == '0' && sqlstate[1] == '1') continue; if (naterror || strcmp((char *) sqlstate, "01S02") != 0) break; } if (!SQL_SUCCEEDED(r)) { DBGMSG(2, "Could not get diagnostic informations."); sqlstate[0] = '\0'; msgtext[0] = '\0'; } else { if (mlen > 0 && msgtext[mlen - 1] == '\n') msgtext[mlen - 1] = '\0'; DBGMSG(3, "Diagnostic info:"); DBGMSGSTR(3, " SQL State: %s", (char *) sqlstate); DBGMSGINT(3, " SQL code : %d", (int) naterror); DBGMSGSTR(3, " Message : %s", (char *) msgtext); } /* Warning: SQL Server errors are positive! */ if (naterror != 100) naterror = -naterror; return naterror; } static const char * getErrorInfo(SQLSMALLINT sqlhdltype, SQLHANDLE sqlhandle) { SQLINTEGER naterror; static SQLCHAR sqlstate[SQL_SQLSTATE_SIZE + 1]; SQLCHAR msgtext[SQL_MAX_MESSAGE_LENGTH + 1]; SQLSMALLINT msgtextl = sizeof(msgtext); naterror = getDiagRec(0, sqlhdltype, sqlhandle, sqlstate, msgtext, msgtextl); return sqlstate; } static const char * getWarningInfo(SQLSMALLINT sqlhdltype, SQLHANDLE sqlhandle) { SQLINTEGER naterror; static SQLCHAR sqlstate[SQL_SQLSTATE_SIZE + 1]; SQLCHAR msgtext[SQL_MAX_MESSAGE_LENGTH + 1]; SQLSMALLINT msgtextl = sizeof(msgtext); naterror = getDiagRec(1, sqlhdltype, sqlhandle, sqlstate, msgtext, msgtextl); return sqlstate; } int main(int argc,char **argv) { SQLRETURN rcode; SQLHSTMT m_hstmt_0; SQLHSTMT m_hstmt; SQLSMALLINT fc = 0; SQLUSMALLINT pos; SQLLEN len; SQLULEN rowsFetched; SQLLEN v_ind_1; char * dbname; SQLWCHAR w_dbname[100]; char * usernm; SQLWCHAR w_usernm[100]; char * passwd; SQLWCHAR w_passwd[100]; if (argc == 1) { dbname = "snc_msvtest1_ida_utf8_2025"; usernm = "msvuser"; passwd = "fourjs"; } else if (argc == 4) { dbname = argv[1]; usernm = argv[2]; passwd = argv[3]; } else { fprintf(stderr,"Usage: %s dsn user pswd\n", argv[0]); exit(1); } if (initIConv() < 0) { DBGMSG(1, "Could not initialize the iconv() library."); exit(1); } m_henv = NULL; rcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv); CHECK_RCODE(SQL_HANDLE_ENV,NULL,"SQLAllocHandle EnvH"); rcode = SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER); CHECK_RCODE(SQL_HANDLE_ENV,m_henv,"ODBC V3"); m_hdbc = NULL; rcode = SQLAllocHandle(SQL_HANDLE_DBC, (SQLHANDLE) m_henv, (SQLHANDLE *) &m_hdbc); CHECK_RCODE(SQL_HANDLE_ENV,m_henv,"SQLAllocHandle DbcH"); rcode = SQLSetConnectAttr(m_hdbc, SQL_COPT_SS_PRESERVE_CURSORS, (SQLPOINTER) SQL_PC_ON, SQL_IS_INTEGER); CHECK_RCODE(SQL_HANDLE_ENV,m_hdbc,"SQL_COPT_SS_PRESERVE_CURSORS"); rcode = SQLSetConnectAttr(m_hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, 0); CHECK_RCODE(SQL_HANDLE_ENV,m_hdbc,"SQL_ATTR_AUTOCOMMIT"); rcode = SQLSetConnectAttr(m_hdbc, SQL_COPT_SS_MARS_ENABLED, (SQLPOINTER) SQL_MARS_ENABLED_YES, SQL_IS_UINTEGER); CHECK_RCODE(SQL_HANDLE_ENV,m_hdbc,"SQL_COPT_SS_MARS_ENABLED"); fprintf(stdout,">> Connecting to DB server...\n"); myMBtoWC(dbname, w_dbname, sizeof(w_dbname)); myMBtoWC(usernm, w_usernm, sizeof(w_usernm)); myMBtoWC(passwd, w_passwd, sizeof(w_passwd)); rcode = SQLConnectW(m_hdbc, w_dbname, SQL_NTS, w_usernm, SQL_NTS, w_passwd, SQL_NTS); CHECK_RCODE(SQL_HANDLE_DBC,m_hdbc,"SQLConnect"); m_hstmt_0 = NULL; rcode = SQLAllocHandle(SQL_HANDLE_STMT, m_hdbc, &m_hstmt_0); CHECK_RCODE(SQL_HANDLE_DBC,m_hdbc,"SQLAllocHandle StmtH 0"); fprintf(stdout,">> Preparing the SQL table...\n"); SQLExecDirectW(m_hstmt_0, getWC("DROP TABLE tab1"), SQL_NTS); SQLExecDirectW(m_hstmt_0, getWC("CREATE TABLE tab1 ( pkey INTEGER NOT NULL PRIMARY KEY, js VECTOR(4))"), SQL_NTS); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt_0,"SQLExecDirectW CREATE TABLE"); SQLExecDirectW(m_hstmt_0, getWC("INSERT INTO tab1 VALUES( 101, '[0.3,0.4,0.5,0.6]' )"), SQL_NTS); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt_0,"SQLExecDirectW INSERT #1"); rcode = SQLFreeHandle(SQL_HANDLE_STMT, (SQLHANDLE) m_hstmt_0); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQLFreeHandle"); m_hstmt = NULL; rcode = SQLAllocHandle(SQL_HANDLE_STMT, m_hdbc, &m_hstmt); CHECK_RCODE(SQL_HANDLE_DBC,m_hdbc,"SQLAllocHandle StmtH"); #ifdef USE_DEFAULT_CURSOR rcode = SQLSetStmtAttrW(m_hstmt, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER) SQL_CURSOR_FORWARD_ONLY, SQL_IS_INTEGER); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQLSetStmtAttr SQL_CURSOR_FORWARD_ONLY"); rcode = SQLSetStmtAttrW(m_hstmt, SQL_ATTR_CONCURRENCY, (SQLPOINTER) SQL_CONCUR_READ_ONLY, SQL_IS_INTEGER); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQLSetStmtAttr SQL_CONCUR_READ_ONLY"); #else rcode = SQLSetStmtAttrW(m_hstmt, SQL_SOPT_SS_CURSOR_OPTIONS, (SQLPOINTER) SQL_CO_FFO, SQL_IS_UINTEGER); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQLSetStmtAttr FFO"); #endif rcode = SQLExecDirectW(m_hstmt, getWC( "SELECT js FROM tab1 ORDER BY pkey" ), SQL_NTS); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQLExecDirectW"); fprintf(stderr,">> SQLExecDirectW rcode = %d\n", rcode); if (rcode == SQL_SUCCESS_WITH_INFO) { getWarningInfo(SQL_HANDLE_STMT, m_hstmt); } rcode = SQLNumResultCols(m_hstmt, &fc); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQLNumResultCols() failed"); for (pos = 1; pos <= fc; pos++) { SQLSMALLINT sqltype; SQLSMALLINT namelen; SQLULEN precision; SQLSMALLINT scale; SQLSMALLINT nullable; SQLULEN size; rcode = SQLDescribeColW(m_hstmt, pos, NULL, 0, &namelen, &sqltype, &precision, &scale, &nullable); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQLDescribeCol() failed"); fprintf(stdout, ">> col %ld type=%d\n", pos, sqltype); } /* rcode = SQLSetStmtAttrW(m_hstmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER) SQL_BIND_BY_COLUMN, SQL_IS_UINTEGER); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQL_BIND_BY_COLUMN failed"); rcode = SQLSetStmtAttrW(m_hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) 1, SQL_IS_UINTEGER); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQL_ATTR_ROW_ARRAY_SIZE failed"); rcode = SQLSetStmtAttrW(m_hstmt, SQL_ATTR_ROWS_FETCHED_PTR, (SQLPOINTER) &rowsFetched, SQL_IS_UINTEGER); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQL_ATTR_ROWS_FETCHED_PTR failed"); */ rcode = SQLBindCol(m_hstmt, 1, SQL_C_WCHAR, NULL, 0, &v_ind_1); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQLBindCol 1"); rcode = SQLFetchScroll(m_hstmt, SQL_FETCH_NEXT, 0); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQLFetch() failed"); fprintf(stdout,">> SQLFetchScroll 1: rcode = %d\n", rcode); if (rcode == SQL_SUCCESS_WITH_INFO) { getWarningInfo(SQL_HANDLE_STMT, m_hstmt); } rcode = SQLFetchScroll(m_hstmt, SQL_FETCH_NEXT, 0); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQLFetch() failed"); fprintf(stderr,">> SQLFetchScroll 2: rcode = %d\n", rcode); rcode = SQLMoreResults(m_hstmt); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQLMoreResuls() failed"); rcode = SQLFreeHandle(SQL_HANDLE_STMT, (SQLHANDLE) m_hstmt); CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt,"SQLFreeHandle"); rcode = SQLDisconnect(m_hdbc); CHECK_RCODE(SQL_HANDLE_DBC,m_hdbc,"SQLDisconnect"); rcode = SQLFreeHandle(SQL_HANDLE_DBC, (SQLHANDLE) m_hdbc); CHECK_RCODE(SQL_HANDLE_DBC,m_hdbc,"SQLFreeHandle DbcH"); rcode = SQLFreeHandle(SQL_HANDLE_ENV, (SQLHANDLE) m_henv); CHECK_RCODE(SQL_HANDLE_ENV,m_henv,"SQLFreeHandle EnvH"); fprintf(stdout,"Test terminated ...\n"); return 0; }