module Database.PostgreSQL.Simple.FromRow.Named
(
gFromRow
, fieldByName
, fieldByNameWith
, NoSuchColumn(..)
, TooManyColumns(..)
) where
import Control.Exception
import Control.Monad.Extra
import Control.Monad.Reader
import Control.Monad.State.Strict
import Data.ByteString (ByteString)
import qualified Data.ByteString.UTF8 as BS
import Data.Typeable
import qualified Database.PostgreSQL.LibPQ as PQ
import Database.PostgreSQL.Simple.FromField hiding (name)
import Database.PostgreSQL.Simple.FromRow
import Database.PostgreSQL.Simple.Internal
import GHC.TypeLits
import Generics.SOP
import qualified Generics.SOP.Type.Metadata as T
npLength :: NP f xs -> Word
npLength xs = go 0 xs
where
go :: Word -> NP f xs -> Word
go !i Nil = i
go !i (_ :* xs') = go (i + 1) xs'
gFromRow :: forall a modName tyName constrName fields xs.
( Generic a
, HasDatatypeInfo a
, All2 FromField (Code a)
, KnownSymbol modName
, KnownSymbol tyName
, DatatypeInfoOf a ~ 'T.ADT modName tyName '[ 'T.Record constrName fields]
, Code a ~ '[xs]
, T.DemoteFieldInfos fields xs
) => RowParser a
gFromRow = do
let f :: forall f. FromField f => FieldInfo f -> RowParser f
f (FieldInfo name) = fieldByName (BS.fromString name)
fieldInfos :: NP FieldInfo xs
fieldInfos = T.demoteFieldInfos (Proxy @fields)
guardMatchingColumnNumber (npLength fieldInfos)
res <-
fmap (to . SOP . Z) $
hsequence
(hcliftA
(Proxy :: Proxy FromField)
f
(T.demoteFieldInfos (Proxy :: Proxy fields)))
setToLastCol
pure res
guardMatchingColumnNumber :: Word -> RowParser ()
guardMatchingColumnNumber numFields =
RP $ do
Row {rowresult} <- ask
PQ.Col (fromIntegral -> numCols) <- liftIO' (PQ.nfields rowresult)
when
(numCols /= numFields)
((lift . lift . conversionError) (TooManyColumns numFields numCols))
liftIO' :: IO a -> ReaderT Row (StateT PQ.Column Conversion) a
liftIO' = lift . lift . liftConversion
data NoSuchColumn =
NoSuchColumn ByteString
deriving (Show, Eq, Ord, Typeable)
instance Exception NoSuchColumn
data TooManyColumns = TooManyColumns
{ numRecordFields :: !Word
, numColumns :: !Word
} deriving (Show, Eq, Ord, Typeable)
instance Exception TooManyColumns
fieldByNameWith :: FieldParser a -> ByteString -> RowParser a
fieldByNameWith fieldP name =
RP $ do
Row {rowresult, row} <- ask
ncols <- liftIO' (PQ.nfields rowresult)
matchingCol <-
liftIO' $
findM
(\col -> (Just name ==) <$> PQ.fname rowresult col)
[PQ.Col 0 .. ncols 1]
case matchingCol of
Nothing -> (lift . lift . conversionError) (NoSuchColumn name)
Just col ->
(lift . lift) $ do
oid <- liftConversion (PQ.ftype rowresult col)
val <- liftConversion (PQ.getvalue rowresult row col)
fieldP (Field rowresult col oid) val
fieldByName :: FromField a => ByteString -> RowParser a
fieldByName = fieldByNameWith fromField
setToLastCol :: RowParser ()
setToLastCol =
RP $ do
Row {rowresult} <- ask
ncols <- liftIO' (PQ.nfields rowresult)
put ncols