It kinda sucks that we get nice syntax support for Entity record but for anything else we're stuck with tuples of values.
Some ideas:
Higher Kinded Data
I'm not a fan of HKD, but it does work for this alright.
data FooF f = FooF { fooName :: f (Value String), fooAge :: f (Value Int) }
instance SqlSelect (FooF SqlExpr) (FooF Identity) where ...
With a bit of TemplateHaskell the instances are relatively easy to generate.
I recall having some super bad type error messages with this, particularly if f got inferred to be two different things in a record.
Separate Records
We could have something like:
data Foo = Foo { fooName :: String, fooAge :: Int }
mkSqlSelectRecord ''Foo
-- ======>
data FooSql = FooSql { fooName :: SqlExpr (Value String), fooAge :: SqlExpr (Value Int) }
instance SqlSelect FooSql Foo where ...
But there's some question about making constructors/types match without conflict, and how to deal with the record labels - keep them the same? suffix/prefix somehow?
Anonymous Records
Possibly large-anon could be used to return something sorta record-like, which we can SqlSelect easily enough.
I've certainly seen anon records with syntax like:
which may be pretty easy to map onto a SqlSelect instance.
prairie ?
Possibly we could leverage prairie. It is inspired by EntityField.
-- for constructing,
buildSql :: (forall a. Field rec a -> SqlExpr (Value a)) -> SqlExpr (Columns a)
-- for access, using `OverloadedRecordDot` or `getFIeld` or generalizing `(^.)`
instance (SymbolToField sym rec typ, Record rec) => HasField sym (Columns a) (SqlExpr (Value a))
instance (FieldDict PersistField a) => SqlSelect (SqlExpr (Columns a)) a
We'd be able to write, like,
do
from ...
where_ ...
pure $ buildSql $ \case
FooName -> user ^. #name
FooAge -> dog ^. #age
Hmm. The problem is basically that we often need to write something like:
data MyRow = MyRow { lots of fields }
myAction :: SqlPersistT IO [MyRow]
myAction = fmap (fmap convert) $ do
select $ do
from ...
where_ ...
pure (lots, of, rows, wow, so, many)
where
convert :: (Value Lots, Value Of, Value Rows, Value Wow, Value So, Value Many) -> MyRow
convert = ...
Tuples suck are are tedious and error prone. It'd be much nicer to use record syntax to construct things in a name-directed way.
myAction :: SqlPersistT IO [MyRow]
myAction = do
select $ do
from ...
where_ ...
pure MyRow
{ myRowLots = lots
, myRowOf = of
, myRowColums = columns -- whoops lmao
, myRowsWow = -- etc
, ..
}
The problem is that myRowLots :: Lots, and lots in the sql is SqlExpr (Value Lots). Type-error.
For Entity, the only way to actually get a hold of an SqlExpr (Entity a) is to use from and pull it out of a table. So that's how we avoid needing to construct an Entity for this.
It kinda sucks that we get nice syntax support for
Entity record
but for anything else we're stuck with tuples of values.Some ideas:
Higher Kinded Data
I'm not a fan of HKD, but it does work for this alright.
With a bit of
TemplateHaskell
the instances are relatively easy to generate.I recall having some super bad type error messages with this, particularly if
f
got inferred to be two different things in a record.Separate Records
We could have something like:
But there's some question about making constructors/types match without conflict, and how to deal with the record labels - keep them the same? suffix/prefix somehow?
Anonymous Records
Possibly
large-anon
could be used to return something sorta record-like, which we canSqlSelect
easily enough.I've certainly seen anon records with syntax like:
which may be pretty easy to map onto a
SqlSelect
instance.prairie
?Possibly we could leverage
prairie
. It is inspired byEntityField
.We'd be able to write, like,
Hmm. The problem is basically that we often need to write something like:
Tuples suck are are tedious and error prone. It'd be much nicer to use record syntax to construct things in a name-directed way.
The problem is that
myRowLots :: Lots
, andlots
in the sql isSqlExpr (Value Lots)
. Type-error.For
Entity
, the only way to actually get a hold of anSqlExpr (Entity a)
is to usefrom
and pull it out of a table. So that's how we avoid needing to construct an Entity for this.