150 Denotation of Tuples, Filtering, and Composition
3. Renaming
ℒ(Γ, P, E as b)
{
return relabel(ℒ(Γ, P, E), b)
}
4. Tuple construction
Tuple construction evaluates every component relative to the same current frame. Later components may also see attributes from earlier component results.
ℒ(Γ, P, (E₁, ..., Eₙ))
{
let γ_base = currentDomain(Γ)
if n = 0
return γ_base
let γ_acc = emptyFrame
for each i
{
let Γᵢ = insertBeforeTop(Γ, γ_acc)
let Pᵢ = P ∪ vars(free(γ_base)) ∪ vars(free(γ_acc))
let γᵢ = ℒ(Γᵢ, Pᵢ, Eᵢ)
// Attribute-set expansion.
if result(γᵢ) = { a = A:Set<Attribute> }
{
let {a₁, ..., aₖ} = value(A)
let γᵢ = ℒ(Γᵢ, Pᵢ, (a₁, ..., aₖ))
}
γ_acc = tupleMerge(γ_acc, γᵢ)
}
return γ_acc
}
The accumulated frame starts empty for non-empty tuples. Each component is still translated in the original frame environment, so the current frame's condition is carried by the component frames. The current frame and earlier tuple fields are protected while later components are translated; this matters for folds, cardinality expressions, and complements inside tuple components. The empty tuple is simply the current domain:
ℒ(Γ, P, ()) = currentDomain(Γ)
5. Filtering
A filter strengthens the current frame and preserves its result.
ℒ(Γ, P, [E])
{
if Γ = []
raise error
let γ₀ = top(Γ)
let γ₁ = ℒ(Γ, P ∪ vars(free(γ₀)), E)
let (a,C,T) = unary(γ₁)
if T ≠ Boolean
raise error
let Θ = formula(γ₁) ∧ C
let F = free(γ₁)
let Q_close =
(vars(F) \ vars(free(γ₀)) \ P)
∩ FV(Θ)
let Q_keep = vars(F) ∩ P ∩ FV(Θ)
let K_close = decls(F, Q_close)
let K_keep = decls(F, Q_keep)
return ⟨
result(γ₀)
for
free(γ₀) ∪ K_keep
where
formula(γ₀) ∧ ∃(K_close) Θ
⟩
}
This is the strict boolean-filter rule. A non-strict existence-filter mode can replace Θ with
formula(γ₁), but the strict rule keeps existence tests explicit.
Because the condition expression is translated with γ₀ in the environment, formula(γ₁) may include
conjuncts already present in formula(γ₀). Implementations may factor those known conjuncts out of
Θ before existential closure; the displayed examples below use this normalized form.
Putting filtering and composition together, the supplier query from the foundations section:
s.supplier.[city = "London"].name
has, after the obvious formula simplifications, the frame:
⟨
{ name = N:String }
for
{ N:String from name }
where
∃S:Supplier ∃C:String (
s.supplier(S)
∧ S.city(C)
∧ C = "London"
∧ S.name(N)
)
⟩
The formula contains qualified unary predicates. In s.supplier(S), S.city(C), and S.name(N),
the type of the value before the dot determines which attribute is meant.
6. Composition
Ordinary composition translates the right-hand expression with the left-hand frame as the current frame, then closes left-hand variables not needed by the result.
ℒ(Γ, P, E₁.E₂)
{
let γ₁ = ℒ(Γ, P, E₁)
let γ₂ = ℒ(push(Γ, γ₁), P, E₂)
return bind∃(γ₁, γ₂, P)
}
For s.supplier.name, the right-hand translation has only name = N:String in its result.
Therefore bind∃ closes the supplier variable S, which is not needed by the result, and produces:
⟨
{ name = N:String }
for
{ N:String from name }
where
∃S:Supplier (
s.supplier(S)
∧ S.name(N)
)
⟩
Universal composition translates the right-hand expression with the left-hand variables protected, then universally closes left-hand variables not needed by the result.
ℒ(Γ, P, E₁⇒E₂)
{
let γ₁ = ℒ(Γ, P, E₁)
let γ₂ = ℒ(push(Γ, γ₁), P ∪ vars(free(γ₁)), E₂)
return bind∀(γ₁, γ₂, P)
}
Because γ₂ is translated with γ₁ as the current frame, its formula may syntactically include
formula(γ₁). Implementations may factor those known conjuncts out of the consequent before rendering
the implication.