diff --git a/setup.py b/setup.py index 7dcdba33..516ada5e 100755 --- a/setup.py +++ b/setup.py @@ -89,10 +89,12 @@ def get_compiler_settings(): '/wd4514', # unreference inline function removed '/wd4820', # padding after struct member '/wd4668', # is not defined as a preprocessor macro + '/wd4710', # function not inlined '/wd4711', # function selected for automatic inline expansion '/wd4100', # unreferenced formal parameter '/wd4127', # "conditional expression is constant" testing compilation constants '/wd4191', # casts to PYCFunction which doesn't have the keywords parameter + '/wd5045', # information note about Spectre mitigation (which we're not using) ]) if '--windbg' in sys.argv: diff --git a/src/connection.cpp b/src/connection.cpp index 1a50d2b5..9b823108 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -109,15 +109,18 @@ static bool ApplyPreconnAttrs(HDBC hdbc, SQLINTEGER ikey, PyObject *value, char if (PyLong_Check(value)) { - if (_PyLong_Sign(value) >= 0) + unsigned long long uval = PyLong_AsUnsignedLongLong(value); + if (PyErr_Occurred()) { - ivalue = (SQLPOINTER)PyLong_AsUnsignedLong(value); - vallen = SQL_IS_UINTEGER; - } else - { - ivalue = (SQLPOINTER)PyLong_AsLong(value); + PyErr_Clear(); + ivalue = (SQLPOINTER)PyLong_AsLongLong(value); vallen = SQL_IS_INTEGER; } + else + { + ivalue = (SQLPOINTER)uval; + vallen = SQL_IS_UINTEGER; + } } else if (PyByteArray_Check(value)) { @@ -377,9 +380,8 @@ static char conv_clear_doc[] = "clear_output_converters() --> None\n\n" "Remove all output converter functions."; -static PyObject* Connection_conv_clear(PyObject* self, PyObject* args) +static PyObject* Connection_conv_clear(PyObject* self, PyObject* /* args (unused) */) { - UNUSED(args); Connection* cnxn = (Connection*)self; Py_XDECREF(cnxn->map_sqltype_to_converter); cnxn->map_sqltype_to_converter = 0; @@ -445,10 +447,8 @@ static char close_doc[] = "Note that closing a connection without committing the changes first will cause\n" "an implicit rollback to be performed."; -static PyObject* Connection_close(PyObject* self, PyObject* args) +static PyObject* Connection_close(PyObject* self, PyObject* /* args (unused) */) { - UNUSED(args); - Connection* cnxn = Connection_Validate(self); if (!cnxn) return 0; @@ -458,10 +458,8 @@ static PyObject* Connection_close(PyObject* self, PyObject* args) Py_RETURN_NONE; } -static PyObject* Connection_cursor(PyObject* self, PyObject* args) +static PyObject* Connection_cursor(PyObject* self, PyObject* /* args (unused) */) { - UNUSED(args); - Connection* cnxn = Connection_Validate(self); if (!cnxn) return 0; @@ -759,10 +757,8 @@ PyObject* Connection_endtrans(Connection* cnxn, SQLSMALLINT type) Py_RETURN_NONE; } -static PyObject* Connection_commit(PyObject* self, PyObject* args) +static PyObject* Connection_commit(PyObject* self, PyObject* /* args (unused) */) { - UNUSED(args); - Connection* cnxn = Connection_Validate(self); if (!cnxn) return 0; @@ -772,10 +768,8 @@ static PyObject* Connection_commit(PyObject* self, PyObject* args) return Connection_endtrans(cnxn, SQL_COMMIT); } -static PyObject* Connection_rollback(PyObject* self, PyObject* args) +static PyObject* Connection_rollback(PyObject* self, PyObject* /* args (unused) */) { - UNUSED(args); - Connection* cnxn = Connection_Validate(self); if (!cnxn) return 0; @@ -810,10 +804,8 @@ static char getinfo_doc[] = "Calls SQLGetInfo, passing `type`, and returns the result formatted as a Python object."; -PyObject* Connection_getautocommit(PyObject* self, void* closure) +PyObject* Connection_getautocommit(PyObject* self, void* /* closure (unused) */) { - UNUSED(closure); - Connection* cnxn = Connection_Validate(self); if (!cnxn) return 0; @@ -823,10 +815,8 @@ PyObject* Connection_getautocommit(PyObject* self, void* closure) return result; } -static int Connection_setautocommit(PyObject* self, PyObject* value, void* closure) +static int Connection_setautocommit(PyObject* self, PyObject* value, void* /* closure (unused) */) { - UNUSED(closure); - Connection* cnxn = Connection_Validate(self); if (!cnxn) return -1; @@ -854,9 +844,8 @@ static int Connection_setautocommit(PyObject* self, PyObject* value, void* closu } -static PyObject* Connection_getclosed(PyObject* self, void* closure) +static PyObject* Connection_getclosed(PyObject* self, void* /* closure (unused) */) { - UNUSED(closure); Connection* cnxn; if (self == 0 || !Connection_Check(self)) @@ -876,10 +865,8 @@ static PyObject* Connection_getclosed(PyObject* self, void* closure) } -static PyObject* Connection_getsearchescape(PyObject* self, void* closure) +static PyObject* Connection_getsearchescape(PyObject* self, void* /* closure (unused) */) { - UNUSED(closure); - Connection* cnxn = (Connection*)self; if (!cnxn->searchescape) @@ -901,10 +888,8 @@ static PyObject* Connection_getsearchescape(PyObject* self, void* closure) return cnxn->searchescape; } -static PyObject* Connection_getmaxwrite(PyObject* self, void* closure) +static PyObject* Connection_getmaxwrite(PyObject* self, void* /* closure (unused) */) { - UNUSED(closure); - Connection* cnxn = Connection_Validate(self); if (!cnxn) return 0; @@ -912,10 +897,8 @@ static PyObject* Connection_getmaxwrite(PyObject* self, void* closure) return PyLong_FromSsize_t(cnxn->maxwrite); } -static int Connection_setmaxwrite(PyObject* self, PyObject* value, void* closure) +static int Connection_setmaxwrite(PyObject* self, PyObject* value, void* /* closure (unused) */) { - UNUSED(closure); - Connection* cnxn = Connection_Validate(self); if (!cnxn) return -1; @@ -943,10 +926,8 @@ static int Connection_setmaxwrite(PyObject* self, PyObject* value, void* closure } -static PyObject* Connection_gettimeout(PyObject* self, void* closure) +static PyObject* Connection_gettimeout(PyObject* self, void* /* closure (unused) */) { - UNUSED(closure); - Connection* cnxn = Connection_Validate(self); if (!cnxn) return 0; @@ -954,10 +935,8 @@ static PyObject* Connection_gettimeout(PyObject* self, void* closure) return PyLong_FromLong(cnxn->timeout); } -static int Connection_settimeout(PyObject* self, PyObject* value, void* closure) +static int Connection_settimeout(PyObject* self, PyObject* value, void* /* closure (unused) */) { - UNUSED(closure); - Connection* cnxn = Connection_Validate(self); if (!cnxn) return -1; @@ -1328,9 +1307,8 @@ static PyObject* Connection_setdecoding(PyObject* self, PyObject* args, PyObject static char enter_doc[] = "__enter__() -> self."; -static PyObject* Connection_enter(PyObject* self, PyObject* args) +static PyObject* Connection_enter(PyObject* self, PyObject* /* args (unused) */) { - UNUSED(args); Py_INCREF(self); return self; } diff --git a/src/cursor.cpp b/src/cursor.cpp index 08002453..b102df72 100644 --- a/src/cursor.cpp +++ b/src/cursor.cpp @@ -435,10 +435,8 @@ static char close_doc[] = "be unusable from this point forward; a ProgrammingError exception will be\n" "raised if any operation is attempted with the cursor."; -static PyObject* Cursor_close(PyObject* self, PyObject* args) +static PyObject* Cursor_close(PyObject* self, PyObject* /* args (unused) */) { - UNUSED(args); - Cursor* cursor = Cursor_Validate(self, CURSOR_REQUIRE_OPEN | CURSOR_RAISE_ERROR); if (!cursor) return 0; @@ -643,13 +641,12 @@ int GetDiagRecs(Cursor* cur) // Default to UTF-16, which may not work if the driver/manager is using some other encoding const char *unicode_enc = cur->cnxn ? cur->cnxn->metadata_enc.name : ENCSTR_UTF16NE; PyObject* msg_value = PyUnicode_Decode( - (char*)cMessageText, iTextLength * sizeof(uint16_t), unicode_enc, "strict" + (char*)cMessageText, (Py_ssize_t)(iTextLength * sizeof(uint16_t)), unicode_enc, "strict" ); if (!msg_value) { // If the char cannot be decoded, return something rather than nothing. - Py_XDECREF(msg_value); - msg_value = PyBytes_FromStringAndSize((char*)cMessageText, iTextLength * sizeof(uint16_t)); + msg_value = PyBytes_FromStringAndSize((char*)cMessageText, (Py_ssize_t)(iTextLength * sizeof(uint16_t))); } PyObject* msg_tuple = PyTuple_New(2); // the message as a Python tuple of class and value @@ -1272,10 +1269,8 @@ static PyObject* Cursor_iternext(PyObject* self) return result; } -static PyObject* Cursor_fetchval(PyObject* self, PyObject* args) +static PyObject* Cursor_fetchval(PyObject* self, PyObject* /* args (unused) */) { - UNUSED(args); - Cursor* cursor = Cursor_Validate(self, CURSOR_REQUIRE_RESULTS | CURSOR_RAISE_ERROR); if (!cursor) return 0; @@ -1292,10 +1287,8 @@ static PyObject* Cursor_fetchval(PyObject* self, PyObject* args) return Row_item(row, 0); } -static PyObject* Cursor_fetchone(PyObject* self, PyObject* args) +static PyObject* Cursor_fetchone(PyObject* self, PyObject* /* args (unused) */) { - UNUSED(args); - PyObject* row; Cursor* cursor = Cursor_Validate(self, CURSOR_REQUIRE_RESULTS | CURSOR_RAISE_ERROR); if (!cursor) @@ -1314,10 +1307,8 @@ static PyObject* Cursor_fetchone(PyObject* self, PyObject* args) } -static PyObject* Cursor_fetchall(PyObject* self, PyObject* args) +static PyObject* Cursor_fetchall(PyObject* self, PyObject* /* args (unused) */) { - UNUSED(args); - PyObject* result; Cursor* cursor = Cursor_Validate(self, CURSOR_REQUIRE_RESULTS | CURSOR_RAISE_ERROR); if (!cursor) @@ -1809,10 +1800,8 @@ static char getTypeInfo_doc[] = "17) num_prec_radix\n" "18) interval_precision"; -static PyObject* Cursor_getTypeInfo(PyObject* self, PyObject* args, PyObject* kwargs) +static PyObject* Cursor_getTypeInfo(PyObject* self, PyObject* args, PyObject* /* kwargs (unused) */) { - UNUSED(kwargs); - int nDataType = SQL_ALL_TYPES; if (!PyArg_ParseTuple(args, "|i", &nDataType)) @@ -1851,10 +1840,8 @@ static PyObject* Cursor_getTypeInfo(PyObject* self, PyObject* args, PyObject* kw } -static PyObject* Cursor_nextset(PyObject* self, PyObject* args) +static PyObject* Cursor_nextset(PyObject* self, PyObject* /* args (unused) */) { - UNUSED(args); - Cursor* cur = Cursor_Validate(self, 0); if (!cur) @@ -2149,9 +2136,8 @@ static char cancel_doc[] = "This calls SQLCancel and is designed to be called from another thread to" "stop processing of an ongoing query."; -static PyObject* Cursor_cancel(PyObject* self, PyObject* args) +static PyObject* Cursor_cancel(PyObject* self, PyObject* /* args (unused) */) { - UNUSED(args); Cursor* cur = Cursor_Validate(self, CURSOR_REQUIRE_OPEN | CURSOR_RAISE_ERROR); if (!cur) return 0; @@ -2167,9 +2153,8 @@ static PyObject* Cursor_cancel(PyObject* self, PyObject* args) } -static PyObject* Cursor_ignored(PyObject* self, PyObject* args) +static PyObject* Cursor_ignored(PyObject* /* self (unused) */, PyObject* /* args (unused) */) { - UNUSED(self, args); Py_RETURN_NONE; } @@ -2223,10 +2208,8 @@ static PyMemberDef Cursor_members[] = { 0 } }; -static PyObject* Cursor_getnoscan(PyObject* self, void *closure) +static PyObject* Cursor_getnoscan(PyObject* self, void* /* closure (unused) */) { - UNUSED(closure); - Cursor* cursor = Cursor_Validate(self, CURSOR_REQUIRE_OPEN | CURSOR_RAISE_ERROR); if (!cursor) return 0; @@ -2249,10 +2232,8 @@ static PyObject* Cursor_getnoscan(PyObject* self, void *closure) Py_RETURN_TRUE; } -static int Cursor_setnoscan(PyObject* self, PyObject* value, void *closure) +static int Cursor_setnoscan(PyObject* self, PyObject* value, void* /* closure (unused) */) { - UNUSED(closure); - Cursor* cursor = Cursor_Validate(self, CURSOR_REQUIRE_OPEN | CURSOR_RAISE_ERROR); if (!cursor) return -1; @@ -2351,9 +2332,8 @@ static char setinputsizes_doc[] = "Setting sizes to None reverts all parameters to the defaults."; static char enter_doc[] = "__enter__() -> self."; -static PyObject* Cursor_enter(PyObject* self, PyObject* args) +static PyObject* Cursor_enter(PyObject* self, PyObject* /* args (unused) */) { - UNUSED(args); Py_INCREF(self); return self; } diff --git a/src/cursor.h b/src/cursor.h index 657eb483..534dac72 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -100,7 +100,8 @@ struct Cursor PyObject* pPreparedSQL; // The number of parameter markers in pPreparedSQL. This will be zero when pPreparedSQL is zero but is set - // immediately after preparing the SQL. + // immediately after preparing the SQL. Must be between 0 and SHRT_MAX - 1 because ODBC uses a signed short for + // indexing into the parameters in some places, with one-based (not zero-based) counting. int paramcount; // If non-zero, a pointer to an array of SQL type values allocated via malloc. This is zero until we actually ask diff --git a/src/errors.cpp b/src/errors.cpp index 6cc230b8..6a5fc012 100644 --- a/src/errors.cpp +++ b/src/errors.cpp @@ -269,7 +269,7 @@ PyObject* GetErrorFromHandle(Connection *conn, const char* szFunction, HDBC hdbc // For now, default to UTF-16 if this is not in the context of a connection. // Note that this will not work if the DM is using a different wide encoding (e.g. UTF-32). const char *unicode_enc = conn ? conn->metadata_enc.name : ENCSTR_UTF16NE; - Object msgStr(PyUnicode_Decode((char*)szMsg, cchMsg * sizeof(uint16_t), unicode_enc, "strict")); + Object msgStr(PyUnicode_Decode((char*)szMsg, (Py_ssize_t)(cchMsg * sizeof(uint16_t)), unicode_enc, "strict")); if (cchMsg != 0 && msgStr.Get()) { diff --git a/src/getdata.cpp b/src/getdata.cpp index c401a35a..89820cf5 100644 --- a/src/getdata.cpp +++ b/src/getdata.cpp @@ -779,14 +779,14 @@ PyObject *GetData_SqlVariant(Cursor *cur, Py_ssize_t iCol) { // 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, + 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); + retcode = SQLColAttribute(cur->hstmt, static_cast(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); diff --git a/src/params.cpp b/src/params.cpp index 089202a8..0251666e 100644 --- a/src/params.cpp +++ b/src/params.cpp @@ -80,7 +80,7 @@ static int DetectCType(PyObject *cell, ParamInfo *pi) // Assume the SQL type is also character (2.x) or binary (3.x). // If it is a max-type (ColumnSize == 0), use DAE. pi->ValueType = SQL_C_BINARY; - pi->BufferLength = pi->ColumnSize ? pi->ColumnSize : sizeof(DAEParam); + pi->BufferLength = (SQLLEN)(pi->ColumnSize ? pi->ColumnSize : sizeof(DAEParam)); } else if (PyUnicode_Check(cell)) { @@ -88,7 +88,7 @@ static int DetectCType(PyObject *cell, ParamInfo *pi) // Assume the SQL type is also wide character. // If it is a max-type (ColumnSize == 0), use DAE. pi->ValueType = SQL_C_WCHAR; - pi->BufferLength = pi->ColumnSize ? pi->ColumnSize * sizeof(SQLWCHAR) : sizeof(DAEParam); + pi->BufferLength = (SQLLEN)(pi->ColumnSize ? pi->ColumnSize * sizeof(SQLWCHAR) : sizeof(DAEParam)); } else if (PyDateTime_Check(cell)) { @@ -120,7 +120,7 @@ static int DetectCType(PyObject *cell, ParamInfo *pi) { // Type_ByteArray: pi->ValueType = SQL_C_BINARY; - pi->BufferLength = pi->ColumnSize ? pi->ColumnSize : sizeof(DAEParam); + pi->BufferLength = (SQLLEN)(pi->ColumnSize ? pi->ColumnSize : sizeof(DAEParam)); } else if (cell == Py_None || cell == null_binary) { @@ -244,9 +244,16 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI Py_XDECREF(absVal); absVal = scaledVal; } + // Yes, it's strange that ODBC supports negative scale, despite the fact that the SQL standard + // itself does not (and PostgreSQL is the only DBMS which supports that extension), but this + // really is supposed to be a signed char, reducing the possible values to a maximum of 127. + pNum->scale = (SQLSCHAR)pi->DecimalDigits; pNum->precision = (SQLCHAR)pi->ColumnSize; - pNum->scale = (SQLCHAR)pi->DecimalDigits; - pNum->sign = _PyLong_Sign(cell) >= 0; +#if PY_VERSION_HEX < 0x030E0000 + pNum->sign = (SQLCHAR)(_PyLong_Sign(cell) < 0 ? 0 : 1); +#else + pNum->sign = (SQLCHAR)(PyLong_IsNegative(cell) ? 0 : 1); +#endif #if PY_VERSION_HEX < 0x030D0000 if (_PyLong_AsByteArray((PyLongObject*)absVal, pNum->val, sizeof(pNum->val), 1, 0)) { #else @@ -289,7 +296,7 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI RaiseErrorV(0, ProgrammingError, "String data, right truncation: length %u buffer %u", len, pi->BufferLength); return false; } - memcpy(*outbuf, PyBytes_AS_STRING(cell), len); + memcpy(*outbuf, PyBytes_AS_STRING(cell), (size_t)len); *outbuf += pi->BufferLength; ind = len; } @@ -328,7 +335,7 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI RaiseErrorV(0, ProgrammingError, "String data, right truncation: length %u buffer %u", len, pi->BufferLength); return false; } - memcpy(*outbuf, PyBytes_AS_STRING((PyObject*)encoded), len); + memcpy(*outbuf, PyBytes_AS_STRING((PyObject*)encoded), (size_t)len); *outbuf += pi->BufferLength; ind = len; } @@ -346,9 +353,9 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI pts->second = PyDateTime_DATE_GET_SECOND(cell); // Truncate the fraction according to precision - size_t digits = min(9, pi->DecimalDigits); + size_t digits = (size_t)min(9, pi->DecimalDigits); long fast_pow10[] = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000}; - SQLUINTEGER milliseconds = PyDateTime_DATE_GET_MICROSECOND(cell) * 1000; + SQLUINTEGER milliseconds = (SQLUINTEGER)(PyDateTime_DATE_GET_MICROSECOND(cell) * 1000); pts->fraction = milliseconds - (milliseconds % fast_pow10[9 - digits]); *outbuf += sizeof(SQL_TIMESTAMP_STRUCT); @@ -376,7 +383,7 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI pt2s->minute = PyDateTime_TIME_GET_MINUTE(cell); pt2s->second = PyDateTime_TIME_GET_SECOND(cell); // This is in units of nanoseconds. - pt2s->fraction = PyDateTime_TIME_GET_MICROSECOND(cell)*1000; + pt2s->fraction = (SQLUINTEGER)(PyDateTime_TIME_GET_MICROSECOND(cell)*1000); *outbuf += sizeof(SQL_SS_TIME2_STRUCT); ind = sizeof(SQL_SS_TIME2_STRUCT); } @@ -413,7 +420,7 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI RaiseErrorV(0, ProgrammingError, "String data, right truncation: length %u buffer %u", len, pi->BufferLength); return false; } - memcpy(*outbuf, PyByteArray_AS_STRING(cell), len); + memcpy(*outbuf, PyByteArray_AS_STRING(cell), (size_t)len); *outbuf += pi->BufferLength; ind = len; } @@ -464,7 +471,7 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI PyObject *newDigits = PyTuple_New(numDigits + scaleDiff); for (Py_ssize_t i = 0; i < numDigits; i++) { - PyTuple_SET_ITEM(newDigits, i, PyLong_FromLong(PyNumber_AsSsize_t(PyTuple_GET_ITEM(digits, i), 0))); + PyTuple_SET_ITEM(newDigits, i, PyLong_FromLong((long)PyNumber_AsSsize_t(PyTuple_GET_ITEM(digits, i), 0))); } for (Py_ssize_t i = numDigits; i < scaleDiff + numDigits; i++) { @@ -479,8 +486,11 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI Py_XDECREF(newDigits); Py_XDECREF(cellParts); + // Yes, it's strange that ODBC supports negative scale, despite the fact that the SQL standard + // itself does not (and PostgreSQL is the only DBMS which supports that extension), but this + // really is supposed to be a signed char, reducing the possible values to a maximum of 127. + pNum->scale = (SQLSCHAR)pi->DecimalDigits; pNum->precision = (SQLCHAR)pi->ColumnSize; - pNum->scale = (SQLCHAR)pi->DecimalDigits; #if PY_VERSION_HEX < 0x030D0000 @@ -999,14 +1009,14 @@ static bool GetTableInfo(Cursor *cur, Py_ssize_t index, PyObject* param, ParamIn // Need to describe in order to fill in IPD with the TVP's type name, because user has // not provided it SQLSMALLINT tvptype; - SQLDescribeParam(cur->hstmt, index + 1, &tvptype, 0, 0, 0); + SQLDescribeParam(cur->hstmt, (SQLUSMALLINT)(index + 1), &tvptype, 0, 0, 0); } info.pObject = param; Py_INCREF(param); info.ValueType = SQL_C_BINARY; info.ParameterType = SQL_SS_TABLE; - info.ColumnSize = nrows; + info.ColumnSize = (SQLULEN)nrows; info.DecimalDigits = 0; info.ParameterValuePtr = &info; info.BufferLength = 0; @@ -1207,7 +1217,7 @@ bool BindParameter(Cursor* cur, Py_ssize_t index, ParamInfo& info) SQLHDESC desc; PyObject *tvpname = PyCodec_Encode(cell0, "UTF-16LE", 0); SQLGetStmtAttr(cur->hstmt, SQL_ATTR_IMP_PARAM_DESC, &desc, 0, 0); - SQLSetDescFieldW(desc, index + 1, SQL_CA_SS_TYPE_NAME, (SQLPOINTER)PyBytes_AsString(tvpname), PyBytes_Size(tvpname)); + SQLSetDescFieldW(desc, (SQLSMALLINT)(index + 1), SQL_CA_SS_TYPE_NAME, (SQLPOINTER)PyBytes_AsString(tvpname), (SQLINTEGER)PyBytes_Size(tvpname)); Py_XDECREF(tvpname); if (nrows > 1) @@ -1217,7 +1227,7 @@ bool BindParameter(Cursor* cur, Py_ssize_t index, ParamInfo& info) if (PyBytes_Check(cell1) || PyUnicode_Check(cell1)) { PyObject *tvpschema = PyCodec_Encode(cell1, "UTF-16LE", 0); - SQLSetDescFieldW(desc, index + 1, SQL_CA_SS_SCHEMA_NAME, (SQLPOINTER)PyBytes_AsString(tvpschema), PyBytes_Size(tvpschema)); + SQLSetDescFieldW(desc, (SQLSMALLINT)(index + 1), SQL_CA_SS_SCHEMA_NAME, (SQLPOINTER)PyBytes_AsString(tvpschema), (SQLINTEGER)PyBytes_Size(tvpschema)); Py_XDECREF(tvpschema); } } @@ -1226,7 +1236,7 @@ bool BindParameter(Cursor* cur, Py_ssize_t index, ParamInfo& info) SQLHDESC desc; SQLGetStmtAttr(cur->hstmt, SQL_ATTR_APP_PARAM_DESC, &desc, 0, 0); - SQLSetDescField(desc, index + 1, SQL_DESC_DATA_PTR, (SQLPOINTER)info.ParameterValuePtr, 0); + SQLSetDescField(desc, (SQLSMALLINT)(index + 1), SQL_DESC_DATA_PTR, (SQLPOINTER)info.ParameterValuePtr, 0); int err = 0; ret = SQLSetStmtAttr(cur->hstmt, SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER)(index + 1), SQL_IS_INTEGER); @@ -1236,7 +1246,7 @@ bool BindParameter(Cursor* cur, Py_ssize_t index, ParamInfo& info) return false; } - Py_ssize_t i = PySequence_Size(info.pObject) - info.ColumnSize; + Py_ssize_t i = (Py_ssize_t)(PySequence_Size(info.pObject) - info.ColumnSize); Py_ssize_t ncols = 0; while (i >= 0 && i < PySequence_Size(info.pObject)) { @@ -1265,7 +1275,7 @@ bool BindParameter(Cursor* cur, Py_ssize_t index, ParamInfo& info) } else { - PyObject *row = PySequence_GetItem(info.pObject, PySequence_Size(info.pObject) - info.ColumnSize); + PyObject *row = PySequence_GetItem(info.pObject, (Py_ssize_t)(PySequence_Size(info.pObject) - info.ColumnSize)); Py_XDECREF(row); info.nested = (ParamInfo*)PyMem_Malloc(ncols * sizeof(ParamInfo)); @@ -1400,6 +1410,11 @@ bool Prepare(Cursor* cur, PyObject* pSql) return false; } + // ODBC sometimes indexes into the parameter list with a 1-based SQLSMALLINT. + if (cParamsT >= SHRT_MAX) { + PyErr_Format(PyExc_ValueError, "%d parameters found; maximum is %d", cParamsT, SHRT_MAX - 1); + return false; + } cur->paramcount = (int)cParamsT; cur->pPreparedSQL = pSql; @@ -1487,7 +1502,7 @@ bool ExecuteMulti(Cursor* cur, PyObject* pSql, PyObject* paramArrayObj) for (Py_ssize_t i = 0; i < cur->paramcount; i++) { SQLSMALLINT nullable; - if (!SQL_SUCCEEDED(SQLDescribeParam(cur->hstmt, i + 1, &(cur->paramInfos[i].ParameterType), + if (!SQL_SUCCEEDED(SQLDescribeParam(cur->hstmt, (SQLUSMALLINT)(i + 1), &(cur->paramInfos[i].ParameterType), &cur->paramInfos[i].ColumnSize, &cur->paramInfos[i].DecimalDigits, &nullable))) { @@ -1544,6 +1559,20 @@ bool ExecuteMulti(Cursor* cur, PyObject* pSql, PyObject* paramArrayObj) // REVIEW: We need a better description of what is going on here. Why is it OK to pass // a fake bindptr to SQLBindParameter. + // 2026-04-02: Here is that explanation. SQL_ATTR_PARAM_BIND_OFFSET_PTR is a Core-conformance + // feature of ODBC 3.x (listed as such in the spec's statement attribute conformance table). + // The spec describes it as follows (SQLSetStmtAttr reference): + // "If this field is non-null, the driver dereferences the pointer, adds the dereferenced + // value to each of the deferred fields in the descriptor record (SQL_DESC_DATA_PTR, + // SQL_DESC_INDICATOR_PTR, and SQL_DESC_OCTET_LENGTH_PTR), and uses the new pointer + // values when binding." + // And from the Parameter Binding Offsets section: + // "This means that either or both the offset and the address to which the offset is added + // can be invalid, as long as their sum is a valid address." + // The pattern this enables is: bind parameters using placeholder pointer values during a setup + // pass, allocate the real data buffer, then set the offset so the driver computes the real + // address at execute time. This avoids having to call SQLBindParameter a second time after + // the buffer is allocated — which is exactly how this code uses it. // Start at a non-zero offset to prevent null pointer detection. char *bindptr = (char*)16; @@ -1556,7 +1585,7 @@ bool ExecuteMulti(Cursor* cur, PyObject* pSql, PyObject* paramArrayObj) goto ErrorRet3; } - if (!SQL_SUCCEEDED(SQLBindParameter(cur->hstmt, i + 1, SQL_PARAM_INPUT, cur->paramInfos[i].ValueType, + if (!SQL_SUCCEEDED(SQLBindParameter(cur->hstmt, (SQLUSMALLINT)(i + 1), SQL_PARAM_INPUT, cur->paramInfos[i].ValueType, cur->paramInfos[i].ParameterType, cur->paramInfos[i].ColumnSize, cur->paramInfos[i].DecimalDigits, bindptr, cur->paramInfos[i].BufferLength, (SQLLEN*)(bindptr + cur->paramInfos[i].BufferLength)))) { @@ -1569,10 +1598,10 @@ bool ExecuteMulti(Cursor* cur, PyObject* pSql, PyObject* paramArrayObj) { SQLHDESC desc; SQLGetStmtAttr(cur->hstmt, SQL_ATTR_APP_PARAM_DESC, &desc, 0, 0); - SQLSetDescField(desc, i + 1, SQL_DESC_TYPE, (SQLPOINTER)SQL_C_NUMERIC, 0); - SQLSetDescField(desc, i + 1, SQL_DESC_PRECISION, (SQLPOINTER)cur->paramInfos[i].ColumnSize, 0); - SQLSetDescField(desc, i + 1, SQL_DESC_SCALE, (SQLPOINTER)(uintptr_t)cur->paramInfos[i].DecimalDigits, 0); - SQLSetDescField(desc, i + 1, SQL_DESC_DATA_PTR, bindptr, 0); + SQLSetDescField(desc, (SQLSMALLINT)(i + 1), SQL_DESC_TYPE, (SQLPOINTER)SQL_C_NUMERIC, 0); + SQLSetDescField(desc, (SQLSMALLINT)(i + 1), SQL_DESC_PRECISION, (SQLPOINTER)cur->paramInfos[i].ColumnSize, 0); + SQLSetDescField(desc, (SQLSMALLINT)(i + 1), SQL_DESC_SCALE, (SQLPOINTER)(uintptr_t)cur->paramInfos[i].DecimalDigits, 0); + SQLSetDescField(desc, (SQLSMALLINT)(i + 1), SQL_DESC_DATA_PTR, bindptr, 0); } bindptr += cur->paramInfos[i].BufferLength + sizeof(SQLLEN); } @@ -1581,7 +1610,7 @@ bool ExecuteMulti(Cursor* cur, PyObject* pSql, PyObject* paramArrayObj) // Assume parameters are homogeneous between rows in the common case, to avoid // another rescan for determining the array height. // Subtract number of rows processed as an upper bound. - if (!(cur->paramArray = (unsigned char*)PyMem_Malloc(rowlen * (rowcount - r)))) + if (!(cur->paramArray = (unsigned char*)PyMem_Malloc((size_t)(rowlen * (rowcount - r))))) { PyErr_NoMemory(); goto ErrorRet4; diff --git a/src/pyodbc.h b/src/pyodbc.h index febcc677..d8c8b82c 100644 --- a/src/pyodbc.h +++ b/src/pyodbc.h @@ -37,7 +37,15 @@ typedef unsigned long long UINT64; #define PY_SSIZE_T_CLEAN 1 +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4365 5039) +#endif #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #include #include #include @@ -85,12 +93,6 @@ inline bool IsSet(DWORD grf, DWORD flags) return (grf & flags) == flags; } -#ifdef UNUSED -#undef UNUSED -#endif - -inline void UNUSED(...) { } - #include #if defined(__SUNPRO_CC) || defined(__SUNPRO_C) || (defined(__GNUC__) && !defined(__MINGW32__)) @@ -115,7 +117,7 @@ inline void _strlwr(char* name) #ifdef PYODBC_TRACE void DebugTrace(const char* szFmt, ...); #else -inline void DebugTrace(const char* szFmt, ...) { UNUSED(szFmt); } +#define DebugTrace(...) do {} while(0) #endif #define TRACE DebugTrace diff --git a/src/pyodbcmodule.cpp b/src/pyodbcmodule.cpp index ede13fe3..21913475 100644 --- a/src/pyodbcmodule.cpp +++ b/src/pyodbcmodule.cpp @@ -391,10 +391,8 @@ static keywordmap keywordmaps[] = }; -static PyObject* mod_connect(PyObject* self, PyObject* args, PyObject* kwargs) +static PyObject* mod_connect(PyObject* /* self (unused) */, PyObject* args, PyObject* kwargs) { - UNUSED(self); - Object pConnectString; int fAutoCommit = 0; int fReadOnly = 0; @@ -525,10 +523,8 @@ static PyObject* mod_connect(PyObject* self, PyObject* args, PyObject* kwargs) } -static PyObject* mod_drivers(PyObject* self) +static PyObject* mod_drivers(PyObject* /* self (unused) */) { - UNUSED(self); - if (henv == SQL_NULL_HANDLE && !AllocateEnv()) return 0; @@ -574,10 +570,8 @@ static PyObject* mod_drivers(PyObject* self) } -static PyObject* mod_datasources(PyObject* self) +static PyObject* mod_datasources(PyObject* /* self (unused) */) { - UNUSED(self); - if (henv == SQL_NULL_HANDLE && !AllocateEnv()) return 0; @@ -611,8 +605,8 @@ static PyObject* mod_datasources(PyObject* self) break; int byteorder = BYTEORDER_NATIVE; - PyObject* key = PyUnicode_DecodeUTF16((char*)szDSN, cbDSN * sizeof(wchar_t), "strict", &byteorder); - PyObject* val = PyUnicode_DecodeUTF16((char*)szDesc, cbDesc * sizeof(wchar_t), "strict", &byteorder); + PyObject* key = PyUnicode_DecodeUTF16((char*)szDSN, (Py_ssize_t)(cbDSN * sizeof(wchar_t)), "strict", &byteorder); + PyObject* val = PyUnicode_DecodeUTF16((char*)szDesc, (Py_ssize_t)(cbDesc * sizeof(wchar_t)), "strict", &byteorder); #else // UTF-8 ret = SQLDataSources(henv, nDirection, szDSN, _countof(szDSN), &cbDSN, szDesc, _countof(szDesc), &cbDesc); @@ -640,10 +634,8 @@ static PyObject* mod_datasources(PyObject* self) } -static PyObject* mod_timefromticks(PyObject* self, PyObject* args) +static PyObject* mod_timefromticks(PyObject* /* self (unused) */, PyObject* args) { - UNUSED(self); - PyObject* num; if (!PyArg_ParseTuple(args, "O", &num)) return 0; @@ -662,23 +654,19 @@ static PyObject* mod_timefromticks(PyObject* self, PyObject* args) } -static PyObject* mod_datefromticks(PyObject* self, PyObject* args) +static PyObject* mod_datefromticks(PyObject* /* self (unused) */, PyObject* args) { - UNUSED(self); return PyDate_FromTimestamp(args); } -static PyObject* mod_timestampfromticks(PyObject* self, PyObject* args) +static PyObject* mod_timestampfromticks(PyObject* /* self (unused) */, PyObject* args) { - UNUSED(self); return PyDateTime_FromTimestamp(args); } -static PyObject* mod_setdecimalsep(PyObject* self, PyObject* args) +static PyObject* mod_setdecimalsep(PyObject* /* self (unused) */, PyObject* args) { - UNUSED(self); - const char* type = "U"; PyObject* p; @@ -689,9 +677,8 @@ static PyObject* mod_setdecimalsep(PyObject* self, PyObject* args) Py_RETURN_NONE; } -static PyObject* mod_getdecimalsep(PyObject* self) +static PyObject* mod_getdecimalsep(PyObject* /* self (unused) */) { - UNUSED(self); return GetDecimalPoint(); } @@ -1242,9 +1229,8 @@ PyMODINIT_FUNC PyInit_pyodbc() #ifdef WINVER -BOOL WINAPI DllMain(HINSTANCE hMod, DWORD fdwReason, LPVOID lpvReserved) +BOOL WINAPI DllMain(HINSTANCE /* hMod (unused) */, DWORD /* fdwReason (unused) */, LPVOID /* lpvReserved (unused) */) { - UNUSED(hMod, fdwReason, lpvReserved); return TRUE; } #endif @@ -1272,7 +1258,7 @@ static PyObject* MakeConnectionString(PyObject* existing, PyObject* parts) if (existing) { assert(PyUnicode_Check(existing)); length = PyUnicode_GET_LENGTH(existing) + 1; // + 1 to add a trailing semicolon - int kind = PyUnicode_KIND(existing); + int kind = (int)PyUnicode_KIND(existing); if (result_kind < kind) result_kind = kind; } @@ -1282,10 +1268,10 @@ static PyObject* MakeConnectionString(PyObject* existing, PyObject* parts) // key=value; length += PyUnicode_GET_LENGTH(key) + 1; length += PyUnicode_GET_LENGTH(value) + 1; - int kind = PyUnicode_KIND(key); + int kind = (int)PyUnicode_KIND(key); if (result_kind < kind) result_kind = kind; - kind = PyUnicode_KIND(value); + kind = (int)PyUnicode_KIND(value); if (result_kind < kind) result_kind = kind; } diff --git a/src/row.cpp b/src/row.cpp index 405637e3..b9db7167 100644 --- a/src/row.cpp +++ b/src/row.cpp @@ -125,10 +125,8 @@ static PyObject* new_check(PyObject* args) return (PyObject*)Row_InternalNew(desc, map, cols, apValues); } -static PyObject* Row_new(PyTypeObject* type, PyObject* args, PyObject* kwargs) +static PyObject* Row_new(PyTypeObject* type, PyObject* args, PyObject* /* kwargs (unused) */) { - UNUSED(kwargs); - PyObject* row = new_check(args); if (row == 0) PyErr_SetString(PyExc_TypeError, "cannot create 'pyodbc.Row' instances"); diff --git a/src/wrapper.h b/src/wrapper.h index 8807a0de..3c4f560b 100644 --- a/src/wrapper.h +++ b/src/wrapper.h @@ -66,7 +66,7 @@ class Object operator PyVarObject*() { return (PyVarObject*)p; } - operator const bool() { return p != 0; } + operator bool() { return p != 0; } PyObject* Get() {