Reading every column as a value #
In the plain semantics every column holds a value of the domain: the
kind vector is a syntactic index, saying which operators may be
written where, and AggQueryIn.evaluatePlain returns plain tuples. The
operators that compare keys – duplicate elimination, the difference, a
grouping's and a partition's indices – are the ones that insist on
regular columns, and AggQueryIn.Explode is what bridges them: it reads an
aggregate column as a key, and over a plain relation it is the identity
(its own docstring says so). AggQueryIn.Retag does the same between
the two value-armed kinds.
This module makes that into a theorem: every query is
plain-equivalent to one whose columns are all regular
(AggQueryIn.exists_allReg), so the kind discipline costs nothing in
the plain semantics. Three readings of an aggregate column have exact
regular counterparts – an aggregate atom is the same comparison on the
column, a range atom is its two comparisons, and a column read as a
token is the column – and the one that has none is
ProjColIn.aggTerm, which post-composes the column with an arbitrary
function of the domain: no term expresses that.
A window is the other exception, and for a different reason: it is the
one operator whose plain semantics reads the kind vector, reading its
input through ColKind.readTuple. That nulling is how the library lists
a frame, and it is one listing the clause allows. An aggregation is
determinate where the listing cannot matter – the aggregate is
symmetric, or any two peers of the clause carry equal values of the
aggregated term – and then every listing the clause allows gives one
value.
Re-typing changes the listing, since an aggregate column read as a value
enters the tie-break, but only inside a block of peers, so a
determinate window reads the same value either way
(ValueFrame.windowValueOpt_readTuple_of_det). That is what
AggQueryIn.Retypable asks of a window – determinacy on every
instance, quantified as AggQueryIn.recSettles is, or an all-regular
input where no column moves. A FILTER clause is asked to read the
occurrence's regular columns, as SQL has it (an aggregate may not occur
inside the FILTER of an aggregate), and is then carried through with
nothing further to prove.
What this is for: the swaps of AggQueryIn.exists_selFree and
AggQueryIn.exists_prodFree – a selection written with a difference, a
product written with an apply – are stated over queries
whose columns are regular, because that is what Diff and an apply's
left side take. Regularizing first is what lets them apply to a query
that has aggregate columns in its type and ordinary values in its
rows.
The three readings that have regular counterparts #
A term reading every column as a regular one. A provenance
column is read as the value it holds; the aggregate-comparison gate has
no plain value of its own (the generic evaluators give it 0), so it
becomes that constant.
Equations
- (TermGIn.const a).toReg = TermGIn.const a
- (TermGIn.outer j).toReg = TermGIn.outer j
- (TermGIn.index k a).toReg = TermGIn.index k ⋯
- (TermGIn.provIndex k a).toReg = TermGIn.index k ⋯
- (TermGIn.cmpAgg k a a_1 a_2).toReg = TermGIn.const 0
- (TermGIn.chiGate op t₁ t₂).toReg = TermGIn.chiGate op t₁.toReg t₂.toReg
- (t₁.add t₂).toReg = t₁.toReg.add t₂.toReg
- (t₁.sub t₂).toReg = t₁.toReg.sub t₂.toReg
- (t₁.mul t₂).toReg = t₁.toReg.mul t₂.toReg
- (TermGIn.caseWhen op t₁ t₂ t₃ t₄).toReg = TermGIn.caseWhen op t₁.toReg t₂.toReg t₃.toReg t₄.toReg
- (t₁.coalesce t₂).toReg = t₁.toReg.coalesce t₂.toReg
Instances For
Dependency graph
A predicate reading every column as a regular one. An aggregate atom becomes the same comparison on the column, and a range atom the conjunction of its two – which is what it says on a row, the difference between them being what they annotate.
Equations
- (GenPredIn.cmp op t₁ t₂).toReg = GenPredIn.cmp op t₁.toReg t₂.toReg
- (GenPredIn.aggCmp k a op t).toReg = GenPredIn.cmp op (TermGIn.index k ⋯) t.toReg
- (GenPredIn.aggRange k a op₁ t₁ op₂ t₂).toReg = (GenPredIn.cmp op₁ (TermGIn.index k ⋯) t₁.toReg).and (GenPredIn.cmp op₂ (TermGIn.index k ⋯) t₂.toReg)
- (φ.and ψ).toReg = φ.toReg.and ψ.toReg
- (φ.or ψ).toReg = φ.toReg.or ψ.toReg
- φ.not.toReg = φ.toReg.not
Instances For
Dependency graph
A projection column read as a regular one. A column read as a
token is the column; a provenance column is the value it holds; a term
over an aggregate column is the same term over that column read as a
regular one (TermIn.atCol). Only the rewritten world's column, which
carries an arbitrary function, has no counterpart, and is sent to a
constant – which is why the lemma below asks sourceCol.
Equations
- (ProjColIn.term t).toReg = ProjColIn.term t.toReg
- (ProjColIn.token k a).toReg = ProjColIn.term (TermGIn.index k ⋯)
- (ProjColIn.aggTerm k a a_1).toReg = ProjColIn.term (TermGIn.const 0)
- (ProjColIn.aggTermOf k a t).toReg = ProjColIn.term (TermIn.atCol k ⋯ t)
- (ProjColIn.provTerm t).toReg = ProjColIn.term t.toReg
Instances For
Dependency graph
Every column it builds is regular.
Dependency graph
Reading a query's own columns as values #
Explode reads one aggregate column as a key and Retag reads the
provenance columns as values, so regularizing a query's columns is a
recursion over them. Both are the identity over a plain relation, which
is what makes the whole thing invisible to evaluatePlain.
The kind vector with the columns below m read as regular.
Instances For
Dependency graph
Dependency graph
Dependency graph
Every column below m read as a value.
Equations
- One or more equations did not get rendered due to their size.
- AggQueryIn.explodeBelow 0 x✝ = AggQueryIn.castKind ⋯ x✝
Instances For
Dependency graph
A query with every column read as a value.
Equations
- q.allRegOf = AggQueryIn.castKind ⋯ (AggQueryIn.explodeBelow n q)
Instances For
Dependency graph
The one reading with no regular counterpart #
What a query has to be for its columns to be readable as values. Two things, and both are real.
No projection column may post-compose an aggregate column
(ProjColIn.aggTerm): its value is gf of the column, and the term
language applies no arbitrary gf.
And every window either reads an all-regular input or is
determinate on every instance, with no FILTER clause. A window is
the one operator whose plain semantics reads the kind vector: it reads
its input through ColKind.readTuple, which nulls the aggregate
columns, so a window over a re-typed input reads values where the
original read 0. Both are listings the clause allows, and they differ
only inside a block of peers – an aggregate column read as a value
enters the tie-break and nothing else. Determinacy is exactly the
condition under which that cannot be seen: the aggregate reads its
frame as a bag, or
the term it aggregates is constant on the rows the clause leaves tied
(Relation.rowsAgree of Provenance.Determinacy, read on the rows the
window reads, AggQueryIn.readRows). Since it is a condition on the
rows, it is quantified over the instance here, in the manner of
AggQueryIn.recSettles;
ValueFrame.windowValueOpt_readTuple_of_det is what it buys, and
ValueFrame.windowValue_readTuple the symmetric case of the same thing.
A query the condition refuses does tell the two readings apart: over the
rows (2,5), (2,6), (1,7) whose first column is an aggregate one,
with no ORDER BY, so that all three are peers, first_value of the
second column reads 5 before the re-typing and 7 after. A FILTER
clause is asked to read the occurrence's regular columns, as SQL has
it: its truth is then
unchanged by the nulling, so it keeps the same rows on both sides.
Equations
- One or more equations did not get rendered due to their size.
- (AggQueryIn.Rel x✝ a).Retypable = True
- (AggQueryIn.Hole x✝¹ s x✝).Retypable = True
- (AggQueryIn.Param pv x✝).Retypable = True
- AggQueryIn.Empty.Retypable = True
- (AggQueryIn.Proj ps q).Retypable = q.Retypable
- (AggQueryIn.Sel φ q hφ).Retypable = q.Retypable
- (q₁.Prod q₂).Retypable = (q₁.Retypable ∧ q₂.Retypable)
- (q₁.Apply q₂).Retypable = (q₁.Retypable ∧ q₂.Retypable)
- (q₁.Sum q₂).Retypable = (q₁.Retypable ∧ q₂.Retypable)
- q.Dedup.Retypable = q.Retypable
- (q₁.Diff q₂).Retypable = (q₁.Retypable ∧ q₂.Retypable)
- (AggQueryIn.Explode k a q).Retypable = q.Retypable
- (AggQueryIn.Mu s q₀ q₁).Retypable = (q₀.Retypable ∧ q₁.Retypable)
- (AggQueryIn.MuSet s q₀ q₁).Retypable = (q₀.Retypable ∧ q₁.Retypable)
- (AggQueryIn.Gamma is ts fs q keep ord).Retypable = q.Retypable
- (AggQueryIn.GammaScalar ts fs q ord).Retypable = q.Retypable
- (AggQueryIn.GammaNest is his p f q).Retypable = q.Retypable
- (AggQueryIn.ProvSum is his t q).Retypable = q.Retypable
- (AggQueryIn.Retag h q).Retypable = q.Retypable
- (AggQueryIn.GammaTok is his ts fs a q keep).Retypable = q.Retypable
- (AggQueryIn.WinExpr P O o ws ts fs g q_1 keeps).Retypable = q_1.Retypable
Instances For
Dependency graph
A window-free query is re-typable. The window is the only thing
AggQueryIn.Retypable asks about, every column of the source language
re-typing on its own (a column reading an aggregate column reads it
through a term, ProjColIn.aggTermOf, and AggQueryIn.usesOnly refuses
the rewritten world's arbitrary function). So a query whose classes
exclude the window is re-typable outright – which is the case the three
passes of Provenance.BasisAssembly are in, the first of them replacing
every window by an apply of a scalar aggregation.
Dependency graph
What the swaps ask of a query: the input of every
selection has regular columns and its predicate has no aggregate atom
(so that the selection can become a difference, which takes regular
columns), and the left factor of every product has regular columns (so
that the product can become an apply, whose left side takes them). It is
what AggQueryIn.exists_allReg leaves behind.
Equations
- (AggQueryIn.Rel x✝ a).SwapReady = True
- (AggQueryIn.Hole x✝¹ s x✝).SwapReady = True
- (AggQueryIn.Param pv x✝).SwapReady = True
- AggQueryIn.Empty.SwapReady = True
- (AggQueryIn.Proj ps q).SwapReady = q.SwapReady
- (AggQueryIn.Sel φ q hφ).SwapReady = ((∀ (i : Fin x✝²), x✝¹ i = ColKind.reg) ∧ φ.hasAggAtom = false ∧ q.SwapReady)
- (q₁.Prod q₂).SwapReady = ((∀ (i : Fin n₁), κ₁ i = ColKind.reg) ∧ q₁.SwapReady ∧ q₂.SwapReady)
- (q₁.Apply q₂).SwapReady = (q₁.SwapReady ∧ q₂.SwapReady)
- (q₁.Sum q₂).SwapReady = (q₁.SwapReady ∧ q₂.SwapReady)
- q.Dedup.SwapReady = q.SwapReady
- (q₁.Diff q₂).SwapReady = (q₁.SwapReady ∧ q₂.SwapReady)
- (AggQueryIn.Explode k a q).SwapReady = q.SwapReady
- (AggQueryIn.Mu s q₀ q₁).SwapReady = (q₀.SwapReady ∧ q₁.SwapReady)
- (AggQueryIn.MuSet s q₀ q₁).SwapReady = (q₀.SwapReady ∧ q₁.SwapReady)
- (AggQueryIn.Gamma is ts fs q keep ord).SwapReady = q.SwapReady
- (AggQueryIn.GammaScalar ts fs q ord).SwapReady = q.SwapReady
- (AggQueryIn.GammaNest is his p f q).SwapReady = q.SwapReady
- (AggQueryIn.ProvSum is his t q).SwapReady = q.SwapReady
- (AggQueryIn.Retag h q).SwapReady = q.SwapReady
- (AggQueryIn.GammaTok is his ts fs a q keep).SwapReady = q.SwapReady
- (AggQueryIn.Win P O o w t fn q a dist keep).SwapReady = q.SwapReady
- (AggQueryIn.WinExpr P O o ws ts fs g q_1 keeps).SwapReady = q_1.SwapReady
Instances For
Dependency graph
The difference a selection becomes is ready for the product swap
wherever its input is: it projects, tags and takes differences, and
introduces no product and no selection of its own. This is what lets the
two swaps be composed, so that the basis with − for σ and the
apply for × is reached.
Dependency graph
A window over columns it does not read #
A window reads the regular columns of its input and carries the
aggregate ones through: ColKind.readTuple nulls an aggregate column
before the partition, the frame, the order and the term see the row. It
is one listing of the frame among those the clause allows, which is all
the semantics asks for, an aggregation being admitted only where its
value does not depend on the listing.
That is why re-typing is not free here: reading an aggregate column as a value puts it into the tie-break, which can reorder rows the clause leaves tied, and an order-dependent aggregate reads the reordered sequence. Where the aggregate reads its frame as a bag the reordering is immaterial, and this section proves that much.
A determinate window reads the same value before and after the
nulling, symmetric or not, and with a FILTER clause or without: the
two listings of the frame are sorted by the clause's keys and differ
only inside a block of peers, where the term does not look
(OrderSpec.map_eq_of_keySorted), and a clause that reads the
occurrence's regular columns keeps the same rows on both sides. The
hypothesis is determinacy – the aggregate reads its frame as a bag, or
the term it aggregates is constant on the rows the clause leaves
tied.
Dependency graph
Every query is one whose columns are all values #
The kind discipline costs nothing in the plain semantics: every query whose projections post-compose no aggregate column is plain-equivalent to one all of whose columns are regular, using the same operator classes and naming the same relations.
Each case rebuilds the operator over re-typed children and then reads
the columns it produces as values (AggQueryIn.allRegOf), which is the
identity there. Explode and Retag disappear: their children come back
already regular.