{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFunctor #-}
module Preql.Wire.FromSql where
import Preql.Wire.Errors
import Preql.Wire.Internal
import Preql.Wire.Tuples (deriveFromSqlTuple)
import Preql.Wire.Types
import Control.Monad.Except
import Control.Monad.Trans.State
import Data.Int
import Data.Time (Day, TimeOfDay, UTCTime)
import Data.UUID (UUID)
import Preql.Imports
import qualified BinaryParser as BP
import qualified Data.Aeson as JSON
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Vector as V
import qualified Database.PostgreSQL.LibPQ as PQ
import qualified PostgreSQL.Binary.Decoding as PGB
import qualified Preql.Wire.TypeInfo.Static as OID
data FieldDecoder a = FieldDecoder PQ.Oid (BP.BinaryParser a)
deriving Functor
throwLocated :: UnlocatedFieldError -> InternalDecoder a
throwLocated failure = do
DecoderState{row = PQ.Row r, column = PQ.Col c} <- get
throwError (FieldError (fromIntegral r) (fromIntegral c) failure)
decodeVector :: RowDecoder a -> PQ.Result -> IO (Either QueryError (Vector a))
decodeVector rd@(RowDecoder oids _parsers) result = do
mismatches <- fmap catMaybes $ for (zip [0 ..] oids) $ \(column@(PQ.Col cint), expected) -> do
actual <- liftIO $ PQ.ftype result column
if actual == expected
then return Nothing
else do
m_name <- liftIO $ PQ.fname result column
let columnName = decodeUtf8With lenientDecode <$> m_name
return $ Just (TypeMismatch{column = fromIntegral cint, ..})
if not (null mismatches)
then return (Left (PgTypeMismatch mismatches))
else do
(PQ.Row ntuples) <- liftIO $ PQ.ntuples result
let toRow = PQ.toRow . fromIntegral
fmap (first DecoderError) . runExceptT $
V.generateM (fromIntegral ntuples) (decodeRow rd result . toRow)
notNull :: FieldDecoder a -> RowDecoder a
notNull (FieldDecoder oid parser) = RowDecoder [oid] $ do
m_bs <- getNextValue
case m_bs of
Nothing -> throwLocated UnexpectedNull
Just bs -> either (throwLocated . ParseFailure) pure (BP.run parser bs)
nullable :: FieldDecoder a -> RowDecoder (Maybe a)
nullable (FieldDecoder oid parser) = RowDecoder [oid] $ do
m_bs <- getNextValue
case m_bs of
Nothing -> return Nothing
Just bs -> either (throwLocated . ParseFailure) (pure . Just) (BP.run parser bs)
class FromSqlField a where
fromSqlField :: FieldDecoder a
class FromSql a where
fromSql :: RowDecoder a
instance FromSqlField Bool where
fromSqlField = FieldDecoder OID.boolOid PGB.bool
instance FromSql Bool where fromSql = notNull fromSqlField
instance FromSqlField Int16 where
fromSqlField = FieldDecoder OID.int2Oid PGB.int
instance FromSql Int16 where fromSql = notNull fromSqlField
instance FromSqlField Int32 where
fromSqlField = FieldDecoder OID.int4Oid PGB.int
instance FromSql Int32 where fromSql = notNull fromSqlField
instance FromSqlField Int64 where
fromSqlField = FieldDecoder OID.int8Oid PGB.int
instance FromSql Int64 where fromSql = notNull fromSqlField
instance FromSqlField Float where
fromSqlField = FieldDecoder OID.float4Oid PGB.float4
instance FromSql Float where fromSql = notNull fromSqlField
instance FromSqlField Double where
fromSqlField = FieldDecoder OID.float8Oid PGB.float8
instance FromSql Double where fromSql = notNull fromSqlField
instance FromSqlField String where
fromSqlField = FieldDecoder OID.textOid (T.unpack <$> PGB.text_strict)
instance FromSql String where fromSql = notNull fromSqlField
instance FromSqlField Text where
fromSqlField = FieldDecoder OID.textOid PGB.text_strict
instance FromSql Text where fromSql = notNull fromSqlField
instance FromSqlField TL.Text where
fromSqlField = FieldDecoder OID.textOid PGB.text_lazy
instance FromSql TL.Text where fromSql = notNull fromSqlField
instance FromSqlField ByteString where
fromSqlField = FieldDecoder OID.byteaOid (BS.copy <$> BP.remainders)
instance FromSql ByteString where fromSql = notNull fromSqlField
instance FromSqlField BSL.ByteString where
fromSqlField = FieldDecoder OID.byteaOid (BSL.fromStrict . BS.copy <$> BP.remainders)
instance FromSql BSL.ByteString where fromSql = notNull fromSqlField
instance FromSqlField UTCTime where
fromSqlField = FieldDecoder OID.timestamptzOid PGB.timestamptz_int
instance FromSql UTCTime where fromSql = notNull fromSqlField
instance FromSqlField Day where
fromSqlField = FieldDecoder OID.dateOid PGB.date
instance FromSql Day where fromSql = notNull fromSqlField
instance FromSqlField TimeOfDay where
fromSqlField = FieldDecoder OID.timeOid PGB.time_int
instance FromSql TimeOfDay where fromSql = notNull fromSqlField
instance FromSqlField TimeTZ where
fromSqlField = FieldDecoder OID.timetzOid (uncurry TimeTZ <$> PGB.timetz_int)
instance FromSql TimeTZ where fromSql = notNull fromSqlField
instance FromSqlField UUID where
fromSqlField = FieldDecoder OID.uuidOid PGB.uuid
instance FromSql UUID where fromSql = notNull fromSqlField
instance FromSqlField JSON.Value where
fromSqlField = FieldDecoder OID.jsonbOid PGB.jsonb_ast
instance FromSql JSON.Value where fromSql = notNull fromSqlField
fromSqlJsonField :: JSON.FromJSON a => FieldDecoder a
fromSqlJsonField = FieldDecoder OID.jsonbOid
(PGB.jsonb_bytes (first T.pack . JSON.eitherDecode . BSL.fromStrict))
instance {-# OVERLAPPABLE #-} FromSqlField a => FromSql (Maybe a) where
fromSql = nullable fromSqlField
instance (FromSql a, FromSql b) => FromSql (a, b) where
fromSql = (,) <$> fromSql <*> fromSql
instance (FromSql a, FromSql b, FromSql c) => FromSql (a, b, c) where
fromSql = (,,) <$> fromSql <*> fromSql <*> fromSql
$(deriveFromSqlTuple 4)
$(deriveFromSqlTuple 5)
$(deriveFromSqlTuple 6)
$(deriveFromSqlTuple 7)
$(deriveFromSqlTuple 8)
$(deriveFromSqlTuple 9)
$(deriveFromSqlTuple 10)
$(deriveFromSqlTuple 11)
$(deriveFromSqlTuple 12)
$(deriveFromSqlTuple 13)
$(deriveFromSqlTuple 14)
$(deriveFromSqlTuple 15)
$(deriveFromSqlTuple 16)
$(deriveFromSqlTuple 17)
$(deriveFromSqlTuple 18)
$(deriveFromSqlTuple 19)
$(deriveFromSqlTuple 20)
$(deriveFromSqlTuple 21)
$(deriveFromSqlTuple 22)
$(deriveFromSqlTuple 23)
$(deriveFromSqlTuple 24)
$(deriveFromSqlTuple 25)