From: Jason Kirtland Date: Wed, 16 Apr 2008 00:53:21 +0000 (+0000) Subject: - Support for COLLATE: collate(expr, col) and expr.collate(col) X-Git-Tag: rel_0_5beta1~182 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=24797113708c0f19ef0d5d81e2950b33f8c1a425;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Support for COLLATE: collate(expr, col) and expr.collate(col) --- diff --git a/CHANGES b/CHANGES index 3bcfd89159..dc3d153c5f 100644 --- 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() + expression operator and collate(, ) 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 diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py index 9e2d50c269..343a0cac8c 100644 --- a/lib/sqlalchemy/__init__.py +++ b/lib/sqlalchemy/__init__.py @@ -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 diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 47e5ec9c5e..1fe9ef0622 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -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 = { diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 648e74e7e1..8c439ce2cb 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -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. `` 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. diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py index 0047d1c732..dfd638ecb1 100644 --- a/lib/sqlalchemy/sql/operators.py +++ b/lib/sqlalchemy/sql/operators.py @@ -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, diff --git a/test/sql/select.py b/test/sql/select.py index b51e65d332..1e96835926 100644 --- a/test/sql/select.py +++ b/test/sql/select.py @@ -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(),