bitemyapp / bitemyapp/esqueleto
`deriveEsqueletoRecord` should "see through" `Maybe` for nested records
- Dominant language
- Haskell
- Stars
- 399
- Forks
- 107
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 1
Description
I've got a record I'm `deriveEsqueletoRecord`ing:
```haskell
data X = X { x :: Entity Foo }
deriveEsqueletoRecord ''X
getX :: SqlQuery X
```
and I'm `subSelect`ing that in another record:
```haskell
data Y = Y { y :: Maybe X }
deriveEsqueletoRecord ''Y
getY = do
pure Y
{ y = subSelect getX
}
```
Unfortunately this fails for a few reasons:
1. `subSelect` only works on a `SqlExpr (Value a)` - fixable with a `subSelectRecord :: SqlQuery a -> SqlExpr (Maybe a)`.
2. The codegen for `SqlY` creates the field `y :: SqlExpr (Value (Maybe X))`. The `SqlExpr (Value (Maybe _))` should be "folded in" to the `Maybe _` bit.
Here's the code that determines the field type:
```haskell
-- | Transforms a record field type into a corresponding `SqlExpr` type.
--
-- * @'Entity' x@ is transformed into @'SqlExpr' ('Entity' x)@.
-- * @'Maybe' ('Entity' x)@ is transformed into @'SqlExpr' ('Maybe' ('Entity' x))@.
-- * @x@ is transformed into @'SqlExpr' ('Value' x)@.
-- * If there exists an instance @'SqlSelect' sql x@, then @x@ is transformed into @sql@.
--
-- This function should match `sqlSelectProcessRowPat`.
sqlFieldType :: Type -> Q Type
sqlFieldType fieldType = do
maybeSqlType <- reifySqlSelectType fieldType
pure $
flip fromMaybe maybeSqlType $
case fieldType of
-- Entity x -> SqlExpr (Entity x)
AppT (ConT ((==) ''Entity -> True)) _innerType -> AppT (ConT ''SqlExpr) fieldType
-- Maybe (Entity x) -> SqlExpr (Maybe (Entity x))
(ConT ((==) ''Maybe -> True))
`AppT` ((ConT ((==) ''Entity -> True))
`AppT` _innerType) -> AppT (ConT ''SqlExpr) fieldType
-- x -> SqlExpr (Value x)
_ -> (ConT ''SqlExpr)
`AppT` ((ConT ''Value)
`AppT` fieldType)
```
And `reifySqlSelectType`:
```haskell
-- Given a type, find the corresponding SQL type.
--
-- If there exists an instance `SqlSelect sql ty`, then the SQL type for `ty`
-- is `sql`.
--
-- This function definitely works for records and instances generated by this
-- module, and might work for instances outside of it.
reifySqlSelectType :: Type -> Q (Maybe Type)
reifySqlSelectType originalType = do
-- Here we query the compiler for Instances of `SqlSelect a $(originalType)`;
-- the API for this is super weird, it interprets a list of types as being
-- applied as successive arguments to the typeclass name.
--
-- See: https://gitlab.haskell.org/ghc/ghc/-/issues/21825
--
-- >>> reifyInstances ''SqlSelect [VarT (mkName "a"), ConT ''MyRecord]
-- [ InstanceD Nothing
-- []
-- (AppT (AppT (ConT Database.Esqueleto.Internal.Internal.SqlSelect)
-- (ConT Ghci3.SqlMyRecord))
-- (ConT Ghci3.MyRecord))
-- []
-- ]
tyVarName <- newName "a"
instances <- reifyInstances ''SqlSelect [VarT tyVarName, originalType]
-- Given the original type (`originalType`) and an instance type for a
-- `SqlSelect` instance, get the SQL type which corresponds to the original
-- type.
let extractSqlRecord :: Type -> Type -> Maybe Type
extractSqlRecord originalTy instanceTy =
case instanceTy of
(ConT ((==) ''SqlSelect -> True))
`AppT` sqlTy
`AppT` ((==) originalTy -> True) -> Just sqlTy
_ -> Nothing
-- Filter `instances` to the instances which match `originalType`.
filteredInstances :: [Type]
filteredInstances =
flip mapMaybe instances
(\case InstanceD _overlap
_constraints
(extractSqlRecord originalType -> Just sqlRecord)
_decs ->
Just sqlRecord
_ -> Nothing)
pure $ listToMaybe filteredInstances
```
Need to experiment a bit with this
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.