haskellesqueleto

Combining countRows with a having clause in esqueleto


I'm working on a very simple toy API to improve my Haskell skills. The relevant database table is called ingredients and has some fields (id, name, category).

I'm now trying to get a selection query working that shows possible duplicates. It does this in a naive way. If two ingredients have the same name, it might be a duplicate. It is however allowed to have ingredients with the same name, it's just that they might be duplicates. It doesn't really matter if this restraint doesn't make sense since it's just a toy project.

Basically I would like to get the following query in esqueleto:

SELECT name FROM ingredients
GROUP BY name
HAVING count(*) > 1;

I succeeded in creating the following esqueleto code

getDuplicateIngredientsStmt :: SqlPersistT (LoggingT IO) [E.Value Text]
getDuplicateIngredientsStmt = E.select
  $ E.from
  $ \ingredients -> do
    E.groupBy $ ingredients E.^. IngredientName
    E.having $ (E.countRows :: SqlExpr (E.Value Int)) E.>. E.val 1
    return $ ingredients E.^. IngredientName

This all works and compiles, great. But is this really as simple as it gets? I'm mainly frustrated with me having to cast the countRows to an SqlExpr is this really necessary?

If I leave out the cast I get the following error (telling me I need the cast)

Ambiguous type variable ‘typ0’ arising from a use of ‘countRows’
  prevents the constraint ‘(Num typ0)’ from being solved.
  Probable fix: use a type annotation to specify what ‘typ0’ should be.

Solution

  • Ah, you're already using the TypeApplications extension. That provides an even easier approach.

    E.having $ E.countRows E.>. E.val @Int 1
    

    That sets the only free type variable in the type of E.val to Int, which does everything you need.

    As for why it's necessary, it's because the types of E.val and E.>. introduce a PersistField constraint, so 1 ends up being inferred to have the type (PersistField t, Num t) => t. This prevents defaulting because the regular defaulting rules apply only when the constraints are in a minimal set that the compiler knows about. (The ExtendedDefaultRules extension would cause it to default to Integer because it knows what to do with the Num portion of the constraint, but I don't think that's really what you're after.)

    I'm just baffled that the error message you quoted doesn't mention both constraints. That would have made the lack of defaulting much more immediately obvious.