As I promised in the previous relevant post, I am moving forward with the LTL constraint for Pandamator (as any of you who have been following the iteration on Flying Donut already know). Apart from some integration in the maintenance scripts, all that remains is actually running the Büchi automaton. Which, of course, is not an easy task to do in SQL… That’s why I’m going to stick to the positive side only, and give you an account of what I have done so far.

The actual syntax of the constraint is very close to what I wrote earlier (I used `WITH`

instead of `WHERE`

because it’s already used as a keyword for defining things).

alter table Production.Product add validtime constraint weightneverdec check 'G(p0 -> G p1)' with p0 = weight > 0, p1 = weight > 0

This is a simple constraint for testing, which means that, whenever `Product.weight`

becomes bigger than zero, it never becomes zero again.

SPOT happily produces the following automaton for that expression.

2 1t 0 1 1 0 -1 & p1 p0 0 0 -1 ! p0 -1 1 0 1 0 -1 p1 -1

The automaton is parsed internally to the following value.

val it : BuchiAutomaton = {acccondnum = 1; states = [|{initial = true; transitions = [{endstate = 1; acccond = [0]; guard = Bin (And,Prop "p1",Prop "p0");}; {endstate = 0; acccond = [0]; guard = Not (Prop "p0");}];}; {initial = false; transitions = [{endstate = 1; acccond = [0]; guard = Prop "p1";}];}|];}

Almost the full definition for LBTT is followed by my (simple) parser, except I assume that states are named as consecutive numbers starting at zero, because SPOT does that.

The abstract syntax tree for the automaton is defined by the following types.

type LogicOp = And | Or | Impl | Xor | Equiv type LogicExpr = | Prop of string | Const of bool | Not of LogicExpr | Bin of LogicOp * LogicExpr * LogicExpr type BuchiAutomatonTransition = { endstate: int; acccond: int list; guard: LogicExpr; } type BuchiAutomatonState = { initial: bool transitions: BuchiAutomatonTransition list; } type BuchiAutomaton = { acccondnum: int; states: BuchiAutomatonState[]; }

A simple translation layer transforms the propositional logic of the guards, together with the definition of the propositions in the constraint, to SQL.

let rec logexp2stringlist (rcl:PropositionDefinition list) (exp:LogicExpr) = match exp with | Const true -> ["(1=1)"] | Const false -> ["(1=0)"] | Not e -> ["NOT";"("] @ (logexp2stringlist rcl e) @ [")"] | Bin (o, x, y) -> let lx = logexp2stringlist rcl x let ly = logexp2stringlist rcl y match o with | And -> ["(";"("] @ lx @ [")";"and";"("] @ ly @ [")";")"] | Or -> ["(";"("] @ lx @ [")";"or";"("] @ ly @ [")";")"] | Impl -> logexp2stringlist rcl (Bin(Or,Not(x),y)) | Equiv -> logexp2stringlist rcl (Bin(Or,Bin(And,x,y),Bin(And,Not(x),Not(y)))) // (x and y) or (not x or not y) | Xor -> logexp2stringlist rcl (Bin(And,Bin(Or,x,y),Not(Bin(And,x,y)))) // (x or y) and not(x and y) | Prop p -> let rc = rcl |> List.find (fun rc -> rc.name = p) "(" :: rc.expression :: [")"]

During `ALTER VALIDTIME`

execution, a view is created to abstract away the exact expressions used for the propositions (as boolean numbers 0/1, since SQL does not recognize logical expressions outside `WHERE`

).

CREATE VIEW [Production].[Product_weightneverdec_GuardView] WITH SCHEMABINDING as SELECT ProductID, ValidToDate, row_number() over (partition by ProductID order by ValidToDate) AS VersionOrdinal, case when ( ( ( weight > 0 ) ) and ( ( weight > 0 ) ) ) then 1 else 0 end AS g0_0, case when NOT ( ( weight > 0 ) ) then 1 else 0 end AS g0_1, case when ( weight > 0 ) then 1 else 0 end AS g1_0 FROM [Production].[Product]

That’s it. I wish I had done more, but I believe I made good use of my scant free time to do as much as possible. Expressed like that, the view can be used to perform (non-deterministic, i.e. parallel…) runs of the automaton on the actual object histories. Stay tuned, I hope my next post on the subject will be a triumphant conclusion to creating the constraint.