module Hasql.Postgres.Mapping where
import Hasql.Postgres.Prelude hiding (bool)
import qualified Language.Haskell.TH as TH
import qualified Hasql.Postgres.PTI as PTI
import qualified Data.Vector as V
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text.Lazy as TL
import qualified PostgreSQLBinary.Array as Array
import qualified PostgreSQLBinary.Encoder as Encoder
import qualified PostgreSQLBinary.Decoder as Decoder
type Environment =
Bool
type Value =
Maybe ByteString
class Mapping a where
oid :: a -> PTI.OID
encode :: Environment -> a -> Value
decode :: Environment -> Value -> Either Text a
class ArrayMapping a where
arrayOID :: a -> PTI.OID
arrayEncode :: Environment -> a -> Array.Data
arrayDecode :: Environment -> Array.Data -> Either Text a
instance Mapping a => Mapping (Maybe a) where
oid =
const $ oid (undefined :: a)
encode e =
join . traverse (encode e)
decode e =
maybe (return Nothing) (fmap Just . (decode e) . Just)
instance (ArrayMapping a, Mapping a) => ArrayMapping (Maybe a) where
arrayOID =
const $ arrayOID (undefined :: a)
arrayEncode e =
\case
Nothing ->
Array.fromSingleton Nothing True (PTI.oidWord32 (oid (undefined :: a)))
Just x ->
setNullable True $ arrayEncode e x
where
setNullable x (dl, vl, _, oid) = (dl, vl, True, oid)
arrayDecode e =
\case
(_, [x], _, _) -> decode e x
x -> Left $ "Array data doesn't match the 'Maybe' type: " <> (fromString . show) x
instance (Mapping a, ArrayMapping a) => Mapping [a] where
oid =
arrayOID
encode e =
Just . Encoder.array . arrayEncode e
decode e x =
do
b <- maybe (Left "NULL input") return x
a <- Decoder.array b
arrayDecode e a
instance (Mapping a, ArrayMapping a) => ArrayMapping [a] where
arrayOID =
const $ arrayOID (undefined :: a)
arrayEncode e =
\case
[] -> ([(0, 1)], [], False, PTI.oidWord32 $ oid (undefined :: a))
x -> Array.fromListUnsafe . map (arrayEncode e) $ x
arrayDecode e x =
traverse (arrayDecode e) $ Array.elements x
instance (Mapping a, ArrayMapping a) => Mapping (Vector a) where
oid =
arrayOID
encode e =
Just . Encoder.array . arrayEncode e
decode e x =
do
b <- maybe (Left "NULL input") return x
a <- Decoder.array b
arrayDecode e a
instance (Mapping a, ArrayMapping a) => ArrayMapping (Vector a) where
arrayOID =
const $ arrayOID (undefined :: a)
arrayEncode e =
arrayEncode e . V.toList
arrayDecode e =
fmap V.fromList . arrayDecode e
let
settings =
[
(,,,)
[t|Int|]
[|PTI.int8|]
[|const $ Encoder.int8 . Left . fromIntegral|]
[|const $ Decoder.int|]
,
(,,,)
[t|Int8|]
[|PTI.int2|]
[|const $ Encoder.int2 . Left . fromIntegral|]
[|const $ Decoder.int|]
,
(,,,)
[t|Int16|]
[|PTI.int2|]
[|const $ Encoder.int2 . Left|]
[|const $ Decoder.int|]
,
(,,,)
[t|Int32|]
[|PTI.int4|]
[|const $ Encoder.int4 . Left|]
[|const $ Decoder.int|]
,
(,,,)
[t|Int64|]
[|PTI.int8|]
[|const $ Encoder.int8 . Left|]
[|const $ Decoder.int|]
,
(,,,)
[t|Word|]
[|PTI.int8|]
[|const $ Encoder.int8 . Right . fromIntegral|]
[|const $ Decoder.int|]
,
(,,,)
[t|Word8|]
[|PTI.int2|]
[|const $ Encoder.int2 . Right . fromIntegral|]
[|const $ Decoder.int|]
,
(,,,)
[t|Word16|]
[|PTI.int2|]
[|const $ Encoder.int2 . Right|]
[|const $ Decoder.int|]
,
(,,,)
[t|Word32|]
[|PTI.int4|]
[|const $ Encoder.int4 . Right|]
[|const $ Decoder.int|]
,
(,,,)
[t|Word64|]
[|PTI.int8|]
[|const $ Encoder.int8 . Right|]
[|const $ Decoder.int|]
,
(,,,)
[t|Float|]
[|PTI.float4|]
[|const $ Encoder.float4|]
[|const $ Decoder.float4|]
,
(,,,)
[t|Double|]
[|PTI.float8|]
[|const $ Encoder.float8|]
[|const $ Decoder.float8|]
,
(,,,)
[t|Scientific|]
[|PTI.numeric|]
[|const $ Encoder.numeric|]
[|const $ Decoder.numeric|]
,
(,,,)
[t|Day|]
[|PTI.date|]
[|const $ Encoder.date|]
[|const $ Decoder.date|]
,
(,,,)
[t|TimeOfDay|]
[|PTI.time|]
[|Encoder.time|]
[|Decoder.time|]
,
(,,,)
[t|(TimeOfDay, TimeZone)|]
[|PTI.timetz|]
[|Encoder.timetz|]
[|Decoder.timetz|]
,
(,,,)
[t|LocalTime|]
[|PTI.timestamp|]
[|Encoder.timestamp|]
[|Decoder.timestamp|]
,
(,,,)
[t|UTCTime|]
[|PTI.timestamptz|]
[|Encoder.timestamptz|]
[|Decoder.timestamptz|]
,
(,,,)
[t|DiffTime|]
[|PTI.interval|]
[|Encoder.interval|]
[|Decoder.interval|]
,
(,,,)
[t|Char|]
[|PTI.text|]
[|const $ Encoder.char|]
[|const $ Decoder.char|]
,
(,,,)
[t|Text|]
[|PTI.text|]
[|const $ Encoder.text . Left|]
[|const $ Decoder.text|]
,
(,,,)
[t|LazyText|]
[|PTI.text|]
[|const $ Encoder.text . Right|]
[|const $ fmap TL.fromStrict . Decoder.text|]
,
(,,,)
[t|ByteString|]
[|PTI.bytea|]
[|const $ Encoder.bytea . Left|]
[|const $ Decoder.bytea|]
,
(,,,)
[t|LazyByteString|]
[|PTI.bytea|]
[|const $ Encoder.bytea . Right|]
[|const $ fmap BL.fromStrict . Decoder.bytea|]
,
(,,,)
[t|Bool|]
[|PTI.bool|]
[|const $ Encoder.bool|]
[|const $ Decoder.bool|]
,
(,,,)
[t|UUID|]
[|PTI.uuid|]
[|const $ Encoder.uuid|]
[|const $ Decoder.uuid|]
]
in
fmap concat $ forM settings $ \(t, pti, encoder, decoder) ->
[d|
instance Mapping $t where
oid =
const $ PTI.ptiOID $pti
encode e =
Just . $encoder e
decode e x =
do
b <- maybe (Left "NULL input") return x
$decoder e b
instance ArrayMapping $t where
arrayOID =
const $ fromMaybe ($bug "No array OID") $ PTI.ptiArrayOID $pti
arrayEncode e x =
Array.fromSingleton (Just ($encoder e x))
(False)
(PTI.oidWord32 (PTI.ptiOID $pti))
arrayDecode e =
\case
(_, [x], _, _) -> decode e x
x -> Left $ "Array data doesn't match the '" <>
$(t >>= TH.stringE . show) <> "' type: " <>
(fromString . show) x
|]