pyodbc 5.2.0__tar.gz → 5.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. pyodbc-5.4.0/PKG-INFO +58 -0
  2. {pyodbc-5.2.0 → pyodbc-5.4.0}/pyproject.toml +3 -4
  3. {pyodbc-5.2.0 → pyodbc-5.4.0}/setup.py +8 -2
  4. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/connection.cpp +197 -20
  5. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/connection.h +15 -1
  6. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/cursor.cpp +336 -11
  7. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/cursor.h +12 -2
  8. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/dbspecific.h +1 -0
  9. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/decimal.cpp +69 -9
  10. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/decimal.h +2 -0
  11. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/errors.cpp +10 -0
  12. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/getdata.cpp +117 -5
  13. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/params.cpp +293 -128
  14. pyodbc-5.4.0/src/pyodbc.egg-info/PKG-INFO +58 -0
  15. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/pyodbc.egg-info/SOURCES.txt +1 -0
  16. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/pyodbc.h +0 -3
  17. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/pyodbc.pyi +145 -65
  18. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/pyodbcmodule.cpp +121 -13
  19. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/pyodbcmodule.h +2 -0
  20. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/row.cpp +1 -1
  21. {pyodbc-5.2.0 → pyodbc-5.4.0}/tests/conftest.py +2 -2
  22. {pyodbc-5.2.0 → pyodbc-5.4.0}/tests/mysql_test.py +52 -15
  23. {pyodbc-5.2.0 → pyodbc-5.4.0}/tests/postgresql_test.py +100 -16
  24. pyodbc-5.4.0/tests/sqlite_test.py +801 -0
  25. {pyodbc-5.2.0 → pyodbc-5.4.0}/tests/sqlserver_test.py +408 -79
  26. pyodbc-5.2.0/PKG-INFO +0 -25
  27. pyodbc-5.2.0/src/pyodbc.egg-info/PKG-INFO +0 -25
  28. {pyodbc-5.2.0 → pyodbc-5.4.0}/LICENSE.txt +0 -0
  29. {pyodbc-5.2.0 → pyodbc-5.4.0}/MANIFEST.in +0 -0
  30. {pyodbc-5.2.0 → pyodbc-5.4.0}/README.md +0 -0
  31. {pyodbc-5.2.0 → pyodbc-5.4.0}/setup.cfg +0 -0
  32. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/cnxninfo.cpp +0 -0
  33. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/cnxninfo.h +0 -0
  34. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/errors.h +0 -0
  35. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/getdata.h +0 -0
  36. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/params.h +0 -0
  37. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/pyodbc.egg-info/dependency_links.txt +0 -0
  38. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/pyodbc.egg-info/top_level.txt +0 -0
  39. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/pyodbcdbg.cpp +0 -0
  40. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/resource.h +0 -0
  41. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/row.h +0 -0
  42. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/textenc.cpp +0 -0
  43. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/textenc.h +0 -0
  44. {pyodbc-5.2.0 → pyodbc-5.4.0}/src/wrapper.h +0 -0
  45. {pyodbc-5.2.0 → pyodbc-5.4.0}/tests/__init__.py +0 -0
pyodbc-5.4.0/PKG-INFO ADDED
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyodbc
3
+ Version: 5.4.0
4
+ Summary: DB API module for ODBC
5
+ Home-page: https://github.com/mkleehammer/pyodbc
6
+ Author-email: Michael Kleehammer <michael@kleehammer.com>
7
+ Maintainer: Michael Kleehammer
8
+ Maintainer-email: Michael Kleehammer <michael@kleehammer.com>
9
+ License-Expression: MIT-0
10
+ Project-URL: Homepage, https://github.com/mkleehammer/pyodbc
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: System Administrators
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Operating System :: POSIX
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Topic :: Database
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE.txt
22
+ Dynamic: home-page
23
+ Dynamic: license-file
24
+ Dynamic: maintainer
25
+ Dynamic: requires-python
26
+
27
+ # pyodbc
28
+
29
+ [![Windows build](https://ci.appveyor.com/api/projects/status/github/mkleehammer/pyodbc?branch=master&svg=true&passingText=Windows%20build&failingText=Windows%20build)](https://ci.appveyor.com/project/mkleehammer/pyodbc)
30
+ [![Ubuntu build](https://github.com/mkleehammer/pyodbc/actions/workflows/ubuntu_build.yml/badge.svg)](https://github.com/mkleehammer/pyodbc/actions/workflows/ubuntu_build.yml)
31
+ [![PyPI](https://img.shields.io/pypi/v/pyodbc?color=brightgreen)](https://pypi.org/project/pyodbc/)
32
+
33
+ pyodbc is an open source Python module that makes accessing ODBC databases simple. It
34
+ implements the [DB API 2.0](https://www.python.org/dev/peps/pep-0249) specification but is packed with even more Pythonic convenience.
35
+
36
+ The easiest way to install pyodbc is to use pip:
37
+
38
+ python -m pip install pyodbc
39
+
40
+ On Macs, you should probably install unixODBC first if you don't already have an ODBC
41
+ driver manager installed. For example, using the [homebrew](https://brew.sh/) package manager:
42
+
43
+ brew install unixodbc
44
+ python -m pip install pyodbc
45
+
46
+ Similarly, on Unix you should make sure you have an ODBC driver manager installed before
47
+ installing pyodbc. See the [docs](https://github.com/mkleehammer/pyodbc/wiki/Install)
48
+ for more information about how to do this on different Unix flavors. (On Windows, the
49
+ ODBC driver manager is built-in.)
50
+
51
+ Precompiled binary wheels are provided for multiple Python versions on most Windows, macOS,
52
+ and Linux platforms. On other platforms pyodbc will be built from the source code. Note,
53
+ pyodbc contains C++ extensions so you will need a suitable C++ compiler when building from
54
+ source. See the [docs](https://github.com/mkleehammer/pyodbc/wiki/Install) for details.
55
+
56
+ [Documentation](https://github.com/mkleehammer/pyodbc/wiki)
57
+
58
+ [Release Notes](https://github.com/mkleehammer/pyodbc/releases)
@@ -1,8 +1,8 @@
1
1
  [project]
2
2
  name = "pyodbc"
3
- version = "5.2.0"
3
+ version = "5.4.0"
4
4
 
5
- requires-python = ">=3.8"
5
+ requires-python = ">=3.11"
6
6
  # This is used by the GitHub action that builds release artifacts using cibuildwheel.
7
7
  # cibuildwheel reads this directly:
8
8
  #
@@ -10,7 +10,7 @@ requires-python = ">=3.8"
10
10
 
11
11
  description = "DB API module for ODBC"
12
12
  readme = "README.md"
13
- license = {text = "MIT-0 License"}
13
+ license = "MIT-0"
14
14
 
15
15
  authors = [{name = "Michael Kleehammer", email="michael@kleehammer.com"}]
16
16
 
@@ -22,7 +22,6 @@ maintainers = [{name = "Michael Kleehammer", email="michael@kleehammer.com"}]
22
22
  classifiers=['Development Status :: 5 - Production/Stable',
23
23
  'Intended Audience :: Developers',
24
24
  'Intended Audience :: System Administrators',
25
- 'License :: OSI Approved :: MIT No Attribution License (MIT-0)',
26
25
  'Operating System :: Microsoft :: Windows',
27
26
  'Operating System :: POSIX',
28
27
  'Programming Language :: Python',
@@ -56,11 +56,11 @@ def main():
56
56
  package_dir={'': 'src'},
57
57
  package_data={'': ['pyodbc.pyi']}, # places pyodbc.pyi alongside pyodbc.{platform}.{pyd|so} in site-packages
58
58
  license='MIT-0',
59
- python_requires='>=3.8',
59
+ python_requires='>=3.11',
60
60
  classifiers=['Development Status :: 5 - Production/Stable',
61
61
  'Intended Audience :: Developers',
62
62
  'Intended Audience :: System Administrators',
63
- 'License :: OSI Approved :: MIT No Attribution License (MIT-0)',
63
+ 'License :: OSI Approved :: MIT-0',
64
64
  'Operating System :: Microsoft :: Windows',
65
65
  'Operating System :: POSIX',
66
66
  'Programming Language :: Python',
@@ -116,6 +116,7 @@ def get_compiler_settings():
116
116
 
117
117
  settings['libraries'].append('odbc32')
118
118
  settings['libraries'].append('advapi32')
119
+ settings['libraries'].append('user32')
119
120
 
120
121
  elif os.environ.get("OS", '').lower().startswith('windows'):
121
122
  # Windows Cygwin (posix on windows)
@@ -147,10 +148,15 @@ def get_compiler_settings():
147
148
  '/opt/homebrew/include',
148
149
  expanduser('~/homebrew/include'),
149
150
  ]
151
+ _HOMEBREW_PREFIX = os.environ.get('HOMEBREW_PREFIX')
152
+ if _HOMEBREW_PREFIX is not None:
153
+ dirs.insert(0, os.path.join(_HOMEBREW_PREFIX, 'include'))
150
154
  settings['include_dirs'].extend(dir for dir in dirs if isdir(dir))
151
155
  # unixODBC make/install places libodbc.dylib in /usr/local/lib/ by default
152
156
  # ( also OS/X since El Capitan prevents /usr/lib from being accessed )
153
157
  settings['library_dirs'] = ['/usr/local/lib', '/opt/homebrew/lib']
158
+ if _HOMEBREW_PREFIX is not None:
159
+ settings['library_dirs'].insert(1, os.path.join(_HOMEBREW_PREFIX, 'lib'))
154
160
  else:
155
161
  # Other posix-like: Linux, Solaris, etc.
156
162
 
@@ -59,7 +59,7 @@ static char* StrDup(const char* text) {
59
59
  }
60
60
 
61
61
 
62
- static bool Connect(PyObject* pConnectString, HDBC hdbc, long timeout, PyObject* encoding)
62
+ static bool Connect(PyObject* pConnectString, HDBC hdbc, long timeout, PyObject* encoding, SQLUSMALLINT driver_completion)
63
63
  {
64
64
  assert(PyUnicode_Check(pConnectString));
65
65
 
@@ -88,25 +88,42 @@ static bool Connect(PyObject* pConnectString, HDBC hdbc, long timeout, PyObject*
88
88
  if (!cstring.isValid())
89
89
  return false;
90
90
 
91
+ SQLHWND hwnd = 0;
92
+ #ifdef _WIN32
93
+ if (driver_completion != SQL_DRIVER_NOPROMPT)
94
+ {
95
+ hwnd = GetDesktopWindow();
96
+ if (!hwnd)
97
+ {
98
+ PyErr_SetString(OperationalError, "Failed to get desktop window handle");
99
+ return false;
100
+ }
101
+ }
102
+ #endif
103
+
91
104
  Py_BEGIN_ALLOW_THREADS
92
- ret = SQLDriverConnectW(hdbc, 0, cstring, SQL_NTS, 0, 0, 0, SQL_DRIVER_NOPROMPT);
105
+ ret = SQLDriverConnectW(hdbc, hwnd, cstring, SQL_NTS, 0, 0, 0, driver_completion);
93
106
  Py_END_ALLOW_THREADS
94
107
  if (SQL_SUCCEEDED(ret))
95
108
  return true;
96
109
 
110
+ if (ret == SQL_NO_DATA)
111
+ {
112
+ PyErr_SetString(OperationalError, "User cancelled connection request");
113
+ return false;
114
+ }
115
+
97
116
  RaiseErrorFromHandle(0, "SQLDriverConnect", hdbc, SQL_NULL_HANDLE);
98
117
 
99
118
  return false;
100
119
  }
101
120
 
102
- static bool ApplyPreconnAttrs(HDBC hdbc, SQLINTEGER ikey, PyObject *value, char *strencoding)
121
+ static bool ApplyPreconnAttrs(HDBC hdbc, SQLINTEGER ikey, PyObject *value, char *strencoding, PyObject *keepalives)
103
122
  {
104
123
  SQLRETURN ret;
105
124
  SQLPOINTER ivalue = 0;
106
125
  SQLINTEGER vallen = 0;
107
126
 
108
- SQLWChar sqlchar;
109
-
110
127
  if (PyLong_Check(value))
111
128
  {
112
129
  if (_PyLong_Sign(value) >= 0)
@@ -121,19 +138,42 @@ static bool ApplyPreconnAttrs(HDBC hdbc, SQLINTEGER ikey, PyObject *value, char
121
138
  }
122
139
  else if (PyByteArray_Check(value))
123
140
  {
124
- ivalue = (SQLPOINTER)PyByteArray_AsString(value);
141
+ // A bytearray is mutable: PyByteArray_AsString returns a pointer into its internal
142
+ // storage, which a concurrent resize (.extend(), +=, slice assignment) reallocates,
143
+ // invalidating the pointer we hand to the driver. Some drivers read the buffer late
144
+ // (during SQLDriverConnectW, after the GIL has been released), so snapshot the contents
145
+ // into an immutable bytes and keep that alive instead -- matching the bytes/str paths
146
+ // and closing the race for the bytearray case.
147
+ // https://github.com/mkleehammer/pyodbc/issues/1497
148
+ Object po(PyBytes_FromObject(value));
149
+ if (!po)
150
+ return false; // OOM
151
+ ivalue = (SQLPOINTER)PyBytes_AsString(po);
125
152
  vallen = SQL_IS_POINTER;
153
+ // PyList_Append increments the refcount, so the list owns the snapshot for as long as
154
+ // the driver needs it; po releases our own reference when it goes out of scope.
155
+ if (PyList_Append(keepalives, po)) // OOM
156
+ return false;
126
157
  }
127
158
  else if (PyBytes_Check(value))
128
159
  {
129
- ivalue = PyBytes_AsString(value);
160
+ // Keep the value alive beyond this function invocation's lifetime.
161
+ if (PyList_Append(keepalives, value)) // OOM
162
+ return false;
163
+ ivalue = (SQLPOINTER)PyBytes_AsString(value);
130
164
  vallen = SQL_IS_POINTER;
131
165
  }
132
166
  else if (PyUnicode_Check(value))
133
167
  {
134
- sqlchar.set(value, strencoding ? strencoding : "utf-16le");
135
- ivalue = sqlchar.get();
168
+ Object po(PyUnicode_AsEncodedString(value, strencoding ? strencoding : "utf-16le", "strict"));
169
+ if (!po)
170
+ return false; // OOM
171
+ ivalue = (SQLPOINTER)PyBytes_AsString(po);
136
172
  vallen = SQL_NTS;
173
+ // PyList_Append increments the refcount, so the list owns the encoded buffer for as long
174
+ // as the driver needs it; po releases our own reference when it goes out of scope.
175
+ if (PyList_Append(keepalives, po)) // OOM
176
+ return false;
137
177
  }
138
178
  else if (PySequence_Check(value))
139
179
  {
@@ -142,7 +182,7 @@ static bool ApplyPreconnAttrs(HDBC hdbc, SQLINTEGER ikey, PyObject *value, char
142
182
  for (Py_ssize_t i = 0; i < len; i++)
143
183
  {
144
184
  Object v(PySequence_GetItem(value, i));
145
- if (!ApplyPreconnAttrs(hdbc, ikey, v.Get(), strencoding))
185
+ if (!ApplyPreconnAttrs(hdbc, ikey, v.Get(), strencoding, keepalives))
146
186
  return false;
147
187
  }
148
188
  return true;
@@ -170,7 +210,7 @@ static bool ApplyPreconnAttrs(HDBC hdbc, SQLINTEGER ikey, PyObject *value, char
170
210
  }
171
211
 
172
212
  PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, long timeout, bool fReadOnly,
173
- PyObject* attrs_before, PyObject* encoding)
213
+ PyObject* attrs_before, PyObject* encoding, SQLUSMALLINT driver_completion)
174
214
  {
175
215
  //
176
216
  // Allocate HDBC and connect
@@ -189,8 +229,18 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, long timeou
189
229
  // Attributes that must be set before connecting.
190
230
  //
191
231
 
232
+ PyObject* preconn_keepalives = 0;
192
233
  if (attrs_before)
193
234
  {
235
+ // We have evidence that some drivers hold on to preconnection values longer after the
236
+ // call to SQLSetConnectAttrW has returned, so we're keeping those values alive to avoid
237
+ // a crash.
238
+ // https://github.com/mkleehammer/pyodbc/issues/1469
239
+ // https://github.com/microsoft/msphpsql/issues/1594
240
+ preconn_keepalives = PyList_New(0);
241
+ if (!preconn_keepalives)
242
+ return 0;
243
+
194
244
  Py_ssize_t pos = 0;
195
245
  PyObject* key = 0;
196
246
  PyObject* value = 0;
@@ -206,22 +256,28 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, long timeou
206
256
 
207
257
  if (PyLong_Check(key))
208
258
  ikey = (int)PyLong_AsLong(key);
209
- if (!ApplyPreconnAttrs(hdbc, ikey, value, strencoding))
259
+ if (!ApplyPreconnAttrs(hdbc, ikey, value, strencoding, preconn_keepalives))
210
260
  {
261
+ Py_DECREF(preconn_keepalives);
211
262
  return 0;
212
263
  }
213
264
  }
214
265
  }
215
266
 
216
- if (!Connect(pConnectString, hdbc, timeout, encoding))
267
+ if (!Connect(pConnectString, hdbc, timeout, encoding, driver_completion))
217
268
  {
218
269
  // Connect has already set an exception.
219
270
  Py_BEGIN_ALLOW_THREADS
220
271
  SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
221
272
  Py_END_ALLOW_THREADS
273
+ Py_XDECREF(preconn_keepalives);
222
274
  return 0;
223
275
  }
224
276
 
277
+ // The current evidence indicates that the bug in Microsoft's driver isn't as bad as it
278
+ // could be, so we don't have to keep these objects alive until the connection is closed.
279
+ Py_XDECREF(preconn_keepalives);
280
+
225
281
  //
226
282
  // Connected, so allocate the Connection object.
227
283
  //
@@ -250,8 +306,11 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, long timeou
250
306
  cnxn->maxwrite = 0;
251
307
  cnxn->timeout = 0;
252
308
  cnxn->map_sqltype_to_converter = 0;
309
+ cnxn->readvar_initsize = 4096;
310
+ cnxn->compat_diagrec_byte_length = false;
253
311
 
254
312
  cnxn->attrs_before = attrs_before_o.Detach();
313
+ cnxn->fetch_decimal_as_string = false;
255
314
 
256
315
  // This is an inefficient default, but should work all the time. When we are offered
257
316
  // single-byte text we don't actually know what the encoding is. For example, with SQL
@@ -351,25 +410,44 @@ static char set_attr_doc[] =
351
410
  "attr_id\n"
352
411
  " The attribute id (integer) to set. These are ODBC or driver constants.\n\n"
353
412
  "value\n"
354
- " An integer value.\n\n"
355
- "At this time, only integer values are supported and are always passed as SQLUINTEGER.";
413
+ " An integer or string value.";
356
414
 
357
415
  static PyObject* Connection_set_attr(PyObject* self, PyObject* args)
358
416
  {
359
417
  int id;
360
- int value;
361
- if (!PyArg_ParseTuple(args, "ii", &id, &value))
418
+ PyObject* value;
419
+ if (!PyArg_ParseTuple(args, "iO", &id, &value))
362
420
  return 0;
363
421
 
364
422
  Connection* cnxn = (Connection*)self;
365
423
 
366
424
  SQLRETURN ret;
367
- Py_BEGIN_ALLOW_THREADS
368
- ret = SQLSetConnectAttr(cnxn->hdbc, id, (SQLPOINTER)(intptr_t)value, SQL_IS_INTEGER);
369
- Py_END_ALLOW_THREADS
425
+ SQLWChar sqlchar; // declared here so buffer stays alive across the call
426
+
427
+ if (PyLong_Check(value))
428
+ {
429
+ long ival = PyLong_AsLong(value);
430
+ Py_BEGIN_ALLOW_THREADS
431
+ ret = SQLSetConnectAttrW(cnxn->hdbc, id, (SQLPOINTER)(intptr_t)ival, SQL_IS_INTEGER);
432
+ Py_END_ALLOW_THREADS
433
+ }
434
+ else if (PyUnicode_Check(value))
435
+ {
436
+ sqlchar.set(value, "utf-16le");
437
+ Py_BEGIN_ALLOW_THREADS
438
+ ret = SQLSetConnectAttrW(cnxn->hdbc, id, sqlchar.get(), SQL_NTS);
439
+ Py_END_ALLOW_THREADS
440
+ }
441
+ else
442
+ {
443
+ PyErr_Format(PyExc_TypeError, "set_attr value must be a string or integer, not '%s'",
444
+ Py_TYPE(value)->tp_name);
445
+ return 0;
446
+ }
370
447
 
371
448
  if (!SQL_SUCCEEDED(ret))
372
449
  return RaiseErrorFromHandle(cnxn, "SQLSetConnectAttr", cnxn->hdbc, SQL_NULL_HANDLE);
450
+
373
451
  Py_RETURN_NONE;
374
452
  }
375
453
 
@@ -875,6 +953,21 @@ static PyObject* Connection_getclosed(PyObject* self, void* closure)
875
953
  Py_RETURN_FALSE;
876
954
  }
877
955
 
956
+ static PyObject* Connection_gethdbc(PyObject* self, void* closure)
957
+ {
958
+ UNUSED(closure);
959
+ Connection* cnxn;
960
+
961
+ if (!self || !Connection_Check(self))
962
+ {
963
+ PyErr_SetString(PyExc_TypeError, "Connection object required");
964
+ return nullptr;
965
+ }
966
+
967
+ cnxn = (Connection*)self;
968
+
969
+ return MakeVoidPointerFromHandle(cnxn->hdbc);
970
+ }
878
971
 
879
972
  static PyObject* Connection_getsearchescape(PyObject* self, void* closure)
880
973
  {
@@ -942,6 +1035,34 @@ static int Connection_setmaxwrite(PyObject* self, PyObject* value, void* closure
942
1035
  return 0;
943
1036
  }
944
1037
 
1038
+ static PyObject* Connection_getreadvarinitsize(PyObject* self, void* closure)
1039
+ {
1040
+ UNUSED(closure);
1041
+
1042
+ Connection* cnxn = Connection_Validate(self);
1043
+ if (!cnxn)
1044
+ return 0;
1045
+ return PyLong_FromSsize_t(cnxn->readvar_initsize);
1046
+ }
1047
+
1048
+ static int Connection_setreadvarinitsize(PyObject* self, PyObject* value, void* closure)
1049
+ {
1050
+ UNUSED(closure);
1051
+
1052
+ Connection* cnxn = Connection_Validate(self);
1053
+ if (!cnxn)
1054
+ return -1;
1055
+ Py_ssize_t v = PyLong_AsSsize_t(value);
1056
+ if (v == -1 && PyErr_Occurred())
1057
+ return -1;
1058
+ if (v < 0)
1059
+ {
1060
+ PyErr_SetString(PyExc_TypeError, "Cannot set readvar_initsize to a negative value.");
1061
+ return -1;
1062
+ }
1063
+ cnxn->readvar_initsize = v;
1064
+ return 0;
1065
+ }
945
1066
 
946
1067
  static PyObject* Connection_gettimeout(PyObject* self, void* closure)
947
1068
  {
@@ -991,6 +1112,53 @@ static int Connection_settimeout(PyObject* self, PyObject* value, void* closure)
991
1112
  return 0;
992
1113
  }
993
1114
 
1115
+ static PyObject* Connection_getfetchdecimalasstring(PyObject* self, void* closure)
1116
+ {
1117
+ UNUSED(closure);
1118
+
1119
+ Connection* cnxn = Connection_Validate(self);
1120
+ if (!cnxn)
1121
+ return 0;
1122
+
1123
+ PyObject* result = cnxn->fetch_decimal_as_string ? Py_True : Py_False;
1124
+ Py_INCREF(result);
1125
+ return result;
1126
+ }
1127
+
1128
+ static int Connection_setfetchdecimalasstring(PyObject* self, PyObject* value, void* closure)
1129
+ {
1130
+ UNUSED(closure);
1131
+
1132
+ Connection* cnxn = Connection_Validate(self);
1133
+ if (!cnxn)
1134
+ return -1;
1135
+
1136
+ if (value == 0)
1137
+ {
1138
+ PyErr_SetString(PyExc_TypeError, "Cannot delete the fetch_decimal_as_string attribute.");
1139
+ return -1;
1140
+ }
1141
+
1142
+ cnxn->fetch_decimal_as_string = PyObject_IsTrue(value);
1143
+ return 0;
1144
+ }
1145
+
1146
+ static PyObject* Connection_getcompat_diagrec_byte_length(PyObject* self, void* closure)
1147
+ {
1148
+ return PyBool_FromLong(((Connection*)self)->compat_diagrec_byte_length);
1149
+ }
1150
+
1151
+ static int Connection_setcompat_diagrec_byte_length(PyObject* self, PyObject* value, void* closure)
1152
+ {
1153
+ if (!PyBool_Check(value))
1154
+ {
1155
+ PyErr_SetString(PyExc_TypeError, "compat_diagrec_byte_length must be a bool");
1156
+ return -1;
1157
+ }
1158
+ ((Connection*)self)->compat_diagrec_byte_length = (value == Py_True);
1159
+ return 0;
1160
+ }
1161
+
994
1162
  static bool _remove_converter(PyObject* self, SQLSMALLINT sqltype)
995
1163
  {
996
1164
  Connection* cnxn = (Connection*)self;
@@ -1386,6 +1554,7 @@ static struct PyMethodDef Connection_methods[] =
1386
1554
  static PyGetSetDef Connection_getseters[] = {
1387
1555
  { "closed", (getter)Connection_getclosed, 0,
1388
1556
  "Returns True if the connection is closed; False otherwise.", 0},
1557
+ { "hdbc", (getter)Connection_gethdbc, 0, "ODBC connection handle.", 0 },
1389
1558
  { "searchescape", (getter)Connection_getsearchescape, 0,
1390
1559
  "The ODBC search pattern escape character, as returned by\n"
1391
1560
  "SQLGetInfo(SQL_SEARCH_PATTERN_ESCAPE). These are driver specific.", 0 },
@@ -1394,6 +1563,14 @@ static PyGetSetDef Connection_getseters[] = {
1394
1563
  { "timeout", Connection_gettimeout, Connection_settimeout,
1395
1564
  "The timeout in seconds, zero means no timeout.", 0 },
1396
1565
  { "maxwrite", Connection_getmaxwrite, Connection_setmaxwrite, "The maximum bytes to write before using SQLPutData.", 0 },
1566
+ { "readvar_initsize", Connection_getreadvarinitsize, Connection_setreadvarinitsize,
1567
+ "The initial buffer size in bytes for reading values from variable-length columns.", 0 },
1568
+ { "fetch_decimal_as_string", Connection_getfetchdecimalasstring, Connection_setfetchdecimalasstring,
1569
+ "If True, DECIMAL and NUMERIC values are fetched as strings using the legacy\n"
1570
+ "locale-aware path. If False (the default), values are fetched using a binary\n"
1571
+ "representation that is not affected by the locale.", 0 },
1572
+ { "compat_diagrec_byte_length", Connection_getcompat_diagrec_byte_length, Connection_setcompat_diagrec_byte_length,
1573
+ "If True, the driver reports byte length instead of character length in SQLGetDiagRecW().", 0 },
1397
1574
  { 0 }
1398
1575
  };
1399
1576
 
@@ -39,9 +39,18 @@ struct Connection
39
39
  // to insert NULLs into binary columns.
40
40
  bool supports_describeparam;
41
41
 
42
+ // Set to true if the driver doesn't handle SQL_NUMERIC_STRUCT properly.
43
+ bool fetch_decimal_as_string;
44
+
42
45
  // The column size of datetime columns, obtained from SQLGetInfo(), used to determine the datetime precision.
43
46
  int datetime_precision;
44
47
 
48
+ // Initial buffer allocation size for ReadVarColumn
49
+ // Any integer greater than 0 overrides the default value of 4096
50
+ // < 1 means use columnSize * cbElement + cbNullTeminator from the column descriptor,
51
+ // up to a ceiling of 32 MB. See https://github.com/mkleehammer/pyodbc/issues/1071.
52
+ Py_ssize_t readvar_initsize;
53
+
45
54
  // The connection timeout in seconds.
46
55
  long timeout;
47
56
 
@@ -87,6 +96,11 @@ struct Connection
87
96
 
88
97
  bool need_long_data_len;
89
98
 
99
+ // Flag for drivers which report the number of bytes in the returned diagnostic message
100
+ // from a call to SQLGetDiagRecW() rather than the number of characters, as required
101
+ // by ODBC. See https://github.com/mkleehammer/pyodbc/issues/489.
102
+ bool compat_diagrec_byte_length;
103
+
90
104
  PyObject* map_sqltype_to_converter;
91
105
  // If converters are defined, this will be a dictionary mapping from the SQLTYPE cast to an
92
106
  // int (because types can be negative) to the converter function.
@@ -104,7 +118,7 @@ struct Connection
104
118
  * exception is set and zero is returned.
105
119
  */
106
120
  PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, long timeout, bool fReadOnly,
107
- PyObject* attrs_before, PyObject* encoding);
121
+ PyObject* attrs_before, PyObject* encoding, SQLUSMALLINT driver_completion);
108
122
 
109
123
  /*
110
124
  * Used by the Cursor to implement commit and rollback.