@@ -1,27 +1,27 @@
1+{-# language AllowAmbiguousTypes #-}
2+{-# language BlockArguments #-}
1{-# language DataKinds #-}
3{-# language DataKinds #-}
2{-# language FlexibleContexts #-}
4{-# language FlexibleContexts #-}
3{-# language GADTs #-}
5{-# language GADTs #-}
6+{-# language NamedFieldPuns #-}
4{-# language ScopedTypeVariables #-}
7{-# language ScopedTypeVariables #-}
5{-# language StandaloneKindSignatures #-}
8{-# language StandaloneKindSignatures #-}
6{-# language TypeApplications #-}
9{-# language TypeApplications #-}
7−{-# language TypeOperators #-}
8{-# language UndecidableInstances #-}
10{-# language UndecidableInstances #-}
11+{-# language UndecidableSuperClasses #-}
12+{-# language ViewPatterns #-}
10module Rel8.Type.Composite
14module Rel8.Type.Composite
11 ( Composite( Composite )
15 ( Composite( Composite )
16+ , DBComposite( compositeFields, compositeTypeName )
16−import Control.Applicative ( liftA2 )
22+import Data.Functor.Const ( Const( Const ), getConst )
17import Data.Kind ( Constraint, Type )
23import Data.Kind ( Constraint, Type )
18−import Data.Proxy ( Proxy( Proxy ) )
24+import Prelude
20− ( Generic, Rep, from, to
21− , (:*:)( (:*:) ), K1( K1 ), M1( M1 )
23−import GHC.TypeLits ( KnownSymbol, Symbol, symbolVal )
24−import Prelude hiding ( null )
27import qualified Hasql.Decoders as Hasql
27import qualified Hasql.Decoders as Hasql
@@ -30,55 +30,85 @@ import qualified Hasql.Decoders as Hasql
30import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
30import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
33+import Rel8.Expr ( Col( DB ), Expr )
34+import Rel8.Expr.Opaleye ( castExpr, fromPrimExpr, toPrimExpr )
35+import Rel8.Schema.HTable ( hfield, hspecs, htabulate, htabulateA )
36+import Rel8.Schema.Name ( Col( NameCol ), Name )
33−import Rel8.Schema.Null ( Nullity( Null, NotNull ), Sql, nullable )
37+import Rel8.Schema.Null ( Nullity( Null, NotNull ) )
38+import Rel8.Schema.Result ( Col( Result ), Result )
39+import Rel8.Schema.Spec ( SSpec( SSpec, nullity, info ) )
40+import Rel8.Table ( Table, fromColumns, toColumns )
41+import Rel8.Table.Eq ( EqTable )
42+import Rel8.Table.HKD ( HKD, HKDable, fromHKD, toHKD )
43+import Rel8.Table.Ord ( OrdTable )
44+import Rel8.Table.Rel8able ()
45+import Rel8.Table.Serialize ( lit )
34import Rel8.Type ( DBType, typeInformation )
46import Rel8.Type ( DBType, typeInformation )
47+import Rel8.Type.Eq ( DBEq )
35import Rel8.Type.Information ( TypeInformation(..) )
48import Rel8.Type.Information ( TypeInformation(..) )
49+import Rel8.Type.Ord ( DBOrd )
52+import Data.Functor.Apply ( WrappedApplicative(..) )
38-- | A deriving-via helper type for column types that store a Haskell product
55-- | 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.
56-- 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
58-- 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
59+-- (created with @CREATE TYPE@). Use 'DBComposite' to specify the name of this
43−-- the name of this type.
60+-- Postgres type and the names of the individual fields (for projecting with
44−type Composite :: Symbol -> Type -> Type
62+type Composite :: Type -> Type
45−newtype Composite name a = Composite
63+newtype Composite a = Composite
46 { unComposite :: a
64 { unComposite :: a
50−instance (KnownSymbol name, Generic a, GComposite (Rep a)) =>
51− DBType (Composite name a)
68+instance DBComposite a => DBType (Composite a) where
53 typeInformation = TypeInformation
69 typeInformation = TypeInformation
54− { decode = Hasql.composite (Composite . to <$> gdecode @(Rep a))
70+ { decode = Hasql.composite (Composite . fromHKD <$> decoder)
55− , encode = Opaleye.FunExpr "ROW" . gencode @(Rep a) . from . unComposite
71+ , encode = encoder . lit . toColumns . toHKD . unComposite
56− , typeName = symbolVal (Proxy @name)
72+ , typeName = compositeTypeName @a
76+instance (DBComposite a, EqTable (HKD a Expr)) => DBEq (Composite a)
79+instance (DBComposite a, OrdTable (HKD a Expr)) => DBOrd (Composite a)
60−type GComposite :: (Type -> Type) -> Constraint
82+type DBComposite :: Type -> Constraint
61−class GComposite rep where
83+class (DBType a, HKDable a) => DBComposite a where
62− gdecode :: Hasql.Composite (rep x)
84+ compositeFields :: HKD a Name
63− gencode :: rep x -> [Opaleye.PrimExpr]
85+ compositeTypeName :: String
88+compose :: DBComposite a => HKD a Expr -> Expr a
89+compose = castExpr . fromPrimExpr . encoder
66−instance GComposite rep => GComposite (M1 i c rep) where
92+decompose :: forall a. DBComposite a => Expr a -> HKD a Expr
67− gdecode = M1 <$> gdecode
93+decompose (toPrimExpr -> a) = fromColumns $ htabulate \field ->
68− gencode (M1 a) = gencode a
94+ case hfield names field of
95+ NameCol name -> case hfield hspecs field of
96+ SSpec {} -> DB $ fromPrimExpr $ Opaleye.CompositeExpr a name
98+ names = toColumns (compositeFields @a)
71−instance (GComposite a, GComposite b) => GComposite (a :*: b) where
101+decoder :: Table Result a => Hasql.Composite a
72− gdecode = liftA2 (:*:) gdecode gdecode
102+decoder = fmap fromColumns $ unwrapApplicative $ htabulateA \field ->
73− gencode (a :*: b) = gencode a <> gencode b
103+ case hfield hspecs field of
104+ SSpec {nullity, info} -> WrapApplicative $ Result <$>
106+ Null -> Hasql.field $ Hasql.nullable $ decode info
107+ NotNull -> Hasql.field $ Hasql.nonNullable $ decode info
76−instance Sql DBType a => GComposite (K1 i a) where
110+encoder :: Table Expr a => a -> Opaleye.PrimExpr
77− gdecode = fmap K1 $ Hasql.field $ case nullable @a of
111+encoder (toColumns -> a) = Opaleye.FunExpr "ROW" exprs
78− Null -> Hasql.nullable (decode typeInformation)
112+ where
79− NotNull -> Hasql.nonNullable (decode typeInformation)
113+ exprs = getConst $ htabulateA \field -> case hfield a field of
80− gencode (K1 a) = case nullable @a of
114+ DB (toPrimExpr -> expr) -> Const [expr]
81− Null -> [maybe null (encode typeInformation) a]
82− NotNull -> [encode typeInformation a]
84− null = Opaleye.ConstExpr Opaleye.NullLit