PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi, bool fUnicodeResults, long timeout, bool fReadOnly) { // pConnectString // A string or unicode object. (This must be checked by the caller.) // // fAnsi // If true, do not attempt a Unicode connection. // // fUnicodeResults // If true, return strings in rows as unicode objects. // // Allocate HDBC and connect // HDBC hdbc = SQL_NULL_HANDLE; SQLRETURN ret; Py_BEGIN_ALLOW_THREADS ret = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) return RaiseErrorFromHandle("SQLAllocHandle", SQL_NULL_HANDLE, SQL_NULL_HANDLE); if (!Connect(pConnectString, hdbc, fAnsi, timeout)) { // Connect has already set an exception. Py_BEGIN_ALLOW_THREADS SQLFreeHandle(SQL_HANDLE_DBC, hdbc); Py_END_ALLOW_THREADS return 0; }
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("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; 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("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 return PyDateTime_FromDateAndTime(value.year, value.month, value.day, value.hour, value.minute, value.second, micros); }
bool BindParameter(Cursor* cur, Py_ssize_t index, ParamInfo& info) { TRACE("BIND: param=%d InputOutputType=%d (%s) ValueType=%d (%s) ParameterType=%d (%s) ColumnSize=%d DecimalDigits=%d BufferLength=%d *pcb=%d\n", (index+1), info.InputOutputType, CInputOutputName(info.InputOutputType), info.ValueType, CTypeName(info.ValueType), info.ParameterType, SqlTypeName(info.ParameterType), info.ColumnSize, info.DecimalDigits, info.BufferLength, info.StrLen_or_Ind); SQLRETURN ret = -1; Py_BEGIN_ALLOW_THREADS ret = SQLBindParameter(cur->hstmt, (SQLUSMALLINT)(index + 1), info.InputOutputType, info.ValueType, info.ParameterType, info.ColumnSize, info.DecimalDigits, info.ParameterValuePtr, info.BufferLength, &info.StrLen_or_Ind); Py_END_ALLOW_THREADS; if (GetConnection(cur)->hdbc == SQL_NULL_HANDLE) { // The connection was closed by another thread in the ALLOW_THREADS block above. RaiseErrorV(0, ProgrammingError, "The cursor's connection was closed."); return false; } if (!SQL_SUCCEEDED(ret)) { RaiseErrorFromHandle("SQLBindParameter", GetConnection(cur)->hdbc, cur->hstmt); return false; } return true; }
static bool Connect(PyObject* pConnectString, HDBC hdbc, bool fAnsi, long timeout, Object& encoding) { // This should have been checked by the global connect function. I(PyString_Check(pConnectString) || PyUnicode_Check(pConnectString)); const int cchMax = 600; if (PySequence_Length(pConnectString) >= cchMax) { PyErr_SetString(PyExc_TypeError, "connection string too long"); return false; } // The driver manager determines if the app is a Unicode app based on whether we call SQLDriverConnectA or // SQLDriverConnectW. Some drivers, notably Microsoft Access/Jet, change their behavior based on this, so we try // the Unicode version first. (The Access driver only supports Unicode text, but SQLDescribeCol returns SQL_CHAR // instead of SQL_WCHAR if we connect with the ANSI version. Obviously this causes lots of errors since we believe // what it tells us (SQL_CHAR).) // Python supports only UCS-2 and UCS-4, so we shouldn't need to worry about receiving surrogate pairs. However, // Windows does use UCS-16, so it is possible something would be misinterpreted as one. We may need to examine // this more. SQLRETURN ret; if (timeout > 0) { Py_BEGIN_ALLOW_THREADS ret = SQLSetConnectAttr(hdbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)(uintptr_t)timeout, SQL_IS_UINTEGER); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) RaiseErrorFromHandle("SQLSetConnectAttr(SQL_ATTR_LOGIN_TIMEOUT)", hdbc, SQL_NULL_HANDLE); }
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. // TODO: Is Unicode a good idea for Python 2.7? We need to know which drivers support Unicode. SQLWCHAR buffer[100]; SQLLEN cbFetched = 0; // Note: will not include the NULL terminator. SQLRETURN ret; Py_BEGIN_ALLOW_THREADS ret = SQLGetData(cur->hstmt, (SQLUSMALLINT)(iCol+1), SQL_C_WCHAR, buffer, sizeof(buffer), &cbFetched); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) return RaiseErrorFromHandle("SQLGetData", cur->cnxn->hdbc, cur->hstmt); if (cbFetched == SQL_NULL_DATA) Py_RETURN_NONE; // 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 SQLWCHAR. int cch = (int)(cbFetched / sizeof(SQLWCHAR)); for (int i = (cch - 1); i >= 0; i--) { if (buffer[i] == chDecimal) { // Must force it to use '.' since the Decimal class doesn't pay attention to the locale. buffer[i] = '.'; } else if ((buffer[i] < '0' || buffer[i] > '9') && buffer[i] != '-') { memmove(&buffer[i], &buffer[i] + 1, (cch - i) * sizeof(SQLWCHAR)); cch--; } } I(buffer[cch] == 0); Object str(PyUnicode_FromSQLWCHAR(buffer, cch)); if (!str) return 0; return PyObject_CallFunction(decimal_type, "O", str.Get()); }
static PyObject* GetDataDecimal(Cursor* cur, int iCol) { // The SQL_NUMERIC_STRUCT support is hopeless (SQL Server ignores scale on input parameters and output columns), 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. ColumnInfo* pinfo = &cur->colinfos[iCol]; SQLLEN cbNeeded = pinfo->column_size + 3 + // sign, decimal, NULL (pinfo->column_size / 3) + 2; // grouping. I believe this covers all cases. SQLLEN cbFetched = 0; char* sz = (char*)_alloca(cbNeeded); if (sz == 0) return PyErr_NoMemory(); SQLRETURN ret; Py_BEGIN_ALLOW_THREADS ret = SQLGetData(cur->hstmt, (SQLSMALLINT)(iCol+1), SQL_C_CHAR, sz, cbNeeded, &cbFetched); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) return RaiseErrorFromHandle("SQLGetData", cur->cnxn->hdbc, cur->hstmt); if (cbFetched == SQL_NULL_DATA) Py_RETURN_NONE; // The decimal class requires the decimal to be a period and does not allow thousands separators. Clean it up. // // Unfortunately this code only handles single-character values, which might be good enough for decimals and // separators, but is certainly not good enough for currency symbols. // // Note: cbFetched does not include the NULL terminator. for (int i = cbFetched - 1; i >=0; i--) { if (sz[i] == chGroupSeparator || sz[i] == '$' || sz[i] == chCurrencySymbol) { memmove(&sz[i], &sz[i] + 1, cbFetched - i); cbFetched--; } else if (sz[i] == chDecimal) { sz[i] = '.'; } } return PyObject_CallFunction(decimal_type, "s", sz); }
static PyObject* mod_drivers(PyObject* self) { UNUSED(self); if (henv == SQL_NULL_HANDLE && !AllocateEnv()) return 0; Object result(PyList_New(0)); if (!result) return 0; SQLCHAR szDriverDesc[500]; SWORD cbDriverDesc; SWORD cbAttrs; SQLRETURN ret; SQLUSMALLINT nDirection = SQL_FETCH_FIRST; for (;;) { Py_BEGIN_ALLOW_THREADS ret = SQLDrivers(henv, nDirection, szDriverDesc, _countof(szDriverDesc), &cbDriverDesc, 0, 0, &cbAttrs); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) break; // REVIEW: This is another reason why we really need a factory that we can use. At this // point we don't have a global text encoding that we can assume for this. Somehow it // seems to be working to use UTF-8, even on Windows. Object name(PyString_FromString((const char*)szDriverDesc)); if (!name) return 0; if (PyList_Append(result, name.Get()) != 0) return 0; name.Detach(); nDirection = SQL_FETCH_NEXT; } if (ret != SQL_NO_DATA) { Py_DECREF(result); return RaiseErrorFromHandle(0, "SQLDrivers", SQL_NULL_HANDLE, SQL_NULL_HANDLE); } return result.Detach(); }
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("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("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* mod_datasources(PyObject* self) { UNUSED(self); if (henv == SQL_NULL_HANDLE && !AllocateEnv()) return 0; PyObject* result = PyDict_New(); if (!result) return 0; SQLCHAR szDSN[SQL_MAX_DSN_LENGTH]; SWORD cbDSN; SQLCHAR szDesc[200]; SWORD cbDesc; SQLUSMALLINT nDirection = SQL_FETCH_FIRST; SQLRETURN ret; for (;;) { Py_BEGIN_ALLOW_THREADS ret = SQLDataSources(henv, SQL_FETCH_NEXT, szDSN, _countof(szDSN), &cbDSN, szDesc, _countof(szDesc), &cbDesc); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) break; PyDict_SetItemString(result, (const char*)szDSN, PyString_FromString((const char*)szDesc)); nDirection = SQL_FETCH_NEXT; } if (ret != SQL_NO_DATA) { Py_DECREF(result); return RaiseErrorFromHandle("SQLDataSources", SQL_NULL_HANDLE, SQL_NULL_HANDLE); } return result; }
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("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("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); }
bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, bool skip_first) { // // Normalize the parameter variables. // // Since we may replace parameters (we replace objects with Py_True/Py_False when writing to a bit/bool column), // allocate an array and use it instead of the original sequence. Since we don't change ownership we don't bother // with incref. (That is, PySequence_GetItem will INCREF and ~ObjectArrayHolder will DECREF.) int params_offset = skip_first ? 1 : 0; Py_ssize_t cParams = original_params == 0 ? 0 : PySequence_Length(original_params) - params_offset; PyObject** params = (PyObject**)malloc(sizeof(PyObject*) * cParams); if (!params) { PyErr_NoMemory(); return 0; } for (Py_ssize_t i = 0; i < cParams; i++) params[i] = PySequence_GetItem(original_params, i + params_offset); ObjectArrayHolder holder(cParams, params); // // Prepare the SQL if necessary. // if (pSql == cur->pPreparedSQL) { // We've already prepared this SQL, so we don't need to do so again. We've also cached the parameter // information in cur->paramdescs. if (cParams != cur->paramcount) { RaiseErrorV(0, ProgrammingError, "The SQL contains %d parameter markers, but %d parameters were supplied", cur->paramcount, cParams); return false; } } else { FreeParameterInfo(cur); SQLRETURN ret; if (PyString_Check(pSql)) { Py_BEGIN_ALLOW_THREADS ret = SQLPrepare(cur->hstmt, (SQLCHAR*)PyString_AS_STRING(pSql), SQL_NTS); Py_END_ALLOW_THREADS } else { Py_BEGIN_ALLOW_THREADS ret = SQLPrepareW(cur->hstmt, (SQLWCHAR*)PyUnicode_AsUnicode(pSql), SQL_NTS); Py_END_ALLOW_THREADS } if (cur->cnxn->hdbc == SQL_NULL_HANDLE) { // The connection was closed by another thread in the ALLOW_THREADS block above. RaiseErrorV(0, ProgrammingError, "The cursor's connection was closed."); return false; } if (!SQL_SUCCEEDED(ret)) { RaiseErrorFromHandle("SQLPrepare", GetConnection(cur)->hdbc, cur->hstmt); return false; } if (!CacheParamDesc(cur)) return false; cur->pPreparedSQL = pSql; Py_INCREF(cur->pPreparedSQL); }
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; 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("SQLGetData", cur->cnxn->hdbc, cur->hstmt); return false; } if (ret == SQL_SUCCESS && 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 PyObject* GetDataString(Cursor* cur, Py_ssize_t iCol) { // Returns a string, unicode, or bytearray object for character and binary data. // // In Python 2.6+, binary data is returned as a byte array. Earlier versions will return an ASCII str object here // which will be wrapped in a buffer object by the caller. // // 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. ColumnInfo* pinfo = &cur->colinfos[iCol]; // Some Unix ODBC drivers do not return the correct length. if (pinfo->sql_type == SQL_GUID) pinfo->column_size = 36; SQLSMALLINT nTargetType; switch (pinfo->sql_type) { case SQL_CHAR: case SQL_VARCHAR: case SQL_LONGVARCHAR: case SQL_GUID: case SQL_SS_XML: #if PY_MAJOR_VERSION < 3 if (cur->cnxn->unicode_results) nTargetType = SQL_C_WCHAR; else nTargetType = SQL_C_CHAR; #else nTargetType = SQL_C_WCHAR; #endif break; case SQL_WCHAR: case SQL_WVARCHAR: case SQL_WLONGVARCHAR: nTargetType = SQL_C_WCHAR; break; default: nTargetType = SQL_C_BINARY; break; } char tempBuffer[1024]; DataBuffer buffer(nTargetType, tempBuffer, sizeof(tempBuffer)); for (int iDbg = 0; iDbg < 10; iDbg++) // failsafe { SQLRETURN ret; SQLLEN cbData = 0; Py_BEGIN_ALLOW_THREADS ret = SQLGetData(cur->hstmt, (SQLUSMALLINT)(iCol+1), nTargetType, buffer.GetBuffer(), buffer.GetRemaining(), &cbData); Py_END_ALLOW_THREADS; if (cbData == SQL_NULL_DATA || (ret == SQL_SUCCESS && 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: // // if (colinfo->column_cur_size < 0) { // /* TODO check what should happen if pcbValue was NULL */ // *pcbValue = SQL_NULL_DATA; // // I believe it will be fine to treat all negative values as NULL for now. Py_RETURN_NONE; } if (!SQL_SUCCEEDED(ret) && ret != SQL_NO_DATA) return RaiseErrorFromHandle("SQLGetData", cur->cnxn->hdbc, cur->hstmt); // The 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. SQLLEN cbBuffer = buffer.GetRemaining(); // how much we gave SQLGetData if (ret == SQL_SUCCESS_WITH_INFO) { // There is more data than fits in the buffer. The amount of data equals the amount of data in the buffer // minus a NULL terminator. SQLLEN cbRead; SQLLEN cbMore; if (cbData == SQL_NO_TOTAL) { // We don't know how much more, so just guess. cbRead = cbBuffer - buffer.null_size; cbMore = 2048; } else if (cbData >= cbBuffer) { // There is more data. We supplied cbBuffer, but there was cbData (more). We received cbBuffer, so we // need to subtract that, allocate enough to read the rest (cbData-cbBuffer). cbRead = cbBuffer - buffer.null_size; cbMore = cbData - cbRead; } else { // I'm not really sure why I would be here ... I would have expected SQL_SUCCESS cbRead = cbData - buffer.null_size; cbMore = 0; } buffer.AddUsed(cbRead); if (!buffer.AllocateMore(cbMore)) return PyErr_NoMemory(); } else if (ret == SQL_SUCCESS) { // For some reason, the NULL terminator is used in intermediate buffers but not in this final one. buffer.AddUsed(cbData); } if (ret == SQL_SUCCESS || ret == SQL_NO_DATA) return buffer.DetachValue(); } // REVIEW: Add an error message. return 0; }
static bool Connect(PyObject* pConnectString, HDBC hdbc, bool fAnsi, long timeout) { // This should have been checked by the global connect function. I(PyString_Check(pConnectString) || PyUnicode_Check(pConnectString)); const int cchMax = 600; if (PySequence_Length(pConnectString) >= cchMax) { PyErr_SetString(PyExc_TypeError, "connection string too long"); return false; } // The driver manager determines if the app is a Unicode app based on whether we call SQLDriverConnectA or // SQLDriverConnectW. Some drivers, notably Microsoft Access/Jet, change their behavior based on this, so we try // the Unicode version first. (The Access driver only supports Unicode text, but SQLDescribeCol returns SQL_CHAR // instead of SQL_WCHAR if we connect with the ANSI version. Obviously this causes lots of errors since we believe // what it tells us (SQL_CHAR).) // Python supports only UCS-2 and UCS-4, so we shouldn't need to worry about receiving surrogate pairs. However, // Windows does use UCS-16, so it is possible something would be misinterpreted as one. We may need to examine // this more. SQLRETURN ret; if (timeout > 0) { //Py_BEGIN_ALLOW_THREADS ret = SQLSetConnectAttr(hdbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)timeout, SQL_IS_UINTEGER); //Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) RaiseErrorFromHandle("SQLSetConnectAttr(SQL_ATTR_LOGIN_TIMEOUT)", hdbc, SQL_NULL_HANDLE); } if (!fAnsi) { SQLWChar connectString(pConnectString); //Py_BEGIN_ALLOW_THREADS ret = SQLDriverConnectW(hdbc, 0, connectString.get(), (SQLSMALLINT)connectString.size(), 0, 0, 0, SQL_DRIVER_NOPROMPT); //Py_END_ALLOW_THREADS if (SQL_SUCCEEDED(ret)) return true; RaiseErrorFromHandle("SQLDriverConnect", hdbc, SQL_NULL_HANDLE); return false; // The Unicode function failed. If the error is that the driver doesn't have a Unicode version (IM001), continue // to the ANSI version. // // I've commented this out since a number of common drivers are returning different errors. The MySQL 5 // driver, for example, returns IM002 "Data source name not found...". // // PyObject* error = GetErrorFromHandle("SQLDriverConnectW", hdbc, SQL_NULL_HANDLE); // if (!HasSqlState(error, "IM001")) // { // RaiseErrorFromException(error); // return false; // } // Py_XDECREF(error); } PyErr_SetString(PyExc_TypeError, "Non-unicode connection strings not supported."); return false; }
static PyObject* GetDataLongLong(Cursor* cur, int iCol) { ColumnInfo* pinfo = &cur->colinfos[iCol]; INT64 value = 0; SQLLEN cbFetched = 0; SQLRETURN ret; SQLSMALLINT nCType = pinfo->is_unsigned ? SQL_C_UBIGINT : SQL_C_SBIGINT; Py_BEGIN_ALLOW_THREADS ret = SQLGetData(cur->hstmt, (SQLSMALLINT)(iCol+1), nCType, &value, sizeof(value), &cbFetched); Py_END_ALLOW_THREADS if (!SQL_SUCCEEDED(ret)) return RaiseErrorFromHandle("SQLGetData", cur->cnxn->hdbc, cur->hstmt); if (cbFetched == SQL_NULL_DATA) Py_RETURN_NONE; if (pinfo->is_unsigned) return PyLong_FromLongLong(*(UINT64*)&value); return PyLong_FromLongLong(*(INT64*)&value); }