sql: Add correlated x AS OF SYSTEM TIME t SINCE l reads over pinned history - #38750
Draft
frankmcsherry wants to merge 3 commits into
Draft
sql: Add correlated x AS OF SYSTEM TIME t SINCE l reads over pinned history#38750frankmcsherry wants to merge 3 commits into
frankmcsherry wants to merge 3 commits into
Conversation
Add a `PIN AT <time>` form of the `RETAIN HISTORY` option on tables, sources, materialized views, and indexes. A pinned collection keeps its since at or below the given time indefinitely, while otherwise following the default compaction lag: the new `CompactionWindow::PinAt` variant lowers to `ReadPolicy::Multiple([lag, ValidFrom(pin)])`. The pin is a catalog fact: it round-trips through the item's SQL, is reported as strategy `PIN AT` in `mz_history_retention_strategies`, and is exposed to the planner through `CatalogItem::compaction_window` so dependents that read history (CHANGES, correlated AS OF) can check it statically. CREATE MATERIALIZED VIEW and ALTER ... RETAIN HISTORY refuse a pin below the initial as-of or the current read frontier, since the history before that point does not exist. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`CHANGES(x AS OF t)` is a table factor that reads a table, source, or materialized view's consolidated update history from `t` onward as an append-only collection: each update becomes a row extended with `mz_timestamp` and `mz_diff`. It requires `x` to carry `RETAIN HISTORY PIN AT` at or before `t`, so the result is a function of the data and `t` alone and never of physical compaction. The read is a `Get` annotated with `changes_as_of`, carried through HIR and MIR. The dataflow builder turns it into a persist import with a fixed as-of; compute reads the shard at that as-of, consolidates per (row, time), promotes time and diff to data, and advances the result to the dataflow as-of. Such imports are never served from indexes and receive no pushed-down MFPs, and a dataflow may not read the same id both plainly and as CHANGES. The import is marked monotonic. ALTER ... RETAIN HISTORY refuses to move a pin past a dependent's CHANGES literal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… history
`FROM x AS OF SYSTEM TIME expr SINCE lit` is a table factor whose `expr`
may refer to columns of the enclosing query (inside LATERAL), yielding the
contents of `x` at virtual time `expr`. It desugars to
SELECT cols FROM (SELECT cols, sum(mz_diff)::bigint AS c
FROM CHANGES(x AS OF lit)
WHERE mz_timestamp <= expr AND expr <= mz_now()
GROUP BY cols), repeat_row(c)
so its semantics are inherited from CHANGES: a time before `lit` yields the
empty relation, a future time delays the row until then, and the result is
a function of the data, `expr`, and `lit` because `x`'s history is pinned.
The desugared query is planned as a LATERAL derived table, with `x`'s bare
name as the default alias. Parser round-trip tests cover the new syntax
along with CHANGES and RETAIN HISTORY PIN AT.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft, PR 3 of 3, stacked on #38749 (
CHANGES). Until the earlier PRs merge this diff includes their commits; review the last commit. This one is a reference implementation for discussion, not a merge candidate.What it does
x AS OF SYSTEM TIME expr SINCE litis a table factor whoseexprmay refer to the enclosing query: the contents ofxat virtual timeexpr. Each order sees the price at its own time, and that value never moves afterwards. A time beforelityields the empty relation (NULL under a LEFT JOIN), a future time delays the row until then, and replicas and restarts agree becausex's history is pinned.How, and why it is a reference implementation
It desugars to a correlated query over
CHANGESand is planned as a LATERAL derived table:That is the definition of "the state at
t" as the integral of the changelog up tot, so the semantics are inherited fromCHANGESrather than specified twice, and the two features cannot disagree about what history means. It also gives a differential-test oracle for the operator this should become.The cost is the problem. As a relational join plus reduce, the plan arranges the facts (twice) and keeps one accumulator per (fact, dimension value it scanned), so state grows with facts times versions. A one-sided lookup does not need any of that: the output for (key, t) depends only on the dimension's history at or before t, which is frozen once the dimension's frontier passes t, so a fact can be looked up once and forgotten (retractions recompute the same value from the pinned history).
differential_dogs3::operators::half_joinalready stashes records until the arrangement frontier passes them and compares trace times against a per-record time; the variant needed compares against the payload time (for late facts), emits atmax(capability, payload time), and accumulates per key at discharge so it can emit the NULL row. The planner would lower the correlated shape to that operator instead of decorrelating. State becomes proportional to the dimension's history since the pin plus facts still waiting for their time.Evidence (local instance)
ORDER BY ts DESC LIMIT 1over an explicit history table) on 10k orders and a 21k-row history: identical results, comparable one-shot latency, one third of the arrangement memory (194k vs 590k records). The dedicated operator would sit near orders plus history, about 31k.Things to discuss
SELECT dim.val AS OF facts.time ... FROM dim). Equivalent for one input; the subquery form is friendlier, the table-factor form composes per input.SINCE litversusAT LEAST lit, and whether a time below the pin yields empty (this PR, per the design note) or clamps to the snapshot at the pin. One token in the desugared predicate either way.SINCEliteral is baked into each dependent, so advancing retention means recreating dependents with a later literal before the pin can move.repeat_row, which sits behindenable_repeat_row; a real implementation would build HIR directly.Tests
Parser round-trip cases in
src/sql-parser/tests/testdata/selectandcreate.Release note: none.
🤖 Generated with Claude Code