Skip to content

Conversation

@techouse
Copy link
Owner

@techouse techouse commented Sep 15, 2025

This pull request significantly improves the handling of default column values during SQLite-to-MySQL schema migration, especially for date/time and boolean types, and enhances compatibility with various MySQL and MariaDB versions. The changes introduce robust translation of SQLite default expressions into MySQL-compatible defaults, with careful version checks for advanced features. Comprehensive tests ensure the new logic is correct and reliable.

Default value translation and version-aware feature support:

  • Added new utility functions (check_mysql_expression_defaults_support, check_mysql_current_timestamp_datetime_support, check_mysql_fractional_seconds_support) to detect support for expression defaults, CURRENT_TIMESTAMP for DATETIME, and fractional seconds in MySQL/MariaDB, with corresponding attributes in SQLite3toMySQLAttributes. [1] [2]
  • Updated the transporter to check these features at initialization and use them when generating MySQL schema, ensuring only supported syntax is emitted for the target database version.

Improved default value handling in schema translation:

  • Implemented _translate_default_for_mysql in SQLite3toMySQL to convert SQLite default expressions (including CURRENT_TIMESTAMP, datetime('now'), booleans, numeric literals, and quoted strings) into the correct MySQL syntax, with logic to handle version-specific support and fallback behavior.
  • Modified table creation logic to use the improved translation for default values, ensuring accurate and compatible schema generation.

Regex and parsing enhancements:

  • Added new regular expressions to robustly match and process SQLite expressions and MySQL default value syntaxes, including a utility to strip fully wrapping parentheses from expressions.

Comprehensive testing:

  • Added thorough unit tests for the new utility functions, default value translation logic, and parentheses stripping, covering a wide range of edge cases and MySQL/MariaDB versions. [1] [2]

Imports and code organization:

  • Updated imports throughout the codebase to include new utility functions where needed. [1] [2]

Fixes:

@techouse techouse self-assigned this Sep 15, 2025
@techouse techouse added the enhancement New feature or request label Sep 15, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 15, 2025

Walkthrough

Adds MySQL/MariaDB feature-detection helpers and runtime flags; implements utilities to strip fully wrapping parentheses and translate SQLite DEFAULT expressions to MySQL-compatible defaults (CURRENT_*, numerics, booleans, strings, hex, expression handling with fractional-seconds support); integrates translation into table creation and adjusts column-length signature.

Changes

Cohort / File(s) Summary
Default translation utilities and integration
src/sqlite3_to_mysql/transporter.py
Added class-level regexes and helpers; added static _strip_wrapping_parentheses(expr: str); added _translate_default_for_mysql(self, column_type: str, default: str) to map SQLite DEFAULT expressions to MySQL (NULL, CURRENT_TIMESTAMP/DATE/TIME including strftime variants and optional fractional seconds, booleans, numerics, quoted/hex literals, expression fallbacks); integrated translator into table creation to emit DEFAULT clauses; updated _column_type_length(cls, column_type: str, default: t.Optional[t.Union[str,int,float]] = None) signature; added runtime flags.
MySQL/MariaDB feature detection
src/sqlite3_to_mysql/mysql_utils.py
Added check_mysql_expression_defaults_support(version_string: str), check_mysql_current_timestamp_datetime_support(version_string: str), and check_mysql_fractional_seconds_support(version_string: str) that parse version strings (including MariaDB detection) and return boolean support flags.
Runtime attributes
src/sqlite3_to_mysql/types.py
Added private boolean attributes _allow_expr_defaults, _allow_current_ts_dt, and _allow_fsp to SQLite3toMySQLAttributes.
Unit tests
tests/unit/mysql_utils_test.py, tests/unit/sqlite3_to_mysql_test.py
Added tests for the three new mysql_utils checkers (MySQL and MariaDB cases) and extensive tests for _strip_wrapping_parentheses and _translate_default_for_mysql, covering feature-flag permutations, datetime/timestamp/time precision handling, boolean and literal passthroughs, and expression stripping.
Functional tests metadata
tests/func/test_cli.py
Marked two CLI functional tests as expected failures with @pytest.mark.xfail: test_no_database_name and test_no_database_user.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant CLI as Caller
  participant T as SQLite3toMySQL
  participant C as ColumnSpec
  participant U as mysql_utils

  CLI->>T: create_table(...)
  T->>C: read type, dflt_value
  T->>U: check_mysql_*_support(version)
  Note right of T #DDEBF7: set runtime flags\n(_allow_expr_defaults, _allow_current_ts_dt, _allow_fsp)
  T->>T: _translate_default_for_mysql(type, str(dflt_value))
  T->>T: _strip_wrapping_parentheses(expr)
  T-->>T: mapped MySQL default (maybe with precision)
  T->>T: _column_type_length(type, default?)
  T-->>CLI: emit column DDL with translated DEFAULT
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I nibble at parens, peel them away,
I hop through CURRENT_ times in play.
Numbers, strings and booleans too,
I tune defaults so columns renew.
Thump! A compatible table—hop anew. 🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description gives a clear, technical summary of the changes, rationale, tests and references the closed issue (Fixes #141), but it does not follow the repository's required template: the "Type of change" checkbox section, the explicit "How Has This Been Tested?" section with reproduction steps/tests, and the completed Checklist items are not present or filled. Because those template sections are missing, reviewers and project automation may not get required metadata about change type and test verification. Update the PR description to include the template sections: mark the applicable "Type of change" checkbox, add a "How Has This Been Tested?" section describing test steps and which tests were run, and complete the Checklist entries (style, self-review, documentation, tests passing). After adding those items the description will conform to the repository template.
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "✨ translate SQLite defaults to MySQL" accurately summarises the primary change — adding translation of SQLite default expressions into MySQL-compatible defaults — and is concise and focused on the main intent of the changeset. It directly reflects the core implementation and tests described in the diffs. The only issue is cosmetic (an emoji and lowercasing) but this does not make the title misleading.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/translate-sqlite-defaults-to-mysql

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4653fc4 and b7bd38e.

📒 Files selected for processing (2)
  • src/sqlite3_to_mysql/transporter.py (5 hunks)
  • tests/unit/sqlite3_to_mysql_test.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/sqlite3_to_mysql_test.py
  • src/sqlite3_to_mysql/transporter.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Test (python3.12, mariadb:5.5, 1, true, 3.12)
  • GitHub Check: Test (python3.9, mariadb:10.0, 1, true, 3.9)
  • GitHub Check: Test (python3.13, mariadb:5.5, 1, true, 3.13)
  • GitHub Check: Test (python3.11, mariadb:10.0, 1, true, 3.11)
  • GitHub Check: Test (python3.10, mariadb:10.0, 1, true, 3.10)
  • GitHub Check: Test (python3.11, mariadb:5.5, 1, true, 3.11)
  • GitHub Check: Test (python3.10, mariadb:5.5, 1, true, 3.10)
  • GitHub Check: Test (python3.9, mariadb:5.5, 1, true, 3.9)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Sep 15, 2025

Codecov Report

❌ Patch coverage is 88.99083% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.85%. Comparing base (39f8622) to head (b7bd38e).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/sqlite3_to_mysql/transporter.py 86.81% 12 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #142      +/-   ##
==========================================
- Coverage   94.62%   93.85%   -0.78%     
==========================================
  Files           8        8              
  Lines         689      797     +108     
==========================================
+ Hits          652      748      +96     
- Misses         37       49      +12     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sqlite3_to_mysql/transporter.py (1)

498-510: Defaults like 0 or '' are silently dropped; also allow translator to suppress unsupported expressions

The truthy check on dflt_value skips legitimate falsy defaults. Build the DEFAULT clause with a None check and only append when the translated default is non‑empty.

Apply this diff:

-            sql += " `{name}` {type} {notnull} {default} {auto_increment}, ".format(
+            # Build DEFAULT clause safely (preserve falsy defaults like 0/'')
+            default_clause = ""
+            if (
+                column["dflt_value"] is not None
+                and column_type not in MYSQL_COLUMN_TYPES_WITHOUT_DEFAULT
+                and not auto_increment
+            ):
+                td = self._translate_default_for_mysql(column_type, str(column["dflt_value"]))
+                if td != "":
+                    default_clause = "DEFAULT " + td
+            sql += " `{name}` {type} {notnull} {default} {auto_increment}, ".format(
                 name=mysql_safe_name,
                 type=column_type,
                 notnull="NOT NULL" if column["notnull"] or column["pk"] else "NULL",
                 auto_increment="AUTO_INCREMENT" if auto_increment else "",
-                default=(
-                    "DEFAULT " + self._translate_default_for_mysql(column_type, str(column["dflt_value"]))
-                    if column["dflt_value"]
-                    and column_type not in MYSQL_COLUMN_TYPES_WITHOUT_DEFAULT
-                    and not auto_increment
-                    else ""
-                ),
+                default=default_clause,
             )
🧹 Nitpick comments (2)
src/sqlite3_to_mysql/transporter.py (2)

356-450: Timezone modifiers may change semantics

SQLite’s "datetime/date/time('now','localtime'|'utc')" modifiers don’t map 1:1 to MySQL. CURRENT_* evaluates in the session time zone. Consider documenting this behavioural change or logging a warning when a timezone modifier is present.


451-459: Don’t truth‑test the default parameter; allow 0

If default is 0, the current check skips it. Use an explicit None check.

Apply this diff:

-        if default:
+        if default is not None:
             return f"({default})"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d3e31e and b0bf5a6.

📒 Files selected for processing (1)
  • src/sqlite3_to_mysql/transporter.py (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Test (python3.10, mariadb:10.0, 1, true, 3.10)
  • GitHub Check: Test (python3.11, mariadb:10.0, 1, true, 3.11)
  • GitHub Check: Test (python3.10, mariadb:5.5, 1, true, 3.10)
  • GitHub Check: Test (python3.9, mariadb:10.0, 1, true, 3.9)
  • GitHub Check: Test (python3.13, mariadb:5.5, 1, true, 3.13)
  • GitHub Check: Test (python3.12, mariadb:5.5, 1, true, 3.12)
  • GitHub Check: Test (python3.11, mariadb:5.5, 1, true, 3.11)
  • GitHub Check: Test (python3.9, mariadb:5.5, 1, true, 3.9)
  • GitHub Check: Codacy Static Code Analysis

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/sqlite3_to_mysql/transporter.py (1)

360-389: Parentheses stripping now safely removes only fully wrapping pairs

This addresses the previously raised pitfall with "(a) + (b)". LGTM.

🧹 Nitpick comments (4)
src/sqlite3_to_mysql/mysql_utils.py (1)

142-164: Version‑gated capability checks look correct; consider DRYing MariaDB detection

Thresholds mirror documented cut‑offs. To reduce repetition and future mistakes, extract a tiny helper, e.g. _is_mariadb(ver: str) -> bool, and reuse it in all checkers.

tests/unit/sqlite3_to_mysql_test.py (2)

670-710: DATETIME on old MySQL may still emit invalid DEFAULT; add failing case

When ts_dt=False (MySQL < 5.6.5), _translate_default_for_mysql currently falls through and returns CURRENT_TIMESTAMP for DATETIME, which MySQL will reject. Please add a case like:

  • ("DATETIME(6)", "CURRENT_TIMESTAMP", dict(expr=False, ts_dt=False, fsp=True), "")

Also consider a couple more literals:

  • Numeric edge forms: ".5", "1." (both valid in SQLite/MySQL) to ensure the numeric regex accepts them.

711-719: TIME fsp mapping covered; add localtime/utc variants

Consider adding:

  • time('now','localtime') → CURRENT_TIME(2)
  • time('now','utc') → CURRENT_TIME(2)
src/sqlite3_to_mysql/transporter.py (1)

65-77: Broaden numeric literal regex to match .5 and 1. forms

Your current pattern doesn’t match literals like ".5" or "1." which both parse in SQLite/MySQL. Recommend this tweak.

Apply this diff:

-    NUMERIC_LITERAL_PATTERN: t.Pattern[str] = re.compile(r"^[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$")
+    NUMERIC_LITERAL_PATTERN: t.Pattern[str] = re.compile(
+        r"^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$"
+    )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 04f90af and 791d3da.

📒 Files selected for processing (5)
  • src/sqlite3_to_mysql/mysql_utils.py (1 hunks)
  • src/sqlite3_to_mysql/transporter.py (5 hunks)
  • src/sqlite3_to_mysql/types.py (1 hunks)
  • tests/unit/mysql_utils_test.py (2 hunks)
  • tests/unit/sqlite3_to_mysql_test.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
tests/unit/sqlite3_to_mysql_test.py (1)
src/sqlite3_to_mysql/transporter.py (2)
  • _strip_wrapping_parentheses (361-389)
  • _translate_default_for_mysql (391-486)
tests/unit/mysql_utils_test.py (1)
src/sqlite3_to_mysql/mysql_utils.py (3)
  • check_mysql_current_timestamp_datetime_support (150-155)
  • check_mysql_expression_defaults_support (142-147)
  • check_mysql_fractional_seconds_support (158-163)
src/sqlite3_to_mysql/transporter.py (1)
src/sqlite3_to_mysql/mysql_utils.py (3)
  • check_mysql_current_timestamp_datetime_support (150-155)
  • check_mysql_expression_defaults_support (142-147)
  • check_mysql_fractional_seconds_support (158-163)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Test (python3.11, mariadb:10.0, 1, true, 3.11)
  • GitHub Check: Test (python3.10, mariadb:10.0, 1, true, 3.10)
  • GitHub Check: Test (python3.13, mariadb:5.5, 1, true, 3.13)
  • GitHub Check: Test (python3.12, mariadb:5.5, 1, true, 3.12)
  • GitHub Check: Test (python3.11, mariadb:5.5, 1, true, 3.11)
  • GitHub Check: Test (python3.9, mariadb:10.0, 1, true, 3.9)
  • GitHub Check: Test (python3.10, mariadb:5.5, 1, true, 3.10)
  • GitHub Check: Test (python3.9, mariadb:5.5, 1, true, 3.9)
🔇 Additional comments (8)
src/sqlite3_to_mysql/types.py (1)

88-90: Attributes added are appropriate; ensure they’re always initialised before use

The three private flags align with the runtime feature checks and tests. Looks good; just make sure they’re set in every code path before any helper that relies on them is called (they are in SQLite3toMySQL.init, but please keep this invariant if future refactors introduce alternate constructors/factories).

tests/unit/mysql_utils_test.py (1)

8-10: Solid coverage of edge and vendor/version boundaries

The parametrisations exercise both MySQL and MariaDB thresholds (including distro suffixes and case). Nice.

Also applies to: 215-229, 230-241, 243-256, 257-267, 269-281, 282-292

tests/unit/sqlite3_to_mysql_test.py (2)

637-657: Parentheses stripper tests are tight and realistic

Good mix of fully‑wrapped, partially wrapped and unmatched cases; these will guard against regressions.


658-669: Lightweight instance builder is handy

The flag toggles mimic runtime checks well and keep the tests fast.

src/sqlite3_to_mysql/transporter.py (4)

47-49: Good: centralised capability gating

Importing the three helpers keeps version logic in one place.


212-214: Flags wired correctly from capability checks

These match the new attributes and tests; nice and explicit.


489-495: Helper extension is fine

Defaulting length via default parameter simplifies PK length handling.


535-545: DEFAULT clause construction is robust

Good: skips types that can’t have defaults, preserves falsy defaults (0/''), and avoids AUTO_INCREMENT columns.

Also applies to: 550-550

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/func/test_cli.py (2)

39-49: Add reason and make xfail strict (or fix root cause and keep test active).

Bare xfail can mask regressions. Either stabilise the test by isolating env (preferred) or keep the xfail but make it explicit and strict.

Apply this minimal change:

-    @pytest.mark.xfail
+    @pytest.mark.xfail(reason="Behaviour varies when CLI picks defaults from environment variables.", strict=True)

If the flakiness is due to env-provided defaults, consider invoking with a clean environment instead of xfail:

result = cli_runner.invoke(sqlite3mysql, ["-f", sqlite_database], env={})

Would you prefer a follow-up PR that removes xfail by isolating the environment for this test?


51-63: Same here: make xfail explicit and strict (or stabilise via env isolation).

Mirror the change above to avoid silent XPASS; or drop xfail and pass an empty env to make the error deterministic.

Suggested tweak:

-    @pytest.mark.xfail
+    @pytest.mark.xfail(reason="Behaviour varies when CLI picks defaults from environment variables.", strict=True)

Optionally replace with:

result: Result = cli_runner.invoke(
    sqlite3mysql, ["-f", sqlite_database, "-d", mysql_credentials.database], env={}
)

Confirm whether the underlying issue is CI environment leakage (env vars). If so, I can prep a targeted fix.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 791d3da and a0a528f.

📒 Files selected for processing (3)
  • src/sqlite3_to_mysql/transporter.py (5 hunks)
  • tests/func/test_cli.py (2 hunks)
  • tests/unit/sqlite3_to_mysql_test.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/sqlite3_to_mysql_test.py
  • src/sqlite3_to_mysql/transporter.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Test (python3.11, mariadb:5.5, 1, true, 3.11)
  • GitHub Check: Test (python3.13, mariadb:5.5, 1, true, 3.13)
  • GitHub Check: Test (python3.11, mariadb:10.0, 1, true, 3.11)
  • GitHub Check: Test (python3.10, mariadb:10.0, 1, true, 3.10)
  • GitHub Check: Test (python3.12, mariadb:5.5, 1, true, 3.12)
  • GitHub Check: Test (python3.10, mariadb:5.5, 1, true, 3.10)
  • GitHub Check: Test (python3.9, mariadb:10.0, 1, true, 3.9)
  • GitHub Check: Test (python3.9, mariadb:5.5, 1, true, 3.9)

@techouse techouse merged commit bb5aba8 into master Sep 21, 2025
60 of 62 checks passed
@techouse techouse deleted the feat/translate-sqlite-defaults-to-mysql branch September 21, 2025 09:40
@techouse techouse linked an issue Sep 21, 2025 that may be closed by this pull request
@techouse techouse linked an issue Oct 18, 2025 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Convert SQLite datetime('now') to MariaDB NOW() Columns having default values not recognized by mariadb

1 participant