@@ -9,7 +9,8 @@ Binary encoding and decoding between Haskell and PostgreSQL types.
11{-# LANGUAGE
11{-# LANGUAGE
12− DeriveFoldable
13+ , DeriveFoldable
13 , DeriveFunctor
14 , DeriveFunctor
14 , DeriveGeneric
15 , DeriveGeneric
15 , DeriveTraversable
16 , DeriveTraversable
@@ -39,9 +40,11 @@ module Squeal.PostgreSQL.Binary
41import BinaryParser
42import BinaryParser
43+import ByteString.StrictBuilder
42import Data.Aeson hiding (Null)
44import Data.Aeson hiding (Null)
43import Data.Int
45import Data.Int
44import Data.Kind
46import Data.Kind
47+import Data.Monoid hiding (All)
45import Data.Scientific
48import Data.Scientific
46import Data.Time
49import Data.Time
47import Data.UUID.Types
50import Data.UUID.Types
@@ -119,13 +122,66 @@ instance (HasOid pg, ToParam x pg)
120 ( IsEnumType x
123 ( IsEnumType x
122− -- , SameLabels (DatatypeInfoOf y) labels
125+ , SameLabels (DatatypeInfoOf x) labels
124− -- uncomment above constraint when GHC issue #11671 is fixed
125− -- Allow labels starting with uppercase with OverloadedLabels
126− -- https://ghc.haskell.org/trac/ghc/ticket/11671?cversion=0&cnum_hist=8
127 ) => ToParam x ('PGenum labels) where
126 ) => ToParam x ('PGenum labels) where
128 toParam = K . Encoding.text_strict . Strict.pack . show
127 toParam = K . Encoding.text_strict . Strict.pack . show
131+ , IsProductType x (Maybes xs)
132+ , AllZip ToAliasedParam xs fields
133+ , SameFields (DatatypeInfoOf x) fields
134+ , All HasAliasedOid fields
135+ ) => ToParam x ('PGcomposite fields) where
139+ encoders = htrans (Proxy @ToAliasedParam) toAliasedParam
142+ :: All HasAliasedOid row
143+ => NP (K (Maybe Encoding.Encoding)) row
144+ -> K Encoding.Encoding ('PGcomposite row)
145+ composite fields = K $
146+ -- <number of fields: 4 bytes>
148+ -- <OID of field's type: sizeof(Oid) bytes>
149+ -- [if value is NULL]
152+ -- <length of value: 4 bytes>
153+ -- <value: <length> bytes>
156+ int32BE (fromIntegral (lengthSList (Proxy @xs))) <>
159+ :: HasAliasedOid field
160+ => K (Maybe Encoding.Encoding) field
161+ -> Encoding.Encoding
162+ each (K field :: K (Maybe Encoding.Encoding) field) =
163+ word32BE (aliasedOid @field)
165+ Nothing -> int64BE (-1)
167+ int32BE (fromIntegral (builderLength value))
170+ hcfoldMap (Proxy @HasAliasedOid) each fields
173+ composite . encoders . unMaybes . unZ . unSOP . from
175+class HasAliasedOid (field :: (Symbol, PGType)) where aliasedOid :: Word32
176+instance HasOid ty => HasAliasedOid (alias ::: ty) where aliasedOid = oid @ty
178+class ToAliasedParam (x :: Type) (field :: (Symbol, PGType)) where
179+ toAliasedParam :: Maybe x -> K (Maybe Encoding.Encoding) field
180+instance ToParam x ty => ToAliasedParam x (alias ::: ty) where
181+ toAliasedParam = \case
182+ Nothing -> K Nothing
183+ Just x -> K . Just . unK $ toParam @x @ty x
129-- | A `ToColumnParam` constraint lifts the `ToParam` encoding
185-- | A `ToColumnParam` constraint lifts the `ToParam` encoding
130-- of a `Type` to a `ColumnType`, encoding `Maybe`s to `Null`s. You should
186-- of a `Type` to a `ColumnType`, encoding `Maybe`s to `Null`s. You should
131-- not define instances of `ToColumnParam`, just use the provided instances.
187-- not define instances of `ToColumnParam`, just use the provided instances.
@@ -231,6 +287,7 @@ instance
231 (hpure Proxy :: NP Proxy pgs)
287 (hpure Proxy :: NP Proxy pgs)
233 composite fields = do
289 composite fields = do
290+ -- <number of fields: 4 bytes>
234 -- [for each field]
291 -- [for each field]
235 -- <OID of field's type: sizeof(Oid) bytes>
292 -- <OID of field's type: sizeof(Oid) bytes>
236 -- [if value is NULL]
293 -- [if value is NULL]
@@ -240,7 +297,6 @@ instance
240 -- <value: <length> bytes>
297 -- <value: <length> bytes>
241 -- [end if]
298 -- [end if]
242 -- [end for]
299 -- [end for]
243− -- <number of fields: 4 bytes>
244 unitOfSize 4
300 unitOfSize 4
246 each field = do
302 each field = do
@@ -274,10 +330,10 @@ instance FromValue pg y
274 => FromColumnValue (column ::: ('NotNull pg)) y where
330 => FromColumnValue (column ::: ('NotNull pg)) y where
275 fromColumnValue = \case
331 fromColumnValue = \case
276 K Nothing -> error "fromColumnValue: saw NULL when expecting NOT NULL"
332 K Nothing -> error "fromColumnValue: saw NULL when expecting NOT NULL"
277− K (Just bytes) ->
333+ K (Just bs) ->
279 errOrValue =
335 errOrValue =
280− Decoding.valueParser (fromValue @pg @y Proxy) bytes
336+ Decoding.valueParser (fromValue @pg @y Proxy) bs
281 err str = error $ "fromColumnValue: " ++ Strict.unpack str
337 err str = error $ "fromColumnValue: " ++ Strict.unpack str
283 either err id errOrValue
339 either err id errOrValue