Documentation

Provenance.Util.ValueTypeNull

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.

class ValueTypeNull (T : Type) extends ValueType T :

A value domain with a null: null-strict arithmetic, and a null distinct from the zero of the domain.

Instances
    Dependency graph

    The three-valued comparison #

    def CompOp.eval3 {T : Type} [ValueType T] (op : CompOp) (a b : T) :

    Three-valued evaluation of a comparison: unknown as soon as an operand is NULL, and the two-valued answer otherwise.

    Equations
    Instances For
      Dependency graph
      @[simp]
      Dependency graph
      theorem CompOp.eval3_eq_true_iff {T : Type} [ValueType T] (op : CompOp) {a b : T} (ha : ValueType.isNull a = false) (hb : ValueType.isNull b = false) :
      op.eval3 a b = Kleene.true ↔ op.eval a b

      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
      @[simp]
      theorem CompOp.negate_eval3 {T : Type} [ValueType T] (op : CompOp) (a b : T) :
      op.negate.eval3 a b = (op.eval3 a b).not

      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.

      def WithNull (T : Type) :

      A value type with one further value adjoined, the null.

      Equations
      Instances For
        Dependency graph

        The adjoined value.

        Equations
        Instances For
          Dependency graph
          def WithNull.val {T : Type} (x : T) :

          A value of the domain, read in the adjoined domain.

          Equations
          Instances For
            Dependency graph
            Dependency graph
            @[instance_reducible]

            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
            @[instance_reducible]
            instance WithNull.instZero {T : Type} [Zero T] :
            Equations
            Dependency graph
            @[instance_reducible]
            instance WithNull.instAdd {T : Type} [Add T] :
            Equations
            Dependency graph
            @[instance_reducible]
            instance WithNull.instSub {T : Type} [Sub T] :
            Equations
            Dependency graph
            @[instance_reducible]
            instance WithNull.instMul {T : Type} [Mul T] :
            Equations
            Dependency graph
            @[instance_reducible]
            Equations
            Dependency graph
            @[instance_reducible]

            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
            @[instance_reducible]
            Equations
            • One or more equations did not get rendered due to their size.
            Dependency graph
            @[instance_reducible]
            Equations
            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.