module Database.Seakale.PostgreSQL.FromRow
( module Database.Seakale.FromRow
) where
import Data.Monoid
import Data.Time
import qualified Data.ByteString.Char8 as BS
import Database.Seakale.FromRow
import Database.Seakale.PostgreSQL
instance FromRow PSQL One Bool where
fromRow = pconsume `pbind` \(_, f) -> case fieldValue f of
Nothing -> pfail "unexpected NULL"
Just "t" -> preturn True
Just "f" -> preturn False
Just bs -> pfail $ "unreadable boolean: " ++ BS.unpack bs
instance FromRow PSQL One UTCTime where
fromRow = pconsume `pbind` \(ColumnInfo{..}, Field{..}) ->
case (colInfoType, fieldValue) of
("timestamp", Just bs) ->
case parseTimeM True defaultTimeLocale "%F %T%Q" (BS.unpack bs) of
Just t -> preturn t
Nothing -> pfail $ "invalid time: " ++ BS.unpack bs
("timestamptz", Just bs) ->
case parseTimeM True defaultTimeLocale "%F %T%Q%z"
(BS.unpack bs ++ "00") of
Just t -> preturn t
Nothing -> pfail $ "invalid time: " ++ BS.unpack bs
(bs, Just _) -> pfail $ "invalid type for time: " ++ BS.unpack bs
(_, Nothing) -> pfail "unexpected NULL for time"
instance FromRow PSQL One String where
fromRow = pmap BS.unpack fromRow
instance FromRow PSQL One a => FromRow PSQL One [a] where
fromRow = pconsume `pbind` \(col@ColumnInfo{..}, Field{..}) ->
case (BS.splitAt 1 colInfoType, fieldValue) of
(("_", typ), Just bs) ->
pbackend `pbind` \backend ->
arrayParser backend (col { colInfoType = typ }) bs
(_, Just _) -> pfail $ "invalid type for list: " ++ BS.unpack colInfoType
(_, Nothing) -> pfail "unexpected NULL for list"
arrayParser :: FromRow PSQL One a => PSQL -> ColumnInfo PSQL -> BS.ByteString
-> RowParser PSQL Zero [a]
arrayParser backend col = either pfail preturn . go
where
go :: FromRow PSQL One a => BS.ByteString -> Either String [a]
go bs = case BS.splitAt 1 bs of
("{", "}") -> return []
("{", bs') -> readValues id bs'
_ -> Left $ "invalid array starting with " ++ show (BS.take 30 bs)
readValues :: FromRow PSQL One a => ([a] -> [a]) -> BS.ByteString
-> Either String [a]
readValues f bs = do
(valBS, bs') <- readByteString bs
let mValBS = if valBS == "NULL" then Nothing else Just valBS
val <- parseRow fromRow backend [col] [Field mValBS]
case BS.splitAt 1 bs' of
(",", bs'') -> readValues (f . (val :)) bs''
("}", "") -> return $! f [val]
_ -> Left $ "invalid array around " ++ show (BS.take 30 bs')
readByteString :: BS.ByteString
-> Either String (BS.ByteString, BS.ByteString)
readByteString bs = case BS.splitAt 1 bs of
("\"", bs') -> readByteString' "" bs'
_ -> return $ BS.span (\c -> c /= ',' && c /= '}') bs
readByteString' :: BS.ByteString -> BS.ByteString
-> Either String (BS.ByteString, BS.ByteString)
readByteString' acc bs =
case fmap (BS.splitAt 1) (BS.span (\c -> c /= '"' && c /= '\\') bs) of
(bs', ("\"", bs'')) -> return (acc <> bs', bs'')
(bs', ("\\", bs'')) -> let (c, bs''') = BS.splitAt 1 bs''
in readByteString' (acc <> bs' <> c) bs'''
(bs', _) -> Left $ "unreadable value around " ++ show (BS.take 30 bs')