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()); }
PyObject* DetachValue() { // At this point, Trim should have been called by PostRead. if (bytesUsed == SQL_NULL_DATA || buffer == 0) Py_RETURN_NONE; if (usingStack) { if (dataType == SQL_C_CHAR) return PyBytes_FromStringAndSize(buffer, bytesUsed); if (dataType == SQL_C_BINARY) { #if PY_VERSION_HEX >= 0x02060000 return PyByteArray_FromStringAndSize(buffer, bytesUsed); #else return PyBytes_FromStringAndSize(buffer, bytesUsed); #endif } if (sizeof(SQLWCHAR) == Py_UNICODE_SIZE) return PyUnicode_FromUnicode((const Py_UNICODE*)buffer, bytesUsed / element_size); return PyUnicode_FromSQLWCHAR((const SQLWCHAR*)buffer, bytesUsed / element_size); } if (bufferOwner && PyUnicode_CheckExact(bufferOwner)) { if (PyUnicode_Resize(&bufferOwner, bytesUsed / element_size) == -1) return 0; PyObject* tmp = bufferOwner; bufferOwner = 0; buffer = 0; return tmp; } if (bufferOwner && PyBytes_CheckExact(bufferOwner)) { if (_PyBytes_Resize(&bufferOwner, bytesUsed) == -1) return 0; PyObject* tmp = bufferOwner; bufferOwner = 0; buffer = 0; return tmp; } #if PY_VERSION_HEX >= 0x02060000 if (bufferOwner && PyByteArray_CheckExact(bufferOwner)) { if (PyByteArray_Resize(bufferOwner, bytesUsed) == -1) return 0; PyObject* tmp = bufferOwner; bufferOwner = 0; buffer = 0; return tmp; } #endif // We have allocated our own SQLWCHAR buffer and must now copy it to a Unicode object. I(bufferOwner == 0); PyObject* result = PyUnicode_FromSQLWCHAR((const SQLWCHAR*)buffer, bytesUsed / element_size); if (result == 0) return false; pyodbc_free(buffer); buffer = 0; return result; }