Skip to content

feat(core): model schema completeness (Schema.closed, open/closed) — Calcite DynamicRecordType pattern - #15

Merged
milindsrivastava1997 merged 3 commits into
mainfrom
docs/scan-binding-schema
May 28, 2026
Merged

milindsrivastava1997 merged 3 commits into
mainfrom
docs/scan-binding-schema

Conversation

@zzylol

@zzylol zzylol commented May 28, 2026

Copy link
Copy Markdown
Contributor

What

Makes the SQL/PromQL schema difference an explicit, checkable property instead of an implicit one that kept causing "shouldn't a scan emit all the labels?" confusion. Two parts:

  1. Docs: Scan.schema reframed as the binding schema (the resolved column set positional ColumnIds index into), not a complete runtime row; SchemaCatalog documented as the column source (input), distinct from the resolved schema it feeds.
  2. Schema.closed: bool — the open/closed completeness marker.

Schema.closed

  • closed — the schema completely enumerates the columns here: a catalog-backed SQL leaf, or an output fully determined by Aggregate/Project.
  • open — a dynamic / superset schema: a schemaless PromQL leaf lists only the (ts, value) floor + referenced labels; the runtime row may carry more.

This is Apache Calcite's DynamicRecordType pattern: a schema starts open at a schemaless leaf and is frozen to closed by the first operator that fully determines its output columns. output_schema_in propagates it:

  • passthrough nodes (Filter/Sort/Limit/Distinct/Window/TimeRange/SetOp/Merge/WindowFunc/BinaryOp) inherit the child's closed;
  • cross-series Aggregate and Project set true (the freeze);
  • per-series reductions (rate/increase, *_over_time) and joins propagate (input.closed, left && right);
  • leaves: SQL scan_sourcetrue, PromQL Binderfalse.

It decouples completeness from the data model (Source::Table vs TimeSeries), so a future registry-backed PromQL catalog stays open (a metric's labels are per-series and time-varying → superset hint, not a per-row contract).

Why this shape (design survey)

Looked at how typed query IRs over varying schema-strictness handle this — only Calcite spans both open and closed in one IR, which is exactly this project's goal (one IR for SQL ∪ PromQL):

Invariant: open ⇒ no closed-world validation (PromQL tolerates unknown labels, unlike Ibis/SQL's eager rejection).

Scope / safety

  • #[serde(default)] → absent deserializes to false (open) — backward-compatible, conservative.
  • No current consumer reads closed — it's a passive marker reserved for label validation / full-output enumeration / cardinality (cost model, Wire workload-level CSE into a cost model #6). Zero behavior change.

Tests

Open→closed freeze (open leaf → per-series rate stays open → cross-series aggregate freezes to closed); serde default (absent ⇒ open); SQL-closed / PromQL-open leaves. Full suite green (141), clippy --all-targets -D warnings + fmt --check clean.

🤖 Generated with Claude Code

zzylol and others added 2 commits May 28, 2026 08:58
…schema

`Scan.schema`'s docstring called it "the authoritative, self-contained output
schema", which overclaims for PromQL and caused confusion: a metric's label set
is open and known only at runtime, so the PromQL schema is usage-derived (the
`(ts, value)` floor + referenced labels), not the metric's full label set.

Reframe it as the **binding schema** — the resolved column set positional
`ColumnId`s index into: complete when catalog-backed (SQL), usage-derived for
schemaless PromQL. Also note on `SchemaCatalog` that it is the column *source*
(the "catalog"), distinct from the resolved `Scan.schema` it feeds, and is the
extension point for a future registry-backed PromQL catalog.

Docs only — no behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Make the SQL/PromQL schema difference an explicit, checkable property instead of
implicit. `Schema` gains `closed: bool` (`#[serde(default)]` → false = open):

- **closed** — the schema completely enumerates the columns here (no more at
  runtime): a catalog-backed SQL leaf, or an output fully determined by an
  `Aggregate`/`Project`.
- **open** — a dynamic / superset schema (a schemaless PromQL leaf lists only
  the `(ts, value)` floor + referenced labels; the runtime row may carry more).

This is Apache Calcite's `DynamicRecordType` pattern: a schema starts open at a
schemaless leaf and is **frozen to closed** by the first operator that fully
determines its output columns. `output_schema_in` propagates accordingly —
passthrough nodes (Filter/Sort/Limit/Distinct/Window/TimeRange/SetOp/Merge/
WindowFunc/BinaryOp) inherit their child's `closed`; cross-series `Aggregate`
and `Project` set `true` (the freeze); per-series reductions (rate/increase,
*_over_time) and joins propagate (`input.closed` / `left && right`). Leaves:
SQL `scan_source` → true, PromQL `Binder` → false.

Decouples completeness from the data model (`Source::Table` vs `TimeSeries`), so
a future registry-backed PromQL catalog stays **open** (per-series + time-varying
labels = superset hint, not a per-row contract). No current consumer reads it —
it's a passive marker reserved for label validation / full-output enumeration /
cardinality (cost model, #6). **Invariant: open ⇒ no closed-world validation**
(PromQL tolerates unknown labels — unlike Ibis/SQL's eager rejection).

`#[serde(default)]` keeps it backward-compatible (absent ⇒ open). Tests: the
open→closed freeze, serde default, SQL-closed / PromQL-open leaves. Full suite
green (141), clippy -D warnings + fmt clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol zzylol changed the title docs: clarify Scan.schema is a binding schema, not a complete output schema feat(core): model schema completeness (Schema.closed, open/closed) — Calcite DynamicRecordType pattern May 28, 2026
…two call sites

The external pattern reference earns its place at the definition site
(`Schema.closed` docstring, as a lookup-able anchor) but is noise repeated at the
Aggregate arm + the test — reword those to describe the behavior (freeze to
closed) directly. The full design survey stays in the PR description.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@milindsrivastava1997
milindsrivastava1997 merged commit 77741d8 into main May 28, 2026
1 check passed
@milindsrivastava1997
milindsrivastava1997 deleted the docs/scan-binding-schema branch May 28, 2026 16:30
zzylol added a commit that referenced this pull request Aug 24, 2026
Restructure the flat 19-section developer guide into the three-part
structure the doc owner asked for: Part 1 - Code Architecture, Part 2
- Interfaces and Definitions, Part 3 - How to Add X, Y, Z (each ending
in how to verify). Content is moved, not rewritten:

Part 1 (Mental model first, per doc-owner follow-up, then a new
whole-PR architecture diagram, then "How the current pieces fit
together"):
- old #1 Mental model -> Part 1 #1
- new: whole-PR architecture diagram (TargetSubDAG's two entry points
  through ReplacementStrategy, PlanSpace/cost_sorted, explanation.rs,
  to a downstream consumer) -> Part 1 #2
- old #3 How the current pieces fit together -> Part 1 #3

Part 2:
- old Terminology's "Implementation" definition merged into the
  Glossary as one more entry (### Implementation), next to
  ReplacementStrategy
- old #2 Glossary -> Part 2 #1 (plus the merged Implementation entry
  and old #10 Matcher, retitled to match glossary-entry style)
- old #10 Matcher (implementation.rs) -> ### Matcher inside the
  Glossary; implementation.rs no longer exists, so the stale title
  is fixed
- old #19's definitional content (ReplacementExplanation/
  ExplanationKind shapes, node_hash, why there's no ExplanationRule
  trait, location-text ownership) -> Part 2 #2

Part 3:
- old #4, #5, #6, #7, #13, #14 -> Part 3 #1, Adding a new
  ReplacementStrategy (ending in Testing a new strategy)
- old #8, #9, #15 -> Part 3 #2, Adding or customizing a CostModel
  (ending in Testing a new cost model)
- old #12 -> Part 3 #3, Adding a new sketch algorithm, with its
  stale implementation.rs/binder references fixed to replacement.rs/
  construct_summary vocabulary, plus a new "Verifying a new sketch
  algorithm" close grounded in the existing coverage-matrix tests
- old #11, #16, #17, #18 -> Part 3 #4-#7 (capstone + closing
  reference material); #18's extension-map table's implementation.rs
  row fixed to replacement.rs
- old #19's "Using it"/"Adding a new kind" content -> Part 3 #8,
  Using and extending explanation.rs

cargo build --workspace --all-targets is clean (docs-only change).

Co-Authored-By: Claude Sonnet 5 <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.

2 participants