]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Support for COLLATE: collate(expr, col) and expr.collate(col)
authorJason Kirtland <jek@discorporate.us>
Wed, 16 Apr 2008 00:53:21 +0000 (00:53 +0000)
committerJason Kirtland <jek@discorporate.us>
Wed, 16 Apr 2008 00:53:21 +0000 (00:53 +0000)
CHANGES
lib/sqlalchemy/__init__.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/expression.py
lib/sqlalchemy/sql/operators.py
test/sql/select.py

diff --git a/CHANGES b/CHANGES
index 3bcfd8915902a3eb54457b216f1bdca00029d351..dc3d153c5fc49f886547e08acb29c31f1102a92f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -6,29 +6,34 @@ CHANGES
 =====
 - orm
     - Fix to the recent relation() refactoring which fixes
-      exotic viewonly relations which join between local and remote table
-      multiple times, with a common column shared between the 
-      joins.
-    
-    - Also re-established viewonly relation() configurations that
-      join across multiple tables.
-      
-    - added experimental relation() flag to help with primaryjoins
-      across functions, etc., _local_remote_pairs=[tuples].  
-      This complements a complex primaryjoin condition allowing 
-      you to provide the individual column pairs which comprise
-      the relation's local and remote sides.  Also improved 
-      lazy load SQL generation to handle placing bind params 
-      inside of functions and other expressions.
-      (partial progress towards [ticket:610])
-    
-    - removed ancient assertion that mapped selectables require
+      exotic viewonly relations which join between local and
+      remote table multiple times, with a common column shared
+      between the joins.
+
+    - Also re-established viewonly relation() configurations
+      that join across multiple tables.
+
+    - Added experimental relation() flag to help with
+      primaryjoins across functions, etc.,
+      _local_remote_pairs=[tuples].  This complements a complex
+      primaryjoin condition allowing you to provide the
+      individual column pairs which comprise the relation's
+      local and remote sides.  Also improved lazy load SQL
+      generation to handle placing bind params inside of
+      functions and other expressions.  (partial progress
+      towards [ticket:610])
+
+    - Removed ancient assertion that mapped selectables require
       "alias names" - the mapper creates its own alias now if
-      none is present.  Though in this case you need to use 
-      the class, not the mapped selectable, as the source of
-      column attributes - so a warning is still issued.
-      
+      none is present.  Though in this case you need to use the
+      class, not the mapped selectable, as the source of column
+      attributes - so a warning is still issued.
+
 - sql
+    - Added COLLATE support via the .collate(<collation>)
+      expression operator and collate(<expr>, <collation>) sql
+      function.
+
     - Fixed bug with union() when applied to non-Table connected
       select statements
 
@@ -39,13 +44,16 @@ CHANGES
 
 - mssql
     - Added "odbc_autotranslate" parameter to engine / dburi
-      parameters. Any given string will be passed through to 
-      the ODBC connection string as 
-            "AutoTranslat=%s" % odbc_autotranslate
+      parameters. Any given string will be passed through to the
+      ODBC connection string as:
+
+            "AutoTranslate=%s" % odbc_autotranslate
+
       [ticket:1005]
 
 - firebird
-    - Handle the "SUBSTRING(:string FROM :start FOR :length)" builtin.
+    - Handle the "SUBSTRING(:string FROM :start FOR :length)"
+      builtin.
 
 
 0.4.5
index 9e2d50c269d06a0b48a98b989ed3a6bdc95729d6..343a0cac8c90331e0f82b414b7a1c0205eb5ac10 100644 (file)
@@ -16,7 +16,7 @@ from sqlalchemy.sql import \
     and_, or_, not_, \
     select, subquery, union, union_all, insert, update, delete, \
     join, outerjoin, \
-    bindparam, outparam, asc, desc, \
+    bindparam, outparam, asc, desc, collate, \
     except_, except_all, exists, intersect, intersect_all, \
     between, case, cast, distinct, extract
 
index 47e5ec9c5e9708c1a8d62469ebe6101e804b7633..1fe9ef0622b2c40e5fe2d9441dfc8b504b5264d1 100644 (file)
@@ -90,7 +90,8 @@ OPERATORS =  {
     operators.as_ : 'AS',
     operators.exists : 'EXISTS',
     operators.is_ : 'IS',
-    operators.isnot : 'IS NOT'
+    operators.isnot : 'IS NOT',
+    operators.collate : 'COLLATE',
 }
 
 FUNCTIONS = {
index 648e74e7e15f4853d7abb04e849c000421591be9..8c439ce2cbc728de830ab6efe538e27cd305bce2 100644 (file)
@@ -40,7 +40,7 @@ __all__ = [
     'Select', 'Selectable', 'TableClause', 'Update', 'alias', 'and_', 'asc',
     'between', 'bindparam', 'case', 'cast', 'column', 'delete',
     'desc', 'distinct', 'except_', 'except_all', 'exists', 'extract', 'func',
-    'modifier',
+    'modifier', 'collate',
     'insert', 'intersect', 'intersect_all', 'join', 'literal',
     'literal_column', 'not_', 'null', 'or_', 'outparam', 'outerjoin', 'select',
     'subquery', 'table', 'text', 'union', 'union_all', 'update', ]
@@ -493,6 +493,14 @@ def extract(field, expr):
     expr = _BinaryExpression(text(field), expr, operators.from_)
     return func.extract(expr)
 
+def collate(expression, collation):
+    """Return the clause ``expression COLLATE collation``."""
+
+    expr = _literal_as_binds(expression)
+    return _CalculatedClause(
+        expr, expr, _literal_as_text(collation),
+        operator=operators.collate, group=False)
+
 def exists(*args, **kwargs):
     """Return an ``EXISTS`` clause as applied to a [sqlalchemy.sql.expression#Select] object.
 
@@ -1226,6 +1234,9 @@ class ColumnOperators(Operators):
     def asc(self):
         return self.operate(operators.asc_op)
 
+    def collate(self, collation):
+        return self.operate(operators.collate, collation)
+
     def __radd__(self, other):
         return self.reverse_operate(operators.add, other)
 
@@ -1390,6 +1401,13 @@ class _CompareMixin(ColumnOperators):
 
         return _BinaryExpression(self, ClauseList(self._check_literal(cleft), self._check_literal(cright), operator=operators.and_, group=False), operators.between_op)
 
+    def collate(self, collation):
+        """Produce a COLLATE clause, i.e. ``<column> COLLATE utf8_bin``"""
+        name = getattr(self, 'name', None)
+        return _CalculatedClause(
+           None, self, _literal_as_text(collation),
+            operator=operators.collate, group=False)
+
     def op(self, operator):
         """produce a generic operator function.
 
index 0047d1c732e66a89e1afa3284fe90cdef801e721..dfd638ecb1b2f5acef8faa8488a456e6daeac5ea 100644 (file)
@@ -22,6 +22,9 @@ def is_():
 def isnot():
     raise NotImplementedError()
 
+def collate():
+    raise NotImplementedError()
+
 def op(a, opstring, b):
     return a.op(opstring)(b)
 
@@ -105,6 +108,7 @@ _PRECEDENCE = {
     and_:3,
     or_:2,
     comma_op:-1,
+    collate: -2,
     as_:-1,
     exists:0,
     _smallest: -1000,
index b51e65d332cd1b18f3d8f7eff1d74af28726f7de..1e96835926e9671b9a6b775564d1e233639f40c1 100644 (file)
@@ -751,6 +751,35 @@ FROM mytable, myothertable WHERE foo.id = foofoo(lala) AND datetime(foo) = Today
 
         self.assert_compile(select([extract("day", func.to_date("03/20/2005", "MM/DD/YYYY"))]), "SELECT extract(day FROM to_date(:to_date_1, :to_date_2)) AS extract_1")
 
+    def test_collate(self):
+        for expr in (select([table1.c.name.collate('somecol')]),
+                     select([collate(table1.c.name, 'somecol')])):
+            self.assert_compile(
+                expr, "SELECT mytable.name COLLATE somecol FROM mytable")
+
+        expr = select([table1.c.name.collate('somecol').like('%x%')])
+        self.assert_compile(expr,
+                            "SELECT mytable.name COLLATE somecol "
+                            "LIKE :param_1 AS anon_1 FROM mytable")
+
+        expr = select([table1.c.name.like(collate('%x%', 'somecol'))])
+        self.assert_compile(expr,
+                            "SELECT mytable.name "
+                            "LIKE :param_1 COLLATE somecol 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('somecol').label('x')])
+        self.assert_compile(expr,
+                            "SELECT concat(:param_1, :param_2) "
+                            "COLLATE somecol AS x")
+
     def test_joins(self):
         self.assert_compile(
             join(table2, table1, table1.c.myid == table2.c.otherid).select(),