// The functions for reading a single value from the database using SQLGetData. There is a different function for // every data type. #include "pyodbc.h" #include "wrapper.h" #include "textenc.h" #include "pyodbcmodule.h" #include "cursor.h" #include "connection.h" #include "errors.h" #include "dbspecific.h" #include #include // NULL terminator notes: // // * pinfo->column_size, from SQLDescribeCol, does not include a NULL terminator. For example, column_size for a // char(10) column would be 10. (Also, when dealing with SQLWCHAR, it is the number of *characters*, not bytes.) // // * When passing a length to PyString_FromStringAndSize and similar Unicode functions, do not add the NULL // terminator -- it will be added automatically. See objects/stringobject.c // // * SQLGetData does not return the NULL terminator in the length indicator. (Therefore, you can pass this value // directly to the Python string functions.) // // * SQLGetData will write a NULL terminator in the output buffer, so you must leave room for it. You must also // include the NULL terminator in the buffer length passed to SQLGetData. // // ODBC generalization: // 1) Include NULL terminators in input buffer lengths. // 2) NULL terminators are not used in data lengths. void GetData_init() { PyDateTime_IMPORT; } static byte* ReallocOrFreeBuffer(byte* pb, Py_ssize_t cbNeed); inline bool IsBinaryType(SQLSMALLINT sqltype) { // Is this SQL type (e.g. SQL_VARBINARY) a binary type or not? switch (sqltype) { case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: return true; } return false; } inline bool IsWideType(SQLSMALLINT sqltype) { switch (sqltype) { case SQL_WCHAR: case SQL_WVARCHAR: case SQL_WLONGVARCHAR: case SQL_SS_XML: case SQL_DB2_XML: return true; } return false; } // TODO: Wont pyodbc_free crash if we didn't use pyodbc_realloc. static bool ReadVarColumn(Cursor* cur, Py_ssize_t iCol, SQLSMALLINT ctype, bool& isNull, byte*& pbResult, Py_ssize_t& cbResult) { // Called to read a variable-length column and return its data in a newly-allocated heap // buffer. // // Returns true if the read was successful and false if the read failed. If the read // failed a Python exception will have been set. // // If a non-null and non-empty value was read, pbResult will be set to a buffer containing // the data and cbResult will be set to the byte length. This length does *not* include a // null terminator. In this case the data *must* be freed using pyodbc_free. // // If a null value was read, isNull is set to true and pbResult and cbResult will be set to // 0. // // If a zero-length value was read, isNull is set to false and pbResult and cbResult will // be set to 0. isNull = false; pbResult = 0; cbResult = 0; const Py_ssize_t cbElement = (Py_ssize_t)(IsWideType(ctype) ? sizeof(ODBCCHAR) : 1); const Py_ssize_t cbNullTerminator = IsBinaryType(ctype) ? 0 : cbElement; // TODO: Make the initial allocation size configurable? Py_ssize_t cbAllocated = 4096; Py_ssize_t cbUsed = 0; byte* pb = (byte*)malloc((size_t)cbAllocated); if (!pb) { PyErr_NoMemory(); return false; } SQLRETURN ret = SQL_SUCCESS_WITH_INFO; do { // Call SQLGetData in a loop as long as it keeps returning partial data (ret == // SQL_SUCCESS_WITH_INFO). Each time through, update the buffer pb, cbAllocated, and // cbUsed. Py_ssize_t cbAvailable = cbAllocated - cbUsed; SQLLEN cbData = 0; Py_BEGIN_ALLOW_THREADS ret = SQLGetData(cur->hstmt, (SQLUSMALLINT)(iCol+1), ctype, &pb[cbUsed], (SQLLEN)cbAvailable, &cbData); Py_END_ALLOW_THREADS; TRACE("ReadVarColumn: SQLGetData avail=%d --> ret=%d cbData=%d\n", (int)cbAvailable, (int)ret, (int)cbData); if (!SQL_SUCCEEDED(ret) && ret != SQL_NO_DATA) { RaiseErrorFromHandle(cur->cnxn, "SQLGetData", cur->cnxn->hdbc, cur->hstmt); return false; } if (ret == SQL_SUCCESS && (int)cbData < 0) { // HACK: FreeTDS 0.91 on OS/X returns -4 for NULL data instead of SQL_NULL_DATA // (-1). I've traced into the code and it appears to be the result of assigning -1 // to a SQLLEN. We are going to treat all negative values as NULL. ret = SQL_NULL_DATA; cbData = 0; } // SQLGetData behavior is incredibly quirky: It doesn't tell us the total, the total // we've read, or even the amount just read. It returns the amount just read, plus any // remaining. Unfortunately, the only way to pick them apart is to subtract out the // amount of buffer we supplied. if (ret == SQL_SUCCESS_WITH_INFO) { // This means we read some data, but there is more. SQLGetData is very weird - it // sets cbRead to the number of bytes we read *plus* the amount remaining. Py_ssize_t cbRemaining = 0; // How many more bytes do we need to allocate, not including null? Py_ssize_t cbRead = 0; // How much did we just read, not including null? if (cbData == SQL_NO_TOTAL) { // This special value indicates there is more data but the driver can't tell us // how much more, so we'll just add whatever we want and try again. It also // tells us, however, that the buffer is full, so the amount we read equals the // amount we offered. Remember that if the type requires a null terminator, it // will be added *every* time, not just at the end, so we need to subtract it. cbRead = (cbAvailable - cbNullTerminator); cbRemaining = 1024 * 1024; } else if ((Py_ssize_t)cbData >= cbAvailable) { // We offered cbAvailable space, but there was cbData data. The driver filled // the buffer with what it could. Remember that if the type requires a null // terminator, the driver is going to append one on *every* read, so we need to // subtract them out. At least we know the exact data amount now and we can // allocate a precise amount. cbRead = (cbAvailable - cbNullTerminator); cbRemaining = cbData - cbRead; } else { // I would not expect to get here - we apparently read all of the data but the // driver did not return SQL_SUCCESS? cbRead = (cbData - cbNullTerminator); cbRemaining = 0; } cbUsed += cbRead; if (cbRemaining > 0) { // This is a tiny bit complicated by the fact that the data is null terminated, // meaning we haven't actually used up the entire buffer (cbAllocated), only // cbUsed (which should be cbAllocated - cbNullTerminator). Py_ssize_t cbNeed = cbUsed + cbRemaining + cbNullTerminator; pb = ReallocOrFreeBuffer(pb, cbNeed); if (!pb) return false; cbAllocated = cbNeed; } } else if (ret == SQL_SUCCESS) { // We read some data and this is the last batch (so we'll drop out of the // loop). // // If I'm reading the documentation correctly, SQLGetData is not going to // include the null terminator in cbRead. cbUsed += cbData; } } while (ret == SQL_SUCCESS_WITH_INFO); isNull = (ret == SQL_NULL_DATA); if (!isNull && cbUsed > 0) { pbResult = pb; cbResult = cbUsed; } else { pyodbc_free(pb); } return true; } static byte* ReallocOrFreeBuffer(byte* pb, Py_ssize_t cbNeed) { // Attempts to reallocate `pb` to size `cbNeed`. If the realloc fails, the original memory // is freed, a memory exception is set, and 0 is returned. Otherwise the new pointer is // returned. byte* pbNew = (byte*)realloc(pb, (size_t)cbNeed); if (pbNew == 0) { pyodbc_free(pb); PyErr_NoMemory(); return 0; } return pbNew; } static PyObject* GetText(Cursor* cur, Py_ssize_t iCol) { // We are reading one of the SQL_WCHAR, SQL_WVARCHAR, etc., and will return // a string. // // If there is no configuration we would expect this to be UTF-16 encoded data. (If no // byte-order-mark, we would expect it to be big-endian.) // // Now, just because the driver is telling us it is wide data doesn't mean it is true. // psqlodbc with UTF-8 will tell us it is wide data but you must ask for single-byte. // (Otherwise it is just UTF-8 with each character stored as 2 bytes.) That's why we allow // the user to configure. ColumnInfo* pinfo = &cur->colinfos[iCol]; const TextEnc& enc = IsWideType(pinfo->sql_type) ? cur->cnxn->sqlwchar_enc : cur->cnxn->sqlchar_enc; bool isNull = false; byte* pbData = 0; Py_ssize_t cbData = 0; if (!ReadVarColumn(cur, iCol, enc.ctype, isNull, pbData, cbData)) return 0; if (isNull) { I(pbData == 0 && cbData == 0); Py_RETURN_NONE; } PyObject* result = TextBufferToObject(enc, pbData, cbData); pyodbc_free(pbData); return result; } static PyObject* GetBinary(Cursor* cur, Py_ssize_t iCol) { // Reads SQL_BINARY. bool isNull = false; byte* pbData = 0; Py_ssize_t cbData = 0; if (!ReadVarColumn(cur, iCol, SQL_C_BINARY, isNull, pbData, cbData)) return 0; if (isNull) { I(pbData == 0 && cbData == 0); Py_RETURN_NONE; } PyObject* obj; #if PY_MAJOR_VERSION >= 3 obj = PyBytes_FromStringAndSize((char*)pbData, cbData); #else obj = PyByteArray_FromStringAndSize((char*)pbData, cbData); #endif pyodbc_free(pbData); return obj; } static PyObject* GetDataUser(Cursor* cur, Py_ssize_t iCol, int conv) { // conv // The index into the connection's user-defined conversions `conv_types`. bool isNull = false; byte* pbData = 0; Py_ssize_t cbData = 0; if (!ReadVarColumn(cur, iCol, SQL_C_BINARY, isNull, pbData, cbData)) return 0; if (isNull) { I(pbData == 0 && cbData == 0); Py_RETURN_NONE; } PyObject* value = PyBytes_FromStringAndSize((char*)pbData, cbData); pyodbc_free(pbData); if (!value) return 0; PyObject* result = PyObject_CallFunction(cur->cnxn->conv_funcs[conv], "(O)", value); Py_DECREF(value); if (!result) return 0; return result; } #if PY_VERSION_HEX < 0x02060000 static PyObject* GetDataBuffer(Cursor* cur, Py_ssize_t iCol) { PyObject* str = GetDataString(cur, iCol); if (str == Py_None) return str; PyObject* buffer = 0; if (str) { buffer = PyBuffer_FromObject(str, 0, PyString_GET_SIZE(str)); Py_DECREF(str); // If no buffer, release it. If buffer, the buffer owns it. } return buffer; } #endif static PyObject* GetDataDecimal(Cursor* cur, Py_ssize_t iCol) { // The SQL_NUMERIC_STRUCT support is hopeless (SQL Server ignores scale on input parameters and output columns, // Oracle does something else weird, and many drivers don't support it at all), so we'll rely on the Decimal's // string parsing. Unfortunately, the Decimal author does not pay attention to the locale, so we have to modify // the string ourselves. // // Oracle inserts group separators (commas in US, periods in some countries), so leave room for that too. // // Some databases support a 'money' type which also inserts currency symbols. Since we don't want to keep track of // all these, we'll ignore all characters we don't recognize. We will look for digits, negative sign (which I hope // is universal), and a decimal point ('.' or ',' usually). We'll do everything as Unicode in case currencies, // etc. are too far out. const TextEnc& enc = cur->cnxn->sqlwchar_enc; // I'm going to request the data as Unicode in case there is a weird currency symbol. If // this is a performance problems we may want a flag on this. bool isNull = false; byte* pbData = 0; Py_ssize_t cbData = 0; if (!ReadVarColumn(cur, iCol, enc.ctype, isNull, pbData, cbData)) return 0; if (isNull) { I(pbData == 0 && cbData == 0); Py_RETURN_NONE; } Object result(TextBufferToObject(enc, pbData, cbData)); pyodbc_free(pbData); if (!result) return 0; // Remove non-digits and convert the databases decimal to a '.' (required by decimal ctor). // // We are assuming that the decimal point and digits fit within the size of ODBCCHAR. // If Unicode, convert to UTF-8 and copy the digits and punctuation out. Since these are // all ASCII characters, we can ignore any multiple-byte characters. Fortunately, if a // character is multi-byte all bytes will have the high bit set. char* pch; Py_ssize_t cch; #if PY_MAJOR_VERSION >= 3 if (PyUnicode_Check(result)) { pch = (char*)PyUnicode_AsUTF8AndSize(result, &cch); } else { int n = PyBytes_AsStringAndSize(result, &pch, &cch); if (n < 0) pch = 0; } #else Object encoded; if (PyUnicode_Check(result)) { encoded = PyUnicode_AsUTF8String(result); if (!encoded) return 0; result = encoded.Detach(); } int n = PyString_AsStringAndSize(result, &pch, &cch); if (n < 0) pch = 0; #endif if (!pch) return 0; // TODO: Why is this limited to 100? Also, can we perform a check on the original and use // it as-is? char ascii[100]; size_t asciilen = 0; const char* pchMax = pch + cch; while (pch < pchMax) { if ((*pch & 0x80) == 0) { if (*pch == chDecimal) { // Must force it to use '.' since the Decimal class doesn't pay attention to the locale. ascii[asciilen++] = '.'; } else if ((*pch >= '0' && *pch <= '9') || *pch == '-') { ascii[asciilen++] = (char)(*pch); } } pch++; } ascii[asciilen] = 0; Object str(PyString_FromStringAndSize(ascii, (Py_ssize_t)asciilen)); if (!str) return 0; PyObject* decimal_type = GetClassForThread("decimal", "Decimal"); if (!decimal_type) return 0; PyObject* decimal = PyObject_CallFunction(decimal_type, "O", str.Get()); Py_DECREF(decimal_type); return decimal; } static PyObject* GetDataBit(Cursor* cur, Py_ssize_t iCol) { SQLCHAR ch; SQLLEN cbFetched; SQLRETURN ret; Py_BEGIN_ALLOW_THREADS ret = SQLGetData(cur->hstmt, (SQLUSMALLINT)(iCol+1), SQL_C_BIT, &ch, sizeof(ch), &cbFetched); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) return RaiseErrorFromHandle(cur->cnxn, "SQLGetData", cur->cnxn->hdbc, cur->hstmt); if (cbFetched == SQL_NULL_DATA) Py_RETURN_NONE; if (ch == SQL_TRUE) Py_RETURN_TRUE; Py_RETURN_FALSE; } static PyObject* GetDataLong(Cursor* cur, Py_ssize_t iCol) { ColumnInfo* pinfo = &cur->colinfos[iCol]; SQLINTEGER value; SQLLEN cbFetched; SQLRETURN ret; SQLSMALLINT nCType = pinfo->is_unsigned ? SQL_C_ULONG : SQL_C_LONG; Py_BEGIN_ALLOW_THREADS ret = SQLGetData(cur->hstmt, (SQLUSMALLINT)(iCol+1), nCType, &value, sizeof(value), &cbFetched); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) return RaiseErrorFromHandle(cur->cnxn, "SQLGetData", cur->cnxn->hdbc, cur->hstmt); if (cbFetched == SQL_NULL_DATA) Py_RETURN_NONE; if (pinfo->is_unsigned) return PyInt_FromLong(*(SQLINTEGER*)&value); return PyInt_FromLong(value); } static PyObject* GetDataLongLong(Cursor* cur, Py_ssize_t iCol) { ColumnInfo* pinfo = &cur->colinfos[iCol]; SQLSMALLINT nCType = pinfo->is_unsigned ? SQL_C_UBIGINT : SQL_C_SBIGINT; SQLBIGINT value; SQLLEN cbFetched; SQLRETURN ret; Py_BEGIN_ALLOW_THREADS ret = SQLGetData(cur->hstmt, (SQLUSMALLINT)(iCol+1), nCType, &value, sizeof(value), &cbFetched); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) return RaiseErrorFromHandle(cur->cnxn, "SQLGetData", cur->cnxn->hdbc, cur->hstmt); if (cbFetched == SQL_NULL_DATA) Py_RETURN_NONE; if (pinfo->is_unsigned) return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG)(SQLUBIGINT)value); return PyLong_FromLongLong((PY_LONG_LONG)value); } static PyObject* GetDataDouble(Cursor* cur, Py_ssize_t iCol) { double value; SQLLEN cbFetched = 0; SQLRETURN ret; Py_BEGIN_ALLOW_THREADS ret = SQLGetData(cur->hstmt, (SQLUSMALLINT)(iCol+1), SQL_C_DOUBLE, &value, sizeof(value), &cbFetched); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) return RaiseErrorFromHandle(cur->cnxn, "SQLGetData", cur->cnxn->hdbc, cur->hstmt); if (cbFetched == SQL_NULL_DATA) Py_RETURN_NONE; return PyFloat_FromDouble(value); } static PyObject* GetSqlServerTime(Cursor* cur, Py_ssize_t iCol) { SQL_SS_TIME2_STRUCT value; SQLLEN cbFetched = 0; SQLRETURN ret; Py_BEGIN_ALLOW_THREADS ret = SQLGetData(cur->hstmt, (SQLUSMALLINT)(iCol+1), SQL_C_BINARY, &value, sizeof(value), &cbFetched); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) return RaiseErrorFromHandle(cur->cnxn, "SQLGetData", cur->cnxn->hdbc, cur->hstmt); if (cbFetched == SQL_NULL_DATA) Py_RETURN_NONE; int micros = (int)(value.fraction / 1000); // nanos --> micros return PyTime_FromTime(value.hour, value.minute, value.second, micros); } static PyObject* GetUUID(Cursor* cur, Py_ssize_t iCol) { // REVIEW: Since GUID is a fixed size, do we need to pass the size or cbFetched? PYSQLGUID guid; SQLLEN cbFetched = 0; SQLRETURN ret; Py_BEGIN_ALLOW_THREADS ret = SQLGetData(cur->hstmt, (SQLUSMALLINT)(iCol+1), SQL_GUID, &guid, sizeof(guid), &cbFetched); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) return RaiseErrorFromHandle(cur->cnxn, "SQLGetData", cur->cnxn->hdbc, cur->hstmt); if (cbFetched == SQL_NULL_DATA) Py_RETURN_NONE; #if PY_MAJOR_VERSION >= 3 const char* szFmt = "(yyy#)"; #else const char* szFmt = "(sss#)"; #endif Object args(Py_BuildValue(szFmt, NULL, NULL, &guid, (int)sizeof(guid))); if (!args) return 0; PyObject* uuid_type = GetClassForThread("uuid", "UUID"); if (!uuid_type) return 0; PyObject* uuid = PyObject_CallObject(uuid_type, args.Get()); Py_DECREF(uuid_type); return uuid; } static PyObject* GetDataTimestamp(Cursor* cur, Py_ssize_t iCol) { TIMESTAMP_STRUCT value; SQLLEN cbFetched = 0; SQLRETURN ret; struct tm t; Py_BEGIN_ALLOW_THREADS ret = SQLGetData(cur->hstmt, (SQLUSMALLINT)(iCol+1), SQL_C_TYPE_TIMESTAMP, &value, sizeof(value), &cbFetched); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) return RaiseErrorFromHandle(cur->cnxn, "SQLGetData", cur->cnxn->hdbc, cur->hstmt); if (cbFetched == SQL_NULL_DATA) Py_RETURN_NONE; switch (cur->colinfos[iCol].sql_type) { case SQL_TYPE_TIME: { int micros = (int)(value.fraction / 1000); // nanos --> micros return PyTime_FromTime(value.hour, value.minute, value.second, micros); } case SQL_TYPE_DATE: return PyDate_FromDate(value.year, value.month, value.day); } int micros = (int)(value.fraction / 1000); // nanos --> micros if (value.hour == 24) { // some backends support 24:00 (hh:mm) as "end of a day" t.tm_year = value.year - 1900; // tm_year is 1900-based t.tm_mon = value.month - 1; // tm_mon is zero-based t.tm_mday = value.day; t.tm_hour = value.hour; t.tm_min = value.minute; t.tm_sec = value.second; t.tm_isdst = -1; // auto-adjust for dst mktime(&t); // normalize values in t return PyDateTime_FromDateAndTime( t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, micros ); } return PyDateTime_FromDateAndTime(value.year, value.month, value.day, value.hour, value.minute, value.second, micros); } int GetUserConvIndex(Cursor* cur, SQLSMALLINT sql_type) { // If this sql type has a user-defined conversion, the index into the connection's `conv_funcs` array is returned. // Otherwise -1 is returned. for (int i = 0; i < cur->cnxn->conv_count; i++) if (cur->cnxn->conv_types[i] == sql_type) return i; return -1; } PyObject* PythonTypeFromSqlType(Cursor* cur, SQLSMALLINT type) { // Returns a type object ('int', 'str', etc.) for the given ODBC C type. This is used to populate // Cursor.description with the type of Python object that will be returned for each column. // // type // The ODBC C type (SQL_C_CHAR, etc.) of the column. // // The returned object does not have its reference count incremented (is a borrowed // reference). // // Keep this in sync with GetData below. int conv_index = GetUserConvIndex(cur, type); if (conv_index != -1) return (PyObject*)&PyString_Type; PyObject* pytype = 0; bool incref = true; switch (type) { case SQL_CHAR: case SQL_VARCHAR: case SQL_LONGVARCHAR: #if PY_MAJOR_VERSION < 3 if (cur->cnxn->str_enc.ctype == SQL_C_CHAR) pytype = (PyObject*)&PyString_Type; else pytype = (PyObject*)&PyUnicode_Type; #else pytype = (PyObject*)&PyUnicode_Type; #endif break; case SQL_GUID: if (UseNativeUUID()) { pytype = GetClassForThread("uuid", "UUID"); incref = false; } else { #if PY_MAJOR_VERSION < 3 if (cur->cnxn->str_enc.ctype == SQL_C_CHAR) pytype = (PyObject*)&PyString_Type; else pytype = (PyObject*)&PyUnicode_Type; #else pytype = (PyObject*)&PyUnicode_Type; #endif } break; case SQL_WCHAR: case SQL_WVARCHAR: case SQL_WLONGVARCHAR: case SQL_SS_XML: case SQL_DB2_XML: pytype = (PyObject*)&PyUnicode_Type; break; case SQL_DECIMAL: case SQL_NUMERIC: pytype = GetClassForThread("decimal", "Decimal"); incref = false; break; case SQL_REAL: case SQL_FLOAT: case SQL_DOUBLE: pytype = (PyObject*)&PyFloat_Type; break; case SQL_SMALLINT: case SQL_INTEGER: case SQL_TINYINT: pytype = (PyObject*)&PyInt_Type; break; case SQL_TYPE_DATE: pytype = (PyObject*)PyDateTimeAPI->DateType; break; case SQL_TYPE_TIME: case SQL_SS_TIME2: // SQL Server 2008+ pytype = (PyObject*)PyDateTimeAPI->TimeType; break; case SQL_TYPE_TIMESTAMP: pytype = (PyObject*)PyDateTimeAPI->DateTimeType; break; case SQL_BIGINT: pytype = (PyObject*)&PyLong_Type; break; case SQL_BIT: pytype = (PyObject*)&PyBool_Type; break; case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: default: #if PY_VERSION_HEX >= 0x02060000 pytype = (PyObject*)&PyByteArray_Type; #else pytype = (PyObject*)&PyBuffer_Type; #endif break; } if (pytype && incref) Py_INCREF(pytype); return pytype; } PyObject* GetData(Cursor* cur, Py_ssize_t iCol) { // Returns an object representing the value in the row/field. If 0 is returned, an exception has already been set. // // The data is assumed to be the default C type for the column's SQL type. ColumnInfo* pinfo = &cur->colinfos[iCol]; // First see if there is a user-defined conversion. int conv_index = GetUserConvIndex(cur, pinfo->sql_type); if (conv_index != -1) return GetDataUser(cur, iCol, conv_index); switch (pinfo->sql_type) { case SQL_WCHAR: case SQL_WVARCHAR: case SQL_WLONGVARCHAR: return GetText(cur, iCol); case SQL_CHAR: case SQL_VARCHAR: case SQL_LONGVARCHAR: case SQL_SS_XML: case SQL_DB2_XML: return GetText(cur, iCol); case SQL_GUID: if (UseNativeUUID()) return GetUUID(cur, iCol); return GetText(cur, iCol); break; case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: return GetBinary(cur, iCol); case SQL_DECIMAL: case SQL_NUMERIC: case SQL_DB2_DECFLOAT: return GetDataDecimal(cur, iCol); case SQL_BIT: return GetDataBit(cur, iCol); case SQL_TINYINT: case SQL_SMALLINT: case SQL_INTEGER: return GetDataLong(cur, iCol); case SQL_BIGINT: return GetDataLongLong(cur, iCol); case SQL_REAL: case SQL_FLOAT: case SQL_DOUBLE: return GetDataDouble(cur, iCol); case SQL_TYPE_DATE: case SQL_TYPE_TIME: case SQL_TYPE_TIMESTAMP: return GetDataTimestamp(cur, iCol); case SQL_SS_TIME2: return GetSqlServerTime(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); }