Skip to content

Parse DuckDB's // integer division operator - #482

Closed
eddietejeda wants to merge 5 commits into
tobilg:mainfrom
hotdata-dev:feat/duckdb-integer-division
Closed

eddietejeda wants to merge 5 commits into
tobilg:mainfrom
hotdata-dev:feat/duckdb-integer-division

Conversation

@eddietejeda

Copy link
Copy Markdown
Contributor

DuckDB has an integer division operator written as //. Right now, the parser doesn't recognize it. See DuckDB operator list here: https://duckdb.org/docs/lts/sql/functions/numeric

SELECT 7 // 2 AS v        -- DuckDB returns 3

What this does

When the source dialect is DuckDB and a / is followed by another /, the parser now treats the pair as one operator and builds an IntDiv node.

DuckDB -> DuckDB       SELECT 7 // 2 AS v
DuckDB -> PostgreSQL   SELECT DIV(7, 2) AS v     -- same output MySQL's 7 DIV 2 gets today

Nothing else is affected. a / / b isn't valid SQL in any dialect, so there's no existing input this could change the meaning of. The check is also limited to DuckDB as the source.

Tests

tests/duckdb_integer_division.rs covers the round trip, the lowering to other targets, precedence against +, and that a plain / still parses as ordinary division.

@tobilg

tobilg commented Sep 29, 2026

Copy link
Copy Markdown
Owner

Verdict: request changes before merging revision 56fb9c0d.

This addresses a real DuckDB parsing gap. Validation passed for all four new tests, 1,301 library tests, 208 dialect-matrix tests, and formatting. Additional execution probes identified the following concerns.

  1. Preserve DuckDB’s division semantics when converting to other dialects.

    DuckDB’s // behavior depends on operand types. These conversions currently change behavior or produce unsupported SQL:

    DuckDB input Source result Generated target SQL Problem
    7.0 // 2 3.5 PostgreSQL: DIV(7.0, 2) Returns 3
    7 // 0 NULL BigQuery: DIV(7, 0) Raises a division-by-zero error
    7 // 2 3 SQLite: DIV(7, 2) Fails because SQLite has no built-in DIV function

    These are existing IntDiv generation limitations exposed by the newly supported DuckDB syntax. Conversions should preserve the source behavior or produce an explicit unsupported diagnostic when that cannot be established.

    SQLGlot 30.14.0 reproduces the fractional and zero-division conversion problems, so matching its output is insufficient to establish correctness here.

  2. Use the existing tokenizer support for //.

    TokenizerConfig::double_slash_int_div, already used by Vertica, recognizes contiguous // as TokenType::Div. Both multiplication parser paths already support that token.

    The proposed parser lookahead also accepts separated slashes:

    SELECT 7 / / 2;
    SELECT 7 / /* comment */ / 2;

    DuckDB rejects both statements, but this revision accepts them even under strict validation and regenerates them as integer division.

    Please enable the existing tokenizer setting for DuckDB and remove the two new parser branches. This fits the shared architecture, avoids duplicated logic, and matches SQLGlot’s tokenizer-level recognition.

    It also avoids materializing and discarding the second slash token. An allocation probe measured 1,000 additional allocations for 1,000 division expressions with the proposed parser approach compared with the existing tokenizer path. This measures allocation overhead; no latency regression has been established.

  3. Include regression coverage in the routinely executed test suites.

    The new standalone duckdb_integer_division test target is not selected by make test-rust-verify or its CI wrappers. Please move these cases into appropriate existing suites so the normal verification process exercises them.

    Additional coverage should include fractional operands, explicitly typed floating-point operands, zero divisors, separated slashes, and AST precedence. For precedence, 2 + 7 // 2 distinguishes the possible groupings; the current 1 + 7 // 2 example produces the same result under either grouping. An AST assertion should verify that integer division binds more tightly than addition.

    After these changes, please run make test-rust-verify to check for regressions.

- Enable the existing double_slash_int_div tokenizer flag for DuckDB
  (already used by Vertica) instead of a bespoke parser lookahead; the
  existing DIV-keyword parsing path then handles it for free. Fixes
  separated/commented slashes (e.g. "7 / / 2") being wrongly accepted
- Report unsupported instead of silently wrong output when a DuckDB //
  operand is a float literal, since DuckDB falls back to ordinary float
  division there (7.0 // 2 is 3.5, not 3) and truncating DIV forms can't
  reproduce that
- Report unsupported for BigQuery when dividing by a literal zero, since
  BigQuery's DIV raises an error where DuckDB's // returns NULL
- Emulate integer division for SQLite (CAST(CAST(x AS REAL) / y AS
  INTEGER)), which has no DIV function and previously got invalid SQL
- Wire duckdb_integer_division into make test-rust-verify; expand
  coverage from 4 to 9 cases
- Move the float-operand guard into the DuckDB-source transform so it
  only applies to DuckDB's //; it had been placed in target-side code
  that can't see the source and broke MySQL's own DIV (7.5 DIV 2) and
  Vertica's //, which genuinely truncate
- Reject a literal-zero divisor for every non-DuckDB target, not just
  BigQuery: PostgreSQL's DIV and ClickHouse's intDiv raise too, where
  DuckDB returns NULL
- Recognize negated, parenthesized, and exponent-form float literals
- Treat Vertica and ClickHouse as truncating targets for the float guard
DuckDB's // is ordinary float division when either operand is a float
literal, so emit / (which then follows each target's own division rules)
instead of reporting it unsupported. hotquery's hotdata target already
lowered IntDiv to / and was producing the right answer here; the guard
would have turned that into an error.
@eddietejeda

Copy link
Copy Markdown
Contributor Author

Addressed the three issues:

  1. Switched to the double_slash_int_div tokenizer flag that Vertica already uses, and removed the parser lookahead. The existing DIV path handles the token. As a result, 7 / / 2 and 7 / /* c */ / 2 are now rejected, as in DuckDB.
  2. If either operand is a float, DuckDB's // is plain float division (7.0 // 2 = 3.5). We now emit / for that case. Dividing by a literal 0 returns NULL in DuckDB but errors elsewhere, so that returns an unsupported error. Both checks run only when the source is DuckDB, so MySQL's and Vertica's DIV are unchanged. SQLite has no DIV function, so it gets CAST(CAST(x AS REAL) / y AS INTEGER).
  3. Added the test file to make test-rust-verify, with tests for the cases above plus an AST check for precedence. The old 1 + 7 // 2 test gives 7 either way, so it proved nothing; 2 + 7 // 2 does.

make test-rust-verify passes.

@tobilg

tobilg commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Thanks, I implemented some additional fixes and tests in #484. Closing.

@tobilg tobilg closed this Oct 7, 2026
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.

2 participants