diff --git a/src/pyodbccompat.h b/src/pyodbccompat.h index 74067095..0ae10062 100644 --- a/src/pyodbccompat.h +++ b/src/pyodbccompat.h @@ -66,13 +66,17 @@ inline void PyString_ConcatAndDel(PyObject** lhs, PyObject* rhs) #endif +#ifndef MAX_UNICODE + #define MAX_UNICODE 0x10ffff +#endif + inline PyObject* Text_New(Py_ssize_t length) { // Returns a new, uninitialized String (Python 2) or Unicode object (Python 3) object. #if PY_MAJOR_VERSION < 3 return PyString_FromStringAndSize(0, length); #else - return PyUnicode_FromUnicode(0, length); + return PyUnicode_New(length, MAX_UNICODE); #endif } diff --git a/src/pyodbcmodule.cpp b/src/pyodbcmodule.cpp index 771fe257..3f8fc548 100644 --- a/src/pyodbcmodule.cpp +++ b/src/pyodbcmodule.cpp @@ -1357,7 +1357,7 @@ static PyObject* MakeConnectionString(PyObject* existing, PyObject* parts) length += Text_Size(key) + 1 + Text_Size(value) + 1; // key=value; } - PyObject* result = PyUnicode_FromUnicode(0, length); + PyObject* result = Text_New(length); if (!result) return 0; diff --git a/tests3/issue998.py b/tests3/issue998.py new file mode 100755 index 00000000..24e58e2a --- /dev/null +++ b/tests3/issue998.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +""" +Verify that no warning is emitted for `PyUnicode_FromUnicode(NULL, size)`. + +See https://github.com/mkleehammer/pyodbc/issues/998. +See also https://bugs.python.org/issue36346. +""" + +import io +import os +import sys +import unittest + +# pylint: disable-next=import-error +from tests3.testutils import add_to_path, load_setup_connection_string + +add_to_path() +import pyodbc # pylint: disable=wrong-import-position + +KB = 1024 +MB = KB * 1024 + +CONNECTION_STRING = None + +CONNECTION_STRING_ERROR_MESSAGE = ( + "Please create tmp/setup.cfg file or " + "set a valid value to CONNECTION_STRING." +) +NO_ERROR = None + + +class SQLPutDataUnicodeToBytesMemoryLeakTestCase(unittest.TestCase): + """Test case for issue998 bug fix.""" + + driver = pyodbc + + @classmethod + def setUpClass(cls): + """Set the connection string.""" + + filename = os.path.splitext(os.path.basename(__file__))[0] + cls.connection_string = ( + load_setup_connection_string(filename) or CONNECTION_STRING + ) + + if cls.connection_string: + return NO_ERROR + return ValueError(CONNECTION_STRING_ERROR_MESSAGE) + + def test_use_correct_unicode_factory_function(self): + """Verify that the obsolete function call has been replaced.""" + + # Create a results set. + with pyodbc.connect(self.connection_string, autocommit=True) as cnxn: + cursor = cnxn.cursor() + cursor.execute("SELECT 1 AS a, 2 AS b") + rows = cursor.fetchall() + + # Redirect stderr so we can detect the warning. + sys.stderr = redirected_stderr = io.StringIO() + + # Convert the results object to a string. + self.assertGreater(len(str(rows)), 0) + + # Restore stderr to the original stream. + sys.stderr = sys.__stderr__ + + # If the bug has been fixed, nothing will have been written to stderr. + self.assertEqual(len(redirected_stderr.getvalue()), 0) + + +def main(): + """Top-level driver for the test.""" + unittest.main() + + +if __name__ == "__main__": + main()