--- /dev/null
+.. change::
+ :tags: bug, mysql, mariadb
+ :tickets: 9722
+
+ Fixed issues regarding reflection of comments for :class:`_schema.Table`
+ and :class:`_schema.Column` objects, where the comments contained control
+ characters such as newlines. Additional testing support for these
+ characters as well as extended Unicode characters in table and column
+ comments (the latter of which aren't supported by MySQL/MariaDB) added to
+ testing overall.
options = {}
- if not line or line == ")":
- pass
-
- else:
- rest_of_line = line[:]
+ if line and line != ")":
+ rest_of_line = line
for regex, cleanup in self._pr_options:
m = regex.search(rest_of_line)
if not m:
comment = spec.get("comment", None)
if comment is not None:
- comment = comment.replace("\\\\", "\\").replace("''", "'")
+ comment = cleanup_text(comment)
sqltext = spec.get("generated")
if sqltext is not None:
re.escape(directive),
self._optional_equals,
)
- self._pr_options.append(
- _pr_compile(
- regex, lambda v: v.replace("\\\\", "\\").replace("''", "'")
- )
- )
+ self._pr_options.append(_pr_compile(regex, cleanup_text))
def _add_option_word(self, directive):
regex = r"(?P<directive>%s)%s" r"(?P<val>\w+)" % (
a = a[1:-1].replace(a[0] * 2, a[0])
strip_values.append(a)
return strip_values
+
+
+def cleanup_text(raw_text: str) -> str:
+ if "\\" in raw_text:
+ raw_text = re.sub(
+ _control_char_regexp, lambda s: _control_char_map[s[0]], raw_text
+ )
+ return raw_text.replace("''", "'")
+
+
+_control_char_map = {
+ "\\\\": "\\",
+ "\\0": "\0",
+ "\\a": "\a",
+ "\\b": "\b",
+ "\\t": "\t",
+ "\\n": "\n",
+ "\\v": "\v",
+ "\\f": "\f",
+ "\\r": "\r",
+ # '\\e':'\e',
+}
+_control_char_regexp = re.compile(
+ "|".join(re.escape(k) for k in _control_char_map)
+)
if TYPE_CHECKING:
from .result import _KeyType
- from .result import RMKeyView
from .result import _ProcessorsType
+ from .result import RMKeyView
_T = TypeVar("_T", bound=Any)
_TP = TypeVar("_TP", bound=Tuple[Any, ...])
"""Indicates if the database support table comment reflection"""
return exclusions.closed()
+ @property
+ def comment_reflection_full_unicode(self):
+ """Indicates if the database support table comment reflection in the
+ full unicode range, including emoji etc.
+ """
+ return exclusions.closed()
+
@property
def constraint_comment_reflection(self):
"""indicates if the database support constraint on constraints
sa.String(20),
comment=r"""Comment types type speedily ' " \ '' Fun!""",
),
+ Column("d3", sa.String(42), comment="Comment\nwith\rescapes"),
schema=schema,
comment=r"""the test % ' " \ table comment""",
)
metadata,
Column("data", sa.String(20)),
schema=schema,
+ comment="no\nconstraints\rhas\fescaped\vcomment",
)
if testing.requires.cross_schema_fk_reflection.enabled:
(schema, "comment_test"): {
"text": r"""the test % ' " \ table comment"""
},
- (schema, "no_constraints"): empty,
+ (schema, "no_constraints"): {
+ "text": "no\nconstraints\rhas\fescaped\vcomment"
+ },
(schema, "local_table"): empty,
(schema, "remote_table"): empty,
(schema, "remote_table_2"): empty,
"d2",
comment=r"""Comment types type speedily ' " \ '' Fun!""",
),
+ col("d3", comment="Comment\nwith\rescapes"),
],
(schema, "no_constraints"): [col("data")],
(schema, "local_table"): [pk("id"), col("data"), col("remote_id")],
tables = [f"{schema}.{t}" for t in tables]
eq_(sorted(m.tables), sorted(tables))
+ @testing.requires.comment_reflection
+ def test_comments_unicode(self, connection, metadata):
+ Table(
+ "unicode_comments",
+ metadata,
+ Column("unicode", Integer, comment="é試蛇ẟΩ"),
+ Column("emoji", Integer, comment="☁️✨"),
+ comment="試蛇ẟΩ✨",
+ )
+
+ metadata.create_all(connection)
+
+ insp = inspect(connection)
+ tc = insp.get_table_comment("unicode_comments")
+ eq_(tc, {"text": "試蛇ẟΩ✨"})
+
+ cols = insp.get_columns("unicode_comments")
+ value = {c["name"]: c["comment"] for c in cols}
+ exp = {"unicode": "é試蛇ẟΩ", "emoji": "☁️✨"}
+ eq_(value, exp)
+
+ @testing.requires.comment_reflection_full_unicode
+ def test_comments_unicode_full(self, connection, metadata):
+
+ Table(
+ "unicode_comments",
+ metadata,
+ Column("emoji", Integer, comment="🐍🧙🝝🧙♂️🧙♀️"),
+ comment="🎩🁰🝑🤷♀️🤷♂️",
+ )
+
+ metadata.create_all(connection)
+
+ insp = inspect(connection)
+ tc = insp.get_table_comment("unicode_comments")
+ eq_(tc, {"text": "🎩🁰🝑🤷♀️🤷♂️"})
+ c = insp.get_columns("unicode_comments")[0]
+ eq_({c["name"]: c["comment"]}, {"emoji": "🐍🧙🝝🧙♂️🧙♀️"})
+
class TableNoColumnsTest(fixtures.TestBase):
__requires__ = ("reflect_tables_no_columns",)
},
)
+ def test_reflect_comment_escapes(self, connection, metadata):
+ c = "\\ - \\\\ - \\0 - \\a - \\b - \\t - \\n - \\v - \\f - \\r"
+ Table("t", metadata, Column("c", Integer, comment=c), comment=c)
+ metadata.create_all(connection)
+
+ insp = inspect(connection)
+ tc = insp.get_table_comment("t")
+ eq_(tc, {"text": c})
+ col = insp.get_columns("t")[0]
+ eq_({col["name"]: col["comment"]}, {"c": c})
+
+ def test_reflect_comment_unicode(self, connection, metadata):
+ c = "☁️✨🐍🁰🝝"
+ c_exp = "☁️✨???"
+ Table("t", metadata, Column("c", Integer, comment=c), comment=c)
+ metadata.create_all(connection)
+
+ insp = inspect(connection)
+ tc = insp.get_table_comment("t")
+ eq_(tc, {"text": c_exp})
+ col = insp.get_columns("t")[0]
+ eq_({col["name"]: col["comment"]}, {"c": c_exp})
+
class RawReflectionTest(fixtures.TestBase):
def setup_test(self):
def comment_reflection(self):
return only_on(["postgresql", "mysql", "mariadb", "oracle", "mssql"])
+ @property
+ def comment_reflection_full_unicode(self):
+ return only_on(["postgresql", "oracle", "mssql"])
+
@property
def constraint_comment_reflection(self):
return only_on(["postgresql"])