]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- mysql, postgres: "%" signs in text() constructs are automatically escaped to "%%".
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 2 Jan 2009 21:24:17 +0000 (21:24 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 2 Jan 2009 21:24:17 +0000 (21:24 +0000)
Because of the backwards incompatible nature of this change,
a warning is emitted if '%%' is detected in the string.  [ticket:1267]

CHANGES
lib/sqlalchemy/databases/mysql.py
lib/sqlalchemy/databases/postgres.py
lib/sqlalchemy/sql/compiler.py
test/sql/query.py

diff --git a/CHANGES b/CHANGES
index e098cbdbc9bb7eaa6a9215d3de42b09835385762..4b3c3a21d72dc6ec67991e9064c889c262194bf7 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -319,6 +319,10 @@ CHANGES
       behavior. [ticket:1243]
 
 - postgres
+    - "%" signs in text() constructs are automatically escaped to "%%".
+      Because of the backwards incompatible nature of this change, 
+      a warning is emitted if '%%' is detected in the string.  [ticket:1267]
+
     - Calling alias.execute() in conjunction with
       server_side_cursors won't raise AttributeError.
 
@@ -339,6 +343,10 @@ CHANGES
       convert_unicode=True flags.  [ticket:1233]
       
 - mysql
+    - "%" signs in text() constructs are automatically escaped to "%%".
+      Because of the backwards incompatible nature of this change, 
+      a warning is emitted if '%%' is detected in the string.
+      
     - Fixed bug in exception raise when FK columns not present
       during reflection. [ticket:1241]
       
index 86fb7b247d1b63c8aae89ba65cb18f6efc5fd716..1b977dce90e52345209e5ac3c3290c2a2d2845a4 100644 (file)
@@ -1952,6 +1952,11 @@ class MySQLCompiler(compiler.DefaultCompiler):
         return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
 
 
+    def post_process_text(self, text):
+        if '%%' in text:
+            util.warn("The SQLAlchemy MySQLDB dialect now automatically escapes '%' in text() expressions to '%%'.")
+        return text.replace('%', '%%')
+
     def get_select_precolumns(self, select):
         if isinstance(select._distinct, basestring):
             return select._distinct.upper() + " "
index 273f5859ecc7983fe9c4e2fb456b0f642cf8c277..6fd0662debb9cb4abd6081e2109876e735dc46a0 100644 (file)
@@ -719,6 +719,11 @@ class PGCompiler(compiler.DefaultCompiler):
         else:
             return "nextval('%s')" % self.preparer.format_sequence(seq)
 
+    def post_process_text(self, text):
+        if '%%' in text:
+            util.warn("The SQLAlchemy psycopg2 dialect now automatically escapes '%' in text() expressions to '%%'.")
+        return text.replace('%', '%%')
+
     def limit_clause(self, select):
         text = ""
         if select._limit is not None:
index 0430f053bd35f0613cdd71225d77d0aafd253ccb..b286398bfcddcec8eb7ed4dc0c89c87cb54fa29e 100644 (file)
@@ -294,6 +294,9 @@ class DefaultCompiler(engine.Compiled):
     def visit_typeclause(self, typeclause, **kwargs):
         return typeclause.type.dialect_impl(self.dialect).get_col_spec()
 
+    def post_process_text(self, text):
+        return text
+        
     def visit_textclause(self, textclause, **kwargs):
         if textclause.typemap is not None:
             for colname, type_ in textclause.typemap.iteritems():
@@ -308,7 +311,7 @@ class DefaultCompiler(engine.Compiled):
 
         # un-escape any \:params
         return BIND_PARAMS_ESC.sub(lambda m: m.group(1),
-            BIND_PARAMS.sub(do_bindparam, textclause.text)
+            BIND_PARAMS.sub(do_bindparam, self.post_process_text(textclause.text))
         )
 
     def visit_null(self, null, **kwargs):
index e62dfa076c9f0b00985fb4bf53a7d308b16a826d..275bbe78c66e33ed01cb09c79826f716410fac26 100644 (file)
@@ -4,7 +4,7 @@ from sqlalchemy import *
 from sqlalchemy import exc, sql
 from sqlalchemy.engine import default
 from testlib import *
-
+from testlib.testing import eq_
 
 class QueryTest(TestBase):
 
@@ -235,6 +235,35 @@ class QueryTest(TestBase):
             l.append(row)
         self.assert_(len(l) == 2, "fetchmany(size=2) got %s rows" % len(l))
 
+    def test_like_ops(self):
+        users.insert().execute(
+            {'user_id':1, 'user_name':'apples'},
+            {'user_id':2, 'user_name':'oranges'},
+            {'user_id':3, 'user_name':'bananas'},
+            {'user_id':4, 'user_name':'legumes'},
+            {'user_id':5, 'user_name':'hi % there'},
+        )
+
+        for expr, result in (
+            (select([users.c.user_id]).where(users.c.user_name.startswith('apple')), [(1,)]),
+            (select([users.c.user_id]).where(users.c.user_name.contains('i % t')), [(5,)]),
+            (select([users.c.user_id]).where(users.c.user_name.endswith('anas')), [(3,)]),
+        ):
+            eq_(expr.execute().fetchall(), result)
+    
+
+    @testing.emits_warning('.*now automatically escapes.*')
+    def test_percents_in_text(self):
+        for expr, result in (
+            (text("select 6 % 10"), 6),
+            (text("select 17 % 10"), 7),
+            (text("select '%'"), '%'),
+            (text("select '%%'"), '%%'),
+            (text("select '%%%'"), '%%%'),
+            (text("select 'hello % world'"), "hello % world")
+        ):
+            eq_(testing.db.scalar(expr), result)
+        
     def test_ilike(self):
         users.insert().execute(
             {'user_id':1, 'user_name':'one'},