From: Mike Bayer Date: Sat, 23 Jan 2021 08:00:56 +0000 (-0500) Subject: include negation symbol in numeric default match X-Git-Tag: rel_1_3_23~9^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a31266f4d790928d2ca80b856ab409e5d1912690;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git include negation symbol in numeric default match Fixed bug where MySQL server default reflection would fail for numeric values with a negation symbol present. Fixes: #5860 Change-Id: I02cacdb4f9ec7d9113c27bf0d9214c4e14f4d4f9 (cherry picked from commit 33886b8692fc9a9cf862386a541d15114a659f43) --- diff --git a/doc/build/changelog/unreleased_13/5860.rst b/doc/build/changelog/unreleased_13/5860.rst new file mode 100644 index 0000000000..f2897ed87f --- /dev/null +++ b/doc/build/changelog/unreleased_13/5860.rst @@ -0,0 +1,7 @@ +.. change:: + :tags: bug, mysql, reflection + :tickets: 5860 + + Fixed bug where MySQL server default reflection would fail for numeric + values with a negation symbol present. + diff --git a/lib/sqlalchemy/dialects/mysql/reflection.py b/lib/sqlalchemy/dialects/mysql/reflection.py index 34c2b204e9..40325f9460 100644 --- a/lib/sqlalchemy/dialects/mysql/reflection.py +++ b/lib/sqlalchemy/dialects/mysql/reflection.py @@ -381,8 +381,8 @@ class MySQLTableDefinitionParser(object): r"(?: +COLLATE +(?P[\w_]+))?" r"(?: +(?P(?:NOT )?NULL))?" r"(?: +DEFAULT +(?P" - r"(?:NULL|'(?:''|[^'])*'|[\w\.\(\)]+" - r"(?: +ON UPDATE [\w\.\(\)]+)?)" + r"(?:NULL|'(?:''|[^'])*'|[\-\w\.\(\)]+" + r"(?: +ON UPDATE [\-\w\.\(\)]+)?)" r"))?" r"(?: +(?:GENERATED ALWAYS)? ?AS +(?P\(" r".*\))? ?(?PVIRTUAL|STORED)?)?" diff --git a/test/dialect/mysql/test_reflection.py b/test/dialect/mysql/test_reflection.py index b11ecd4897..276de0a601 100644 --- a/test/dialect/mysql/test_reflection.py +++ b/test/dialect/mysql/test_reflection.py @@ -16,6 +16,7 @@ from sqlalchemy import Integer from sqlalchemy import LargeBinary from sqlalchemy import MetaData from sqlalchemy import NCHAR +from sqlalchemy import Numeric from sqlalchemy import select from sqlalchemy import SmallInteger from sqlalchemy import sql @@ -24,6 +25,7 @@ from sqlalchemy import Table from sqlalchemy import testing from sqlalchemy import Text from sqlalchemy import TIMESTAMP +from sqlalchemy import types from sqlalchemy import Unicode from sqlalchemy import UnicodeText from sqlalchemy import UniqueConstraint @@ -232,95 +234,55 @@ class ReflectionTest(fixtures.TestBase, AssertsCompiledSQL): __only_on__ = "mysql" __backend__ = True - def test_default_reflection(self): - """Test reflection of column defaults.""" - - # TODO: this test is a mess. should be broken into individual - # combinations - - from sqlalchemy.dialects.mysql import VARCHAR - - def_table = Table( - "mysql_def", - MetaData(testing.db), - Column( - "c1", - VARCHAR(10, collation="utf8_unicode_ci"), - DefaultClause(""), - nullable=False, + @testing.combinations( + ( + mysql.VARCHAR(10, collation="utf8_unicode_ci"), + DefaultClause(""), + "''", + ), + (String(10), DefaultClause("abc"), "'abc'"), + (String(10), DefaultClause("0"), "'0'"), + ( + TIMESTAMP, + DefaultClause("2009-04-05 12:00:00"), + "'2009-04-05 12:00:00'", + ), + (TIMESTAMP, None, None), + ( + TIMESTAMP, + DefaultClause( + sql.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") ), - Column("c2", String(10), DefaultClause("0")), - Column("c3", String(10), DefaultClause("abc")), - Column("c4", TIMESTAMP, DefaultClause("2009-04-05 12:00:00")), - Column("c5", TIMESTAMP), - Column( - "c6", - TIMESTAMP, - DefaultClause( - sql.text( - "CURRENT_TIMESTAMP " "ON UPDATE CURRENT_TIMESTAMP" - ) - ), + re.compile( + r"CURRENT_TIMESTAMP(\(\))? ON UPDATE CURRENT_TIMESTAMP(\(\))?", + re.I, ), - Column("c7", mysql.DOUBLE(), DefaultClause("0.0000")), - Column("c8", mysql.DOUBLE(22, 6), DefaultClause("0.0000")), - ) - def_table.create() - try: - reflected = Table("mysql_def", MetaData(testing.db), autoload=True) - finally: - def_table.drop() - assert def_table.c.c1.server_default.arg == "" - assert def_table.c.c2.server_default.arg == "0" - assert def_table.c.c3.server_default.arg == "abc" - assert def_table.c.c4.server_default.arg == "2009-04-05 12:00:00" - assert str(reflected.c.c1.server_default.arg) == "''" - assert str(reflected.c.c2.server_default.arg) == "'0'" - assert str(reflected.c.c3.server_default.arg) == "'abc'" - assert ( - str(reflected.c.c4.server_default.arg) == "'2009-04-05 12:00:00'" - ) - assert reflected.c.c5.default is None - assert reflected.c.c5.server_default is None - assert reflected.c.c6.default is None - assert str(reflected.c.c7.server_default.arg) in ("0", "'0'") - - # this is because the numeric is 6 decimal places, MySQL - # formats it to that many places. - assert str(reflected.c.c8.server_default.arg) in ( - "0.000000", - "'0.000000'", - ) - - assert re.match( - r"CURRENT_TIMESTAMP(\(\))? ON UPDATE CURRENT_TIMESTAMP(\(\))?", - str(reflected.c.c6.server_default.arg).upper(), - ) - reflected.create() - try: - reflected2 = Table( - "mysql_def", MetaData(testing.db), autoload=True - ) - finally: - reflected.drop() - assert str(reflected2.c.c1.server_default.arg) == "''" - assert str(reflected2.c.c2.server_default.arg) == "'0'" - assert str(reflected2.c.c3.server_default.arg) == "'abc'" - assert ( - str(reflected2.c.c4.server_default.arg) == "'2009-04-05 12:00:00'" - ) - assert reflected.c.c5.default is None - assert reflected.c.c5.server_default is None - assert reflected.c.c6.default is None - assert str(reflected.c.c7.server_default.arg) in ("0", "'0'") - assert str(reflected.c.c8.server_default.arg) in ( - "0.000000", - "'0.000000'", - ) - assert re.match( - r"CURRENT_TIMESTAMP(\(\))? ON UPDATE CURRENT_TIMESTAMP(\(\))?", - str(reflected.c.c6.server_default.arg).upper(), - ) + ), + (mysql.DOUBLE(), DefaultClause("0.0000"), "0"), + (mysql.DOUBLE(22, 6), DefaultClause("0.0000"), "0.000000"), + (Integer, DefaultClause("1"), "1"), + (Integer, DefaultClause("-1"), "-1"), + (mysql.DOUBLE, DefaultClause("-25.03"), "-25.03"), + (mysql.DOUBLE, DefaultClause("-.001"), "-0.001"), + argnames="datatype, default, expected", + ) + @testing.provide_metadata + def test_default_reflection(self, datatype, default, expected, connection): + metadata = self.metadata + t1 = Table("t1", metadata, Column("x", datatype, default)) + t1.create(connection) + insp = inspect(connection) + + datatype_inst = types.to_instance(datatype) + + col = insp.get_columns("t1")[0] + if hasattr(expected, "match"): + assert expected.match(col["default"]) + elif isinstance(datatype_inst, (Integer, Numeric)): + pattern = re.compile(r"\'?%s\'?" % expected) + assert pattern.match(col["default"]) + else: + eq_(col["default"], expected) def test_reflection_with_table_options(self): comment = r"""Comment types type speedily ' " \ '' Fun!"""