Open derrickbeining opened 2 years ago
The RightEntity
that I'm trying to parse out of my left join query consists of more than one column, so I can't use the Only
wrapper.
Does my question make sense? Here's some example code
data LeftEntity = LeftEntity
{ leId :: Id LeftEntity
, leName :: Text
, leAssociatedRightEntityId :: Id RightEntity
}
deriving (Show, Generic, ToRow, FromRow)
data RightEntity = RightEntity
{ reId :: Id RightEntity
, reName :: Text
, reDescription :: Text
}
deriving (Show, Generic, ToRow, FromRow)
getLeftEntityWithAssociatedRightEntity = do
rows :: [ LeftEntity :. Maybe RightEntity ] <- runSelect [PGQ.sqlExp|
select
le.id,
le.name,
le.associated_right_entity_id,
re.id,
re.name,
re.uuid
from left_entity le
left join right_entity re on re.id = le.associated_right_entity_id
|]
pure $
flip fmap rows $ \(leftEntity :. mRightEntity) ->
(leftEntity, mRightEntity)
This doesn't compile because there's no instance of FromRow a => FromRow (Maybe a)
So I have to change Maybe RightEntity
to something that can collect all RightEntity fields (which are nullable in a left join) like
data RightEntityFields = RightEntityFields
{ reId :: Maybe (Id RightEntity)
, reName :: Maybe Text
, reDescription :: Maybe Text
}
deriving (Show, Generic, ToRow, FromRow)
And then make a function mkRightEntityFromFields :: RightEntityFields -> Maybe RightEntity
and map the results of my query to that.
I'm wanting to know if there's a way to avoid all that boilerplate.
I see there's something close to what I want but it's for tuples:
(FromField a, FromField b) => FromRow (Maybe (a, b))
Someone pointed out to me that I could try uncurrying the constructor I was wanting to parse into and map that over the Maybe (a, b, ...z)
to avoid having to define an intermediate structure.
So I could do something like
getLeftEntitiesWithAssociatedRightEntities = do
rows :: [ LeftEntity :. Maybe RightEntity ] <-
List.map go <$> runSelect [PGQ.sqlExp|
select
le.id,
le.name,
le.associated_right_entity_id,
re.id,
re.name,
re.uuid
from left_entity le
left join right_entity re on re.id = le.associated_right_entity_id
|]
pure $
flip fmap rows $ \(leftEntity :. mRightEntity) ->
(leftEntity, mRightEntity)
where
go (left :. maybeRight) = left :. fmap (uncurry3 RightEntity) maybeRight
That does seem to work and I like that much better than having to define an ad-hoc type to collect intermediate results. But I still wish I could just parse directly into Maybe RightEntity
without having to jump through extra hoops. Is there a way to achieve that? Perhaps a new feature to add to the library?
There is nothing wrong in defining ad-hoc types for specific purposes. Types are cheap.
Now as I think of it, the FromRow a => Maybe (FromRow a)
is impossible to define as FromRow
class doesn't tell how many columns it will consume. RowParser
consumes some columns, in LeftEntity :. Maybe RightEntity
specific case you know that Maybe
part should consume all the rest of columns. But in Maybe X .: Maybe Y
that would be wrong.
I see there's something close to what I want but it's for tuples:
(FromField a, FromField b) => FromRow (Maybe (a, b))
That's different. It has FromField
s as context, not FromRow
, so column count is known.
If you enable FlexibleInstances, you can define a Maybe instance for your type. Still a bit of boilerplate, but at least you don't need an intermediate type.
instance FromRow (Maybe RightEntity) where
fromRow = maybeRightEntity <$> field <*> field <*> field
maybeRightEntity :: Maybe (Id RightEntity) -> Maybe Text -> Maybe Text -> Maybe RightEntity
maybeRightEntity (Just a) (Just b) (Just c) = Just $ RightEntity a b c
maybeRightEntity = Nothing
I'm kind of new to haskell, so forgive me if the answer to this is obvious, but I was attempting to do a left join in order to get
rows :: [LeftEntity :. Maybe RightEntity]
but then was informed by the compiler that there's no instance forinstance FromRow a => FromRow (Maybe a)
. So I ended up having parse into an intermediate structure first like[LeftEntity :. IntermediateRightEntityResults]
and then transformIntermediateRightEntityResult
intoMaybe RightEntity
. I would really prefer to be able to parse directly into the Maybe, though. Is there reason there isn't an instance forFromRow (Maybe a)
? And is there perhaps some other way to do what I want? Or is parsing into a temporary intermediate structure just how things are supposed to be done?