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}'" diff --git a/tests/unit/test_types_and_defaults_extra.py b/tests/unit/test_types_and_defaults_extra.py index ee7b74c..a84bd3e 100644 --- a/tests/unit/test_types_and_defaults_extra.py +++ b/tests/unit/test_types_and_defaults_extra.py @@ -84,3 +84,12 @@ 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'" + 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'"