]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- The precedence rules for the :meth:`.ColumnOperators.collate` operator
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 6 Dec 2013 00:03:31 +0000 (19:03 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 6 Dec 2013 00:03:31 +0000 (19:03 -0500)
have been modified, such that the COLLATE operator is now of lower
precedence than the comparison operators.  This has the effect that
a COLLATE applied to a comparison will not render parenthesis
around the comparison, which is not parsed by backends such as
MSSQL.  The change is backwards incompatible for those setups that
were working around the issue by applying :meth:`.Operators.collate`
to an individual element of the comparison expression,
rather than the comparison expression as a whole. [ticket:2879]

doc/build/changelog/changelog_09.rst
doc/build/changelog/migration_09.rst
lib/sqlalchemy/sql/operators.py
test/sql/test_compiler.py
test/sql/test_operators.py

index 370692528b656f6e70adfe06d02abbe9d997e9c6..14688fbfdd1de8e2b5c5f96686b8a3af68c343c2 100644 (file)
 .. changelog::
     :version: 0.9.0b2
 
+    .. change::
+        :tags: bug, sql
+        :tickets: 2879
+
+        The precedence rules for the :meth:`.ColumnOperators.collate` operator
+        have been modified, such that the COLLATE operator is now of lower
+        precedence than the comparison operators.  This has the effect that
+        a COLLATE applied to a comparison will not render parenthesis
+        around the comparison, which is not parsed by backends such as
+        MSSQL.  The change is backwards incompatible for those setups that
+        were working around the issue by applying :meth:`.Operators.collate`
+        to an individual element of the comparison expression,
+        rather than the comparison expression as a whole.
+
+        .. seelalso::
+
+            :ref:`migration_2879`
+
     .. change::
         :tags: bug, orm, declarative
         :tickets: 2865
index e70a17fb0599fd6deab014a63755f4d8507d41e8..cb658903a7341ff37508e79e3f65c7e053718f68 100644 (file)
@@ -501,7 +501,55 @@ spaces are passed through as is::
 
 :ticket:`2873`
 
+.. _migration_2879:
 
+The precedence rules for COLLATE have been changed
+--------------------------------------------------
+
+Previously, an expression like the following::
+
+    print (column('x') == 'somevalue').collate("en_EN")
+
+would produce an expression like this::
+
+    -- 0.8 behavior
+    (x = :x_1) COLLATE en_EN
+
+The above is misunderstood by MSSQL and is generally not the syntax suggested
+for any database.  The expression will now produce the syntax illustrated
+by that of most database documentation::
+
+    -- 0.9 behavior
+    x = :x_1 COLLATE en_EN
+
+The potentially backwards incompatible change arises if the :meth:`.collate`
+operator is being applied to the right-hand column, as follows::
+
+    print column('x') == literal('somevalue').collate("en_EN")
+
+In 0.8, this produces::
+
+    x = :param_1 COLLATE en_EN
+
+However in 0.9, will now produce the more accurate, but probably not what you
+want, form of::
+
+    x = (:param_1 COLLATE en_EN)
+
+The :meth:`.ColumnOperators.collate` operator now works more appropriately within an
+``ORDER BY`` expression as well, as a specific precedence has been given to the
+``ASC`` and ``DESC`` operators which will again ensure no parentheses are
+generated::
+
+    >>> # 0.8
+    >>> print column('x').collate('en_EN').desc()
+    (x COLLATE en_EN) DESC
+
+    >>> # 0.9
+    >>> print column('x').collate('en_EN').desc()
+    x COLLATE en_EN DESC
+
+:ticket:`2879`
 
 .. _migration_2850:
 
index 5bd4b302b1ed2604018d60f5c3d92e6b06306d0c..a156a97967ee6056274dc872d030e3afae2ba0c7 100644 (file)
@@ -831,7 +831,11 @@ _PRECEDENCE = {
     and_: 3,
     or_: 2,
     comma_op: -1,
-    collate: 7,
+
+    desc_op: 3,
+    asc_op: 3,
+    collate: 4,
+
     as_: -1,
     exists: 0,
     _asbool: -10,
index 36dfa2ff10fda2b8359b9eb4d1d174f2153cc6ee..ca24deb313be48c9d8965f5a72a60ed30ab60756 100644 (file)
@@ -1219,58 +1219,6 @@ class SelectTest(fixtures.TestBase, AssertsCompiledSQL):
                 "/ values.val1 > :param_1"
          )
 
-    def test_collate(self):
-        for expr in (select([table1.c.name.collate('latin1_german2_ci')]),
-                     select([collate(table1.c.name, 'latin1_german2_ci')])):
-            self.assert_compile(
-                expr, "SELECT mytable.name COLLATE latin1_german2_ci "
-                "AS anon_1 FROM mytable")
-
-        assert table1.c.name.collate('latin1_german2_ci').type is \
-            table1.c.name.type
-
-        expr = select([table1.c.name.collate('latin1_german2_ci').\
-                    label('k1')]).order_by('k1')
-        self.assert_compile(expr,
-            "SELECT mytable.name "
-            "COLLATE latin1_german2_ci AS k1 FROM mytable ORDER BY k1")
-
-        expr = select([collate('foo', 'latin1_german2_ci').label('k1')])
-        self.assert_compile(expr,
-                    "SELECT :param_1 COLLATE latin1_german2_ci AS k1")
-
-        expr = select([table1.c.name.collate('latin1_german2_ci').like('%x%')])
-        self.assert_compile(expr,
-                            "SELECT mytable.name COLLATE latin1_german2_ci "
-                            "LIKE :param_1 AS anon_1 FROM mytable")
-
-        expr = select([table1.c.name.like(collate('%x%',
-                                'latin1_german2_ci'))])
-        self.assert_compile(expr,
-                        "SELECT mytable.name "
-                        "LIKE :param_1 COLLATE latin1_german2_ci AS anon_1 "
-                        "FROM mytable")
-
-        expr = select([table1.c.name.collate('col1').like(
-            collate('%x%', 'col2'))])
-        self.assert_compile(expr,
-                            "SELECT mytable.name COLLATE col1 "
-                            "LIKE :param_1 COLLATE col2 AS anon_1 "
-                            "FROM mytable")
-
-        expr = select([func.concat('a', 'b').\
-                collate('latin1_german2_ci').label('x')])
-        self.assert_compile(expr,
-                            "SELECT concat(:param_1, :param_2) "
-                            "COLLATE latin1_german2_ci AS x")
-
-
-        expr = select([table1.c.name]).\
-                        order_by(table1.c.name.collate('latin1_german2_ci'))
-        self.assert_compile(expr,
-                            "SELECT mytable.name FROM mytable ORDER BY "
-                            "mytable.name COLLATE latin1_german2_ci")
-
     def test_percent_chars(self):
         t = table("table%name",
             column("percent%"),
index 0124d85fa0231cceef0bd0546cc13628089c794d..670d088d2353e5273d61c8bf6318a66c8e5d03e2 100644 (file)
@@ -673,6 +673,58 @@ class OperatorPrecedenceTest(fixtures.TestBase, testing.AssertsCompiledSQL):
                                 self.table2.c.field).is_(None)),
             "SELECT op.field FROM op WHERE (op.field MATCH op.field) IS NULL")
 
+    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)"
+        )
+
+    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"
+        )
+
+    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"
+        )
+
+    def test_operator_precedence_collate_4(self):
+        self.assert_compile(
+            and_(
+                (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"
+        )
+
+    def test_operator_precedence_collate_5(self):
+        self.assert_compile(
+            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"
+        )
+
+    def test_operator_precedence_collate_6(self):
+        self.assert_compile(
+            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"
+        )
+
+    def test_operator_precedence_collate_7(self):
+        self.assert_compile(
+            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"
+        )
+
     def test_commutative_operators(self):
         self.assert_compile(
          literal("a") + literal("b") * literal("c"),