summaryrefslogtreecommitdiff
path: root/test/dialect/mssql
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-10-18 09:44:37 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2022-10-18 14:11:11 -0400
commitf4214975a7deb5e13f8b6cf21e39697821396a7f (patch)
treef4c3f6480812e12f8d833aed892b195349b18fed /test/dialect/mssql
parent665c94cc2f0340735515c4f4477e11b556d2bcd8 (diff)
downloadsqlalchemy-f4214975a7deb5e13f8b6cf21e39697821396a7f.tar.gz
further qualify pyodbc setinputsizes types for long stirngs
Fixed regression caused by SQL Server pyodbc change :ticket:`8177` where we now use ``setinputsizes()`` by default; for VARCHAR, this fails if the character size is greater than 4000 (or 2000, depending on data) characters as the incoming datatype is NVARCHAR, which has a limit of 4000 characters, despite the fact that VARCHAR can handle unlimited characters. Additional pyodbc-specific typing information is now passed to ``setinputsizes()`` when the datatype's size is > 2000 characters. The change is also applied to the :class:`.JSON` type which was also impacted by this issue for large JSON serializations. Fixes: #8661 Change-Id: I07fa873e95dbd2c94f3d286e93e8b3229c3a9807
Diffstat (limited to 'test/dialect/mssql')
-rw-r--r--test/dialect/mssql/test_types.py167
1 files changed, 167 insertions, 0 deletions
diff --git a/test/dialect/mssql/test_types.py b/test/dialect/mssql/test_types.py
index ff84f180b..eb14cb30f 100644
--- a/test/dialect/mssql/test_types.py
+++ b/test/dialect/mssql/test_types.py
@@ -6,6 +6,7 @@ import os
import sqlalchemy as sa
from sqlalchemy import Boolean
+from sqlalchemy import cast
from sqlalchemy import Column
from sqlalchemy import column
from sqlalchemy import Date
@@ -18,6 +19,7 @@ from sqlalchemy import LargeBinary
from sqlalchemy import literal
from sqlalchemy import MetaData
from sqlalchemy import Numeric
+from sqlalchemy import NVARCHAR
from sqlalchemy import PickleType
from sqlalchemy import schema
from sqlalchemy import select
@@ -32,6 +34,7 @@ from sqlalchemy import types
from sqlalchemy import Unicode
from sqlalchemy import UnicodeText
from sqlalchemy.dialects.mssql import base as mssql
+from sqlalchemy.dialects.mssql import NTEXT
from sqlalchemy.dialects.mssql import ROWVERSION
from sqlalchemy.dialects.mssql import TIMESTAMP
from sqlalchemy.dialects.mssql import UNIQUEIDENTIFIER
@@ -1236,6 +1239,170 @@ class StringTest(fixtures.TestBase, AssertsCompiledSQL):
)
+class StringRoundTripTest(fixtures.TestBase):
+ """tests for #8661
+
+
+ at the moment most of these are using the default setinputsizes enabled
+ behavior, with the exception of the plain executemany() calls for inserts.
+
+
+ """
+
+ __only_on__ = "mssql"
+ __backend__ = True
+
+ @testing.combinations(
+ ("unicode", True), ("ascii", False), argnames="unicode_", id_="ia"
+ )
+ @testing.combinations(
+ String,
+ Unicode,
+ NVARCHAR,
+ NTEXT,
+ Text,
+ UnicodeText,
+ argnames="datatype",
+ )
+ @testing.combinations(
+ 100, 1999, 2000, 2001, 3999, 4000, 4001, 5000, argnames="length"
+ )
+ def test_long_strings_inpplace(
+ self, connection, unicode_, length, datatype
+ ):
+ if datatype is NVARCHAR and length != "max" and length > 4000:
+ return
+ elif unicode_ and datatype not in (NVARCHAR, UnicodeText):
+ return
+
+ if datatype in (String, NVARCHAR):
+ dt = datatype(length)
+ else:
+ dt = datatype()
+
+ if length == "max":
+ length = 12000
+
+ if unicode_:
+ data = "réve🐍illé" * ((length // 9) + 1)
+ data = data[0 : (length // 2)]
+ else:
+ data = "abcdefg" * ((length // 7) + 1)
+ data = data[0:length]
+ assert len(data) == length
+
+ stmt = select(cast(literal(data, type_=dt), type_=dt))
+ result = connection.scalar(stmt)
+ eq_(result, data)
+
+ @testing.combinations(
+ ("unicode", True), ("ascii", False), argnames="unicode_", id_="ia"
+ )
+ @testing.combinations(
+ ("returning", True),
+ ("noreturning", False),
+ argnames="use_returning",
+ id_="ia",
+ )
+ @testing.combinations(
+ ("insertmany", True),
+ ("insertsingle", False),
+ argnames="insertmany",
+ id_="ia",
+ )
+ @testing.combinations(
+ String,
+ Unicode,
+ NVARCHAR,
+ NTEXT,
+ Text,
+ UnicodeText,
+ argnames="datatype",
+ )
+ @testing.combinations(
+ 100, 1999, 2000, 2001, 3999, 4000, 4001, 5000, "max", argnames="length"
+ )
+ def test_long_strings_in_context(
+ self,
+ connection,
+ metadata,
+ unicode_,
+ length,
+ datatype,
+ use_returning,
+ insertmany,
+ ):
+
+ if datatype is NVARCHAR and length != "max" and length > 4000:
+ return
+ elif unicode_ and datatype not in (NVARCHAR, UnicodeText):
+ return
+
+ if datatype in (String, NVARCHAR):
+ dt = datatype(length)
+ else:
+ dt = datatype()
+
+ t = Table(
+ "t",
+ metadata,
+ Column("id", Integer, primary_key=True),
+ Column("data", dt),
+ )
+
+ t.create(connection)
+
+ if length == "max":
+ length = 12000
+
+ if unicode_:
+ data = "réve🐍illé" * ((length // 9) + 1)
+ data = data[0 : (length // 2)]
+ else:
+ data = "abcdefg" * ((length // 7) + 1)
+ data = data[0:length]
+ assert len(data) == length
+
+ if insertmany:
+ insert_data = [{"data": data}, {"data": data}, {"data": data}]
+ expected_data = [data, data, data]
+ else:
+ insert_data = {"data": data}
+ expected_data = [data]
+
+ if use_returning:
+ result = connection.execute(
+ t.insert().returning(t.c.data), insert_data
+ )
+ eq_(result.scalars().all(), expected_data)
+ else:
+ connection.execute(t.insert(), insert_data)
+
+ result = connection.scalars(select(t.c.data))
+ eq_(result.all(), expected_data)
+
+ # note that deprecate_large_types indicates that UnicodeText
+ # will be fulfilled by NVARCHAR, and not NTEXT. However if NTEXT
+ # is used directly, it isn't supported in WHERE clauses:
+ # "The data types ntext and (anything, including ntext itself) are
+ # incompatible in the equal to operator."
+ if datatype is NTEXT:
+ return
+
+ # test WHERE criteria
+ connection.execute(
+ t.insert(), [{"data": "some other data %d" % i} for i in range(3)]
+ )
+
+ result = connection.scalars(select(t.c.data).where(t.c.data == data))
+ eq_(result.all(), expected_data)
+
+ result = connection.scalars(
+ select(t.c.data).where(t.c.data != data).order_by(t.c.id)
+ )
+ eq_(result.all(), ["some other data %d" % i for i in range(3)])
+
+
class UniqueIdentifierTest(test_types.UuidTest):
__only_on__ = "mssql"
__backend__ = True