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.
- {pyodbc-5.0.1/src/pyodbc.egg-info → pyodbc-5.2.0}/PKG-INFO +3 -3
- {pyodbc-5.0.1 → pyodbc-5.2.0}/pyproject.toml +3 -3
- {pyodbc-5.0.1 → pyodbc-5.2.0}/setup.py +2 -2
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/connection.cpp +3 -3
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/connection.h +1 -1
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/cursor.cpp +1 -3
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/cursor.h +2 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/dbspecific.h +1 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/getdata.cpp +66 -21
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/params.cpp +17 -1
- {pyodbc-5.0.1 → pyodbc-5.2.0/src/pyodbc.egg-info}/PKG-INFO +3 -3
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbc.h +5 -1
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbc.pyi +14 -11
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbcdbg.cpp +1 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbcmodule.cpp +2 -1
- {pyodbc-5.0.1 → pyodbc-5.2.0}/tests/postgresql_test.py +27 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/tests/sqlserver_test.py +66 -4
- {pyodbc-5.0.1 → pyodbc-5.2.0}/LICENSE.txt +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/MANIFEST.in +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/README.md +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/setup.cfg +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/cnxninfo.cpp +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/cnxninfo.h +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/decimal.cpp +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/decimal.h +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/errors.cpp +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/errors.h +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/getdata.h +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/params.h +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbc.egg-info/SOURCES.txt +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbc.egg-info/dependency_links.txt +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbc.egg-info/top_level.txt +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/pyodbcmodule.h +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/resource.h +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/row.cpp +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/row.h +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/textenc.cpp +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/textenc.h +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/src/wrapper.h +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/tests/__init__.py +0 -0
- {pyodbc-5.0.1 → pyodbc-5.2.0}/tests/conftest.py +0 -0
- {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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
{
|
|
@@ -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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
"""
|
|
699
|
-
|
|
700
|
-
|
|
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
|
-
"""
|
|
706
|
-
|
|
707
|
-
|
|
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,
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
#
|
|
1076
|
-
|
|
1077
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|