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 r
, like
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:
sub
is a subset ofsuper
, ifsuper
has thefield
of 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.