return self
+ def _with_collation(self, collation: str) -> Self:
+ """set up error handling for the collate expression"""
+ raise NotImplementedError("this datatype does not support collation")
+
@util.ro_memoized_property
def _type_affinity(self) -> Optional[Type[TypeEngine[_T]]]:
"""Return a rudimental 'affinity' value expressing the general class
{},
)
+ def _copy_with_check(self) -> Self:
+ tt = self.copy()
+ if not isinstance(tt, self.__class__):
+ raise AssertionError(
+ "Type object %s does not properly "
+ "implement the copy() method, it must "
+ "return an object of type %s" % (self, self.__class__)
+ )
+ return tt
+
def _gen_dialect_impl(self, dialect: Dialect) -> TypeEngine[_T]:
if dialect.name in self._variant_mapping:
adapted = dialect.type_descriptor(
# to a copy of this TypeDecorator and return
# that.
typedesc = self.load_dialect_impl(dialect).dialect_impl(dialect)
- tt = self.copy()
- if not isinstance(tt, self.__class__):
- raise AssertionError(
- "Type object %s does not properly "
- "implement the copy() method, it must "
- "return an object of type %s" % (self, self.__class__)
- )
+ tt = self._copy_with_check()
tt.impl = tt.impl_instance = typedesc
return tt
+ def _with_collation(self, collation: str) -> Self:
+ tt = self._copy_with_check()
+ tt.impl = tt.impl_instance = self.impl_instance._with_collation(
+ collation
+ )
+ return tt
+
@util.ro_non_memoized_property
def _type_affinity(self) -> Optional[Type[TypeEngine[Any]]]:
return self.impl_instance._type_affinity
from sqlalchemy import cast
from sqlalchemy import CHAR
from sqlalchemy import CLOB
+from sqlalchemy import collate
from sqlalchemy import DATE
from sqlalchemy import Date
from sqlalchemy import DATETIME
import sqlalchemy.dialects.oracle as oracle
import sqlalchemy.dialects.postgresql as pg
from sqlalchemy.engine import default
+from sqlalchemy.engine import interfaces
from sqlalchemy.schema import AddConstraint
from sqlalchemy.schema import CheckConstraint
from sqlalchemy.sql import column
+from sqlalchemy.sql import compiler
from sqlalchemy.sql import ddl
from sqlalchemy.sql import elements
from sqlalchemy.sql import null
],
)
+ @testing.fixture
+ def renders_bind_cast(self):
+ class MyText(Text):
+ render_bind_cast = True
+
+ class MyCompiler(compiler.SQLCompiler):
+ def render_bind_cast(self, type_, dbapi_type, sqltext):
+ return f"""{sqltext}->BINDCAST->[{
+ self.dialect.type_compiler_instance.process(
+ dbapi_type, identifier_preparer=self.preparer
+ )
+ }]"""
+
+ class MyDialect(default.DefaultDialect):
+ bind_typing = interfaces.BindTyping.RENDER_CASTS
+ colspecs = {Text: MyText}
+ statement_compiler = MyCompiler
+
+ return MyDialect()
+
+ @testing.combinations(
+ (lambda c1: c1.like("qpr"), "q LIKE :q_1->BINDCAST->[TEXT]"),
+ (
+ lambda c2: c2.like("qpr"),
+ 'q LIKE :q_1->BINDCAST->[TEXT COLLATE "xyz"]',
+ ),
+ (
+ # new behavior, a type with no collation passed into collate()
+ # now has a new type with that collation, so we get the collate
+ # on the right side bind-cast. previous to #11576 we'd only
+ # get TEXT for the bindcast.
+ lambda c1: collate(c1, "abc").like("qpr"),
+ '(q COLLATE abc) LIKE :param_1->BINDCAST->[TEXT COLLATE "abc"]',
+ ),
+ (
+ lambda c2: collate(c2, "abc").like("qpr"),
+ '(q COLLATE abc) LIKE :param_1->BINDCAST->[TEXT COLLATE "abc"]',
+ ),
+ argnames="testcase,expected",
+ )
+ @testing.variation("use_type_decorator", [True, False])
+ def test_collate_type_interaction(
+ self, renders_bind_cast, testcase, expected, use_type_decorator
+ ):
+ """test #11576.
+
+ This involves dialects that use the render_bind_cast feature only,
+ currently asycnpg and psycopg. However, the implementation of the
+ feature is mostly in Core, so a fixture dialect / compiler is used so
+ that the test is agnostic of those dialects.
+
+ """
+
+ if use_type_decorator:
+
+ class MyTextThing(TypeDecorator):
+ cache_ok = True
+ impl = Text
+
+ c1 = Column("q", MyTextThing())
+ c2 = Column("q", MyTextThing(collation="xyz"))
+ else:
+ c1 = Column("q", Text())
+ c2 = Column("q", Text(collation="xyz"))
+
+ expr = testing.resolve_lambda(testcase, c1=c1, c2=c2)
+ if use_type_decorator:
+ assert isinstance(expr.left.type, MyTextThing)
+ self.assert_compile(expr, expected, dialect=renders_bind_cast)
+
+ # original types still work, have not been modified
+ eq_(c1.type.collation, None)
+ eq_(c2.type.collation, "xyz")
+
+ self.assert_compile(
+ c1.like("qpr"),
+ "q LIKE :q_1->BINDCAST->[TEXT]",
+ dialect=renders_bind_cast,
+ )
+ self.assert_compile(
+ c2.like("qpr"),
+ 'q LIKE :q_1->BINDCAST->[TEXT COLLATE "xyz"]',
+ dialect=renders_bind_cast,
+ )
+
def test_bind_adapt(self, connection):
# test an untyped bind gets the left side's type