Skip to content

Support date arithmetic with INTERVAL operators - #539

Draft
JanJakes wants to merge 4 commits into
trunkfrom
interval-arithmetic
Draft

JanJakes wants to merge 4 commits into
trunkfrom
interval-arithmetic

Conversation

@JanJakes

@JanJakes JanJakes commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR adds date arithmetic with INTERVAL, building on #510 by @samuelmbabhazi:

SELECT d + INTERVAL 1 DAY;
SELECT d - INTERVAL 5 HOUR;
SELECT d + INTERVAL 1 DAY - INTERVAL 2 HOUR;

These queries previously failed with a syntax error. They now use SQLite’s DATETIME() function, sharing the translation with DATE_ADD() and DATE_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:

  • Reject expressions the parser cannot group correctly, including INTERVAL 1 DAY + d, 1 + d + INTERVAL 1 DAY, and d + INTERVAL 1 DAY + x. Errors suggest adding parentheses.
  • Support QUARTER and SQL_TSI_* units, and handle negative and computed interval values.
  • Reject unsupported units, including MICROSECOND and composite units such as HOUR_MINUTE, instead of silently returning NULL.

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:

  • Numeric dates: Values such as 20240101 are not interpreted as MySQL dates.
  • Interval values: MySQL converts the value to a whole number. Here, INTERVAL 1.5 DAY adds 36 hours instead of 2 days, and a string such as ' 1' returns NULL with + but works with -.
  • Date-only inputs: These return datetime strings, so comparisons with DATE values can be wrong. For example, d < CURDATE() - INTERVAL 7 DAY also matches the boundary day.
  • Time inputs: TIME values also return datetime strings, and times past midnight wrap around instead of going over 24 hours.
  • Month ends: Month-end arithmetic can overflow into the following month.
  • ADDDATE() and SUBDATE(): These functions remain unsupported.

Expected results in the new tests were checked against MySQL 8.4.

Fixes #454
Fixes #90

@coderabbitai

coderabbitai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

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
@JanJakes
JanJakes force-pushed the interval-arithmetic branch from dd2ddda to 918410c Compare October 19, 2026 14:39
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.
@JanJakes
JanJakes force-pushed the interval-arithmetic branch from 918410c to 5b7787c Compare October 20, 2026 08:36

This branch has not been deployed

No deployments
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.

MySQL-on-SQLite does not translate DATE arithmetic with INTERVAL HOUR Date operations not supported

2 participants