]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
include negation symbol in numeric default match
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 23 Jan 2021 08:00:56 +0000 (03:00 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 23 Jan 2021 14:59:12 +0000 (09:59 -0500)
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)

doc/build/changelog/unreleased_13/5860.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/mysql/reflection.py
test/dialect/mysql/test_reflection.py

diff --git a/doc/build/changelog/unreleased_13/5860.rst b/doc/build/changelog/unreleased_13/5860.rst
new file mode 100644 (file)
index 0000000..f2897ed
--- /dev/null
@@ -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.
+
index 34c2b204e99df8cb009305ca3ceb48125059aa1e..40325f946071fa366e10234dc178464358e26b37 100644 (file)
@@ -381,8 +381,8 @@ class MySQLTableDefinitionParser(object):
             r"(?: +COLLATE +(?P<collate>[\w_]+))?"
             r"(?: +(?P<notnull>(?:NOT )?NULL))?"
             r"(?: +DEFAULT +(?P<default>"
-            r"(?:NULL|'(?:''|[^'])*'|[\w\.\(\)]+"
-            r"(?: +ON UPDATE [\w\.\(\)]+)?)"
+            r"(?:NULL|'(?:''|[^'])*'|[\-\w\.\(\)]+"
+            r"(?: +ON UPDATE [\-\w\.\(\)]+)?)"
             r"))?"
             r"(?: +(?:GENERATED ALWAYS)? ?AS +(?P<generated>\("
             r".*\))? ?(?P<persistence>VIRTUAL|STORED)?)?"
index b11ecd48972fbd914c473e6b38a7768150620950..276de0a60124ea780e4ad1364fd06f5a3881b9d3 100644 (file)
@@ -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!"""