hasql/library/Hasql/Encoders.hs

559 lines
14 KiB
Haskell
Raw Normal View History

2015-11-21 16:42:12 +03:00
-- |
-- A DSL for declaration of query parameter encoders.
2015-12-05 09:09:31 +03:00
module Hasql.Encoders
2015-11-08 21:09:42 +03:00
(
-- * Params
Params,
2015-11-22 09:08:06 +03:00
unit,
2015-11-08 21:09:42 +03:00
value,
nullableValue,
-- * Value
Value,
bool,
int2,
int4,
int8,
float4,
float8,
numeric,
char,
text,
bytea,
date,
timestamp,
timestamptz,
time,
timetz,
interval,
uuid,
2017-03-14 18:11:42 +03:00
inet,
2015-11-08 21:09:42 +03:00
json,
2016-02-09 13:14:19 +03:00
jsonBytes,
2016-01-29 11:46:18 +03:00
jsonb,
2016-02-09 13:14:19 +03:00
jsonbBytes,
2015-11-08 21:09:42 +03:00
array,
2015-11-10 17:53:14 +03:00
enum,
2016-01-12 10:55:36 +03:00
unknown,
2015-11-08 21:09:42 +03:00
-- * Array
Array,
arrayValue,
arrayNullableValue,
arrayDimension,
-- ** Insert Many
-- $insertMany
2015-11-08 21:09:42 +03:00
)
where
import Hasql.Private.Prelude hiding (bool)
2017-03-20 23:13:21 +03:00
import qualified PostgreSQL.Binary.Encoding as A
import qualified PostgreSQL.Binary.Data as B
import qualified Text.Builder as C
import qualified Hasql.Private.Encoders.Params as Params
import qualified Hasql.Private.Encoders.Value as Value
import qualified Hasql.Private.Encoders.Array as Array
import qualified Hasql.Private.PTI as PTI
import qualified Hasql.Private.Prelude as Prelude
2015-11-08 21:09:42 +03:00
2015-11-21 13:36:01 +03:00
-- * Parameters Product Encoder
2015-11-08 21:09:42 +03:00
-------------------------
-- |
2015-11-21 13:36:01 +03:00
-- Encoder of some representation of the parameters product.
2015-11-08 21:09:42 +03:00
--
-- Has instances of 'Contravariant', 'Divisible' and 'Monoid',
-- which you can use to compose multiple parameters together.
-- E.g.,
--
2015-11-09 07:10:40 +03:00
-- @
2015-11-21 18:07:49 +03:00
-- someParamsEncoder :: 'Params' (Int64, Maybe Text)
2015-11-21 13:36:01 +03:00
-- someParamsEncoder =
2015-11-21 18:07:49 +03:00
-- 'contramap' 'fst' ('value' 'int8') '<>'
-- 'contramap' 'snd' ('nullableValue' 'text')
2015-11-09 07:10:40 +03:00
-- @
--
-- As a general solution for tuples of any arity, instead of 'fst' and 'snd',
2015-11-21 18:03:29 +03:00
-- consider the functions of the @contrazip@ family
-- from the \"contravariant-extras\" package.
-- E.g., here's how you can achieve the same as the above:
2015-11-09 07:10:40 +03:00
--
-- @
2015-11-21 18:07:49 +03:00
-- someParamsEncoder :: 'Params' (Int64, Maybe Text)
2015-11-21 13:36:01 +03:00
-- someParamsEncoder =
2015-11-21 18:07:49 +03:00
-- 'contrazip2' ('value' 'int8') ('nullableValue' 'text')
2015-11-09 07:10:40 +03:00
-- @
2015-11-08 21:09:42 +03:00
--
2015-11-21 16:22:38 +03:00
-- Here's how you can implement encoders for custom composite types:
--
-- @
-- data Person =
-- Person { name :: Text, gender :: Gender, age :: Int }
--
-- data Gender =
-- Male | Female
--
2015-11-21 18:07:49 +03:00
-- personParams :: 'Params' Person
2015-11-21 16:22:38 +03:00
-- personParams =
2015-11-21 18:07:49 +03:00
-- 'contramap' name ('value' 'text') '<>'
-- 'contramap' gender ('value' genderValue) '<>'
-- 'contramap' (fromIntegral . age) ('value' 'int8')
2015-11-21 16:22:38 +03:00
--
2015-11-21 18:07:49 +03:00
-- genderValue :: 'Value' Gender
2015-11-21 16:22:38 +03:00
-- genderValue =
2015-11-21 18:07:49 +03:00
-- 'contramap' genderText 'text'
2015-11-21 16:22:38 +03:00
-- where
-- genderText gender =
-- case gender of
-- Male -> "male"
-- Female -> "female"
-- @
--
2015-11-08 21:09:42 +03:00
newtype Params a =
Params (Params.Params a)
deriving (Contravariant, Divisible, Decidable, Monoid, Semigroup)
2015-11-08 21:09:42 +03:00
2015-11-22 09:08:06 +03:00
-- |
-- Encode no parameters.
--
{-# INLINABLE unit #-}
unit :: Params ()
unit =
Params mempty
2015-11-08 21:09:42 +03:00
-- |
2015-11-21 13:36:01 +03:00
-- Lift an individual value encoder to a parameters encoder.
2015-11-08 21:09:42 +03:00
--
{-# INLINABLE value #-}
value :: Value a -> Params a
value (Value x) =
Params (Params.value x)
-- |
2015-11-21 13:36:01 +03:00
-- Lift an individual nullable value encoder to a parameters encoder.
2015-11-08 21:09:42 +03:00
--
{-# INLINABLE nullableValue #-}
nullableValue :: Value a -> Params (Maybe a)
nullableValue (Value x) =
Params (Params.nullableValue x)
-- ** Instances
-------------------------
2015-11-22 09:08:06 +03:00
-- |
-- Maps to 'unit'.
2015-11-15 14:45:56 +03:00
instance Default (Params ()) where
{-# INLINE def #-}
def =
2015-11-22 09:08:06 +03:00
unit
2015-11-15 14:45:56 +03:00
2015-11-08 21:09:42 +03:00
instance Default (Value a) => Default (Params (Identity a)) where
{-# INLINE def #-}
def =
contramap runIdentity (value def)
instance (Default (Value a1), Default (Value a2)) => Default (Params (a1, a2)) where
{-# INLINE def #-}
def =
2015-11-22 09:07:58 +03:00
contrazip2 (value def) (value def)
instance (Default (Value a1), Default (Value a2), Default (Value a3)) => Default (Params (a1, a2, a3)) where
{-# INLINE def #-}
def =
contrazip3 (value def) (value def) (value def)
instance (Default (Value a1), Default (Value a2), Default (Value a3), Default (Value a4)) => Default (Params (a1, a2, a3, a4)) where
{-# INLINE def #-}
def =
contrazip4 (value def) (value def) (value def) (value def)
instance (Default (Value a1), Default (Value a2), Default (Value a3), Default (Value a4), Default (Value a5)) => Default (Params (a1, a2, a3, a4, a5)) where
{-# INLINE def #-}
def =
contrazip5 (value def) (value def) (value def) (value def) (value def)
2015-11-08 21:09:42 +03:00
2015-11-21 13:36:01 +03:00
-- * Value Encoder
2015-11-08 21:09:42 +03:00
-------------------------
-- |
2015-11-21 13:36:01 +03:00
-- An individual value encoder.
2015-11-08 21:09:42 +03:00
-- Will be mapped to a single placeholder in the query.
--
newtype Value a =
Value (Value.Value a)
deriving (Contravariant)
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @BOOL@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE bool #-}
bool :: Value Bool
bool =
Value (Value.unsafePTIWithShow PTI.bool (const A.bool))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @INT2@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE int2 #-}
int2 :: Value Int16
int2 =
Value (Value.unsafePTIWithShow PTI.int2 (const A.int2_int16))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @INT4@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE int4 #-}
int4 :: Value Int32
int4 =
Value (Value.unsafePTIWithShow PTI.int4 (const A.int4_int32))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @INT8@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE int8 #-}
int8 :: Value Int64
int8 =
Value (Value.unsafePTIWithShow PTI.int8 (const A.int8_int64))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @FLOAT4@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE float4 #-}
float4 :: Value Float
float4 =
Value (Value.unsafePTIWithShow PTI.float4 (const A.float4))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @FLOAT8@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE float8 #-}
float8 :: Value Double
float8 =
Value (Value.unsafePTIWithShow PTI.float8 (const A.float8))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @NUMERIC@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE numeric #-}
2017-03-20 23:13:21 +03:00
numeric :: Value B.Scientific
2015-11-08 21:09:42 +03:00
numeric =
Value (Value.unsafePTIWithShow PTI.numeric (const A.numeric))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @CHAR@ values.
-- Note that it supports UTF-8 values and
-- identifies itself under the @TEXT@ OID because of that.
2015-11-08 21:09:42 +03:00
{-# INLINABLE char #-}
char :: Value Char
char =
Value (Value.unsafePTIWithShow PTI.text (const A.char_utf8))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @TEXT@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE text #-}
text :: Value Text
text =
Value (Value.unsafePTIWithShow PTI.text (const A.text_strict))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @BYTEA@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE bytea #-}
bytea :: Value ByteString
bytea =
Value (Value.unsafePTIWithShow PTI.bytea (const A.bytea_strict))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @DATE@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE date #-}
2017-03-20 23:13:21 +03:00
date :: Value B.Day
2015-11-08 21:09:42 +03:00
date =
Value (Value.unsafePTIWithShow PTI.date (const A.date))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @TIMESTAMP@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE timestamp #-}
2017-03-20 23:13:21 +03:00
timestamp :: Value B.LocalTime
2015-11-08 21:09:42 +03:00
timestamp =
Value (Value.unsafePTIWithShow PTI.timestamp (Prelude.bool A.timestamp_float A.timestamp_int))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @TIMESTAMPTZ@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE timestamptz #-}
2017-03-20 23:13:21 +03:00
timestamptz :: Value B.UTCTime
2015-11-08 21:09:42 +03:00
timestamptz =
Value (Value.unsafePTIWithShow PTI.timestamptz (Prelude.bool A.timestamptz_float A.timestamptz_int))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @TIME@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE time #-}
2017-03-20 23:13:21 +03:00
time :: Value B.TimeOfDay
2015-11-08 21:09:42 +03:00
time =
Value (Value.unsafePTIWithShow PTI.time (Prelude.bool A.time_float A.time_int))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @TIMETZ@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE timetz #-}
2017-03-20 23:13:21 +03:00
timetz :: Value (B.TimeOfDay, B.TimeZone)
2015-11-08 21:09:42 +03:00
timetz =
Value (Value.unsafePTIWithShow PTI.timetz (Prelude.bool A.timetz_float A.timetz_int))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @INTERVAL@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE interval #-}
2017-03-20 23:13:21 +03:00
interval :: Value B.DiffTime
2015-11-08 21:09:42 +03:00
interval =
Value (Value.unsafePTIWithShow PTI.interval (Prelude.bool A.interval_float A.interval_int))
2015-11-08 21:09:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
-- Encoder of @UUID@ values.
2015-11-08 21:09:42 +03:00
{-# INLINABLE uuid #-}
2017-03-20 23:13:21 +03:00
uuid :: Value B.UUID
2015-11-08 21:09:42 +03:00
uuid =
Value (Value.unsafePTIWithShow PTI.uuid (const A.uuid))
2015-11-08 21:09:42 +03:00
2017-03-14 18:11:42 +03:00
-- |
-- Encoder of @INET@ values.
{-# INLINABLE inet #-}
2017-03-20 23:13:21 +03:00
inet :: Value (B.NetAddr B.IP)
2017-03-14 18:11:42 +03:00
inet =
Value (Value.unsafePTIWithShow PTI.inet (const A.inet))
2017-03-14 18:11:42 +03:00
2015-11-21 16:46:16 +03:00
-- |
2016-02-09 13:14:19 +03:00
-- Encoder of @JSON@ values from JSON AST.
2015-11-08 21:09:42 +03:00
{-# INLINABLE json #-}
2017-03-20 23:13:21 +03:00
json :: Value B.Value
2015-11-08 21:09:42 +03:00
json =
Value (Value.unsafePTIWithShow PTI.json (const A.json_ast))
2015-11-08 21:09:42 +03:00
2016-01-29 11:46:18 +03:00
-- |
2016-02-09 13:14:19 +03:00
-- Encoder of @JSON@ values from raw JSON.
{-# INLINABLE jsonBytes #-}
jsonBytes :: Value ByteString
jsonBytes =
Value (Value.unsafePTIWithShow PTI.json (const A.json_bytes))
2016-02-09 13:14:19 +03:00
-- |
-- Encoder of @JSONB@ values from JSON AST.
2016-01-29 11:46:18 +03:00
{-# INLINABLE jsonb #-}
2017-03-20 23:13:21 +03:00
jsonb :: Value B.Value
2016-01-29 11:46:18 +03:00
jsonb =
Value (Value.unsafePTIWithShow PTI.jsonb (const A.jsonb_ast))
2016-02-09 13:14:19 +03:00
-- |
-- Encoder of @JSONB@ values from raw JSON.
{-# INLINABLE jsonbBytes #-}
jsonbBytes :: Value ByteString
jsonbBytes =
Value (Value.unsafePTIWithShow PTI.jsonb (const A.jsonb_bytes))
2016-01-29 11:46:18 +03:00
2015-11-09 07:10:40 +03:00
-- |
2015-11-21 13:36:01 +03:00
-- Unlifts the 'Array' encoder to the plain 'Value' encoder.
2015-11-08 21:09:42 +03:00
{-# INLINABLE array #-}
array :: Array a -> Value a
array (Array (Array.Array valueOID arrayOID arrayEncoder renderer)) =
let
encoder env input =
A.array (PTI.oidWord32 valueOID) (arrayEncoder env input)
in Value (Value.Value arrayOID arrayOID encoder renderer)
2015-11-08 21:09:42 +03:00
2015-11-10 17:53:14 +03:00
-- |
-- Given a function,
-- which maps the value into the textual enum label from the DB side,
2015-11-21 13:36:01 +03:00
-- produces a encoder of that value.
2015-11-10 17:53:14 +03:00
{-# INLINABLE enum #-}
enum :: (a -> Text) -> Value a
enum mapping =
Value (Value.unsafePTI PTI.text (const (A.text_strict . mapping)) (C.text . mapping))
2015-11-10 17:53:14 +03:00
2016-01-12 10:55:36 +03:00
-- |
-- Identifies the value with the PostgreSQL's \"unknown\" type,
-- thus leaving it up to Postgres to infer the actual type of the value.
2017-11-01 20:47:05 +03:00
--
-- The value transimitted is any value encoded in the Postgres' Text data format.
-- For reference, see the
-- <https://www.postgresql.org/docs/10/static/protocol-overview.html#protocol-format-codes Formats and Format Codes>
-- section of the Postgres' documentation.
2016-01-12 10:55:36 +03:00
{-# INLINABLE unknown #-}
unknown :: Value ByteString
unknown =
Value (Value.unsafePTIWithShow PTI.unknown (const A.bytea_strict))
2016-01-12 10:55:36 +03:00
2015-11-08 21:09:42 +03:00
-- ** Instances
-------------------------
-- | Maps to 'bool'.
instance Default (Value Bool) where
{-# INLINE def #-}
def =
bool
-- | Maps to 'int2'.
instance Default (Value Int16) where
{-# INLINE def #-}
def =
int2
-- | Maps to 'int4'.
instance Default (Value Int32) where
{-# INLINE def #-}
def =
int4
-- | Maps to 'int8'.
instance Default (Value Int64) where
{-# INLINE def #-}
def =
int8
-- | Maps to 'float4'.
instance Default (Value Float) where
{-# INLINE def #-}
def =
float4
-- | Maps to 'float8'.
instance Default (Value Double) where
{-# INLINE def #-}
def =
float8
-- | Maps to 'numeric'.
2017-03-20 23:13:21 +03:00
instance Default (Value B.Scientific) where
2015-11-08 21:09:42 +03:00
{-# INLINE def #-}
def =
numeric
-- | Maps to 'char'.
instance Default (Value Char) where
{-# INLINE def #-}
def =
char
-- | Maps to 'text'.
instance Default (Value Text) where
{-# INLINE def #-}
def =
text
-- | Maps to 'bytea'.
instance Default (Value ByteString) where
{-# INLINE def #-}
def =
bytea
-- | Maps to 'date'.
2017-03-20 23:13:21 +03:00
instance Default (Value B.Day) where
2015-11-08 21:09:42 +03:00
{-# INLINE def #-}
def =
date
-- | Maps to 'timestamp'.
2017-03-20 23:13:21 +03:00
instance Default (Value B.LocalTime) where
2015-11-08 21:09:42 +03:00
{-# INLINE def #-}
def =
timestamp
-- | Maps to 'timestamptz'.
2017-03-20 23:13:21 +03:00
instance Default (Value B.UTCTime) where
2015-11-08 21:09:42 +03:00
{-# INLINE def #-}
def =
timestamptz
-- | Maps to 'time'.
2017-03-20 23:13:21 +03:00
instance Default (Value B.TimeOfDay) where
2015-11-08 21:09:42 +03:00
{-# INLINE def #-}
def =
time
-- | Maps to 'timetz'.
2017-03-20 23:13:21 +03:00
instance Default (Value (B.TimeOfDay, B.TimeZone)) where
2015-11-08 21:09:42 +03:00
{-# INLINE def #-}
def =
timetz
-- | Maps to 'interval'.
2017-03-20 23:13:21 +03:00
instance Default (Value B.DiffTime) where
2015-11-08 21:09:42 +03:00
{-# INLINE def #-}
def =
interval
-- | Maps to 'uuid'.
2017-03-20 23:13:21 +03:00
instance Default (Value B.UUID) where
2015-11-08 21:09:42 +03:00
{-# INLINE def #-}
def =
uuid
-- | Maps to 'json'.
2017-03-20 23:13:21 +03:00
instance Default (Value B.Value) where
2015-11-08 21:09:42 +03:00
{-# INLINE def #-}
def =
json
-- * Array
-------------------------
2015-11-09 07:10:40 +03:00
-- |
2015-11-21 13:36:01 +03:00
-- A generic array encoder.
2015-11-09 07:10:40 +03:00
--
-- Here's an example of its usage:
--
-- >x :: Value [[Int64]]
-- >x =
-- > array (arrayDimension foldl' (arrayDimension foldl' (arrayValue int8)))
--
-- Please note that the PostgreSQL __IN__ keyword does not "accept" an array, but rather a syntactical list of
-- values, thus this encoder is not suited for that. Use a **field** = ANY($1) query instead.
--
2015-11-08 21:09:42 +03:00
newtype Array a =
Array (Array.Array a)
2015-11-09 07:10:40 +03:00
-- |
2015-11-21 13:36:01 +03:00
-- Lifts the 'Value' encoder into the 'Array' encoder of a non-nullable value.
2015-11-08 21:09:42 +03:00
{-# INLINABLE arrayValue #-}
arrayValue :: Value a -> Array a
arrayValue (Value (Value.Value elementOID arrayOID encoder renderer)) =
Array (Array.value elementOID arrayOID encoder renderer)
2015-11-08 21:09:42 +03:00
2015-11-09 07:10:40 +03:00
-- |
2015-11-21 13:36:01 +03:00
-- Lifts the 'Value' encoder into the 'Array' encoder of a nullable value.
2015-11-08 21:09:42 +03:00
{-# INLINABLE arrayNullableValue #-}
arrayNullableValue :: Value a -> Array (Maybe a)
arrayNullableValue (Value (Value.Value elementOID arrayOID encoder renderer)) =
Array (Array.nullableValue elementOID arrayOID encoder renderer)
2015-11-08 21:09:42 +03:00
2015-11-09 07:10:40 +03:00
-- |
2015-11-22 10:42:18 +03:00
-- An encoder of an array dimension,
2015-11-09 07:10:40 +03:00
-- which thus provides support for multidimensional arrays.
--
-- Accepts:
--
-- * An implementation of the left-fold operation,
-- such as @Data.Foldable.'foldl''@,
-- which determines the input value.
--
2015-11-21 13:36:01 +03:00
-- * A component encoder, which can be either another 'arrayDimension',
2015-11-09 07:10:40 +03:00
-- 'arrayValue' or 'arrayNullableValue'.
--
2015-11-08 21:09:42 +03:00
{-# INLINABLE arrayDimension #-}
arrayDimension :: (forall a. (a -> b -> a) -> a -> c -> a) -> Array b -> Array c
arrayDimension foldl (Array imp) =
Array (Array.dimension foldl imp)
-- $insertMany
-- It is not currently possible to pass in an array of encodable values
-- to use in an 'insert many' query using Hasql. Instead, PostgreSQL's
-- (9.4 or later) `unnest` function can be used to in an analogous way
-- to haskell's `zip` function by passing in multiple arrays of values
-- to be zipped into the rows we want to insert:
--
-- @
-- insertMultipleLocations :: Query (Vector (UUID, Double, Double)) ()
-- insertMultipleLocations =
-- statement sql encoder decoder True
-- where
-- sql =
-- "insert into location (id, x, y) select * from unnest ($1, $2, $3)"
-- encoder =
-- contramap Vector.unzip3 $
-- contrazip3 (vector Encoders.uuid) (vector Encoders.float8) (vector Encoders.float8)
-- where
-- vector value =
-- Encoders.value (Encoders.array (Encoders.arrayDimension foldl' (Encoders.arrayValue value)))
-- decoder =
-- Decoders.unit
-- @