Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/mysql_to_sqlite3/transporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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}'"
Expand Down
9 changes: 9 additions & 0 deletions tests/unit/test_types_and_defaults_extra.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'"