Skip to content

Commit 0abb379

Browse files
authored
🦺 enhance handling of MySQL string literals and default values (#115)
1 parent d973233 commit 0abb379

File tree

2 files changed

+32
-1
lines changed

2 files changed

+32
-1
lines changed

src/mysql_to_sqlite3/transporter.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,20 @@ def _transpile_mysql_expr_to_sqlite(cls, expr_sql: str) -> t.Optional[str]:
324324
)
325325
return None
326326

327+
@classmethod
328+
def _normalize_literal_with_sqlglot(cls, expr_sql: str) -> t.Optional[str]:
329+
"""Normalize a MySQL literal using sqlglot, returning SQLite SQL if literal-like."""
330+
cleaned: str = expr_sql.strip().rstrip(";")
331+
try:
332+
node: Expression = parse_one(cleaned, read="mysql")
333+
except (ParseError, ValueError):
334+
return None
335+
if isinstance(node, exp.Literal):
336+
return node.sql(dialect="sqlite")
337+
if isinstance(node, exp.Paren) and isinstance(node.this, exp.Literal):
338+
return node.this.sql(dialect="sqlite")
339+
return None
340+
327341
@staticmethod
328342
def _quote_sqlite_identifier(name: t.Union[str, bytes, bytearray]) -> str:
329343
"""Safely quote an identifier for SQLite using sqlglot.
@@ -569,7 +583,15 @@ def _translate_default_from_mysql_to_sqlite(
569583
# Allow simple arithmetic constant expressions composed of numbers and + - * /
570584
if re.match(r"^[\d\.\s\+\-\*/\(\)]+$", norm) and any(ch.isdigit() for ch in norm):
571585
return f"DEFAULT {norm}"
572-
# Robustly escape single quotes for plain string defaults
586+
stripped_default = column_default.strip()
587+
if stripped_default.startswith("'") or (
588+
stripped_default.startswith("(") and stripped_default.endswith(")")
589+
):
590+
normalized_literal: t.Optional[str] = cls._normalize_literal_with_sqlglot(column_default)
591+
if normalized_literal is not None:
592+
return f"DEFAULT {normalized_literal}"
593+
594+
# Fallback: robustly escape single quotes for plain string defaults
573595
_escaped = column_default.replace("\\'", "'")
574596
_escaped = _escaped.replace("'", "''")
575597
return f"DEFAULT '{_escaped}'"

tests/unit/test_types_and_defaults_extra.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,12 @@ def test_translate_default_bool_boolean_type(self, monkeypatch: pytest.MonkeyPat
8484
monkeypatch.setattr(sqlite3, "sqlite_version", "3.40.0")
8585
assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite(True, column_type="BOOLEAN") == "DEFAULT(TRUE)"
8686
assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite(False, column_type="BOOLEAN") == "DEFAULT(FALSE)"
87+
88+
def test_translate_default_prequoted_string_literal(self) -> None:
89+
# MariaDB can report TEXT defaults already wrapped in single quotes; ensure they're normalized
90+
assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("'[]'") == "DEFAULT '[]'"
91+
assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("'It''s'") == "DEFAULT 'It''s'"
92+
assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("'a\\'b'") == "DEFAULT 'a''b'"
93+
assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("''") == "DEFAULT ''"
94+
assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("('value')") == "DEFAULT 'value'"
95+
assert MySQLtoSQLite._translate_default_from_mysql_to_sqlite("'tab\\there'") == "DEFAULT 'tab\there'"

0 commit comments

Comments
 (0)