From ee51907a2f21686e96774a58d687d1ff5249b9a8 Mon Sep 17 00:00:00 2001 From: Will Hinson Date: Thu, 16 May 2024 00:03:36 -0400 Subject: [PATCH] Implemented support for the SQL Server sql_variant data type --- src/dbspecific.h | 1 + src/getdata.cpp | 32 ++++++++++++++++++++++++++++++++ src/pyodbc.h | 4 ++++ 3 files changed, 37 insertions(+) diff --git a/src/dbspecific.h b/src/dbspecific.h index d9bc4dc4..e6af0a93 100644 --- a/src/dbspecific.h +++ b/src/dbspecific.h @@ -11,6 +11,7 @@ // SQL Server +#define SQL_SS_VARIANT -150 // SQL Server 2008 SQL_VARIANT type #define SQL_SS_XML -152 // SQL Server 2005 XML type #define SQL_DB2_DECFLOAT -360 // IBM DB/2 DECFLOAT type #define SQL_DB2_XML -370 // IBM DB/2 XML type diff --git a/src/getdata.cpp b/src/getdata.cpp index 626f41b1..3831c5d9 100644 --- a/src/getdata.cpp +++ b/src/getdata.cpp @@ -38,6 +38,7 @@ void GetData_init() } static byte* ReallocOrFreeBuffer(byte* pb, Py_ssize_t cbNeed); +PyObject *GetData_SqlVariant(Cursor *cur, Py_ssize_t iCol); inline bool IsBinaryType(SQLSMALLINT sqltype) { @@ -751,8 +752,39 @@ PyObject* GetData(Cursor* cur, Py_ssize_t iCol) case SQL_SS_TIME2: return GetSqlServerTime(cur, iCol); + + case SQL_SS_VARIANT: + return GetData_SqlVariant(cur, iCol); } return RaiseErrorV("HY106", ProgrammingError, "ODBC SQL type %d is not yet supported. column-index=%zd type=%d", (int)pinfo->sql_type, iCol, (int)pinfo->sql_type); } + +PyObject *GetData_SqlVariant(Cursor *cur, Py_ssize_t iCol) { + char pBuff; + + SQLLEN indicator, variantType; + SQLRETURN retcode; + + // Call SQLGetData on the current column with a data length of 0. According to MS, this makes + // the ODBC driver read the sql_variant header which contains the underlying data type + pBuff = 0; + indicator = 0; + retcode = SQLGetData(cur->hstmt, static_cast(iCol + 1), SQL_C_BINARY, + &pBuff, 0, &indicator); + if (!SQL_SUCCEEDED(retcode)) + return RaiseErrorFromHandle(cur->cnxn, "SQLGetData", cur->cnxn->hdbc, cur->hstmt); + + // Get the SQL_CA_SS_VARIANT_TYPE field for the column which will contain the underlying data type + variantType = 0; + retcode = SQLColAttribute(cur->hstmt, iCol + 1, SQL_CA_SS_VARIANT_TYPE, NULL, 0, NULL, &variantType); + if (!SQL_SUCCEEDED(retcode)) + return RaiseErrorFromHandle(cur->cnxn, "SQLColAttribute", cur->cnxn->hdbc, cur->hstmt); + + // Replace the original SQL_VARIANT data type with the underlying data type then call GetData() again + cur->colinfos[iCol].sql_type = static_cast(variantType); + return GetData(cur, iCol); + + // NOTE: we don't free the hstmt here as it's managed by the cursor +} diff --git a/src/pyodbc.h b/src/pyodbc.h index a18529e9..9ef2bd77 100644 --- a/src/pyodbc.h +++ b/src/pyodbc.h @@ -76,6 +76,10 @@ typedef unsigned long long UINT64; #define SQL_CA_SS_CATALOG_NAME 1225 #endif +#ifndef SQL_CA_SS_VARIANT_TYPE +#define SQL_CA_SS_VARIANT_TYPE 1215 +#endif + inline bool IsSet(DWORD grf, DWORD flags) { return (grf & flags) == flags;