Skip to content

Commit 2df6eb1

Browse files
zzzeekGerrit Code Review
authored andcommitted
Merge "include negation symbol in numeric default match"
2 parents 33b1275 + cdf1ba9 commit 2df6eb1

File tree

3 files changed

+56
-90
lines changed

3 files changed

+56
-90
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.. change::
2+
:tags: bug, mysql, reflection
3+
:tickets: 5860
4+
5+
Fixed bug where MySQL server default reflection would fail for numeric
6+
values with a negation symbol present.
7+

lib/sqlalchemy/dialects/mysql/reflection.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,8 +381,8 @@ def _prep_regexes(self):
381381
r"(?: +COLLATE +(?P<collate>[\w_]+))?"
382382
r"(?: +(?P<notnull>(?:NOT )?NULL))?"
383383
r"(?: +DEFAULT +(?P<default>"
384-
r"(?:NULL|'(?:''|[^'])*'|[\w\.\(\)]+"
385-
r"(?: +ON UPDATE [\w\.\(\)]+)?)"
384+
r"(?:NULL|'(?:''|[^'])*'|[\-\w\.\(\)]+"
385+
r"(?: +ON UPDATE [\-\w\.\(\)]+)?)"
386386
r"))?"
387387
r"(?: +(?:GENERATED ALWAYS)? ?AS +(?P<generated>\("
388388
r".*\))? ?(?P<persistence>VIRTUAL|STORED)?)?"

test/dialect/mysql/test_reflection.py

Lines changed: 47 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from sqlalchemy import LargeBinary
1717
from sqlalchemy import MetaData
1818
from sqlalchemy import NCHAR
19+
from sqlalchemy import Numeric
1920
from sqlalchemy import select
2021
from sqlalchemy import SmallInteger
2122
from sqlalchemy import sql
@@ -24,6 +25,7 @@
2425
from sqlalchemy import testing
2526
from sqlalchemy import Text
2627
from sqlalchemy import TIMESTAMP
28+
from sqlalchemy import types
2729
from sqlalchemy import Unicode
2830
from sqlalchemy import UnicodeText
2931
from sqlalchemy import UniqueConstraint
@@ -249,98 +251,55 @@ class ReflectionTest(fixtures.TestBase, AssertsCompiledSQL):
249251
__only_on__ = "mysql", "mariadb"
250252
__backend__ = True
251253

252-
def test_default_reflection(self):
253-
"""Test reflection of column defaults."""
254-
255-
# TODO: this test is a mess. should be broken into individual
256-
# combinations
257-
258-
from sqlalchemy.dialects.mysql import VARCHAR
259-
260-
def_table = Table(
261-
"mysql_def",
262-
MetaData(),
263-
Column(
264-
"c1",
265-
VARCHAR(10, collation="utf8_unicode_ci"),
266-
DefaultClause(""),
267-
nullable=False,
254+
@testing.combinations(
255+
(
256+
mysql.VARCHAR(10, collation="utf8_unicode_ci"),
257+
DefaultClause(""),
258+
"''",
259+
),
260+
(String(10), DefaultClause("abc"), "'abc'"),
261+
(String(10), DefaultClause("0"), "'0'"),
262+
(
263+
TIMESTAMP,
264+
DefaultClause("2009-04-05 12:00:00"),
265+
"'2009-04-05 12:00:00'",
266+
),
267+
(TIMESTAMP, None, None),
268+
(
269+
TIMESTAMP,
270+
DefaultClause(
271+
sql.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
268272
),
269-
Column("c2", String(10), DefaultClause("0")),
270-
Column("c3", String(10), DefaultClause("abc")),
271-
Column("c4", TIMESTAMP, DefaultClause("2009-04-05 12:00:00")),
272-
Column("c5", TIMESTAMP),
273-
Column(
274-
"c6",
275-
TIMESTAMP,
276-
DefaultClause(
277-
sql.text(
278-
"CURRENT_TIMESTAMP " "ON UPDATE CURRENT_TIMESTAMP"
279-
)
280-
),
273+
re.compile(
274+
r"CURRENT_TIMESTAMP(\(\))? ON UPDATE CURRENT_TIMESTAMP(\(\))?",
275+
re.I,
281276
),
282-
Column("c7", mysql.DOUBLE(), DefaultClause("0.0000")),
283-
Column("c8", mysql.DOUBLE(22, 6), DefaultClause("0.0000")),
284-
)
277+
),
278+
(mysql.DOUBLE(), DefaultClause("0.0000"), "0"),
279+
(mysql.DOUBLE(22, 6), DefaultClause("0.0000"), "0.000000"),
280+
(Integer, DefaultClause("1"), "1"),
281+
(Integer, DefaultClause("-1"), "-1"),
282+
(mysql.DOUBLE, DefaultClause("-25.03"), "-25.03"),
283+
(mysql.DOUBLE, DefaultClause("-.001"), "-0.001"),
284+
argnames="datatype, default, expected",
285+
)
286+
def test_default_reflection(
287+
self, datatype, default, expected, metadata, connection
288+
):
289+
t1 = Table("t1", metadata, Column("x", datatype, default))
290+
t1.create(connection)
291+
insp = inspect(connection)
285292

286-
def_table.create(testing.db)
287-
try:
288-
reflected = Table(
289-
"mysql_def", MetaData(), autoload_with=testing.db
290-
)
291-
finally:
292-
def_table.drop(testing.db)
293-
assert def_table.c.c1.server_default.arg == ""
294-
assert def_table.c.c2.server_default.arg == "0"
295-
assert def_table.c.c3.server_default.arg == "abc"
296-
assert def_table.c.c4.server_default.arg == "2009-04-05 12:00:00"
297-
assert str(reflected.c.c1.server_default.arg) == "''"
298-
assert str(reflected.c.c2.server_default.arg) == "'0'"
299-
assert str(reflected.c.c3.server_default.arg) == "'abc'"
300-
assert (
301-
str(reflected.c.c4.server_default.arg) == "'2009-04-05 12:00:00'"
302-
)
303-
assert reflected.c.c5.default is None
304-
assert reflected.c.c5.server_default is None
305-
assert reflected.c.c6.default is None
306-
assert str(reflected.c.c7.server_default.arg) in ("0", "'0'")
307-
308-
# this is because the numeric is 6 decimal places, MySQL
309-
# formats it to that many places.
310-
assert str(reflected.c.c8.server_default.arg) in (
311-
"0.000000",
312-
"'0.000000'",
313-
)
293+
datatype_inst = types.to_instance(datatype)
314294

315-
assert re.match(
316-
r"CURRENT_TIMESTAMP(\(\))? ON UPDATE CURRENT_TIMESTAMP(\(\))?",
317-
str(reflected.c.c6.server_default.arg).upper(),
318-
)
319-
reflected.create(testing.db)
320-
try:
321-
reflected2 = Table(
322-
"mysql_def", MetaData(), autoload_with=testing.db
323-
)
324-
finally:
325-
reflected.drop(testing.db)
326-
assert str(reflected2.c.c1.server_default.arg) == "''"
327-
assert str(reflected2.c.c2.server_default.arg) == "'0'"
328-
assert str(reflected2.c.c3.server_default.arg) == "'abc'"
329-
assert (
330-
str(reflected2.c.c4.server_default.arg) == "'2009-04-05 12:00:00'"
331-
)
332-
assert reflected.c.c5.default is None
333-
assert reflected.c.c5.server_default is None
334-
assert reflected.c.c6.default is None
335-
assert str(reflected.c.c7.server_default.arg) in ("0", "'0'")
336-
assert str(reflected.c.c8.server_default.arg) in (
337-
"0.000000",
338-
"'0.000000'",
339-
)
340-
assert re.match(
341-
r"CURRENT_TIMESTAMP(\(\))? ON UPDATE CURRENT_TIMESTAMP(\(\))?",
342-
str(reflected.c.c6.server_default.arg).upper(),
343-
)
295+
col = insp.get_columns("t1")[0]
296+
if hasattr(expected, "match"):
297+
assert expected.match(col["default"])
298+
elif isinstance(datatype_inst, (Integer, Numeric)):
299+
pattern = re.compile(r"\'?%s\'?" % expected)
300+
assert pattern.match(col["default"])
301+
else:
302+
eq_(col["default"], expected)
344303

345304
def test_reflection_with_table_options(self, metadata, connection):
346305
comment = r"""Comment types type speedily ' " \ '' Fun!"""

0 commit comments

Comments
 (0)