on dev help sign in
0.5
212174eEitan Chatav
squeal-postgresql/squeal-postgresql.cabal+3
@@ -22,12 +22,15 @@ library
22 hs-source-dirs: src
22 hs-source-dirs: src
23 exposed-modules:
23 exposed-modules:
24 Squeal.PostgreSQL
24 Squeal.PostgreSQL
25+ Squeal.PostgreSQL.Alias
25 Squeal.PostgreSQL.Binary
26 Squeal.PostgreSQL.Binary
26 Squeal.PostgreSQL.Definition
27 Squeal.PostgreSQL.Definition
27 Squeal.PostgreSQL.Expression
28 Squeal.PostgreSQL.Expression
29+ Squeal.PostgreSQL.List
28 Squeal.PostgreSQL.Manipulation
30 Squeal.PostgreSQL.Manipulation
29 Squeal.PostgreSQL.Migration
31 Squeal.PostgreSQL.Migration
30 Squeal.PostgreSQL.Pool
32 Squeal.PostgreSQL.Pool
33+ Squeal.PostgreSQL.PG
31 Squeal.PostgreSQL.PQ
34 Squeal.PostgreSQL.PQ
32 Squeal.PostgreSQL.Render
35 Squeal.PostgreSQL.Render
33 Squeal.PostgreSQL.Query
36 Squeal.PostgreSQL.Query
squeal-postgresql/src/Squeal/PostgreSQL.hs+7−1
@@ -194,20 +194,26 @@ in
194[User {userName = "Alice", userEmail = Just "alice@gmail.com"},User {userName = "Bob", userEmail = Nothing},User {userName = "Carole", userEmail = Just "carole@hotmail.com"}]
194[User {userName = "Alice", userEmail = Just "alice@gmail.com"},User {userName = "Bob", userEmail = Nothing},User {userName = "Carole", userEmail = Just "carole@hotmail.com"}]
195-}
195-}
196module Squeal.PostgreSQL
196module Squeal.PostgreSQL
197− ( module Squeal.PostgreSQL.Binary
197+ ( module Squeal.PostgreSQL.Alias
198+ , module Squeal.PostgreSQL.Binary
198 , module Squeal.PostgreSQL.Definition
199 , module Squeal.PostgreSQL.Definition
199 , module Squeal.PostgreSQL.Expression
200 , module Squeal.PostgreSQL.Expression
201+ , module Squeal.PostgreSQL.List
200 , module Squeal.PostgreSQL.Manipulation
202 , module Squeal.PostgreSQL.Manipulation
203+ , module Squeal.PostgreSQL.PG
201 , module Squeal.PostgreSQL.PQ
204 , module Squeal.PostgreSQL.PQ
202 , module Squeal.PostgreSQL.Query
205 , module Squeal.PostgreSQL.Query
203 , module Squeal.PostgreSQL.Schema
206 , module Squeal.PostgreSQL.Schema
204 , module Squeal.PostgreSQL.Transaction
207 , module Squeal.PostgreSQL.Transaction
205 ) where
208 ) where
206
209
210+import Squeal.PostgreSQL.Alias
207import Squeal.PostgreSQL.Binary
211import Squeal.PostgreSQL.Binary
208import Squeal.PostgreSQL.Definition
212import Squeal.PostgreSQL.Definition
209import Squeal.PostgreSQL.Expression
213import Squeal.PostgreSQL.Expression
214+import Squeal.PostgreSQL.List
210import Squeal.PostgreSQL.Manipulation
215import Squeal.PostgreSQL.Manipulation
216+import Squeal.PostgreSQL.PG
211import Squeal.PostgreSQL.PQ
217import Squeal.PostgreSQL.PQ
212import Squeal.PostgreSQL.Query
218import Squeal.PostgreSQL.Query
213import Squeal.PostgreSQL.Schema
219import Squeal.PostgreSQL.Schema
squeal-postgresql/src/Squeal/PostgreSQL/Alias.hsnew file+242
@@ -0,0 +1,242 @@
1+{-# LANGUAGE
2+ AllowAmbiguousTypes
3+ , ConstraintKinds
4+ , DeriveAnyClass
5+ , DeriveGeneric
6+ , FlexibleContexts
7+ , FlexibleInstances
8+ , FunctionalDependencies
9+ , GADTs
10+ , LambdaCase
11+ , OverloadedStrings
12+ , QuantifiedConstraints
13+ , RankNTypes
14+ , ScopedTypeVariables
15+ , StandaloneDeriving
16+ , TypeApplications
17+ , TypeFamilyDependencies
18+ , TypeInType
19+ , TypeOperators
20+ , UndecidableInstances
21+ , UndecidableSuperClasses
22+#-}
23+
24+module Squeal.PostgreSQL.Alias
25+ ( (:::)
26+ , Alias (..)
27+ , IsLabel (..)
28+ , Aliased (As)
29+ , Aliasable (as)
30+ , renderAliased
31+ , Has
32+ , HasUnique
33+ , HasAll
34+ , HasIn
35+ , QualifiedAlias (..)
36+ , IsQualified (..)
37+ , DefaultAliasable (..)
38+ -- * Grouping
39+ , Grouping (..)
40+ , GroupedBy
41+ ) where
42+
43+import Control.DeepSeq
44+import Data.ByteString (ByteString)
45+import Data.String (fromString)
46+import GHC.OverloadedLabels
47+import GHC.TypeLits
48+
49+import qualified Generics.SOP as SOP
50+import qualified GHC.Generics as GHC
51+
52+import Squeal.PostgreSQL.Render
53+
54+-- | The alias operator `:::` is like a promoted version of `As`,
55+-- a type level pair between an alias and some type.
56+type (:::) (alias :: Symbol) ty = '(alias,ty)
57+infixr 6 :::
58+
59+
60+-- | `Grouping` is an auxiliary namespace, created by
61+-- @GROUP BY@ clauses (`Squeal.PostgreSQL.Query.group`), and used
62+-- for typesafe aggregation
63+data Grouping
64+ = Ungrouped -- ^ no aggregation permitted
65+ | Grouped [(Symbol,Symbol)] -- ^ aggregation required for any column which is not grouped
66+
67+{- | A `GroupedBy` constraint indicates that a table qualified column is
68+a member of the auxiliary namespace created by @GROUP BY@ clauses and thus,
69+may be called in an output `Squeal.PostgreSQL.Expression.Expression` without aggregating.
70+-}
71+class (KnownSymbol table, KnownSymbol column)
72+ => GroupedBy table column bys where
73+instance {-# OVERLAPPING #-} (KnownSymbol table, KnownSymbol column)
74+ => GroupedBy table column ('(table,column) ': bys)
75+instance {-# OVERLAPPABLE #-}
76+ ( KnownSymbol table
77+ , KnownSymbol column
78+ , GroupedBy table column bys
79+ ) => GroupedBy table column (tabcol ': bys)
80+
81+-- | `Alias`es are proxies for a type level string or `Symbol`
82+-- and have an `IsLabel` instance so that with @-XOverloadedLabels@
83+--
84+-- >>> :set -XOverloadedLabels
85+-- >>> #foobar :: Alias "foobar"
86+-- Alias
87+data Alias (alias :: Symbol) = Alias
88+ deriving (Eq,GHC.Generic,Ord,Show,NFData)
89+instance alias1 ~ alias2 => IsLabel alias1 (Alias alias2) where
90+ fromLabel = Alias
91+instance aliases ~ '[alias] => IsLabel alias (SOP.NP Alias aliases) where
92+ fromLabel = fromLabel SOP.:* SOP.Nil
93+-- | >>> printSQL (#jimbob :: Alias "jimbob")
94+-- "jimbob"
95+instance KnownSymbol alias => RenderSQL (Alias alias) where
96+ renderSQL = doubleQuoted . fromString . symbolVal
97+
98+-- | >>> import Generics.SOP (SOP.NP(..))
99+-- >>> printSQL (#jimbob SOP.:* #kandi :: SOP.NP Alias '["jimbob", "kandi"])
100+-- "jimbob", "kandi"
101+instance SOP.All KnownSymbol aliases => RenderSQL (SOP.NP Alias aliases) where
102+ renderSQL
103+ = commaSeparated
104+ . SOP.hcollapse
105+ . SOP.hcmap (SOP.Proxy @KnownSymbol) (SOP.K . renderSQL)
106+
107+-- | The `As` operator is used to name an expression. `As` is like a demoted
108+-- version of `:::`.
109+--
110+-- >>> Just "hello" `As` #hi :: Aliased Maybe ("hi" ::: String)
111+-- As (Just "hello") Alias
112+data Aliased expression aliased where
113+ As
114+ :: KnownSymbol alias
115+ => expression ty
116+ -> Alias alias
117+ -> Aliased expression (alias ::: ty)
118+deriving instance Show (expression ty)
119+ => Show (Aliased expression (alias ::: ty))
120+deriving instance Eq (expression ty)
121+ => Eq (Aliased expression (alias ::: ty))
122+deriving instance Ord (expression ty)
123+ => Ord (Aliased expression (alias ::: ty))
124+instance (alias0 ~ alias1, alias0 ~ alias2, KnownSymbol alias2)
125+ => IsLabel alias0 (Aliased Alias (alias1 ::: alias2)) where
126+ fromLabel = fromLabel @alias2 `As` fromLabel @alias1
127+
128+-- | The `Aliasable` class provides a way to scrap your `SOP.Nil`s
129+-- in an `SOP.NP` list of `Aliased` expressions.
130+class KnownSymbol alias => Aliasable alias expression aliased
131+ | aliased -> expression
132+ , aliased -> alias
133+ where as :: expression -> Alias alias -> aliased
134+instance (KnownSymbol alias, aliased ~ (alias ::: ty)) => Aliasable alias
135+ (expression ty)
136+ (Aliased expression aliased)
137+ where
138+ as = As
139+instance (KnownSymbol alias, tys ~ '[alias ::: ty]) => Aliasable alias
140+ (expression ty)
141+ (SOP.NP (Aliased expression) tys)
142+ where
143+ expression `as` alias = expression `As` alias SOP.:* SOP.Nil
144+
145+-- | >>> let renderMaybe = fromString . maybe "Nothing" (const "Just")
146+-- >>> renderAliased renderMaybe (Just (3::Int) `As` #an_int)
147+-- "Just AS \"an_int\""
148+renderAliased
149+ :: (forall ty. expression ty -> ByteString)
150+ -> Aliased expression aliased
151+ -> ByteString
152+renderAliased render (expression `As` alias) =
153+ render expression <> " AS " <> renderSQL alias
154+
155+-- | @HasUnique alias fields field@ is a constraint that proves that
156+-- @fields@ is a singleton of @alias ::: field@.
157+type HasUnique alias fields field = fields ~ '[alias ::: field]
158+
159+-- | @Has alias fields field@ is a constraint that proves that
160+-- @fields@ has a field of @alias ::: field@, inferring @field@
161+-- from @alias@ and @fields@.
162+class KnownSymbol alias =>
163+ Has (alias :: Symbol) (fields :: [(Symbol,kind)]) (field :: kind)
164+ | alias fields -> field where
165+instance {-# OVERLAPPING #-} KnownSymbol alias
166+ => Has alias (alias ::: field ': fields) field
167+instance {-# OVERLAPPABLE #-} (KnownSymbol alias, Has alias fields field)
168+ => Has alias (field' ': fields) field
169+
170+{-| @HasIn fields (alias ::: field)@ is a constraint that proves that
171+@fields@ has a field of @alias ::: field@. It is used in @UPDATE@s to
172+choose which subfields to update.
173+-}
174+class HasIn fields field where
175+instance (Has alias fields field) => HasIn fields (alias ::: field) where
176+
177+{- |
178+The `DefaultAliasable` class is intended to help with Scrap your SOP.Nils
179+for default inserts and updates.
180+-}
181+class KnownSymbol alias
182+ => DefaultAliasable alias aliased | aliased -> alias where
183+ defaultAs :: Alias alias -> aliased
184+
185+-- | `HasAll` extends `Has` to take lists of @aliases@ and @fields@ and infer
186+-- a list of @subfields@.
187+class
188+ ( SOP.All KnownSymbol aliases
189+ ) => HasAll
190+ (aliases :: [Symbol])
191+ (fields :: [(Symbol,kind)])
192+ (subfields :: [(Symbol,kind)])
193+ | aliases fields -> subfields where
194+instance {-# OVERLAPPING #-} HasAll '[] fields '[]
195+instance {-# OVERLAPPABLE #-}
196+ (Has alias fields field, HasAll aliases fields subfields)
197+ => HasAll (alias ': aliases) fields (alias ::: field ': subfields)
198+
199+-- | Analagous to `IsLabel`, the constraint
200+-- `IsQualified` defines `!` for a column alias qualified
201+-- by a table alias.
202+class IsQualified table column expression where
203+ (!) :: Alias table -> Alias column -> expression
204+ infixl 9 !
205+instance IsQualified table column (Alias table, Alias column) where (!) = (,)
206+
207+{-| `QualifiedAlias`es enables multi-schema support by allowing a reference
208+to a `Table`, `Typedef` or `View` to be qualified by their schemas. By default,
209+a qualifier of @public@ is provided.
210+
211+>>> :{
212+let
213+ alias1 :: QualifiedAlias "sch" "tab"
214+ alias1 = #sch ! #tab
215+ alias2 :: QualifiedAlias "public" "vw"
216+ alias2 = #vw
217+in printSQL alias1 >> printSQL alias2
218+:}
219+"sch"."tab"
220+"vw"
221+-}
222+data QualifiedAlias (qualifier :: Symbol) (alias :: Symbol) = QualifiedAlias
223+ deriving (Eq,GHC.Generic,Ord,Show,NFData)
224+instance (q ~ q', a ~ a') => IsQualified q a (QualifiedAlias q' a') where
225+ _!_ = QualifiedAlias
226+instance (q' ~ "public", a ~ a') => IsLabel a (QualifiedAlias q' a') where
227+ fromLabel = QualifiedAlias
228+instance (q0 ~ q1, a0 ~ a1, a1 ~ a2, KnownSymbol a2) =>
229+ IsQualified q0 a0 (Aliased (QualifiedAlias q1) (a1 ::: a2)) where
230+ _!_ = QualifiedAlias `As` Alias
231+instance (q ~ "public", a0 ~ a1, a1 ~ a2, KnownSymbol a2) =>
232+ IsLabel a0 (Aliased (QualifiedAlias q) (a1 ::: a2)) where
233+ fromLabel = QualifiedAlias `As` Alias
234+
235+instance (KnownSymbol q, KnownSymbol a)
236+ => RenderSQL (QualifiedAlias q a) where
237+ renderSQL _ =
238+ let
239+ qualifier = renderSQL (Alias @q)
240+ alias = renderSQL (Alias @a)
241+ in
242+ if qualifier == "\"public\"" then alias else qualifier <> "." <> alias
squeal-postgresql/src/Squeal/PostgreSQL/Binary.hs+8−181
@@ -202,14 +202,8 @@ True
202#-}
202#-}
203
203
204module Squeal.PostgreSQL.Binary
204module Squeal.PostgreSQL.Binary
205− ( -- * PG type families
206− PG
207− , NullPG
208− , TuplePG
209− , RowPG
210− , LabelsPG
211− -- * Storage newtypes
205+ ( -- * Storage newtypes
212− , Money (..)
206+ Money (..)
213 , Json (..)
207 , Json (..)
214 , Jsonb (..)
208 , Jsonb (..)
215 , Composite (..)
209 , Composite (..)
@@ -219,24 +213,23 @@ module Squeal.PostgreSQL.Binary
219 -- * Encoding
213 -- * Encoding
220 , ToParam (..)
214 , ToParam (..)
221 , ToParams (..)
215 , ToParams (..)
216+ , ToNullityParam
217+ , ToField
218+ , ToFixArray
222 -- * Decoding
219 -- * Decoding
223 , FromValue (..)
220 , FromValue (..)
224 , FromRow (..)
221 , FromRow (..)
225 -- * Only
222 -- * Only
226 , Only (..)
223 , Only (..)
227− , P (..)
228 ) where
224 ) where
229
225
230import BinaryParser
226import BinaryParser
231import ByteString.StrictBuilder (builderLength, int32BE, int64BE, word32BE)
227import ByteString.StrictBuilder (builderLength, int32BE, int64BE, word32BE)
232import Control.Arrow (left)
228import Control.Arrow (left)
233import Control.Monad
229import Control.Monad
234−import Data.Aeson (Value)
235−import Data.ByteString (ByteString)
236import Data.Int
230import Data.Int
237import Data.Kind
231import Data.Kind
238import Data.Scientific
232import Data.Scientific
239−import Data.Text (Text)
240import Data.Time
233import Data.Time
241import Data.UUID.Types
234import Data.UUID.Types
242import Data.Vector (Vector)
235import Data.Vector (Vector)
@@ -254,11 +247,13 @@ import qualified Data.Text.Lazy as Lazy (Text)
254import qualified Data.Text as Strict (Text)
247import qualified Data.Text as Strict (Text)
255import qualified Data.Text as Strict.Text
248import qualified Data.Text as Strict.Text
256import qualified Data.Vector as Vector
249import qualified Data.Vector as Vector
257−import qualified Generics.SOP.Type.Metadata as Type
258import qualified GHC.Generics as GHC
250import qualified GHC.Generics as GHC
259import qualified PostgreSQL.Binary.Decoding as Decoding
251import qualified PostgreSQL.Binary.Decoding as Decoding
260import qualified PostgreSQL.Binary.Encoding as Encoding
252import qualified PostgreSQL.Binary.Encoding as Encoding
261
253
254+import Squeal.PostgreSQL.Alias
255+import Squeal.PostgreSQL.List
256+import Squeal.PostgreSQL.PG
262import Squeal.PostgreSQL.Schema
257import Squeal.PostgreSQL.Schema
263
258
264-- | A `ToParam` constraint gives an encoding of a Haskell `Type` into
259-- | A `ToParam` constraint gives an encoding of a Haskell `Type` into
@@ -786,171 +781,3 @@ type it's applied to should be stored as a `PGfixarray`.
786newtype FixArray arr = FixArray {getFixArray :: arr}
781newtype FixArray arr = FixArray {getFixArray :: arr}
787 deriving (Eq, Ord, Show, Read, GHC.Generic)
782 deriving (Eq, Ord, Show, Read, GHC.Generic)
788type instance PG (FixArray x) = 'PGfixarray (DimPG x) (FixPG x)
783type instance PG (FixArray x) = 'PGfixarray (DimPG x) (FixPG x)
789−
790−type family DimPG (hask :: Type) :: [Nat] where
791− DimPG (x,x) = 2 ': DimPG x
792− DimPG (x,x,x) = 3 ': DimPG x
793− DimPG (x,x,x,x) = 4 ': DimPG x
794− DimPG (x,x,x,x,x) = 5 ': DimPG x
795− DimPG (x,x,x,x,x,x) = 6 ': DimPG x
796− DimPG (x,x,x,x,x,x,x) = 7 ': DimPG x
797− DimPG (x,x,x,x,x,x,x,x) = 8 ': DimPG x
798− DimPG (x,x,x,x,x,x,x,x,x) = 9 ': DimPG x
799− DimPG (x,x,x,x,x,x,x,x,x,x) = 10 ': DimPG x
800− DimPG x = '[]
801−
802−type family FixPG (hask :: Type) :: NullityType where
803− FixPG (x,x) = FixPG x
804− FixPG (x,x,x) = FixPG x
805− FixPG (x,x,x,x) = FixPG x
806− FixPG (x,x,x,x,x) = FixPG x
807− FixPG (x,x,x,x,x,x) = FixPG x
808− FixPG (x,x,x,x,x,x,x) = FixPG x
809− FixPG (x,x,x,x,x,x,x,x) = FixPG x
810− FixPG (x,x,x,x,x,x,x,x,x) = FixPG x
811− FixPG (x,x,x,x,x,x,x,x,x,x) = FixPG x
812− FixPG (x,x,x,x,x,x,x,x,x,x,x) = FixPG x
813− FixPG x = NullPG x
814−
815−{- | The `PG` type family embeds a subset of Haskell types
816−as Postgres types. As an open type family, `PG` is extensible.
817−
818−>>> :kind! PG LocalTime
819−PG LocalTime :: PGType
820−= 'PGtimestamp
821−
822−>>> newtype MyDouble = My Double
823−>>> :set -XTypeFamilies
824−>>> type instance PG MyDouble = 'PGfloat8
825−-}
826−type family PG (hask :: Type) :: PGType
827−type instance PG Bool = 'PGbool
828−type instance PG Int16 = 'PGint2
829−type instance PG Int32 = 'PGint4
830−type instance PG Int64 = 'PGint8
831−type instance PG Word16 = 'PGint2
832−type instance PG Word32 = 'PGint4
833−type instance PG Word64 = 'PGint8
834−type instance PG Scientific = 'PGnumeric
835−type instance PG Float = 'PGfloat4
836−type instance PG Double = 'PGfloat8
837−type instance PG Char = 'PGchar 1
838−type instance PG Text = 'PGtext
839−type instance PG Lazy.Text = 'PGtext
840−type instance PG String = 'PGtext
841−type instance PG ByteString = 'PGbytea
842−type instance PG Lazy.ByteString = 'PGbytea
843−type instance PG LocalTime = 'PGtimestamp
844−type instance PG UTCTime = 'PGtimestamptz
845−type instance PG Day = 'PGdate
846−type instance PG TimeOfDay = 'PGtime
847−type instance PG (TimeOfDay, TimeZone) = 'PGtimetz
848−type instance PG DiffTime = 'PGinterval
849−type instance PG UUID = 'PGuuid
850−type instance PG (NetAddr IP) = 'PGinet
851−type instance PG Value = 'PGjson
852−
853−{-| The `LabelsPG` type family calculates the constructors of a
854−Haskell enum type.
855−
856−>>> data Schwarma = Beef | Lamb | Chicken deriving GHC.Generic
857−>>> instance Generic Schwarma
858−>>> instance HasDatatypeInfo Schwarma
859−>>> :kind! LabelsPG Schwarma
860−LabelsPG Schwarma :: [Type.ConstructorName]
861−= '["Beef", "Lamb", "Chicken"]
862−-}
863−type family LabelsPG (hask :: Type) :: [Type.ConstructorName] where
864− LabelsPG hask =
865− ConstructorNamesOf (ConstructorsOf (DatatypeInfoOf hask))
866−
867−{- | `RowPG` turns a Haskell record type into a `RowType`.
868−
869−>>> data Person = Person { name :: Text, age :: Int32 } deriving GHC.Generic
870−>>> instance Generic Person
871−>>> instance HasDatatypeInfo Person
872−>>> :kind! RowPG Person
873−RowPG Person :: [(Symbol, NullityType)]
874−= '["name" ::: 'NotNull 'PGtext, "age" ::: 'NotNull 'PGint4]
875−-}
876−type family RowPG (hask :: Type) :: RowType where
877− RowPG (hask1, hask2) = Join (RowPG hask1) (RowPG hask2)
878− RowPG (hask1, hask2, hask3) =
879− Join (RowPG hask1) (RowPG (hask2, hask3))
880− RowPG (hask1, hask2, hask3, hask4) =
881− Join (RowPG hask1) (RowPG (hask2, hask3, hask4))
882− RowPG (hask1, hask2, hask3, hask4, hask5) =
883− Join (RowPG hask1) (RowPG (hask2, hask3, hask4, hask5))
884− RowPG (P (col ::: head)) = '[col ::: NullPG head]
885− RowPG hask = RowOf (RecordCodeOf hask)
886−
887−type family RowOf (record :: [(Symbol, Type)]) :: RowType where
888− RowOf '[] = '[]
889− RowOf (col ::: ty ': record) = col ::: NullPG ty ': RowOf record
890−
891−{- | `NullPG` turns a Haskell type into a `NullityType`.
892−
893−>>> :kind! NullPG Double
894−NullPG Double :: NullityType
895−= 'NotNull 'PGfloat8
896−>>> :kind! NullPG (Maybe Double)
897−NullPG (Maybe Double) :: NullityType
898−= 'Null 'PGfloat8
899−-}
900−type family NullPG (hask :: Type) :: NullityType where
901− NullPG (Maybe hask) = 'Null (PG hask)
902− NullPG hask = 'NotNull (PG hask)
903−
904−{- | `TuplePG` turns a Haskell tuple type (including record types) into
905−the corresponding list of `NullityType`s.
906−
907−>>> :kind! TuplePG (Double, Maybe Char)
908−TuplePG (Double, Maybe Char) :: [NullityType]
909−= '[ 'NotNull 'PGfloat8, 'Null ('PGchar 1)]
910−-}
911−type family TuplePG (hask :: Type) :: [NullityType] where
912− TuplePG hask = TupleOf (TupleCodeOf hask (Code hask))
913−
914−type family TupleOf (tuple :: [Type]) :: [NullityType] where
915− TupleOf '[] = '[]
916− TupleOf (hask ': tuple) = NullPG hask ': TupleOf tuple
917−
918−type family TupleCodeOf (hask :: Type) (code :: [[Type]]) :: [Type] where
919− TupleCodeOf hask '[tuple] = tuple
920− TupleCodeOf hask '[] =
921− TypeError
922− ( 'Text "The type `" ':<>: 'ShowType hask ':<>: 'Text "' is not a tuple type."
923− ':$$: 'Text "It is a void type with no constructors."
924− )
925− TupleCodeOf hask (_ ': _ ': _) =
926− TypeError
927− ( 'Text "The type `" ':<>: 'ShowType hask ':<>: 'Text "' is not a tuple type."
928− ':$$: 'Text "It is a sum type with more than one constructor."
929− )
930−
931−-- | Calculates constructors of a datatype.
932−type family ConstructorsOf (datatype :: Type.DatatypeInfo)
933− :: [Type.ConstructorInfo] where
934− ConstructorsOf ('Type.ADT _module _datatype constructors) =
935− constructors
936− ConstructorsOf ('Type.Newtype _module _datatype constructor) =
937− '[constructor]
938−
939−-- | Calculates the name of a nullary constructor, otherwise
940−-- generates a type error.
941−type family ConstructorNameOf (constructors :: Type.ConstructorInfo)
942− :: Type.ConstructorName where
943− ConstructorNameOf ('Type.Constructor name) = name
944− ConstructorNameOf ('Type.Infix name _assoc _fix) = TypeError
945− ('Text "ConstructorNameOf error: non-nullary constructor "
946− ':<>: 'Text name)
947− ConstructorNameOf ('Type.Record name _fields) = TypeError
948− ('Text "ConstructorNameOf error: non-nullary constructor "
949− ':<>: 'Text name)
950−
951−-- | Calculate the names of nullary constructors.
952−type family ConstructorNamesOf (constructors :: [Type.ConstructorInfo])
953− :: [Type.ConstructorName] where
954− ConstructorNamesOf '[] = '[]
955− ConstructorNamesOf (constructor ': constructors) =
956− ConstructorNameOf constructor ': ConstructorNamesOf constructors
squeal-postgresql/src/Squeal/PostgreSQL/Definition.hs+2
@@ -95,8 +95,10 @@ import Prelude hiding ((.), id)
95import qualified Generics.SOP as SOP
95import qualified Generics.SOP as SOP
96import qualified GHC.Generics as GHC
96import qualified GHC.Generics as GHC
97
97
98+import Squeal.PostgreSQL.Alias
98import Squeal.PostgreSQL.Binary
99import Squeal.PostgreSQL.Binary
99import Squeal.PostgreSQL.Expression
100import Squeal.PostgreSQL.Expression
101+import Squeal.PostgreSQL.PG
100import Squeal.PostgreSQL.Query
102import Squeal.PostgreSQL.Query
101import Squeal.PostgreSQL.Render
103import Squeal.PostgreSQL.Render
102import Squeal.PostgreSQL.Schema
104import Squeal.PostgreSQL.Schema
squeal-postgresql/src/Squeal/PostgreSQL/Expression.hs+3
@@ -279,7 +279,10 @@ import qualified Data.Text.Lazy as Lazy.Text
279import qualified GHC.Generics as GHC
279import qualified GHC.Generics as GHC
280import qualified Generics.SOP as SOP
280import qualified Generics.SOP as SOP
281
281
282+import Squeal.PostgreSQL.Alias
282import Squeal.PostgreSQL.Binary
283import Squeal.PostgreSQL.Binary
284+import Squeal.PostgreSQL.List
285+import Squeal.PostgreSQL.PG
283import Squeal.PostgreSQL.Render
286import Squeal.PostgreSQL.Render
284import Squeal.PostgreSQL.Schema
287import Squeal.PostgreSQL.Schema
285
288
squeal-postgresql/src/Squeal/PostgreSQL/List.hsnew file+123
@@ -0,0 +1,123 @@
1+{-# LANGUAGE
2+ DataKinds
3+ , FlexibleContexts
4+ , GADTs
5+ , LambdaCase
6+ , MultiParamTypeClasses
7+ , OverloadedStrings
8+ , PolyKinds
9+ , QuantifiedConstraints
10+ , RankNTypes
11+ , ScopedTypeVariables
12+ , TypeApplications
13+ , TypeFamilies
14+ , TypeOperators
15+ , UndecidableInstances
16+#-}
17+
18+module Squeal.PostgreSQL.List
19+ ( SOP.NP (..)
20+ , (*:)
21+ , Join
22+ , (+++)
23+ , disjoin
24+ , AlignedList (..)
25+ , single
26+ , extractList
27+ , alignedMap
28+ , Elem
29+ , In
30+ , Length
31+ ) where
32+
33+import Control.Category
34+import Data.Kind
35+import Data.Type.Bool
36+import GHC.TypeLits
37+
38+import Generics.SOP as SOP
39+
40+import Squeal.PostgreSQL.Render
41+
42+-- | `Join` is simply promoted `++` and is used in @JOIN@s in
43+-- `Squeal.PostgreSQL.Query.FromClause`s.
44+type family Join xs ys where
45+ Join '[] ys = ys
46+ Join (x ': xs) ys = x ': Join xs ys
47+
48+-- | Concatenate `NP` lists.
49+(+++) :: NP f xs -> NP f ys -> NP f (Join xs ys)
50+xs +++ ys = case xs of
51+ Nil -> ys
52+ x :* xs' -> x :* (xs' +++ ys)
53+infixr 5 +++
54+
55+-- | `disjoin` is a utility function for splitting an `NP` list into pieces.
56+disjoin
57+ :: forall xs ys expr. SListI xs
58+ => NP expr (Join xs ys)
59+ -> (NP expr xs, NP expr ys)
60+disjoin = case sList @xs of
61+ SNil -> \ys -> (Nil, ys)
62+ SCons -> \(x :* xsys) ->
63+ case disjoin xsys of (xs,ys) -> (x :* xs, ys)
64+
65+-- | An `AlignedList` is a type-aligned list or free category.
66+data AlignedList p x0 x1 where
67+ Done :: AlignedList p x x
68+ (:>>) :: p x0 x1 -> AlignedList p x1 x2 -> AlignedList p x0 x2
69+infixr 7 :>>
70+instance Category (AlignedList p) where
71+ id = Done
72+ (.) list = \case
73+ Done -> list
74+ step :>> steps -> step :>> (steps >>> list)
75+instance (forall t0 t1. RenderSQL (p t0 t1))
76+ => RenderSQL (AlignedList p x0 x1) where
77+ renderSQL = \case
78+ Done -> ""
79+ step :>> Done -> renderSQL step
80+ step :>> steps -> renderSQL step <> ", " <> renderSQL steps
81+
82+extractList :: (forall a0 a1. p a0 a1 -> b) -> AlignedList p x0 x1 -> [b]
83+extractList f = \case
84+ Done -> []
85+ step :>> steps -> (f step):extractList f steps
86+
87+alignedMap
88+ :: (forall z0 z1. p z0 z1 -> q z0 z1)
89+ -> AlignedList p x0 x1
90+ -> AlignedList q x0 x1
91+alignedMap f = \case
92+ Done -> Done
93+ x :>> xs -> f x :>> alignedMap f xs
94+
95+-- | A `single` step.
96+single :: p x0 x1 -> AlignedList p x0 x1
97+single step = step :>> Done
98+
99+-- | A useful operator for ending an `NP` list of length at least 2 without `Nil`.
100+(*:) :: f x -> f y -> NP f '[x,y]
101+x *: y = x :* y :* Nil
102+infixl 9 *:
103+
104+-- | @Elem@ is a promoted `Data.List.elem`.
105+type family Elem x xs where
106+ Elem x '[] = 'False
107+ Elem x (x ': xs) = 'True
108+ Elem x (_ ': xs) = Elem x xs
109+
110+-- | @In x xs@ is a constraint that proves that @x@ is in @xs@.
111+type family In x xs :: Constraint where
112+ In x xs = If (Elem x xs) (() :: Constraint)
113+ (TypeError ('ShowType x ':<>: 'Text "is not in " ':<>: 'ShowType xs))
114+
115+{- | Calculate the `Length` of a type level list
116+
117+>>> :kind! Length '[Char,String,Bool,Double]
118+Length '[Char,String,Bool,Double] :: Nat
119+= 4
120+-}
121+type family Length (xs :: [k]) :: Nat where
122+ Length (x : xs) = 1 + Length xs
123+ Length '[] = 0
squeal-postgresql/src/Squeal/PostgreSQL/Manipulation.hs+3−1
@@ -62,8 +62,10 @@ import GHC.TypeLits (KnownSymbol)
62import qualified Generics.SOP as SOP
62import qualified Generics.SOP as SOP
63import qualified GHC.Generics as GHC
63import qualified GHC.Generics as GHC
64
64
65−import Squeal.PostgreSQL.Binary
65+import Squeal.PostgreSQL.Alias
66import Squeal.PostgreSQL.Expression
66import Squeal.PostgreSQL.Expression
67+import Squeal.PostgreSQL.List
68+import Squeal.PostgreSQL.PG
67import Squeal.PostgreSQL.Render
69import Squeal.PostgreSQL.Render
68import Squeal.PostgreSQL.Query
70import Squeal.PostgreSQL.Query
69import Squeal.PostgreSQL.Schema
71import Squeal.PostgreSQL.Schema
squeal-postgresql/src/Squeal/PostgreSQL/Migration.hs+21−20
@@ -147,22 +147,23 @@ module Squeal.PostgreSQL.Migration
147 , defaultMain
147 , defaultMain
148 ) where
148 ) where
149
149
150−import Control.Category
150+import Control.Category
151−import Control.Monad
151+import Control.Monad
152−import Data.ByteString (ByteString)
152+import Data.ByteString (ByteString)
153−import Data.Foldable (traverse_)
153+import Data.Foldable (traverse_)
154−import Data.Function ((&))
154+import Data.Function ((&))
155−import Data.List ((\\))
155+import Data.List ((\\))
156−import Data.Text (Text)
156+import Data.Text (Text)
157−import qualified Data.Text.IO as T (putStrLn)
158−import Data.Time (UTCTime)
157+import Data.Time (UTCTime)
159−import Generics.SOP (K (..))
158+import Generics.SOP (K (..))
160−import qualified Generics.SOP as SOP
161−import qualified GHC.Generics as GHC
162−import Prelude hiding ((.), id)
159+import Prelude hiding ((.), id)
163−import Squeal.PostgreSQL
160+import Squeal.PostgreSQL
164−import System.Environment
161+import System.Environment
165−import UnliftIO (MonadIO (..))
162+import UnliftIO (MonadIO (..))
163+
164+import qualified Data.Text.IO as Text (putStrLn)
165+import qualified Generics.SOP as SOP
166+import qualified GHC.Generics as GHC
166
167
167-- | A `Migration` is a named "isomorphism" over a given category.
168-- | A `Migration` is a named "isomorphism" over a given category.
168-- It should contain an inverse pair of `up` and `down`
169-- It should contain an inverse pair of `up` and `down`
@@ -437,17 +438,17 @@ defaultMain connectTo migrations = do
437 fmap migrationName <$> (unsafePQ (define createMigrations & pqThen (runQuery selectMigrations)) >>= getRows)
438 fmap migrationName <$> (unsafePQ (define createMigrations & pqThen (runQuery selectMigrations)) >>= getRows)
438
439
439 displayListOfNames :: [Text] -> IO ()
440 displayListOfNames :: [Text] -> IO ()
440− displayListOfNames [] = T.putStrLn " None"
441+ displayListOfNames [] = Text.putStrLn " None"
441 displayListOfNames xs =
442 displayListOfNames xs =
442− let singleName n = T.putStrLn $ " - " <> n
443+ let singleName n = Text.putStrLn $ " - " <> n
443 in traverse_ singleName xs
444 in traverse_ singleName xs
444
445
445 displayUnrunned :: [Text] -> IO ()
446 displayUnrunned :: [Text] -> IO ()
446 displayUnrunned unrunned =
447 displayUnrunned unrunned =
447− T.putStrLn "Migrations left to run:"
448+ Text.putStrLn "Migrations left to run:"
448 >> displayListOfNames unrunned
449 >> displayListOfNames unrunned
449
450
450 displayRunned :: [Text] -> IO ()
451 displayRunned :: [Text] -> IO ()
451 displayRunned runned =
452 displayRunned runned =
452− T.putStrLn "Migrations already run:"
453+ Text.putStrLn "Migrations already run:"
453 >> displayListOfNames runned
454 >> displayListOfNames runned
squeal-postgresql/src/Squeal/PostgreSQL/PG.hsnew file+297
@@ -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
squeal-postgresql/src/Squeal/PostgreSQL/Query.hs+3−1
@@ -112,8 +112,10 @@ import GHC.TypeLits
112
112
113import qualified GHC.Generics as GHC
113import qualified GHC.Generics as GHC
114
114
115−import Squeal.PostgreSQL.Binary
115+import Squeal.PostgreSQL.Alias
116import Squeal.PostgreSQL.Expression
116import Squeal.PostgreSQL.Expression
117+import Squeal.PostgreSQL.List
118+import Squeal.PostgreSQL.PG
117import Squeal.PostgreSQL.Render
119import Squeal.PostgreSQL.Render
118import Squeal.PostgreSQL.Schema
120import Squeal.PostgreSQL.Schema
119
121
squeal-postgresql/src/Squeal/PostgreSQL/Schema.hs+15−302
@@ -10,7 +10,6 @@ tables, schema, constraints, aliases, enumerated labels, and groupings.
10It also defines useful type families to operate on these. Finally,
10It also defines useful type families to operate on these. Finally,
11it defines an embedding of Haskell types into Postgres types.
11it defines an embedding of Haskell types into Postgres types.
12-}
12-}
13−{-# OPTIONS_GHC -fno-warn-unticked-promoted-constructors #-}
14{-# LANGUAGE
13{-# LANGUAGE
15 AllowAmbiguousTypes
14 AllowAmbiguousTypes
16 , ConstraintKinds
15 , ConstraintKinds
@@ -54,33 +53,9 @@ module Squeal.PostgreSQL.Schema
54 , TableConstraint (..)
53 , TableConstraint (..)
55 , TableConstraints
54 , TableConstraints
56 , Uniquely
55 , Uniquely
57− -- * Aliases
58− , (:::)
59− , Alias (..)
60− , IsLabel (..)
61− , Aliased (As)
62− , Aliasable (as)
63− , renderAliased
64− , Has
65− , HasUnique
66− , HasAll
67− , QualifiedAlias (..)
68− , IsQualified (..)
69− , HasIn
70− , IsNotElem
71− , AllUnique
72− , DefaultAliasable (..)
73 -- * Enumerated Labels
56 -- * Enumerated Labels
74 , IsPGlabel (..)
57 , IsPGlabel (..)
75 , PGlabel (..)
58 , PGlabel (..)
76− -- * Grouping
77− , Grouping (..)
78− , GroupedBy
79− -- * Aligned lists
80− , AlignedList (..)
81− , single
82− , extractList
83− , alignedMap
84 -- * Data Definitions
59 -- * Data Definitions
85 , Create
60 , Create
86 , Drop
61 , Drop
@@ -88,14 +63,8 @@ module Squeal.PostgreSQL.Schema
88 , Rename
63 , Rename
89 , ConstraintInvolves
64 , ConstraintInvolves
90 , DropIfConstraintsInvolve
65 , DropIfConstraintsInvolve
91− -- * Lists
66+ , IsNotElem
92− , Join
67+ , AllUnique
93− , disjoin
94− , Elem
95− , In
96− , Length
97− , (*:)
98− , (+++)
99 -- * Type Classifications
68 -- * Type Classifications
100 , HasOid (..)
69 , HasOid (..)
101 , PGNum
70 , PGNum
@@ -118,20 +87,15 @@ module Squeal.PostgreSQL.Schema
118 ) where
87 ) where
119
88
120import Control.Category
89import Control.Category
121−import Control.DeepSeq
122−import Data.ByteString (ByteString)
123import Data.Kind
90import Data.Kind
124import Data.Monoid hiding (All)
91import Data.Monoid hiding (All)
125−import Data.String
126import Data.Type.Bool
92import Data.Type.Bool
127import Data.Word (Word32)
93import Data.Word (Word32)
128import Generics.SOP
94import Generics.SOP
129−import GHC.OverloadedLabels
130import GHC.TypeLits
95import GHC.TypeLits
131import Prelude hiding (id, (.))
96import Prelude hiding (id, (.))
132
97
133−import qualified GHC.Generics as GHC
98+import Squeal.PostgreSQL.Alias
134−
135import Squeal.PostgreSQL.Render
99import Squeal.PostgreSQL.Render
136
100
137-- | `PGType` is the promoted datakind of PostgreSQL types.
101-- | `PGType` is the promoted datakind of PostgreSQL types.
@@ -216,11 +180,6 @@ data NullityType
216type (:=>) constraint ty = '(constraint,ty)
180type (:=>) constraint ty = '(constraint,ty)
217infixr 7 :=>
181infixr 7 :=>
218
182
219−-- | The alias operator `:::` is like a promoted version of `As`,
220−-- a type level pair between an alias and some type.
221−type (:::) (alias :: Symbol) ty = '(alias,ty)
222−infixr 6 :::
223−
224-- | `ColumnConstraint` encodes the availability of @DEFAULT@ for inserts and updates.
183-- | `ColumnConstraint` encodes the availability of @DEFAULT@ for inserts and updates.
225-- A column can be assigned a default value.
184-- A column can be assigned a default value.
226-- A data `Squeal.PostgreSQL.Manipulations.Manipulation` command can also
185-- A data `Squeal.PostgreSQL.Manipulations.Manipulation` command can also
@@ -327,203 +286,6 @@ type family TableToColumns (table :: TableType) :: ColumnsType where
327type family TableToRow (table :: TableType) :: RowType where
286type family TableToRow (table :: TableType) :: RowType where
328 TableToRow tab = ColumnsToRow (TableToColumns tab)
287 TableToRow tab = ColumnsToRow (TableToColumns tab)
329
288
330−-- | `Grouping` is an auxiliary namespace, created by
331−-- @GROUP BY@ clauses (`Squeal.PostgreSQL.Query.group`), and used
332−-- for typesafe aggregation
333−data Grouping
334− = Ungrouped -- ^ no aggregation permitted
335− | Grouped [(Symbol,Symbol)] -- ^ aggregation required for any column which is not grouped
336−
337−{- | A `GroupedBy` constraint indicates that a table qualified column is
338−a member of the auxiliary namespace created by @GROUP BY@ clauses and thus,
339−may be called in an output `Squeal.PostgreSQL.Expression.Expression` without aggregating.
340−-}
341−class (KnownSymbol table, KnownSymbol column)
342− => GroupedBy table column bys where
343−instance {-# OVERLAPPING #-} (KnownSymbol table, KnownSymbol column)
344− => GroupedBy table column ('(table,column) ': bys)
345−instance {-# OVERLAPPABLE #-}
346− ( KnownSymbol table
347− , KnownSymbol column
348− , GroupedBy table column bys
349− ) => GroupedBy table column (tabcol ': bys)
350−
351−-- | `Alias`es are proxies for a type level string or `Symbol`
352−-- and have an `IsLabel` instance so that with @-XOverloadedLabels@
353−--
354−-- >>> :set -XOverloadedLabels
355−-- >>> #foobar :: Alias "foobar"
356−-- Alias
357−data Alias (alias :: Symbol) = Alias
358− deriving (Eq,GHC.Generic,Ord,Show,NFData)
359−instance alias1 ~ alias2 => IsLabel alias1 (Alias alias2) where
360− fromLabel = Alias
361−instance aliases ~ '[alias] => IsLabel alias (NP Alias aliases) where
362− fromLabel = fromLabel :* Nil
363−-- | >>> printSQL (#jimbob :: Alias "jimbob")
364−-- "jimbob"
365−instance KnownSymbol alias => RenderSQL (Alias alias) where
366− renderSQL = doubleQuoted . fromString . symbolVal
367−
368−-- | >>> import Generics.SOP (NP(..))
369−-- >>> printSQL (#jimbob :* #kandi :: NP Alias '["jimbob", "kandi"])
370−-- "jimbob", "kandi"
371−instance All KnownSymbol aliases => RenderSQL (NP Alias aliases) where
372− renderSQL
373− = commaSeparated
374− . hcollapse
375− . hcmap (Proxy @KnownSymbol) (K . renderSQL)
376−
377−-- | The `As` operator is used to name an expression. `As` is like a demoted
378−-- version of `:::`.
379−--
380−-- >>> Just "hello" `As` #hi :: Aliased Maybe ("hi" ::: String)
381−-- As (Just "hello") Alias
382−data Aliased expression aliased where
383− As
384− :: KnownSymbol alias
385− => expression ty
386− -> Alias alias
387− -> Aliased expression (alias ::: ty)
388−deriving instance Show (expression ty)
389− => Show (Aliased expression (alias ::: ty))
390−deriving instance Eq (expression ty)
391− => Eq (Aliased expression (alias ::: ty))
392−deriving instance Ord (expression ty)
393− => Ord (Aliased expression (alias ::: ty))
394−instance (alias0 ~ alias1, alias0 ~ alias2, KnownSymbol alias2)
395− => IsLabel alias0 (Aliased Alias (alias1 ::: alias2)) where
396− fromLabel = fromLabel @alias2 `As` fromLabel @alias1
397−
398−-- | The `Aliasable` class provides a way to scrap your `Nil`s
399−-- in an `NP` list of `Aliased` expressions.
400−class KnownSymbol alias => Aliasable alias expression aliased
401− | aliased -> expression
402− , aliased -> alias
403− where as :: expression -> Alias alias -> aliased
404−instance (KnownSymbol alias, aliased ~ (alias ::: ty)) => Aliasable alias
405− (expression ty)
406− (Aliased expression aliased)
407− where
408− as = As
409−instance (KnownSymbol alias, tys ~ '[alias ::: ty]) => Aliasable alias
410− (expression ty)
411− (NP (Aliased expression) tys)
412− where
413− expression `as` alias = expression `As` alias :* Nil
414−
415−-- | >>> let renderMaybe = fromString . maybe "Nothing" (const "Just")
416−-- >>> renderAliased renderMaybe (Just (3::Int) `As` #an_int)
417−-- "Just AS \"an_int\""
418−renderAliased
419− :: (forall ty. expression ty -> ByteString)
420− -> Aliased expression aliased
421− -> ByteString
422−renderAliased render (expression `As` alias) =
423− render expression <> " AS " <> renderSQL alias
424−
425−-- | @HasUnique alias fields field@ is a constraint that proves that
426−-- @fields@ is a singleton of @alias ::: field@.
427−type HasUnique alias fields field = fields ~ '[alias ::: field]
428−
429−-- | @Has alias fields field@ is a constraint that proves that
430−-- @fields@ has a field of @alias ::: field@, inferring @field@
431−-- from @alias@ and @fields@.
432−class KnownSymbol alias =>
433− Has (alias :: Symbol) (fields :: [(Symbol,kind)]) (field :: kind)
434− | alias fields -> field where
435−instance {-# OVERLAPPING #-} KnownSymbol alias
436− => Has alias (alias ::: field ': fields) field
437−instance {-# OVERLAPPABLE #-} (KnownSymbol alias, Has alias fields field)
438− => Has alias (field' ': fields) field
439−
440−{-| @HasIn fields (alias ::: field)@ is a constraint that proves that
441−@fields@ has a field of @alias ::: field@. It is used in @UPDATE@s to
442−choose which subfields to update.
443−-}
444−class HasIn fields field where
445−instance (Has alias fields field) => HasIn fields (alias ::: field) where
446−
447−-- | Utility class for `AllUnique` to provide nicer error messages.
448−class IsNotElem x isElem where
449−instance IsNotElem x 'False where
450−instance (TypeError ( 'Text "Cannot assign to "
451− ':<>: 'ShowType alias
452− ':<>: 'Text " more than once"))
453− => IsNotElem '(alias, a) 'True where
454−
455−-- | No elem of @xs@ appears more than once, in the context of assignment.
456−class AllUnique (xs :: [(Symbol, a)]) where
457−instance AllUnique '[] where
458−instance (IsNotElem x (Elem x xs), AllUnique xs) => AllUnique (x ': xs) where
459−
460−{- |
461−The `DefaultAliasable` class is intended to help with Scrap your Nils
462−for default inserts and updates.
463−-}
464−class KnownSymbol alias
465− => DefaultAliasable alias aliased | aliased -> alias where
466− defaultAs :: Alias alias -> aliased
467−
468−-- | `HasAll` extends `Has` to take lists of @aliases@ and @fields@ and infer
469−-- a list of @subfields@.
470−class
471− ( All KnownSymbol aliases
472− ) => HasAll
473− (aliases :: [Symbol])
474− (fields :: [(Symbol,kind)])
475− (subfields :: [(Symbol,kind)])
476− | aliases fields -> subfields where
477−instance {-# OVERLAPPING #-} HasAll '[] fields '[]
478−instance {-# OVERLAPPABLE #-}
479− (Has alias fields field, HasAll aliases fields subfields)
480− => HasAll (alias ': aliases) fields (alias ::: field ': subfields)
481−
482−-- | Analagous to `IsLabel`, the constraint
483−-- `IsQualified` defines `!` for a column alias qualified
484−-- by a table alias.
485−class IsQualified table column expression where
486− (!) :: Alias table -> Alias column -> expression
487− infixl 9 !
488−instance IsQualified table column (Alias table, Alias column) where (!) = (,)
489−
490−{-| `QualifiedAlias`es enables multi-schema support by allowing a reference
491−to a `Table`, `Typedef` or `View` to be qualified by their schemas. By default,
492−a qualifier of @public@ is provided.
493−
494−>>> :{
495−let
496− alias1 :: QualifiedAlias "sch" "tab"
497− alias1 = #sch ! #tab
498− alias2 :: QualifiedAlias "public" "vw"
499− alias2 = #vw
500−in printSQL alias1 >> printSQL alias2
501−:}
502−"sch"."tab"
503−"vw"
504−-}
505−data QualifiedAlias (qualifier :: Symbol) (alias :: Symbol) = QualifiedAlias
506− deriving (Eq,GHC.Generic,Ord,Show,NFData)
507−instance (q ~ q', a ~ a') => IsQualified q a (QualifiedAlias q' a') where
508− _!_ = QualifiedAlias
509−instance (q' ~ "public", a ~ a') => IsLabel a (QualifiedAlias q' a') where
510− fromLabel = QualifiedAlias
511−instance (q0 ~ q1, a0 ~ a1, a1 ~ a2, KnownSymbol a2) =>
512− IsQualified q0 a0 (Aliased (QualifiedAlias q1) (a1 ::: a2)) where
513− _!_ = QualifiedAlias `As` Alias
514−instance (q ~ "public", a0 ~ a1, a1 ~ a2, KnownSymbol a2) =>
515− IsLabel a0 (Aliased (QualifiedAlias q) (a1 ::: a2)) where
516− fromLabel = QualifiedAlias `As` Alias
517−
518−instance (KnownSymbol q, KnownSymbol a)
519− => RenderSQL (QualifiedAlias q a) where
520− renderSQL _ =
521− let
522− qualifier = renderSQL (Alias @q)
523− alias = renderSQL (Alias @a)
524− in
525− if qualifier == "\"public\"" then alias else qualifier <> "." <> alias
526−
527-- | @Elem@ is a promoted `Data.List.elem`.
289-- | @Elem@ is a promoted `Data.List.elem`.
528type family Elem x xs where
290type family Elem x xs where
529 Elem x '[] = 'False
291 Elem x '[] = 'False
@@ -584,22 +346,6 @@ type family NullifyFrom (tables :: FromType) :: FromType where
584 NullifyFrom (table ::: columns ': tables) =
346 NullifyFrom (table ::: columns ': tables) =
585 table ::: NullifyRow columns ': NullifyFrom tables
347 table ::: NullifyRow columns ': NullifyFrom tables
586
348
587−-- | `Join` is simply promoted `++` and is used in @JOIN@s in
588−-- `Squeal.PostgreSQL.Query.FromClause`s.
589−type family Join xs ys where
590− Join '[] ys = ys
591− Join (x ': xs) ys = x ': Join xs ys
592−
593−-- | `disjoin` is a utility function for splitting an `NP` list into pieces.
594−disjoin
595− :: forall xs ys expr. SListI xs
596− => NP expr (Join xs ys)
597− -> (NP expr xs, NP expr ys)
598−disjoin = case sList @xs of
599− SNil -> \ys -> (Nil, ys)
600− SCons -> \(x :* xsys) ->
601− case disjoin xsys of (xs,ys) -> (x :* xs, ys)
602−
603-- | @Create alias x xs@ adds @alias ::: x@ to the end of @xs@ and is used in
349-- | @Create alias x xs@ adds @alias ::: x@ to the end of @xs@ and is used in
604-- `Squeal.PostgreSQL.Definition.createTable` statements and in @ALTER TABLE@
350-- `Squeal.PostgreSQL.Definition.createTable` statements and in @ALTER TABLE@
605-- `Squeal.PostgreSQL.Definition.addColumn`.
351-- `Squeal.PostgreSQL.Definition.addColumn`.
@@ -734,48 +480,15 @@ type PGJsonKey = '[ 'PGint2, 'PGint4, 'PGtext ]
734-- | Is a type a valid JSON type?
480-- | Is a type a valid JSON type?
735type PGJsonType = '[ 'PGjson, 'PGjsonb ]
481type PGJsonType = '[ 'PGjson, 'PGjsonb ]
736
482
737−-- | An `AlignedList` is a type-aligned list or free category.
483+-- | Utility class for `AllUnique` to provide nicer error messages.
738−data AlignedList p x0 x1 where
484+class IsNotElem x isElem where
739− Done :: AlignedList p x x
485+instance IsNotElem x 'False where
740− (:>>) :: p x0 x1 -> AlignedList p x1 x2 -> AlignedList p x0 x2
486+instance (TypeError ( 'Text "Cannot assign to "
741−infixr 7 :>>
487+ ':<>: 'ShowType alias
742−instance Category (AlignedList p) where
488+ ':<>: 'Text " more than once"))
743− id = Done
489+ => IsNotElem '(alias, a) 'True where
744− (.) list = \case
490+
745− Done -> list
491+-- | No elem of @xs@ appears more than once, in the context of assignment.
746− step :>> steps -> step :>> (steps >>> list)
492+class AllUnique (xs :: [(Symbol, a)]) where
747−instance (forall t0 t1. RenderSQL (p t0 t1))
493+instance AllUnique '[] where
748− => RenderSQL (AlignedList p x0 x1) where
494+instance (IsNotElem x (Elem x xs), AllUnique xs) => AllUnique (x ': xs) where
749− renderSQL = \case
750− Done -> ""
751− step :>> Done -> renderSQL step
752− step :>> steps -> renderSQL step <> ", " <> renderSQL steps
753−
754−extractList :: (forall a0 a1. p a0 a1 -> b) -> AlignedList p x0 x1 -> [b]
755−extractList f = \case
756− Done -> []
757− step :>> steps -> (f step):extractList f steps
758−
759−alignedMap
760− :: (forall z0 z1. p z0 z1 -> q z0 z1)
761− -> AlignedList p x0 x1
762− -> AlignedList q x0 x1
763−alignedMap f = \case
764− Done -> Done
765− x :>> xs -> f x :>> alignedMap f xs
766−
767−-- | A `single` step.
768−single :: p x0 x1 -> AlignedList p x0 x1
769−single step = step :>> Done
770−
771−-- | A useful operator for ending an `NP` list of length at least 2 without `Nil`.
772−(*:) :: f x -> f y -> NP f '[x,y]
773−x *: y = x :* y :* Nil
774−infixl 9 *:
775−
776−-- | Concatenate `NP` lists.
777−(+++) :: NP f xs -> NP f ys -> NP f (Join xs ys)
778−xs +++ ys = case xs of
779− Nil -> ys
780− x :* xs' -> x :* (xs' +++ ys)
781−infixr 5 +++
squeal-postgresql/src/Squeal/PostgreSQL/Tutorial.hsnew file+39
@@ -0,0 +1,39 @@
1+{-|
2+Module: Squeal.PostgreSQL.Tutorail
3+Description: Squeal tutorial module
4+Copyright: (c) Eitan Chatav, 2017
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+== `P` records
9+
10+A `P` record is an ad-hoc, anonymous record constructed
11+as a tuple of `P`s, a datatype from `Generics.SOP.Record`, like so:
12+
13+>>> type Rec = (P ("foo" ::: Double), P ("bar" ::: Char))
14+
15+Let's see what the image of `Rec` is under the `RowPG` type family.
16+
17+>>> :kind! RowPG Rec
18+RowPG Rec :: [(Symbol, NullityType)]
19+= '[ '("foo", 'NotNull 'PGfloat8), "bar" ::: 'NotNull ('PGchar 1)]
20+
21+`P` records are ad-hoc, anonymous records so unlike Haskell records
22+you don't have to define them outside of their use site, for instance
23+in the row type parameter of a `Query_` type family.
24+
25+>>> :{
26+let
27+ query :: Query_ schemas () (P ("foo" ::: Double))
28+ query = values_ (1 `as` #foo)
29+in printSQL query
30+:}
31+SELECT * FROM (VALUES (1)) AS t ("foo")
32+
33+You can even mix `P` records with normal Haskell records, which is very
34+useful for creating entity types, Haskell records with associated ids.
35+
36+>>> data Row = Row {foo :: Double, bar :: Char} deriving (GHC.Generic)
37+-}
38+
39+module Squeal.PostgreSQL.Tutorial where