Value domains with a null #
SQL's NULL is a value of the domain like any other, and it is the same
value in every possible world: it is not an unknown ranging over values.
What sets it apart is how the operations treat it. Arithmetic is
null-strict – a NULL operand makes the result NULL – and a comparison
with a NULL operand is neither true nor false but unknown
(Provenance.Util.Kleene).
Two equalities follow, as in SQL. Comparison equality is the three-valued
one, CompOp.eval3 CompOp.eq, which is never true of a NULL. Syntactic
equality is = on the domain, under which two NULLs are identical; it is
what grouping, partitioning, duplicate elimination and difference use, and
it is two-valued. The library already decides those four with DecidableEq,
so they need nothing new: what this module adds is the null, the strictness
it obeys, and the three-valued comparison.
ValueTypeNull extends ValueType rather than replacing it, so that the
statements proved over a domain without a null stay true as stated, and
WithNull T builds the canonical instance over any value type: a copy of
T with one further value, where strictness holds by construction.
A value domain with a null: null-strict arithmetic, and a null distinct from the zero of the domain.
- zero : T
- add : T → T → T
- sub : T → T → T
- mul : T → T → T
- min : T → T → T
- max : T → T → T
- nonzero : T
- null : T
The distinguished value.
The domain's null test is the test for that value.
Addition is null-strict.
Subtraction is null-strict on the left.
Subtraction is null-strict on the right.
Multiplication is null-strict on the left.
Multiplication is null-strict on the right.
Instances
Dependency graph
The three-valued comparison #
Three-valued evaluation of a comparison: unknown as soon as an
operand is NULL, and the two-valued answer otherwise.
Equations
- op.eval3 a b = if op.strict = true ∧ (ValueType.isNull a = true ∨ ValueType.isNull b = true) then Kleene.unknown else Kleene.ofBool (decide (op.eval a b))
Instances For
Dependency graph
Dependency graph
Away from the null the three-valued reading is the two-valued one. This is what lets the statements proved under two-valued logic be recovered: they are the null-free case.
Dependency graph
The negator is the three-valued negation, and unconditionally so.
Away from the null it is the Boolean complement; at the null both readings
are unknown, and Kleene negation fixes unknown – so the equality holds
there not because the negator complements anything but because there is
nothing to complement. Pushing NOT through a comparison by PostgreSQL's
operator negator is therefore sound as it stands.
Dependency graph
The canonical null-bearing domain #
Adjoining one value to a value type gives a ValueTypeNull, with strictness
holding by construction: the operations are defined only where both operands
are. This is the domain the examples use, and the one an implementation's
nullable column has.
A value type with one further value adjoined, the null.
Instances For
Dependency graph
The adjoined value.
Equations
Instances For
Dependency graph
Dependency graph
Dependency graph
The canonical order, with the null first. It orders group and frame
sequences and has no ORDER BY meaning, so where the null falls in it is
immaterial.
Equations
- One or more equations did not get rendered due to their size.
Dependency graph
Equations
- WithNull.instZero = { zero := WithNull.val 0 }
Dependency graph
Equations
- WithNull.instAdd = { add := fun (a b : WithNull T) => Option.map₂ (fun (x1 x2 : T) => x1 + x2) a b }
Dependency graph
Equations
- WithNull.instSub = { sub := fun (a b : WithNull T) => Option.map₂ (fun (x1 x2 : T) => x1 - x2) a b }
Dependency graph
Equations
- WithNull.instMul = { mul := fun (a b : WithNull T) => Option.map₂ (fun (x1 x2 : T) => x1 * x2) a b }
Dependency graph
Equations
- WithNull.instAddCommSemigroup = { toAdd := WithNull.instAdd, add_assoc := ⋯, add_comm := ⋯ }
Dependency graph
The adjoined value is the null, and nothing else is.
Equations
- One or more equations did not get rendered due to their size.
Dependency graph
Equations
- One or more equations did not get rendered due to their size.
Dependency graph
Dependency graph
The rewriting domain reads its data arm as the data domain #
The rewritten world lives in V ⊕ K, the data on the left and the
annotation on the right. A term carried into it reads the same values on
the data arm, which is what the lemmas below say of the comparison and of
the null test.