Repository navigation
Support date arithmetic with INTERVAL operators - #539
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
850e0c6 to
dd2ddda
Compare
MySQL accepts `expr + INTERVAL n unit`, `expr - INTERVAL n unit` and `INTERVAL n unit + expr`, and the parser already does too, but the translator passed the expression through unchanged, so SQLite rejected it with a syntax error. The three forms now share the DATETIME() translation used by DATE_ADD() and DATE_SUB(), which gains two fixes on the way: a negative value no longer produces a modifier that SQLite rejects, and a computed value is parenthesized so that `||` does not bind to it first. Fixes #454 Fixes #90
dd2ddda to
918410c
Compare
The grammar doesn't group "INTERVAL n unit + expr" like MySQL. Its operand includes AND, OR, and XOR, which MySQL applies to the whole expression, so this query deleted every row, while MySQL deletes none: DELETE FROM t WHERE INTERVAL 30 DAY + d < '2025-01-01' AND id = 99 The parser also matches "expr + INTERVAL 1 DAY + x" as "expr + (INTERVAL 1 DAY + x)". The prefix form is rare, and the reported queries use the suffix form, so reject it until the grammar follows MySQL's precedence.
The grammar nests "op bitExpr" to the right, regardless of operator precedence. In "1 + d + INTERVAL 1 DAY", the interval was applied to "d" only, while MySQL applies it to "1 + d". Reject an interval in such a nested operand, and suggest parentheses. The rewritten fold also no longer duplicates translate_sequence(), and the docblock no longer claims that INTERVAL is the only "bitExpr" form that SQLite can't run.
The unit was taken from the token text, so units SQLite doesn't support produced an invalid DATETIME() modifier, and the result was NULL. With the INTERVAL operators, "UPDATE t SET d = d + INTERVAL 1 QUARTER" wrote NULL into the column, while MySQL adds 3 months. The same happened with the SQL_TSI_* synonyms, whose text differs from the unit they map to. Units are now looked up by token type. QUARTER becomes 3 months, and MICROSECOND and the composite units, such as HOUR_MINUTE, are rejected.
918410c to
5b7787c
Compare
MySQL returns a date when it adds only days, weeks, months, quarters, or years to a date. The translation always returned a datetime, so '2024-01-01' + INTERVAL 1 DAY gave '2024-01-02 00:00:00'. SQLite compares these values as strings, so comparisons with DATE values matched the wrong rows. This query also deleted the '2024-01-02' row, which MySQL keeps: DELETE FROM t WHERE d < '2024-01-03' - INTERVAL 1 DAY Dates are stored as 10-character strings, so use DATE() for such inputs. Chained intervals now become a single call with one modifier each, so the date expression isn't repeated for every interval. This fixes the first case of the linked issue. Month ends, ADDDATE(), and SUBDATE() remain open there. #531
## Release `3.1.1` Version bump and changelog update for release `3.1.1`. **Changelog draft:** * Support date arithmetic with `INTERVAL` operators ([#539](#539)) * Fix `ROLLBACK` as the first transaction of a connection ([#537](#537)) * Fix matching a foreign key reference to a unique key with a different case ([#534](#534)) * Fix `FOUND_ROWS()` without `SQL_CALC_FOUND_ROWS` ([#533](#533)) * Fix unique keys and foreign key restore without stringified fetches ([#532](#532)) * Fix `CREATE TEMPORARY TABLE IF NOT EXISTS` and `DROP TEMPORARY TABLE` as the first temporary table ([#524](#524)) * Fix a warning for a foreign key referencing a table with a shorter unique key ([#523](#523)) * Fix temporary table lookup without stringified fetches and with a different case ([#522](#522)) **Full changelog:** v3.1.0...release/v3.1.1 ## Next steps 1. **Review** the changes in this pull request. 2. **Push** any additional edits to this branch (`release/v3.1.1`). 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/).
Summary
This PR adds date arithmetic with
INTERVAL, building on #510 by @samuelmbabhazi:These queries previously failed with a syntax error. They now use SQLite’s
DATE()andDATETIME()functions, sharing the translation withDATE_ADD()andDATE_SUB().Why the extra fixes
Our current parser groups some expressions differently from MySQL. Translating them directly can produce wrong results—or update or delete the wrong rows.
This PR adds safeguards to the implementation in #510:
INTERVAL 1 DAY + d,1 + d + INTERVAL 1 DAY, andd + INTERVAL 1 DAY + x. Errors suggest adding parentheses.QUARTERandSQL_TSI_*units, and handle negative and computed interval values.MICROSECONDand composite units such asHOUR_MINUTE, instead of silently returningNULL.'2024-01-01' + INTERVAL 1 DAYreturned2024-01-02 00:00:00, so comparisons withDATEvalues could match the wrong rows. This also fixesDATE_ADD()andDATE_SUB(), the first case of DATE_ADD() and DATE_SUB() overflow past the end of the month, and ADDDATE() and SUBDATE() are not supported #531.The supported forms cover the linked issues. The expression restrictions can be revisited after the planned switch to a parser based on MySQL’s own grammar.
Remaining limitations
The existing date translation still differs from MySQL in these cases:
20240101are not interpreted as MySQL dates.INTERVAL 1.5 DAYadds 36 hours instead of 2 days, and a string such as' 1'returnsNULLwith+but works with-.TIMEvalues return datetime strings, and times past midnight wrap around instead of going over 24 hours.ADDDATE()andSUBDATE(): These functions remain unsupported.Month ends and
ADDDATE()/SUBDATE()are tracked in #531.Expected results in the new tests were checked against MySQL 8.4.
Fixes #454
Fixes #90
Summary by CodeRabbit
New Features
Bug Fixes