From 3363881988ea518972a5f41d92384ef8ebdb3230 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 1 Nov 2025 08:46:46 +0000 Subject: [PATCH 1/3] :safety_vest: add normalization for MySQL literals using sqlglot in MySQLtoSQLite class --- src/mysql_to_sqlite3/transporter.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/mysql_to_sqlite3/transporter.py b/src/mysql_to_sqlite3/transporter.py index 8ce806d..fb5400e 100644 --- a/src/mysql_to_sqlite3/transporter.py +++ b/src/mysql_to_sqlite3/transporter.py @@ -324,6 +324,20 @@ def _transpile_mysql_expr_to_sqlite(cls, expr_sql: str) -> t.Optional[str]: ) return None + @classmethod + def _normalize_literal_with_sqlglot(cls, expr_sql: str) -> t.Optional[str]: + """Normalize a MySQL literal using sqlglot, returning SQLite SQL if literal-like.""" + cleaned: str = expr_sql.strip().rstrip(";") + try: + node: Expression = parse_one(cleaned, read="mysql") + except (ParseError, ValueError): + return None + if isinstance(node, exp.Literal): + return node.sql(dialect="sqlite") + if isinstance(node, exp.Paren) and isinstance(node.this, exp.Literal): + return node.this.sql(dialect="sqlite") + return None + @staticmethod def _quote_sqlite_identifier(name: t.Union[str, bytes, bytearray]) -> str: """Safely quote an identifier for SQLite using sqlglot. @@ -569,7 +583,15 @@ def _translate_default_from_mysql_to_sqlite( # Allow simple arithmetic constant expressions composed of numbers and + - * / if re.match(r"^[\d\.\s\+\-\*/\(\)]+$", norm) and any(ch.isdigit() for ch in norm): return f"DEFAULT {norm}" - # Robustly escape single quotes for plain string defaults + stripped_default = column_default.strip() + if stripped_default.startswith("'") or ( + stripped_default.startswith("(") and stripped_default.endswith(")") + ): + normalized_literal: t.Optional[str] = cls._normalize_literal_with_sqlglot(column_default) + if normalized_literal is not None: + return f"DEFAULT {normalized_literal}" + + # Fallback: robustly escape single quotes for plain string defaults _escaped = column_default.replace("\\'", "'") _escaped = _escaped.replace("'", "''") return f"DEFAULT '{_escaped}'" From 46d2c35032f8f72eeb831b8fb0f0eb4968ad066b Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 1 Nov 2025 08:47:06 +0000 Subject: [PATCH 2/3] :white_check_mark: add normalization for prequoted string literals in default values --- tests/unit/test_types_and_defaults_extra.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit/test_types_and_defaults_extra.py b/tests/unit/test_types_and_defaults_extra.py index ee7b74c..89ffb8d 100644 --- a/tests/unit/test_types_and_defaults_extra.py +++ b/tests/unit/test_types_and_defaults_extra.py @@ -84,3 +84,9 @@ def test_translate_default_bool_boolean_type(self, monkeypatch: pytest.MonkeyPat monkeypatch.setattr(sqlite3, "sqlite_version", "3.40.0") assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite(True, column_type="BOOLEAN") == "DEFAULT(TRUE)" assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite(False, column_type="BOOLEAN") == "DEFAULT(FALSE)" + + def test_translate_default_prequoted_string_literal(self) -> None: + # MariaDB can report TEXT defaults already wrapped in single quotes; ensure they're normalized + assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("'[]'") == "DEFAULT '[]'" + assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("'It''s'") == "DEFAULT 'It''s'" + assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("'a\\'b'") == "DEFAULT 'a''b'" From 6057781532c8da0c75ffa224d08bb1350be5d60d Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 1 Nov 2025 09:11:32 +0000 Subject: [PATCH 3/3] :white_check_mark: add tests for translating default prequoted string literals in MySQLtoSQLite --- tests/unit/test_types_and_defaults_extra.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/test_types_and_defaults_extra.py b/tests/unit/test_types_and_defaults_extra.py index 89ffb8d..a84bd3e 100644 --- a/tests/unit/test_types_and_defaults_extra.py +++ b/tests/unit/test_types_and_defaults_extra.py @@ -90,3 +90,6 @@ def test_translate_default_prequoted_string_literal(self) -> None: assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("'[]'") == "DEFAULT '[]'" assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("'It''s'") == "DEFAULT 'It''s'" assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("'a\\'b'") == "DEFAULT 'a''b'" + assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("''") == "DEFAULT ''" + assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("('value')") == "DEFAULT 'value'" + assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("'tab\\there'") == "DEFAULT 'tab\there'"