prowdsponsor / esqueleto

Bare bones, type-safe EDSL for SQL queries on persistent backends.
http://hackage.haskell.org/package/esqueleto
BSD 3-Clause "New" or "Revised" License
177 stars 51 forks source link

Is it possible to treat a primary key as an Int64? #155

Closed felixSchl closed 7 years ago

felixSchl commented 7 years ago

Given a query that will run in an SqlBackend environment, is it possible to cast, say product ^. ProductId to an SqlExpr of Value Int64? My initial approach to fmap over both Value a and SqlExpr but had to find out it could not deduce a Functor SqlExpr.

For example, this does not work:

-- ...
                [ E.when_ (p E.^. ProductKind E.==. (E.val $ BookProductType))
                    E.then_ $ do
                        ((fromSqlKey <$>) <$>) <$> do
                            mBook E.?. BookProductId
                ]
-- ...

as it causes:

-- causes:
    • Could not deduce (Functor SqlExpr) arising from a use of ‘<$>’
      from the context: (BaseBackend backend ~ SqlBackend,
                         PersistUniqueRead backend,
                         PersistQueryRead backend,
                         IsPersistBackend backend,
                         Database.Esqueleto.Internal.Sql.SqlSelect a r,
                         MonadIO m)
        bound by the inferred type of
                 q :: (BaseBackend backend ~ SqlBackend, PersistUniqueRead backend,
                       PersistQueryRead backend, IsPersistBackend backend,
                       Database.Esqueleto.Internal.Sql.SqlSelect a r, MonadIO m) =>
                      ((SqlExpr (Entity Product), SqlExpr (E.Value (Maybe Int64)))
                       -> SqlQuery a)
                      -> ReaderT backend m [r]
    • In a stmt of a 'do' block:
        ((fromSqlKey <$>) <$>) <$> do { mBook ?. BookProductId }
      In the second argument of ‘($)’, namely
        ‘do { ((fromSqlKey <$>) <$>) <$> do { mBook ?. BookProductId } }’
      In the expression:
        when_ (p ^. ProductKind E.==. (val $ BookProductType)) then_
        $ do { ((fromSqlKey <$>) <$>) <$> do { mBook ?. BookProductId } }

The use case here is to be able to return a list of primary keys which may come from different tables. My models look similar to:

Product
    sku Text
    kind ProductType
    UniqueSku sku
    deriving Show Typeable

BookProduct
    productId ProductId
    isbn Text Maybe
    UniqueProductId productId
    deriving Show Typeable

Is this possible at all or am I doing things completely wrong?

felixSchl commented 7 years ago

I found a somewhat nasty way around it:

-- Must use this qualified lookup, else will get:
--     * Couldn't match type ‘BaseBackend backend’ with ‘SqlBackend'
import qualified Database.Esqueleto.Internal.Sql as EIS

-- ...

    E.case_
        [ E.when_ (p E.^. ProductKind E.==. (E.val $ BookProductType))
            -- Force Haskell to accept the underlying value as the required return Value.
            -- Not very safe, but at least the function clearly indicates that.
            E.then_ $ EIS.veryUnsafeCoerceSqlExprValue $ mBook E.?. BookProductId
        ]
        (E.else_ E.nothing)

-- ...