I was just discussing HaskellDB’s major flaws with Oliver Charles and I noted that one huge problem is that the type of
update does not restrict the record given to make the update. Its type is
update :: (ShowLabels s, ToPrimExprs s) => Database -- ^ Database. -> Table r -- ^ Entity. -> (Rel r -> Expr Bool) -- ^ Predicate. -> (Rel r -> Record s) -- ^ Updates. -> IO ()
which is straight-forward enough. The problem is the “updates” argument, which will allow me to write a bogus field that does not belong to
update mytable (\row -> row!field .==. whatever) (\row -> badfield <<- whatever)
This problem actually bit me in the ass in production once before. That is not an exciting bug to have.
So I thought, we need to prove that for the type above,
s <: r (read as “s is a subtype of r”). How do we express that? How about a type class.
The type-class can be
class Subset sub super
But how to implement it? Well, we need to say that for every
field of sub, that
field is also a field of
super. That’s made easy for us, because HaskellDB already has a
HasField field record class for exactly that!
instance (HasField field super,Subset sub super) => Subset (RecCons field typ sub) super
This is similar to traversing a list at the value level, with
RecCons field type sub like a pattern-match on the current element. You can read it as:
subis a subset of
fieldof the head of the list, and the tail is a subset of super
So far so good. Now we need a base case, to cover the last element of the list:
instance Subset RecNil super
And we’re done. Update now becomes
update :: (Subset s r,ShowLabels s, ToPrimExprs s) => Database -- ^ Database. -> Table r -- ^ Entity. -> (Rel r -> Expr Bool) -- ^ Predicate. -> (Rel r -> Record s) -- ^ Updates. -> IO ()
Testing this on my codebase actually found a bug in which I was using the wrong field!
I will send this to the maintainer of HaskellDB as it’s a glaring bug waiting to happen to someone.