pyodbc 5.0.1__tar.gz → 5.2.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 (42) hide show
  1. {pyodbc-5.0.1/src/pyodbc.egg-info → pyodbc-5.2.0}/PKG-INFO +3 -3
  2. {pyodbc-5.0.1 → pyodbc-5.2.0}/pyproject.toml +3 -3
  3. {pyodbc-5.0.1 → pyodbc-5.2.0}/setup.py +2 -2
  4. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/connection.cpp +3 -3
  5. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/connection.h +1 -1
  6. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/cursor.cpp +1 -3
  7. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/cursor.h +2 -0
  8. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/dbspecific.h +1 -0
  9. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/getdata.cpp +66 -21
  10. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/params.cpp +17 -1
  11. {pyodbc-5.0.1 → pyodbc-5.2.0/src/pyodbc.egg-info}/PKG-INFO +3 -3
  12. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbc.h +5 -1
  13. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbc.pyi +14 -11
  14. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbcdbg.cpp +1 -0
  15. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbcmodule.cpp +2 -1
  16. {pyodbc-5.0.1 → pyodbc-5.2.0}/tests/postgresql_test.py +27 -0
  17. {pyodbc-5.0.1 → pyodbc-5.2.0}/tests/sqlserver_test.py +66 -4
  18. {pyodbc-5.0.1 → pyodbc-5.2.0}/LICENSE.txt +0 -0
  19. {pyodbc-5.0.1 → pyodbc-5.2.0}/MANIFEST.in +0 -0
  20. {pyodbc-5.0.1 → pyodbc-5.2.0}/README.md +0 -0
  21. {pyodbc-5.0.1 → pyodbc-5.2.0}/setup.cfg +0 -0
  22. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/cnxninfo.cpp +0 -0
  23. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/cnxninfo.h +0 -0
  24. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/decimal.cpp +0 -0
  25. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/decimal.h +0 -0
  26. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/errors.cpp +0 -0
  27. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/errors.h +0 -0
  28. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/getdata.h +0 -0
  29. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/params.h +0 -0
  30. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbc.egg-info/SOURCES.txt +0 -0
  31. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbc.egg-info/dependency_links.txt +0 -0
  32. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbc.egg-info/top_level.txt +0 -0
  33. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbcmodule.h +0 -0
  34. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/resource.h +0 -0
  35. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/row.cpp +0 -0
  36. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/row.h +0 -0
  37. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/textenc.cpp +0 -0
  38. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/textenc.h +0 -0
  39. {pyodbc-5.0.1 → pyodbc-5.2.0}/src/wrapper.h +0 -0
  40. {pyodbc-5.0.1 → pyodbc-5.2.0}/tests/__init__.py +0 -0
  41. {pyodbc-5.0.1 → pyodbc-5.2.0}/tests/conftest.py +0 -0
  42. {pyodbc-5.0.1 → pyodbc-5.2.0}/tests/mysql_test.py +0 -0
@@ -1,16 +1,16 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyodbc
3
- Version: 5.0.1
3
+ Version: 5.2.0
4
4
  Summary: DB API Module for ODBC
5
5
  Home-page: https://github.com/mkleehammer/pyodbc
6
6
  Maintainer: Michael Kleehammer
7
7
  Maintainer-email: michael@kleehammer.com
8
- License: MIT
8
+ License: MIT-0
9
9
  Platform: UNKNOWN
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: Intended Audience :: System Administrators
13
- Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: License :: OSI Approved :: MIT No Attribution License (MIT-0)
14
14
  Classifier: Operating System :: Microsoft :: Windows
15
15
  Classifier: Operating System :: POSIX
16
16
  Classifier: Programming Language :: Python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyodbc"
3
- version = "5.0.1"
3
+ version = "5.2.0"
4
4
 
5
5
  requires-python = ">=3.8"
6
6
  # This is used by the GitHub action that builds release artifacts using cibuildwheel.
@@ -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 License"}
13
+ license = {text = "MIT-0 License"}
14
14
 
15
15
  authors = [{name = "Michael Kleehammer", email="michael@kleehammer.com"}]
16
16
 
@@ -22,7 +22,7 @@ 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 License',
25
+ 'License :: OSI Approved :: MIT No Attribution License (MIT-0)',
26
26
  'Operating System :: Microsoft :: Windows',
27
27
  'Operating System :: POSIX',
28
28
  'Programming Language :: Python',
@@ -55,12 +55,12 @@ def main():
55
55
  packages=[''],
56
56
  package_dir={'': 'src'},
57
57
  package_data={'': ['pyodbc.pyi']}, # places pyodbc.pyi alongside pyodbc.{platform}.{pyd|so} in site-packages
58
- license='MIT',
58
+ license='MIT-0',
59
59
  python_requires='>=3.8',
60
60
  classifiers=['Development Status :: 5 - Production/Stable',
61
61
  'Intended Audience :: Developers',
62
62
  'Intended Audience :: System Administrators',
63
- 'License :: OSI Approved :: MIT License',
63
+ 'License :: OSI Approved :: MIT No Attribution License (MIT-0)',
64
64
  'Operating System :: Microsoft :: Windows',
65
65
  'Operating System :: POSIX',
66
66
  'Programming Language :: Python',
@@ -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, Object& encoding)
62
+ static bool Connect(PyObject* pConnectString, HDBC hdbc, long timeout, PyObject* encoding)
63
63
  {
64
64
  assert(PyUnicode_Check(pConnectString));
65
65
 
@@ -170,7 +170,7 @@ static bool ApplyPreconnAttrs(HDBC hdbc, SQLINTEGER ikey, PyObject *value, char
170
170
  }
171
171
 
172
172
  PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, long timeout, bool fReadOnly,
173
- PyObject* attrs_before, Object& encoding)
173
+ PyObject* attrs_before, PyObject* encoding)
174
174
  {
175
175
  //
176
176
  // Allocate HDBC and connect
@@ -196,7 +196,7 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, long timeou
196
196
  PyObject* value = 0;
197
197
 
198
198
  Object encodingholder;
199
- char *strencoding = encoding.Get() ?
199
+ char *strencoding = (encoding != 0) ?
200
200
  (PyUnicode_Check(encoding) ? PyBytes_AsString(encodingholder = PyCodec_Encode(encoding, "utf-8", "strict")) :
201
201
  PyBytes_Check(encoding) ? PyBytes_AsString(encoding) : 0) : 0;
202
202
 
@@ -104,7 +104,7 @@ struct Connection
104
104
  * exception is set and zero is returned.
105
105
  */
106
106
  PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, long timeout, bool fReadOnly,
107
- PyObject* attrs_before, Object& encoding);
107
+ PyObject* attrs_before, PyObject* encoding);
108
108
 
109
109
  /*
110
110
  * Used by the Cursor to implement commit and rollback.
@@ -577,7 +577,7 @@ static bool PrepareResults(Cursor* cur, int cCols)
577
577
  }
578
578
 
579
579
 
580
- static int GetDiagRecs(Cursor* cur)
580
+ int GetDiagRecs(Cursor* cur)
581
581
  {
582
582
  // Retrieves all diagnostic records from the cursor and assigns them to the "messages" attribute.
583
583
 
@@ -873,8 +873,6 @@ static PyObject* execute(Cursor* cur, PyObject* pSql, PyObject* params, bool ski
873
873
  Py_XDECREF(prevParam->pObject);
874
874
  newParam.BufferLength = newParam.StrLen_or_Ind;
875
875
  newParam.StrLen_or_Ind = SQL_DATA_AT_EXEC;
876
- Py_INCREF(cell);
877
- newParam.pObject = cell;
878
876
  *prevParam = newParam;
879
877
  if(prevParam->ParameterValuePtr == &newParam.Data)
880
878
  {
@@ -156,6 +156,8 @@ struct Cursor
156
156
  PyObject* messages;
157
157
  };
158
158
 
159
+ int GetDiagRecs(Cursor* cur);
160
+
159
161
  void Cursor_init();
160
162
 
161
163
  Cursor* Cursor_New(Connection* cnxn);
@@ -11,6 +11,7 @@
11
11
  // SQL Server
12
12
 
13
13
 
14
+ #define SQL_SS_VARIANT -150 // SQL Server 2008 SQL_VARIANT type
14
15
  #define SQL_SS_XML -152 // SQL Server 2005 XML type
15
16
  #define SQL_DB2_DECFLOAT -360 // IBM DB/2 DECFLOAT type
16
17
  #define SQL_DB2_XML -370 // IBM DB/2 XML type
@@ -38,6 +38,7 @@ void GetData_init()
38
38
  }
39
39
 
40
40
  static byte* ReallocOrFreeBuffer(byte* pb, Py_ssize_t cbNeed);
41
+ PyObject *GetData_SqlVariant(Cursor *cur, Py_ssize_t iCol);
41
42
 
42
43
  inline bool IsBinaryType(SQLSMALLINT sqltype)
43
44
  {
@@ -534,28 +535,30 @@ static PyObject* GetDataTimestamp(Cursor* cur, Py_ssize_t iCol)
534
535
 
535
536
  switch (cur->colinfos[iCol].sql_type)
536
537
  {
537
- case SQL_TYPE_TIME:
538
- {
539
- int micros = (int)(value.fraction / 1000); // nanos --> micros
540
- return PyTime_FromTime(value.hour, value.minute, value.second, micros);
541
- }
542
-
543
- case SQL_TYPE_DATE:
544
- return PyDate_FromDate(value.year, value.month, value.day);
545
-
546
- case SQL_TYPE_TIMESTAMP:
547
- {
548
- if (value.year < 1)
549
- {
550
- value.year = 1;
551
- }
552
- else if (value.year > 9999)
553
- {
554
- value.year = 9999;
555
- }
556
- }
538
+ case SQL_TYPE_TIME:
539
+ {
540
+ int micros = (int)(value.fraction / 1000); // nanos --> micros
541
+ return PyTime_FromTime(value.hour, value.minute, value.second, micros);
542
+ }
543
+
544
+ case SQL_TYPE_DATE:
545
+ case SQL_DATE:
546
+ return PyDate_FromDate(value.year, value.month, value.day);
547
+
548
+ case SQL_TYPE_TIMESTAMP:
549
+ case SQL_TIMESTAMP:
550
+ {
551
+ if (value.year < 1)
552
+ {
553
+ value.year = 1;
554
+ }
555
+ else if (value.year > 9999)
556
+ {
557
+ value.year = 9999;
558
+ }
559
+ }
557
560
  }
558
-
561
+
559
562
 
560
563
  int micros = (int)(value.fraction / 1000); // nanos --> micros
561
564
 
@@ -645,6 +648,7 @@ PyObject* PythonTypeFromSqlType(Cursor* cur, SQLSMALLINT type)
645
648
  break;
646
649
 
647
650
  case SQL_TYPE_DATE:
651
+ case SQL_DATE:
648
652
  pytype = (PyObject*)PyDateTimeAPI->DateType;
649
653
  break;
650
654
 
@@ -654,6 +658,7 @@ PyObject* PythonTypeFromSqlType(Cursor* cur, SQLSMALLINT type)
654
658
  break;
655
659
 
656
660
  case SQL_TYPE_TIMESTAMP:
661
+ case SQL_TIMESTAMP:
657
662
  pytype = (PyObject*)PyDateTimeAPI->DateTimeType;
658
663
  break;
659
664
 
@@ -744,15 +749,55 @@ PyObject* GetData(Cursor* cur, Py_ssize_t iCol)
744
749
  return GetDataDouble(cur, iCol);
745
750
 
746
751
 
752
+ case SQL_DATE:
747
753
  case SQL_TYPE_DATE:
748
754
  case SQL_TYPE_TIME:
755
+ case SQL_TIMESTAMP:
749
756
  case SQL_TYPE_TIMESTAMP:
750
757
  return GetDataTimestamp(cur, iCol);
751
758
 
752
759
  case SQL_SS_TIME2:
753
760
  return GetSqlServerTime(cur, iCol);
761
+
762
+ case SQL_SS_VARIANT:
763
+ return GetData_SqlVariant(cur, iCol);
754
764
  }
755
765
 
756
766
  return RaiseErrorV("HY106", ProgrammingError, "ODBC SQL type %d is not yet supported. column-index=%zd type=%d",
757
767
  (int)pinfo->sql_type, iCol, (int)pinfo->sql_type);
758
768
  }
769
+
770
+ PyObject *GetData_SqlVariant(Cursor *cur, Py_ssize_t iCol) {
771
+ char pBuff;
772
+
773
+ SQLLEN indicator, variantType;
774
+ SQLRETURN retcode;
775
+
776
+ PyObject *decodeResult;
777
+
778
+ // Call SQLGetData on the current column with a data length of 0. According to MS, this makes
779
+ // the ODBC driver read the sql_variant header which contains the underlying data type
780
+ pBuff = 0;
781
+ indicator = 0;
782
+ retcode = SQLGetData(cur->hstmt, static_cast<SQLSMALLINT>(iCol + 1), SQL_C_BINARY,
783
+ &pBuff, 0, &indicator);
784
+ if (!SQL_SUCCEEDED(retcode))
785
+ return RaiseErrorFromHandle(cur->cnxn, "SQLGetData", cur->cnxn->hdbc, cur->hstmt);
786
+
787
+ // Get the SQL_CA_SS_VARIANT_TYPE field for the column which will contain the underlying data type
788
+ variantType = 0;
789
+ retcode = SQLColAttribute(cur->hstmt, iCol + 1, SQL_CA_SS_VARIANT_TYPE, NULL, 0, NULL, &variantType);
790
+ if (!SQL_SUCCEEDED(retcode))
791
+ return RaiseErrorFromHandle(cur->cnxn, "SQLColAttribute", cur->cnxn->hdbc, cur->hstmt);
792
+
793
+ // Replace the original SQL_VARIANT data type with the underlying data type then call GetData() again
794
+ cur->colinfos[iCol].sql_type = static_cast<SQLSMALLINT>(variantType);
795
+ decodeResult = GetData(cur, iCol);
796
+
797
+ // Restore the original SQL_VARIANT data type so that the next decode will call this method again
798
+ cur->colinfos[iCol].sql_type = static_cast<SQLSMALLINT>(SQL_SS_VARIANT);
799
+
800
+ return decodeResult;
801
+
802
+ // NOTE: we don't free the hstmt here as it's managed by the cursor
803
+ }
@@ -247,8 +247,13 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI
247
247
  pNum->precision = (SQLCHAR)pi->ColumnSize;
248
248
  pNum->scale = (SQLCHAR)pi->DecimalDigits;
249
249
  pNum->sign = _PyLong_Sign(cell) >= 0;
250
- if (_PyLong_AsByteArray((PyLongObject*)absVal, pNum->val, sizeof(pNum->val), 1, 0))
250
+ #if PY_VERSION_HEX < 0x030D0000
251
+ if (_PyLong_AsByteArray((PyLongObject*)absVal, pNum->val, sizeof(pNum->val), 1, 0)) {
252
+ #else
253
+ if (_PyLong_AsByteArray((PyLongObject*)absVal, pNum->val, sizeof(pNum->val), 1, 0, 1)) {
254
+ #endif
251
255
  goto NumericOverflow;
256
+ }
252
257
  Py_XDECREF(absVal);
253
258
  *outbuf += pi->BufferLength;
254
259
  ind = sizeof(SQL_NUMERIC_STRUCT);
@@ -477,7 +482,12 @@ static int PyToCType(Cursor *cur, unsigned char **outbuf, PyObject *cell, ParamI
477
482
  pNum->precision = (SQLCHAR)pi->ColumnSize;
478
483
  pNum->scale = (SQLCHAR)pi->DecimalDigits;
479
484
 
485
+
486
+ #if PY_VERSION_HEX < 0x030D0000
480
487
  int ret = _PyLong_AsByteArray((PyLongObject*)digitLong, pNum->val, sizeof(pNum->val), 1, 0);
488
+ #else
489
+ int ret = _PyLong_AsByteArray((PyLongObject*)digitLong, pNum->val, sizeof(pNum->val), 1, 0, 1);
490
+ #endif
481
491
  Py_XDECREF(digitLong);
482
492
  if (ret)
483
493
  {
@@ -1266,6 +1276,7 @@ bool BindParameter(Cursor* cur, Py_ssize_t index, ParamInfo& info)
1266
1276
  {
1267
1277
  // Bind the TVP's columns --- all need to use DAE
1268
1278
  PyObject *param = PySequence_GetItem(row, i);
1279
+ Py_XDECREF(param);
1269
1280
  GetParameterInfo(cur, i, param, info.nested[i], true);
1270
1281
  info.nested[i].BufferLength = info.nested[i].StrLen_or_Ind;
1271
1282
  info.nested[i].StrLen_or_Ind = SQL_DATA_AT_EXEC;
@@ -1674,6 +1685,11 @@ bool ExecuteMulti(Cursor* cur, PyObject* pSql, PyObject* paramArrayObj)
1674
1685
  goto ErrorRet8;
1675
1686
  }
1676
1687
 
1688
+ if (rc == SQL_SUCCESS_WITH_INFO)
1689
+ {
1690
+ GetDiagRecs(cur);
1691
+ }
1692
+
1677
1693
  // TODO: Refactor into ProcessDAEParams() ?
1678
1694
  while (rc == SQL_NEED_DATA)
1679
1695
  {
@@ -1,16 +1,16 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyodbc
3
- Version: 5.0.1
3
+ Version: 5.2.0
4
4
  Summary: DB API Module for ODBC
5
5
  Home-page: https://github.com/mkleehammer/pyodbc
6
6
  Maintainer: Michael Kleehammer
7
7
  Maintainer-email: michael@kleehammer.com
8
- License: MIT
8
+ License: MIT-0
9
9
  Platform: UNKNOWN
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: Intended Audience :: System Administrators
13
- Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: License :: OSI Approved :: MIT No Attribution License (MIT-0)
14
14
  Classifier: Operating System :: Microsoft :: Windows
15
15
  Classifier: Operating System :: POSIX
16
16
  Classifier: Programming Language :: Python
@@ -76,6 +76,10 @@ typedef unsigned long long UINT64;
76
76
  #define SQL_CA_SS_CATALOG_NAME 1225
77
77
  #endif
78
78
 
79
+ #ifndef SQL_CA_SS_VARIANT_TYPE
80
+ #define SQL_CA_SS_VARIANT_TYPE 1215
81
+ #endif
82
+
79
83
  inline bool IsSet(DWORD grf, DWORD flags)
80
84
  {
81
85
  return (grf & flags) == flags;
@@ -117,7 +121,7 @@ inline void DebugTrace(const char* szFmt, ...) { UNUSED(szFmt); }
117
121
 
118
122
  // issue #880: entry missing from iODBC sqltypes.h
119
123
  #ifndef BYTE
120
- typedef unsigned char BYTE;
124
+ typedef unsigned char BYTE;
121
125
  #endif
122
126
  bool PyMem_Realloc(BYTE** pp, size_t newlen);
123
127
  // A wrapper around realloc with a safer interface. If it is successful, *pp is updated to the
@@ -60,6 +60,7 @@ SQL_TYPE_DATE: int
60
60
  SQL_TYPE_TIME: int
61
61
  SQL_TYPE_TIMESTAMP: int
62
62
  SQL_SS_TIME2: int
63
+ SQL_SS_VARIANT: int
63
64
  SQL_SS_XML: int
64
65
  SQL_INTERVAL_MONTH: int
65
66
  SQL_INTERVAL_YEAR: int
@@ -497,11 +498,13 @@ class Connection:
497
498
  ...
498
499
 
499
500
  def commit(self) -> None:
500
- """Commit all SQL statements executed on the connection since the last commit/rollback."""
501
+ """Commit all pending transactions since the last commit/rollback. This includes
502
+ all SQL statements executed from ALL cursors created on this connection."""
501
503
  ...
502
504
 
503
505
  def rollback(self) -> None:
504
- """Rollback all SQL statements executed on the connection since the last commit/rollback."""
506
+ """Rollback all pending transactions since the last commit/rollback. This includes
507
+ all SQL statements executed from ALL cursors created on this connection."""
505
508
  ...
506
509
 
507
510
  def close(self) -> None:
@@ -695,17 +698,17 @@ class Cursor:
695
698
  ...
696
699
 
697
700
  def commit(self) -> None:
698
- """Commit all SQL statements executed on the parent connection since the last
699
- commit/rollback. Note, this affects ALL cursors on the parent connection.
700
- Hence, consider calling commit() on the parent Connection object instead.
701
- """
701
+ """A convenience function for executing a commit on the parent connection.
702
+ Commits all pending transactions on the parent connection since the last
703
+ commit/rollback. This includes all SQL statements executed from ALL cursors
704
+ created on the parent connection, not just this cursor."""
702
705
  ...
703
706
 
704
707
  def rollback(self) -> None:
705
- """Rollback all SQL statements executed on the parent connection since the last
706
- commit/rollback. Note, this affects ALL cursors on the parent connection.
707
- Hence, consider calling rollback() on the parent Connection object instead.
708
- """
708
+ """A convenience function for executing a rollback on the parent connection.
709
+ Rolls back all pending transactions on the parent connection since the last
710
+ commit/rollback. This includes all SQL statements executed from ALL cursors
711
+ created on the parent connection, not just this cursor."""
709
712
  ...
710
713
 
711
714
  def cancel(self) -> None:
@@ -985,7 +988,7 @@ def connect(connstring: Optional[str] = None,
985
988
  encoding: str = 'utf-16le',
986
989
  readonly: bool = False,
987
990
  timeout: int = 0,
988
- attrs_before: Optional[Dict[int, Any]] = None,
991
+ attrs_before: Optional[Dict[int, Union[int, bytes, bytearray, str, Sequence[str]]]] = None,
989
992
  **kwargs: Any) -> Connection:
990
993
  """Create a new ODBC connection to a database. See the Wiki for details:
991
994
  https://github.com/mkleehammer/pyodbc/wiki/The-pyodbc-Module#connect
@@ -34,6 +34,7 @@ const char* SqlTypeName(SQLSMALLINT n)
34
34
  _MAKESTR(SQL_TYPE_TIME);
35
35
  _MAKESTR(SQL_TYPE_TIMESTAMP);
36
36
  _MAKESTR(SQL_SS_TIME2);
37
+ _MAKESTR(SQL_SS_VARIANT);
37
38
  _MAKESTR(SQL_SS_XML);
38
39
  _MAKESTR(SQL_BINARY);
39
40
  _MAKESTR(SQL_VARBINARY);
@@ -399,7 +399,7 @@ static PyObject* mod_connect(PyObject* self, PyObject* args, PyObject* kwargs)
399
399
  int fAutoCommit = 0;
400
400
  int fReadOnly = 0;
401
401
  long timeout = 0;
402
- Object encoding;
402
+ PyObject* encoding = 0;
403
403
 
404
404
  Object attrs_before; // Optional connect attrs set before connecting
405
405
 
@@ -865,6 +865,7 @@ static const ConstantDef aConstants[] = {
865
865
  MAKECONST(SQL_TYPE_TIME),
866
866
  MAKECONST(SQL_TYPE_TIMESTAMP),
867
867
  MAKECONST(SQL_SS_TIME2),
868
+ MAKECONST(SQL_SS_VARIANT),
868
869
  MAKECONST(SQL_SS_XML),
869
870
  MAKECONST(SQL_INTERVAL_MONTH),
870
871
  MAKECONST(SQL_INTERVAL_YEAR),
@@ -593,3 +593,30 @@ def test_output_conversion(cursor: pyodbc.Cursor):
593
593
  cursor.connection.add_output_converter(pyodbc.SQL_WVARCHAR, None)
594
594
  value = cursor.execute("select v from t1").fetchone()[0]
595
595
  assert value == '123.45'
596
+
597
+
598
+ def test_refcount_encoding():
599
+ """
600
+ Ensure we handle the reference count to `encoding` properly. In the past we freed a
601
+ borrowed reference. This would cause a segfault.
602
+ """
603
+ # https://github.com/mkleehammer/pyodbc/issues/1343
604
+ import sys
605
+ encoding = 'utf-16le'
606
+ count_before = sys.getrefcount(encoding)
607
+
608
+ def _test():
609
+ # I've moved this into a function so the exception's stack trace will be freed under
610
+ # the covers when we leave the function. Otherwise we'd have a 2nd reference to
611
+ # `encoding` in the stack trace of the exception.
612
+ try:
613
+ cnxn = pyodbc.connect(CNXNSTR, encoding=encoding)
614
+ except:
615
+ pass
616
+
617
+ for i in range(10):
618
+ _test()
619
+
620
+ count_after = sys.getrefcount(encoding)
621
+
622
+ assert count_after == count_before
@@ -1072,10 +1072,9 @@ def test_cursor_messages_with_print(cursor: pyodbc.Cursor):
1072
1072
  """
1073
1073
  assert not cursor.messages
1074
1074
 
1075
- # SQL Server PRINT statements are never more than 8000 characters
1076
- # https://docs.microsoft.com/en-us/sql/t-sql/language-elements/print-transact-sql#remarks
1077
- for msg in ('hello world', 'ABCDEFGHIJ' * 800):
1078
- cursor.execute(f"PRINT '{msg}'")
1075
+ # ascii / extended ascii / unicode / beyond BMP unicode
1076
+ for msg in ('hello world', 'a \xeb a', 'b \u0394 b', 'c \U0001F31C c'):
1077
+ cursor.execute(f"PRINT N'{msg}'") # note, unicode literal
1079
1078
  messages = cursor.messages
1080
1079
  assert isinstance(messages, list)
1081
1080
  assert len(messages) == 1
@@ -1086,6 +1085,31 @@ def test_cursor_messages_with_print(cursor: pyodbc.Cursor):
1086
1085
  assert '[01000] (0)' == messages[0][0]
1087
1086
  assert messages[0][1].endswith(msg)
1088
1087
 
1088
+ # maximum size message
1089
+ # SQL Server PRINT statements are never more than 8000 characters
1090
+ # https://docs.microsoft.com/en-us/sql/t-sql/language-elements/print-transact-sql#remarks
1091
+ msg = 'ABCDEFGH' * 1000
1092
+ cursor.execute(f"PRINT '{msg}'") # note, plain ascii literal
1093
+ messages = cursor.messages
1094
+ assert len(messages) == 1
1095
+ assert messages[0][1].endswith(msg)
1096
+
1097
+
1098
+ def test_cursor_messages_with_fast_executemany(cursor: pyodbc.Cursor):
1099
+ """
1100
+ Ensure the Cursor.messages attribute is set with fast_executemany=True.
1101
+ """
1102
+ cursor.execute("create table t2(id1 int, id2 int)")
1103
+ cursor.commit()
1104
+
1105
+ cursor.fast_executemany = True
1106
+ cursor.executemany(
1107
+ "print 'hello';insert into t2(id1, id2) values (?, ?)",
1108
+ [(10, 11), (20, 21)],
1109
+ )
1110
+ assert len(cursor.messages) == 2
1111
+ assert all(m[1].endswith('hello') for m in cursor.messages)
1112
+
1089
1113
 
1090
1114
  def test_cursor_messages_with_stored_proc(cursor: pyodbc.Cursor):
1091
1115
  """
@@ -1590,6 +1614,44 @@ def test_tvp_diffschema(cursor: pyodbc.Cursor):
1590
1614
  _test_tvp(cursor, True)
1591
1615
 
1592
1616
 
1617
+ @pytest.mark.skipif(SQLSERVER_YEAR < 2000, reason='sql_variant not supported until 2000')
1618
+ def test_sql_variant(cursor: pyodbc.Cursor):
1619
+ """
1620
+ Tests decoding of the sql_variant data type as performed by the GetData_SqlVariant() method.
1621
+ """
1622
+
1623
+ cursor.execute("create table t1 (a sql_variant)")
1624
+
1625
+ # insert a number of values of disparate types. this is not exhaustive as not all
1626
+ # types that can be contained within a sql_variant field are supported by pyodbc
1627
+ cursor.execute("insert into t1 values (456.7)")
1628
+ cursor.execute("insert into t1 values ('a string')")
1629
+ cursor.execute("insert into t1 values (CAST('2024-06-03' AS DATE))")
1630
+ cursor.execute("insert into t1 values (CAST('2024-06-03 23:46:03.000' AS DATETIME))")
1631
+ cursor.execute("insert into t1 values (CAST('binary data' AS VARBINARY(200)))")
1632
+ cursor.execute(
1633
+ "insert into t1 values (CAST('0592b437-745f-4b2c-a997-97022c624cf6' AS UNIQUEIDENTIFIER))"
1634
+ )
1635
+
1636
+ # select all of the values we inserted and ensure they have the correct types
1637
+ results = [record[0] for record in cursor.execute("select a from t1").fetchall()]
1638
+ for index, assertion_tuple in enumerate(
1639
+ [
1640
+ (Decimal, Decimal("456.7")),
1641
+ (str, "a string"),
1642
+ (date, date(2024, 6, 3)),
1643
+ (datetime, datetime(2024, 6, 3, 23, 46, 3)),
1644
+ (bytes, b'binary data'),
1645
+ (uuid.UUID, uuid.UUID("0592b437-745f-4b2c-a997-97022c624cf6"))
1646
+ ]
1647
+ ):
1648
+ # pylint: disable=unidiomatic-typecheck
1649
+ expected_type, expected_value = assertion_tuple
1650
+
1651
+ assert type(results[index]) == expected_type
1652
+ assert results[index] == expected_value
1653
+
1654
+
1593
1655
  def get_sqlserver_version(cursor: pyodbc.Cursor):
1594
1656
 
1595
1657
  """
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes