@@ -34,17 +34,24 @@ it defines an embedding of Haskell types into Postgres types.
36module Squeal.PostgreSQL.Schema
36module Squeal.PostgreSQL.Schema
37− ( -- * Types
37+ ( -- * Postgres Types
38 PGType (..)
38 PGType (..)
40 , NullityType (..)
39 , NullityType (..)
45 , FromType
41 , FromType
42+ -- * Haskell to Postgres Types
46 , TableType
54 , TableType
48 , SchemumType (..)
55 , SchemumType (..)
49 , SchemaType
56 , SchemaType
50 -- * Constraints
57 -- * Constraints
@@ -61,7 +68,6 @@ module Squeal.PostgreSQL.Schema
61 , Aliased (As)
68 , Aliased (As)
62 , Aliasable (as)
69 , Aliasable (as)
63 , renderAliasedAs
70 , renderAliasedAs
66 , HasUnique
72 , HasUnique
@@ -73,57 +79,45 @@ module Squeal.PostgreSQL.Schema
73 , PGlabel (..)
79 , PGlabel (..)
74 , renderLabel
80 , renderLabel
75 , renderLabels
81 , renderLabels
76 -- * Grouping
83 -- * Grouping
77 , Grouping (..)
84 , Grouping (..)
78 , GroupedBy
85 , GroupedBy
79− -- * Type Families
86+ -- * Aligned lists
80− , Join
87+ , AlignedList (..)
89+ -- * Data Definitions
94+ , DropIfConstraintsInvolve
100+ -- * Type Classifications
89 , PGIntegral
103 , PGIntegral
90 , PGFloating
104 , PGFloating
91 , PGTypeOf
105 , PGTypeOf
92− , PGarrayOf
106+ , PGArrayOf
93− , PGarray
107+ , PGArray
94− , PGtextArray
108+ , PGTextArray
95 , SamePGType
111 , SamePGType
96 , AllNotNull
112 , AllNotNull
97 , NotAllNull
113 , NotAllNull
98 , NullifyType
115 , NullifyType
99 , NullifyRow
116 , NullifyRow
100 , NullifyFrom
117 , NullifyFrom
101− , ColumnsToRow
118+ -- * Table Conversions
102 , TableToColumns
119 , TableToColumns
103 , TableToRow
120 , TableToRow
104− , ConstraintInvolves
105− , DropIfConstraintsInvolve
123− , ConstructorNamesOf
129import Control.Category
123import Control.Category
@@ -305,19 +299,24 @@ type family Uniquely
306type TableType = (TableConstraints,ColumnsType)
300type TableType = (TableConstraints,ColumnsType)
308−-- | `RowType` is a row of `NullityType`
302+{- | A `RowType` is a row of `NullityType`. They correspond to Haskell
309−--
303+record types by means of `RowPG` and are used in many places.
311−-- type family PersonRelation :: RowType where
306+type family PersonRow :: RowType where
312−-- PersonRelation =
307+ PersonRow =
313−-- '[ "name" ::: 'NotNull 'PGtext
308+ '[ "name" ::: 'NotNull 'PGtext
314−-- , "age" ::: 'NotNull 'PGint4
309+ , "age" ::: 'NotNull 'PGint4
315−-- , "dateOfBirth" ::: 'Null 'PGdate
310+ , "dateOfBirth" ::: 'Null 'PGdate
318type RowType = [(Symbol,NullityType)]
314type RowType = [(Symbol,NullityType)]
320−-- | `FromType` is a row of `RowType`s, thought of as a product.
316+{- | `FromType` is a row of `RowType`s. It can be thought of as
317+a product, or horizontal gluing and is used in `Squeal.PostgreSQL.Query.FromClause`s
318+and `Squeal.PostgreSQL.Query.TableExpression`s.
321type FromType = [(Symbol,RowType)]
320type FromType = [(Symbol,RowType)]
323-- | `ColumnsToRow` removes column constraints.
322-- | `ColumnsToRow` removes column constraints.
@@ -435,11 +434,6 @@ renderAliasedAs
435renderAliasedAs render (expression `As` alias) =
434renderAliasedAs render (expression `As` alias) =
436 render expression <> " AS " <> renderAlias alias
435 render expression <> " AS " <> renderAlias alias
438−-- | `AliasesOf` retains the AliasesOf in a row.
439−type family AliasesOf aliaseds where
441− AliasesOf (alias ::: ty ': tys) = alias ': AliasesOf tys
443-- | @HasUnique alias fields field@ is a constraint that proves that
437-- | @HasUnique alias fields field@ is a constraint that proves that
444-- @fields@ is a singleton of @alias ::: field@.
438-- @fields@ is a singleton of @alias ::: field@.
445type HasUnique alias fields field = fields ~ '[alias ::: field]
439type HasUnique alias fields field = fields ~ '[alias ::: field]
@@ -486,21 +480,15 @@ type family Elem x xs where
486-- | @In x xs@ is a constraint that proves that @x@ is in @xs@.
480-- | @In x xs@ is a constraint that proves that @x@ is in @xs@.
487type family In x xs :: Constraint where In x xs = Elem x xs ~ 'True
481type family In x xs :: Constraint where In x xs = Elem x xs ~ 'True
489−-- | `PGNum` is a constraint on `PGType` whose
483+-- | Numeric Postgres types.
490−-- `Squeal.PostgreSQL.Expression.Expression`s have a `Num` constraint.
491−type PGNum ty =
484+type PGNum =
492− In ty '[ 'PGint2, 'PGint4, 'PGint8, 'PGnumeric, 'PGfloat4, 'PGfloat8]
485+ '[ 'PGint2, 'PGint4, 'PGint8, 'PGnumeric, 'PGfloat4, 'PGfloat8]
494−-- | `PGFloating` is a constraint on `PGType` whose
487+-- | Floating Postgres types.
495−-- `Squeal.PostgreSQL.Expression.Expression`s
496−-- have `Fractional` and `Floating` constraints.
497−type PGFloating ty = In ty '[ 'PGfloat4, 'PGfloat8, 'PGnumeric]
488+type PGFloating = '[ 'PGfloat4, 'PGfloat8, 'PGnumeric]
499−-- | `PGIntegral` is a constraint on `PGType` whose
490+-- | Integral Postgres types.
500−-- `Squeal.PostgreSQL.Expression.Expression`s
501−-- have `Squeal.PostgreSQL.Expression.div_` and
502−-- `Squeal.PostgreSQL.Expression.mod_` functions.
503−type PGIntegral ty = In ty '[ 'PGint2, 'PGint4, 'PGint8]
491+type PGIntegral = '[ 'PGint2, 'PGint4, 'PGint8]
505-- | Error message helper for displaying unavailable\/unknown\/placeholder type
493-- | Error message helper for displaying unavailable\/unknown\/placeholder type
506-- variables whose kind is known.
494-- variables whose kind is known.
@@ -511,27 +499,27 @@ type ErrPGfixarrayOf t = ErrArrayOf ('ShowType 'PGfixarray :<>: 'Text " " :<>: P
511type ErrPGvararrayOf t = ErrArrayOf ('ShowType 'PGvararray) t
499type ErrPGvararrayOf t = ErrArrayOf ('ShowType 'PGvararray) t
513-- | Ensure a type is a valid array type.
501-- | Ensure a type is a valid array type.
514−type family PGarray name arr :: Constraint where
502+type family PGArray name arr :: Constraint where
515− PGarray name ('PGvararray x) = ()
503+ PGArray name ('PGvararray x) = ()
516− PGarray name ('PGfixarray n x) = ()
504+ PGArray name ('PGfixarray n x) = ()
517− PGarray name val = TypeError
505+ PGArray name val = TypeError
518− ('Text name :<>: 'Text ": Unsatisfied PGarray constraint. Expected either: "
506+ ('Text name :<>: 'Text ": Unsatisfied PGArray constraint. Expected either: "
519 :$$: 'Text " • " :<>: ErrPGvararrayOf (Placeholder PGType)
507 :$$: 'Text " • " :<>: ErrPGvararrayOf (Placeholder PGType)
520 :$$: 'Text " • " :<>: ErrPGfixarrayOf (Placeholder PGType)
508 :$$: 'Text " • " :<>: ErrPGfixarrayOf (Placeholder PGType)
521 :$$: 'Text "But got: " :<>: 'ShowType val)
509 :$$: 'Text "But got: " :<>: 'ShowType val)
523-- | Ensure a type is a valid array type with a specific element type.
511-- | Ensure a type is a valid array type with a specific element type.
524−type family PGarrayOf name arr ty :: Constraint where
512+type family PGArrayOf name arr ty :: Constraint where
525− PGarrayOf name ('PGvararray x) ty = x ~ ty
513+ PGArrayOf name ('PGvararray x) ty = x ~ ty
526− PGarrayOf name ('PGfixarray n x) ty = x ~ ty
514+ PGArrayOf name ('PGfixarray n x) ty = x ~ ty
527− PGarrayOf name val ty = TypeError
515+ PGArrayOf name val ty = TypeError
528− ( 'Text name :<>: 'Text "Unsatisfied PGarrayOf constraint. Expected either: "
516+ ( 'Text name :<>: 'Text "Unsatisfied PGArrayOf constraint. Expected either: "
529 :$$: 'Text " • " :<>: ErrPGvararrayOf ( 'ShowType ty )
517 :$$: 'Text " • " :<>: ErrPGvararrayOf ( 'ShowType ty )
530 :$$: 'Text " • " :<>: ErrPGfixarrayOf ( 'ShowType ty )
518 :$$: 'Text " • " :<>: ErrPGfixarrayOf ( 'ShowType ty )
531 :$$: 'Text "But got: " :<>: 'ShowType val)
519 :$$: 'Text "But got: " :<>: 'ShowType val)
533-- | Ensure a type is a valid array type whose elements are text.
521-- | Ensure a type is a valid array type whose elements are text.
534−type PGtextArray name arr = PGarrayOf name arr ('NotNull 'PGtext)
522+type PGTextArray name arr = PGArrayOf name arr ('NotNull 'PGtext)
536-- | `PGTypeOf` forgets about @NULL@ and any column constraints.
524-- | `PGTypeOf` forgets about @NULL@ and any column constraints.
537type family PGTypeOf (ty :: NullityType) :: PGType where
525type family PGTypeOf (ty :: NullityType) :: PGType where
@@ -628,6 +616,12 @@ type family DropIfConstraintsInvolve column constraints where
628 (DropIfConstraintsInvolve column constraints)
616 (DropIfConstraintsInvolve column constraints)
629 (alias ::: constraint ': DropIfConstraintsInvolve column constraints)
617 (alias ::: constraint ': DropIfConstraintsInvolve column constraints)
619+{- | Calculate the `Length` of a type level list
621+>>> :kind! Length '[Char,String,Bool,Double]
622+Length '[Char,String,Bool,Double] :: Nat
631type family Length (xs :: [k]) :: Nat where
625type family Length (xs :: [k]) :: Nat where
632 Length (x : xs) = 1 + Length xs
626 Length (x : xs) = 1 + Length xs
633 Length '[] = 0
627 Length '[] = 0
@@ -639,8 +633,28 @@ data SchemumType
639 | View RowType
633 | View RowType
640 | Typedef PGType
634 | Typedef PGType
642−-- | The schema of a database consists of a list of aliased,
636+{- | The schema of a database consists of a list of aliased,
643−-- user-defined `SchemumType`s.
637+user-defined `SchemumType`s.
640+type family Schema :: SchemaType where
642+ '[ "users" ::: 'Table (
643+ '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
644+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
645+ , "name" ::: 'NoDef :=> 'NotNull 'PGtext
647+ , "emails" ::: 'Table (
648+ '[ "pk_emails" ::: 'PrimaryKey '["id"]
649+ , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
651+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
652+ , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
653+ , "email" ::: 'NoDef :=> 'Null 'PGtext
644type SchemaType = [(Symbol,SchemumType)]
658type SchemaType = [(Symbol,SchemumType)]
646-- | `IsPGlabel` looks very much like the `IsLabel` class. Whereas
660-- | `IsPGlabel` looks very much like the `IsLabel` class. Whereas
@@ -664,12 +678,16 @@ renderLabels
664renderLabels = hcollapse
678renderLabels = hcollapse
665 . hcmap (Proxy @KnownSymbol) (K . renderLabel)
679 . hcmap (Proxy @KnownSymbol) (K . renderLabel)
667−-- | The `PG` type family embeds a subset of Haskell types
681+{- | The `PG` type family embeds a subset of Haskell types
668−-- as Postgres basic types.
682+as Postgres types. As an open type family, `PG` is extensible.
670−-- >>> :kind! PG LocalTime
684+>>> :kind! PG LocalTime
671−-- PG LocalTime :: PGType
685+PG LocalTime :: PGType
672−-- = 'PGtimestamp
686+= 'PGtimestamp
688+>>> newtype MyDouble = My Double
689+>>> type instance PG MyDouble = 'PGfloat8
673type family PG (hask :: Type) :: PGType
691type family PG (hask :: Type) :: PGType
674type instance PG Bool = 'PGbool
692type instance PG Bool = 'PGbool
675type instance PG Int16 = 'PGint2
693type instance PG Int16 = 'PGint2
@@ -716,28 +734,53 @@ type instance PG (hask, hask, hask, hask, hask, hask, hask, hask, hask, hask)
716type instance PG (Composite hask) = 'PGcomposite (RowPG hask)
734type instance PG (Composite hask) = 'PGcomposite (RowPG hask)
717type instance PG (Enumerated hask) = 'PGenum (LabelsPG hask)
735type instance PG (Enumerated hask) = 'PGenum (LabelsPG hask)
737+{- | The `Json` newtype is an indication that the Haskell
738+type it's applied to should be stored as `PGjson`.
719newtype Json hask = Json {getJson :: hask}
740newtype Json hask = Json {getJson :: hask}
720 deriving (Eq, Ord, Show, Read, GHC.Generic)
741 deriving (Eq, Ord, Show, Read, GHC.Generic)
743+{- | The `Jsonb` newtype is an indication that the Haskell
744+type it's applied to should be stored as `PGjsonb`.
721newtype Jsonb hask = Jsonb {getJsonb :: hask}
746newtype Jsonb hask = Jsonb {getJsonb :: hask}
722 deriving (Eq, Ord, Show, Read, GHC.Generic)
747 deriving (Eq, Ord, Show, Read, GHC.Generic)
749+{- | The `Composite` newtype is an indication that the Haskell
750+type it's applied to should be stored as a `PGcomposite`.
723newtype Composite record = Composite {getComposite :: record}
752newtype Composite record = Composite {getComposite :: record}
724 deriving (Eq, Ord, Show, Read, GHC.Generic)
753 deriving (Eq, Ord, Show, Read, GHC.Generic)
755+{- | The `Enumerated` newtype is an indication that the Haskell
756+type it's applied to should be stored as `PGenum`.
725newtype Enumerated enum = Enumerated {getEnumerated :: enum}
758newtype Enumerated enum = Enumerated {getEnumerated :: enum}
726 deriving (Eq, Ord, Show, Read, GHC.Generic)
759 deriving (Eq, Ord, Show, Read, GHC.Generic)
728−-- | The `LabelsPG` type family calculates the constructors of a
761+{-| The `LabelsPG` type family calculates the constructors of a
729−-- Haskell enum type.
762+Haskell enum type.
731−-- >>> data Schwarma = Beef | Lamb | Chicken deriving GHC.Generic
764+>>> data Schwarma = Beef | Lamb | Chicken deriving GHC.Generic
732−-- >>> instance Generic Schwarma
765+>>> instance Generic Schwarma
733−-- >>> instance HasDatatypeInfo Schwarma
766+>>> instance HasDatatypeInfo Schwarma
734−-- >>> :kind! LabelsPG Schwarma
767+>>> :kind! LabelsPG Schwarma
735−-- LabelsPG Schwarma :: [Type.ConstructorName]
768+LabelsPG Schwarma :: [Type.ConstructorName]
736−-- = '["Beef", "Lamb", "Chicken"]
769+= '["Beef", "Lamb", "Chicken"]
737type family LabelsPG (hask :: Type) :: [Type.ConstructorName] where
771type family LabelsPG (hask :: Type) :: [Type.ConstructorName] where
738 LabelsPG hask =
772 LabelsPG hask =
739 ConstructorNamesOf (ConstructorsOf (DatatypeInfoOf hask))
773 ConstructorNamesOf (ConstructorsOf (DatatypeInfoOf hask))
775+{- | `RowPG` turns a Haskell record type into a `RowType`.
777+>>> data Person = Person { name :: Text, age :: Int32 } deriving GHC.Generic
778+>>> instance Generic Person
779+>>> instance HasDatatypeInfo Person
780+>>> :kind! RowPG Person
781+RowPG Person :: [(Symbol, NullityType)]
782+= '["name" ::: 'NotNull 'PGtext, "age" ::: 'NotNull 'PGint4]
741type family RowPG (hask :: Type) :: RowType where
784type family RowPG (hask :: Type) :: RowType where
742 RowPG hask = RowOf (RecordCodeOf hask)
785 RowPG hask = RowOf (RecordCodeOf hask)
@@ -748,10 +791,26 @@ type family RowOf (fields :: [(Symbol, Type)]) :: RowType where
748type family FieldPG (field :: (Symbol, Type)) :: (Symbol, NullityType) where
791type family FieldPG (field :: (Symbol, Type)) :: (Symbol, NullityType) where
749 FieldPG (field ::: hask) = field ::: NullPG hask
792 FieldPG (field ::: hask) = field ::: NullPG hask
794+{- | `NullPG` turns a Haskell type into a `NullityType`.
796+>>> :kind! NullPG Double
797+NullPG Double :: NullityType
799+>>> :kind! NullPG (Maybe Double)
800+NullPG (Maybe Double) :: NullityType
751type family NullPG (hask :: Type) :: NullityType where
803type family NullPG (hask :: Type) :: NullityType where
752 NullPG (Maybe hask) = 'Null (PG hask)
804 NullPG (Maybe hask) = 'Null (PG hask)
753 NullPG hask = 'NotNull (PG hask)
805 NullPG hask = 'NotNull (PG hask)
807+{- | `TuplePG` turns a Haskell tuple type (including record types) into
808+the corresponding list of `NullityType`s.
810+>>> :kind! TuplePG (Double, Maybe Char)
811+TuplePG (Double, Maybe Char) :: [NullityType]
812+= '['NotNull 'PGfloat8, 'Null ('PGchar 1)]
755type family TuplePG (hask :: Type) :: [NullityType] where
814type family TuplePG (hask :: Type) :: [NullityType] where
756 TuplePG hask = TupleOf (TupleCodeOf hask (Code hask))
815 TuplePG hask = TupleOf (TupleCodeOf hask (Code hask))
@@ -800,10 +859,10 @@ type family ConstructorNamesOf (constructors :: [Type.ConstructorInfo])
800 ConstructorNameOf constructor ': ConstructorNamesOf constructors
859 ConstructorNameOf constructor ': ConstructorNamesOf constructors
802-- | Is a type a valid JSON key?
861-- | Is a type a valid JSON key?
803−type PGjsonKey key = key `In` '[ 'PGint2, 'PGint4, 'PGtext ]
862+type PGJsonKey = '[ 'PGint2, 'PGint4, 'PGtext ]
805-- | Is a type a valid JSON type?
864-- | Is a type a valid JSON type?
806−type PGjson_ json = json `In` '[ 'PGjson, 'PGjsonb ]
865+type PGJsonType = '[ 'PGjson, 'PGjsonb ]
808-- | An `AlignedList` is a type-aligned list or free category.
867-- | An `AlignedList` is a type-aligned list or free category.
809data AlignedList p x0 x1 where
868data AlignedList p x0 x1 where