]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Apply percent sign escaping to literal binds, comments
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 25 Aug 2017 19:26:02 +0000 (15:26 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 25 Aug 2017 20:53:49 +0000 (16:53 -0400)
Fixed bug in new percent-sign support (e.g. :ticket:`3740`) where a bound
parameter rendered with literal_binds would fail to escape percent-signs
for relevant dialects.  In addition, ensured new table / column comment
support feature also fully makes use of literal-rendered parameters so that
this percent sign support takes place with table / column comment DDL as
well, allowing percent sign support for the mysql / psycopg2 backends that
require escaping of percent signs.

Change-Id: Ia4136a300933e9bc6a01a7b9afd5c7b9a3fee4e3
Fixes: #4054
Fixes: #4052
lib/sqlalchemy/dialects/mysql/base.py
lib/sqlalchemy/sql/sqltypes.py
lib/sqlalchemy/testing/suite/test_reflection.py
lib/sqlalchemy/testing/suite/test_types.py
test/dialect/mysql/test_reflection.py
test/sql/test_text.py

index 0cffae3895c1e3c5a6cee24cb9bd41b4eaf31eeb..9d6dd7188237e28c17df4a6f12a17bfdc2288ac6 100644 (file)
@@ -1171,7 +1171,9 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
         ], nonpart_options):
             arg = opts[opt]
             if opt in _reflection._options_of_type_string:
-                arg = "'%s'" % arg.replace("\\", "\\\\").replace("'", "''")
+
+                arg = self.sql_compiler.render_literal_value(
+                    arg, sqltypes.String())
 
             if opt in ('DATA_DIRECTORY', 'INDEX_DIRECTORY',
                        'DEFAULT_CHARACTER_SET', 'CHARACTER_SET',
@@ -1196,7 +1198,8 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
         ], part_options):
             arg = opts[opt]
             if opt in _reflection._options_of_type_string:
-                arg = "'%s'" % arg.replace("\\", "\\\\").replace("'", "''")
+                arg = self.sql_compiler.render_literal_value(
+                    arg, sqltypes.String())
 
             opt = opt.replace('_', ' ')
             joiner = ' '
index 6838baa5f1df55a1658b32ba6e30793ee7984312..fe9a56b6ec3d7644afef8d5341eeccd9bbfd01c6 100644 (file)
@@ -205,6 +205,10 @@ class String(Concatenable, TypeEngine):
     def literal_processor(self, dialect):
         def process(value):
             value = value.replace("'", "''")
+
+            if dialect.identifier_preparer._double_percents:
+                value = value.replace('%', '%%')
+
             return "'%s'" % value
         return process
 
index 54b59a432fe5e1d5269ed916d5dde24bba0feaa6..7674338b4e21104197af250f3db8f945edb37470 100644 (file)
@@ -104,9 +104,12 @@ class ComponentReflectionTest(fixtures.TablesTest):
               )
         Table('comment_test', metadata,
               Column('id', sa.Integer, primary_key=True, comment='id comment'),
-              Column('data', sa.String(20), comment='data comment'),
+              Column('data', sa.String(20), comment='data % comment'),
+              Column(
+                  'd2', sa.String(20),
+                  comment=r"""Comment types type speedily ' " \ '' Fun!"""),
               schema=schema,
-              comment='the test table comment')
+              comment=r"""the test % ' " \ table comment""")
 
         if testing.requires.index_reflection.enabled:
             cls.define_index(metadata, users)
@@ -274,7 +277,7 @@ class ComponentReflectionTest(fixtures.TablesTest):
 
         eq_(
             insp.get_table_comment("comment_test", schema=schema),
-            {"text": "the test table comment"}
+            {"text": r"""the test % ' " \ table comment"""}
         )
 
         eq_(
@@ -290,7 +293,9 @@ class ComponentReflectionTest(fixtures.TablesTest):
             ],
             [
                 {'comment': 'id comment', 'name': 'id'},
-                {'comment': 'data comment', 'name': 'data'}
+                {'comment': 'data % comment', 'name': 'data'},
+                {'comment': r"""Comment types type speedily ' " \ '' Fun!""",
+                 'name': 'd2'}
             ]
         )
 
index a345454be3952badc9627f301c0b201443e8a713..83aac28505aad7824e6a5f8d9705544504aae5d5 100644 (file)
@@ -187,6 +187,10 @@ class TextTest(_LiteralRoundTripFixture, fixtures.TablesTest):
         data = r'backslash one \ backslash two \\ end'
         self._literal_round_trip(Text, [data], [data])
 
+    def test_literal_percentsigns(self):
+        data = r'percent % signs %% percent'
+        self._literal_round_trip(Text, [data], [data])
+
 
 class StringTest(_LiteralRoundTripFixture, fixtures.TestBase):
     __backend__ = True
index dc088223d80a3486ee7df96d85dd75ee7a19471f..9437631d774e3f4f1da554922cd293d985593531 100644 (file)
@@ -263,7 +263,7 @@ class ReflectionTest(fixtures.TestBase, AssertsExecutionResults):
             'mysql_def', MetaData(testing.db),
             Column('c1', Integer()),
             mysql_engine='MEMORY',
-            mysql_comment=comment,
+            comment=comment,
             mysql_default_charset='utf8',
             mysql_auto_increment='5',
             mysql_avg_row_length='3',
@@ -280,7 +280,7 @@ class ReflectionTest(fixtures.TestBase, AssertsExecutionResults):
             def_table.drop()
 
         assert def_table.kwargs['mysql_engine'] == 'MEMORY'
-        assert def_table.kwargs['mysql_comment'] == comment
+        assert def_table.comment == comment
         assert def_table.kwargs['mysql_default_charset'] == 'utf8'
         assert def_table.kwargs['mysql_auto_increment'] == '5'
         assert def_table.kwargs['mysql_avg_row_length'] == '3'
@@ -288,6 +288,8 @@ class ReflectionTest(fixtures.TestBase, AssertsExecutionResults):
         assert def_table.kwargs['mysql_connection'] == 'fish'
 
         assert reflected.kwargs['mysql_engine'] == 'MEMORY'
+
+        assert reflected.comment == comment
         assert reflected.kwargs['mysql_comment'] == comment
         assert reflected.kwargs['mysql_default charset'] == 'utf8'
         assert reflected.kwargs['mysql_avg_row_length'] == '3'
index ca4d10702a3fbdd3a938f67bb10f0d35f1cbfa60..c31c22853ecaaae9bef04851a9f33437703f7e73 100644 (file)
@@ -4,7 +4,7 @@ from sqlalchemy.testing import fixtures, AssertsCompiledSQL, eq_, \
     assert_raises_message, expect_warnings, assert_warnings
 from sqlalchemy import text, select, Integer, String, Float, \
     bindparam, and_, func, literal_column, exc, MetaData, Table, Column,\
-    asc, func, desc, union
+    asc, func, desc, union, literal
 from sqlalchemy.types import NullType
 from sqlalchemy.sql import table, column, util as sql_util
 from sqlalchemy import util
@@ -331,6 +331,23 @@ class BindParamTest(fixtures.TestBase, AssertsCompiledSQL):
             dialect="mysql"
         )
 
+    def test_percent_signs_literal_binds(self):
+        stmt = select([literal("percent % signs %%")])
+        self.assert_compile(
+            stmt,
+            "SELECT 'percent % signs %%' AS anon_1",
+            dialect="sqlite",
+            literal_binds=True
+        )
+
+        self.assert_compile(
+            stmt,
+            "SELECT 'percent %% signs %%%%' AS anon_1",
+            dialect="mysql",
+            literal_binds=True
+        )
+
+
 class AsFromTest(fixtures.TestBase, AssertsCompiledSQL):
     __dialect__ = 'default'