Skip to content

feat(sql): support date types and calendar interval expressions - #419

Merged
zzylol merged 5 commits into
mainfrom
feat/type-extension
Sep 15, 2026
Merged

zzylol merged 5 commits into
mainfrom
feat/type-extension

Conversation

@Selvomega

@Selvomega Selvomega commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Why

SQL data-quality checks using CAST(... AS DATE) or INTERVAL '30' DAY failed during lowering because the canonical IR lacked date and calendar-interval types.

What and how

Add DataType::Date, DataType::Interval, and a calendar-interval literal carrying months, days, and nanoseconds. Bridge Arrow date and interval types into these representations and preserve temporal result types through date/timestamp shifts and interval negation. Normalize typed Arrow date literals to date casts and explicitly reject multi-column COUNT(DISTINCT ...), whose tuple semantics the IR cannot represent. Reject fixed-duration subtraction before lowering, including inside predicates, CASE branches, and sort expressions, because the IR cannot preserve its unit.

Before this PR

l_receiptdate <= l_shipdate + INTERVAL '30' DAY failed with an unsupported interval scalar; CAST('1992-01-01' AS DATE) failed with unsupported Arrow Date32.

After this PR

Both expressions lower successfully. Typed Arrow date literals also lower as date casts. Date shifts retain Date, timestamp shifts retain Timestamp, and -CAST('1 day' AS INTERVAL) retains Interval. Unsupported d - d duration results fail explicitly even in expressions such as (d - d) IS NULL.

Verification

  • Regression tests reproduced nested-duration acceptance and interval negation being typed as Float64; both pass after the fixes.
  • All 1,118 workspace tests pass, including doc tests. Regression tests also reproduce and fix typed Arrow date-literal rejection and silent loss of composite DISTINCT keys.
  • The new TPC-H data-quality corpus uses actual Date columns and lowers 47 of 50 checks, with the exact rejected IDs and reasons pinned; the BGP corpus now lowers 154 queries, up from 152.
  • The packet-trace corpus lowers 61/70 queries; nine composite distinct counts now fail explicitly instead of silently counting the first key.
  • cargo +1.98.0 test --workspace --locked, workspace Clippy with warnings denied, and formatting checks pass.
  • Vendored MetricsQL baseline verification passes. The external-consumer script is blocked locally: it hardcodes cargo +stable, whose installed toolchain is missing rustc.

Limitations

Arrow Date64 normalizes to Date and registers back as Date32. Fixed-duration results remain unsupported. Composite distinct counts and the two TPC-H correlation checks remain unsupported. Verification covers planning/lowering, not database execution; performance measurements and screenshots are not applicable.

Closes #416
Closes #418

…as DataType and support Interval as literal and DataType
@Selvomega
Selvomega requested review from zzylol and a lite review from Copilot September 14, 2026 16:56
@Selvomega Selvomega changed the title Patching the DataType an Literal type coverage issues Closes #418, Closes #416 Patching the DataType and Literal type coverage issues Sep 14, 2026
@Selvomega Selvomega changed the title Patching the DataType and Literal type coverage issues feat(types): Patching the DataType and Literal type coverage issues Sep 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved date/interval handling and data-quality coverage issues must be addressed before approval.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Expands SQL and pre-ASAP support for date/interval types and adds TPC-H data-quality coverage.

Changes:

  • Adds date and interval type/scalar handling.
  • Updates temporal expression inference and SQL type bridging.
  • Adds DQC and workload coverage.
File summaries
File Summary Final review notes
crates/types/src/pre_asap/schema.rs Adds Date and Interval types.
crates/types/src/pre_asap/query_expr.rs Adds temporal arithmetic inference and tests. Negative interval arithmetic can infer Float64 (moderate, 2 votes).
crates/types/src/pre_asap/expr_ir.rs Adds interval scalar representation.
crates/frontend-sql/tests/data_quality_check/tpch_deequ.rs Adds TPC-H lowering coverage. Exercise date columns directly and assert the exact rejected query set (moderate, 2 votes; moderate, 1 vote).
crates/frontend-sql/tests/data_quality_check/data/tpch_deequ_queries.sql Adds the DQC query corpus. Composite COUNT(DISTINCT ...) may count only the first column (critical, 1 vote).
crates/frontend-sql/tests/bgp_jan2024_workload/bgp_jan2024_workload.rs Updates interval-related tally expectations. Update the stale explanatory rationale (nit, 1 vote).
crates/frontend-sql/src/sql/types.rs Bridges date and interval SQL types. Date literals lack lowering, and reverse interval mapping is missing (moderate, 3 votes; moderate, 1 vote).
crates/frontend-sql/Cargo.toml Registers the integration test.
Review details

Suppressed comments (3)

crates/frontend-sql/src/sql/types.rs:187

  • dtype_to_arrow emits an Arrow interval for DataType::Interval, but the reverse match still rejects every ArrowDataType::Interval. The comment above explicitly acknowledges that a hand-built catalog can reach this branch, so such a schema cannot round-trip and interval-typed planning paths fail with UnsupportedFeature. Add the reverse interval mapping (or reject Interval before registration) instead of exposing a one-way bridge.
            ArrowDataType::Interval(datafusion::arrow::datatypes::IntervalUnit::MonthDayNano)

crates/frontend-sql/tests/bgp_jan2024_workload/bgp_jan2024_workload.rs:204

  • The tally update is inconsistent with the explanatory paragraph immediately above it: that paragraph still says the two toStartOfInterval queries fail in Other because interval conversion is unsupported, while these new expectations say both lower and Other is zero. Please update that rationale as part of this tally change so the pinned test does not document the old failure mode.
    // 152 -> 154: `ScalarValue::Interval` (this branch) converts the
    // `INTERVAL x unit` literal the two `toStartOfInterval(...)` queries
    // carry, which the note above recorded as an open companion gap.
    expect(Category::Lowered, 154);

crates/frontend-sql/tests/data_quality_check/tpch_deequ.rs:116

  • This ratchet checks only the aggregate lowered == 48, so it can pass if one currently supported query starts rejecting while one of the two corr queries starts lowering (or the reverse). Since the comment promises that the two rejects are specifically corr, track the query IDs/error categories and assert that exact rejected set so the test detects coverage swaps, not just the total.
    // Coverage ratchet. The 2 that do not lower are the `corr` cells; adding a
    // binary `AggIntent` would raise this to 50.
    assert_eq!(
        t.lowered, 48,
        "SQL lowering coverage moved off the DQC ratchet: {t:?}"
  • Files reviewed: 8/8 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/frontend-sql/src/sql/types.rs
Comment thread crates/frontend-sql/tests/data_quality_check/tpch_deequ.rs Outdated
Comment thread crates/types/src/pre_asap/query_expr.rs
@zzylol zzylol changed the title feat(types): Patching the DataType and Literal type coverage issues feat(sql): support date types and calendar interval expressions Sep 15, 2026
@zzylol
zzylol merged commit 3c31338 into main Sep 15, 2026
4 checks passed
@zzylol
zzylol deleted the feat/type-extension branch September 15, 2026 16:20
Selvomega added a commit that referenced this pull request Sep 15, 2026
Two conflicts, both from main landing after this branch was cut.

`tests/sql_lowering.rs`: each side appended a test function at the same
place — `corr_result_is_nullable_float` here, `composite_distinct_is_rejected`
on main (#419). They are unrelated, so both are kept.

`tests/data_quality_check/tpch_deequ.rs`: no textual conflict, since this
branch never touched the file. #419 added the corpus ratchet and pinned
U-P4d and U-P4l as rejected-because-`corr`. Both lower now, so the ratchet
moves: 47 -> 49 lowered, P2b alone rejected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Selvomega added a commit that referenced this pull request Sep 16, 2026
…UNT(DISTINCT)

Two changes, in dependency order. The second is only expressible because of
the first.

`AggIntent` asserted that an aggregate reads one column: `input_col() ->
Option<ColumnId>` was how every consumer reached an input. #421 added
`PearsonCorr { left, right }` and had to make it return `None` from that
accessor *defensively*, so no single-column consumer could pick up half of the
pair. The accessor had become a trap each new multi-column intent must
remember to opt out of, and the cost of forgetting is a silently wrong number
rather than a compile error.

That is also why `COUNT(DISTINCT a, b)` had nowhere to land. It counts
distinct *tuples*; SQL lowering dropped every argument after the first,
reporting single-column cardinality where the query asked for tuple
cardinality. #419 stopped the miscount by rejecting the call outright, which
left TPC-H/Deequ U-P2b and nine synthetic-packet-trace flow counts permanently
unlowerable.

Before this PR:

    SELECT count(DISTINCT l_orderkey, l_linenumber) * 1.0 / count(*)
    FROM lineitem
    -> lowering failed: unsupported aggregate: multi-column COUNT(DISTINCT)

After this PR:

    Aggregate { measures: [Cardinality { cols: [0, 1], .. }], .. }

What changed:

- `input_col()` is removed. `input_cols() -> Vec<C>` is the only column
  accessor, so no consumer can ask for "the" input column of an aggregate that
  reads two. An intent may now declare whatever arity its semantics need
  without a defensive opt-out, and `PearsonCorr`'s opt-out is gone. The
  genuinely unary reducers keep `col: Option<C>` — this relaxes what consumers
  assume, it does not widen every variant.
- `AggIntent::Cardinality` takes `cols: Vec<C>`. One entry is
  `COUNT(DISTINCT col)`, several count distinct tuples, empty keeps the PromQL
  "the sample value" convention. It is the first variable-arity intent, and was
  not expressible while the accessor split existed.
- Realization is the single-column one with a wider item: a tuple becomes a
  `SummaryInputExpr::Tuple`, which HLL/Theta/KMV hash as one value. UnivMon is
  withheld from a tuple — it estimates frequency moments over a single value
  stream — and the value-frequency rule refuses a multi-column intent directly,
  so that invariant does not rest on the candidate table alone.
- SQL lowering resolves every `COUNT(DISTINCT ...)` argument as a grouping key,
  so a qualifier survives a join; an expression argument is rejected rather
  than reduced over a probe column.

Serialization of `Cardinality` changes from `col` to `cols`; a payload that
omits the field still reads as the implicit input.

Both corpus ratchets move in this commit: tpch_deequ 49 -> 50 lowered with no
rejections, synthetic_packet_trace 61 -> 70.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

[Feature] ASAPPlanner cannot understand (time) Interval literal values [Feature] Have ASAPPlanner support Date32 and Date64 types in DataFusion

3 participants