@@ -0,0 +1,297 @@
1+
2+{-# LANGUAGE
3+ AllowAmbiguousTypes
4+ , DeriveFoldable
5+ , DeriveFunctor
6+ , DeriveGeneric
7+ , DeriveTraversable
8+ , DefaultSignatures
9+ , FlexibleContexts
10+ , FlexibleInstances
11+ , FunctionalDependencies
12+ , GADTs
13+ , LambdaCase
14+ , OverloadedStrings
15+ , MultiParamTypeClasses
16+ , ScopedTypeVariables
17+ , TypeApplications
18+ , TypeFamilies
19+ , TypeInType
20+ , TypeOperators
21+ , UndecidableInstances
22+ , UndecidableSuperClasses
23+#-}
24+
25+module Squeal.PostgreSQL.PG
26+ ( PG
27+ , NullPG
28+ , TuplePG
29+ , RowPG
30+ , Entity
31+ , SOP.P (..)
32+ , LabelsPG
33+ , DimPG
34+ , FixPG
35+ , TupleOf
36+ , TupleCodeOf
37+ , ConstructorsOf
38+ , ConstructorNameOf
39+ , ConstructorNamesOf
40+ ) where
41+
42+import Data.Aeson (Value)
43+import Data.Kind (Type)
44+import Data.Int (Int16, Int32, Int64)
45+import Data.Scientific (Scientific)
46+import Data.Time (Day, DiffTime, LocalTime, TimeOfDay, TimeZone, UTCTime)
47+import Data.Word (Word16, Word32, Word64)
48+import Data.UUID.Types (UUID)
49+import GHC.TypeLits
50+import Network.IP.Addr (NetAddr, IP)
51+
52+import qualified Data.ByteString.Lazy as Lazy (ByteString)
53+import qualified Data.ByteString as Strict (ByteString)
54+import qualified Data.Text.Lazy as Lazy (Text)
55+import qualified Data.Text as Strict (Text)
56+import qualified Generics.SOP as SOP
57+import qualified Generics.SOP.Record as SOP
58+import qualified Generics.SOP.Type.Metadata as Type
59+
60+import Squeal.PostgreSQL.Alias
61+import Squeal.PostgreSQL.List
62+import Squeal.PostgreSQL.Schema
63+
64+{- | The `PG` type family embeds a subset of Haskell types
65+as Postgres types. As an open type family, `PG` is extensible.
66+
67+>>> :kind! PG LocalTime
68+PG LocalTime :: PGType
69+= 'PGtimestamp
70+
71+>>> newtype MyDouble = My Double
72+>>> :set -XTypeFamilies
73+>>> type instance PG MyDouble = 'PGfloat8
74+-}
75+type family PG (hask :: Type) :: PGType
76+type instance PG Bool = 'PGbool
77+type instance PG Int16 = 'PGint2
78+type instance PG Int32 = 'PGint4
79+type instance PG Int64 = 'PGint8
80+type instance PG Word16 = 'PGint2
81+type instance PG Word32 = 'PGint4
82+type instance PG Word64 = 'PGint8
83+type instance PG Scientific = 'PGnumeric
84+type instance PG Float = 'PGfloat4
85+type instance PG Double = 'PGfloat8
86+type instance PG Char = 'PGchar 1
87+type instance PG Strict.Text = 'PGtext
88+type instance PG Lazy.Text = 'PGtext
89+type instance PG String = 'PGtext
90+type instance PG Strict.ByteString = 'PGbytea
91+type instance PG Lazy.ByteString = 'PGbytea
92+type instance PG LocalTime = 'PGtimestamp
93+type instance PG UTCTime = 'PGtimestamptz
94+type instance PG Day = 'PGdate
95+type instance PG TimeOfDay = 'PGtime
96+type instance PG (TimeOfDay, TimeZone) = 'PGtimetz
97+type instance PG DiffTime = 'PGinterval
98+type instance PG UUID = 'PGuuid
99+type instance PG (NetAddr IP) = 'PGinet
100+type instance PG Value = 'PGjson
101+
102+{-| The `LabelsPG` type family calculates the constructors of a
103+Haskell enum type.
104+
105+>>> data Schwarma = Beef | Lamb | Chicken deriving GHC.Generic
106+>>> instance Generic Schwarma
107+>>> instance HasDatatypeInfo Schwarma
108+>>> :kind! LabelsPG Schwarma
109+LabelsPG Schwarma :: [Type.ConstructorName]
110+= '["Beef", "Lamb", "Chicken"]
111+-}
112+type family LabelsPG (hask :: Type) :: [Type.ConstructorName] where
113+ LabelsPG hask =
114+ ConstructorNamesOf (ConstructorsOf (SOP.DatatypeInfoOf hask))
115+
116+{- |
117+`RowPG` turns a Haskell `Type` into a `RowType`.
118+
119+`RowPG` may be applied to normal Haskell record types provided they
120+have the `Generic` and `HasDatatypeInfo` instances;
121+
122+>>> data Person = Person { name :: Text, age :: Int32 } deriving GHC.Generic
123+>>> instance Generic Person
124+>>> instance HasDatatypeInfo Person
125+>>> :kind! RowPG Person
126+RowPG Person :: [(Symbol, NullityType)]
127+= '["name" ::: 'NotNull 'PGtext, "age" ::: 'NotNull 'PGint4]
128+
129+Or to tuples of `P` types;
130+
131+>>> :kind! RowPG (P ("id" ::: Int32), P ("foreign_id" ::: Int32))
132+RowPG (P ("id" ::: Int32), P ("foreign_id" ::: Int32)) :: [(Symbol,
133+ NullityType)]
134+= '[ '("id", 'NotNull 'PGint4), "foreign_id" ::: 'NotNull 'PGint4]
135+
136+Or to tuples which mix `P` types and record types.
137+
138+>>> :kind! RowPG (P ("id" ::: Int32), Person, P ("foreign_id" ::: Int32))
139+RowPG (P ("id" ::: Int32), Person, P ("foreign_id" ::: Int32)) :: [(Symbol,
140+ NullityType)]
141+= '[ '("id", 'NotNull 'PGint4), '("name", 'NotNull 'PGtext),
142+ '("age", 'NotNull 'PGint4), "foreign_id" ::: 'NotNull 'PGint4]
143+-}
144+type family RowPG (hask :: Type) :: RowType where
145+ RowPG (hask1, hask2) = Join (RowPG hask1) (RowPG hask2)
146+ RowPG (hask1, hask2, hask3) =
147+ Join (RowPG hask1) (RowPG (hask2, hask3))
148+ RowPG (hask1, hask2, hask3, hask4) =
149+ Join (RowPG hask1) (RowPG (hask2, hask3, hask4))
150+ RowPG (hask1, hask2, hask3, hask4, hask5) =
151+ Join (RowPG hask1) (RowPG (hask2, hask3, hask4, hask5))
152+ RowPG (SOP.P (col ::: head)) = '[col ::: NullPG head]
153+ RowPG hask = RowOf (SOP.RecordCodeOf hask)
154+
155+type family RowOf (record :: [(Symbol, Type)]) :: RowType where
156+ RowOf '[] = '[]
157+ RowOf (col ::: ty ': record) = col ::: NullPG ty ': RowOf record
158+
159+type family ColumnsOf (record :: [(Symbol, Type)]) :: ColumnsType where
160+ ColumnsOf '[] = '[]
161+ ColumnsOf (col ::: ty ': record) =
162+ col ::: 'NoDef :=> NullPG ty ': ColumnsOf record
163+
164+{- |
165+`Entity` turns a Haskell `Type` into a `ColumnsType`.
166+
167+`Entity` may be applied to normal Haskell record types provided they
168+have the `Generic` and `HasDatatypeInfo` instances;
169+
170+>>> data Person = Person { name :: Text, age :: Int32 } deriving GHC.Generic
171+>>> instance Generic Person
172+>>> instance HasDatatypeInfo Person
173+>>> :kind! Entity Person
174+Entity Person :: [(Symbol, (ColumnConstraint, NullityType))]
175+= '["name" ::: ('NoDef :=> 'NotNull 'PGtext),
176+ "age" ::: ('NoDef :=> 'NotNull 'PGint4)]
177+
178+Or to tuples of `P` types;
179+
180+>>> :kind! Entity (P ("id" ::: Int32), P ("foreign_id" ::: Int32))
181+Entity (P ("id" ::: Int32), P ("foreign_id" ::: Int32)) :: [(Symbol,
182+ ColumnType)]
183+= '[ '("id", 'Def :=> 'NotNull 'PGint4),
184+ "foreign_id" ::: ('Def :=> 'NotNull 'PGint4)]
185+
186+Or to tuples which mix `P` types and record types.
187+
188+>>> :kind! Entity (P ("id" ::: Int32), Person, P ("foreign_id" ::: Int32))
189+Entity (P ("id" ::: Int32), Person, P ("foreign_id" ::: Int32)) :: [(Symbol,
190+ ColumnType)]
191+= '[ '("id", 'Def :=> 'NotNull 'PGint4),
192+ '("name", 'NoDef :=> 'NotNull 'PGtext),
193+ '("age", 'NoDef :=> 'NotNull 'PGint4),
194+ "foreign_id" ::: ('Def :=> 'NotNull 'PGint4)]
195+-}
196+type family Entity (hask :: Type) :: ColumnsType where
197+ Entity (hask1, hask2) = Join (Entity hask1) (Entity hask2)
198+ Entity (hask1, hask2, hask3) =
199+ Join (Entity hask1) (Entity (hask2, hask3))
200+ Entity (hask1, hask2, hask3, hask4) =
201+ Join (Entity hask1) (Entity (hask2, hask3, hask4))
202+ Entity (hask1, hask2, hask3, hask4, hask5) =
203+ Join (Entity hask1) (Entity (hask2, hask3, hask4, hask5))
204+ Entity (SOP.P (col ::: head)) = '[col ::: 'Def :=> NullPG head]
205+ Entity hask = ColumnsOf (SOP.RecordCodeOf hask)
206+
207+{- | `NullPG` turns a Haskell type into a `NullityType`.
208+
209+>>> :kind! NullPG Double
210+NullPG Double :: NullityType
211+= 'NotNull 'PGfloat8
212+>>> :kind! NullPG (Maybe Double)
213+NullPG (Maybe Double) :: NullityType
214+= 'Null 'PGfloat8
215+-}
216+type family NullPG (hask :: Type) :: NullityType where
217+ NullPG (Maybe hask) = 'Null (PG hask)
218+ NullPG hask = 'NotNull (PG hask)
219+
220+{- | `TuplePG` turns a Haskell tuple type (including record types) into
221+the corresponding list of `NullityType`s.
222+
223+>>> :kind! TuplePG (Double, Maybe Char)
224+TuplePG (Double, Maybe Char) :: [NullityType]
225+= '[ 'NotNull 'PGfloat8, 'Null ('PGchar 1)]
226+-}
227+type family TuplePG (hask :: Type) :: [NullityType] where
228+ TuplePG hask = TupleOf (TupleCodeOf hask (SOP.Code hask))
229+
230+type family TupleOf (tuple :: [Type]) :: [NullityType] where
231+ TupleOf '[] = '[]
232+ TupleOf (hask ': tuple) = NullPG hask ': TupleOf tuple
233+
234+type family TupleCodeOf (hask :: Type) (code :: [[Type]]) :: [Type] where
235+ TupleCodeOf hask '[tuple] = tuple
236+ TupleCodeOf hask '[] =
237+ TypeError
238+ ( 'Text "The type `" ':<>: 'ShowType hask ':<>: 'Text "' is not a tuple type."
239+ ':$$: 'Text "It is a void type with no constructors."
240+ )
241+ TupleCodeOf hask (_ ': _ ': _) =
242+ TypeError
243+ ( 'Text "The type `" ':<>: 'ShowType hask ':<>: 'Text "' is not a tuple type."
244+ ':$$: 'Text "It is a sum type with more than one constructor."
245+ )
246+
247+-- | Calculates constructors of a datatype.
248+type family ConstructorsOf (datatype :: Type.DatatypeInfo)
249+ :: [Type.ConstructorInfo] where
250+ ConstructorsOf ('Type.ADT _module _datatype constructors) =
251+ constructors
252+ ConstructorsOf ('Type.Newtype _module _datatype constructor) =
253+ '[constructor]
254+
255+-- | Calculates the name of a nullary constructor, otherwise
256+-- generates a type error.
257+type family ConstructorNameOf (constructor :: Type.ConstructorInfo)
258+ :: Type.ConstructorName where
259+ ConstructorNameOf ('Type.Constructor name) = name
260+ ConstructorNameOf ('Type.Infix name _assoc _fix) = TypeError
261+ ('Text "ConstructorNameOf error: non-nullary constructor "
262+ ':<>: 'Text name)
263+ ConstructorNameOf ('Type.Record name _fields) = TypeError
264+ ('Text "ConstructorNameOf error: non-nullary constructor "
265+ ':<>: 'Text name)
266+
267+-- | Calculate the names of nullary constructors.
268+type family ConstructorNamesOf (constructors :: [Type.ConstructorInfo])
269+ :: [Type.ConstructorName] where
270+ ConstructorNamesOf '[] = '[]
271+ ConstructorNamesOf (constructor ': constructors) =
272+ ConstructorNameOf constructor ': ConstructorNamesOf constructors
273+
274+type family DimPG (hask :: Type) :: [Nat] where
275+ DimPG (x,x) = 2 ': DimPG x
276+ DimPG (x,x,x) = 3 ': DimPG x
277+ DimPG (x,x,x,x) = 4 ': DimPG x
278+ DimPG (x,x,x,x,x) = 5 ': DimPG x
279+ DimPG (x,x,x,x,x,x) = 6 ': DimPG x
280+ DimPG (x,x,x,x,x,x,x) = 7 ': DimPG x
281+ DimPG (x,x,x,x,x,x,x,x) = 8 ': DimPG x
282+ DimPG (x,x,x,x,x,x,x,x,x) = 9 ': DimPG x
283+ DimPG (x,x,x,x,x,x,x,x,x,x) = 10 ': DimPG x
284+ DimPG x = '[]
285+
286+type family FixPG (hask :: Type) :: NullityType where
287+ FixPG (x,x) = FixPG x
288+ FixPG (x,x,x) = FixPG x
289+ FixPG (x,x,x,x) = FixPG x
290+ FixPG (x,x,x,x,x) = FixPG x
291+ FixPG (x,x,x,x,x,x) = FixPG x
292+ FixPG (x,x,x,x,x,x,x) = FixPG x
293+ FixPG (x,x,x,x,x,x,x,x) = FixPG x
294+ FixPG (x,x,x,x,x,x,x,x,x) = FixPG x
295+ FixPG (x,x,x,x,x,x,x,x,x,x) = FixPG x
296+ FixPG (x,x,x,x,x,x,x,x,x,x,x) = FixPG x
297+ FixPG x = NullPG x