.. changelog::
:version: 1.2.0b1
+ .. change:: 3785
+ :tags: bug, sql
+ :tickets: 3785
+
+ The expression used for COLLATE as rendered by the column-level
+ :func:`.expression.collate` and :meth:`.ColumnOperators.collate` is now
+ quoted as an identifier when the name is case sensitive, e.g. has
+ uppercase characters. Note that this does not impact type-level
+ collation, which is already quoted.
+
+ .. seealso::
+
+ :ref:`change_3785`
+
.. change:: 3229
:tags: feature, orm, ext
:tickets: 3229
:ticket:`3907`
+.. _change_3785:
+
+The column-level COLLATE keyword now quotes the collation name
+--------------------------------------------------------------
+
+A bug in the :func:`.expression.collate` and :meth:`.ColumnOperators.collate`
+functions, used to supply ad-hoc column collations at the statement level,
+is fixed, where a case sensitive name would not be quoted::
+
+ stmt = select([mytable.c.x, mytable.c.y]).\
+ order_by(mytable.c.somecolumn.collate("fr_FR"))
+
+now renders::
+
+ SELECT mytable.x, mytable.y,
+ FROM mytable ORDER BY mytable.somecolumn COLLATE "fr_FR"
+
+Previously, the case sensitive name `"fr_FR"` would not be quoted. Currently,
+manual quoting of the "fr_FR" name is **not** detected, so applications that
+are manually quoting the identifier should be adjusted. Note that this change
+does not impact the use of collations at the type level (e.g. specified
+on the datatype like :class:`.String` at the table level), where quoting
+is already applied.
+
+:ticket:`3785`
+
Dialect Improvements and Changes - PostgreSQL
=============================================
mycolumn COLLATE utf8_bin
+ The collation expression is also quoted if it is a case sensitive
+ identifer, e.g. contains uppercase characters.
+
+ .. versionchanged:: 1.2 quoting is automatically applied to COLLATE
+ expressions if they are case sensitive.
+
"""
expr = _literal_as_binds(expression)
return BinaryExpression(
expr,
- _literal_as_text(collation),
+ ColumnClause(collation),
operators.collate, type_=expr.type)
def collate(self, collation):
"""Produce a :func:`~.expression.collate` clause against
- the parent object, given the collation string."""
+ the parent object, given the collation string.
+
+ .. seealso::
+
+ :func:`~.expression.collate`
+
+ """
return self.operate(collate, collation)
def __radd__(self, other):
def test_collate(self):
User = self.classes.User
- self._test(collate(User.id, 'binary'), "users.id COLLATE binary")
+ self._test(collate(User.id, 'utf8_bin'), "users.id COLLATE utf8_bin")
- self._test(User.id.collate('binary'), "users.id COLLATE binary")
+ self._test(User.id.collate('utf8_bin'), "users.id COLLATE utf8_bin")
def test_selfref_between(self):
User = self.classes.User
def test_operator_precedence_collate_1(self):
self.assert_compile(
self.table1.c.name == literal('foo').collate('utf-8'),
- "mytable.name = (:param_1 COLLATE utf-8)"
+ 'mytable.name = (:param_1 COLLATE "utf-8")'
)
def test_operator_precedence_collate_2(self):
self.assert_compile(
(self.table1.c.name == literal('foo')).collate('utf-8'),
- "mytable.name = :param_1 COLLATE utf-8"
+ 'mytable.name = :param_1 COLLATE "utf-8"'
)
def test_operator_precedence_collate_3(self):
self.assert_compile(
self.table1.c.name.collate('utf-8') == 'foo',
- "(mytable.name COLLATE utf-8) = :param_1"
+ '(mytable.name COLLATE "utf-8") = :param_1'
)
def test_operator_precedence_collate_4(self):
(self.table1.c.name == literal('foo')).collate('utf-8'),
(self.table2.c.field == literal('bar')).collate('utf-8'),
),
- "mytable.name = :param_1 COLLATE utf-8 "
- "AND op.field = :param_2 COLLATE utf-8"
+ 'mytable.name = :param_1 COLLATE "utf-8" '
+ 'AND op.field = :param_2 COLLATE "utf-8"'
)
def test_operator_precedence_collate_5(self):
select([self.table1.c.name]).order_by(
self.table1.c.name.collate('utf-8').desc()),
"SELECT mytable.name FROM mytable "
- "ORDER BY mytable.name COLLATE utf-8 DESC"
+ 'ORDER BY mytable.name COLLATE "utf-8" DESC'
)
def test_operator_precedence_collate_6(self):
select([self.table1.c.name]).order_by(
self.table1.c.name.collate('utf-8').desc().nullslast()),
"SELECT mytable.name FROM mytable "
- "ORDER BY mytable.name COLLATE utf-8 DESC NULLS LAST"
+ 'ORDER BY mytable.name COLLATE "utf-8" DESC NULLS LAST'
)
def test_operator_precedence_collate_7(self):
select([self.table1.c.name]).order_by(
self.table1.c.name.collate('utf-8').asc()),
"SELECT mytable.name FROM mytable "
- "ORDER BY mytable.name COLLATE utf-8 ASC"
+ 'ORDER BY mytable.name COLLATE "utf-8" ASC'
)
def test_commutative_operators(self):
'SELECT t1.col1 AS "ShouldQuote" FROM t1 ORDER BY "ShouldQuote"'
)
+ def test_collate(self):
+ self.assert_compile(
+ column('foo').collate('utf8'),
+ "foo COLLATE utf8"
+ )
+
+ self.assert_compile(
+ column('foo').collate('fr_FR'),
+ 'foo COLLATE "fr_FR"'
+ )
+
+ self.assert_compile(
+ column('foo').collate('utf8_GERMAN_ci'),
+ 'foo COLLATE `utf8_GERMAN_ci`',
+ dialect="mysql"
+ )
+
def test_join(self):
# Lower case names, should not quote
metadata = MetaData()