From d825d354e8764f308c8f57c7ec3c95b90f7129b0 Mon Sep 17 00:00:00 2001 From: codeAnqiang-ma <273298913+codeAnqiang-ma@users.noreply.github.com> Date: Thu, 13 Aug 2026 01:37:12 +0800 Subject: [PATCH 1/3] fix(mysql): map %v, %x and %r date format specifiers Co-authored-by: Cursor --- sqlglot/dialects/mysql.py | 3 +++ tests/dialects/test_mysql.py | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/sqlglot/dialects/mysql.py b/sqlglot/dialects/mysql.py index 1936f8b4ad..675fbc4a04 100644 --- a/sqlglot/dialects/mysql.py +++ b/sqlglot/dialects/mysql.py @@ -44,8 +44,11 @@ class MySQL(Dialect): "%u": "%W", "%k": "%-H", "%l": "%-I", + "%r": "%I:%M:%S %p", "%T": "%H:%M:%S", + "%v": "%V", "%W": "%A", + "%x": "%G", } VALID_INTERVAL_UNITS = { diff --git a/tests/dialects/test_mysql.py b/tests/dialects/test_mysql.py index 4dc656d17a..9481b34c22 100644 --- a/tests/dialects/test_mysql.py +++ b/tests/dialects/test_mysql.py @@ -818,6 +818,26 @@ def test_date_format(self): "snowflake": "SELECT TO_CHAR(CAST('1900-10-04 22:23:00' AS TIMESTAMP), 'DD yy DY DD mm mon')", }, ) + self.validate_all( + "SELECT DATE_FORMAT('2021-01-01 22:23:00', '%x-%v')", + write={ + "mysql": "SELECT DATE_FORMAT('2021-01-01 22:23:00', '%x-%v')", + "duckdb": "SELECT STRFTIME(CAST('2021-01-01 22:23:00' AS TIMESTAMP), '%G-%V')", + }, + ) + self.validate_all( + "SELECT DATE_FORMAT(CAST('2021-01-01 22:23:00' AS DATETIME), '%x-%v')", + read={ + "duckdb": "SELECT STRFTIME(CAST('2021-01-01 22:23:00' AS TIMESTAMP), '%G-%V')", + }, + ) + self.validate_all( + "SELECT DATE_FORMAT('2007-10-04 22:23:00', '%r')", + write={ + "mysql": "SELECT DATE_FORMAT('2007-10-04 22:23:00', '%r')", + "duckdb": "SELECT STRFTIME(CAST('2007-10-04 22:23:00' AS TIMESTAMP), '%I:%M:%S %p')", + }, + ) def test_mysql_time(self): self.validate_identity("TIME_STR_TO_UNIX(x)", "UNIX_TIMESTAMP(x)") From 361b6c55c52144c74f53a38166e4ad6352fd17a0 Mon Sep 17 00:00:00 2001 From: codeAnqiang-ma <273298913+codeAnqiang-ma@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:05:37 +0800 Subject: [PATCH 2/3] fix(mysql): drop %v mapping to keep native %V intact on generation The inverse mapping derived from TIME_MAPPING rewrote MySQL's %V (Sunday-based week) into %v on generation, so DATE_FORMAT(x, '%X-%V') did not round-trip (Presto/Trino included, via the shared mapping). MySQL has four week specifiers (%U, %u, %V, %v) but strftime only has three (%U, %W, %V), so a lossless %v mapping is not possible; keep the collision-free %x and %r entries and add round-trip regression tests. Co-authored-by: Cursor --- sqlglot/dialects/mysql.py | 4 +++- tests/dialects/test_mysql.py | 15 ++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/sqlglot/dialects/mysql.py b/sqlglot/dialects/mysql.py index 675fbc4a04..84a233d001 100644 --- a/sqlglot/dialects/mysql.py +++ b/sqlglot/dialects/mysql.py @@ -46,9 +46,11 @@ class MySQL(Dialect): "%l": "%-I", "%r": "%I:%M:%S %p", "%T": "%H:%M:%S", - "%v": "%V", "%W": "%A", "%x": "%G", + # %v (ISO week) is intentionally unmapped: its strftime equivalent %V is also a + # native MySQL specifier (Sunday-based week), so the derived INVERSE_TIME_MAPPING + # would rewrite %V to %v on generation, silently changing week numbering } VALID_INTERVAL_UNITS = { diff --git a/tests/dialects/test_mysql.py b/tests/dialects/test_mysql.py index 9481b34c22..ede61c3e9f 100644 --- a/tests/dialects/test_mysql.py +++ b/tests/dialects/test_mysql.py @@ -819,16 +819,16 @@ def test_date_format(self): }, ) self.validate_all( - "SELECT DATE_FORMAT('2021-01-01 22:23:00', '%x-%v')", + "SELECT DATE_FORMAT('2021-01-01 22:23:00', '%x')", write={ - "mysql": "SELECT DATE_FORMAT('2021-01-01 22:23:00', '%x-%v')", - "duckdb": "SELECT STRFTIME(CAST('2021-01-01 22:23:00' AS TIMESTAMP), '%G-%V')", + "mysql": "SELECT DATE_FORMAT('2021-01-01 22:23:00', '%x')", + "duckdb": "SELECT STRFTIME(CAST('2021-01-01 22:23:00' AS TIMESTAMP), '%G')", }, ) self.validate_all( - "SELECT DATE_FORMAT(CAST('2021-01-01 22:23:00' AS DATETIME), '%x-%v')", + "SELECT DATE_FORMAT(CAST('2021-01-01 22:23:00' AS DATETIME), '%x')", read={ - "duckdb": "SELECT STRFTIME(CAST('2021-01-01 22:23:00' AS TIMESTAMP), '%G-%V')", + "duckdb": "SELECT STRFTIME(CAST('2021-01-01 22:23:00' AS TIMESTAMP), '%G')", }, ) self.validate_all( @@ -838,6 +838,11 @@ def test_date_format(self): "duckdb": "SELECT STRFTIME(CAST('2007-10-04 22:23:00' AS TIMESTAMP), '%I:%M:%S %p')", }, ) + # Native week/year specifiers must survive a MySQL round-trip untouched: + # %V/%X (Sunday-based) are distinct from %v/%x (ISO) and must not be rewritten + self.validate_identity("SELECT DATE_FORMAT(x, '%X-%V')") + self.validate_identity("SELECT DATE_FORMAT(x, '%x-%v')") + self.validate_identity("SELECT DATE_FORMAT(x, '%U')") def test_mysql_time(self): self.validate_identity("TIME_STR_TO_UNIX(x)", "UNIX_TIMESTAMP(x)") From 2f4654303835124f187021040a5deb910cf02a96 Mon Sep 17 00:00:00 2001 From: fivetran-kwoodbeck Date: Thu, 13 Aug 2026 13:35:28 -0400 Subject: [PATCH 3/3] ajusted comments --- sqlglot/dialects/mysql.py | 4 +--- tests/dialects/test_mysql.py | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/sqlglot/dialects/mysql.py b/sqlglot/dialects/mysql.py index 84a233d001..1d0d4b49ca 100644 --- a/sqlglot/dialects/mysql.py +++ b/sqlglot/dialects/mysql.py @@ -48,9 +48,7 @@ class MySQL(Dialect): "%T": "%H:%M:%S", "%W": "%A", "%x": "%G", - # %v (ISO week) is intentionally unmapped: its strftime equivalent %V is also a - # native MySQL specifier (Sunday-based week), so the derived INVERSE_TIME_MAPPING - # would rewrite %V to %v on generation, silently changing week numbering + # %v (ISO week) is unmapped due to collision with %V (roundtrip issue) } VALID_INTERVAL_UNITS = { diff --git a/tests/dialects/test_mysql.py b/tests/dialects/test_mysql.py index ede61c3e9f..fd7780e716 100644 --- a/tests/dialects/test_mysql.py +++ b/tests/dialects/test_mysql.py @@ -838,8 +838,6 @@ def test_date_format(self): "duckdb": "SELECT STRFTIME(CAST('2007-10-04 22:23:00' AS TIMESTAMP), '%I:%M:%S %p')", }, ) - # Native week/year specifiers must survive a MySQL round-trip untouched: - # %V/%X (Sunday-based) are distinct from %v/%x (ISO) and must not be rewritten self.validate_identity("SELECT DATE_FORMAT(x, '%X-%V')") self.validate_identity("SELECT DATE_FORMAT(x, '%x-%v')") self.validate_identity("SELECT DATE_FORMAT(x, '%U')")