Skip to content

Translate MySQL CONVERT() expressions to SQLite#356

Merged
JanJakes merged 3 commits intotrunkfrom
convert-fix
Apr 10, 2026
Merged

Translate MySQL CONVERT() expressions to SQLite#356
JanJakes merged 3 commits intotrunkfrom
convert-fix

Conversation

@JanJakes
Copy link
Copy Markdown
Member

@JanJakes JanJakes commented Apr 6, 2026

Summary

Fixes #344.

MySQL's CONVERT() function was passed through to SQLite unchanged, causing queries like SELECT CONVERT('Customer' USING utf8mb4) COLLATE utf8mb4_bin to fail with a syntax error.

This PR has two parts:

  1. Extract simpleExprBody as a named grammar rule. The %simpleExpr_factored fragment is promoted to a real simpleExprBody rule so it creates its own AST node. This separates the core expression (CONVERT, CAST, literals, etc.) from trailing modifiers (COLLATE, CONCAT_PIPES) that remain in the parent simpleExpr node, making individual expression handlers simpler.

  2. Translate CONVERT() expressions. Adds explicit handling for both forms of CONVERT() in the AST-based driver:

    • CONVERT(expr, type) is translated to CAST(expr AS type), reusing the existing castType translation.
    • CONVERT(expr USING charset) is reduced to just the expression, as SQLite stores all text as UTF-8 and charset conversions are not needed.

Test plan

  • Added testConvert translation test — verifies SQL-to-SQL translation for both CONVERT forms and COLLATE.
  • Added testConvertExpression — verifies CONVERT with type casting (BINARY, CHAR, SIGNED, UNSIGNED, DECIMAL, DATE).
  • Added testConvertUsingExpression — verifies CONVERT with charset conversion (utf8mb4, utf8, latin1).
  • Added testConvertUsingWithCollate — verifies the exact query from CONVERT() queries fail with error #344.
  • Added testConvertWithColumnReferences — verifies CONVERT with column references in SELECT, WHERE, and ORDER BY.
  • Full test suite passes (640 tests, 0 failures).
  • PHPCS passes.

JanJakes added 2 commits April 7, 2026 20:55
Promote the %simpleExpr_factored fragment to a real "simpleExprBody"
rule so it creates its own AST node. This separates the core expression
(CONVERT, CAST, literals, etc.) from trailing modifiers (COLLATE,
CONCAT_PIPES) that remain in the parent "simpleExpr" node, making
individual expression handlers simpler.
SQLite doesn't support the CONVERT() function. Translate its two forms:
- CONVERT(expr, type) is equivalent to CAST(expr AS type).
- CONVERT(expr USING charset) is a character set conversion that is
  a no-op in SQLite, as all text is stored as UTF-8.

Fixes #344
@JanJakes JanJakes marked this pull request as ready for review April 8, 2026 08:43
@JanJakes JanJakes requested review from a team, bgrgicak and brandonpayton and removed request for a team and brandonpayton April 8, 2026 08:44
@JanJakes
Copy link
Copy Markdown
Member Author

JanJakes commented Apr 8, 2026

The Query Monitor tests failure is unrelated to this PR and I will address it separately. It seems that QM 4.0 is incompatbible with our integration.

bgrgicak
bgrgicak previously approved these changes Apr 10, 2026
CONVERT('abc', BINARY) AS expr_1,
CONVERT('abc', CHAR) AS expr_2,
CONVERT('-10', SIGNED) AS expr_3,
CONVERT('-10', UNSIGNED) AS expr_4,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I have one question, in MySQL SELECT CONVERT('-10', UNSIGNED); will return 18446744073709551606.
Why is '-10' expected here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@bgrgicak I missed this, thanks for catching that! It seems that the main problem is that SQLite doesn't support UNSIGNED integers, so it cannot store values as large as 18446744073709551606.

That looks like a bigger problem, and I'm not sure whether it's reasonably solvable 😬 I guess for now, we should add a TODO comment explaining the issue.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

A TODO sounds good if we can't resolve it in this PR.

In this case, is it better to throw an error or silently return the incorrect value?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@bgrgicak There are likely more issues like this where we translate a MySQL construct to a similar one in SQLite, but they don't behave 100% the same. I'm not sure if UNSIGNED is the only incompatibility here, but it will affect not only CONVERT but especially CAST and potentially some arithmetic, etc.

Therefore, I think the TODO is fine (and I can also create a ticket), and we don't need to try to detect and fail the invalid cases at the moment. Fortunately, casting a signed negative integer to an unsigned one seems to be a very strange use case that is hopefully not encountered in reality.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I added the TODOs.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Issue: #359

MySQL wraps negative values on UNSIGNED casts using modular
arithmetic (2^64), but SQLite has no unsigned integer type.
Mark this as a known limitation to be addressed.
@JanJakes JanJakes merged commit b9af09c into trunk Apr 10, 2026
15 of 16 checks passed
@JanJakes JanJakes deleted the convert-fix branch April 10, 2026 08:59
@JanJakes JanJakes mentioned this pull request Apr 10, 2026
JanJakes added a commit that referenced this pull request Apr 10, 2026
## Release `2.2.23`

Version bump and changelog update for release `2.2.23`.

**Changelog draft:**
* Add Query Monitor 4.0 support
([#357](#357))
* Translate MySQL CONVERT() expressions to SQLite
([#356](#356))

**Full changelog:**
v2.2.22...release/v2.2.23

## Next steps

1. **Review** the changes in this pull request.
2. **Push** any additional edits to this branch (`release/v2.2.23`).
3. **Merge** this pull request to complete the release.

Merging will automatically build the plugin ZIP, create a [GitHub
release](https://github.com/WordPress/sqlite-database-integration/releases),
and deploy to
[WordPress.org](https://wordpress.org/plugins/sqlite-database-integration/).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CONVERT() queries fail with error

2 participants