@@ -0,0 +1,84 @@
1+{-# language DataKinds #-}
2+{-# language FlexibleContexts #-}
4+{-# language ScopedTypeVariables #-}
5+{-# language StandaloneKindSignatures #-}
6+{-# language TypeApplications #-}
7+{-# language TypeOperators #-}
8+{-# language UndecidableInstances #-}
10+module Rel8.Type.Composite
11+ ( Composite( Composite )
16+import Control.Applicative ( liftA2 )
17+import Data.Kind ( Constraint, Type )
18+import Data.Proxy ( Proxy( Proxy ) )
20+ ( Generic, Rep, from, to
21+ , (:*:)( (:*:) ), K1( K1 ), M1( M1 )
23+import GHC.TypeLits ( KnownSymbol, Symbol, symbolVal )
24+import Prelude hiding ( null )
27+import qualified Hasql.Decoders as Hasql
30+import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
33+import Rel8.Schema.Null ( Nullity( Null, NotNull ), Sql, nullable )
34+import Rel8.Type ( DBType, typeInformation )
35+import Rel8.Type.Information ( TypeInformation(..) )
38+-- | A deriving-via helper type for column types that store a Haskell product
39+-- type in a single Postgres column using a Postgres composite type.
41+-- Note that this must map to a specific extant type in your database's schema
42+-- (created with @CREATE TYPE@). The @name@ parameter of @Composite@ should be
43+-- the name of this type.
44+type Composite :: Symbol -> Type -> Type
45+newtype Composite name a = Composite
50+instance (KnownSymbol name, Generic a, GComposite (Rep a)) =>
51+ DBType (Composite name a)
53+ typeInformation = TypeInformation
54+ { decode = Hasql.composite (Composite . to <$> gdecode @(Rep a))
55+ , encode = Opaleye.FunExpr "ROW" . gencode @(Rep a) . from . unComposite
56+ , typeName = symbolVal (Proxy @name)
60+type GComposite :: (Type -> Type) -> Constraint
61+class GComposite rep where
62+ gdecode :: Hasql.Composite (rep x)
63+ gencode :: rep x -> [Opaleye.PrimExpr]
66+instance GComposite rep => GComposite (M1 i c rep) where
67+ gdecode = M1 <$> gdecode
68+ gencode (M1 a) = gencode a
71+instance (GComposite a, GComposite b) => GComposite (a :*: b) where
72+ gdecode = liftA2 (:*:) gdecode gdecode
73+ gencode (a :*: b) = gencode a <> gencode b
76+instance Sql DBType a => GComposite (K1 i a) where
77+ gdecode = fmap K1 $ Hasql.field $ case nullable @a of
78+ Null -> Hasql.nullable (decode typeInformation)
79+ NotNull -> Hasql.nonNullable (decode typeInformation)
80+ gencode (K1 a) = case nullable @a of
81+ Null -> [maybe null (encode typeInformation) a]
82+ NotNull -> [encode typeInformation a]
84+ null = Opaleye.ConstExpr Opaleye.NullLit