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