on dev help sign in
Merge pull request #121 from morphismtech/dev-0.5

Getting closer to 0.5

commit4c6fa9564d724d1630e2d84fe545e7a3b4898fef authorEitan Chatav <eitan.chatav@gmail.com> dateSat 13 Apr 2019, 18:17 (8 years ago) parentb7781f48b44f7f
32 files changed+4188−3127
squeal-postgresql/squeal-postgresql.cabal+21−1 squeal-postgresql/src/Squeal/PostgreSQL.hs+42−2 squeal-postgresql/src/Squeal/PostgreSQL/Alias.hs+258 squeal-postgresql/src/Squeal/PostgreSQL/Binary.hs+24−258 squeal-postgresql/src/Squeal/PostgreSQL/Definition.hs+8−1 squeal-postgresql/src/Squeal/PostgreSQL/Expression.hs+30−2223 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Aggregate.hs+447 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Collection.hs+159 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Comparison.hs+206 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Json.hs+528 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Literal.hs+87 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Logic.hs+105 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Math.hs+103 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Null.hs+104 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Parameter.hs+80 squeal-postgresql/src/Squeal/PostgreSQL/Expression/SetOf.hs+106 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Sort.hs+85 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Subquery.hs+121 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Text.hs+65 squeal-postgresql/src/Squeal/PostgreSQL/Expression/TextSearch.hs+158 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Time.hs+197 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Type.hs+283 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Window.hs+284 squeal-postgresql/src/Squeal/PostgreSQL/List.hs+141 squeal-postgresql/src/Squeal/PostgreSQL/Manipulation.hs+7−16 squeal-postgresql/src/Squeal/PostgreSQL/Migration.hs+30−31 squeal-postgresql/src/Squeal/PostgreSQL/PG.hs+389 squeal-postgresql/src/Squeal/PostgreSQL/PQ.hs+3 squeal-postgresql/src/Squeal/PostgreSQL/Query.hs+42−288 squeal-postgresql/src/Squeal/PostgreSQL/Render.hs+18−3 squeal-postgresql/src/Squeal/PostgreSQL/Schema.hs+18−304 squeal-postgresql/src/Squeal/PostgreSQL/Tutorial.hs+39
squeal-postgresql/squeal-postgresql.cabal+21−1
@@ -22,19 +22,39 @@ library
2222 hs-source-dirs: src
2323 exposed-modules:
2424 Squeal.PostgreSQL
25+ Squeal.PostgreSQL.Alias
2526 Squeal.PostgreSQL.Binary
2627 Squeal.PostgreSQL.Definition
2728 Squeal.PostgreSQL.Expression
29+ Squeal.PostgreSQL.Expression.Aggregate
30+ Squeal.PostgreSQL.Expression.Collection
31+ Squeal.PostgreSQL.Expression.Comparison
32+ Squeal.PostgreSQL.Expression.Json
33+ Squeal.PostgreSQL.Expression.Literal
34+ Squeal.PostgreSQL.Expression.Logic
35+ Squeal.PostgreSQL.Expression.Math
36+ Squeal.PostgreSQL.Expression.Null
37+ Squeal.PostgreSQL.Expression.Parameter
38+ Squeal.PostgreSQL.Expression.SetOf
39+ Squeal.PostgreSQL.Expression.Sort
40+ Squeal.PostgreSQL.Expression.Subquery
41+ Squeal.PostgreSQL.Expression.Text
42+ Squeal.PostgreSQL.Expression.TextSearch
43+ Squeal.PostgreSQL.Expression.Time
44+ Squeal.PostgreSQL.Expression.Type
45+ Squeal.PostgreSQL.Expression.Window
46+ Squeal.PostgreSQL.List
2847 Squeal.PostgreSQL.Manipulation
2948 Squeal.PostgreSQL.Migration
3049 Squeal.PostgreSQL.Pool
50+ Squeal.PostgreSQL.PG
3151 Squeal.PostgreSQL.PQ
3252 Squeal.PostgreSQL.Render
3353 Squeal.PostgreSQL.Query
3454 Squeal.PostgreSQL.Schema
3555 Squeal.PostgreSQL.Transaction
3656 default-language: Haskell2010
37− ghc-options: -Wall
57+ ghc-options: -Wall -Werror
3858 build-depends:
3959 aeson >= 1.2.4.0
4060 , base >= 4.11.1.0 && < 5.0
squeal-postgresql/src/Squeal/PostgreSQL.hs+42−2
@@ -1,7 +1,7 @@
11{-|
22Module: Squeal.PostgreSQL
33Description: Squeal export module
4−Copyright: (c) Eitan Chatav, 2017
4+Copyright: (c) Eitan Chatav, 2019
55Maintainer: eitan@morphism.tech
66Stability: experimental
77
@@ -194,20 +194,60 @@ in
194194[User {userName = "Alice", userEmail = Just "alice@gmail.com"},User {userName = "Bob", userEmail = Nothing},User {userName = "Carole", userEmail = Just "carole@hotmail.com"}]
195195-}
196196module Squeal.PostgreSQL
197− ( module Squeal.PostgreSQL.Binary
197+ ( module Squeal.PostgreSQL.Alias
198+ , module Squeal.PostgreSQL.Binary
198199 , module Squeal.PostgreSQL.Definition
199200 , module Squeal.PostgreSQL.Expression
201+ , module Squeal.PostgreSQL.Expression.Aggregate
202+ , module Squeal.PostgreSQL.Expression.Collection
203+ , module Squeal.PostgreSQL.Expression.Comparison
204+ , module Squeal.PostgreSQL.Expression.Json
205+ , module Squeal.PostgreSQL.Expression.Literal
206+ , module Squeal.PostgreSQL.Expression.Logic
207+ , module Squeal.PostgreSQL.Expression.Math
208+ , module Squeal.PostgreSQL.Expression.Null
209+ , module Squeal.PostgreSQL.Expression.Parameter
210+ , module Squeal.PostgreSQL.Expression.SetOf
211+ , module Squeal.PostgreSQL.Expression.Sort
212+ , module Squeal.PostgreSQL.Expression.Subquery
213+ , module Squeal.PostgreSQL.Expression.Text
214+ , module Squeal.PostgreSQL.Expression.TextSearch
215+ , module Squeal.PostgreSQL.Expression.Time
216+ , module Squeal.PostgreSQL.Expression.Type
217+ , module Squeal.PostgreSQL.Expression.Window
218+ , module Squeal.PostgreSQL.List
200219 , module Squeal.PostgreSQL.Manipulation
220+ , module Squeal.PostgreSQL.PG
201221 , module Squeal.PostgreSQL.PQ
202222 , module Squeal.PostgreSQL.Query
203223 , module Squeal.PostgreSQL.Schema
204224 , module Squeal.PostgreSQL.Transaction
205225 ) where
206226
227+import Squeal.PostgreSQL.Alias
207228import Squeal.PostgreSQL.Binary
208229import Squeal.PostgreSQL.Definition
209230import Squeal.PostgreSQL.Expression
231+import Squeal.PostgreSQL.Expression.Aggregate
232+import Squeal.PostgreSQL.Expression.Collection
233+import Squeal.PostgreSQL.Expression.Comparison
234+import Squeal.PostgreSQL.Expression.Json
235+import Squeal.PostgreSQL.Expression.Literal
236+import Squeal.PostgreSQL.Expression.Logic
237+import Squeal.PostgreSQL.Expression.Math
238+import Squeal.PostgreSQL.Expression.Null
239+import Squeal.PostgreSQL.Expression.Parameter
240+import Squeal.PostgreSQL.Expression.SetOf
241+import Squeal.PostgreSQL.Expression.Sort
242+import Squeal.PostgreSQL.Expression.Subquery
243+import Squeal.PostgreSQL.Expression.Text
244+import Squeal.PostgreSQL.Expression.TextSearch
245+import Squeal.PostgreSQL.Expression.Time
246+import Squeal.PostgreSQL.Expression.Type
247+import Squeal.PostgreSQL.Expression.Window
248+import Squeal.PostgreSQL.List
210249import Squeal.PostgreSQL.Manipulation
250+import Squeal.PostgreSQL.PG
211251import Squeal.PostgreSQL.PQ
212252import Squeal.PostgreSQL.Query
213253import Squeal.PostgreSQL.Schema
squeal-postgresql/src/Squeal/PostgreSQL/Alias.hsnew file+258
@@ -0,0 +1,258 @@
1+{-|
2+Module: Squeal.PostgreSQL.Alias
3+Description: Aliases
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+This module embeds Postgres's alias system in Haskell in
9+a typesafe fashion. Thanks to GHC's @OverloadedLabels@ extension,
10+Squeal can reference aliases by prepending with a @#@.
11+-}
12+
13+{-# LANGUAGE
14+ AllowAmbiguousTypes
15+ , ConstraintKinds
16+ , DeriveAnyClass
17+ , DeriveGeneric
18+ , FlexibleContexts
19+ , FlexibleInstances
20+ , FunctionalDependencies
21+ , GADTs
22+ , LambdaCase
23+ , OverloadedStrings
24+ , QuantifiedConstraints
25+ , RankNTypes
26+ , ScopedTypeVariables
27+ , StandaloneDeriving
28+ , TypeApplications
29+ , TypeFamilyDependencies
30+ , TypeInType
31+ , TypeOperators
32+ , UndecidableInstances
33+ , UndecidableSuperClasses
34+#-}
35+
36+module Squeal.PostgreSQL.Alias
37+ ( (:::)
38+ , Alias (..)
39+ , IsLabel (..)
40+ , Aliased (As)
41+ , Aliasable (as)
42+ , renderAliased
43+ , Has
44+ , HasUnique
45+ , HasAll
46+ , HasIn
47+ , QualifiedAlias (..)
48+ , IsQualified (..)
49+ , DefaultAliasable (..)
50+ -- * Grouping
51+ , Grouping (..)
52+ , GroupedBy
53+ ) where
54+
55+import Control.DeepSeq
56+import Data.ByteString (ByteString)
57+import Data.String (fromString)
58+import GHC.OverloadedLabels
59+import GHC.TypeLits
60+
61+import qualified Generics.SOP as SOP
62+import qualified GHC.Generics as GHC
63+
64+import Squeal.PostgreSQL.List
65+import Squeal.PostgreSQL.Render
66+
67+-- $setup
68+-- >>> import Squeal.PostgreSQL
69+
70+-- | The alias operator `:::` is like a promoted version of `As`,
71+-- a type level pair between an alias and some type.
72+type (:::) (alias :: Symbol) ty = '(alias,ty)
73+infixr 6 :::
74+
75+
76+-- | `Grouping` is an auxiliary namespace, created by
77+-- @GROUP BY@ clauses (`Squeal.PostgreSQL.Query.group`), and used
78+-- for typesafe aggregation
79+data Grouping
80+ = Ungrouped -- ^ no aggregation permitted
81+ | Grouped [(Symbol,Symbol)] -- ^ aggregation required for any column which is not grouped
82+
83+{- | A `GroupedBy` constraint indicates that a table qualified column is
84+a member of the auxiliary namespace created by @GROUP BY@ clauses and thus,
85+may be called in an output `Squeal.PostgreSQL.Expression.Expression` without aggregating.
86+-}
87+class (KnownSymbol table, KnownSymbol column)
88+ => GroupedBy table column bys where
89+instance {-# OVERLAPPING #-} (KnownSymbol table, KnownSymbol column)
90+ => GroupedBy table column ('(table,column) ': bys)
91+instance {-# OVERLAPPABLE #-}
92+ ( KnownSymbol table
93+ , KnownSymbol column
94+ , GroupedBy table column bys
95+ ) => GroupedBy table column (tabcol ': bys)
96+
97+-- | `Alias`es are proxies for a type level string or `Symbol`
98+-- and have an `IsLabel` instance so that with @-XOverloadedLabels@
99+--
100+-- >>> :set -XOverloadedLabels
101+-- >>> #foobar :: Alias "foobar"
102+-- Alias
103+data Alias (alias :: Symbol) = Alias
104+ deriving (Eq,GHC.Generic,Ord,Show,NFData)
105+instance alias1 ~ alias2 => IsLabel alias1 (Alias alias2) where
106+ fromLabel = Alias
107+instance aliases ~ '[alias] => IsLabel alias (NP Alias aliases) where
108+ fromLabel = fromLabel SOP.:* Nil
109+-- | >>> printSQL (#jimbob :: Alias "jimbob")
110+-- "jimbob"
111+instance KnownSymbol alias => RenderSQL (Alias alias) where
112+ renderSQL = doubleQuoted . fromString . symbolVal
113+
114+-- >>> printSQL (#jimbob :* #kandi :: NP Alias '["jimbob", "kandi"])
115+-- "jimbob", "kandi"
116+instance SOP.All KnownSymbol aliases => RenderSQL (NP Alias aliases) where
117+ renderSQL
118+ = commaSeparated
119+ . SOP.hcollapse
120+ . SOP.hcmap (SOP.Proxy @KnownSymbol) (SOP.K . renderSQL)
121+
122+-- | The `As` operator is used to name an expression. `As` is like a demoted
123+-- version of `:::`.
124+--
125+-- >>> Just "hello" `As` #hi :: Aliased Maybe ("hi" ::: String)
126+-- As (Just "hello") Alias
127+data Aliased expression aliased where
128+ As
129+ :: KnownSymbol alias
130+ => expression ty
131+ -> Alias alias
132+ -> Aliased expression (alias ::: ty)
133+deriving instance Show (expression ty)
134+ => Show (Aliased expression (alias ::: ty))
135+deriving instance Eq (expression ty)
136+ => Eq (Aliased expression (alias ::: ty))
137+deriving instance Ord (expression ty)
138+ => Ord (Aliased expression (alias ::: ty))
139+instance (alias0 ~ alias1, alias0 ~ alias2, KnownSymbol alias2)
140+ => IsLabel alias0 (Aliased Alias (alias1 ::: alias2)) where
141+ fromLabel = fromLabel @alias2 `As` fromLabel @alias1
142+
143+-- | The `Aliasable` class provides a way to scrap your `Nil`s
144+-- in an `NP` list of `Aliased` expressions.
145+class KnownSymbol alias => Aliasable alias expression aliased
146+ | aliased -> expression
147+ , aliased -> alias
148+ where as :: expression -> Alias alias -> aliased
149+instance (KnownSymbol alias, aliased ~ (alias ::: ty)) => Aliasable alias
150+ (expression ty)
151+ (Aliased expression aliased)
152+ where
153+ as = As
154+instance (KnownSymbol alias, tys ~ '[alias ::: ty]) => Aliasable alias
155+ (expression ty)
156+ (NP (Aliased expression) tys)
157+ where
158+ expression `as` alias = expression `As` alias SOP.:* Nil
159+
160+-- | >>> let renderMaybe = fromString . maybe "Nothing" (const "Just")
161+-- >>> renderAliased renderMaybe (Just (3::Int) `As` #an_int)
162+-- "Just AS \"an_int\""
163+renderAliased
164+ :: (forall ty. expression ty -> ByteString)
165+ -> Aliased expression aliased
166+ -> ByteString
167+renderAliased render (expression `As` alias) =
168+ render expression <> " AS " <> renderSQL alias
169+
170+-- | @HasUnique alias fields field@ is a constraint that proves that
171+-- @fields@ is a singleton of @alias ::: field@.
172+type HasUnique alias fields field = fields ~ '[alias ::: field]
173+
174+-- | @Has alias fields field@ is a constraint that proves that
175+-- @fields@ has a field of @alias ::: field@, inferring @field@
176+-- from @alias@ and @fields@.
177+class KnownSymbol alias =>
178+ Has (alias :: Symbol) (fields :: [(Symbol,kind)]) (field :: kind)
179+ | alias fields -> field where
180+instance {-# OVERLAPPING #-} KnownSymbol alias
181+ => Has alias (alias ::: field ': fields) field
182+instance {-# OVERLAPPABLE #-} (KnownSymbol alias, Has alias fields field)
183+ => Has alias (field' ': fields) field
184+
185+{-| @HasIn fields (alias ::: field)@ is a constraint that proves that
186+@fields@ has a field of @alias ::: field@. It is used in @UPDATE@s to
187+choose which subfields to update.
188+-}
189+class HasIn fields field where
190+instance (Has alias fields field) => HasIn fields (alias ::: field) where
191+
192+{- |
193+The `DefaultAliasable` class is intended to help with Scrap your Nils
194+for default inserts and updates.
195+-}
196+class KnownSymbol alias
197+ => DefaultAliasable alias aliased | aliased -> alias where
198+ defaultAs :: Alias alias -> aliased
199+
200+-- | `HasAll` extends `Has` to take lists of @aliases@ and @fields@ and infer
201+-- a list of @subfields@.
202+class
203+ ( SOP.All KnownSymbol aliases
204+ ) => HasAll
205+ (aliases :: [Symbol])
206+ (fields :: [(Symbol,kind)])
207+ (subfields :: [(Symbol,kind)])
208+ | aliases fields -> subfields where
209+instance {-# OVERLAPPING #-} HasAll '[] fields '[]
210+instance {-# OVERLAPPABLE #-}
211+ (Has alias fields field, HasAll aliases fields subfields)
212+ => HasAll (alias ': aliases) fields (alias ::: field ': subfields)
213+
214+-- | Analagous to `IsLabel`, the constraint
215+-- `IsQualified` defines `!` for a column alias qualified
216+-- by a table alias.
217+class IsQualified table column expression where
218+ (!) :: Alias table -> Alias column -> expression
219+ infixl 9 !
220+instance IsQualified table column (Alias table, Alias column) where (!) = (,)
221+
222+{-| `QualifiedAlias`es enables multi-schema support by allowing a reference
223+to a `Squeal.PostgreSQL.Schema.Table`, `Squeal.PostgreSQL.Schema.Typedef`
224+or `Squeal.PostgreSQL.Schema.View` to be qualified by their schemas. By default,
225+a qualifier of @public@ is provided.
226+
227+>>> :{
228+let
229+ alias1 :: QualifiedAlias "sch" "tab"
230+ alias1 = #sch ! #tab
231+ alias2 :: QualifiedAlias "public" "vw"
232+ alias2 = #vw
233+in printSQL alias1 >> printSQL alias2
234+:}
235+"sch"."tab"
236+"vw"
237+-}
238+data QualifiedAlias (qualifier :: Symbol) (alias :: Symbol) = QualifiedAlias
239+ deriving (Eq,GHC.Generic,Ord,Show,NFData)
240+instance (q ~ q', a ~ a') => IsQualified q a (QualifiedAlias q' a') where
241+ _!_ = QualifiedAlias
242+instance (q' ~ "public", a ~ a') => IsLabel a (QualifiedAlias q' a') where
243+ fromLabel = QualifiedAlias
244+instance (q0 ~ q1, a0 ~ a1, a1 ~ a2, KnownSymbol a2) =>
245+ IsQualified q0 a0 (Aliased (QualifiedAlias q1) (a1 ::: a2)) where
246+ _!_ = QualifiedAlias `As` Alias
247+instance (q ~ "public", a0 ~ a1, a1 ~ a2, KnownSymbol a2) =>
248+ IsLabel a0 (Aliased (QualifiedAlias q) (a1 ::: a2)) where
249+ fromLabel = QualifiedAlias `As` Alias
250+
251+instance (KnownSymbol q, KnownSymbol a)
252+ => RenderSQL (QualifiedAlias q a) where
253+ renderSQL _ =
254+ let
255+ qualifier = renderSQL (Alias @q)
256+ alias = renderSQL (Alias @a)
257+ in
258+ if qualifier == "\"public\"" then alias else qualifier <> "." <> alias
squeal-postgresql/src/Squeal/PostgreSQL/Binary.hs+24−258
@@ -202,41 +202,28 @@ True
202202#-}
203203
204204module Squeal.PostgreSQL.Binary
205− ( -- * PG type families
206− PG
207− , NullPG
208− , TuplePG
209− , RowPG
210− , LabelsPG
211− -- * Storage newtypes
212− , Money (..)
213− , Json (..)
214− , Jsonb (..)
215− , Composite (..)
216− , Enumerated (..)
217− , VarArray (..)
218− , FixArray (..)
219− -- * Encoding
220− , ToParam (..)
205+ ( -- * Encoding
206+ ToParam (..)
221207 , ToParams (..)
208+ , ToNullityParam (..)
209+ , ToField (..)
210+ , ToFixArray (..)
222211 -- * Decoding
223212 , FromValue (..)
224213 , FromRow (..)
214+ , FromField (..)
215+ , FromFixArray (..)
225216 -- * Only
226217 , Only (..)
227− , P (..)
228218 ) where
229219
230220import BinaryParser
231221import ByteString.StrictBuilder (builderLength, int32BE, int64BE, word32BE)
232222import Control.Arrow (left)
233223import Control.Monad
234−import Data.Aeson (Value)
235−import Data.ByteString (ByteString)
236224import Data.Int
237225import Data.Kind
238226import Data.Scientific
239−import Data.Text (Text)
240227import Data.Time
241228import Data.UUID.Types
242229import Data.Vector (Vector)
@@ -254,11 +241,13 @@ import qualified Data.Text.Lazy as Lazy (Text)
254241import qualified Data.Text as Strict (Text)
255242import qualified Data.Text as Strict.Text
256243import qualified Data.Vector as Vector
257−import qualified Generics.SOP.Type.Metadata as Type
258244import qualified GHC.Generics as GHC
259245import qualified PostgreSQL.Binary.Decoding as Decoding
260246import qualified PostgreSQL.Binary.Encoding as Encoding
261247
248+import Squeal.PostgreSQL.Alias
249+import Squeal.PostgreSQL.List
250+import Squeal.PostgreSQL.PG
262251import Squeal.PostgreSQL.Schema
263252
264253-- | A `ToParam` constraint gives an encoding of a Haskell `Type` into
@@ -407,6 +396,8 @@ class HasAliasedOid (field :: (Symbol, NullityType)) where
407396instance HasOid ty => HasAliasedOid (alias ::: nullity ty) where
408397 aliasedOid = oid @ty
409398
399+-- | A `ToNullityParam` constraint gives an encoding of a Haskell `Type` into
400+-- into the binary format of a PostgreSQL `NullityType`.
410401class ToNullityParam (x :: Type) (ty :: NullityType) where
411402 toNullityParam :: x -> K (Maybe Encoding.Encoding) ty
412403instance ToParam x pg => ToNullityParam x ('NotNull pg) where
@@ -414,11 +405,19 @@ instance ToParam x pg => ToNullityParam x ('NotNull pg) where
414405instance ToParam x pg => ToNullityParam (Maybe x) ('Null pg) where
415406 toNullityParam = K . fmap (unK . toParam @x @pg)
416407
408+-- | A `ToField` constraint lifts the `ToParam` parser
409+-- to an encoding of a @(Symbol, Type)@ to a @(Symbol, NullityType)@,
410+-- encoding `Null`s to `Maybe`s. You should not define instances for
411+-- `FromField`, just use the provided instances.
417412class ToField (x :: (Symbol, Type)) (field :: (Symbol, NullityType)) where
418413 toField :: P x -> K (Maybe Encoding.Encoding) field
419414instance ToNullityParam x ty => ToField (alias ::: x) (alias ::: ty) where
420415 toField (P x) = K . unK $ toNullityParam @x @ty x
421416
417+-- | A `ToFixArray` constraint gives an encoding of a Haskell `Type`
418+-- into the binary format of a PostgreSQL fixed-length array.
419+-- You should not define instances for
420+-- `ToFixArray`, just use the provided instances.
422421class ToFixArray (x :: Type) (dims :: [Nat]) (array :: NullityType) where
423422 toFixArray :: x -> K (K Encoding.Array dims) array
424423instance ToNullityParam x ty => ToFixArray x '[] ty where
@@ -602,6 +601,10 @@ instance FromValue pg y
602601 K (Just bytestring) -> P . Just <$>
603602 Decoding.valueParser (fromValue @pg) bytestring
604603
604+-- | A `FromFixArray` constraint gives a decoding to a Haskell `Type`
605+-- from the binary format of a PostgreSQL fixed-length array.
606+-- You should not define instances for
607+-- `FromFixArray`, just use the provided instances.
605608class FromFixArray (dims :: [Nat]) (ty :: NullityType) (y :: Type) where
606609 fromFixArray :: Decoding.Array y
607610instance FromValue pg y => FromFixArray '[] ('NotNull pg) y where
@@ -717,240 +720,3 @@ replicateMN
717720 => m x -> m (NP I xs)
718721replicateMN mx = hsequence' $
719722 hcpure (Proxy :: Proxy ((~) x)) (Comp (I <$> mx))
720−
721−{- | The `Money` newtype stores a monetary value in terms
722−of the number of cents, i.e. @$2,000.20@ would be expressed as
723−@Money { cents = 200020 }@.
724−
725−>>> import Control.Monad (void)
726−>>> import Control.Monad.IO.Class (liftIO)
727−>>> import Squeal.PostgreSQL
728−>>> :{
729−let
730− roundTrip :: Query_ (Public '[]) (Only Money) (Only Money)
731− roundTrip = values_ $ parameter @1 money `as` #fromOnly
732−:}
733−
734−>>> let input = Only (Money 20020)
735−
736−>>> :{
737−void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
738− result <- runQueryParams roundTrip input
739− Just output <- firstRow result
740− liftIO . print $ input == output
741−:}
742−True
743−-}
744−newtype Money = Money { cents :: Int64 }
745− deriving (Eq, Ord, Show, Read, GHC.Generic)
746−type instance PG Money = 'PGmoney
747−
748−{- | The `Json` newtype is an indication that the Haskell
749−type it's applied to should be stored as a `PGjson`.
750−-}
751−newtype Json hask = Json {getJson :: hask}
752− deriving (Eq, Ord, Show, Read, GHC.Generic)
753−type instance PG (Json hask) = 'PGjson
754−
755−{- | The `Jsonb` newtype is an indication that the Haskell
756−type it's applied to should be stored as a `PGjsonb`.
757−-}
758−newtype Jsonb hask = Jsonb {getJsonb :: hask}
759− deriving (Eq, Ord, Show, Read, GHC.Generic)
760−type instance PG (Jsonb hask) = 'PGjsonb
761−
762−{- | The `Composite` newtype is an indication that the Haskell
763−type it's applied to should be stored as a `PGcomposite`.
764−-}
765−newtype Composite record = Composite {getComposite :: record}
766− deriving (Eq, Ord, Show, Read, GHC.Generic)
767−type instance PG (Composite hask) = 'PGcomposite (RowPG hask)
768−
769−{- | The `Enumerated` newtype is an indication that the Haskell
770−type it's applied to should be stored as a `PGenum`.
771−-}
772−newtype Enumerated enum = Enumerated {getEnumerated :: enum}
773− deriving (Eq, Ord, Show, Read, GHC.Generic)
774−type instance PG (Enumerated hask) = 'PGenum (LabelsPG hask)
775−
776−{- | The `VarArray` newtype is an indication that the Haskell
777−type it's applied to should be stored as a `PGvararray`.
778−-}
779−newtype VarArray arr = VarArray {getVarArray :: arr}
780− deriving (Eq, Ord, Show, Read, GHC.Generic)
781−type instance PG (VarArray (Vector hask)) = 'PGvararray (NullPG hask)
782−
783−{- | The `FixArray` newtype is an indication that the Haskell
784−type it's applied to should be stored as a `PGfixarray`.
785−-}
786−newtype FixArray arr = FixArray {getFixArray :: arr}
787− deriving (Eq, Ord, Show, Read, GHC.Generic)
788−type 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+8−1
@@ -95,12 +95,19 @@ import Prelude hiding ((.), id)
9595import qualified Generics.SOP as SOP
9696import qualified GHC.Generics as GHC
9797
98−import Squeal.PostgreSQL.Binary
98+import Squeal.PostgreSQL.Alias
9999import Squeal.PostgreSQL.Expression
100+import Squeal.PostgreSQL.Expression.Logic
101+import Squeal.PostgreSQL.Expression.Type
102+import Squeal.PostgreSQL.List
103+import Squeal.PostgreSQL.PG
100104import Squeal.PostgreSQL.Query
101105import Squeal.PostgreSQL.Render
102106import Squeal.PostgreSQL.Schema
103107
108+-- $setup
109+-- >>> import Squeal.PostgreSQL
110+
104111{-----------------------------------------
105112statements
106113-----------------------------------------}
squeal-postgresql/src/Squeal/PostgreSQL/Expression.hs+30−2223
@@ -1,14 +1,13 @@
11{-|
22Module: Squeal.PostgreSQL.Expression
33Description: Squeal expressions
4−Copyright: (c) Eitan Chatav, 2017
4+Copyright: (c) Eitan Chatav, 2019
55Maintainer: eitan@morphism.tech
66Stability: experimental
77
88Squeal expressions are the atoms used to build statements.
99-}
1010
11−{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
1211{-# LANGUAGE
1312 AllowAmbiguousTypes
1413 , ConstraintKinds
@@ -45,244 +44,35 @@ module Squeal.PostgreSQL.Expression
4544 , unsafeFunctionVar
4645 , FunctionN
4746 , unsafeFunctionN
48− , Literal (..)
49− , HasParameter (parameter)
50− , param
51− -- * Null
52− , null_
53− , notNull
54− , coalesce
55− , fromNull
56− , isNull
57− , isNotNull
58− , matchNull
59− , nullIf
60− -- * Collections
61− , array
62− , array1
63− , array2
64− , cardinality
65− , index
66− , row
67− , field
6847 , PGSubset (..)
69− -- * Numerical
70− , atan2_
71− , quot_
72− , rem_
73− , trunc
74− , round_
75− , ceiling_
76− -- * Logic
77− , Condition
78− , true
79− , false
80− , not_
81− , (.&&)
82− , (.||)
83− , caseWhenThenElse
84− , ifThenElse
85− -- * Comparison
86− , (.==)
87− , (./=)
88− , (.>=)
89− , (.<)
90− , (.<=)
91− , (.>)
92− , greatest
93− , least
94− , between
95− , notBetween
96− , betweenSymmetric
97− , notBetweenSymmetric
98− , isDistinctFrom
99− , isNotDistinctFrom
100− , isTrue
101− , isNotTrue
102− , isFalse
103− , isNotFalse
104− , isUnknown
105− , isNotUnknown
106− -- * Time
107− , TimeOp (..)
108− , currentDate
109− , currentTime
110− , currentTimestamp
111− , localTime
112− , localTimestamp
113− , now
114− , makeDate
115− , makeTime
116− , makeTimestamp
117− , makeTimestamptz
118− , interval_
119− , TimeUnit (..)
120− -- * Text
121− , lower
122− , upper
123− , charLength
124− , like
125− , ilike
126− -- * Json
127− -- ** Json and Jsonb operators
128− , (.->)
129− , (.->>)
130− , (.#>)
131− , (.#>>)
132− -- ** Jsonb operators
133− , (.?)
134− , (.?|)
135− , (.?&)
136− , (.-.)
137− , (#-.)
138− -- ** Functions
139− , toJson
140− , toJsonb
141− , arrayToJson
142− , rowToJson
143− , jsonBuildArray
144− , jsonbBuildArray
145− , PGBuildObject
146− , jsonBuildObject
147− , jsonbBuildObject
148− , jsonObject
149− , jsonbObject
150− , jsonZipObject
151− , jsonbZipObject
152− , jsonArrayLength
153− , jsonbArrayLength
154− , jsonExtractPath
155− , jsonbExtractPath
156− , jsonExtractPathAsText
157− , jsonbExtractPathAsText
158− , jsonTypeof
159− , jsonbTypeof
160− , jsonStripNulls
161− , jsonbStripNulls
162− , jsonbSet
163− , jsonbInsert
164− , jsonbPretty
165− -- * Text Search
166− , (@@)
167− , (.&)
168− , (.|)
169− , (.!)
170− , (<->)
171− , arrayToTSvector
172− , tsvectorLength
173− , numnode
174− , plainToTSquery
175− , phraseToTSquery
176− , websearchToTSquery
177− , queryTree
178− , toTSquery
179− , toTSvector
180− , setWeight
181− , strip
182− , jsonToTSvector
183− , jsonbToTSvector
184− , tsDelete
185− , tsFilter
186− , tsHeadline
187− -- * Aggregation
188− , Aggregate (..)
189− , Distinction (..)
190− , PGAvg
191− -- * Window Functions
192− , WindowDefinition (..)
193− , partitionBy
194− , WindowFunction (..)
195− , rank
196− , rowNumber
197− , denseRank
198− , percentRank
199− , cumeDist
200− , ntile
201− , lag
202− , lead
203− , firstValue
204− , lastValue
205− , nthValue
206− -- * Sorting
207− , SortExpression (..)
208− , OrderBy (..)
209− -- * Types
210− , TypeExpression (..)
211− , cast
212− , PGTyped (..)
213− , typedef
214− , typetable
215− , typeview
216− , bool
217− , int2
218− , smallint
219− , int4
220− , int
221− , integer
222− , int8
223− , bigint
224− , numeric
225− , float4
226− , real
227− , float8
228− , doublePrecision
229− , money
230− , text
231− , char
232− , character
233− , varchar
234− , characterVarying
235− , bytea
236− , timestamp
237− , timestampWithTimeZone
238− , date
239− , time
240− , timeWithTimeZone
241− , interval
242− , uuid
243− , inet
244− , json
245− , jsonb
246− , vararray
247− , fixarray
248− , tsvector
249− , tsquery
25048 -- * Re-export
25149 , (&)
252− , NP (..)
25350 , K (..)
51+ , unK
25452 ) where
25553
25654import Control.Category
25755import Control.DeepSeq
25856import Data.ByteString (ByteString)
259−import Data.ByteString.Lazy (toStrict)
260−import ByteString.StrictBuilder (builderBytes)
26157import Data.Function ((&))
262−import Data.Kind
26358import Data.Semigroup hiding (All)
264−import qualified Data.Aeson as JSON
265−import Data.Int
26659import Data.String
267−import Data.Text (Text)
268−import Data.Word
26960import Generics.SOP hiding (All, from)
27061import GHC.OverloadedLabels
27162import GHC.TypeLits
27263import Numeric
27364import Prelude hiding (id, (.))
27465
275−import qualified Data.ByteString as ByteString
276−import qualified Data.Text as Text
277−import qualified Data.Text.Lazy as Lazy (Text)
278−import qualified Data.Text.Lazy as Lazy.Text
27966import qualified GHC.Generics as GHC
280−import qualified Generics.SOP as SOP
28167
282−import Squeal.PostgreSQL.Binary
68+import Squeal.PostgreSQL.Alias
69+import Squeal.PostgreSQL.List
28370import Squeal.PostgreSQL.Render
28471import Squeal.PostgreSQL.Schema
28572
73+-- $setup
74+-- >>> import Squeal.PostgreSQL
75+
28676{-----------------------------------------
28777column expressions
28878-----------------------------------------}
@@ -292,7 +82,7 @@ such as in the target `Squeal.PostgreSQL.Query.List` of the
29282`Squeal.PostgreSQL.Query.select` command,
29383as new column values in `Squeal.PostgreSQL.Manipulation.insertRow` or
29484`Squeal.PostgreSQL.Manipulation.update`,
295−or in search `Condition`s in a number of commands.
85+or in search `Squeal.PostgreSQL.Logic.Condition`s in a number of commands.
29686
29787The expression syntax allows the calculation of
29888values from primitive expression using arithmetic, logical,
@@ -326,7 +116,7 @@ instance RenderSQL (Expression outer commons grp schemas params from ty) where
326116 renderSQL = renderExpression
327117
328118-- | An `Expr` is a closed `Expression`.
329−-- It is a F`RankNType` but don't be scared.
119+-- It is a F@RankNType@ but don't be scared.
330120-- Think of it as an expression which sees no
331121-- namespaces, so you can't use parameters
332122-- or alias references. It can be used as
@@ -336,7 +126,7 @@ type Expr x
336126 . Expression outer commons grp schemas params from x
337127 -- ^ cannot reference aliases
338128
339−-- | A `RankNType` for binary operators.
129+-- | A @RankNType@ for binary operators.
340130type Operator x1 x2 y
341131 = forall outer commons grp schemas params from
342132 . Expression outer commons grp schemas params from x1
@@ -346,7 +136,7 @@ type Operator x1 x2 y
346136 -> Expression outer commons grp schemas params from y
347137 -- ^ output
348138
349−-- | A `RankNType` for functions with a single argument.
139+-- | A @RankNType@ for functions with a single argument.
350140-- These could be either function calls or unary operators.
351141-- This is a subtype of the usual Haskell function type `Prelude.->`,
352142-- indeed a subcategory as it is closed under the usual
@@ -358,7 +148,7 @@ type (:-->) x y
358148 -> Expression outer commons grp schemas params from y
359149 -- ^ output
360150
361−{- | A `RankNType` for functions with a fixed-length list of heterogeneous arguments.
151+{- | A @RankNType@ for functions with a fixed-length list of heterogeneous arguments.
362152Use the `*:` operator to end your argument lists, like so.
363153
364154>>> printSQL (unsafeFunctionN "fun" (true :* false :* localTime *: true))
@@ -371,7 +161,7 @@ type FunctionN xs y
371161 -> Expression outer commons grp schemas params from y
372162 -- ^ output
373163
374−{- | A `RankNType` for functions with a variable-length list of
164+{- | A @RankNType@ for functions with a variable-length list of
375165homogeneous arguments and at least 1 more argument.
376166-}
377167type FunctionVar x0 x1 y
@@ -390,46 +180,6 @@ unsafeFunctionVar :: ByteString -> FunctionVar x0 x1 y
390180unsafeFunctionVar fun xs x = UnsafeExpression $ fun <> parenthesized
391181 (commaSeparated (renderSQL <$> xs) <> ", " <> renderSQL x)
392182
393−{- | A `HasParameter` constraint is used to indicate a value that is
394−supplied externally to a SQL statement.
395−`Squeal.PostgreSQL.PQ.manipulateParams`,
396−`Squeal.PostgreSQL.PQ.queryParams` and
397−`Squeal.PostgreSQL.PQ.traversePrepared` support specifying data values
398−separately from the SQL command string, in which case `param`s are used to
399−refer to the out-of-line data values.
400−-}
401−class KnownNat n => HasParameter
402− (n :: Nat)
403− (params :: [NullityType])
404− (ty :: NullityType)
405− | n params -> ty where
406− -- | `parameter` takes a `Nat` using type application and a `TypeExpression`.
407− --
408− -- >>> let expr = parameter @1 int4 :: Expression outer '[] grp schemas '[ 'Null 'PGint4] from ('Null 'PGint4)
409− -- >>> printSQL expr
410− -- ($1 :: int4)
411− parameter
412− :: TypeExpression schemas ty
413− -> Expression outer commons grp schemas params from ty
414− parameter ty = UnsafeExpression $ parenthesized $
415− "$" <> renderNat @n <+> "::"
416− <+> renderSQL ty
417−instance {-# OVERLAPPING #-} HasParameter 1 (ty1:tys) ty1
418−instance {-# OVERLAPPABLE #-} (KnownNat n, HasParameter (n-1) params ty)
419− => HasParameter n (ty' : params) ty
420−
421−-- | `param` takes a `Nat` using type application and for basic types,
422−-- infers a `TypeExpression`.
423−--
424−-- >>> let expr = param @1 :: Expression outer commons grp schemas '[ 'Null 'PGint4] from ('Null 'PGint4)
425−-- >>> printSQL expr
426−-- ($1 :: int4)
427−param
428− :: forall n outer commons schemas params from grp ty
429− . (PGTyped schemas ty, HasParameter n params ty)
430− => Expression outer commons grp schemas params from ty -- ^ param
431−param = parameter @n (pgtype @schemas)
432−
433183instance (HasUnique tab (Join outer from) row, Has col row ty)
434184 => IsLabel col (Expression outer commons 'Ungrouped schemas params from ty) where
435185 fromLabel = UnsafeExpression $ renderSQL (Alias @col)
@@ -526,214 +276,10 @@ instance
526276 (NP (Aliased (Expression outer commons ('Grouped bys) schemas params from)) columns) where
527277 tab ! col = tab ! col :* Nil
528278
529−-- | analagous to `Nothing`
530−--
531−-- >>> printSQL null_
532−-- NULL
533−null_ :: Expr ('Null ty)
534−null_ = UnsafeExpression "NULL"
535−
536−-- | analagous to `Just`
537−--
538−-- >>> printSQL $ notNull true
539−-- TRUE
540−notNull :: 'NotNull ty :--> 'Null ty
541−notNull = UnsafeExpression . renderSQL
542−
543−-- | return the leftmost value which is not NULL
544−--
545−-- >>> printSQL $ coalesce [null_, true] false
546−-- COALESCE(NULL, TRUE, FALSE)
547−coalesce :: FunctionVar ('Null ty) ('NotNull ty) ('NotNull ty)
548−coalesce nullxs notNullx = UnsafeExpression $
549− "COALESCE" <> parenthesized (commaSeparated
550− ((renderSQL <$> nullxs) <> [renderSQL notNullx]))
551−
552−-- | analagous to `Data.Maybe.fromMaybe` using @COALESCE@
553−--
554−-- >>> printSQL $ fromNull true null_
555−-- COALESCE(NULL, TRUE)
556−fromNull
557− :: Expression outer commons grp schemas params from ('NotNull ty)
558− -- ^ what to convert @NULL@ to
559− -> Expression outer commons grp schemas params from ('Null ty)
560− -> Expression outer commons grp schemas params from ('NotNull ty)
561−fromNull notNullx nullx = coalesce [nullx] notNullx
562−
563−-- | >>> printSQL $ null_ & isNull
564−-- NULL IS NULL
565−isNull :: 'Null ty :--> null 'PGbool
566−isNull x = UnsafeExpression $ renderSQL x <+> "IS NULL"
567−
568−-- | >>> printSQL $ null_ & isNotNull
569−-- NULL IS NOT NULL
570−isNotNull :: 'Null ty :--> null 'PGbool
571−isNotNull x = UnsafeExpression $ renderSQL x <+> "IS NOT NULL"
572−
573−-- | analagous to `maybe` using @IS NULL@
574−--
575−-- >>> printSQL $ matchNull true not_ null_
576−-- CASE WHEN NULL IS NULL THEN TRUE ELSE (NOT NULL) END
577−matchNull
578− :: Expression outer commons grp schemas params from (nullty)
579− -- ^ what to convert @NULL@ to
580− -> ( Expression outer commons grp schemas params from ('NotNull ty)
581− -> Expression outer commons grp schemas params from (nullty) )
582− -- ^ function to perform when @NULL@ is absent
583− -> Expression outer commons grp schemas params from ('Null ty)
584− -> Expression outer commons grp schemas params from (nullty)
585−matchNull y f x = ifThenElse (isNull x) y
586− (f (UnsafeExpression (renderSQL x)))
587−
588−{-| right inverse to `fromNull`, if its arguments are equal then
589−`nullIf` gives @NULL@.
590−
591−>>> :set -XTypeApplications -XDataKinds
592−>>> let expr = nullIf (false *: param @1) :: Expression outer commons grp schemas '[ 'NotNull 'PGbool] from ('Null 'PGbool)
593−>>> printSQL expr
594−NULLIF(FALSE, ($1 :: bool))
595−-}
596−nullIf :: FunctionN '[ 'NotNull ty, 'NotNull ty] ('Null ty)
597−nullIf = unsafeFunctionN "NULLIF"
598−
599−-- | >>> printSQL $ array [null_, false, true]
600−-- ARRAY[NULL, FALSE, TRUE]
601−array
602− :: [Expression outer commons grp schemas params from ty]
603− -- ^ array elements
604− -> Expression outer commons grp schemas params from (null ('PGvararray ty))
605−array xs = UnsafeExpression $ "ARRAY" <>
606− bracketed (commaSeparated (renderSQL <$> xs))
607−
608−{- | construct a 1-dimensional fixed length array
609−
610−>>> printSQL $ array1 (null_ :* false *: true)
611−ARRAY[NULL, FALSE, TRUE]
612−
613−>>> :type array1 (null_ :* false *: true)
614−array1 (null_ :* false *: true)
615− :: Expression
616− outer
617− commons
618− grp
619− schemas
620− params
621− from
622− (null ('PGfixarray '[3] ('Null 'PGbool)))
623−-}
624−array1
625− :: (n ~ Length tys, SOP.All ((~) ty) tys)
626− => NP (Expression outer commons grp schemas params from) tys
627− -> Expression outer commons grp schemas params from (null ('PGfixarray '[n] ty))
628−array1 xs = UnsafeExpression $ "ARRAY" <>
629− bracketed (renderCommaSeparated renderSQL xs)
630−
631−{- | construct a 2-dimensional fixed length array
632−
633−>>> printSQL $ array2 ((null_ :* false *: true) *: (false :* null_ *: true))
634−ARRAY[[NULL, FALSE, TRUE], [FALSE, NULL, TRUE]]
635−
636−>>> :type array2 ((null_ :* false *: true) *: (false :* null_ *: true))
637−array2 ((null_ :* false *: true) *: (false :* null_ *: true))
638− :: Expression
639− outer
640− commons
641− grp
642− schemas
643− params
644− from
645− (null ('PGfixarray '[2, 3] ('Null 'PGbool)))
646−-}
647−array2
648− :: ( SOP.All ((~) tys) tyss
649− , SOP.All SOP.SListI tyss
650− , Length tyss ~ n1
651− , SOP.All ((~) ty) tys
652− , Length tys ~ n2 )
653− => NP (NP (Expression outer commons grp schemas params from)) tyss
654− -> Expression outer commons grp schemas params from (null ('PGfixarray '[n1,n2] ty))
655−array2 xss = UnsafeExpression $ "ARRAY" <>
656− bracketed (renderCommaSeparatedConstraint @SOP.SListI (bracketed . renderCommaSeparated renderSQL) xss)
657−
658−-- | >>> printSQL $ cardinality (array [null_, false, true])
659−-- cardinality(ARRAY[NULL, FALSE, TRUE])
660−cardinality :: null ('PGvararray ty) :--> null 'PGint8
661−cardinality = unsafeFunction "cardinality"
662−
663−-- | >>> printSQL $ array [null_, false, true] & index 2
664−-- (ARRAY[NULL, FALSE, TRUE])[2]
665−index
666− :: Word64 -- ^ index
667− -> null ('PGvararray ty) :--> NullifyType ty
668−index n expr = UnsafeExpression $
669− parenthesized (renderSQL expr) <> "[" <> fromString (show n) <> "]"
670−
671279instance (KnownSymbol label, label `In` labels) => IsPGlabel label
672280 (Expression outer commons grp schemas params from (null ('PGenum labels))) where
673281 label = UnsafeExpression $ renderSQL (PGlabel @label)
674282
675−-- | A row constructor is an expression that builds a row value
676−-- (also called a composite value) using values for its member fields.
677−--
678−-- >>> :{
679−-- type Complex = 'PGcomposite
680−-- '[ "real" ::: 'NotNull 'PGfloat8
681−-- , "imaginary" ::: 'NotNull 'PGfloat8 ]
682−-- :}
683−--
684−-- >>> let i = row (0 `as` #real :* 1 `as` #imaginary) :: Expression outer commons grp schemas params from ('NotNull Complex)
685−-- >>> printSQL i
686−-- ROW(0, 1)
687−row
688− :: SListI row
689− => NP (Aliased (Expression outer commons grp schemas params from)) row
690− -- ^ zero or more expressions for the row field values
691− -> Expression outer commons grp schemas params from (null ('PGcomposite row))
692−row exprs = UnsafeExpression $ "ROW" <> parenthesized
693− (renderCommaSeparated (\ (expr `As` _) -> renderSQL expr) exprs)
694−
695−-- | >>> :{
696−-- type Complex = 'PGcomposite
697−-- '[ "real" ::: 'NotNull 'PGfloat8
698−-- , "imaginary" ::: 'NotNull 'PGfloat8 ]
699−-- type Schema = '["complex" ::: 'Typedef Complex]
700−-- :}
701−--
702−-- >>> let i = row (0 `as` #real :* 1 `as` #imaginary) :: Expression outer '[] grp (Public Schema) from params ('NotNull Complex)
703−-- >>> printSQL $ i & field #complex #imaginary
704−-- (ROW(0, 1)::"complex")."imaginary"
705−field
706− :: ( Has sch schemas schema
707− , Has tydef schema ('Typedef ('PGcomposite row))
708− , Has field row ty)
709− => QualifiedAlias sch tydef -- ^ row type
710− -> Alias field -- ^ field name
711− -> Expression outer commons grp schemas params from ('NotNull ('PGcomposite row))
712− -> Expression outer commons grp schemas params from ty
713−field td fld expr = UnsafeExpression $
714− parenthesized (renderSQL expr <> "::" <> renderSQL td)
715− <> "." <> renderSQL fld
716−
717−instance Semigroup
718− (Expression outer commons grp schemas params from (null ('PGvararray ty))) where
719− (<>) = unsafeBinaryOp "||"
720−
721−instance Monoid
722− (Expression outer commons grp schemas params from (null ('PGvararray ty))) where
723− mempty = array []
724− mappend = (<>)
725−
726−-- | >>> let expr = greatest [param @1] currentTimestamp :: Expression outer commons grp schemas '[ 'NotNull 'PGtimestamptz] from ('NotNull 'PGtimestamptz)
727−-- >>> printSQL expr
728−-- GREATEST(($1 :: timestamp with time zone), CURRENT_TIMESTAMP)
729−greatest :: FunctionVar ty ty ty
730−greatest = unsafeFunctionVar "GREATEST"
731−
732−-- | >>> printSQL $ least [null_] currentTimestamp
733−-- LEAST(NULL, CURRENT_TIMESTAMP)
734−least :: FunctionVar ty ty ty
735−least = unsafeFunctionVar "LEAST"
736−
737283-- | >>> printSQL $ unsafeBinaryOp "OR" true false
738284-- (TRUE OR FALSE)
739285unsafeBinaryOp :: ByteString -> Operator ty0 ty1 ty2
@@ -806,364 +352,15 @@ instance (ty `In` PGNum, ty `In` PGFloating) => Floating
806352 acosh x = log (x + sqrt (x*x - 1))
807353 atanh x = log ((1 + x) / (1 - x)) / 2
808354
809−-- | >>> :{
810−-- let
811−-- expression :: Expr (null 'PGfloat4)
812−-- expression = atan2_ (pi *: 2)
813−-- in printSQL expression
814−-- :}
815−-- atan2(pi(), 2)
816−atan2_
817− :: float `In` PGFloating
818− => FunctionN '[ null float, null float] (null float)
819−atan2_ = unsafeFunctionN "atan2"
820−
821−-- When a `cast` is applied to an `Expression` of a known type, it
822−-- represents a run-time type conversion. The cast will succeed only if a
823−-- suitable type conversion operation has been defined.
824−--
825−-- | >>> printSQL $ true & cast int4
826−-- (TRUE :: int4)
827−cast
828− :: TypeExpression schemas ty1
829− -- ^ type to cast as
830− -> Expression outer commons grp schemas params from ty0
831− -- ^ value to convert
832− -> Expression outer commons grp schemas params from ty1
833−cast ty x = UnsafeExpression $ parenthesized $
834− renderSQL x <+> "::" <+> renderSQL ty
835−
836−-- | integer division, truncates the result
837−--
838−-- >>> :{
839−-- let
840−-- expression :: Expression outer commons grp schemas params from (null 'PGint2)
841−-- expression = 5 `quot_` 2
842−-- in printSQL expression
843−-- :}
844−-- (5 / 2)
845−quot_
846− :: int `In` PGIntegral
847− => Operator (null int) (null int) (null int)
848−quot_ = unsafeBinaryOp "/"
849−
850−-- | remainder upon integer division
851−--
852−-- >>> :{
853−-- let
854−-- expression :: Expression outer commons grp schemas params from (null 'PGint2)
855−-- expression = 5 `rem_` 2
856−-- in printSQL expression
857−-- :}
858−-- (5 % 2)
859−rem_
860− :: int `In` PGIntegral
861− => Operator (null int) (null int) (null int)
862−rem_ = unsafeBinaryOp "%"
863−
864−-- | >>> :{
865−-- let
866−-- expression :: Expression outer commons grp schemas params from (null 'PGfloat4)
867−-- expression = trunc pi
868−-- in printSQL expression
869−-- :}
870−-- trunc(pi())
871−trunc :: frac `In` PGFloating => null frac :--> null frac
872−trunc = unsafeFunction "trunc"
873−
874−-- | >>> :{
875−-- let
876−-- expression :: Expression outer commons grp schemas params from (null 'PGfloat4)
877−-- expression = round_ pi
878−-- in printSQL expression
879−-- :}
880−-- round(pi())
881−round_ :: frac `In` PGFloating => null frac :--> null frac
882−round_ = unsafeFunction "round"
883−
884−-- | >>> :{
885−-- let
886−-- expression :: Expression outer commons grp schemas params from (null 'PGfloat4)
887−-- expression = ceiling_ pi
888−-- in printSQL expression
889−-- :}
890−-- ceiling(pi())
891−ceiling_ :: frac `In` PGFloating => null frac :--> null frac
892−ceiling_ = unsafeFunction "ceiling"
893−
894−-- | A `Condition` is an `Expression`, which can evaluate
895−-- to `true`, `false` or `null_`. This is because SQL uses
896−-- a three valued logic.
897−type Condition outer commons grp schemas params from =
898− Expression outer commons grp schemas params from ('Null 'PGbool)
899−
900−-- | >>> printSQL true
901−-- TRUE
902−true :: Expr (null 'PGbool)
903−true = UnsafeExpression "TRUE"
904−
905−-- | >>> printSQL false
906−-- FALSE
907−false :: Expr (null 'PGbool)
908−false = UnsafeExpression "FALSE"
909−
910−-- | >>> printSQL $ not_ true
911−-- (NOT TRUE)
912−not_ :: null 'PGbool :--> null 'PGbool
913−not_ = unsafeUnaryOpL "NOT"
914−
915−-- | >>> printSQL $ true .&& false
916−-- (TRUE AND FALSE)
917−(.&&) :: Operator (null 'PGbool) (null 'PGbool) (null 'PGbool)
918−infixr 3 .&&
919−(.&&) = unsafeBinaryOp "AND"
920−
921−-- | >>> printSQL $ true .|| false
922−-- (TRUE OR FALSE)
923−(.||) :: Operator (null 'PGbool) (null 'PGbool) (null 'PGbool)
924−infixr 2 .||
925−(.||) = unsafeBinaryOp "OR"
926−
927−-- | >>> :{
928−-- let
929−-- expression :: Expression outer commons grp schemas params from (null 'PGint2)
930−-- expression = caseWhenThenElse [(true, 1), (false, 2)] 3
931−-- in printSQL expression
932−-- :}
933−-- CASE WHEN TRUE THEN 1 WHEN FALSE THEN 2 ELSE 3 END
934−caseWhenThenElse
935− :: [ ( Condition outer commons grp schemas params from
936− , Expression outer commons grp schemas params from ty
937− ) ]
938− -- ^ whens and thens
939− -> Expression outer commons grp schemas params from ty
940− -- ^ else
941− -> Expression outer commons grp schemas params from ty
942−caseWhenThenElse whenThens else_ = UnsafeExpression $ mconcat
943− [ "CASE"
944− , mconcat
945− [ mconcat
946− [ " WHEN ", renderSQL when_
947− , " THEN ", renderSQL then_
948− ]
949− | (when_,then_) <- whenThens
950− ]
951− , " ELSE ", renderSQL else_
952− , " END"
953− ]
954−
955−-- | >>> :{
956−-- let
957−-- expression :: Expression outer commons grp schemas params from (null 'PGint2)
958−-- expression = ifThenElse true 1 0
959−-- in printSQL expression
960−-- :}
961−-- CASE WHEN TRUE THEN 1 ELSE 0 END
962−ifThenElse
963− :: Condition outer commons grp schemas params from
964− -> Expression outer commons grp schemas params from ty -- ^ then
965− -> Expression outer commons grp schemas params from ty -- ^ else
966− -> Expression outer commons grp schemas params from ty
967−ifThenElse if_ then_ else_ = caseWhenThenElse [(if_,then_)] else_
968−
969−-- | Comparison operations like `.==`, `./=`, `.>`, `.>=`, `.<` and `.<=`
970−-- will produce @NULL@s if one of their arguments is @NULL@.
971−--
972−-- >>> printSQL $ true .== null_
973−-- (TRUE = NULL)
974−(.==) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
975−(.==) = unsafeBinaryOp "="
976−infix 4 .==
977−
978−-- | >>> printSQL $ true ./= null_
979−-- (TRUE <> NULL)
980−(./=) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
981−(./=) = unsafeBinaryOp "<>"
982−infix 4 ./=
983−
984−-- | >>> printSQL $ true .>= null_
985−-- (TRUE >= NULL)
986−(.>=) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
987−(.>=) = unsafeBinaryOp ">="
988−infix 4 .>=
989−
990−-- | >>> printSQL $ true .< null_
991−-- (TRUE < NULL)
992−(.<) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
993−(.<) = unsafeBinaryOp "<"
994−infix 4 .<
995−
996−-- | >>> printSQL $ true .<= null_
997−-- (TRUE <= NULL)
998−(.<=) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
999−(.<=) = unsafeBinaryOp "<="
1000−infix 4 .<=
1001−
1002−-- | >>> printSQL $ true .> null_
1003−-- (TRUE > NULL)
1004−(.>) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
1005−(.>) = unsafeBinaryOp ">"
1006−infix 4 .>
1007−
1008−{- | >>> printSQL $ true `between` (null_, false)
1009−TRUE BETWEEN NULL AND FALSE
1010−-}
1011−between
1012− :: Expression outer commons grp schemas params from (null ty)
1013− -> ( Expression outer commons grp schemas params from (null ty)
1014− , Expression outer commons grp schemas params from (null ty) ) -- ^ bounds
1015− -> Condition outer commons grp schemas params from
1016−between a (x,y) = UnsafeExpression $ renderSQL a <+> "BETWEEN"
1017− <+> renderSQL x <+> "AND" <+> renderSQL y
1018−
1019−{- | >>> printSQL $ true `notBetween` (null_, false)
1020−TRUE NOT BETWEEN NULL AND FALSE
1021−-}
1022−notBetween
1023− :: Expression outer commons grp schemas params from (null ty)
1024− -> ( Expression outer commons grp schemas params from (null ty)
1025− , Expression outer commons grp schemas params from (null ty) ) -- ^ bounds
1026− -> Condition outer commons grp schemas params from
1027−notBetween a (x,y) = UnsafeExpression $ renderSQL a <+> "NOT BETWEEN"
1028− <+> renderSQL x <+> "AND" <+> renderSQL y
1029−
1030−{- | between, after sorting the comparison values
1031−
1032−>>> printSQL $ true `betweenSymmetric` (null_, false)
1033−TRUE BETWEEN SYMMETRIC NULL AND FALSE
1034−-}
1035−betweenSymmetric
1036− :: Expression outer commons grp schemas params from (null ty)
1037− -> ( Expression outer commons grp schemas params from (null ty)
1038− , Expression outer commons grp schemas params from (null ty) ) -- ^ bounds
1039− -> Condition outer commons grp schemas params from
1040−betweenSymmetric a (x,y) = UnsafeExpression $ renderSQL a
1041− <+> "BETWEEN SYMMETRIC" <+> renderSQL x <+> "AND" <+> renderSQL y
1042−
1043−{- | not between, after sorting the comparison values
1044−
1045−>>> printSQL $ true `notBetweenSymmetric` (null_, false)
1046−TRUE NOT BETWEEN SYMMETRIC NULL AND FALSE
1047−-}
1048−notBetweenSymmetric
1049− :: Expression outer commons grp schemas params from (null ty)
1050− -> ( Expression outer commons grp schemas params from (null ty)
1051− , Expression outer commons grp schemas params from (null ty) ) -- ^ bounds
1052− -> Condition outer commons grp schemas params from
1053−notBetweenSymmetric a (x,y) = UnsafeExpression $ renderSQL a
1054− <+> "NOT BETWEEN SYMMETRIC" <+> renderSQL x <+> "AND" <+> renderSQL y
1055−
1056−{- | not equal, treating null like an ordinary value
1057−
1058−>>> printSQL $ true `isDistinctFrom` null_
1059−(TRUE IS DISTINCT FROM NULL)
1060−-}
1061−isDistinctFrom :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
1062−isDistinctFrom = unsafeBinaryOp "IS DISTINCT FROM"
1063−
1064−{- | equal, treating null like an ordinary value
1065−
1066−>>> printSQL $ true `isNotDistinctFrom` null_
1067−(TRUE IS NOT DISTINCT FROM NULL)
1068−-}
1069−isNotDistinctFrom :: Operator (null0 ty) (null1 ty) ('NotNull 'PGbool)
1070−isNotDistinctFrom = unsafeBinaryOp "IS NOT DISTINCT FROM"
1071−
1072−{- | is true
1073−
1074−>>> printSQL $ true & isTrue
1075−(TRUE IS TRUE)
1076−-}
1077−isTrue :: null0 'PGbool :--> null1 'PGbool
1078−isTrue = unsafeUnaryOpR "IS TRUE"
1079−
1080−{- | is false or unknown
1081−
1082−>>> printSQL $ true & isNotTrue
1083−(TRUE IS NOT TRUE)
1084−-}
1085−isNotTrue :: null0 'PGbool :--> null1 'PGbool
1086−isNotTrue = unsafeUnaryOpR "IS NOT TRUE"
1087−
1088−{- | is false
1089−
1090−>>> printSQL $ true & isFalse
1091−(TRUE IS FALSE)
1092−-}
1093−isFalse :: null0 'PGbool :--> null1 'PGbool
1094−isFalse = unsafeUnaryOpR "IS FALSE"
1095−
1096−{- | is true or unknown
1097−
1098−>>> printSQL $ true & isNotFalse
1099−(TRUE IS NOT FALSE)
1100−-}
1101−isNotFalse :: null0 'PGbool :--> null1 'PGbool
1102−isNotFalse = unsafeUnaryOpR "IS NOT FALSE"
1103−
1104−{- | is unknown
1105−
1106−>>> printSQL $ true & isUnknown
1107−(TRUE IS UNKNOWN)
1108−-}
1109−isUnknown :: null0 'PGbool :--> null1 'PGbool
1110−isUnknown = unsafeUnaryOpR "IS UNKNOWN"
1111−
1112−{- | is true or false
1113−
1114−>>> printSQL $ true & isNotUnknown
1115−(TRUE IS NOT UNKNOWN)
1116−-}
1117−isNotUnknown :: null0 'PGbool :--> null1 'PGbool
1118−isNotUnknown = unsafeUnaryOpR "IS NOT UNKNOWN"
1119−
1120−-- | >>> printSQL currentDate
1121−-- CURRENT_DATE
1122−currentDate :: Expr (null 'PGdate)
1123−currentDate = UnsafeExpression "CURRENT_DATE"
1124−
1125−-- | >>> printSQL currentTime
1126−-- CURRENT_TIME
1127−currentTime :: Expr (null 'PGtimetz)
1128−currentTime = UnsafeExpression "CURRENT_TIME"
1129−
1130−-- | >>> printSQL currentTimestamp
1131−-- CURRENT_TIMESTAMP
1132−currentTimestamp :: Expr (null 'PGtimestamptz)
1133−currentTimestamp = UnsafeExpression "CURRENT_TIMESTAMP"
1134−
1135−-- | >>> printSQL localTime
1136−-- LOCALTIME
1137−localTime :: Expr (null 'PGtime)
1138−localTime = UnsafeExpression "LOCALTIME"
1139−
1140−-- | >>> printSQL localTimestamp
1141−-- LOCALTIMESTAMP
1142−localTimestamp :: Expr (null 'PGtimestamp)
1143−localTimestamp = UnsafeExpression "LOCALTIMESTAMP"
1144−
1145−-- | Current date and time (equivalent to `currentTimestamp`)
1146−--
1147−-- >>> printSQL now
1148−-- now()
1149−now :: Expr (null 'PGtimestamptz)
1150−now = UnsafeExpression "now()"
1151−
1152−{-----------------------------------------
1153−text
1154−-----------------------------------------}
1155−
1156−escape :: Char -> String
1157−escape = \case
1158− '\NUL' -> "\\0"
1159− '\'' -> "''"
1160− '"' -> "\\\""
1161− '\b' -> "\\b"
1162− '\n' -> "\\n"
1163− '\r' -> "\\r"
1164− '\t' -> "\\t"
1165− '\\' -> "\\\\"
1166− c -> [c]
355+-- | Contained by operators
356+class PGSubset container where
357+ (@>) :: Operator (null0 container) (null1 container) ('Null 'PGbool)
358+ (@>) = unsafeBinaryOp "@>"
359+ (<@) :: Operator (null0 container) (null1 container) ('Null 'PGbool)
360+ (<@) = unsafeBinaryOp "<@"
361+instance PGSubset 'PGjsonb
362+instance PGSubset 'PGtsquery
363+instance PGSubset ('PGvararray ty)
1167364
1168365instance IsString
1169366 (Expression outer commons grp schemas params from (null 'PGtext)) where
@@ -1171,13 +368,19 @@ instance IsString
1171368 "E\'" <> fromString (escape =<< str) <> "\'"
1172369instance IsString
1173370 (Expression outer commons grp schemas params from (null 'PGtsvector)) where
1174− fromString str = cast tsvector . UnsafeExpression $
371+ fromString str = UnsafeExpression . parenthesized . (<> " :: tsvector") $
1175372 "E\'" <> fromString (escape =<< str) <> "\'"
1176373instance IsString
1177374 (Expression outer commons grp schemas params from (null 'PGtsquery)) where
1178− fromString str = cast tsquery . UnsafeExpression $
375+ fromString str = UnsafeExpression . parenthesized . (<> " :: tsquery") $
1179376 "E\'" <> fromString (escape =<< str) <> "\'"
1180377
378+instance Semigroup
379+ (Expression outer commons grp schemas params from (null ('PGvararray ty))) where
380+ (<>) = unsafeBinaryOp "||"
381+instance Semigroup
382+ (Expression outer commons grp schemas params from (null 'PGjsonb)) where
383+ (<>) = unsafeBinaryOp "||"
1181384instance Semigroup
1182385 (Expression outer commons grp schemas params from (null 'PGtext)) where
1183386 (<>) = unsafeBinaryOp "||"
@@ -1193,1399 +396,3 @@ instance Monoid
1193396 (Expression outer commons grp schemas params from (null 'PGtsvector)) where
1194397 mempty = fromString ""
1195398 mappend = (<>)
1196−
1197−-- | >>> printSQL $ lower "ARRRGGG"
1198−-- lower(E'ARRRGGG')
1199−lower :: null 'PGtext :--> null 'PGtext
1200−lower = unsafeFunction "lower"
1201−
1202−-- | >>> printSQL $ upper "eeee"
1203−-- upper(E'eeee')
1204−upper :: null 'PGtext :--> null 'PGtext
1205−upper = unsafeFunction "upper"
1206−
1207−-- | >>> printSQL $ charLength "four"
1208−-- char_length(E'four')
1209−charLength :: null 'PGtext :--> null 'PGint4
1210−charLength = unsafeFunction "char_length"
1211−
1212−-- | The `like` expression returns true if the @string@ matches
1213−-- the supplied @pattern@. If @pattern@ does not contain percent signs
1214−-- or underscores, then the pattern only represents the string itself;
1215−-- in that case `like` acts like the equals operator. An underscore (_)
1216−-- in pattern stands for (matches) any single character; a percent sign (%)
1217−-- matches any sequence of zero or more characters.
1218−--
1219−-- >>> printSQL $ "abc" `like` "a%"
1220−-- (E'abc' LIKE E'a%')
1221−like :: Operator (null 'PGtext) (null 'PGtext) ('Null 'PGbool)
1222−like = unsafeBinaryOp "LIKE"
1223−
1224−-- | The key word ILIKE can be used instead of LIKE to make the
1225−-- match case-insensitive according to the active locale.
1226−--
1227−-- >>> printSQL $ "abc" `ilike` "a%"
1228−-- (E'abc' ILIKE E'a%')
1229−ilike :: Operator (null 'PGtext) (null 'PGtext) ('Null 'PGbool)
1230−ilike = unsafeBinaryOp "ILIKE"
1231−
1232−{-----------------------------------------
1233− -- json and jsonb support
1234−
1235−See https://www.postgresql.org/docs/10/static/functions-json.html -- most
1236−comments lifted directly from this page.
1237−
1238−Table 9.44: json and jsonb operators
1239−-----------------------------------------}
1240−
1241−-- | Get JSON value (object field or array element) at a key.
1242−(.->)
1243− :: (json `In` PGJsonType, key `In` PGJsonKey)
1244− => Operator (null json) (null key) ('Null json)
1245−infixl 8 .->
1246−(.->) = unsafeBinaryOp "->"
1247−
1248−-- | Get JSON value (object field or array element) at a key, as text.
1249−(.->>)
1250− :: (json `In` PGJsonType, key `In` PGJsonKey)
1251− => Operator (null json) (null key) ('Null 'PGtext)
1252−infixl 8 .->>
1253−(.->>) = unsafeBinaryOp "->>"
1254−
1255−-- | Get JSON value at a specified path.
1256−(.#>)
1257− :: json `In` PGJsonType
1258− => Operator (null json) (null ('PGvararray ('NotNull 'PGtext))) ('Null json)
1259−infixl 8 .#>
1260−(.#>) = unsafeBinaryOp "#>"
1261−
1262−-- | Get JSON value at a specified path as text.
1263−(.#>>)
1264− :: json `In` PGJsonType
1265− => Operator (null json) (null ('PGvararray ('NotNull 'PGtext))) ('Null 'PGtext)
1266−infixl 8 .#>>
1267−(.#>>) = unsafeBinaryOp "#>>"
1268−
1269−-- Additional jsonb operators
1270−
1271−class PGSubset container where
1272− (@>) :: Operator (null0 container) (null1 container) ('Null 'PGbool)
1273− (@>) = unsafeBinaryOp "@>"
1274− (<@) :: Operator (null0 container) (null1 container) ('Null 'PGbool)
1275− (<@) = unsafeBinaryOp "<@"
1276−instance PGSubset 'PGjsonb
1277−instance PGSubset 'PGtsquery
1278−instance PGSubset ('PGvararray ty)
1279−
1280−-- | Does the string exist as a top-level key within the JSON value?
1281−(.?) :: Operator (null 'PGjsonb) (null 'PGtext) ('Null 'PGbool)
1282−infixl 9 .?
1283−(.?) = unsafeBinaryOp "?"
1284−
1285−-- | Do any of these array strings exist as top-level keys?
1286−(.?|) :: Operator
1287− (null 'PGjsonb)
1288− (null ('PGvararray ('NotNull 'PGtext)))
1289− ('Null 'PGbool)
1290−infixl 9 .?|
1291−(.?|) = unsafeBinaryOp "?|"
1292−
1293−-- | Do all of these array strings exist as top-level keys?
1294−(.?&) :: Operator
1295− (null 'PGjsonb)
1296− (null ('PGvararray ('NotNull 'PGtext)))
1297− ('Null 'PGbool)
1298−infixl 9 .?&
1299−(.?&) = unsafeBinaryOp "?&"
1300−
1301−instance Semigroup
1302− (Expression outer commons grp schemas params from (null 'PGjsonb)) where
1303− (<>) = unsafeBinaryOp "||"
1304−
1305−-- | Delete a key or keys from a JSON object, or remove an array element.
1306−--
1307−-- If the right operand is..
1308−--
1309−-- @ text @: Delete key/value pair or string element from left operand. Key/value pairs
1310−-- are matched based on their key value.
1311−--
1312−-- @ text[] @: Delete multiple key/value pairs or string elements
1313−-- from left operand. Key/value pairs are matched based on their key value.
1314−--
1315−-- @ integer @: Delete the array element with specified index (Negative integers
1316−-- count from the end). Throws an error if top level container is not an array.
1317−(.-.)
1318− :: (key `In` '[ 'PGtext, 'PGvararray ('NotNull 'PGtext), 'PGint4, 'PGint2 ]) -- hlint error without parens here
1319− => Operator (null 'PGjsonb) (null key) (null 'PGjsonb)
1320−infixl 6 .-.
1321−(.-.) = unsafeBinaryOp "-"
1322−
1323−-- | Delete the field or element with specified path (for JSON arrays, negative
1324−-- integers count from the end)
1325−(#-.) :: Operator (null 'PGjsonb) (null ('PGvararray ('NotNull 'PGtext))) (null 'PGjsonb)
1326−infixl 6 #-.
1327−(#-.) = unsafeBinaryOp "#-"
1328−
1329−{-----------------------------------------
1330−Table 9.45: JSON creation functions
1331−-----------------------------------------}
1332−
1333−-- | Returns the value as json. Arrays and composites are converted
1334−-- (recursively) to arrays and objects; otherwise, if there is a cast from the
1335−-- type to json, the cast function will be used to perform the conversion;
1336−-- otherwise, a scalar value is produced. For any scalar type other than a
1337−-- number, a Boolean, or a null value, the text representation will be used, in
1338−-- such a fashion that it is a valid json value.
1339−toJson :: null ty :--> null 'PGjson
1340−toJson = unsafeFunction "to_json"
1341−
1342−-- | Returns the value as jsonb. Arrays and composites are converted
1343−-- (recursively) to arrays and objects; otherwise, if there is a cast from the
1344−-- type to json, the cast function will be used to perform the conversion;
1345−-- otherwise, a scalar value is produced. For any scalar type other than a
1346−-- number, a Boolean, or a null value, the text representation will be used, in
1347−-- such a fashion that it is a valid jsonb value.
1348−toJsonb :: null ty :--> null 'PGjsonb
1349−toJsonb = unsafeFunction "to_jsonb"
1350−
1351−-- | Returns the array as a JSON array. A PostgreSQL multidimensional array
1352−-- becomes a JSON array of arrays.
1353−arrayToJson :: null ('PGvararray ty) :--> null 'PGjson
1354−arrayToJson = unsafeFunction "array_to_json"
1355−
1356−-- | Returns the row as a JSON object.
1357−rowToJson :: null ('PGcomposite ty) :--> null 'PGjson
1358−rowToJson = unsafeFunction "row_to_json"
1359−
1360−-- | Builds a possibly-heterogeneously-typed JSON array out of a variadic
1361−-- argument list.
1362−jsonBuildArray :: SListI tuple => FunctionN tuple (null 'PGjson)
1363−jsonBuildArray = unsafeFunctionN "json_build_array"
1364−
1365−-- | Builds a possibly-heterogeneously-typed (binary) JSON array out of a
1366−-- variadic argument list.
1367−jsonbBuildArray :: SListI tuple => FunctionN tuple (null 'PGjsonb)
1368−jsonbBuildArray = unsafeFunctionN "jsonb_build_array"
1369−
1370−class PGBuildObject tys where
1371−instance PGBuildObject '[]
1372−instance (PGBuildObject tys, key `In` PGJsonKey)
1373− => PGBuildObject ('NotNull key ': value ': tys)
1374−
1375−-- | Builds a possibly-heterogeneously-typed JSON object out of a variadic
1376−-- argument list. The elements of the argument list must alternate between text
1377−-- and values.
1378−jsonBuildObject
1379− :: (SListI elems, PGBuildObject elems)
1380− => FunctionN elems (null 'PGjson)
1381−jsonBuildObject = unsafeFunctionN "json_build_object"
1382−
1383−-- | Builds a possibly-heterogeneously-typed (binary) JSON object out of a
1384−-- variadic argument list. The elements of the argument list must alternate
1385−-- between keys and values.
1386−jsonbBuildObject
1387− :: (SListI elems, PGBuildObject elems)
1388− => FunctionN elems (null 'PGjsonb)
1389−jsonbBuildObject = unsafeFunctionN "jsonb_build_object"
1390−
1391−-- | Builds a JSON object out of a text array.
1392−-- The array must have two dimensions
1393−-- such that each inner array has exactly two elements,
1394−-- which are taken as a key/value pair.
1395−jsonObject
1396− :: null ('PGfixarray '[n,2] ('NotNull 'PGtext))
1397− :--> null 'PGjson
1398−jsonObject = unsafeFunction "json_object"
1399−
1400−-- | Builds a binary JSON object out of a text array.
1401−-- The array must have two dimensions
1402−-- such that each inner array has exactly two elements,
1403−-- which are taken as a key/value pair.
1404−jsonbObject
1405− :: null ('PGfixarray '[n,2] ('NotNull 'PGtext))
1406− :--> null 'PGjsonb
1407−jsonbObject = unsafeFunction "jsonb_object"
1408−
1409−-- | This is an alternate form of 'jsonObject' that takes two arrays; one for
1410−-- keys and one for values, that are zipped pairwise to create a JSON object.
1411−jsonZipObject :: FunctionN
1412− '[ null ('PGvararray ('NotNull 'PGtext))
1413− , null ('PGvararray ('NotNull 'PGtext)) ]
1414− ( null 'PGjson )
1415−jsonZipObject = unsafeFunctionN "json_object"
1416−
1417−-- | This is an alternate form of 'jsonbObject' that takes two arrays; one for
1418−-- keys and one for values, that are zipped pairwise to create a binary JSON
1419−-- object.
1420−jsonbZipObject :: FunctionN
1421− '[ null ('PGvararray ('NotNull 'PGtext))
1422− , null ('PGvararray ('NotNull 'PGtext)) ]
1423− ( null 'PGjsonb )
1424−jsonbZipObject = unsafeFunctionN "jsonb_object"
1425−
1426−{-----------------------------------------
1427−Table 9.46: JSON processing functions
1428−-----------------------------------------}
1429−
1430−-- | Returns the number of elements in the outermost JSON array.
1431−jsonArrayLength :: null 'PGjson :--> null 'PGint4
1432−jsonArrayLength = unsafeFunction "json_array_length"
1433−
1434−-- | Returns the number of elements in the outermost binary JSON array.
1435−jsonbArrayLength :: null 'PGjsonb :--> null 'PGint4
1436−jsonbArrayLength = unsafeFunction "jsonb_array_length"
1437−
1438−-- | Returns JSON value pointed to by the given path (equivalent to #>
1439−-- operator).
1440−jsonExtractPath
1441− :: SListI elems
1442− => Expression outer commons grp schemas params from (null 'PGjson)
1443− -> NP (Expression outer commons grp schemas params from) elems
1444− -> Expression outer commons grp schemas params from (null 'PGjsonb)
1445−jsonExtractPath x xs =
1446− unsafeFunctionN "json_extract_path" (x :* xs)
1447−
1448−-- | Returns JSON value pointed to by the given path (equivalent to #>
1449−-- operator).
1450−jsonbExtractPath
1451− :: SListI elems
1452− => Expression outer commons grp schemas params from (null 'PGjsonb)
1453− -> NP (Expression outer commons grp schemas params from) elems
1454− -> Expression outer commons grp schemas params from (null 'PGjsonb)
1455−jsonbExtractPath x xs =
1456− unsafeFunctionN "jsonb_extract_path" (x :* xs)
1457−
1458−-- | Returns JSON value pointed to by the given path (equivalent to #>
1459−-- operator), as text.
1460−jsonExtractPathAsText
1461− :: SListI elems
1462− => Expression outer commons grp schemas params from (null 'PGjson)
1463− -> NP (Expression outer commons grp schemas params from) elems
1464− -> Expression outer commons grp schemas params from (null 'PGjson)
1465−jsonExtractPathAsText x xs =
1466− unsafeFunctionN "json_extract_path_text" (x :* xs)
1467−
1468−-- | Returns JSON value pointed to by the given path (equivalent to #>
1469−-- operator), as text.
1470−jsonbExtractPathAsText
1471− :: SListI elems
1472− => Expression outer commons grp schemas params from (null 'PGjsonb)
1473− -> NP (Expression outer commons grp schemas params from) elems
1474− -> Expression outer commons grp schemas params from (null 'PGjsonb)
1475−jsonbExtractPathAsText x xs =
1476− unsafeFunctionN "jsonb_extract_path_text" (x :* xs)
1477−
1478−-- | Returns the type of the outermost JSON value as a text string. Possible
1479−-- types are object, array, string, number, boolean, and null.
1480−jsonTypeof :: null 'PGjson :--> null 'PGtext
1481−jsonTypeof = unsafeFunction "json_typeof"
1482−
1483−-- | Returns the type of the outermost binary JSON value as a text string.
1484−-- Possible types are object, array, string, number, boolean, and null.
1485−jsonbTypeof :: null 'PGjsonb :--> null 'PGtext
1486−jsonbTypeof = unsafeFunction "jsonb_typeof"
1487−
1488−-- | Returns its argument with all object fields that have null values omitted.
1489−-- Other null values are untouched.
1490−jsonStripNulls :: null 'PGjson :--> null 'PGjson
1491−jsonStripNulls = unsafeFunction "json_strip_nulls"
1492−
1493−-- | Returns its argument with all object fields that have null values omitted.
1494−-- Other null values are untouched.
1495−jsonbStripNulls :: null 'PGjsonb :--> null 'PGjsonb
1496−jsonbStripNulls = unsafeFunction "jsonb_strip_nulls"
1497−
1498−-- | @ jsonbSet target path new_value create_missing @
1499−--
1500−-- Returns target with the section designated by path replaced by new_value,
1501−-- or with new_value added if create_missing is true ( default is true) and the
1502−-- item designated by path does not exist. As with the path orientated
1503−-- operators, negative integers that appear in path count from the end of JSON
1504−-- arrays.
1505−jsonbSet
1506− :: Expression outer commons grp schemas params from (null 'PGjsonb)
1507− -> Expression outer commons grp schemas params from (null ('PGvararray ('NotNull 'PGtext)))
1508− -> Expression outer commons grp schemas params from (null 'PGjsonb)
1509− -> Maybe (Expression outer commons grp schemas params from (null 'PGbool))
1510− -> Expression outer commons grp schemas params from (null 'PGjsonb)
1511−jsonbSet tgt path val createMissing = case createMissing of
1512− Just m -> unsafeFunctionN "jsonb_set" (tgt :* path :* val :* m :* Nil)
1513− Nothing -> unsafeFunctionN "jsonb_set" (tgt :* path :* val :* Nil)
1514−
1515−-- | @ jsonbInsert target path new_value insert_after @
1516−--
1517−-- Returns target with new_value inserted. If target section designated by
1518−-- path is in a JSONB array, new_value will be inserted before target or after
1519−-- if insert_after is true (default is false). If target section designated by
1520−-- path is in JSONB object, new_value will be inserted only if target does not
1521−-- exist. As with the path orientated operators, negative integers that appear
1522−-- in path count from the end of JSON arrays.
1523−jsonbInsert
1524− :: Expression outer commons grp schemas params from (null 'PGjsonb)
1525− -> Expression outer commons grp schemas params from (null ('PGvararray ('NotNull 'PGtext)))
1526− -> Expression outer commons grp schemas params from (null 'PGjsonb)
1527− -> Maybe (Expression outer commons grp schemas params from (null 'PGbool))
1528− -> Expression outer commons grp schemas params from (null 'PGjsonb)
1529−jsonbInsert tgt path val insertAfter = case insertAfter of
1530− Just i -> unsafeFunctionN "jsonb_insert" (tgt :* path :* val :* i :* Nil)
1531− Nothing -> unsafeFunctionN "jsonb_insert" (tgt :* path :* val :* Nil)
1532−
1533−-- | Returns its argument as indented JSON text.
1534−jsonbPretty :: null 'PGjsonb :--> null 'PGtext
1535−jsonbPretty = unsafeFunction "jsonb_pretty"
1536−
1537−(@@) :: Operator (null 'PGtsvector) (null 'PGtsquery) ('Null 'PGbool)
1538−(@@) = unsafeBinaryOp "@@"
1539−
1540−(.&) :: Operator (null 'PGtsquery) (null 'PGtsquery) (null 'PGtsquery)
1541−(.&) = unsafeBinaryOp "&&"
1542−
1543−(.|) :: Operator (null 'PGtsquery) (null 'PGtsquery) (null 'PGtsquery)
1544−(.|) = unsafeBinaryOp "||"
1545−
1546−(.!) :: null 'PGtsquery :--> null 'PGtsquery
1547−(.!) = unsafeUnaryOpL "!!"
1548−
1549−(<->) :: Operator (null 'PGtsquery) (null 'PGtsquery) (null 'PGtsquery)
1550−(<->) = unsafeBinaryOp "<->"
1551−
1552−arrayToTSvector
1553− :: null ('PGvararray ('NotNull 'PGtext))
1554− :--> null 'PGtsvector
1555−arrayToTSvector = unsafeFunction "array_to_tsvector"
1556−
1557−tsvectorLength :: null 'PGtsvector :--> null 'PGint4
1558−tsvectorLength = unsafeFunction "length"
1559−
1560−numnode :: null 'PGtsquery :--> null 'PGint4
1561−numnode = unsafeFunction "numnode"
1562−
1563−plainToTSquery :: null 'PGtext :--> null 'PGtsquery
1564−plainToTSquery = unsafeFunction "plainto_tsquery"
1565−
1566−phraseToTSquery :: null 'PGtext :--> null 'PGtsquery
1567−phraseToTSquery = unsafeFunction "phraseto_tsquery"
1568−
1569−websearchToTSquery :: null 'PGtext :--> null 'PGtsquery
1570−websearchToTSquery = unsafeFunction "websearch_to_tsquery"
1571−
1572−queryTree :: null 'PGtsquery :--> null 'PGtext
1573−queryTree = unsafeFunction "query_tree"
1574−
1575−toTSquery :: null 'PGtext :--> null 'PGtsquery
1576−toTSquery = unsafeFunction "to_tsquery"
1577−
1578−toTSvector
1579− :: ty `In` '[ 'PGtext, 'PGjson, 'PGjsonb]
1580− => null ty :--> null 'PGtsvector
1581−toTSvector = unsafeFunction "to_tsvector"
1582−
1583−setWeight
1584− :: FunctionN '[null 'PGtsvector, null ('PGchar 1)] (null 'PGtsvector)
1585−setWeight = unsafeFunctionN "set_weight"
1586−
1587−strip :: null 'PGtsvector :--> null 'PGtsvector
1588−strip = unsafeFunction "strip"
1589−
1590−jsonToTSvector :: FunctionN '[null 'PGjson, null 'PGjson] (null 'PGtsvector)
1591−jsonToTSvector = unsafeFunctionN "json_to_tsvector"
1592−
1593−jsonbToTSvector :: FunctionN '[null 'PGjsonb, null 'PGjsonb] (null 'PGtsvector)
1594−jsonbToTSvector = unsafeFunctionN "jsonb_to_tsvector"
1595−
1596−tsDelete :: FunctionN
1597− '[null 'PGtsvector, null ('PGvararray ('NotNull 'PGtext))]
1598− (null 'PGtsvector)
1599−tsDelete = unsafeFunctionN "ts_delete"
1600−
1601−tsFilter :: FunctionN
1602− '[null 'PGtsvector, null ('PGvararray ('NotNull ('PGchar 1)))]
1603− (null 'PGtsvector)
1604−tsFilter = unsafeFunctionN "ts_filter"
1605−
1606−tsHeadline
1607− :: document `In` '[ 'PGtext, 'PGjson, 'PGjsonb]
1608− => FunctionN '[null document, null 'PGtsquery] (null 'PGtext)
1609−tsHeadline = unsafeFunctionN "ts_headline"
1610−{-----------------------------------------
1611−aggregation
1612−-----------------------------------------}
1613−
1614−unsafeAggregateN
1615− :: SOP.SListI xs
1616− => ByteString -- ^ function
1617− -> Distinction (NP (Expression outer commons 'Ungrouped schemas params from)) xs
1618− -> Expression outer commons ('Grouped bys) schemas params from y
1619−unsafeAggregateN fun xs = UnsafeExpression $ fun <> parenthesized (renderSQL xs)
1620−
1621−{- |
1622−`Aggregate` functions compute a single result from a set of input values.
1623−`Aggregate` functions can be used as `GroupedBy` `Expression`s as well
1624−as `WindowFunction`s.
1625−-}
1626−class Aggregate expr1 exprN aggr
1627− | aggr -> expr1, aggr -> exprN where
1628−
1629− -- | A special aggregation that does not require an input
1630− --
1631− -- >>> :{
1632− -- let
1633− -- expression :: Expression '[] commons ('Grouped bys) schemas params from ('NotNull 'PGint8)
1634− -- expression = countStar
1635− -- in printSQL expression
1636− -- :}
1637− -- count(*)
1638− countStar :: aggr ('NotNull 'PGint8)
1639−
1640− -- | >>> :{
1641− -- let
1642− -- expression :: Expression '[] commons ('Grouped bys) schemas params '[tab ::: '["col" ::: null ty]] ('NotNull 'PGint8)
1643− -- expression = count (All #col)
1644− -- in printSQL expression
1645− -- :}
1646− -- count(ALL "col")
1647− count
1648− :: expr1 ty
1649− -- ^ what to count
1650− -> aggr ('NotNull 'PGint8)
1651−
1652− -- | >>> :{
1653− -- let
1654− -- expression :: Expression '[] commons ('Grouped bys) schemas params '[tab ::: '["col" ::: 'Null 'PGnumeric]] ('NotNull 'PGnumeric)
1655− -- expression = sum_ (Distinct #col)
1656− -- in printSQL expression
1657− -- :}
1658− -- sum(DISTINCT "col")
1659− sum_
1660− :: ty `In` PGNum
1661− => expr1 (null ty)
1662− -> aggr ('NotNull ty)
1663−
1664− -- | input values, including nulls, concatenated into an array
1665− arrayAgg
1666− :: expr1 ty
1667− -> aggr ('Null ('PGvararray ty))
1668−
1669− -- | aggregates values as a JSON array
1670− jsonAgg
1671− :: expr1 ty
1672− -> aggr ('Null 'PGjson)
1673−
1674− -- | aggregates values as a JSON array
1675− jsonbAgg
1676− :: expr1 ty
1677− -> aggr ('Null 'PGjsonb)
1678−
1679− {- |
1680− the bitwise AND of all non-null input values, or null if none
1681− >>> :{
1682− let
1683− expression :: Expression '[] commons ('Grouped bys) schemas params '[tab ::: '["col" ::: null 'PGint4]] ('Null 'PGint4)
1684− expression = bitAnd (Distinct #col)
1685− in printSQL expression
1686− :}
1687− bit_and(DISTINCT "col")
1688− -}
1689− bitAnd
1690− :: int `In` PGIntegral
1691− => expr1 (null int)
1692− -- ^ what to aggregate
1693− -> aggr ('Null int)
1694−
1695− {- |
1696− the bitwise OR of all non-null input values, or null if none
1697−
1698− >>> :{
1699− let
1700− expression :: Expression '[] commons ('Grouped bys) schemas params '[tab ::: '["col" ::: null 'PGint4]] ('Null 'PGint4)
1701− expression = bitOr (All #col)
1702− in printSQL expression
1703− :}
1704− bit_or(ALL "col")
1705− -}
1706− bitOr
1707− :: int `In` PGIntegral
1708− => expr1 (null int)
1709− -- ^ what to aggregate
1710− -> aggr ('Null int)
1711−
1712− {- |
1713− true if all input values are true, otherwise false
1714−
1715− >>> :{
1716− let
1717− winFun :: WindowFunction '[] commons 'Ungrouped schemas params '[tab ::: '["col" ::: null 'PGbool]] ('Null 'PGbool)
1718− winFun = boolAnd #col
1719− in printSQL winFun
1720− :}
1721− bool_and("col")
1722− -}
1723− boolAnd
1724− :: expr1 (null 'PGbool)
1725− -- ^ what to aggregate
1726− -> aggr ('Null 'PGbool)
1727−
1728− {- |
1729− true if at least one input value is true, otherwise false
1730−
1731− >>> :{
1732− let
1733− expression :: Expression '[] commons ('Grouped bys) schemas params '[tab ::: '["col" ::: null 'PGbool]] ('Null 'PGbool)
1734− expression = boolOr (All #col)
1735− in printSQL expression
1736− :}
1737− bool_or(ALL "col")
1738− -}
1739− boolOr
1740− :: expr1 (null 'PGbool)
1741− -- ^ what to aggregate
1742− -> aggr ('Null 'PGbool)
1743−
1744− {- |
1745− equivalent to `boolAnd`
1746−
1747− >>> :{
1748− let
1749− expression :: Expression '[] commons ('Grouped bys) schemas params '[tab ::: '["col" ::: null 'PGbool]] ('Null 'PGbool)
1750− expression = every (Distinct #col)
1751− in printSQL expression
1752− :}
1753− every(DISTINCT "col")
1754− -}
1755− every
1756− :: expr1 (null 'PGbool)
1757− -- ^ what to aggregate
1758− -> aggr ('Null 'PGbool)
1759−
1760− {- |maximum value of expression across all input values-}
1761− max_
1762− :: expr1 (null ty)
1763− -- ^ what to maximize
1764− -> aggr ('Null ty)
1765−
1766− -- | minimum value of expression across all input values
1767− min_
1768− :: expr1 (null ty)
1769− -- ^ what to minimize
1770− -> aggr ('Null ty)
1771−
1772− -- | the average (arithmetic mean) of all input values
1773− avg
1774− :: expr1 (null ty)
1775− -- ^ what to average
1776− -> aggr ('Null (PGAvg ty))
1777−
1778− {- | correlation coefficient
1779− >>> :{
1780− let
1781− expression :: Expression '[] c ('Grouped g) s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGfloat8)
1782− expression = corr (All (#y *: #x))
1783− in printSQL expression
1784− :}
1785− corr(ALL "y", "x")
1786− -}
1787− corr
1788− :: exprN '[null 'PGfloat8, null 'PGfloat8]
1789− -> aggr ('Null 'PGfloat8)
1790−
1791− {- | population covariance
1792− >>> :{
1793− let
1794− expression :: Expression '[] c ('Grouped g) s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGfloat8)
1795− expression = covarPop (All (#y *: #x))
1796− in printSQL expression
1797− :}
1798− covar_pop(ALL "y", "x")
1799− -}
1800− covarPop
1801− :: exprN '[null 'PGfloat8, null 'PGfloat8]
1802− -> aggr ('Null 'PGfloat8)
1803−
1804− {- | sample covariance
1805− >>> :{
1806− let
1807− winFun :: WindowFunction '[] c 'Ungrouped s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGfloat8)
1808− winFun = covarSamp (#y *: #x)
1809− in printSQL winFun
1810− :}
1811− covar_samp("y", "x")
1812− -}
1813− covarSamp
1814− :: exprN '[null 'PGfloat8, null 'PGfloat8]
1815− -> aggr ('Null 'PGfloat8)
1816−
1817− {- | average of the independent variable (sum(X)/N)
1818− >>> :{
1819− let
1820− expression :: Expression '[] c ('Grouped g) s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGfloat8)
1821− expression = regrAvgX (All (#y *: #x))
1822− in printSQL expression
1823− :}
1824− regr_avgx(ALL "y", "x")
1825− -}
1826− regrAvgX
1827− :: exprN '[null 'PGfloat8, null 'PGfloat8]
1828− -> aggr ('Null 'PGfloat8)
1829−
1830− {- | average of the dependent variable (sum(Y)/N)
1831− >>> :{
1832− let
1833− winFun :: WindowFunction '[] c 'Ungrouped s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGfloat8)
1834− winFun = regrAvgY (#y *: #x)
1835− in printSQL winFun
1836− :}
1837− regr_avgy("y", "x")
1838− -}
1839− regrAvgY
1840− :: exprN '[null 'PGfloat8, null 'PGfloat8]
1841− -> aggr ('Null 'PGfloat8)
1842−
1843− {- | number of input rows in which both expressions are nonnull
1844− >>> :{
1845− let
1846− winFun :: WindowFunction '[] c 'Ungrouped s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGint8)
1847− winFun = regrCount (#y *: #x)
1848− in printSQL winFun
1849− :}
1850− regr_count("y", "x")
1851− -}
1852− regrCount
1853− :: exprN '[null 'PGfloat8, null 'PGfloat8]
1854− -> aggr ('Null 'PGint8)
1855−
1856− {- | y-intercept of the least-squares-fit linear equation determined by the (X, Y) pairs
1857− >>> :{
1858− let
1859− expression :: Expression '[] c ('Grouped g) s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGfloat8)
1860− expression = regrIntercept (All (#y *: #x))
1861− in printSQL expression
1862− :}
1863− regr_intercept(ALL "y", "x")
1864− -}
1865− regrIntercept
1866− :: exprN '[null 'PGfloat8, null 'PGfloat8]
1867− -> aggr ('Null 'PGfloat8)
1868−
1869− -- | @regr_r2(Y, X)@, square of the correlation coefficient
1870− regrR2
1871− :: exprN '[null 'PGfloat8, null 'PGfloat8]
1872− -> aggr ('Null 'PGfloat8)
1873−
1874− -- | @regr_slope(Y, X)@, slope of the least-squares-fit linear equation
1875− -- determined by the (X, Y) pairs
1876− regrSlope
1877− :: exprN '[null 'PGfloat8, null 'PGfloat8]
1878− -> aggr ('Null 'PGfloat8)
1879−
1880− -- | @regr_sxx(Y, X)@, sum(X^2) - sum(X)^2/N
1881− -- (“sum of squares” of the independent variable)
1882− regrSxx
1883− :: exprN '[null 'PGfloat8, null 'PGfloat8]
1884− -> aggr ('Null 'PGfloat8)
1885−
1886− -- | @regr_sxy(Y, X)@, sum(X*Y) - sum(X) * sum(Y)/N
1887− -- (“sum of products” of independent times dependent variable)
1888− regrSxy
1889− :: exprN '[null 'PGfloat8, null 'PGfloat8]
1890− -> aggr ('Null 'PGfloat8)
1891−
1892− -- | @regr_syy(Y, X)@, sum(Y^2) - sum(Y)^2/N
1893− -- (“sum of squares” of the dependent variable)
1894− regrSyy
1895− :: exprN '[null 'PGfloat8, null 'PGfloat8]
1896− -> aggr ('Null 'PGfloat8)
1897−
1898− -- | historical alias for `stddevSamp`
1899− stddev
1900− :: expr1 (null ty)
1901− -> aggr ('Null (PGAvg ty))
1902−
1903− -- | population standard deviation of the input values
1904− stddevPop
1905− :: expr1 (null ty)
1906− -> aggr ('Null (PGAvg ty))
1907−
1908− -- | sample standard deviation of the input values
1909− stddevSamp
1910− :: expr1 (null ty)
1911− -> aggr ('Null (PGAvg ty))
1912−
1913− -- | historical alias for `varSamp`
1914− variance
1915− :: expr1 (null ty)
1916− -> aggr ('Null (PGAvg ty))
1917−
1918− -- | population variance of the input values
1919− -- (square of the population standard deviation)
1920− varPop
1921− :: expr1 (null ty)
1922− -> aggr ('Null (PGAvg ty))
1923−
1924− -- | sample variance of the input values
1925− -- (square of the sample standard deviation)
1926− varSamp
1927− :: expr1 (null ty)
1928− -> aggr ('Null (PGAvg ty))
1929−
1930−{- |
1931−`Distinction`s are used for the input of `Aggregate` `Expression`s.
1932−`All` invokes the aggregate once for each input row.
1933−`Distinct` invokes the aggregate once for each distinct value of the expression
1934−(or distinct set of values, for multiple expressions) found in the input
1935−-}
1936−data Distinction (expr :: kind -> Type) (ty :: kind)
1937− = All (expr ty)
1938− | Distinct (expr ty)
1939− deriving (GHC.Generic,Show,Eq,Ord)
1940−instance NFData (Distinction (Expression outer commons grp schemas params from) ty)
1941−instance RenderSQL (Distinction (Expression outer commons grp schemas params from) ty) where
1942− renderSQL = \case
1943− All x -> "ALL" <+> renderSQL x
1944− Distinct x -> "DISTINCT" <+> renderSQL x
1945−instance SOP.SListI tys => RenderSQL
1946− (Distinction (NP (Expression outer commons grp schemas params from)) tys) where
1947− renderSQL = \case
1948− All xs -> "ALL" <+> renderCommaSeparated renderSQL xs
1949− Distinct xs -> "DISTINCT" <+> renderCommaSeparated renderSQL xs
1950−
1951−instance Aggregate
1952− (Distinction (Expression outer commons 'Ungrouped schemas params from))
1953− (Distinction (NP (Expression outer commons 'Ungrouped schemas params from)))
1954− (Expression outer commons ('Grouped bys) schemas params from) where
1955− countStar = UnsafeExpression "count(*)"
1956− count = unsafeAggregate1 "count"
1957− sum_ = unsafeAggregate1 "sum"
1958− arrayAgg = unsafeAggregate1 "array_agg"
1959− jsonAgg = unsafeAggregate1 "json_agg"
1960− jsonbAgg = unsafeAggregate1 "jsonb_agg"
1961− bitAnd = unsafeAggregate1 "bit_and"
1962− bitOr = unsafeAggregate1 "bit_or"
1963− boolAnd = unsafeAggregate1 "bool_and"
1964− boolOr = unsafeAggregate1 "bool_or"
1965− every = unsafeAggregate1 "every"
1966− max_ = unsafeAggregate1 "max"
1967− min_ = unsafeAggregate1 "min"
1968− avg = unsafeAggregate1 "avg"
1969− corr = unsafeAggregateN "corr"
1970− covarPop = unsafeAggregateN "covar_pop"
1971− covarSamp = unsafeAggregateN "covar_samp"
1972− regrAvgX = unsafeAggregateN "regr_avgx"
1973− regrAvgY = unsafeAggregateN "regr_avgy"
1974− regrCount = unsafeAggregateN "regr_count"
1975− regrIntercept = unsafeAggregateN "regr_intercept"
1976− regrR2 = unsafeAggregateN "regr_r2"
1977− regrSlope = unsafeAggregateN "regr_slope"
1978− regrSxx = unsafeAggregateN "regr_sxx"
1979− regrSxy = unsafeAggregateN "regr_sxy"
1980− regrSyy = unsafeAggregateN "regr_syy"
1981− stddev = unsafeAggregate1 "stddev"
1982− stddevPop = unsafeAggregate1 "stddev_pop"
1983− stddevSamp = unsafeAggregate1 "stddev_samp"
1984− variance = unsafeAggregate1 "variance"
1985− varPop = unsafeAggregate1 "var_pop"
1986− varSamp = unsafeAggregate1 "var_samp"
1987−instance Aggregate
1988− (Expression outer commons grp schemas params from)
1989− (NP (Expression outer commons grp schemas params from))
1990− (WindowFunction outer commons grp schemas params from) where
1991− countStar = UnsafeWindowFunction "count(*)"
1992− count = unsafeWindowFunction1 "count"
1993− sum_ = unsafeWindowFunction1 "sum"
1994− arrayAgg = unsafeWindowFunction1 "array_agg"
1995− jsonAgg = unsafeWindowFunction1 "json_agg"
1996− jsonbAgg = unsafeWindowFunction1 "jsonb_agg"
1997− bitAnd = unsafeWindowFunction1 "bit_and"
1998− bitOr = unsafeWindowFunction1 "bit_or"
1999− boolAnd = unsafeWindowFunction1 "bool_and"
2000− boolOr = unsafeWindowFunction1 "bool_or"
2001− every = unsafeWindowFunction1 "every"
2002− max_ = unsafeWindowFunction1 "max"
2003− min_ = unsafeWindowFunction1 "min"
2004− avg = unsafeWindowFunction1 "avg"
2005− corr = unsafeWindowFunctionN "corr"
2006− covarPop = unsafeWindowFunctionN "covar_pop"
2007− covarSamp = unsafeWindowFunctionN "covar_samp"
2008− regrAvgX = unsafeWindowFunctionN "regr_avgx"
2009− regrAvgY = unsafeWindowFunctionN "regr_avgy"
2010− regrCount = unsafeWindowFunctionN "regr_count"
2011− regrIntercept = unsafeWindowFunctionN "regr_intercept"
2012− regrR2 = unsafeWindowFunctionN "regr_r2"
2013− regrSlope = unsafeWindowFunctionN "regr_slope"
2014− regrSxx = unsafeWindowFunctionN "regr_sxx"
2015− regrSxy = unsafeWindowFunctionN "regr_sxy"
2016− regrSyy = unsafeWindowFunctionN "regr_syy"
2017− stddev = unsafeWindowFunction1 "stddev"
2018− stddevPop = unsafeWindowFunction1 "stddev_pop"
2019− stddevSamp = unsafeWindowFunction1 "stddev_samp"
2020− variance = unsafeWindowFunction1 "variance"
2021− varPop = unsafeWindowFunction1 "var_pop"
2022− varSamp = unsafeWindowFunction1 "var_samp"
2023−
2024−-- | escape hatch to define aggregate functions
2025−unsafeAggregate1
2026− :: ByteString -- ^ aggregate function
2027− -> Distinction (Expression outer commons 'Ungrouped schemas params from) x
2028− -> Expression outer commons ('Grouped bys) schemas params from y
2029−unsafeAggregate1 fun x = UnsafeExpression $ fun <> parenthesized (renderSQL x)
2030−
2031−-- | A type family that calculates `PGAvg` type of a `PGType`.
2032−type family PGAvg ty where
2033− PGAvg 'PGint2 = 'PGnumeric
2034− PGAvg 'PGint4 = 'PGnumeric
2035− PGAvg 'PGint8 = 'PGnumeric
2036− PGAvg 'PGnumeric = 'PGnumeric
2037− PGAvg 'PGfloat4 = 'PGfloat8
2038− PGAvg 'PGfloat8 = 'PGfloat8
2039− PGAvg 'PGinterval = 'PGinterval
2040− PGAvg pg = TypeError
2041− ('Text "Squeal type error: No average for " ':<>: 'ShowType pg)
2042−
2043−{-----------------------------------------
2044−type expressions
2045−-----------------------------------------}
2046−
2047−-- | `TypeExpression`s are used in `cast`s and
2048−-- `Squeal.PostgreSQL.Definition.createTable` commands.
2049−newtype TypeExpression (schemas :: SchemasType) (ty :: NullityType)
2050− = UnsafeTypeExpression { renderTypeExpression :: ByteString }
2051− deriving (GHC.Generic,Show,Eq,Ord,NFData)
2052−instance RenderSQL (TypeExpression schemas ty) where
2053− renderSQL = renderTypeExpression
2054−
2055−-- | The enum or composite type in a `Typedef` can be expressed by its alias.
2056−typedef
2057− :: (Has sch schemas schema, Has td schema ('Typedef ty))
2058− => QualifiedAlias sch td
2059− -> TypeExpression schemas (null ty)
2060−typedef = UnsafeTypeExpression . renderSQL
2061−
2062−-- | The composite type corresponding to a `Table` definition can be expressed
2063−-- by its alias.
2064−typetable
2065− :: (Has sch schemas schema, Has tab schema ('Table table))
2066− => QualifiedAlias sch tab
2067− -> TypeExpression schemas (null ('PGcomposite (TableToRow table)))
2068−typetable = UnsafeTypeExpression . renderSQL
2069−
2070−-- | The composite type corresponding to a `View` definition can be expressed
2071−-- by its alias.
2072−typeview
2073− :: (Has sch schemas schema, Has vw schema ('View view))
2074− => QualifiedAlias sch vw
2075− -> TypeExpression schemas (null ('PGcomposite view))
2076−typeview = UnsafeTypeExpression . renderSQL
2077−
2078−-- | logical Boolean (true/false)
2079−bool :: TypeExpression schemas (null 'PGbool)
2080−bool = UnsafeTypeExpression "bool"
2081−-- | signed two-byte integer
2082−int2, smallint :: TypeExpression schemas (null 'PGint2)
2083−int2 = UnsafeTypeExpression "int2"
2084−smallint = UnsafeTypeExpression "smallint"
2085−-- | signed four-byte integer
2086−int4, int, integer :: TypeExpression schemas (null 'PGint4)
2087−int4 = UnsafeTypeExpression "int4"
2088−int = UnsafeTypeExpression "int"
2089−integer = UnsafeTypeExpression "integer"
2090−-- | signed eight-byte integer
2091−int8, bigint :: TypeExpression schemas (null 'PGint8)
2092−int8 = UnsafeTypeExpression "int8"
2093−bigint = UnsafeTypeExpression "bigint"
2094−-- | arbitrary precision numeric type
2095−numeric :: TypeExpression schemas (null 'PGnumeric)
2096−numeric = UnsafeTypeExpression "numeric"
2097−-- | single precision floating-point number (4 bytes)
2098−float4, real :: TypeExpression schemas (null 'PGfloat4)
2099−float4 = UnsafeTypeExpression "float4"
2100−real = UnsafeTypeExpression "real"
2101−-- | double precision floating-point number (8 bytes)
2102−float8, doublePrecision :: TypeExpression schemas (null 'PGfloat8)
2103−float8 = UnsafeTypeExpression "float8"
2104−doublePrecision = UnsafeTypeExpression "double precision"
2105−-- | currency amount
2106−money :: TypeExpression schema (null 'PGmoney)
2107−money = UnsafeTypeExpression "money"
2108−-- | variable-length character string
2109−text :: TypeExpression schemas (null 'PGtext)
2110−text = UnsafeTypeExpression "text"
2111−-- | fixed-length character string
2112−char, character
2113− :: forall n schemas null. (KnownNat n, 1 <= n)
2114− => TypeExpression schemas (null ('PGchar n))
2115−char = UnsafeTypeExpression $ "char(" <> renderNat @n <> ")"
2116−character = UnsafeTypeExpression $ "character(" <> renderNat @n <> ")"
2117−-- | variable-length character string
2118−varchar, characterVarying
2119− :: forall n schemas null. (KnownNat n, 1 <= n)
2120− => TypeExpression schemas (null ('PGvarchar n))
2121−varchar = UnsafeTypeExpression $ "varchar(" <> renderNat @n <> ")"
2122−characterVarying = UnsafeTypeExpression $
2123− "character varying(" <> renderNat @n <> ")"
2124−-- | binary data ("byte array")
2125−bytea :: TypeExpression schemas (null 'PGbytea)
2126−bytea = UnsafeTypeExpression "bytea"
2127−-- | date and time (no time zone)
2128−timestamp :: TypeExpression schemas (null 'PGtimestamp)
2129−timestamp = UnsafeTypeExpression "timestamp"
2130−-- | date and time, including time zone
2131−timestampWithTimeZone :: TypeExpression schemas (null 'PGtimestamptz)
2132−timestampWithTimeZone = UnsafeTypeExpression "timestamp with time zone"
2133−-- | calendar date (year, month, day)
2134−date :: TypeExpression schemas (null 'PGdate)
2135−date = UnsafeTypeExpression "date"
2136−-- | time of day (no time zone)
2137−time :: TypeExpression schemas (null 'PGtime)
2138−time = UnsafeTypeExpression "time"
2139−-- | time of day, including time zone
2140−timeWithTimeZone :: TypeExpression schemas (null 'PGtimetz)
2141−timeWithTimeZone = UnsafeTypeExpression "time with time zone"
2142−-- | time span
2143−interval :: TypeExpression schemas (null 'PGinterval)
2144−interval = UnsafeTypeExpression "interval"
2145−-- | universally unique identifier
2146−uuid :: TypeExpression schemas (null 'PGuuid)
2147−uuid = UnsafeTypeExpression "uuid"
2148−-- | IPv4 or IPv6 host address
2149−inet :: TypeExpression schemas (null 'PGinet)
2150−inet = UnsafeTypeExpression "inet"
2151−-- | textual JSON data
2152−json :: TypeExpression schemas (null 'PGjson)
2153−json = UnsafeTypeExpression "json"
2154−-- | binary JSON data, decomposed
2155−jsonb :: TypeExpression schemas (null 'PGjsonb)
2156−jsonb = UnsafeTypeExpression "jsonb"
2157−-- | variable length array
2158−vararray
2159− :: TypeExpression schemas pg
2160− -> TypeExpression schemas (null ('PGvararray pg))
2161−vararray ty = UnsafeTypeExpression $ renderSQL ty <> "[]"
2162−-- | fixed length array
2163−--
2164−-- >>> renderSQL (fixarray @'[2] json)
2165−-- "json[2]"
2166−fixarray
2167− :: forall dims schemas null pg. SOP.All KnownNat dims
2168− => TypeExpression schemas pg
2169− -> TypeExpression schemas (null ('PGfixarray dims pg))
2170−fixarray ty = UnsafeTypeExpression $
2171− renderSQL ty <> renderDims @dims
2172− where
2173− renderDims :: forall ns. SOP.All KnownNat ns => ByteString
2174− renderDims =
2175− ("[" <>)
2176− . (<> "]")
2177− . ByteString.intercalate "]["
2178− . hcollapse
2179− $ hcmap (SOP.Proxy @KnownNat)
2180− (K . fromString . show . natVal)
2181− (hpure SOP.Proxy :: NP SOP.Proxy ns)
2182−tsvector :: TypeExpression schemas (null 'PGtsvector)
2183−tsvector = UnsafeTypeExpression "tsvector"
2184−tsquery :: TypeExpression schemas (null 'PGtsquery)
2185−tsquery = UnsafeTypeExpression "tsquery"
2186−
2187−-- | `pgtype` is a demoted version of a `PGType`
2188−class PGTyped schemas (ty :: NullityType) where
2189− pgtype :: TypeExpression schemas ty
2190−instance PGTyped schemas (null 'PGbool) where pgtype = bool
2191−instance PGTyped schemas (null 'PGint2) where pgtype = int2
2192−instance PGTyped schemas (null 'PGint4) where pgtype = int4
2193−instance PGTyped schemas (null 'PGint8) where pgtype = int8
2194−instance PGTyped schemas (null 'PGnumeric) where pgtype = numeric
2195−instance PGTyped schemas (null 'PGfloat4) where pgtype = float4
2196−instance PGTyped schemas (null 'PGfloat8) where pgtype = float8
2197−instance PGTyped schemas (null 'PGmoney) where pgtype = money
2198−instance PGTyped schemas (null 'PGtext) where pgtype = text
2199−instance (KnownNat n, 1 <= n)
2200− => PGTyped schemas (null ('PGchar n)) where pgtype = char @n
2201−instance (KnownNat n, 1 <= n)
2202− => PGTyped schemas (null ('PGvarchar n)) where pgtype = varchar @n
2203−instance PGTyped schemas (null 'PGbytea) where pgtype = bytea
2204−instance PGTyped schemas (null 'PGtimestamp) where pgtype = timestamp
2205−instance PGTyped schemas (null 'PGtimestamptz) where pgtype = timestampWithTimeZone
2206−instance PGTyped schemas (null 'PGdate) where pgtype = date
2207−instance PGTyped schemas (null 'PGtime) where pgtype = time
2208−instance PGTyped schemas (null 'PGtimetz) where pgtype = timeWithTimeZone
2209−instance PGTyped schemas (null 'PGinterval) where pgtype = interval
2210−instance PGTyped schemas (null 'PGuuid) where pgtype = uuid
2211−instance PGTyped schemas (null 'PGjson) where pgtype = json
2212−instance PGTyped schemas (null 'PGjsonb) where pgtype = jsonb
2213−instance PGTyped schemas ty
2214− => PGTyped schemas (null ('PGvararray ty)) where
2215− pgtype = vararray (pgtype @schemas @ty)
2216−instance (SOP.All KnownNat dims, PGTyped schemas ty)
2217− => PGTyped schemas (null ('PGfixarray dims ty)) where
2218− pgtype = fixarray @dims (pgtype @schemas @ty)
2219−instance PGTyped schemas (null 'PGtsvector) where pgtype = tsvector
2220−instance PGTyped schemas (null 'PGtsquery) where pgtype = tsquery
2221−
2222−{-----------------------------------------
2223−Sorting
2224−-----------------------------------------}
2225−
2226−-- | `SortExpression`s are used by `orderBy` to optionally sort the results
2227−-- of a `Squeal.PostgreSQL.Query.Query`. `Asc` or `Desc`
2228−-- set the sort direction of a `NotNull` result
2229−-- column to ascending or descending. Ascending order puts smaller values
2230−-- first, where "smaller" is defined in terms of the `.<` operator. Similarly,
2231−-- descending order is determined with the `.>` operator. `AscNullsFirst`,
2232−-- `AscNullsLast`, `DescNullsFirst` and `DescNullsLast` options are used to
2233−-- determine whether nulls appear before or after non-null values in the sort
2234−-- ordering of a `Null` result column.
2235−data SortExpression outer commons grp schemas params from where
2236− Asc
2237− :: Expression outer commons grp schemas params from ('NotNull ty)
2238− -> SortExpression outer commons grp schemas params from
2239− Desc
2240− :: Expression outer commons grp schemas params from ('NotNull ty)
2241− -> SortExpression outer commons grp schemas params from
2242− AscNullsFirst
2243− :: Expression outer commons grp schemas params from ('Null ty)
2244− -> SortExpression outer commons grp schemas params from
2245− AscNullsLast
2246− :: Expression outer commons grp schemas params from ('Null ty)
2247− -> SortExpression outer commons grp schemas params from
2248− DescNullsFirst
2249− :: Expression outer commons grp schemas params from ('Null ty)
2250− -> SortExpression outer commons grp schemas params from
2251− DescNullsLast
2252− :: Expression outer commons grp schemas params from ('Null ty)
2253− -> SortExpression outer commons grp schemas params from
2254−deriving instance Show (SortExpression outer commons grp schemas params from)
2255−
2256−{- |
2257−The `orderBy` clause causes the result rows of a `Squeal.PostgreSQL.Query.TableExpression`
2258−to be sorted according to the specified `SortExpression`(s).
2259−If two rows are equal according to the leftmost expression,
2260−they are compared according to the next expression and so on.
2261−If they are equal according to all specified expressions,
2262−they are returned in an implementation-dependent order.
2263−
2264−You can also control the order in which rows are processed by window functions
2265−using `orderBy` within `Squeal.PostgreSQL.Query.Over`.
2266−-}
2267−class OrderBy expr where
2268− orderBy
2269− :: [SortExpression outer commons grp schemas params from]
2270− -> expr outer commons grp schemas params from
2271− -> expr outer commons grp schemas params from
2272−
2273−-- | Render a `SortExpression`.
2274−instance RenderSQL (SortExpression outer commons grp schemas params from) where
2275− renderSQL = \case
2276− Asc expression -> renderSQL expression <+> "ASC"
2277− Desc expression -> renderSQL expression <+> "DESC"
2278− AscNullsFirst expression -> renderSQL expression
2279− <+> "ASC NULLS FIRST"
2280− DescNullsFirst expression -> renderSQL expression
2281− <+> "DESC NULLS FIRST"
2282− AscNullsLast expression -> renderSQL expression <+> "ASC NULLS LAST"
2283− DescNullsLast expression -> renderSQL expression <+> "DESC NULLS LAST"
2284−
2285−-- | A `WindowDefinition` is a set of table rows that are somehow related
2286−-- to the current row
2287−data WindowDefinition outer commons grp schemas params from where
2288− WindowDefinition
2289− :: SListI bys
2290− => NP (Expression outer commons grp schemas params from) bys
2291− -> [SortExpression outer commons grp schemas params from]
2292− -> WindowDefinition outer commons grp schemas params from
2293−
2294−instance OrderBy WindowDefinition where
2295− orderBy sortsR (WindowDefinition parts sortsL)
2296− = WindowDefinition parts (sortsL ++ sortsR)
2297−
2298−instance RenderSQL (WindowDefinition outer commons schemas from grp params) where
2299− renderSQL (WindowDefinition part ord) =
2300− renderPartitionByClause part <> renderOrderByClause ord
2301− where
2302− renderPartitionByClause = \case
2303− Nil -> ""
2304− parts -> "PARTITION" <+> "BY" <+> renderCommaSeparated renderExpression parts
2305− renderOrderByClause = \case
2306− [] -> ""
2307− srts -> " ORDER" <+> "BY"
2308− <+> commaSeparated (renderSQL <$> srts)
2309−
2310−{- |
2311−The `partitionBy` clause within `Squeal.PostgreSQL.Query.Over` divides the rows into groups,
2312−or partitions, that share the same values of the `partitionBy` `Expression`(s).
2313−For each row, the window function is computed across the rows that fall into
2314−the same partition as the current row.
2315−-}
2316−partitionBy
2317− :: SListI bys
2318− => NP (Expression outer commons grp schemas params from) bys
2319− -> WindowDefinition outer commons grp schemas params from
2320−partitionBy bys = WindowDefinition bys []
2321−
2322−{- |
2323−A window function performs a calculation across a set of table rows
2324−that are somehow related to the current row. This is comparable to the type
2325−of calculation that can be done with an aggregate function.
2326−However, window functions do not cause rows to become grouped into a single
2327−output row like non-window aggregate calls would.
2328−Instead, the rows retain their separate identities.
2329−Behind the scenes, the window function is able to access more than
2330−just the current row of the query result.
2331−-}
2332−newtype WindowFunction
2333− (outer :: FromType)
2334− (commons :: FromType)
2335− (grp :: Grouping)
2336− (schemas :: SchemasType)
2337− (params :: [NullityType])
2338− (from :: FromType)
2339− (ty :: NullityType)
2340− = UnsafeWindowFunction { renderWindowFunction :: ByteString }
2341− deriving (GHC.Generic,Show,Eq,Ord,NFData)
2342−
2343−instance RenderSQL (WindowFunction outer commons grp schemas params from ty) where
2344− renderSQL = renderWindowFunction
2345−
2346−type WinFun0 x
2347− = forall outer commons grp schemas params from
2348− . WindowFunction outer commons grp schemas params from x
2349− -- ^ cannot reference aliases
2350−
2351−type WinFun1 x y
2352− = forall outer commons grp schemas params from
2353− . Expression outer commons grp schemas params from x
2354− -- ^ input
2355− -> WindowFunction outer commons grp schemas params from y
2356− -- ^ output
2357−
2358−type WinFunN xs y
2359− = forall outer commons grp schemas params from
2360− . NP (Expression outer commons grp schemas params from) xs
2361− -- ^ inputs
2362− -> WindowFunction outer commons grp schemas params from y
2363− -- ^ output
2364−
2365−unsafeWindowFunction1 :: ByteString -> WinFun1 x y
2366−unsafeWindowFunction1 fun x
2367− = UnsafeWindowFunction $ fun <> parenthesized (renderSQL x)
2368−
2369−unsafeWindowFunctionN :: SOP.SListI xs => ByteString -> WinFunN xs y
2370−unsafeWindowFunctionN fun xs = UnsafeWindowFunction $ fun <>
2371− parenthesized (renderCommaSeparated renderSQL xs)
2372−
2373−{- | rank of the current row with gaps; same as `rowNumber` of its first peer
2374−>>> printSQL rank
2375−rank()
2376−-}
2377−rank :: WinFun0 ('NotNull 'PGint8)
2378−rank = UnsafeWindowFunction "rank()"
2379−
2380−{- | number of the current row within its partition, counting from 1
2381−>>> printSQL rowNumber
2382−row_number()
2383−-}
2384−rowNumber :: WinFun0 ('NotNull 'PGint8)
2385−rowNumber = UnsafeWindowFunction "row_number()"
2386−
2387−{- | rank of the current row without gaps; this function counts peer groups
2388−>>> printSQL denseRank
2389−dense_rank()
2390−-}
2391−denseRank :: WinFun0 ('NotNull 'PGint8)
2392−denseRank = UnsafeWindowFunction "dense_rank()"
2393−
2394−{- | relative rank of the current row: (rank - 1) / (total partition rows - 1)
2395−>>> printSQL percentRank
2396−percent_rank()
2397−-}
2398−percentRank :: WinFun0 ('NotNull 'PGfloat8)
2399−percentRank = UnsafeWindowFunction "percent_rank()"
2400−
2401−{- | cumulative distribution: (number of partition rows
2402−preceding or peer with current row) / total partition rows
2403−>>> printSQL cumeDist
2404−cume_dist()
2405−-}
2406−cumeDist :: WinFun0 ('NotNull 'PGfloat8)
2407−cumeDist = UnsafeWindowFunction "cume_dist()"
2408−
2409−{- | integer ranging from 1 to the argument value,
2410−dividing the partition as equally as possible
2411−-}
2412−ntile :: WinFun1 ('NotNull 'PGint4) ('NotNull 'PGint4)
2413−ntile = unsafeWindowFunction1 "ntile"
2414−
2415−{- | returns value evaluated at the row that is offset rows before the current
2416−row within the partition; if there is no such row, instead return default
2417−(which must be of the same type as value). Both offset and default are
2418−evaluated with respect to the current row.
2419−-}
2420−lag :: WinFunN '[ty, 'NotNull 'PGint4, ty] ty
2421−lag = unsafeWindowFunctionN "lag"
2422−
2423−{- | returns value evaluated at the row that is offset rows after the current
2424−row within the partition; if there is no such row, instead return default
2425−(which must be of the same type as value). Both offset and default are
2426−evaluated with respect to the current row.
2427−-}
2428−lead :: WinFunN '[ty, 'NotNull 'PGint4, ty] ty
2429−lead = unsafeWindowFunctionN "lag"
2430−
2431−{- | returns value evaluated at the row that is the
2432−first row of the window frame
2433−-}
2434−firstValue :: WinFun1 ty ty
2435−firstValue = unsafeWindowFunction1 "first_value"
2436−
2437−{- | returns value evaluated at the row that is the
2438−last row of the window frame
2439−-}
2440−lastValue :: WinFun1 ty ty
2441−lastValue = unsafeWindowFunction1 "last_value"
2442−
2443−{- | returns value evaluated at the row that is the nth
2444−row of the window frame (counting from 1); null if no such row
2445−-}
2446−nthValue :: WinFunN '[null ty, 'NotNull 'PGint4] ('Null ty)
2447−nthValue = unsafeWindowFunctionN "nth_value"
2448−
2449−{-|
2450−Create date from year, month and day fields
2451−
2452−>>> printSQL (makeDate (1984 :* 7 *: 3))
2453−make_date(1984, 7, 3)
2454−-}
2455−makeDate :: FunctionN
2456− '[ null 'PGint4, null 'PGint4, null 'PGint4 ]
2457− ( null 'PGdate )
2458−makeDate = unsafeFunctionN "make_date"
2459−
2460−{-|
2461−Create time from hour, minute and seconds fields
2462−
2463−>>> printSQL (makeTime (8 :* 15 *: 23.5))
2464−make_time(8, 15, 23.5)
2465−-}
2466−makeTime :: FunctionN
2467− '[ null 'PGint4, null 'PGint4, null 'PGfloat8 ]
2468− ( null 'PGtime )
2469−makeTime = unsafeFunctionN "make_time"
2470−
2471−{-|
2472−Create timestamp from year, month, day, hour, minute and seconds fields
2473−
2474−>>> printSQL (makeTimestamp (2013 :* 7 :* 15 :* 8 :* 15 *: 23.5))
2475−make_timestamp(2013, 7, 15, 8, 15, 23.5)
2476−-}
2477−makeTimestamp :: FunctionN
2478− '[ null 'PGint4, null 'PGint4, null 'PGint4
2479− , null 'PGint4, null 'PGint4, null 'PGfloat8 ]
2480− ( null 'PGtimestamp )
2481−makeTimestamp = unsafeFunctionN "make_timestamp"
2482−
2483−{-|
2484−Create timestamp with time zone from
2485−year, month, day, hour, minute and seconds fields;
2486−the current time zone is used
2487−
2488−>>> printSQL (makeTimestamptz (2013 :* 7 :* 15 :* 8 :* 15 *: 23.5))
2489−make_timestamptz(2013, 7, 15, 8, 15, 23.5)
2490−-}
2491−makeTimestamptz :: FunctionN
2492− '[ null 'PGint4, null 'PGint4, null 'PGint4
2493− , null 'PGint4, null 'PGint4, null 'PGfloat8 ]
2494− ( null 'PGtimestamptz )
2495−makeTimestamptz = unsafeFunctionN "make_timestamptz"
2496−
2497−{-|
2498−Affine space operations on time types.
2499−-}
2500−class TimeOp time diff | time -> diff where
2501− {-|
2502− >>> printSQL (makeDate (1984 :* 7 *: 3) !+ 365)
2503− (make_date(1984, 7, 3) + 365)
2504− -}
2505− (!+) :: Operator (null time) (null diff) (null time)
2506− (!+) = unsafeBinaryOp "+"
2507− {-|
2508− >>> printSQL (365 +! makeDate (1984 :* 7 *: 3))
2509− (365 + make_date(1984, 7, 3))
2510− -}
2511− (+!) :: Operator (null diff) (null time) (null time)
2512− (+!) = unsafeBinaryOp "+"
2513− {-|
2514− >>> printSQL (makeDate (1984 :* 7 *: 3) !- 365)
2515− (make_date(1984, 7, 3) - 365)
2516− -}
2517− (!-) :: Operator (null time) (null diff) (null time)
2518− (!-) = unsafeBinaryOp "-"
2519− {-|
2520− >>> printSQL (makeDate (1984 :* 7 *: 3) !-! currentDate)
2521− (make_date(1984, 7, 3) - CURRENT_DATE)
2522− -}
2523− (!-!) :: Operator (null time) (null time) (null diff)
2524− (!-!) = unsafeBinaryOp "-"
2525−instance TimeOp 'PGtimestamp 'PGinterval
2526−instance TimeOp 'PGtimestamptz 'PGinterval
2527−instance TimeOp 'PGtime 'PGinterval
2528−instance TimeOp 'PGtimetz 'PGinterval
2529−instance TimeOp 'PGinterval 'PGinterval
2530−instance TimeOp 'PGdate 'PGint4
2531−infixl 6 !+
2532−infixl 6 +!
2533−infixl 6 !-
2534−infixl 6 !-!
2535−
2536−data TimeUnit
2537− = Years | Months | Weeks | Days
2538− | Hours | Minutes | Seconds
2539− | Microseconds | Milliseconds
2540− | Decades | Centuries | Millennia
2541− deriving (Eq, Ord, Show, Read, Enum, GHC.Generic)
2542−instance SOP.Generic TimeUnit
2543−instance SOP.HasDatatypeInfo TimeUnit
2544−instance RenderSQL TimeUnit where
2545− renderSQL = \case
2546− Years -> "years"
2547− Months -> "months"
2548− Weeks -> "weeks"
2549− Days -> "days"
2550− Hours -> "hours"
2551− Minutes -> "minutes"
2552− Seconds -> "seconds"
2553− Microseconds -> "microseconds"
2554− Milliseconds -> "milliseconds"
2555− Decades -> "decades"
2556− Centuries -> "centuries"
2557− Millennia -> "millennia"
2558−
2559−-- | >>> printSQL $ interval_ 7 Days
2560−-- (INTERVAL '7.0 days')
2561−interval_ :: Double -> TimeUnit -> Expr (null 'PGinterval)
2562−interval_ num unit = UnsafeExpression . parenthesized $ "INTERVAL" <+>
2563− "'" <> fromString (show num) <+> renderSQL unit <> "'"
2564−
2565−class Literal hask where
2566− literal :: hask -> Expr (null (PG hask))
2567−instance JSON.ToJSON hask => Literal (Json hask) where
2568− literal = cast json . UnsafeExpression
2569− . singleQuotedUtf8 . toStrict . JSON.encode . getJson
2570−instance JSON.ToJSON hask => Literal (Jsonb hask) where
2571− literal = cast jsonb . UnsafeExpression
2572− . singleQuotedUtf8 . toStrict . JSON.encode . getJsonb
2573−instance Literal Char where
2574− literal chr = UnsafeExpression $
2575− "E\'" <> fromString (escape chr) <> "\'"
2576−instance Literal String where literal = fromString
2577−instance Literal Int16 where literal = fromIntegral
2578−instance Literal Int32 where literal = fromIntegral
2579−instance Literal Int64 where literal = fromIntegral
2580−instance Literal Float where literal = fromRational . toRational
2581−instance Literal Double where literal = fromRational . toRational
2582−instance Literal Text where literal = fromString . Text.unpack
2583−instance Literal Lazy.Text where literal = fromString . Lazy.Text.unpack
2584−instance ToParam (Enumerated enum) (PG (Enumerated enum))
2585− => Literal (Enumerated enum) where
2586− literal
2587− = UnsafeExpression
2588− . singleQuotedUtf8
2589− . builderBytes
2590− . unK
2591− . toParam @(Enumerated enum) @(PG (Enumerated enum))
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Aggregate.hsnew file+447
@@ -0,0 +1,447 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.Aggregate
3+Description: Aggregate functions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Aggregate functions
9+-}
10+
11+{-# LANGUAGE
12+ DataKinds
13+ , DeriveGeneric
14+ , FlexibleContexts
15+ , FlexibleInstances
16+ , FunctionalDependencies
17+ , LambdaCase
18+ , MultiParamTypeClasses
19+ , OverloadedStrings
20+ , PolyKinds
21+ , TypeFamilies
22+ , TypeOperators
23+ , UndecidableInstances
24+#-}
25+
26+module Squeal.PostgreSQL.Expression.Aggregate
27+ ( Aggregate (..)
28+ , Distinction (..)
29+ , PGAvg
30+ ) where
31+
32+import Control.DeepSeq
33+import Data.ByteString (ByteString)
34+import Data.Kind
35+import GHC.TypeLits
36+
37+import qualified GHC.Generics as GHC
38+import qualified Generics.SOP as SOP
39+
40+import Squeal.PostgreSQL.Alias
41+import Squeal.PostgreSQL.Expression
42+import Squeal.PostgreSQL.List
43+import Squeal.PostgreSQL.Render
44+import Squeal.PostgreSQL.Schema
45+
46+-- $setup
47+-- >>> import Squeal.PostgreSQL
48+
49+{- |
50+`Aggregate` functions compute a single result from a set of input values.
51+`Aggregate` functions can be used as `GroupedBy` `Expression`s as well
52+as `Squeal.PostgreSQL.Expression.Window.WindowFunction`s.
53+-}
54+class Aggregate expr1 exprN aggr
55+ | aggr -> expr1, aggr -> exprN where
56+
57+ -- | A special aggregation that does not require an input
58+ --
59+ -- >>> :{
60+ -- let
61+ -- expression :: Expression '[] commons ('Grouped bys) schemas params from ('NotNull 'PGint8)
62+ -- expression = countStar
63+ -- in printSQL expression
64+ -- :}
65+ -- count(*)
66+ countStar :: aggr ('NotNull 'PGint8)
67+
68+ -- | >>> :{
69+ -- let
70+ -- expression :: Expression '[] commons ('Grouped bys) schemas params '[tab ::: '["col" ::: null ty]] ('NotNull 'PGint8)
71+ -- expression = count (All #col)
72+ -- in printSQL expression
73+ -- :}
74+ -- count(ALL "col")
75+ count
76+ :: expr1 ty
77+ -- ^ what to count
78+ -> aggr ('NotNull 'PGint8)
79+
80+ -- | >>> :{
81+ -- let
82+ -- expression :: Expression '[] commons ('Grouped bys) schemas params '[tab ::: '["col" ::: 'Null 'PGnumeric]] ('NotNull 'PGnumeric)
83+ -- expression = sum_ (Distinct #col)
84+ -- in printSQL expression
85+ -- :}
86+ -- sum(DISTINCT "col")
87+ sum_
88+ :: ty `In` PGNum
89+ => expr1 (null ty)
90+ -> aggr ('NotNull ty)
91+
92+ -- | input values, including nulls, concatenated into an array
93+ arrayAgg
94+ :: expr1 ty
95+ -> aggr ('Null ('PGvararray ty))
96+
97+ -- | aggregates values as a JSON array
98+ jsonAgg
99+ :: expr1 ty
100+ -> aggr ('Null 'PGjson)
101+
102+ -- | aggregates values as a JSON array
103+ jsonbAgg
104+ :: expr1 ty
105+ -> aggr ('Null 'PGjsonb)
106+
107+ {- |
108+ the bitwise AND of all non-null input values, or null if none
109+
110+ >>> :{
111+ let
112+ expression :: Expression '[] commons ('Grouped bys) schemas params '[tab ::: '["col" ::: null 'PGint4]] ('Null 'PGint4)
113+ expression = bitAnd (Distinct #col)
114+ in printSQL expression
115+ :}
116+ bit_and(DISTINCT "col")
117+ -}
118+ bitAnd
119+ :: int `In` PGIntegral
120+ => expr1 (null int)
121+ -- ^ what to aggregate
122+ -> aggr ('Null int)
123+
124+ {- |
125+ the bitwise OR of all non-null input values, or null if none
126+
127+ >>> :{
128+ let
129+ expression :: Expression '[] commons ('Grouped bys) schemas params '[tab ::: '["col" ::: null 'PGint4]] ('Null 'PGint4)
130+ expression = bitOr (All #col)
131+ in printSQL expression
132+ :}
133+ bit_or(ALL "col")
134+ -}
135+ bitOr
136+ :: int `In` PGIntegral
137+ => expr1 (null int)
138+ -- ^ what to aggregate
139+ -> aggr ('Null int)
140+
141+ {- |
142+ true if all input values are true, otherwise false
143+
144+ >>> :{
145+ let
146+ winFun :: WindowFunction '[] commons 'Ungrouped schemas params '[tab ::: '["col" ::: null 'PGbool]] ('Null 'PGbool)
147+ winFun = boolAnd #col
148+ in printSQL winFun
149+ :}
150+ bool_and("col")
151+ -}
152+ boolAnd
153+ :: expr1 (null 'PGbool)
154+ -- ^ what to aggregate
155+ -> aggr ('Null 'PGbool)
156+
157+ {- |
158+ true if at least one input value is true, otherwise false
159+
160+ >>> :{
161+ let
162+ expression :: Expression '[] commons ('Grouped bys) schemas params '[tab ::: '["col" ::: null 'PGbool]] ('Null 'PGbool)
163+ expression = boolOr (All #col)
164+ in printSQL expression
165+ :}
166+ bool_or(ALL "col")
167+ -}
168+ boolOr
169+ :: expr1 (null 'PGbool)
170+ -- ^ what to aggregate
171+ -> aggr ('Null 'PGbool)
172+
173+ {- |
174+ equivalent to `boolAnd`
175+
176+ >>> :{
177+ let
178+ expression :: Expression '[] commons ('Grouped bys) schemas params '[tab ::: '["col" ::: null 'PGbool]] ('Null 'PGbool)
179+ expression = every (Distinct #col)
180+ in printSQL expression
181+ :}
182+ every(DISTINCT "col")
183+ -}
184+ every
185+ :: expr1 (null 'PGbool)
186+ -- ^ what to aggregate
187+ -> aggr ('Null 'PGbool)
188+
189+ {- |maximum value of expression across all input values-}
190+ max_
191+ :: expr1 (null ty)
192+ -- ^ what to maximize
193+ -> aggr ('Null ty)
194+
195+ -- | minimum value of expression across all input values
196+ min_
197+ :: expr1 (null ty)
198+ -- ^ what to minimize
199+ -> aggr ('Null ty)
200+
201+ -- | the average (arithmetic mean) of all input values
202+ avg
203+ :: expr1 (null ty)
204+ -- ^ what to average
205+ -> aggr ('Null (PGAvg ty))
206+
207+ {- | correlation coefficient
208+
209+ >>> :{
210+ let
211+ expression :: Expression '[] c ('Grouped g) s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGfloat8)
212+ expression = corr (All (#y *: #x))
213+ in printSQL expression
214+ :}
215+ corr(ALL "y", "x")
216+ -}
217+ corr
218+ :: exprN '[null 'PGfloat8, null 'PGfloat8]
219+ -> aggr ('Null 'PGfloat8)
220+
221+ {- | population covariance
222+
223+ >>> :{
224+ let
225+ expression :: Expression '[] c ('Grouped g) s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGfloat8)
226+ expression = covarPop (All (#y *: #x))
227+ in printSQL expression
228+ :}
229+ covar_pop(ALL "y", "x")
230+ -}
231+ covarPop
232+ :: exprN '[null 'PGfloat8, null 'PGfloat8]
233+ -> aggr ('Null 'PGfloat8)
234+
235+ {- | sample covariance
236+
237+ >>> :{
238+ let
239+ winFun :: WindowFunction '[] c 'Ungrouped s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGfloat8)
240+ winFun = covarSamp (#y *: #x)
241+ in printSQL winFun
242+ :}
243+ covar_samp("y", "x")
244+ -}
245+ covarSamp
246+ :: exprN '[null 'PGfloat8, null 'PGfloat8]
247+ -> aggr ('Null 'PGfloat8)
248+
249+ {- | average of the independent variable (sum(X)/N)
250+
251+ >>> :{
252+ let
253+ expression :: Expression '[] c ('Grouped g) s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGfloat8)
254+ expression = regrAvgX (All (#y *: #x))
255+ in printSQL expression
256+ :}
257+ regr_avgx(ALL "y", "x")
258+ -}
259+ regrAvgX
260+ :: exprN '[null 'PGfloat8, null 'PGfloat8]
261+ -> aggr ('Null 'PGfloat8)
262+
263+ {- | average of the dependent variable (sum(Y)/N)
264+
265+ >>> :{
266+ let
267+ winFun :: WindowFunction '[] c 'Ungrouped s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGfloat8)
268+ winFun = regrAvgY (#y *: #x)
269+ in printSQL winFun
270+ :}
271+ regr_avgy("y", "x")
272+ -}
273+ regrAvgY
274+ :: exprN '[null 'PGfloat8, null 'PGfloat8]
275+ -> aggr ('Null 'PGfloat8)
276+
277+ {- | number of input rows in which both expressions are nonnull
278+
279+ >>> :{
280+ let
281+ winFun :: WindowFunction '[] c 'Ungrouped s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGint8)
282+ winFun = regrCount (#y *: #x)
283+ in printSQL winFun
284+ :}
285+ regr_count("y", "x")
286+ -}
287+ regrCount
288+ :: exprN '[null 'PGfloat8, null 'PGfloat8]
289+ -> aggr ('Null 'PGint8)
290+
291+ {- | y-intercept of the least-squares-fit linear equation determined by the (X, Y) pairs
292+ >>> :{
293+ let
294+ expression :: Expression '[] c ('Grouped g) s p '[t ::: '["x" ::: 'NotNull 'PGfloat8, "y" ::: 'NotNull 'PGfloat8]] ('Null 'PGfloat8)
295+ expression = regrIntercept (All (#y *: #x))
296+ in printSQL expression
297+ :}
298+ regr_intercept(ALL "y", "x")
299+ -}
300+ regrIntercept
301+ :: exprN '[null 'PGfloat8, null 'PGfloat8]
302+ -> aggr ('Null 'PGfloat8)
303+
304+ -- | @regr_r2(Y, X)@, square of the correlation coefficient
305+ regrR2
306+ :: exprN '[null 'PGfloat8, null 'PGfloat8]
307+ -> aggr ('Null 'PGfloat8)
308+
309+ -- | @regr_slope(Y, X)@, slope of the least-squares-fit linear equation
310+ -- determined by the (X, Y) pairs
311+ regrSlope
312+ :: exprN '[null 'PGfloat8, null 'PGfloat8]
313+ -> aggr ('Null 'PGfloat8)
314+
315+ -- | @regr_sxx(Y, X)@, sum(X^2) - sum(X)^2/N
316+ -- (“sum of squares” of the independent variable)
317+ regrSxx
318+ :: exprN '[null 'PGfloat8, null 'PGfloat8]
319+ -> aggr ('Null 'PGfloat8)
320+
321+ -- | @regr_sxy(Y, X)@, sum(X*Y) - sum(X) * sum(Y)/N
322+ -- (“sum of products” of independent times dependent variable)
323+ regrSxy
324+ :: exprN '[null 'PGfloat8, null 'PGfloat8]
325+ -> aggr ('Null 'PGfloat8)
326+
327+ -- | @regr_syy(Y, X)@, sum(Y^2) - sum(Y)^2/N
328+ -- (“sum of squares” of the dependent variable)
329+ regrSyy
330+ :: exprN '[null 'PGfloat8, null 'PGfloat8]
331+ -> aggr ('Null 'PGfloat8)
332+
333+ -- | historical alias for `stddevSamp`
334+ stddev
335+ :: expr1 (null ty)
336+ -> aggr ('Null (PGAvg ty))
337+
338+ -- | population standard deviation of the input values
339+ stddevPop
340+ :: expr1 (null ty)
341+ -> aggr ('Null (PGAvg ty))
342+
343+ -- | sample standard deviation of the input values
344+ stddevSamp
345+ :: expr1 (null ty)
346+ -> aggr ('Null (PGAvg ty))
347+
348+ -- | historical alias for `varSamp`
349+ variance
350+ :: expr1 (null ty)
351+ -> aggr ('Null (PGAvg ty))
352+
353+ -- | population variance of the input values
354+ -- (square of the population standard deviation)
355+ varPop
356+ :: expr1 (null ty)
357+ -> aggr ('Null (PGAvg ty))
358+
359+ -- | sample variance of the input values
360+ -- (square of the sample standard deviation)
361+ varSamp
362+ :: expr1 (null ty)
363+ -> aggr ('Null (PGAvg ty))
364+
365+{- |
366+`Distinction`s are used for the input of `Aggregate` `Expression`s.
367+`All` invokes the aggregate once for each input row.
368+`Distinct` invokes the aggregate once for each distinct value of the expression
369+(or distinct set of values, for multiple expressions) found in the input
370+-}
371+data Distinction (expr :: kind -> Type) (ty :: kind)
372+ = All (expr ty)
373+ | Distinct (expr ty)
374+ deriving (GHC.Generic,Show,Eq,Ord)
375+instance NFData (Distinction (Expression outer commons grp schemas params from) ty)
376+instance RenderSQL (Distinction (Expression outer commons grp schemas params from) ty) where
377+ renderSQL = \case
378+ All x -> "ALL" <+> renderSQL x
379+ Distinct x -> "DISTINCT" <+> renderSQL x
380+instance SOP.SListI tys => RenderSQL
381+ (Distinction (NP (Expression outer commons grp schemas params from)) tys) where
382+ renderSQL = \case
383+ All xs -> "ALL" <+> renderCommaSeparated renderSQL xs
384+ Distinct xs -> "DISTINCT" <+> renderCommaSeparated renderSQL xs
385+
386+instance Aggregate
387+ (Distinction (Expression outer commons 'Ungrouped schemas params from))
388+ (Distinction (NP (Expression outer commons 'Ungrouped schemas params from)))
389+ (Expression outer commons ('Grouped bys) schemas params from) where
390+ countStar = UnsafeExpression "count(*)"
391+ count = unsafeAggregate1 "count"
392+ sum_ = unsafeAggregate1 "sum"
393+ arrayAgg = unsafeAggregate1 "array_agg"
394+ jsonAgg = unsafeAggregate1 "json_agg"
395+ jsonbAgg = unsafeAggregate1 "jsonb_agg"
396+ bitAnd = unsafeAggregate1 "bit_and"
397+ bitOr = unsafeAggregate1 "bit_or"
398+ boolAnd = unsafeAggregate1 "bool_and"
399+ boolOr = unsafeAggregate1 "bool_or"
400+ every = unsafeAggregate1 "every"
401+ max_ = unsafeAggregate1 "max"
402+ min_ = unsafeAggregate1 "min"
403+ avg = unsafeAggregate1 "avg"
404+ corr = unsafeAggregateN "corr"
405+ covarPop = unsafeAggregateN "covar_pop"
406+ covarSamp = unsafeAggregateN "covar_samp"
407+ regrAvgX = unsafeAggregateN "regr_avgx"
408+ regrAvgY = unsafeAggregateN "regr_avgy"
409+ regrCount = unsafeAggregateN "regr_count"
410+ regrIntercept = unsafeAggregateN "regr_intercept"
411+ regrR2 = unsafeAggregateN "regr_r2"
412+ regrSlope = unsafeAggregateN "regr_slope"
413+ regrSxx = unsafeAggregateN "regr_sxx"
414+ regrSxy = unsafeAggregateN "regr_sxy"
415+ regrSyy = unsafeAggregateN "regr_syy"
416+ stddev = unsafeAggregate1 "stddev"
417+ stddevPop = unsafeAggregate1 "stddev_pop"
418+ stddevSamp = unsafeAggregate1 "stddev_samp"
419+ variance = unsafeAggregate1 "variance"
420+ varPop = unsafeAggregate1 "var_pop"
421+ varSamp = unsafeAggregate1 "var_samp"
422+
423+-- | escape hatch to define aggregate functions
424+unsafeAggregate1
425+ :: ByteString -- ^ aggregate function
426+ -> Distinction (Expression outer commons 'Ungrouped schemas params from) x
427+ -> Expression outer commons ('Grouped bys) schemas params from y
428+unsafeAggregate1 fun x = UnsafeExpression $ fun <> parenthesized (renderSQL x)
429+
430+unsafeAggregateN
431+ :: SOP.SListI xs
432+ => ByteString -- ^ function
433+ -> Distinction (NP (Expression outer commons 'Ungrouped schemas params from)) xs
434+ -> Expression outer commons ('Grouped bys) schemas params from y
435+unsafeAggregateN fun xs = UnsafeExpression $ fun <> parenthesized (renderSQL xs)
436+
437+-- | A type family that calculates `PGAvg` type of a `PGType`.
438+type family PGAvg ty where
439+ PGAvg 'PGint2 = 'PGnumeric
440+ PGAvg 'PGint4 = 'PGnumeric
441+ PGAvg 'PGint8 = 'PGnumeric
442+ PGAvg 'PGnumeric = 'PGnumeric
443+ PGAvg 'PGfloat4 = 'PGfloat8
444+ PGAvg 'PGfloat8 = 'PGfloat8
445+ PGAvg 'PGinterval = 'PGinterval
446+ PGAvg pg = TypeError
447+ ('Text "Squeal type error: No average for " ':<>: 'ShowType pg)
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Collection.hsnew file+159
@@ -0,0 +1,159 @@
1+{-# LANGUAGE
2+ AllowAmbiguousTypes
3+ , DataKinds
4+ , FlexibleContexts
5+ , FlexibleInstances
6+ , MultiParamTypeClasses
7+ , OverloadedLabels
8+ , OverloadedStrings
9+ , RankNTypes
10+ , ScopedTypeVariables
11+ , TypeApplications
12+ , TypeFamilies
13+ , TypeOperators
14+ , UndecidableInstances
15+#-}
16+
17+module Squeal.PostgreSQL.Expression.Collection
18+ ( array
19+ , array1
20+ , array2
21+ , cardinality
22+ , index
23+ , unnest
24+ , row
25+ , field
26+ ) where
27+
28+import Data.String
29+import Data.Word (Word64)
30+
31+import qualified Generics.SOP as SOP
32+
33+import Squeal.PostgreSQL.Alias
34+import Squeal.PostgreSQL.Expression
35+import Squeal.PostgreSQL.Expression.SetOf
36+import Squeal.PostgreSQL.List
37+import Squeal.PostgreSQL.Render
38+import Squeal.PostgreSQL.Schema
39+
40+-- $setup
41+-- >>> import Squeal.PostgreSQL
42+
43+-- | >>> printSQL $ array [null_, false, true]
44+-- ARRAY[NULL, FALSE, TRUE]
45+array
46+ :: [Expression outer commons grp schemas params from ty]
47+ -- ^ array elements
48+ -> Expression outer commons grp schemas params from (null ('PGvararray ty))
49+array xs = UnsafeExpression $ "ARRAY" <>
50+ bracketed (commaSeparated (renderSQL <$> xs))
51+
52+{- | construct a 1-dimensional fixed length array
53+
54+>>> printSQL $ array1 (null_ :* false *: true)
55+ARRAY[NULL, FALSE, TRUE]
56+
57+>>> :type array1 (null_ :* false *: true)
58+array1 (null_ :* false *: true)
59+ :: Expression
60+ outer
61+ commons
62+ grp
63+ schemas
64+ params
65+ from
66+ (null ('PGfixarray '[3] ('Null 'PGbool)))
67+-}
68+array1
69+ :: (n ~ Length tys, SOP.All ((~) ty) tys)
70+ => NP (Expression outer commons grp schemas params from) tys
71+ -> Expression outer commons grp schemas params from (null ('PGfixarray '[n] ty))
72+array1 xs = UnsafeExpression $ "ARRAY" <>
73+ bracketed (renderCommaSeparated renderSQL xs)
74+
75+{- | construct a 2-dimensional fixed length array
76+
77+>>> printSQL $ array2 ((null_ :* false *: true) *: (false :* null_ *: true))
78+ARRAY[[NULL, FALSE, TRUE], [FALSE, NULL, TRUE]]
79+
80+>>> :type array2 ((null_ :* false *: true) *: (false :* null_ *: true))
81+array2 ((null_ :* false *: true) *: (false :* null_ *: true))
82+ :: Expression
83+ outer
84+ commons
85+ grp
86+ schemas
87+ params
88+ from
89+ (null ('PGfixarray '[2, 3] ('Null 'PGbool)))
90+-}
91+array2
92+ :: ( SOP.All ((~) tys) tyss
93+ , SOP.All SOP.SListI tyss
94+ , Length tyss ~ n1
95+ , SOP.All ((~) ty) tys
96+ , Length tys ~ n2 )
97+ => NP (NP (Expression outer commons grp schemas params from)) tyss
98+ -> Expression outer commons grp schemas params from (null ('PGfixarray '[n1,n2] ty))
99+array2 xss = UnsafeExpression $ "ARRAY" <>
100+ bracketed (renderCommaSeparatedConstraint @SOP.SListI (bracketed . renderCommaSeparated renderSQL) xss)
101+
102+-- | >>> printSQL $ cardinality (array [null_, false, true])
103+-- cardinality(ARRAY[NULL, FALSE, TRUE])
104+cardinality :: null ('PGvararray ty) :--> null 'PGint8
105+cardinality = unsafeFunction "cardinality"
106+
107+-- | >>> printSQL $ array [null_, false, true] & index 2
108+-- (ARRAY[NULL, FALSE, TRUE])[2]
109+index
110+ :: Word64 -- ^ index
111+ -> null ('PGvararray ty) :--> NullifyType ty
112+index n expr = UnsafeExpression $
113+ parenthesized (renderSQL expr) <> "[" <> fromString (show n) <> "]"
114+
115+-- | Expand an array to a set of rows
116+unnest :: SetOfFunction "unnest" (null ('PGvararray ty)) '["unnest" ::: ty]
117+unnest = unsafeSetOfFunction
118+
119+-- | A row constructor is an expression that builds a row value
120+-- (also called a composite value) using values for its member fields.
121+--
122+-- >>> :{
123+-- type Complex = 'PGcomposite
124+-- '[ "real" ::: 'NotNull 'PGfloat8
125+-- , "imaginary" ::: 'NotNull 'PGfloat8 ]
126+-- :}
127+--
128+-- >>> let i = row (0 `as` #real :* 1 `as` #imaginary) :: Expression outer commons grp schemas params from ('NotNull Complex)
129+-- >>> printSQL i
130+-- ROW(0, 1)
131+row
132+ :: SOP.SListI row
133+ => NP (Aliased (Expression outer commons grp schemas params from)) row
134+ -- ^ zero or more expressions for the row field values
135+ -> Expression outer commons grp schemas params from (null ('PGcomposite row))
136+row exprs = UnsafeExpression $ "ROW" <> parenthesized
137+ (renderCommaSeparated (\ (expr `As` _) -> renderSQL expr) exprs)
138+
139+-- | >>> :{
140+-- type Complex = 'PGcomposite
141+-- '[ "real" ::: 'NotNull 'PGfloat8
142+-- , "imaginary" ::: 'NotNull 'PGfloat8 ]
143+-- type Schema = '["complex" ::: 'Typedef Complex]
144+-- :}
145+--
146+-- >>> let i = row (0 `as` #real :* 1 `as` #imaginary) :: Expression outer '[] grp (Public Schema) from params ('NotNull Complex)
147+-- >>> printSQL $ i & field #complex #imaginary
148+-- (ROW(0, 1)::"complex")."imaginary"
149+field
150+ :: ( Has sch schemas schema
151+ , Has tydef schema ('Typedef ('PGcomposite row))
152+ , Has field row ty)
153+ => QualifiedAlias sch tydef -- ^ row type
154+ -> Alias field -- ^ field name
155+ -> Expression outer commons grp schemas params from ('NotNull ('PGcomposite row))
156+ -> Expression outer commons grp schemas params from ty
157+field td fld expr = UnsafeExpression $
158+ parenthesized (renderSQL expr <> "::" <> renderSQL td)
159+ <> "." <> renderSQL fld
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Comparison.hsnew file+206
@@ -0,0 +1,206 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.Comparison
3+Description: Comparison expressions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Comparison functions and operators
9+-}
10+
11+{-# LANGUAGE
12+ OverloadedStrings
13+ , RankNTypes
14+ , TypeInType
15+ , TypeOperators
16+#-}
17+
18+module Squeal.PostgreSQL.Expression.Comparison
19+ ( (.==)
20+ , (./=)
21+ , (.>=)
22+ , (.<)
23+ , (.<=)
24+ , (.>)
25+ , greatest
26+ , least
27+ , BetweenExpr
28+ , between
29+ , notBetween
30+ , betweenSymmetric
31+ , notBetweenSymmetric
32+ , isDistinctFrom
33+ , isNotDistinctFrom
34+ , isTrue
35+ , isNotTrue
36+ , isFalse
37+ , isNotFalse
38+ , isUnknown
39+ , isNotUnknown
40+ ) where
41+
42+import Data.ByteString
43+
44+import Squeal.PostgreSQL.Expression
45+import Squeal.PostgreSQL.Expression.Logic
46+import Squeal.PostgreSQL.Render
47+import Squeal.PostgreSQL.Schema
48+
49+-- $setup
50+-- >>> import Squeal.PostgreSQL
51+
52+-- | Comparison operations like `.==`, `./=`, `.>`, `.>=`, `.<` and `.<=`
53+-- will produce @NULL@s if one of their arguments is @NULL@.
54+--
55+-- >>> printSQL $ true .== null_
56+-- (TRUE = NULL)
57+(.==) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
58+(.==) = unsafeBinaryOp "="
59+infix 4 .==
60+
61+-- | >>> printSQL $ true ./= null_
62+-- (TRUE <> NULL)
63+(./=) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
64+(./=) = unsafeBinaryOp "<>"
65+infix 4 ./=
66+
67+-- | >>> printSQL $ true .>= null_
68+-- (TRUE >= NULL)
69+(.>=) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
70+(.>=) = unsafeBinaryOp ">="
71+infix 4 .>=
72+
73+-- | >>> printSQL $ true .< null_
74+-- (TRUE < NULL)
75+(.<) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
76+(.<) = unsafeBinaryOp "<"
77+infix 4 .<
78+
79+-- | >>> printSQL $ true .<= null_
80+-- (TRUE <= NULL)
81+(.<=) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
82+(.<=) = unsafeBinaryOp "<="
83+infix 4 .<=
84+
85+-- | >>> printSQL $ true .> null_
86+-- (TRUE > NULL)
87+(.>) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
88+(.>) = unsafeBinaryOp ">"
89+infix 4 .>
90+
91+-- | >>> let expr = greatest [param @1] currentTimestamp :: Expression outer commons grp schemas '[ 'NotNull 'PGtimestamptz] from ('NotNull 'PGtimestamptz)
92+-- >>> printSQL expr
93+-- GREATEST(($1 :: timestamp with time zone), CURRENT_TIMESTAMP)
94+greatest :: FunctionVar ty ty ty
95+greatest = unsafeFunctionVar "GREATEST"
96+
97+-- | >>> printSQL $ least [null_] currentTimestamp
98+-- LEAST(NULL, CURRENT_TIMESTAMP)
99+least :: FunctionVar ty ty ty
100+least = unsafeFunctionVar "LEAST"
101+
102+{- |
103+A @RankNType@ for comparison expressions like `between`.
104+-}
105+type BetweenExpr
106+ = forall outer commons grp schemas params from ty
107+ . Expression outer commons grp schemas params from ty
108+ -> ( Expression outer commons grp schemas params from ty
109+ , Expression outer commons grp schemas params from ty ) -- ^ bounds
110+ -> Condition outer commons grp schemas params from
111+
112+unsafeBetweenExpr :: ByteString -> BetweenExpr
113+unsafeBetweenExpr fun a (x,y) = UnsafeExpression $
114+ renderSQL a <+> fun <+> renderSQL x <+> "AND" <+> renderSQL y
115+
116+{- | >>> printSQL $ true `between` (null_, false)
117+TRUE BETWEEN NULL AND FALSE
118+-}
119+between :: BetweenExpr
120+between = unsafeBetweenExpr "BETWEEN"
121+
122+{- | >>> printSQL $ true `notBetween` (null_, false)
123+TRUE NOT BETWEEN NULL AND FALSE
124+-}
125+notBetween :: BetweenExpr
126+notBetween = unsafeBetweenExpr "NOT BETWEEN"
127+
128+{- | between, after sorting the comparison values
129+
130+>>> printSQL $ true `betweenSymmetric` (null_, false)
131+TRUE BETWEEN SYMMETRIC NULL AND FALSE
132+-}
133+betweenSymmetric :: BetweenExpr
134+betweenSymmetric = unsafeBetweenExpr "BETWEEN SYMMETRIC"
135+
136+{- | not between, after sorting the comparison values
137+
138+>>> printSQL $ true `notBetweenSymmetric` (null_, false)
139+TRUE NOT BETWEEN SYMMETRIC NULL AND FALSE
140+-}
141+notBetweenSymmetric :: BetweenExpr
142+notBetweenSymmetric = unsafeBetweenExpr "NOT BETWEEN SYMMETRIC"
143+
144+{- | not equal, treating null like an ordinary value
145+
146+>>> printSQL $ true `isDistinctFrom` null_
147+(TRUE IS DISTINCT FROM NULL)
148+-}
149+isDistinctFrom :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
150+isDistinctFrom = unsafeBinaryOp "IS DISTINCT FROM"
151+
152+{- | equal, treating null like an ordinary value
153+
154+>>> printSQL $ true `isNotDistinctFrom` null_
155+(TRUE IS NOT DISTINCT FROM NULL)
156+-}
157+isNotDistinctFrom :: Operator (null0 ty) (null1 ty) ('NotNull 'PGbool)
158+isNotDistinctFrom = unsafeBinaryOp "IS NOT DISTINCT FROM"
159+
160+{- | is true
161+
162+>>> printSQL $ true & isTrue
163+(TRUE IS TRUE)
164+-}
165+isTrue :: null0 'PGbool :--> null1 'PGbool
166+isTrue = unsafeUnaryOpR "IS TRUE"
167+
168+{- | is false or unknown
169+
170+>>> printSQL $ true & isNotTrue
171+(TRUE IS NOT TRUE)
172+-}
173+isNotTrue :: null0 'PGbool :--> null1 'PGbool
174+isNotTrue = unsafeUnaryOpR "IS NOT TRUE"
175+
176+{- | is false
177+
178+>>> printSQL $ true & isFalse
179+(TRUE IS FALSE)
180+-}
181+isFalse :: null0 'PGbool :--> null1 'PGbool
182+isFalse = unsafeUnaryOpR "IS FALSE"
183+
184+{- | is true or unknown
185+
186+>>> printSQL $ true & isNotFalse
187+(TRUE IS NOT FALSE)
188+-}
189+isNotFalse :: null0 'PGbool :--> null1 'PGbool
190+isNotFalse = unsafeUnaryOpR "IS NOT FALSE"
191+
192+{- | is unknown
193+
194+>>> printSQL $ true & isUnknown
195+(TRUE IS UNKNOWN)
196+-}
197+isUnknown :: null0 'PGbool :--> null1 'PGbool
198+isUnknown = unsafeUnaryOpR "IS UNKNOWN"
199+
200+{- | is true or false
201+
202+>>> printSQL $ true & isNotUnknown
203+(TRUE IS NOT UNKNOWN)
204+-}
205+isNotUnknown :: null0 'PGbool :--> null1 'PGbool
206+isNotUnknown = unsafeUnaryOpR "IS NOT UNKNOWN"
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Json.hsnew file+528
@@ -0,0 +1,528 @@
1+{-# LANGUAGE
2+ DataKinds
3+ , FlexibleContexts
4+ , FlexibleInstances
5+ , GADTs
6+ , OverloadedLabels
7+ , OverloadedStrings
8+ , PolyKinds
9+ , RankNTypes
10+ , ScopedTypeVariables
11+ , TypeApplications
12+ , TypeOperators
13+ , UndecidableInstances
14+#-}
15+
16+module Squeal.PostgreSQL.Expression.Json
17+ ( -- * Json and Jsonb operators
18+ (.->)
19+ , (.->>)
20+ , (.#>)
21+ , (.#>>)
22+ -- * Jsonb operators
23+ , (.?)
24+ , (.?|)
25+ , (.?&)
26+ , (.-.)
27+ , (#-.)
28+ -- * Json and Jsonb functions
29+ , toJson
30+ , toJsonb
31+ , arrayToJson
32+ , rowToJson
33+ , jsonBuildArray
34+ , jsonbBuildArray
35+ , PGBuildObject
36+ , jsonBuildObject
37+ , jsonbBuildObject
38+ , jsonObject
39+ , jsonbObject
40+ , jsonZipObject
41+ , jsonbZipObject
42+ , jsonArrayLength
43+ , jsonbArrayLength
44+ , jsonExtractPath
45+ , jsonbExtractPath
46+ , jsonExtractPathAsText
47+ , jsonbExtractPathAsText
48+ , jsonTypeof
49+ , jsonbTypeof
50+ , jsonStripNulls
51+ , jsonbStripNulls
52+ , jsonbSet
53+ , jsonbInsert
54+ , jsonbPretty
55+ -- * Json and Jsonb set returning functions
56+ , jsonEach
57+ , jsonbEach
58+ , jsonEachText
59+ , jsonbEachText
60+ , jsonObjectKeys
61+ , jsonbObjectKeys
62+ , jsonPopulateRecord
63+ , jsonbPopulateRecord
64+ , jsonPopulateRecordSet
65+ , jsonbPopulateRecordSet
66+ , jsonToRecord
67+ , jsonbToRecord
68+ , jsonToRecordSet
69+ , jsonbToRecordSet
70+ ) where
71+
72+import Data.ByteString (ByteString)
73+import GHC.TypeLits
74+
75+import Squeal.PostgreSQL.Alias
76+import Squeal.PostgreSQL.Expression
77+import Squeal.PostgreSQL.Expression.SetOf
78+import Squeal.PostgreSQL.Expression.Type
79+import Squeal.PostgreSQL.List
80+import Squeal.PostgreSQL.Query
81+import Squeal.PostgreSQL.Render
82+import Squeal.PostgreSQL.Schema
83+
84+import qualified Generics.SOP as SOP
85+
86+-- $setup
87+-- >>> import Squeal.PostgreSQL
88+-- >>> import Data.Aeson
89+
90+{-----------------------------------------
91+ -- json and jsonb support
92+
93+See https://www.postgresql.org/docs/10/static/functions-json.html -- most
94+comments lifted directly from this page.
95+
96+Table 9.44: json and jsonb operators
97+-----------------------------------------}
98+
99+-- | Get JSON value (object field or array element) at a key.
100+(.->)
101+ :: (json `In` PGJsonType, key `In` PGJsonKey)
102+ => Operator (null json) (null key) ('Null json)
103+infixl 8 .->
104+(.->) = unsafeBinaryOp "->"
105+
106+-- | Get JSON value (object field or array element) at a key, as text.
107+(.->>)
108+ :: (json `In` PGJsonType, key `In` PGJsonKey)
109+ => Operator (null json) (null key) ('Null 'PGtext)
110+infixl 8 .->>
111+(.->>) = unsafeBinaryOp "->>"
112+
113+-- | Get JSON value at a specified path.
114+(.#>)
115+ :: json `In` PGJsonType
116+ => Operator (null json) (null ('PGvararray ('NotNull 'PGtext))) ('Null json)
117+infixl 8 .#>
118+(.#>) = unsafeBinaryOp "#>"
119+
120+-- | Get JSON value at a specified path as text.
121+(.#>>)
122+ :: json `In` PGJsonType
123+ => Operator (null json) (null ('PGvararray ('NotNull 'PGtext))) ('Null 'PGtext)
124+infixl 8 .#>>
125+(.#>>) = unsafeBinaryOp "#>>"
126+
127+-- Additional jsonb operators
128+
129+-- | Does the string exist as a top-level key within the JSON value?
130+(.?) :: Operator (null 'PGjsonb) (null 'PGtext) ('Null 'PGbool)
131+infixl 9 .?
132+(.?) = unsafeBinaryOp "?"
133+
134+-- | Do any of these array strings exist as top-level keys?
135+(.?|) :: Operator
136+ (null 'PGjsonb)
137+ (null ('PGvararray ('NotNull 'PGtext)))
138+ ('Null 'PGbool)
139+infixl 9 .?|
140+(.?|) = unsafeBinaryOp "?|"
141+
142+-- | Do all of these array strings exist as top-level keys?
143+(.?&) :: Operator
144+ (null 'PGjsonb)
145+ (null ('PGvararray ('NotNull 'PGtext)))
146+ ('Null 'PGbool)
147+infixl 9 .?&
148+(.?&) = unsafeBinaryOp "?&"
149+
150+-- | Delete a key or keys from a JSON object, or remove an array element.
151+--
152+-- If the right operand is..
153+--
154+-- @ text @: Delete key/value pair or string element from left operand. Key/value pairs
155+-- are matched based on their key value.
156+--
157+-- @ text[] @: Delete multiple key/value pairs or string elements
158+-- from left operand. Key/value pairs are matched based on their key value.
159+--
160+-- @ integer @: Delete the array element with specified index (Negative integers
161+-- count from the end). Throws an error if top level container is not an array.
162+(.-.)
163+ :: (key `In` '[ 'PGtext, 'PGvararray ('NotNull 'PGtext), 'PGint4, 'PGint2 ]) -- hlint error without parens here
164+ => Operator (null 'PGjsonb) (null key) (null 'PGjsonb)
165+infixl 6 .-.
166+(.-.) = unsafeBinaryOp "-"
167+
168+-- | Delete the field or element with specified path (for JSON arrays, negative
169+-- integers count from the end)
170+(#-.) :: Operator (null 'PGjsonb) (null ('PGvararray ('NotNull 'PGtext))) (null 'PGjsonb)
171+infixl 6 #-.
172+(#-.) = unsafeBinaryOp "#-"
173+
174+{-----------------------------------------
175+Table 9.45: JSON creation functions
176+-----------------------------------------}
177+
178+-- | Returns the value as json. Arrays and composites are converted
179+-- (recursively) to arrays and objects; otherwise, if there is a cast from the
180+-- type to json, the cast function will be used to perform the conversion;
181+-- otherwise, a scalar value is produced. For any scalar type other than a
182+-- number, a Boolean, or a null value, the text representation will be used, in
183+-- such a fashion that it is a valid json value.
184+toJson :: null ty :--> null 'PGjson
185+toJson = unsafeFunction "to_json"
186+
187+-- | Returns the value as jsonb. Arrays and composites are converted
188+-- (recursively) to arrays and objects; otherwise, if there is a cast from the
189+-- type to json, the cast function will be used to perform the conversion;
190+-- otherwise, a scalar value is produced. For any scalar type other than a
191+-- number, a Boolean, or a null value, the text representation will be used, in
192+-- such a fashion that it is a valid jsonb value.
193+toJsonb :: null ty :--> null 'PGjsonb
194+toJsonb = unsafeFunction "to_jsonb"
195+
196+-- | Returns the array as a JSON array. A PostgreSQL multidimensional array
197+-- becomes a JSON array of arrays.
198+arrayToJson :: null ('PGvararray ty) :--> null 'PGjson
199+arrayToJson = unsafeFunction "array_to_json"
200+
201+-- | Returns the row as a JSON object.
202+rowToJson :: null ('PGcomposite ty) :--> null 'PGjson
203+rowToJson = unsafeFunction "row_to_json"
204+
205+-- | Builds a possibly-heterogeneously-typed JSON array out of a variadic
206+-- argument list.
207+jsonBuildArray :: SOP.SListI tuple => FunctionN tuple (null 'PGjson)
208+jsonBuildArray = unsafeFunctionN "json_build_array"
209+
210+-- | Builds a possibly-heterogeneously-typed (binary) JSON array out of a
211+-- variadic argument list.
212+jsonbBuildArray :: SOP.SListI tuple => FunctionN tuple (null 'PGjsonb)
213+jsonbBuildArray = unsafeFunctionN "jsonb_build_array"
214+
215+class PGBuildObject tys where
216+instance PGBuildObject '[]
217+instance (PGBuildObject tys, key `In` PGJsonKey)
218+ => PGBuildObject ('NotNull key ': value ': tys)
219+
220+-- | Builds a possibly-heterogeneously-typed JSON object out of a variadic
221+-- argument list. The elements of the argument list must alternate between text
222+-- and values.
223+jsonBuildObject
224+ :: (SOP.SListI elems, PGBuildObject elems)
225+ => FunctionN elems (null 'PGjson)
226+jsonBuildObject = unsafeFunctionN "json_build_object"
227+
228+-- | Builds a possibly-heterogeneously-typed (binary) JSON object out of a
229+-- variadic argument list. The elements of the argument list must alternate
230+-- between keys and values.
231+jsonbBuildObject
232+ :: (SOP.SListI elems, PGBuildObject elems)
233+ => FunctionN elems (null 'PGjsonb)
234+jsonbBuildObject = unsafeFunctionN "jsonb_build_object"
235+
236+-- | Builds a JSON object out of a text array.
237+-- The array must have two dimensions
238+-- such that each inner array has exactly two elements,
239+-- which are taken as a key/value pair.
240+jsonObject
241+ :: null ('PGfixarray '[n,2] ('NotNull 'PGtext))
242+ :--> null 'PGjson
243+jsonObject = unsafeFunction "json_object"
244+
245+-- | Builds a binary JSON object out of a text array.
246+-- The array must have two dimensions
247+-- such that each inner array has exactly two elements,
248+-- which are taken as a key/value pair.
249+jsonbObject
250+ :: null ('PGfixarray '[n,2] ('NotNull 'PGtext))
251+ :--> null 'PGjsonb
252+jsonbObject = unsafeFunction "jsonb_object"
253+
254+-- | This is an alternate form of 'jsonObject' that takes two arrays; one for
255+-- keys and one for values, that are zipped pairwise to create a JSON object.
256+jsonZipObject :: FunctionN
257+ '[ null ('PGvararray ('NotNull 'PGtext))
258+ , null ('PGvararray ('NotNull 'PGtext)) ]
259+ ( null 'PGjson )
260+jsonZipObject = unsafeFunctionN "json_object"
261+
262+-- | This is an alternate form of 'jsonbObject' that takes two arrays; one for
263+-- keys and one for values, that are zipped pairwise to create a binary JSON
264+-- object.
265+jsonbZipObject :: FunctionN
266+ '[ null ('PGvararray ('NotNull 'PGtext))
267+ , null ('PGvararray ('NotNull 'PGtext)) ]
268+ ( null 'PGjsonb )
269+jsonbZipObject = unsafeFunctionN "jsonb_object"
270+
271+{-----------------------------------------
272+Table 9.46: JSON processing functions
273+-----------------------------------------}
274+
275+-- | Returns the number of elements in the outermost JSON array.
276+jsonArrayLength :: null 'PGjson :--> null 'PGint4
277+jsonArrayLength = unsafeFunction "json_array_length"
278+
279+-- | Returns the number of elements in the outermost binary JSON array.
280+jsonbArrayLength :: null 'PGjsonb :--> null 'PGint4
281+jsonbArrayLength = unsafeFunction "jsonb_array_length"
282+
283+-- | Returns JSON value pointed to by the given path (equivalent to #>
284+-- operator).
285+jsonExtractPath
286+ :: SOP.SListI elems
287+ => Expression outer commons grp schemas params from (null 'PGjson)
288+ -> NP (Expression outer commons grp schemas params from) elems
289+ -> Expression outer commons grp schemas params from (null 'PGjsonb)
290+jsonExtractPath x xs =
291+ unsafeFunctionN "json_extract_path" (x :* xs)
292+
293+-- | Returns JSON value pointed to by the given path (equivalent to #>
294+-- operator).
295+jsonbExtractPath
296+ :: SOP.SListI elems
297+ => Expression outer commons grp schemas params from (null 'PGjsonb)
298+ -> NP (Expression outer commons grp schemas params from) elems
299+ -> Expression outer commons grp schemas params from (null 'PGjsonb)
300+jsonbExtractPath x xs =
301+ unsafeFunctionN "jsonb_extract_path" (x :* xs)
302+
303+-- | Returns JSON value pointed to by the given path (equivalent to #>
304+-- operator), as text.
305+jsonExtractPathAsText
306+ :: SOP.SListI elems
307+ => Expression outer commons grp schemas params from (null 'PGjson)
308+ -> NP (Expression outer commons grp schemas params from) elems
309+ -> Expression outer commons grp schemas params from (null 'PGjson)
310+jsonExtractPathAsText x xs =
311+ unsafeFunctionN "json_extract_path_text" (x :* xs)
312+
313+-- | Returns JSON value pointed to by the given path (equivalent to #>
314+-- operator), as text.
315+jsonbExtractPathAsText
316+ :: SOP.SListI elems
317+ => Expression outer commons grp schemas params from (null 'PGjsonb)
318+ -> NP (Expression outer commons grp schemas params from) elems
319+ -> Expression outer commons grp schemas params from (null 'PGjsonb)
320+jsonbExtractPathAsText x xs =
321+ unsafeFunctionN "jsonb_extract_path_text" (x :* xs)
322+
323+-- | Returns the type of the outermost JSON value as a text string. Possible
324+-- types are object, array, string, number, boolean, and null.
325+jsonTypeof :: null 'PGjson :--> null 'PGtext
326+jsonTypeof = unsafeFunction "json_typeof"
327+
328+-- | Returns the type of the outermost binary JSON value as a text string.
329+-- Possible types are object, array, string, number, boolean, and null.
330+jsonbTypeof :: null 'PGjsonb :--> null 'PGtext
331+jsonbTypeof = unsafeFunction "jsonb_typeof"
332+
333+-- | Returns its argument with all object fields that have null values omitted.
334+-- Other null values are untouched.
335+jsonStripNulls :: null 'PGjson :--> null 'PGjson
336+jsonStripNulls = unsafeFunction "json_strip_nulls"
337+
338+-- | Returns its argument with all object fields that have null values omitted.
339+-- Other null values are untouched.
340+jsonbStripNulls :: null 'PGjsonb :--> null 'PGjsonb
341+jsonbStripNulls = unsafeFunction "jsonb_strip_nulls"
342+
343+-- | @ jsonbSet target path new_value create_missing @
344+--
345+-- Returns target with the section designated by path replaced by new_value,
346+-- or with new_value added if create_missing is true ( default is true) and the
347+-- item designated by path does not exist. As with the path orientated
348+-- operators, negative integers that appear in path count from the end of JSON
349+-- arrays.
350+jsonbSet
351+ :: Expression outer commons grp schemas params from (null 'PGjsonb)
352+ -> Expression outer commons grp schemas params from (null ('PGvararray ('NotNull 'PGtext)))
353+ -> Expression outer commons grp schemas params from (null 'PGjsonb)
354+ -> Maybe (Expression outer commons grp schemas params from (null 'PGbool))
355+ -> Expression outer commons grp schemas params from (null 'PGjsonb)
356+jsonbSet tgt path val createMissing = case createMissing of
357+ Just m -> unsafeFunctionN "jsonb_set" (tgt :* path :* val :* m :* Nil)
358+ Nothing -> unsafeFunctionN "jsonb_set" (tgt :* path :* val :* Nil)
359+
360+-- | @ jsonbInsert target path new_value insert_after @
361+--
362+-- Returns target with new_value inserted. If target section designated by
363+-- path is in a JSONB array, new_value will be inserted before target or after
364+-- if insert_after is true (default is false). If target section designated by
365+-- path is in JSONB object, new_value will be inserted only if target does not
366+-- exist. As with the path orientated operators, negative integers that appear
367+-- in path count from the end of JSON arrays.
368+jsonbInsert
369+ :: Expression outer commons grp schemas params from (null 'PGjsonb)
370+ -> Expression outer commons grp schemas params from (null ('PGvararray ('NotNull 'PGtext)))
371+ -> Expression outer commons grp schemas params from (null 'PGjsonb)
372+ -> Maybe (Expression outer commons grp schemas params from (null 'PGbool))
373+ -> Expression outer commons grp schemas params from (null 'PGjsonb)
374+jsonbInsert tgt path val insertAfter = case insertAfter of
375+ Just i -> unsafeFunctionN "jsonb_insert" (tgt :* path :* val :* i :* Nil)
376+ Nothing -> unsafeFunctionN "jsonb_insert" (tgt :* path :* val :* Nil)
377+
378+-- | Returns its argument as indented JSON text.
379+jsonbPretty :: null 'PGjsonb :--> null 'PGtext
380+jsonbPretty = unsafeFunction "jsonb_pretty"
381+
382+
383+{- | Expands the outermost JSON object into a set of key/value pairs.
384+
385+>>> printSQL (select Star (from (jsonEach (literal (Json (object ["a" .= "foo", "b" .= "bar"]))))))
386+SELECT * FROM json_each(('{"a":"foo","b":"bar"}' :: json))
387+-}
388+jsonEach :: SetOfFunction "json_each" (null 'PGjson)
389+ '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGjson]
390+jsonEach = unsafeSetOfFunction
391+
392+{- | Expands the outermost binary JSON object into a set of key/value pairs.
393+
394+>>> printSQL (select Star (from (jsonbEach (literal (Jsonb (object ["a" .= "foo", "b" .= "bar"]))))))
395+SELECT * FROM jsonb_each(('{"a":"foo","b":"bar"}' :: jsonb))
396+-}
397+jsonbEach
398+ :: SetOfFunction "jsonb_each" (nullity 'PGjsonb)
399+ '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGjson]
400+jsonbEach = unsafeSetOfFunction
401+
402+{- | Expands the outermost JSON object into a set of key/value pairs.
403+
404+>>> printSQL (select Star (from (jsonEachText (literal (Json (object ["a" .= "foo", "b" .= "bar"]))))))
405+SELECT * FROM json_each_text(('{"a":"foo","b":"bar"}' :: json))
406+-}
407+jsonEachText
408+ :: SetOfFunction "json_each_text" (null 'PGjson)
409+ '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGtext]
410+jsonEachText = unsafeSetOfFunction
411+
412+{- | Expands the outermost binary JSON object into a set of key/value pairs.
413+
414+>>> printSQL (select Star (from (jsonbEachText (literal (Jsonb (object ["a" .= "foo", "b" .= "bar"]))))))
415+SELECT * FROM jsonb_each_text(('{"a":"foo","b":"bar"}' :: jsonb))
416+-}
417+jsonbEachText
418+ :: SetOfFunction "jsonb_each_text" (null 'PGjsonb)
419+ '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGtext]
420+jsonbEachText = unsafeSetOfFunction
421+
422+{- | Returns set of keys in the outermost JSON object.
423+
424+>>> printSQL (jsonObjectKeys (literal (Json (object ["a" .= "foo", "b" .= "bar"]))))
425+json_object_keys(('{"a":"foo","b":"bar"}' :: json))
426+-}
427+jsonObjectKeys
428+ :: SetOfFunction "json_object_keys" (nullity 'PGjson)
429+ '["json_object_keys" ::: 'NotNull 'PGtext]
430+jsonObjectKeys = unsafeSetOfFunction
431+
432+{- | Returns set of keys in the outermost JSON object.
433+
434+>>> printSQL (jsonbObjectKeys (literal (Jsonb (object ["a" .= "foo", "b" .= "bar"]))))
435+jsonb_object_keys(('{"a":"foo","b":"bar"}' :: jsonb))
436+-}
437+jsonbObjectKeys
438+ :: SetOfFunction "jsonb_object_keys" (null 'PGjsonb)
439+ '["jsonb_object_keys" ::: 'NotNull 'PGtext]
440+jsonbObjectKeys = unsafeSetOfFunction
441+
442+unsafePopulateFunction
443+ :: forall fun schemas null outer commons params ty row
444+ . KnownSymbol fun
445+ => Alias fun
446+ -> TypeExpression schemas (null ('PGcomposite row))
447+ -> Expression outer commons 'Ungrouped schemas params '[] ty
448+ -> FromClause outer commons schemas params '[fun ::: row]
449+unsafePopulateFunction _fun ty expr = UnsafeFromClause $ renderSymbol @fun
450+ <> parenthesized ("null::" <> renderSQL ty <> ", " <> renderSQL expr)
451+
452+-- | Expands the JSON expression to a row whose columns match the record
453+-- type defined by the given table.
454+jsonPopulateRecord
455+ :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
456+ -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
457+ -> FromClause outer commons schemas params '["json_populate_record" ::: row]
458+jsonPopulateRecord = unsafePopulateFunction #json_populate_record
459+
460+-- | Expands the binary JSON expression to a row whose columns match the record
461+-- type defined by the given table.
462+jsonbPopulateRecord
463+ :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
464+ -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
465+ -> FromClause outer commons schemas params '["jsonb_populate_record" ::: row]
466+jsonbPopulateRecord = unsafePopulateFunction #jsonb_populate_record
467+
468+-- | Expands the outermost array of objects in the given JSON expression to a
469+-- set of rows whose columns match the record type defined by the given table.
470+jsonPopulateRecordSet
471+ :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
472+ -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json array
473+ -> FromClause outer commons schemas params '["json_populate_record_set" ::: row]
474+jsonPopulateRecordSet = unsafePopulateFunction #json_populate_record_set
475+
476+-- | Expands the outermost array of objects in the given binary JSON expression
477+-- to a set of rows whose columns match the record type defined by the given
478+-- table.
479+jsonbPopulateRecordSet
480+ :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
481+ -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb array
482+ -> FromClause outer commons schemas params '["jsonb_populate_record_set" ::: row]
483+jsonbPopulateRecordSet = unsafePopulateFunction #jsonb_populate_record_set
484+
485+unsafeRecordFunction
486+ :: (SOP.SListI record, json `In` PGJsonType)
487+ => ByteString
488+ -> Expression outer commons 'Ungrouped schemas params '[] (nullity json)
489+ -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
490+ -> FromClause outer commons schemas params '[tab ::: record]
491+unsafeRecordFunction fun expr (types `As` tab) = UnsafeFromClause $
492+ fun <> parenthesized (renderSQL expr) <+> "AS" <+> renderSQL tab
493+ <> parenthesized (renderCommaSeparated renderTy types)
494+ where
495+ renderTy :: Aliased (TypeExpression schemas) ty -> ByteString
496+ renderTy (ty `As` alias) = renderSQL alias <+> renderSQL ty
497+
498+-- | Builds an arbitrary record from a JSON object.
499+jsonToRecord
500+ :: SOP.SListI record
501+ => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
502+ -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
503+ -> FromClause outer commons schemas params '[tab ::: record]
504+jsonToRecord = unsafeRecordFunction "json_to_record"
505+
506+-- | Builds an arbitrary record from a binary JSON object.
507+jsonbToRecord
508+ :: SOP.SListI record
509+ => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
510+ -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
511+ -> FromClause outer commons schemas params '[tab ::: record]
512+jsonbToRecord = unsafeRecordFunction "jsonb_to_record"
513+
514+-- | Builds an arbitrary set of records from a JSON array of objects.
515+jsonToRecordSet
516+ :: SOP.SListI record
517+ => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json array
518+ -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
519+ -> FromClause outer commons schemas params '[tab ::: record]
520+jsonToRecordSet = unsafeRecordFunction "json_to_record_set"
521+
522+-- | Builds an arbitrary set of records from a binary JSON array of objects.
523+jsonbToRecordSet
524+ :: SOP.SListI record
525+ => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb array
526+ -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
527+ -> FromClause outer commons schemas params '[tab ::: record]
528+jsonbToRecordSet = unsafeRecordFunction "jsonb_to_record_set"
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Literal.hsnew file+87
@@ -0,0 +1,87 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.Literal
3+Description: Literal expressions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Literal expressions
9+-}
10+
11+{-# LANGUAGE
12+ FlexibleContexts
13+ , FlexibleInstances
14+ , LambdaCase
15+ , OverloadedStrings
16+ , RankNTypes
17+ , ScopedTypeVariables
18+ , TypeApplications
19+ , TypeSynonymInstances
20+ , UndecidableInstances
21+#-}
22+
23+module Squeal.PostgreSQL.Expression.Literal (Literal (..)) where
24+
25+import ByteString.StrictBuilder (builderBytes)
26+import Data.ByteString.Lazy (toStrict)
27+import Data.Int
28+import Data.String
29+import Data.Text (Text)
30+
31+import qualified Data.Aeson as JSON
32+import qualified Data.Text as Text
33+import qualified Data.Text.Lazy as Lazy (Text)
34+import qualified Data.Text.Lazy as Lazy.Text
35+
36+import Squeal.PostgreSQL.Binary
37+import Squeal.PostgreSQL.Expression
38+import Squeal.PostgreSQL.Expression.Logic
39+import Squeal.PostgreSQL.PG
40+import Squeal.PostgreSQL.Render
41+
42+{- |
43+The `Literal` class allows embedding a Haskell value directly
44+as an `Expression` using `literal`.
45+
46+>>> printSQL (literal 'a')
47+E'a'
48+
49+>>> printSQL (literal (1 :: Double))
50+1.0
51+
52+>>> printSQL (literal (Json [1 :: Double, 2]))
53+('[1.0,2.0]' :: json)
54+
55+>>> printSQL (literal (Enumerated GT))
56+'GT'
57+-}
58+class Literal hask where literal :: hask -> Expr (null (PG hask))
59+instance Literal Bool where
60+ literal = \case
61+ True -> true
62+ False -> false
63+instance JSON.ToJSON hask => Literal (Json hask) where
64+ literal = UnsafeExpression . parenthesized . (<> " :: json")
65+ . singleQuotedUtf8 . toStrict . JSON.encode . getJson
66+instance JSON.ToJSON hask => Literal (Jsonb hask) where
67+ literal = UnsafeExpression . parenthesized . (<> " :: jsonb")
68+ . singleQuotedUtf8 . toStrict . JSON.encode . getJsonb
69+instance Literal Char where
70+ literal chr = UnsafeExpression $
71+ "E\'" <> fromString (escape chr) <> "\'"
72+instance Literal String where literal = fromString
73+instance Literal Int16 where literal = fromIntegral
74+instance Literal Int32 where literal = fromIntegral
75+instance Literal Int64 where literal = fromIntegral
76+instance Literal Float where literal = fromRational . toRational
77+instance Literal Double where literal = fromRational . toRational
78+instance Literal Text where literal = fromString . Text.unpack
79+instance Literal Lazy.Text where literal = fromString . Lazy.Text.unpack
80+instance ToParam (Enumerated enum) (PG (Enumerated enum))
81+ => Literal (Enumerated enum) where
82+ literal
83+ = UnsafeExpression
84+ . singleQuotedUtf8
85+ . builderBytes
86+ . unK
87+ . toParam @(Enumerated enum) @(PG (Enumerated enum))
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Logic.hsnew file+105
@@ -0,0 +1,105 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.Logic
3+Description: Logical expressions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Logical expressions and operators
9+-}
10+
11+{-# LANGUAGE
12+ DataKinds
13+ , OverloadedStrings
14+ , TypeOperators
15+#-}
16+
17+module Squeal.PostgreSQL.Expression.Logic
18+ ( Condition
19+ , true
20+ , false
21+ , not_
22+ , (.&&)
23+ , (.||)
24+ , caseWhenThenElse
25+ , ifThenElse
26+ ) where
27+
28+import Squeal.PostgreSQL.Expression
29+import Squeal.PostgreSQL.Render
30+import Squeal.PostgreSQL.Schema
31+
32+-- | A `Condition` is an `Expression`, which can evaluate
33+-- to `true`, `false` or `Squeal.PostgreSQL.Null.null_`. This is because SQL uses
34+-- a three valued logic.
35+type Condition outer commons grp schemas params from =
36+ Expression outer commons grp schemas params from ('Null 'PGbool)
37+
38+-- | >>> printSQL true
39+-- TRUE
40+true :: Expr (null 'PGbool)
41+true = UnsafeExpression "TRUE"
42+
43+-- | >>> printSQL false
44+-- FALSE
45+false :: Expr (null 'PGbool)
46+false = UnsafeExpression "FALSE"
47+
48+-- | >>> printSQL $ not_ true
49+-- (NOT TRUE)
50+not_ :: null 'PGbool :--> null 'PGbool
51+not_ = unsafeUnaryOpL "NOT"
52+
53+-- | >>> printSQL $ true .&& false
54+-- (TRUE AND FALSE)
55+(.&&) :: Operator (null 'PGbool) (null 'PGbool) (null 'PGbool)
56+infixr 3 .&&
57+(.&&) = unsafeBinaryOp "AND"
58+
59+-- | >>> printSQL $ true .|| false
60+-- (TRUE OR FALSE)
61+(.||) :: Operator (null 'PGbool) (null 'PGbool) (null 'PGbool)
62+infixr 2 .||
63+(.||) = unsafeBinaryOp "OR"
64+
65+-- | >>> :{
66+-- let
67+-- expression :: Expression outer commons grp schemas params from (null 'PGint2)
68+-- expression = caseWhenThenElse [(true, 1), (false, 2)] 3
69+-- in printSQL expression
70+-- :}
71+-- CASE WHEN TRUE THEN 1 WHEN FALSE THEN 2 ELSE 3 END
72+caseWhenThenElse
73+ :: [ ( Condition outer commons grp schemas params from
74+ , Expression outer commons grp schemas params from ty
75+ ) ]
76+ -- ^ whens and thens
77+ -> Expression outer commons grp schemas params from ty
78+ -- ^ else
79+ -> Expression outer commons grp schemas params from ty
80+caseWhenThenElse whenThens else_ = UnsafeExpression $ mconcat
81+ [ "CASE"
82+ , mconcat
83+ [ mconcat
84+ [ " WHEN ", renderSQL when_
85+ , " THEN ", renderSQL then_
86+ ]
87+ | (when_,then_) <- whenThens
88+ ]
89+ , " ELSE ", renderSQL else_
90+ , " END"
91+ ]
92+
93+-- | >>> :{
94+-- let
95+-- expression :: Expression outer commons grp schemas params from (null 'PGint2)
96+-- expression = ifThenElse true 1 0
97+-- in printSQL expression
98+-- :}
99+-- CASE WHEN TRUE THEN 1 ELSE 0 END
100+ifThenElse
101+ :: Condition outer commons grp schemas params from
102+ -> Expression outer commons grp schemas params from ty -- ^ then
103+ -> Expression outer commons grp schemas params from ty -- ^ else
104+ -> Expression outer commons grp schemas params from ty
105+ifThenElse if_ then_ else_ = caseWhenThenElse [(if_,then_)] else_
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Math.hsnew file+103
@@ -0,0 +1,103 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.Math
3+Description: Math expressions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Mathematical functions and operators
9+-}
10+
11+{-# LANGUAGE
12+ DataKinds
13+ , OverloadedStrings
14+ , TypeOperators
15+#-}
16+
17+module Squeal.PostgreSQL.Expression.Math
18+ ( atan2_
19+ , quot_
20+ , rem_
21+ , trunc
22+ , round_
23+ , ceiling_
24+ ) where
25+
26+import Squeal.PostgreSQL.Expression
27+import Squeal.PostgreSQL.List
28+import Squeal.PostgreSQL.Schema
29+
30+-- $setup
31+-- >>> import Squeal.PostgreSQL
32+-- >>> import Squeal.PostgreSQL.Render
33+
34+-- | >>> :{
35+-- let
36+-- expression :: Expr (null 'PGfloat4)
37+-- expression = atan2_ (pi *: 2)
38+-- in printSQL expression
39+-- :}
40+-- atan2(pi(), 2)
41+atan2_
42+ :: float `In` PGFloating
43+ => FunctionN '[ null float, null float] (null float)
44+atan2_ = unsafeFunctionN "atan2"
45+
46+
47+-- | integer division, truncates the result
48+--
49+-- >>> :{
50+-- let
51+-- expression :: Expression outer commons grp schemas params from (null 'PGint2)
52+-- expression = 5 `quot_` 2
53+-- in printSQL expression
54+-- :}
55+-- (5 / 2)
56+quot_
57+ :: int `In` PGIntegral
58+ => Operator (null int) (null int) (null int)
59+quot_ = unsafeBinaryOp "/"
60+
61+-- | remainder upon integer division
62+--
63+-- >>> :{
64+-- let
65+-- expression :: Expression outer commons grp schemas params from (null 'PGint2)
66+-- expression = 5 `rem_` 2
67+-- in printSQL expression
68+-- :}
69+-- (5 % 2)
70+rem_
71+ :: int `In` PGIntegral
72+ => Operator (null int) (null int) (null int)
73+rem_ = unsafeBinaryOp "%"
74+
75+-- | >>> :{
76+-- let
77+-- expression :: Expression outer commons grp schemas params from (null 'PGfloat4)
78+-- expression = trunc pi
79+-- in printSQL expression
80+-- :}
81+-- trunc(pi())
82+trunc :: frac `In` PGFloating => null frac :--> null frac
83+trunc = unsafeFunction "trunc"
84+
85+-- | >>> :{
86+-- let
87+-- expression :: Expression outer commons grp schemas params from (null 'PGfloat4)
88+-- expression = round_ pi
89+-- in printSQL expression
90+-- :}
91+-- round(pi())
92+round_ :: frac `In` PGFloating => null frac :--> null frac
93+round_ = unsafeFunction "round"
94+
95+-- | >>> :{
96+-- let
97+-- expression :: Expression outer commons grp schemas params from (null 'PGfloat4)
98+-- expression = ceiling_ pi
99+-- in printSQL expression
100+-- :}
101+-- ceiling(pi())
102+ceiling_ :: frac `In` PGFloating => null frac :--> null frac
103+ceiling_ = unsafeFunction "ceiling"
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Null.hsnew file+104
@@ -0,0 +1,104 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.Null
3+Description: Null
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Null values and null handling functions
9+-}
10+
11+{-# LANGUAGE
12+ DataKinds
13+ , OverloadedStrings
14+ , TypeOperators
15+#-}
16+
17+module Squeal.PostgreSQL.Expression.Null
18+ ( null_
19+ , notNull
20+ , coalesce
21+ , fromNull
22+ , isNull
23+ , isNotNull
24+ , matchNull
25+ , nullIf
26+ ) where
27+
28+import Squeal.PostgreSQL.Expression
29+import Squeal.PostgreSQL.Expression.Logic
30+import Squeal.PostgreSQL.Render
31+import Squeal.PostgreSQL.Schema
32+
33+-- $setup
34+-- >>> import Squeal.PostgreSQL
35+
36+-- | analagous to `Nothing`
37+--
38+-- >>> printSQL null_
39+-- NULL
40+null_ :: Expr ('Null ty)
41+null_ = UnsafeExpression "NULL"
42+
43+-- | analagous to `Just`
44+--
45+-- >>> printSQL $ notNull true
46+-- TRUE
47+notNull :: 'NotNull ty :--> 'Null ty
48+notNull = UnsafeExpression . renderSQL
49+
50+-- | return the leftmost value which is not NULL
51+--
52+-- >>> printSQL $ coalesce [null_, true] false
53+-- COALESCE(NULL, TRUE, FALSE)
54+coalesce :: FunctionVar ('Null ty) ('NotNull ty) ('NotNull ty)
55+coalesce nullxs notNullx = UnsafeExpression $
56+ "COALESCE" <> parenthesized (commaSeparated
57+ ((renderSQL <$> nullxs) <> [renderSQL notNullx]))
58+
59+-- | analagous to `Data.Maybe.fromMaybe` using @COALESCE@
60+--
61+-- >>> printSQL $ fromNull true null_
62+-- COALESCE(NULL, TRUE)
63+fromNull
64+ :: Expression outer commons grp schemas params from ('NotNull ty)
65+ -- ^ what to convert @NULL@ to
66+ -> Expression outer commons grp schemas params from ('Null ty)
67+ -> Expression outer commons grp schemas params from ('NotNull ty)
68+fromNull notNullx nullx = coalesce [nullx] notNullx
69+
70+-- | >>> printSQL $ null_ & isNull
71+-- NULL IS NULL
72+isNull :: 'Null ty :--> null 'PGbool
73+isNull x = UnsafeExpression $ renderSQL x <+> "IS NULL"
74+
75+-- | >>> printSQL $ null_ & isNotNull
76+-- NULL IS NOT NULL
77+isNotNull :: 'Null ty :--> null 'PGbool
78+isNotNull x = UnsafeExpression $ renderSQL x <+> "IS NOT NULL"
79+
80+-- | analagous to `maybe` using @IS NULL@
81+--
82+-- >>> printSQL $ matchNull true not_ null_
83+-- CASE WHEN NULL IS NULL THEN TRUE ELSE (NOT NULL) END
84+matchNull
85+ :: Expression outer commons grp schemas params from (nullty)
86+ -- ^ what to convert @NULL@ to
87+ -> ( Expression outer commons grp schemas params from ('NotNull ty)
88+ -> Expression outer commons grp schemas params from (nullty) )
89+ -- ^ function to perform when @NULL@ is absent
90+ -> Expression outer commons grp schemas params from ('Null ty)
91+ -> Expression outer commons grp schemas params from (nullty)
92+matchNull y f x = ifThenElse (isNull x) y
93+ (f (UnsafeExpression (renderSQL x)))
94+
95+{-| right inverse to `fromNull`, if its arguments are equal then
96+`nullIf` gives @NULL@.
97+
98+>>> :set -XTypeApplications -XDataKinds
99+>>> let expr = nullIf (false *: param @1) :: Expression outer commons grp schemas '[ 'NotNull 'PGbool] from ('Null 'PGbool)
100+>>> printSQL expr
101+NULLIF(FALSE, ($1 :: bool))
102+-}
103+nullIf :: FunctionN '[ 'NotNull ty, 'NotNull ty] ('Null ty)
104+nullIf = unsafeFunctionN "NULLIF"
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Parameter.hsnew file+80
@@ -0,0 +1,80 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.Parameter
3+Description: Parameters
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Parameters, out-of-line data values
9+-}
10+
11+{-# LANGUAGE
12+ AllowAmbiguousTypes
13+ , DataKinds
14+ , FlexibleContexts
15+ , FlexibleInstances
16+ , FunctionalDependencies
17+ , KindSignatures
18+ , MultiParamTypeClasses
19+ , OverloadedStrings
20+ , RankNTypes
21+ , ScopedTypeVariables
22+ , TypeApplications
23+ , TypeOperators
24+ , UndecidableInstances
25+#-}
26+
27+module Squeal.PostgreSQL.Expression.Parameter
28+ ( HasParameter (parameter)
29+ , param
30+ ) where
31+
32+import GHC.TypeLits
33+
34+import Squeal.PostgreSQL.Expression
35+import Squeal.PostgreSQL.Expression.Type
36+import Squeal.PostgreSQL.Render
37+import Squeal.PostgreSQL.Schema
38+
39+-- $setup
40+-- >>> import Squeal.PostgreSQL
41+
42+{- | A `HasParameter` constraint is used to indicate a value that is
43+supplied externally to a SQL statement.
44+`Squeal.PostgreSQL.PQ.manipulateParams`,
45+`Squeal.PostgreSQL.PQ.queryParams` and
46+`Squeal.PostgreSQL.PQ.traversePrepared` support specifying data values
47+separately from the SQL command string, in which case `param`s are used to
48+refer to the out-of-line data values.
49+-}
50+class KnownNat n => HasParameter
51+ (n :: Nat)
52+ (params :: [NullityType])
53+ (ty :: NullityType)
54+ | n params -> ty where
55+ -- | `parameter` takes a `Nat` using type application and a `TypeExpression`.
56+ --
57+ -- >>> let expr = parameter @1 int4 :: Expression outer '[] grp schemas '[ 'Null 'PGint4] from ('Null 'PGint4)
58+ -- >>> printSQL expr
59+ -- ($1 :: int4)
60+ parameter
61+ :: TypeExpression schemas ty
62+ -> Expression outer commons grp schemas params from ty
63+ parameter ty = UnsafeExpression $ parenthesized $
64+ "$" <> renderNat @n <+> "::"
65+ <+> renderSQL ty
66+instance {-# OVERLAPPING #-} HasParameter 1 (ty1:tys) ty1
67+instance {-# OVERLAPPABLE #-} (KnownNat n, HasParameter (n-1) params ty)
68+ => HasParameter n (ty' : params) ty
69+
70+-- | `param` takes a `Nat` using type application and for basic types,
71+-- infers a `TypeExpression`.
72+--
73+-- >>> let expr = param @1 :: Expression outer commons grp schemas '[ 'Null 'PGint4] from ('Null 'PGint4)
74+-- >>> printSQL expr
75+-- ($1 :: int4)
76+param
77+ :: forall n outer commons schemas params from grp ty
78+ . (PGTyped schemas ty, HasParameter n params ty)
79+ => Expression outer commons grp schemas params from ty -- ^ param
80+param = parameter @n (pgtype @schemas)
squeal-postgresql/src/Squeal/PostgreSQL/Expression/SetOf.hsnew file+106
@@ -0,0 +1,106 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.SetOf
3+Description: Set returning functions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Set returning functions
9+-}
10+
11+{-# LANGUAGE
12+ AllowAmbiguousTypes
13+ , DataKinds
14+ , FlexibleContexts
15+ , OverloadedStrings
16+ , PolyKinds
17+ , RankNTypes
18+ , ScopedTypeVariables
19+ , TypeApplications
20+ , TypeOperators
21+#-}
22+
23+module Squeal.PostgreSQL.Expression.SetOf
24+ ( generateSeries
25+ , generateSeriesStep
26+ , generateSeriesTimestamp
27+ , SetOfFunction
28+ , unsafeSetOfFunction
29+ , SetOfFunctionN
30+ , unsafeSetOfFunctionN
31+ ) where
32+
33+import GHC.TypeLits
34+
35+import qualified Generics.SOP as SOP
36+
37+import Squeal.PostgreSQL.Alias
38+import Squeal.PostgreSQL.Expression
39+import Squeal.PostgreSQL.List
40+import Squeal.PostgreSQL.Render
41+import Squeal.PostgreSQL.Query
42+import Squeal.PostgreSQL.Schema
43+
44+{- |
45+A @RankNType@ for set returning functions with 1 argument.
46+-}
47+type SetOfFunction fun ty setof
48+ = forall outer commons schemas params
49+ . Expression outer commons 'Ungrouped schemas params '[] ty
50+ -- ^ input
51+ -> FromClause outer commons schemas params '[fun ::: setof]
52+ -- ^ output
53+
54+-- | Escape hatch for a set returning function with 1 argument.
55+unsafeSetOfFunction
56+ :: forall fun ty setof. KnownSymbol fun
57+ => SetOfFunction fun ty setof -- ^ set returning function
58+unsafeSetOfFunction x = UnsafeFromClause $
59+ renderSymbol @fun <> parenthesized (renderSQL x)
60+
61+{- |
62+A @RankNType@ for set returning functions with multiple argument.
63+-}
64+type SetOfFunctionN fun tys setof
65+ = forall outer commons schemas params
66+ . NP (Expression outer commons 'Ungrouped schemas params '[]) tys
67+ -- ^ inputs
68+ -> FromClause outer commons schemas params '[fun ::: setof]
69+ -- ^ output
70+
71+-- | Escape hatch for a set returning function with multiple argument.
72+unsafeSetOfFunctionN
73+ :: forall fun tys setof. (SOP.SListI tys, KnownSymbol fun)
74+ => SetOfFunctionN fun tys setof -- ^ set returning function
75+unsafeSetOfFunctionN xs = UnsafeFromClause $
76+ renderSymbol @fun <> parenthesized (renderCommaSeparated renderSQL xs)
77+
78+{- | @generateSeries (start *: stop)@
79+
80+Generate a series of values, from @start@ to @stop@ with a step size of one
81+-}
82+generateSeries
83+ :: ty `In` '[ 'PGint4, 'PGint8, 'PGnumeric]
84+ => SetOfFunctionN "generate_series" '[ null ty, null ty]
85+ '["generate_series" ::: null ty] -- ^ set returning function
86+generateSeries = unsafeSetOfFunctionN
87+
88+{- | @generateSeries (start :* stop *: step)@
89+
90+Generate a series of values, from @start@ to @stop@ with a step size of @step@
91+-}
92+generateSeriesStep
93+ :: ty `In` '[ 'PGint4, 'PGint8, 'PGnumeric]
94+ => SetOfFunctionN "generate_series" '[null ty, null ty, null ty]
95+ '["generate_series" ::: null ty] -- ^ set returning function
96+generateSeriesStep = unsafeSetOfFunctionN
97+
98+{- | @generateSeries (start :* stop *: step)@
99+
100+Generate a series of values, from @start@ to @stop@ with a step size of @step@
101+-}
102+generateSeriesTimestamp
103+ :: ty `In` '[ 'PGtimestamp, 'PGtimestamptz]
104+ => SetOfFunctionN "generate_series" '[null ty, null ty, null 'PGinterval]
105+ '["generate_series" ::: null ty] -- ^ set returning function
106+generateSeriesTimestamp = unsafeSetOfFunctionN
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Sort.hsnew file+85
@@ -0,0 +1,85 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.Sort
3+Description: Sort expressions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Sort expressions
9+-}
10+
11+{-# LANGUAGE
12+ DataKinds
13+ , GADTs
14+ , LambdaCase
15+ , OverloadedStrings
16+ , StandaloneDeriving
17+#-}
18+
19+module Squeal.PostgreSQL.Expression.Sort
20+ ( SortExpression (..)
21+ , OrderBy (..)
22+ ) where
23+
24+import Squeal.PostgreSQL.Expression
25+import Squeal.PostgreSQL.Render
26+import Squeal.PostgreSQL.Schema
27+
28+-- | `SortExpression`s are used by `orderBy` to optionally sort the results
29+-- of a `Squeal.PostgreSQL.Query.Query`. `Asc` or `Desc`
30+-- set the sort direction of a `NotNull` result
31+-- column to ascending or descending. Ascending order puts smaller values
32+-- first, where "smaller" is defined in terms of the
33+-- `Squeal.PostgreSQL.Expression.Comparison..<` operator. Similarly,
34+-- descending order is determined with the
35+-- `Squeal.PostgreSQL.Expression.Comparison..>` operator. `AscNullsFirst`,
36+-- `AscNullsLast`, `DescNullsFirst` and `DescNullsLast` options are used to
37+-- determine whether nulls appear before or after non-null values in the sort
38+-- ordering of a `Null` result column.
39+data SortExpression outer commons grp schemas params from where
40+ Asc
41+ :: Expression outer commons grp schemas params from ('NotNull ty)
42+ -> SortExpression outer commons grp schemas params from
43+ Desc
44+ :: Expression outer commons grp schemas params from ('NotNull ty)
45+ -> SortExpression outer commons grp schemas params from
46+ AscNullsFirst
47+ :: Expression outer commons grp schemas params from ('Null ty)
48+ -> SortExpression outer commons grp schemas params from
49+ AscNullsLast
50+ :: Expression outer commons grp schemas params from ('Null ty)
51+ -> SortExpression outer commons grp schemas params from
52+ DescNullsFirst
53+ :: Expression outer commons grp schemas params from ('Null ty)
54+ -> SortExpression outer commons grp schemas params from
55+ DescNullsLast
56+ :: Expression outer commons grp schemas params from ('Null ty)
57+ -> SortExpression outer commons grp schemas params from
58+deriving instance Show (SortExpression outer commons grp schemas params from)
59+instance RenderSQL (SortExpression outer commons grp schemas params from) where
60+ renderSQL = \case
61+ Asc expression -> renderSQL expression <+> "ASC"
62+ Desc expression -> renderSQL expression <+> "DESC"
63+ AscNullsFirst expression -> renderSQL expression
64+ <+> "ASC NULLS FIRST"
65+ DescNullsFirst expression -> renderSQL expression
66+ <+> "DESC NULLS FIRST"
67+ AscNullsLast expression -> renderSQL expression <+> "ASC NULLS LAST"
68+ DescNullsLast expression -> renderSQL expression <+> "DESC NULLS LAST"
69+
70+{- |
71+The `orderBy` clause causes the result rows of a `Squeal.PostgreSQL.Query.TableExpression`
72+to be sorted according to the specified `SortExpression`(s).
73+If two rows are equal according to the leftmost expression,
74+they are compared according to the next expression and so on.
75+If they are equal according to all specified expressions,
76+they are returned in an implementation-dependent order.
77+
78+You can also control the order in which rows are processed by window functions
79+using `orderBy` within `Squeal.PostgreSQL.Query.Over`.
80+-}
81+class OrderBy expr where
82+ orderBy
83+ :: [SortExpression outer commons grp schemas params from]
84+ -> expr outer commons grp schemas params from
85+ -> expr outer commons grp schemas params from
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Subquery.hsnew file+121
@@ -0,0 +1,121 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.Subquery
3+Description: Subquery expressions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Subquery expressions
9+-}
10+
11+{-# LANGUAGE
12+ DataKinds
13+ , OverloadedStrings
14+ , RankNTypes
15+ , TypeOperators
16+#-}
17+
18+module Squeal.PostgreSQL.Expression.Subquery
19+ ( exists
20+ , in_
21+ , notIn
22+ , subAll
23+ , subAny
24+ ) where
25+
26+import Squeal.PostgreSQL.Alias
27+import Squeal.PostgreSQL.Expression
28+import Squeal.PostgreSQL.Expression.Logic
29+import Squeal.PostgreSQL.List
30+import Squeal.PostgreSQL.Query
31+import Squeal.PostgreSQL.Render
32+import Squeal.PostgreSQL.Schema
33+
34+-- $setup
35+-- >>> import Squeal.PostgreSQL
36+-- >>> import Squeal.PostgreSQL.Render
37+
38+{- |
39+The argument of `exists` is an arbitrary subquery. The subquery is evaluated
40+to determine whether it returns any rows. If it returns at least one row,
41+the result of `exists` is `true`; if the subquery returns no rows,
42+the result of `exists` is `false`.
43+
44+The subquery can refer to variables from the surrounding query,
45+which will act as constants during any one evaluation of the subquery.
46+
47+The subquery will generally only be executed long enough to determine whether
48+at least one row is returned, not all the way to completion.
49+-}
50+exists
51+ :: Query (Join outer from) commons schemas params row
52+ -> Condition outer commons grp schemas params from
53+exists query = UnsafeExpression $ "EXISTS" <+> parenthesized (renderSQL query)
54+
55+{- |
56+The right-hand side is a parenthesized subquery, which must return
57+exactly one column. The left-hand expression is evaluated and compared to each
58+row of the subquery result using the given `Operator`,
59+which must yield a Boolean result. The result of `subAll` is `true`
60+if all rows yield true (including the case where the subquery returns no rows).
61+The result is `false` if any `false` result is found.
62+The result is `Squeal.PostgreSQL.Expression.Null.null_` if
63+ no comparison with a subquery row returns `false`,
64+and at least one comparison returns `Squeal.PostgreSQL.Expression.Null.null_`.
65+
66+>>> printSQL $ subAll true (.==) (values_ (true `as` #foo))
67+(TRUE = ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo")))
68+-}
69+subAll
70+ :: Expression outer commons grp schemas params from ty1 -- ^ expression
71+ -> Operator ty1 ty2 ('Null 'PGbool) -- ^ operator
72+ -> Query (Join outer from) commons schemas params '[col ::: ty2] -- ^ subquery
73+ -> Condition outer commons grp schemas params from
74+subAll expr (?) qry = expr ?
75+ (UnsafeExpression $ "ALL" <+> parenthesized (renderSQL qry))
76+
77+{- |
78+The right-hand side is a parenthesized subquery, which must return exactly one column.
79+The left-hand expression is evaluated and compared to each row of the subquery result
80+using the given `Operator`, which must yield a Boolean result. The result of `subAny` is `true`
81+if any `true` result is obtained. The result is `false` if no true result is found
82+(including the case where the subquery returns no rows).
83+
84+>>> printSQL $ subAll "foo" like (values_ ("foobar" `as` #foo))
85+(E'foo' LIKE ALL (SELECT * FROM (VALUES (E'foobar')) AS t ("foo")))
86+-}
87+subAny
88+ :: Expression outer commons grp schemas params from ty1 -- ^ expression
89+ -> Operator ty1 ty2 ('Null 'PGbool) -- ^ operator
90+ -> Query (Join outer from) commons schemas params '[col ::: ty2] -- ^ subquery
91+ -> Condition outer commons grp schemas params from
92+subAny expr (?) qry = expr ?
93+ (UnsafeExpression $ "ANY" <+> parenthesized (renderSQL qry))
94+
95+{- |
96+The result is `true` if the left-hand expression's result is equal
97+to any of the right-hand expressions.
98+
99+>>> printSQL $ true `in_` [true, false, null_]
100+TRUE IN (TRUE, FALSE, NULL)
101+-}
102+in_
103+ :: Expression outer commons grp schemas params from ty -- ^ expression
104+ -> [Expression outer commons grp schemas params from ty]
105+ -> Condition outer commons grp schemas params from
106+expr `in_` exprs = UnsafeExpression $ renderSQL expr <+> "IN"
107+ <+> parenthesized (commaSeparated (renderSQL <$> exprs))
108+
109+{- |
110+The result is `true` if the left-hand expression's result is not equal
111+to any of the right-hand expressions.
112+
113+>>> printSQL $ true `notIn` [false, null_]
114+TRUE NOT IN (FALSE, NULL)
115+-}
116+notIn
117+ :: Expression outer commons grp schemas params from ty -- ^ expression
118+ -> [Expression outer commons grp schemas params from ty]
119+ -> Condition outer commons grp schemas params from
120+expr `notIn` exprs = UnsafeExpression $ renderSQL expr <+> "NOT IN"
121+ <+> parenthesized (commaSeparated (renderSQL <$> exprs))
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Text.hsnew file+65
@@ -0,0 +1,65 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.Text
3+Description: Text expressions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+String functions and operators
9+-}
10+
11+{-# LANGUAGE
12+ DataKinds
13+ , OverloadedStrings
14+ , TypeOperators
15+#-}
16+
17+module Squeal.PostgreSQL.Expression.Text
18+ ( lower
19+ , upper
20+ , charLength
21+ , like
22+ , ilike
23+ ) where
24+
25+import Squeal.PostgreSQL.Expression
26+import Squeal.PostgreSQL.Schema
27+
28+-- $setup
29+-- >>> import Squeal.PostgreSQL
30+-- >>> import Squeal.PostgreSQL.Render
31+
32+-- | >>> printSQL $ lower "ARRRGGG"
33+-- lower(E'ARRRGGG')
34+lower :: null 'PGtext :--> null 'PGtext
35+lower = unsafeFunction "lower"
36+
37+-- | >>> printSQL $ upper "eeee"
38+-- upper(E'eeee')
39+upper :: null 'PGtext :--> null 'PGtext
40+upper = unsafeFunction "upper"
41+
42+-- | >>> printSQL $ charLength "four"
43+-- char_length(E'four')
44+charLength :: null 'PGtext :--> null 'PGint4
45+charLength = unsafeFunction "char_length"
46+
47+-- | The `like` expression returns true if the @string@ matches
48+-- the supplied @pattern@. If @pattern@ does not contain percent signs
49+-- or underscores, then the pattern only represents the string itself;
50+-- in that case `like` acts like the equals operator. An underscore (_)
51+-- in pattern stands for (matches) any single character; a percent sign (%)
52+-- matches any sequence of zero or more characters.
53+--
54+-- >>> printSQL $ "abc" `like` "a%"
55+-- (E'abc' LIKE E'a%')
56+like :: Operator (null 'PGtext) (null 'PGtext) ('Null 'PGbool)
57+like = unsafeBinaryOp "LIKE"
58+
59+-- | The key word ILIKE can be used instead of LIKE to make the
60+-- match case-insensitive according to the active locale.
61+--
62+-- >>> printSQL $ "abc" `ilike` "a%"
63+-- (E'abc' ILIKE E'a%')
64+ilike :: Operator (null 'PGtext) (null 'PGtext) ('Null 'PGbool)
65+ilike = unsafeBinaryOp "ILIKE"
squeal-postgresql/src/Squeal/PostgreSQL/Expression/TextSearch.hsnew file+158
@@ -0,0 +1,158 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.TextSearch
3+Description: Text search expressions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Text search functions and operators
9+-}
10+
11+{-# LANGUAGE
12+ DataKinds
13+ , OverloadedStrings
14+ , TypeOperators
15+#-}
16+
17+module Squeal.PostgreSQL.Expression.TextSearch
18+ ( (@@)
19+ , (.&)
20+ , (.|)
21+ , (.!)
22+ , (<->)
23+ , arrayToTSvector
24+ , tsvectorLength
25+ , numnode
26+ , plainToTSquery
27+ , phraseToTSquery
28+ , websearchToTSquery
29+ , queryTree
30+ , toTSquery
31+ , toTSvector
32+ , setWeight
33+ , strip
34+ , jsonToTSvector
35+ , jsonbToTSvector
36+ , tsDelete
37+ , tsFilter
38+ , tsHeadline
39+ ) where
40+
41+import Squeal.PostgreSQL.Expression
42+import Squeal.PostgreSQL.List
43+import Squeal.PostgreSQL.Schema
44+
45+-- | `Squeal.PostgreSQL.Expression.Type.tsvector` matches tsquery ?
46+(@@) :: Operator (null 'PGtsvector) (null 'PGtsquery) ('Null 'PGbool)
47+(@@) = unsafeBinaryOp "@@"
48+
49+-- | AND `Squeal.PostgreSQL.Expression.Type.tsquery`s together
50+(.&) :: Operator (null 'PGtsquery) (null 'PGtsquery) (null 'PGtsquery)
51+(.&) = unsafeBinaryOp "&&"
52+
53+-- | OR `Squeal.PostgreSQL.Expression.Type.tsquery`s together
54+(.|) :: Operator (null 'PGtsquery) (null 'PGtsquery) (null 'PGtsquery)
55+(.|) = unsafeBinaryOp "||"
56+
57+-- | negate a `Squeal.PostgreSQL.Expression.Type.tsquery`
58+(.!) :: null 'PGtsquery :--> null 'PGtsquery
59+(.!) = unsafeUnaryOpL "!!"
60+
61+-- | `Squeal.PostgreSQL.Expression.Type.tsquery` followed by
62+-- `Squeal.PostgreSQL.Expression.Type.tsquery`
63+(<->) :: Operator (null 'PGtsquery) (null 'PGtsquery) (null 'PGtsquery)
64+(<->) = unsafeBinaryOp "<->"
65+
66+-- | convert array of lexemes to `Squeal.PostgreSQL.Expression.Type.tsvector`
67+arrayToTSvector
68+ :: null ('PGvararray ('NotNull 'PGtext))
69+ :--> null 'PGtsvector
70+arrayToTSvector = unsafeFunction "array_to_tsvector"
71+
72+-- | number of lexemes in `Squeal.PostgreSQL.Expression.Type.tsvector`
73+tsvectorLength :: null 'PGtsvector :--> null 'PGint4
74+tsvectorLength = unsafeFunction "length"
75+
76+-- | number of lexemes plus operators in `Squeal.PostgreSQL.Expression.Type.tsquery`
77+numnode :: null 'PGtsquery :--> null 'PGint4
78+numnode = unsafeFunction "numnode"
79+
80+-- | produce `Squeal.PostgreSQL.Expression.Type.tsquery` ignoring punctuation
81+plainToTSquery :: null 'PGtext :--> null 'PGtsquery
82+plainToTSquery = unsafeFunction "plainto_tsquery"
83+
84+-- | produce `Squeal.PostgreSQL.Expression.Type.tsquery` that searches for a phrase,
85+-- ignoring punctuation
86+phraseToTSquery :: null 'PGtext :--> null 'PGtsquery
87+phraseToTSquery = unsafeFunction "phraseto_tsquery"
88+
89+-- | produce `Squeal.PostgreSQL.Expression.Type.tsquery` from a web search style query
90+websearchToTSquery :: null 'PGtext :--> null 'PGtsquery
91+websearchToTSquery = unsafeFunction "websearch_to_tsquery"
92+
93+-- | get indexable part of a `Squeal.PostgreSQL.Expression.Type.tsquery`
94+queryTree :: null 'PGtsquery :--> null 'PGtext
95+queryTree = unsafeFunction "query_tree"
96+
97+-- | normalize words and convert to `Squeal.PostgreSQL.Expression.Type.tsquery`
98+toTSquery :: null 'PGtext :--> null 'PGtsquery
99+toTSquery = unsafeFunction "to_tsquery"
100+
101+-- | reduce document text to `Squeal.PostgreSQL.Expression.Type.tsvector`
102+toTSvector
103+ :: ty `In` '[ 'PGtext, 'PGjson, 'PGjsonb]
104+ => null ty :--> null 'PGtsvector
105+toTSvector = unsafeFunction "to_tsvector"
106+
107+-- | assign weight to each element of `Squeal.PostgreSQL.Expression.Type.tsvector`
108+setWeight
109+ :: FunctionN '[null 'PGtsvector, null ('PGchar 1)] (null 'PGtsvector)
110+setWeight = unsafeFunctionN "set_weight"
111+
112+-- | remove positions and weights from `Squeal.PostgreSQL.Expression.Type.tsvector`
113+strip :: null 'PGtsvector :--> null 'PGtsvector
114+strip = unsafeFunction "strip"
115+
116+-- | @jsonToTSvector (document *: filter)@
117+-- reduce each value in the document, specified by filter to a `Squeal.PostgreSQL.Expression.Type.tsvector`,
118+-- and then concatenate those in document order to produce a single `Squeal.PostgreSQL.Expression.Type.tsvector`.
119+-- filter is a `Squeal.PostgreSQL.Expression.Type.json` array, that enumerates what kind of elements
120+-- need to be included into the resulting `Squeal.PostgreSQL.Expression.Type.tsvector`.
121+-- Possible values for filter are "string" (to include all string values),
122+-- "numeric" (to include all numeric values in the string format),
123+-- "boolean" (to include all Boolean values in the string format "true"/"false"),
124+-- "key" (to include all keys) or "all" (to include all above).
125+-- These values can be combined together to include, e.g. all string and numeric values.
126+jsonToTSvector :: FunctionN '[null 'PGjson, null 'PGjson] (null 'PGtsvector)
127+jsonToTSvector = unsafeFunctionN "json_to_tsvector"
128+
129+-- | @jsonbToTSvector (document *: filter)@
130+-- reduce each value in the document, specified by filter to a `Squeal.PostgreSQL.Expression.Type.tsvector`,
131+-- and then concatenate those in document order to produce a single `Squeal.PostgreSQL.Expression.Type.tsvector`.
132+-- filter is a `Squeal.PostgreSQL.Expression.Type.jsonb` array, that enumerates what kind of elements
133+-- need to be included into the resulting `Squeal.PostgreSQL.Expression.Type.tsvector`.
134+-- Possible values for filter are "string" (to include all string values),
135+-- "numeric" (to include all numeric values in the string format),
136+-- "boolean" (to include all Boolean values in the string format "true"/"false"),
137+-- "key" (to include all keys) or "all" (to include all above).
138+-- These values can be combined together to include, e.g. all string and numeric values.
139+jsonbToTSvector :: FunctionN '[null 'PGjsonb, null 'PGjsonb] (null 'PGtsvector)
140+jsonbToTSvector = unsafeFunctionN "jsonb_to_tsvector"
141+
142+-- | remove given lexeme from `Squeal.PostgreSQL.Expression.Type.tsvector`
143+tsDelete :: FunctionN
144+ '[null 'PGtsvector, null ('PGvararray ('NotNull 'PGtext))]
145+ (null 'PGtsvector)
146+tsDelete = unsafeFunctionN "ts_delete"
147+
148+-- | select only elements with given weights from `Squeal.PostgreSQL.Expression.Type.tsvector`
149+tsFilter :: FunctionN
150+ '[null 'PGtsvector, null ('PGvararray ('NotNull ('PGchar 1)))]
151+ (null 'PGtsvector)
152+tsFilter = unsafeFunctionN "ts_filter"
153+
154+-- | display a `Squeal.PostgreSQL.Expression.Type.tsquery` match
155+tsHeadline
156+ :: document `In` '[ 'PGtext, 'PGjson, 'PGjsonb]
157+ => FunctionN '[null document, null 'PGtsquery] (null 'PGtext)
158+tsHeadline = unsafeFunctionN "ts_headline"
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Time.hsnew file+197
@@ -0,0 +1,197 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.Time
3+Description: Date/Time expressions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Date/Time functions and operators
9+-}
10+
11+{-# LANGUAGE
12+ DataKinds
13+ , DeriveGeneric
14+ , FunctionalDependencies
15+ , LambdaCase
16+ , MultiParamTypeClasses
17+ , OverloadedStrings
18+ , PolyKinds
19+ , RankNTypes
20+#-}
21+
22+module Squeal.PostgreSQL.Expression.Time
23+ ( TimeOp (..)
24+ , currentDate
25+ , currentTime
26+ , currentTimestamp
27+ , localTime
28+ , localTimestamp
29+ , now
30+ , makeDate
31+ , makeTime
32+ , makeTimestamp
33+ , makeTimestamptz
34+ , interval_
35+ , TimeUnit (..)
36+ ) where
37+
38+import Data.String
39+
40+import qualified GHC.Generics as GHC
41+import qualified Generics.SOP as SOP
42+
43+import Squeal.PostgreSQL.Expression
44+import Squeal.PostgreSQL.Render
45+import Squeal.PostgreSQL.Schema
46+
47+-- $setup
48+-- >>> import Squeal.PostgreSQL
49+
50+-- | >>> printSQL currentDate
51+-- CURRENT_DATE
52+currentDate :: Expr (null 'PGdate)
53+currentDate = UnsafeExpression "CURRENT_DATE"
54+
55+-- | >>> printSQL currentTime
56+-- CURRENT_TIME
57+currentTime :: Expr (null 'PGtimetz)
58+currentTime = UnsafeExpression "CURRENT_TIME"
59+
60+-- | >>> printSQL currentTimestamp
61+-- CURRENT_TIMESTAMP
62+currentTimestamp :: Expr (null 'PGtimestamptz)
63+currentTimestamp = UnsafeExpression "CURRENT_TIMESTAMP"
64+
65+-- | >>> printSQL localTime
66+-- LOCALTIME
67+localTime :: Expr (null 'PGtime)
68+localTime = UnsafeExpression "LOCALTIME"
69+
70+-- | >>> printSQL localTimestamp
71+-- LOCALTIMESTAMP
72+localTimestamp :: Expr (null 'PGtimestamp)
73+localTimestamp = UnsafeExpression "LOCALTIMESTAMP"
74+
75+-- | Current date and time (equivalent to `currentTimestamp`)
76+--
77+-- >>> printSQL now
78+-- now()
79+now :: Expr (null 'PGtimestamptz)
80+now = UnsafeExpression "now()"
81+
82+{-|
83+Create date from year, month and day fields
84+
85+>>> printSQL (makeDate (1984 :* 7 *: 3))
86+make_date(1984, 7, 3)
87+-}
88+makeDate :: FunctionN
89+ '[ null 'PGint4, null 'PGint4, null 'PGint4 ]
90+ ( null 'PGdate )
91+makeDate = unsafeFunctionN "make_date"
92+
93+{-|
94+Create time from hour, minute and seconds fields
95+
96+>>> printSQL (makeTime (8 :* 15 *: 23.5))
97+make_time(8, 15, 23.5)
98+-}
99+makeTime :: FunctionN
100+ '[ null 'PGint4, null 'PGint4, null 'PGfloat8 ]
101+ ( null 'PGtime )
102+makeTime = unsafeFunctionN "make_time"
103+
104+{-|
105+Create timestamp from year, month, day, hour, minute and seconds fields
106+
107+>>> printSQL (makeTimestamp (2013 :* 7 :* 15 :* 8 :* 15 *: 23.5))
108+make_timestamp(2013, 7, 15, 8, 15, 23.5)
109+-}
110+makeTimestamp :: FunctionN
111+ '[ null 'PGint4, null 'PGint4, null 'PGint4
112+ , null 'PGint4, null 'PGint4, null 'PGfloat8 ]
113+ ( null 'PGtimestamp )
114+makeTimestamp = unsafeFunctionN "make_timestamp"
115+
116+{-|
117+Create timestamp with time zone from
118+year, month, day, hour, minute and seconds fields;
119+the current time zone is used
120+
121+>>> printSQL (makeTimestamptz (2013 :* 7 :* 15 :* 8 :* 15 *: 23.5))
122+make_timestamptz(2013, 7, 15, 8, 15, 23.5)
123+-}
124+makeTimestamptz :: FunctionN
125+ '[ null 'PGint4, null 'PGint4, null 'PGint4
126+ , null 'PGint4, null 'PGint4, null 'PGfloat8 ]
127+ ( null 'PGtimestamptz )
128+makeTimestamptz = unsafeFunctionN "make_timestamptz"
129+
130+{-|
131+Affine space operations on time types.
132+-}
133+class TimeOp time diff | time -> diff where
134+ {-|
135+ >>> printSQL (makeDate (1984 :* 7 *: 3) !+ 365)
136+ (make_date(1984, 7, 3) + 365)
137+ -}
138+ (!+) :: Operator (null time) (null diff) (null time)
139+ (!+) = unsafeBinaryOp "+"
140+ {-|
141+ >>> printSQL (365 +! makeDate (1984 :* 7 *: 3))
142+ (365 + make_date(1984, 7, 3))
143+ -}
144+ (+!) :: Operator (null diff) (null time) (null time)
145+ (+!) = unsafeBinaryOp "+"
146+ {-|
147+ >>> printSQL (makeDate (1984 :* 7 *: 3) !- 365)
148+ (make_date(1984, 7, 3) - 365)
149+ -}
150+ (!-) :: Operator (null time) (null diff) (null time)
151+ (!-) = unsafeBinaryOp "-"
152+ {-|
153+ >>> printSQL (makeDate (1984 :* 7 *: 3) !-! currentDate)
154+ (make_date(1984, 7, 3) - CURRENT_DATE)
155+ -}
156+ (!-!) :: Operator (null time) (null time) (null diff)
157+ (!-!) = unsafeBinaryOp "-"
158+instance TimeOp 'PGtimestamp 'PGinterval
159+instance TimeOp 'PGtimestamptz 'PGinterval
160+instance TimeOp 'PGtime 'PGinterval
161+instance TimeOp 'PGtimetz 'PGinterval
162+instance TimeOp 'PGinterval 'PGinterval
163+instance TimeOp 'PGdate 'PGint4
164+infixl 6 !+
165+infixl 6 +!
166+infixl 6 !-
167+infixl 6 !-!
168+
169+-- | A `TimeUnit` to use in `interval_` construction.
170+data TimeUnit
171+ = Years | Months | Weeks | Days
172+ | Hours | Minutes | Seconds
173+ | Microseconds | Milliseconds
174+ | Decades | Centuries | Millennia
175+ deriving (Eq, Ord, Show, Read, Enum, GHC.Generic)
176+instance SOP.Generic TimeUnit
177+instance SOP.HasDatatypeInfo TimeUnit
178+instance RenderSQL TimeUnit where
179+ renderSQL = \case
180+ Years -> "years"
181+ Months -> "months"
182+ Weeks -> "weeks"
183+ Days -> "days"
184+ Hours -> "hours"
185+ Minutes -> "minutes"
186+ Seconds -> "seconds"
187+ Microseconds -> "microseconds"
188+ Milliseconds -> "milliseconds"
189+ Decades -> "decades"
190+ Centuries -> "centuries"
191+ Millennia -> "millennia"
192+
193+-- | >>> printSQL $ interval_ 7 Days
194+-- (INTERVAL '7.0 days')
195+interval_ :: Double -> TimeUnit -> Expr (null 'PGinterval)
196+interval_ num unit = UnsafeExpression . parenthesized $ "INTERVAL" <+>
197+ "'" <> fromString (show num) <+> renderSQL unit <> "'"
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Type.hsnew file+283
@@ -0,0 +1,283 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression
3+Description: Type expressions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Type expressions.
9+-}
10+
11+{-# LANGUAGE
12+ AllowAmbiguousTypes
13+ , DataKinds
14+ , DeriveGeneric
15+ , FlexibleContexts
16+ , FlexibleInstances
17+ , GADTs
18+ , GeneralizedNewtypeDeriving
19+ , KindSignatures
20+ , MultiParamTypeClasses
21+ , OverloadedStrings
22+ , RankNTypes
23+ , ScopedTypeVariables
24+ , TypeApplications
25+ , TypeOperators
26+#-}
27+
28+module Squeal.PostgreSQL.Expression.Type
29+ ( TypeExpression (..)
30+ , cast
31+ , PGTyped (..)
32+ , typedef
33+ , typetable
34+ , typeview
35+ , bool
36+ , int2
37+ , smallint
38+ , int4
39+ , int
40+ , integer
41+ , int8
42+ , bigint
43+ , numeric
44+ , float4
45+ , real
46+ , float8
47+ , doublePrecision
48+ , money
49+ , text
50+ , char
51+ , character
52+ , varchar
53+ , characterVarying
54+ , bytea
55+ , timestamp
56+ , timestampWithTimeZone
57+ , date
58+ , time
59+ , timeWithTimeZone
60+ , interval
61+ , uuid
62+ , inet
63+ , json
64+ , jsonb
65+ , vararray
66+ , fixarray
67+ , tsvector
68+ , tsquery
69+ ) where
70+
71+import Control.DeepSeq
72+import Data.ByteString
73+import Data.String
74+import GHC.TypeLits
75+
76+import qualified Data.ByteString as ByteString
77+import qualified GHC.Generics as GHC
78+import qualified Generics.SOP as SOP
79+
80+import Squeal.PostgreSQL.Alias
81+import Squeal.PostgreSQL.Expression
82+import Squeal.PostgreSQL.Render
83+import Squeal.PostgreSQL.Schema
84+
85+-- $setup
86+-- >>> import Squeal.PostgreSQL
87+
88+-- When a `cast` is applied to an `Expression` of a known type, it
89+-- represents a run-time type conversion. The cast will succeed only if a
90+-- suitable type conversion operation has been defined.
91+--
92+-- | >>> printSQL $ true & cast int4
93+-- (TRUE :: int4)
94+cast
95+ :: TypeExpression schemas ty1
96+ -- ^ type to cast as
97+ -> Expression outer commons grp schemas params from ty0
98+ -- ^ value to convert
99+ -> Expression outer commons grp schemas params from ty1
100+cast ty x = UnsafeExpression $ parenthesized $
101+ renderSQL x <+> "::" <+> renderSQL ty
102+
103+
104+{-----------------------------------------
105+type expressions
106+-----------------------------------------}
107+
108+-- | `TypeExpression`s are used in `cast`s and
109+-- `Squeal.PostgreSQL.Definition.createTable` commands.
110+newtype TypeExpression (schemas :: SchemasType) (ty :: NullityType)
111+ = UnsafeTypeExpression { renderTypeExpression :: ByteString }
112+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
113+instance RenderSQL (TypeExpression schemas ty) where
114+ renderSQL = renderTypeExpression
115+
116+-- | The enum or composite type in a `Typedef` can be expressed by its alias.
117+typedef
118+ :: (Has sch schemas schema, Has td schema ('Typedef ty))
119+ => QualifiedAlias sch td
120+ -> TypeExpression schemas (null ty)
121+typedef = UnsafeTypeExpression . renderSQL
122+
123+-- | The composite type corresponding to a `Table` definition can be expressed
124+-- by its alias.
125+typetable
126+ :: (Has sch schemas schema, Has tab schema ('Table table))
127+ => QualifiedAlias sch tab
128+ -> TypeExpression schemas (null ('PGcomposite (TableToRow table)))
129+typetable = UnsafeTypeExpression . renderSQL
130+
131+-- | The composite type corresponding to a `View` definition can be expressed
132+-- by its alias.
133+typeview
134+ :: (Has sch schemas schema, Has vw schema ('View view))
135+ => QualifiedAlias sch vw
136+ -> TypeExpression schemas (null ('PGcomposite view))
137+typeview = UnsafeTypeExpression . renderSQL
138+
139+-- | logical Boolean (true/false)
140+bool :: TypeExpression schemas (null 'PGbool)
141+bool = UnsafeTypeExpression "bool"
142+-- | signed two-byte integer
143+int2, smallint :: TypeExpression schemas (null 'PGint2)
144+int2 = UnsafeTypeExpression "int2"
145+smallint = UnsafeTypeExpression "smallint"
146+-- | signed four-byte integer
147+int4, int, integer :: TypeExpression schemas (null 'PGint4)
148+int4 = UnsafeTypeExpression "int4"
149+int = UnsafeTypeExpression "int"
150+integer = UnsafeTypeExpression "integer"
151+-- | signed eight-byte integer
152+int8, bigint :: TypeExpression schemas (null 'PGint8)
153+int8 = UnsafeTypeExpression "int8"
154+bigint = UnsafeTypeExpression "bigint"
155+-- | arbitrary precision numeric type
156+numeric :: TypeExpression schemas (null 'PGnumeric)
157+numeric = UnsafeTypeExpression "numeric"
158+-- | single precision floating-point number (4 bytes)
159+float4, real :: TypeExpression schemas (null 'PGfloat4)
160+float4 = UnsafeTypeExpression "float4"
161+real = UnsafeTypeExpression "real"
162+-- | double precision floating-point number (8 bytes)
163+float8, doublePrecision :: TypeExpression schemas (null 'PGfloat8)
164+float8 = UnsafeTypeExpression "float8"
165+doublePrecision = UnsafeTypeExpression "double precision"
166+-- | currency amount
167+money :: TypeExpression schema (null 'PGmoney)
168+money = UnsafeTypeExpression "money"
169+-- | variable-length character string
170+text :: TypeExpression schemas (null 'PGtext)
171+text = UnsafeTypeExpression "text"
172+-- | fixed-length character string
173+char, character
174+ :: forall n schemas null. (KnownNat n, 1 <= n)
175+ => TypeExpression schemas (null ('PGchar n))
176+char = UnsafeTypeExpression $ "char(" <> renderNat @n <> ")"
177+character = UnsafeTypeExpression $ "character(" <> renderNat @n <> ")"
178+-- | variable-length character string
179+varchar, characterVarying
180+ :: forall n schemas null. (KnownNat n, 1 <= n)
181+ => TypeExpression schemas (null ('PGvarchar n))
182+varchar = UnsafeTypeExpression $ "varchar(" <> renderNat @n <> ")"
183+characterVarying = UnsafeTypeExpression $
184+ "character varying(" <> renderNat @n <> ")"
185+-- | binary data ("byte array")
186+bytea :: TypeExpression schemas (null 'PGbytea)
187+bytea = UnsafeTypeExpression "bytea"
188+-- | date and time (no time zone)
189+timestamp :: TypeExpression schemas (null 'PGtimestamp)
190+timestamp = UnsafeTypeExpression "timestamp"
191+-- | date and time, including time zone
192+timestampWithTimeZone :: TypeExpression schemas (null 'PGtimestamptz)
193+timestampWithTimeZone = UnsafeTypeExpression "timestamp with time zone"
194+-- | calendar date (year, month, day)
195+date :: TypeExpression schemas (null 'PGdate)
196+date = UnsafeTypeExpression "date"
197+-- | time of day (no time zone)
198+time :: TypeExpression schemas (null 'PGtime)
199+time = UnsafeTypeExpression "time"
200+-- | time of day, including time zone
201+timeWithTimeZone :: TypeExpression schemas (null 'PGtimetz)
202+timeWithTimeZone = UnsafeTypeExpression "time with time zone"
203+-- | time span
204+interval :: TypeExpression schemas (null 'PGinterval)
205+interval = UnsafeTypeExpression "interval"
206+-- | universally unique identifier
207+uuid :: TypeExpression schemas (null 'PGuuid)
208+uuid = UnsafeTypeExpression "uuid"
209+-- | IPv4 or IPv6 host address
210+inet :: TypeExpression schemas (null 'PGinet)
211+inet = UnsafeTypeExpression "inet"
212+-- | textual JSON data
213+json :: TypeExpression schemas (null 'PGjson)
214+json = UnsafeTypeExpression "json"
215+-- | binary JSON data, decomposed
216+jsonb :: TypeExpression schemas (null 'PGjsonb)
217+jsonb = UnsafeTypeExpression "jsonb"
218+-- | variable length array
219+vararray
220+ :: TypeExpression schemas pg
221+ -> TypeExpression schemas (null ('PGvararray pg))
222+vararray ty = UnsafeTypeExpression $ renderSQL ty <> "[]"
223+-- | fixed length array
224+--
225+-- >>> renderSQL (fixarray @'[2] json)
226+-- "json[2]"
227+fixarray
228+ :: forall dims schemas null pg. SOP.All KnownNat dims
229+ => TypeExpression schemas pg
230+ -> TypeExpression schemas (null ('PGfixarray dims pg))
231+fixarray ty = UnsafeTypeExpression $
232+ renderSQL ty <> renderDims @dims
233+ where
234+ renderDims :: forall ns. SOP.All KnownNat ns => ByteString
235+ renderDims =
236+ ("[" <>)
237+ . (<> "]")
238+ . ByteString.intercalate "]["
239+ . SOP.hcollapse
240+ $ SOP.hcmap (SOP.Proxy @KnownNat)
241+ (K . fromString . show . natVal)
242+ (SOP.hpure SOP.Proxy :: SOP.NP SOP.Proxy ns)
243+-- | text search query
244+tsvector :: TypeExpression schemas (null 'PGtsvector)
245+tsvector = UnsafeTypeExpression "tsvector"
246+-- | text search document
247+tsquery :: TypeExpression schemas (null 'PGtsquery)
248+tsquery = UnsafeTypeExpression "tsquery"
249+
250+-- | `pgtype` is a demoted version of a `PGType`
251+class PGTyped schemas (ty :: NullityType) where
252+ pgtype :: TypeExpression schemas ty
253+instance PGTyped schemas (null 'PGbool) where pgtype = bool
254+instance PGTyped schemas (null 'PGint2) where pgtype = int2
255+instance PGTyped schemas (null 'PGint4) where pgtype = int4
256+instance PGTyped schemas (null 'PGint8) where pgtype = int8
257+instance PGTyped schemas (null 'PGnumeric) where pgtype = numeric
258+instance PGTyped schemas (null 'PGfloat4) where pgtype = float4
259+instance PGTyped schemas (null 'PGfloat8) where pgtype = float8
260+instance PGTyped schemas (null 'PGmoney) where pgtype = money
261+instance PGTyped schemas (null 'PGtext) where pgtype = text
262+instance (KnownNat n, 1 <= n)
263+ => PGTyped schemas (null ('PGchar n)) where pgtype = char @n
264+instance (KnownNat n, 1 <= n)
265+ => PGTyped schemas (null ('PGvarchar n)) where pgtype = varchar @n
266+instance PGTyped schemas (null 'PGbytea) where pgtype = bytea
267+instance PGTyped schemas (null 'PGtimestamp) where pgtype = timestamp
268+instance PGTyped schemas (null 'PGtimestamptz) where pgtype = timestampWithTimeZone
269+instance PGTyped schemas (null 'PGdate) where pgtype = date
270+instance PGTyped schemas (null 'PGtime) where pgtype = time
271+instance PGTyped schemas (null 'PGtimetz) where pgtype = timeWithTimeZone
272+instance PGTyped schemas (null 'PGinterval) where pgtype = interval
273+instance PGTyped schemas (null 'PGuuid) where pgtype = uuid
274+instance PGTyped schemas (null 'PGjson) where pgtype = json
275+instance PGTyped schemas (null 'PGjsonb) where pgtype = jsonb
276+instance PGTyped schemas ty
277+ => PGTyped schemas (null ('PGvararray ty)) where
278+ pgtype = vararray (pgtype @schemas @ty)
279+instance (SOP.All KnownNat dims, PGTyped schemas ty)
280+ => PGTyped schemas (null ('PGfixarray dims ty)) where
281+ pgtype = fixarray @dims (pgtype @schemas @ty)
282+instance PGTyped schemas (null 'PGtsvector) where pgtype = tsvector
283+instance PGTyped schemas (null 'PGtsquery) where pgtype = tsquery
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Window.hsnew file+284
@@ -0,0 +1,284 @@
1+{-|
2+Module: Squeal.PostgreSQL.Expression.Window
3+Description: Window functions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Window functions and definitions
9+-}
10+
11+{-# LANGUAGE
12+ DataKinds
13+ , DeriveGeneric
14+ , FlexibleContexts
15+ , FlexibleInstances
16+ , GADTs
17+ , GeneralizedNewtypeDeriving
18+ , LambdaCase
19+ , MultiParamTypeClasses
20+ , OverloadedStrings
21+ , RankNTypes
22+ , KindSignatures
23+#-}
24+
25+module Squeal.PostgreSQL.Expression.Window
26+ ( -- * functions
27+ partitionBy
28+ , rank
29+ , rowNumber
30+ , denseRank
31+ , percentRank
32+ , cumeDist
33+ , ntile
34+ , lag
35+ , lead
36+ , firstValue
37+ , lastValue
38+ , nthValue
39+ , unsafeWindowFunction1
40+ , unsafeWindowFunctionN
41+ -- * types
42+ , WindowFunction (..)
43+ , WindowDefinition (..)
44+ , WinFun0
45+ , WinFun1
46+ , WinFunN
47+ ) where
48+
49+import Control.DeepSeq
50+import Data.ByteString
51+
52+import qualified GHC.Generics as GHC
53+import qualified Generics.SOP as SOP
54+
55+import Squeal.PostgreSQL.Alias
56+import Squeal.PostgreSQL.Expression
57+import Squeal.PostgreSQL.Expression.Aggregate
58+import Squeal.PostgreSQL.Expression.Sort
59+import Squeal.PostgreSQL.List
60+import Squeal.PostgreSQL.Render
61+import Squeal.PostgreSQL.Schema
62+
63+instance Aggregate
64+ (Expression outer commons grp schemas params from)
65+ (NP (Expression outer commons grp schemas params from))
66+ (WindowFunction outer commons grp schemas params from) where
67+ countStar = UnsafeWindowFunction "count(*)"
68+ count = unsafeWindowFunction1 "count"
69+ sum_ = unsafeWindowFunction1 "sum"
70+ arrayAgg = unsafeWindowFunction1 "array_agg"
71+ jsonAgg = unsafeWindowFunction1 "json_agg"
72+ jsonbAgg = unsafeWindowFunction1 "jsonb_agg"
73+ bitAnd = unsafeWindowFunction1 "bit_and"
74+ bitOr = unsafeWindowFunction1 "bit_or"
75+ boolAnd = unsafeWindowFunction1 "bool_and"
76+ boolOr = unsafeWindowFunction1 "bool_or"
77+ every = unsafeWindowFunction1 "every"
78+ max_ = unsafeWindowFunction1 "max"
79+ min_ = unsafeWindowFunction1 "min"
80+ avg = unsafeWindowFunction1 "avg"
81+ corr = unsafeWindowFunctionN "corr"
82+ covarPop = unsafeWindowFunctionN "covar_pop"
83+ covarSamp = unsafeWindowFunctionN "covar_samp"
84+ regrAvgX = unsafeWindowFunctionN "regr_avgx"
85+ regrAvgY = unsafeWindowFunctionN "regr_avgy"
86+ regrCount = unsafeWindowFunctionN "regr_count"
87+ regrIntercept = unsafeWindowFunctionN "regr_intercept"
88+ regrR2 = unsafeWindowFunctionN "regr_r2"
89+ regrSlope = unsafeWindowFunctionN "regr_slope"
90+ regrSxx = unsafeWindowFunctionN "regr_sxx"
91+ regrSxy = unsafeWindowFunctionN "regr_sxy"
92+ regrSyy = unsafeWindowFunctionN "regr_syy"
93+ stddev = unsafeWindowFunction1 "stddev"
94+ stddevPop = unsafeWindowFunction1 "stddev_pop"
95+ stddevSamp = unsafeWindowFunction1 "stddev_samp"
96+ variance = unsafeWindowFunction1 "variance"
97+ varPop = unsafeWindowFunction1 "var_pop"
98+ varSamp = unsafeWindowFunction1 "var_samp"
99+
100+-- | A `WindowDefinition` is a set of table rows that are somehow related
101+-- to the current row
102+data WindowDefinition outer commons grp schemas params from where
103+ WindowDefinition
104+ :: SOP.SListI bys
105+ => NP (Expression outer commons grp schemas params from) bys
106+ -- ^ `partitionBy` clause
107+ -> [SortExpression outer commons grp schemas params from]
108+ -- ^ `Squeal.PostgreSQL.Expression.Sort.orderBy` clause
109+ -> WindowDefinition outer commons grp schemas params from
110+
111+instance OrderBy WindowDefinition where
112+ orderBy sortsR (WindowDefinition parts sortsL)
113+ = WindowDefinition parts (sortsL ++ sortsR)
114+
115+instance RenderSQL (WindowDefinition outer commons schemas from grp params) where
116+ renderSQL (WindowDefinition part ord) =
117+ renderPartitionByClause part <> renderOrderByClause ord
118+ where
119+ renderPartitionByClause = \case
120+ Nil -> ""
121+ parts -> "PARTITION" <+> "BY" <+> renderCommaSeparated renderExpression parts
122+ renderOrderByClause = \case
123+ [] -> ""
124+ srts -> " ORDER" <+> "BY"
125+ <+> commaSeparated (renderSQL <$> srts)
126+
127+{- |
128+The `partitionBy` clause within `Squeal.PostgreSQL.Query.Over` divides the rows into groups,
129+or partitions, that share the same values of the `partitionBy` `Expression`(s).
130+For each row, the window function is computed across the rows that fall into
131+the same partition as the current row.
132+-}
133+partitionBy
134+ :: SOP.SListI bys
135+ => NP (Expression outer commons grp schemas params from) bys -- ^ partitions
136+ -> WindowDefinition outer commons grp schemas params from
137+partitionBy bys = WindowDefinition bys []
138+
139+{- |
140+A window function performs a calculation across a set of table rows
141+that are somehow related to the current row. This is comparable to the type
142+of calculation that can be done with an aggregate function.
143+However, window functions do not cause rows to become grouped into a single
144+output row like non-window aggregate calls would.
145+Instead, the rows retain their separate identities.
146+Behind the scenes, the window function is able to access more than
147+just the current row of the query result.
148+-}
149+newtype WindowFunction
150+ (outer :: FromType)
151+ (commons :: FromType)
152+ (grp :: Grouping)
153+ (schemas :: SchemasType)
154+ (params :: [NullityType])
155+ (from :: FromType)
156+ (ty :: NullityType)
157+ = UnsafeWindowFunction { renderWindowFunction :: ByteString }
158+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
159+
160+instance RenderSQL (WindowFunction outer commons grp schemas params from ty) where
161+ renderSQL = renderWindowFunction
162+
163+{- |
164+A @RankNType@ for window functions with no arguments.
165+-}
166+type WinFun0 x
167+ = forall outer commons grp schemas params from
168+ . WindowFunction outer commons grp schemas params from x
169+ -- ^ cannot reference aliases
170+
171+{- |
172+A @RankNType@ for window functions with 1 argument.
173+-}
174+type WinFun1 x y
175+ = forall outer commons grp schemas params from
176+ . Expression outer commons grp schemas params from x
177+ -- ^ input
178+ -> WindowFunction outer commons grp schemas params from y
179+ -- ^ output
180+
181+{- | A @RankNType@ for window functions with a fixed-length
182+list of heterogeneous arguments.
183+Use the `*:` operator to end your argument lists.
184+-}
185+type WinFunN xs y
186+ = forall outer commons grp schemas params from
187+ . NP (Expression outer commons grp schemas params from) xs
188+ -- ^ inputs
189+ -> WindowFunction outer commons grp schemas params from y
190+ -- ^ output
191+
192+-- | escape hatch for defining window functions
193+unsafeWindowFunction1 :: ByteString -> WinFun1 x y
194+unsafeWindowFunction1 fun x
195+ = UnsafeWindowFunction $ fun <> parenthesized (renderSQL x)
196+
197+-- | escape hatch for defining multi-argument window functions
198+unsafeWindowFunctionN :: SOP.SListI xs => ByteString -> WinFunN xs y
199+unsafeWindowFunctionN fun xs = UnsafeWindowFunction $ fun <>
200+ parenthesized (renderCommaSeparated renderSQL xs)
201+
202+{- | rank of the current row with gaps; same as `rowNumber` of its first peer
203+
204+>>> printSQL rank
205+rank()
206+-}
207+rank :: WinFun0 ('NotNull 'PGint8)
208+rank = UnsafeWindowFunction "rank()"
209+
210+{- | number of the current row within its partition, counting from 1
211+
212+>>> printSQL rowNumber
213+row_number()
214+-}
215+rowNumber :: WinFun0 ('NotNull 'PGint8)
216+rowNumber = UnsafeWindowFunction "row_number()"
217+
218+{- | rank of the current row without gaps; this function counts peer groups
219+
220+>>> printSQL denseRank
221+dense_rank()
222+-}
223+denseRank :: WinFun0 ('NotNull 'PGint8)
224+denseRank = UnsafeWindowFunction "dense_rank()"
225+
226+{- | relative rank of the current row: (rank - 1) / (total partition rows - 1)
227+
228+>>> printSQL percentRank
229+percent_rank()
230+-}
231+percentRank :: WinFun0 ('NotNull 'PGfloat8)
232+percentRank = UnsafeWindowFunction "percent_rank()"
233+
234+{- | cumulative distribution: (number of partition rows
235+preceding or peer with current row) / total partition rows
236+
237+>>> printSQL cumeDist
238+cume_dist()
239+-}
240+cumeDist :: WinFun0 ('NotNull 'PGfloat8)
241+cumeDist = UnsafeWindowFunction "cume_dist()"
242+
243+{- | integer ranging from 1 to the argument value,
244+dividing the partition as equally as possible
245+
246+>>> printSQL $ ntile 5
247+ntile(5)
248+-}
249+ntile :: WinFun1 ('NotNull 'PGint4) ('NotNull 'PGint4)
250+ntile = unsafeWindowFunction1 "ntile"
251+
252+{- | returns value evaluated at the row that is offset rows before the current
253+row within the partition; if there is no such row, instead return default
254+(which must be of the same type as value). Both offset and default are
255+evaluated with respect to the current row.
256+-}
257+lag :: WinFunN '[ty, 'NotNull 'PGint4, ty] ty
258+lag = unsafeWindowFunctionN "lag"
259+
260+{- | returns value evaluated at the row that is offset rows after the current
261+row within the partition; if there is no such row, instead return default
262+(which must be of the same type as value). Both offset and default are
263+evaluated with respect to the current row.
264+-}
265+lead :: WinFunN '[ty, 'NotNull 'PGint4, ty] ty
266+lead = unsafeWindowFunctionN "lead"
267+
268+{- | returns value evaluated at the row that is the
269+first row of the window frame
270+-}
271+firstValue :: WinFun1 ty ty
272+firstValue = unsafeWindowFunction1 "first_value"
273+
274+{- | returns value evaluated at the row that is the
275+last row of the window frame
276+-}
277+lastValue :: WinFun1 ty ty
278+lastValue = unsafeWindowFunction1 "last_value"
279+
280+{- | returns value evaluated at the row that is the nth
281+row of the window frame (counting from 1); null if no such row
282+-}
283+nthValue :: WinFunN '[null ty, 'NotNull 'PGint4] ('Null ty)
284+nthValue = unsafeWindowFunctionN "nth_value"
squeal-postgresql/src/Squeal/PostgreSQL/List.hsnew file+141
@@ -0,0 +1,141 @@
1+{-|
2+Module: Squeal.PostgreSQL.List
3+Description: List related types and functions
4+Copyright: (c) Eitan Chatav, 2019
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Haskell singly-linked lists are very powerful. This module
9+provides functionality for type-level lists, heterogeneous
10+lists and type aligned lists.
11+-}
12+
13+{-# LANGUAGE
14+ DataKinds
15+ , FlexibleContexts
16+ , GADTs
17+ , LambdaCase
18+ , MultiParamTypeClasses
19+ , OverloadedStrings
20+ , PolyKinds
21+ , QuantifiedConstraints
22+ , RankNTypes
23+ , ScopedTypeVariables
24+ , TypeApplications
25+ , TypeFamilies
26+ , TypeOperators
27+ , UndecidableInstances
28+#-}
29+
30+module Squeal.PostgreSQL.List
31+ ( SOP.NP (..)
32+ , (*:)
33+ , Join
34+ , disjoin
35+ , Additional (..)
36+ , AlignedList (..)
37+ , single
38+ , extractList
39+ , mapAligned
40+ , Elem
41+ , In
42+ , Length
43+ ) where
44+
45+import Control.Category
46+import Data.Function ((&))
47+import Data.Kind
48+import Data.Type.Bool
49+import GHC.TypeLits
50+
51+import Generics.SOP as SOP
52+
53+import Squeal.PostgreSQL.Render
54+
55+-- | `Join` is simply promoted `++` and is used in @JOIN@s in
56+-- `Squeal.PostgreSQL.Query.FromClause`s.
57+type family Join xs ys where
58+ Join '[] ys = ys
59+ Join (x ': xs) ys = x ': Join xs ys
60+
61+-- | `disjoin` is a utility function for splitting an `NP` list into pieces.
62+disjoin
63+ :: forall xs ys expr. SListI xs
64+ => NP expr (Join xs ys)
65+ -> (NP expr xs, NP expr ys)
66+disjoin = case sList @xs of
67+ SNil -> \ys -> (Nil, ys)
68+ SCons -> \(x :* xsys) ->
69+ case disjoin xsys of (xs,ys) -> (x :* xs, ys)
70+
71+-- | The `Additional` class is for appending
72+-- type-level list parameterized constructors such as `NP`,
73+-- `Squeal.PostgreSQL.Query.Selection`, and `Squeal.PostgreSQL.Query.FromClause`.
74+class Additional expr where
75+ also :: expr ys -> expr xs -> expr (Join xs ys)
76+instance Additional (NP expr) where
77+ also ys = \case
78+ Nil -> ys
79+ x :* xs -> x :* (xs & also ys)
80+
81+-- | An `AlignedList` is a type-aligned list or free category.
82+data AlignedList p x0 x1 where
83+ Done :: AlignedList p x x
84+ (:>>) :: p x0 x1 -> AlignedList p x1 x2 -> AlignedList p x0 x2
85+infixr 7 :>>
86+instance Category (AlignedList p) where
87+ id = Done
88+ (.) list = \case
89+ Done -> list
90+ step :>> steps -> step :>> (steps >>> list)
91+instance (forall t0 t1. RenderSQL (p t0 t1))
92+ => RenderSQL (AlignedList p x0 x1) where
93+ renderSQL = \case
94+ Done -> ""
95+ step :>> Done -> renderSQL step
96+ step :>> steps -> renderSQL step <> ", " <> renderSQL steps
97+
98+-- | `extractList` turns an `AlignedList` into a standard list.
99+extractList :: (forall a0 a1. p a0 a1 -> b) -> AlignedList p x0 x1 -> [b]
100+extractList f = \case
101+ Done -> []
102+ step :>> steps -> (f step):extractList f steps
103+
104+-- | `mapAligned` applies a function to each element of an `AlignedList`.
105+mapAligned
106+ :: (forall z0 z1. p z0 z1 -> q z0 z1)
107+ -> AlignedList p x0 x1
108+ -> AlignedList q x0 x1
109+mapAligned f = \case
110+ Done -> Done
111+ x :>> xs -> f x :>> mapAligned f xs
112+
113+-- | A `single` step.
114+single :: p x0 x1 -> AlignedList p x0 x1
115+single step = step :>> Done
116+
117+-- | A useful operator for ending an `NP` list of length at least 2 without `Nil`.
118+(*:) :: f x -> f y -> NP f '[x,y]
119+x *: y = x :* y :* Nil
120+infixl 9 *:
121+
122+-- | @Elem@ is a promoted `Data.List.elem`.
123+type family Elem x xs where
124+ Elem x '[] = 'False
125+ Elem x (x ': xs) = 'True
126+ Elem x (_ ': xs) = Elem x xs
127+
128+-- | @In x xs@ is a constraint that proves that @x@ is in @xs@.
129+type family In x xs :: Constraint where
130+ In x xs = If (Elem x xs) (() :: Constraint)
131+ (TypeError ('ShowType x ':<>: 'Text "is not in " ':<>: 'ShowType xs))
132+
133+{- | Calculate the `Length` of a type level list
134+
135+>>> :kind! Length '[Char,String,Bool,Double]
136+Length '[Char,String,Bool,Double] :: Nat
137+= 4
138+-}
139+type family Length (xs :: [k]) :: Nat where
140+ Length (x : xs) = 1 + Length xs
141+ Length '[] = 0
squeal-postgresql/src/Squeal/PostgreSQL/Manipulation.hs+7−16
@@ -51,7 +51,6 @@ module Squeal.PostgreSQL.Manipulation
5151 -- * Delete
5252 , deleteFrom
5353 , deleteFrom_
54− , also
5554 ) where
5655
5756import Control.DeepSeq
@@ -62,12 +61,18 @@ import GHC.TypeLits (KnownSymbol)
6261import qualified Generics.SOP as SOP
6362import qualified GHC.Generics as GHC
6463
65−import Squeal.PostgreSQL.Binary
64+import Squeal.PostgreSQL.Alias
6665import Squeal.PostgreSQL.Expression
66+import Squeal.PostgreSQL.Expression.Logic
67+import Squeal.PostgreSQL.List
68+import Squeal.PostgreSQL.PG
6769import Squeal.PostgreSQL.Render
6870import Squeal.PostgreSQL.Query
6971import Squeal.PostgreSQL.Schema
7072
73+-- $setup
74+-- >>> import Squeal.PostgreSQL
75+
7176{- |
7277A `Manipulation` is a statement which may modify data in the database,
7378but does not alter its schemas. Examples are inserts, updates and deletes.
@@ -569,17 +574,3 @@ deleteFrom_
569574 -- ^ condition under which to delete a row
570575 -> Manipulation commons schemas params '[]
571576deleteFrom_ tab wh = deleteFrom tab NoUsing wh (Returning_ Nil)
572−
573−-- | This has the behaviour of a cartesian product, taking all
574−-- possible combinations between @left@ and @right@ - exactly like a
575−-- `crossJoin`. Used when no `crossJoin` syntax is required but simply
576−-- a comma separated list of tables. Typical case is the `UsingClause`
577−-- of a `deleteFrom` query.
578−also
579− :: FromClause outer commons schemas params right
580− -- ^ right
581− -> FromClause outer commons schemas params left
582− -- ^ left
583− -> FromClause outer commons schemas params (Join left right)
584−also right left = UnsafeFromClause $
585− renderSQL left <> "," <+> renderSQL right
squeal-postgresql/src/Squeal/PostgreSQL/Migration.hs+30−31
@@ -120,6 +120,7 @@ In addition to enabling `Migration`s using pure SQL `Definition`s for
120120the `up` and `down` instructions, you can also perform impure `IO` actions
121121by using a `Migration`s over the `Terminally` `PQ` `IO` category.
122122-}
123+
123124{-# LANGUAGE
124125 DataKinds
125126 , DeriveGeneric
@@ -147,22 +148,24 @@ module Squeal.PostgreSQL.Migration
147148 , defaultMain
148149 ) where
149150
150−import Control.Category
151−import Control.Monad
152−import Data.ByteString (ByteString)
153−import Data.Foldable (traverse_)
154−import Data.Function ((&))
155−import Data.List ((\\))
156−import Data.Text (Text)
157−import qualified Data.Text.IO as T (putStrLn)
158−import Data.Time (UTCTime)
159−import Generics.SOP (K (..))
160−import qualified Generics.SOP as SOP
161−import qualified GHC.Generics as GHC
162−import Prelude hiding ((.), id)
163−import Squeal.PostgreSQL
164−import System.Environment
165−import UnliftIO (MonadIO (..))
151+import Control.Category
152+import Control.Monad
153+import Data.ByteString (ByteString)
154+import Data.Foldable (traverse_)
155+import Data.Function ((&))
156+import Data.List ((\\))
157+import Data.Text (Text)
158+import Data.Time (UTCTime)
159+import Generics.SOP (K (..))
160+import Prelude hiding ((.), id)
161+import System.Environment
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
167+
168+import Squeal.PostgreSQL
166169
167170-- | A `Migration` is a named "isomorphism" over a given category.
168171-- It should contain an inverse pair of `up` and `down`
@@ -202,14 +205,14 @@ class Category p => Migratory p where
202205 -> PQ schemas1 schemas0 IO ()
203206
204207instance Migratory Definition where
205− migrateUp = migrateUp . alignedMap pureMigration
206− migrateDown = migrateDown . alignedMap pureMigration
208+ migrateUp = migrateUp . mapAligned pureMigration
209+ migrateDown = migrateDown . mapAligned pureMigration
207210
208211{- | `Terminally` turns an indexed monad transformer and the monad it transforms
209212into a category by restricting the return type to @()@ and permuting the type variables.
210213This is similar to how applying a monad to @()@ yields a monoid.
211214Since a `Terminally` action has a trivial return value, the only reason
212−to run one is for the side effects, in particular database effects.
215+to run one is for the side effects, in particular database and other IO effects.
213216-}
214217newtype Terminally trans monad x0 x1 = Terminally
215218 { runTerminally :: trans x0 x1 monad () }
@@ -352,18 +355,14 @@ createMigrations =
352355
353356-- | Inserts a `Migration` into the `MigrationsTable`, returning
354357-- the time at which it was inserted.
355−insertMigration
356− :: Manipulation_ MigrationsSchemas (Only Text) (P ("executed_at" ::: UTCTime))
357−insertMigration = insertInto #schema_migrations
358+insertMigration :: Manipulation_ MigrationsSchemas (Only Text) ()
359+insertMigration = insertInto_ #schema_migrations
358360 (Values_ ((param @1) `as` #name :* defaultAs #executed_at))
359− OnConflictDoRaise (Returning #executed_at)
360361
361362-- | Deletes a `Migration` from the `MigrationsTable`, returning
362363-- the time at which it was inserted.
363−deleteMigration
364− :: Manipulation_ MigrationsSchemas (Only Text) (P ("executed_at" ::: UTCTime))
365−deleteMigration = deleteFrom #schema_migrations
366− NoUsing (#name .== param @1) (Returning #executed_at)
364+deleteMigration :: Manipulation_ MigrationsSchemas (Only Text) ()
365+deleteMigration = deleteFrom_ #schema_migrations (#name .== param @1)
367366
368367-- | Selects a `Migration` from the `MigrationsTable`, returning
369368-- the time at which it was inserted.
@@ -437,17 +436,17 @@ defaultMain connectTo migrations = do
437436 fmap migrationName <$> (unsafePQ (define createMigrations & pqThen (runQuery selectMigrations)) >>= getRows)
438437
439438 displayListOfNames :: [Text] -> IO ()
440− displayListOfNames [] = T.putStrLn " None"
439+ displayListOfNames [] = Text.putStrLn " None"
441440 displayListOfNames xs =
442− let singleName n = T.putStrLn $ " - " <> n
441+ let singleName n = Text.putStrLn $ " - " <> n
443442 in traverse_ singleName xs
444443
445444 displayUnrunned :: [Text] -> IO ()
446445 displayUnrunned unrunned =
447− T.putStrLn "Migrations left to run:"
446+ Text.putStrLn "Migrations left to run:"
448447 >> displayListOfNames unrunned
449448
450449 displayRunned :: [Text] -> IO ()
451450 displayRunned runned =
452− T.putStrLn "Migrations already run:"
451+ Text.putStrLn "Migrations already run:"
453452 >> displayListOfNames runned
squeal-postgresql/src/Squeal/PostgreSQL/PG.hsnew file+389
@@ -0,0 +1,389 @@
1+{-|
2+Module: Squeal.PostgreSQL.PG
3+Description: Embedding of Haskell types into Postgres's type system
4+Copyright: (c) Eitan Chatav, 2010
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+`Squeal.PostgreSQL.PG` provides type families for turning Haskell
9+`Type`s into corresponding Postgres types.
10+-}
11+{-# LANGUAGE
12+ AllowAmbiguousTypes
13+ , DeriveFoldable
14+ , DeriveFunctor
15+ , DeriveGeneric
16+ , DeriveTraversable
17+ , DefaultSignatures
18+ , FlexibleContexts
19+ , FlexibleInstances
20+ , FunctionalDependencies
21+ , GADTs
22+ , LambdaCase
23+ , OverloadedStrings
24+ , MultiParamTypeClasses
25+ , ScopedTypeVariables
26+ , TypeApplications
27+ , TypeFamilies
28+ , TypeInType
29+ , TypeOperators
30+ , UndecidableInstances
31+ , UndecidableSuperClasses
32+#-}
33+
34+module Squeal.PostgreSQL.PG
35+ ( -- * PG embeddings
36+ PG
37+ , NullPG
38+ , TuplePG
39+ , RowPG
40+ -- * Storage newtypes
41+ , Money (..)
42+ , Json (..)
43+ , Jsonb (..)
44+ , Composite (..)
45+ , Enumerated (..)
46+ , VarArray (..)
47+ , FixArray (..)
48+ , SOP.P (..)
49+ -- * Type families
50+ , LabelsPG
51+ , DimPG
52+ , FixPG
53+ , TupleOf
54+ , TupleCodeOf
55+ , RowOf
56+ , ConstructorsOf
57+ , ConstructorNameOf
58+ , ConstructorNamesOf
59+ ) where
60+
61+import Data.Aeson (Value)
62+import Data.Kind (Type)
63+import Data.Int (Int16, Int32, Int64)
64+import Data.Scientific (Scientific)
65+import Data.Time (Day, DiffTime, LocalTime, TimeOfDay, TimeZone, UTCTime)
66+import Data.Vector (Vector)
67+import Data.Word (Word16, Word32, Word64)
68+import Data.UUID.Types (UUID)
69+import GHC.TypeLits
70+import Network.IP.Addr (NetAddr, IP)
71+
72+import qualified Data.ByteString.Lazy as Lazy (ByteString)
73+import qualified Data.ByteString as Strict (ByteString)
74+import qualified Data.Text.Lazy as Lazy (Text)
75+import qualified Data.Text as Strict (Text)
76+import qualified GHC.Generics as GHC
77+import qualified Generics.SOP as SOP
78+import qualified Generics.SOP.Record as SOP
79+import qualified Generics.SOP.Type.Metadata as Type
80+
81+import Squeal.PostgreSQL.Alias
82+import Squeal.PostgreSQL.List
83+import Squeal.PostgreSQL.Schema
84+
85+-- $setup
86+-- >>> import Squeal.PostgreSQL
87+-- >>> import Data.Text (Text)
88+
89+{- | The `PG` type family embeds a subset of Haskell types
90+as Postgres types. As an open type family, `PG` is extensible.
91+
92+>>> :kind! PG LocalTime
93+PG LocalTime :: PGType
94+= 'PGtimestamp
95+
96+>>> newtype MyDouble = My Double
97+>>> :set -XTypeFamilies
98+>>> type instance PG MyDouble = 'PGfloat8
99+-}
100+type family PG (hask :: Type) :: PGType
101+-- | `PGbool`
102+type instance PG Bool = 'PGbool
103+-- | `PGint2`
104+type instance PG Int16 = 'PGint2
105+-- | `PGint4`
106+type instance PG Int32 = 'PGint4
107+-- | `PGint8`
108+type instance PG Int64 = 'PGint8
109+-- | `PGint2`
110+type instance PG Word16 = 'PGint2
111+-- | `PGint4`
112+type instance PG Word32 = 'PGint4
113+-- | `PGint8`
114+type instance PG Word64 = 'PGint8
115+-- | `PGnumeric`
116+type instance PG Scientific = 'PGnumeric
117+-- | `PGfloat4`
118+type instance PG Float = 'PGfloat4
119+-- | `PGfloat8`
120+type instance PG Double = 'PGfloat8
121+-- | `PGchar` @1@
122+type instance PG Char = 'PGchar 1
123+-- | `PGtext`
124+type instance PG Strict.Text = 'PGtext
125+-- | `PGtext`
126+type instance PG Lazy.Text = 'PGtext
127+-- | `PGtext`
128+type instance PG String = 'PGtext
129+-- | `PGbytea`
130+type instance PG Strict.ByteString = 'PGbytea
131+-- | `PGbytea`
132+type instance PG Lazy.ByteString = 'PGbytea
133+-- | `PGtimestamp`
134+type instance PG LocalTime = 'PGtimestamp
135+-- | `PGtimestamptz`
136+type instance PG UTCTime = 'PGtimestamptz
137+-- | `PGdate`
138+type instance PG Day = 'PGdate
139+-- | `PGtime`
140+type instance PG TimeOfDay = 'PGtime
141+-- | `PGtimetz`
142+type instance PG (TimeOfDay, TimeZone) = 'PGtimetz
143+-- | `PGinterval`
144+type instance PG DiffTime = 'PGinterval
145+-- | `PGuuid`
146+type instance PG UUID = 'PGuuid
147+-- | `PGinet`
148+type instance PG (NetAddr IP) = 'PGinet
149+-- | `PGjson`
150+type instance PG Value = 'PGjson
151+
152+{-| The `LabelsPG` type family calculates the constructors of a
153+Haskell enum type.
154+
155+>>> data Schwarma = Beef | Lamb | Chicken deriving GHC.Generic
156+>>> instance SOP.Generic Schwarma
157+>>> instance SOP.HasDatatypeInfo Schwarma
158+>>> :kind! LabelsPG Schwarma
159+LabelsPG Schwarma :: [Type.ConstructorName]
160+= '["Beef", "Lamb", "Chicken"]
161+-}
162+type family LabelsPG (hask :: Type) :: [Type.ConstructorName] where
163+ LabelsPG hask =
164+ ConstructorNamesOf (ConstructorsOf (SOP.DatatypeInfoOf hask))
165+
166+{- |
167+`RowPG` turns a Haskell `Type` into a `RowType`.
168+
169+`RowPG` may be applied to normal Haskell record types provided they
170+have `SOP.Generic` and `SOP.HasDatatypeInfo` instances;
171+
172+>>> data Person = Person { name :: Strict.Text, age :: Int32 } deriving GHC.Generic
173+>>> instance SOP.Generic Person
174+>>> instance SOP.HasDatatypeInfo Person
175+>>> :kind! RowPG Person
176+RowPG Person :: [(Symbol, NullityType)]
177+= '["name" ::: 'NotNull 'PGtext, "age" ::: 'NotNull 'PGint4]
178+
179+Or to tuples of `SOP.P` types;
180+
181+>>> :kind! RowPG (SOP.P ("id" ::: Int32), SOP.P ("foreign_id" ::: Int32))
182+RowPG (SOP.P ("id" ::: Int32), SOP.P ("foreign_id" ::: Int32)) :: [(Symbol,
183+ NullityType)]
184+= '[ '("id", 'NotNull 'PGint4), "foreign_id" ::: 'NotNull 'PGint4]
185+
186+Or to tuples which mix `SOP.P` types and record types.
187+
188+>>> :kind! RowPG (SOP.P ("id" ::: Int32), Person, SOP.P ("foreign_id" ::: Int32))
189+RowPG (SOP.P ("id" ::: Int32), Person, SOP.P ("foreign_id" ::: Int32)) :: [(Symbol,
190+ NullityType)]
191+= '[ '("id", 'NotNull 'PGint4), '("name", 'NotNull 'PGtext),
192+ '("age", 'NotNull 'PGint4), "foreign_id" ::: 'NotNull 'PGint4]
193+-}
194+type family RowPG (hask :: Type) :: RowType where
195+ RowPG (hask1, hask2) = Join (RowPG hask1) (RowPG hask2)
196+ RowPG (hask1, hask2, hask3) =
197+ Join (RowPG hask1) (RowPG (hask2, hask3))
198+ RowPG (hask1, hask2, hask3, hask4) =
199+ Join (RowPG hask1) (RowPG (hask2, hask3, hask4))
200+ RowPG (hask1, hask2, hask3, hask4, hask5) =
201+ Join (RowPG hask1) (RowPG (hask2, hask3, hask4, hask5))
202+ RowPG (SOP.P (col ::: ty)) = '[col ::: NullPG ty]
203+ RowPG hask = RowOf (SOP.RecordCodeOf hask)
204+
205+-- | `RowOf` applies `NullPG` to the fields of a list.
206+type family RowOf (record :: [(Symbol, Type)]) :: RowType where
207+ RowOf '[] = '[]
208+ RowOf (col ::: ty ': record) = col ::: NullPG ty ': RowOf record
209+
210+{- | `NullPG` turns a Haskell type into a `NullityType`.
211+
212+>>> :kind! NullPG Double
213+NullPG Double :: NullityType
214+= 'NotNull 'PGfloat8
215+>>> :kind! NullPG (Maybe Double)
216+NullPG (Maybe Double) :: NullityType
217+= 'Null 'PGfloat8
218+-}
219+type family NullPG (hask :: Type) :: NullityType where
220+ NullPG (Maybe hask) = 'Null (PG hask)
221+ NullPG hask = 'NotNull (PG hask)
222+
223+{- | `TuplePG` turns a Haskell tuple type (including record types) into
224+the corresponding list of `NullityType`s.
225+
226+>>> :kind! TuplePG (Double, Maybe Char)
227+TuplePG (Double, Maybe Char) :: [NullityType]
228+= '[ 'NotNull 'PGfloat8, 'Null ('PGchar 1)]
229+-}
230+type family TuplePG (hask :: Type) :: [NullityType] where
231+ TuplePG hask = TupleOf (TupleCodeOf hask (SOP.Code hask))
232+
233+-- | `TupleOf` turns a list of Haskell `Type`s into a list of `NullityType`s.
234+type family TupleOf (tuple :: [Type]) :: [NullityType] where
235+ TupleOf '[] = '[]
236+ TupleOf (hask ': tuple) = NullPG hask ': TupleOf tuple
237+
238+-- | `TupleCodeOf` takes the `SOP.Code` of a haskell `Type`
239+-- and if it's a simple product returns it, otherwise giving a `TypeError`.
240+type family TupleCodeOf (hask :: Type) (code :: [[Type]]) :: [Type] where
241+ TupleCodeOf hask '[tuple] = tuple
242+ TupleCodeOf hask '[] =
243+ TypeError
244+ ( 'Text "The type `" ':<>: 'ShowType hask ':<>: 'Text "' is not a tuple type."
245+ ':$$: 'Text "It is a void type with no constructors."
246+ )
247+ TupleCodeOf hask (_ ': _ ': _) =
248+ TypeError
249+ ( 'Text "The type `" ':<>: 'ShowType hask ':<>: 'Text "' is not a tuple type."
250+ ':$$: 'Text "It is a sum type with more than one constructor."
251+ )
252+
253+-- | Calculates constructors of a datatype.
254+type family ConstructorsOf (datatype :: Type.DatatypeInfo)
255+ :: [Type.ConstructorInfo] where
256+ ConstructorsOf ('Type.ADT _module _datatype constructors) =
257+ constructors
258+ ConstructorsOf ('Type.Newtype _module _datatype constructor) =
259+ '[constructor]
260+
261+-- | Calculates the name of a nullary constructor, otherwise
262+-- generates a type error.
263+type family ConstructorNameOf (constructor :: Type.ConstructorInfo)
264+ :: Type.ConstructorName where
265+ ConstructorNameOf ('Type.Constructor name) = name
266+ ConstructorNameOf ('Type.Infix name _assoc _fix) = TypeError
267+ ('Text "ConstructorNameOf error: non-nullary constructor "
268+ ':<>: 'Text name)
269+ ConstructorNameOf ('Type.Record name _fields) = TypeError
270+ ('Text "ConstructorNameOf error: non-nullary constructor "
271+ ':<>: 'Text name)
272+
273+-- | Calculate the names of nullary constructors.
274+type family ConstructorNamesOf (constructors :: [Type.ConstructorInfo])
275+ :: [Type.ConstructorName] where
276+ ConstructorNamesOf '[] = '[]
277+ ConstructorNamesOf (constructor ': constructors) =
278+ ConstructorNameOf constructor ': ConstructorNamesOf constructors
279+
280+-- | `DimPG` turns Haskell nested homogeneous tuples into a list of lengths.
281+type family DimPG (hask :: Type) :: [Nat] where
282+ DimPG (x,x) = 2 ': DimPG x
283+ DimPG (x,x,x) = 3 ': DimPG x
284+ DimPG (x,x,x,x) = 4 ': DimPG x
285+ DimPG (x,x,x,x,x) = 5 ': DimPG x
286+ DimPG (x,x,x,x,x,x) = 6 ': DimPG x
287+ DimPG (x,x,x,x,x,x,x) = 7 ': DimPG x
288+ DimPG (x,x,x,x,x,x,x,x) = 8 ': DimPG x
289+ DimPG (x,x,x,x,x,x,x,x,x) = 9 ': DimPG x
290+ DimPG (x,x,x,x,x,x,x,x,x,x) = 10 ': DimPG x
291+ DimPG x = '[]
292+
293+-- | `FixPG` extracts `NullPG` of the base type of nested homogeneous tuples.
294+type family FixPG (hask :: Type) :: NullityType where
295+ FixPG (x,x) = FixPG x
296+ FixPG (x,x,x) = FixPG x
297+ FixPG (x,x,x,x) = FixPG x
298+ FixPG (x,x,x,x,x) = FixPG x
299+ FixPG (x,x,x,x,x,x) = FixPG x
300+ FixPG (x,x,x,x,x,x,x) = FixPG x
301+ FixPG (x,x,x,x,x,x,x,x) = FixPG x
302+ FixPG (x,x,x,x,x,x,x,x,x) = FixPG x
303+ FixPG (x,x,x,x,x,x,x,x,x,x) = FixPG x
304+ FixPG (x,x,x,x,x,x,x,x,x,x,x) = FixPG x
305+ FixPG x = NullPG x
306+
307+{- | The `Money` newtype stores a monetary value in terms
308+of the number of cents, i.e. @$2,000.20@ would be expressed as
309+@Money { cents = 200020 }@.
310+
311+>>> import Control.Monad (void)
312+>>> import Control.Monad.IO.Class (liftIO)
313+>>> import Squeal.PostgreSQL
314+>>> :{
315+let
316+ roundTrip :: Query_ (Public '[]) (Only Money) (Only Money)
317+ roundTrip = values_ $ parameter @1 money `as` #fromOnly
318+:}
319+
320+>>> let input = Only (Money 20020)
321+
322+>>> :{
323+void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
324+ result <- runQueryParams roundTrip input
325+ Just output <- firstRow result
326+ liftIO . print $ input == output
327+:}
328+True
329+-}
330+newtype Money = Money { cents :: Int64 }
331+ deriving (Eq, Ord, Show, Read, GHC.Generic)
332+-- | `PGmoney`
333+type instance PG Money = 'PGmoney
334+
335+{- | The `Json` newtype is an indication that the Haskell
336+type it's applied to should be stored as a `PGjson`.
337+-}
338+newtype Json hask = Json {getJson :: hask}
339+ deriving (Eq, Ord, Show, Read, GHC.Generic)
340+-- | `PGjson`
341+type instance PG (Json hask) = 'PGjson
342+
343+{- | The `Jsonb` newtype is an indication that the Haskell
344+type it's applied to should be stored as a `PGjsonb`.
345+-}
346+newtype Jsonb hask = Jsonb {getJsonb :: hask}
347+ deriving (Eq, Ord, Show, Read, GHC.Generic)
348+-- | `PGjsonb`
349+type instance PG (Jsonb hask) = 'PGjsonb
350+
351+{- | The `Composite` newtype is an indication that the Haskell
352+type it's applied to should be stored as a `PGcomposite`.
353+-}
354+newtype Composite record = Composite {getComposite :: record}
355+ deriving (Eq, Ord, Show, Read, GHC.Generic)
356+-- | `PGcomposite` @(@`RowPG` @hask)@
357+type instance PG (Composite hask) = 'PGcomposite (RowPG hask)
358+
359+{- | The `Enumerated` newtype is an indication that the Haskell
360+type it's applied to should be stored as a `PGenum`.
361+-}
362+newtype Enumerated enum = Enumerated {getEnumerated :: enum}
363+ deriving (Eq, Ord, Show, Read, GHC.Generic)
364+-- | `PGenum` @(@`LabelsPG` @hask)@
365+type instance PG (Enumerated hask) = 'PGenum (LabelsPG hask)
366+
367+{- | The `VarArray` newtype is an indication that the Haskell
368+type it's applied to should be stored as a `PGvararray`.
369+
370+>>> :kind! PG (VarArray (Vector Double))
371+PG (VarArray (Vector Double)) :: PGType
372+= 'PGvararray ('NotNull 'PGfloat8)
373+-}
374+newtype VarArray arr = VarArray {getVarArray :: arr}
375+ deriving (Eq, Ord, Show, Read, GHC.Generic)
376+-- | `PGvararray` @(@`NullPG` @hask)@
377+type instance PG (VarArray (Vector hask)) = 'PGvararray (NullPG hask)
378+
379+{- | The `FixArray` newtype is an indication that the Haskell
380+type it's applied to should be stored as a `PGfixarray`.
381+
382+>>> :kind! PG (FixArray ((Double, Double), (Double, Double)))
383+PG (FixArray ((Double, Double), (Double, Double))) :: PGType
384+= 'PGfixarray '[2, 2] ('NotNull 'PGfloat8)
385+-}
386+newtype FixArray arr = FixArray {getFixArray :: arr}
387+ deriving (Eq, Ord, Show, Read, GHC.Generic)
388+-- | `PGfixarray` @(@`DimPG` @hask) (@`FixPG` @hask)@
389+type instance PG (FixArray hask) = 'PGfixarray (DimPG hask) (FixPG hask)
squeal-postgresql/src/Squeal/PostgreSQL/PQ.hs+3
@@ -103,6 +103,9 @@ import qualified Control.Monad.Trans.Writer.Strict as Strict
103103import qualified Control.Monad.Trans.RWS.Lazy as Lazy
104104import qualified Control.Monad.Trans.RWS.Strict as Strict
105105
106+-- $setup
107+-- >>> import Squeal.PostgreSQL
108+
106109{- | Makes a new connection to the database server.
107110
108111This function opens a new database connection using the parameters taken
squeal-postgresql/src/Squeal/PostgreSQL/Query.hs+42−288
@@ -1,7 +1,7 @@
11{-|
22Module: Squeal.PostgreSQL.Query
33Description: Squeal queries
4−Copyright: (c) Eitan Chatav, 2017
4+Copyright: (c) Eitan Chatav, 2019
55Maintainer: eitan@morphism.tech
66Stability: experimental
77
@@ -53,23 +53,6 @@ module Squeal.PostgreSQL.Query
5353 -- ** With
5454 , With (..)
5555 , CommonTableExpression (..)
56− -- ** Unnest
57− , unnest
58− -- ** Json
59− , jsonEach
60− , jsonbEach
61− , jsonEachText
62− , jsonbEachText
63− , jsonObjectKeys
64− , jsonbObjectKeys
65− , jsonPopulateRecord
66− , jsonbPopulateRecord
67− , jsonPopulateRecordSet
68− , jsonbPopulateRecordSet
69− , jsonToRecord
70− , jsonbToRecord
71− , jsonToRecordSet
72− , jsonbToRecordSet
7356 -- * Table Expressions
7457 , TableExpression (..)
7558 , from
@@ -93,13 +76,6 @@ module Squeal.PostgreSQL.Query
9376 , By (..)
9477 , GroupByClause (..)
9578 , HavingClause (..)
96− -- * Subquery Expressions
97− , exists
98− , in_
99− , notIn
100− , Operator
101− , subAll
102− , subAny
10379 ) where
10480
10581import Control.DeepSeq
@@ -112,11 +88,19 @@ import GHC.TypeLits
11288
11389import qualified GHC.Generics as GHC
11490
115−import Squeal.PostgreSQL.Binary
91+import Squeal.PostgreSQL.Alias
11692import Squeal.PostgreSQL.Expression
93+import Squeal.PostgreSQL.Expression.Logic
94+import Squeal.PostgreSQL.Expression.Sort
95+import Squeal.PostgreSQL.Expression.Window
96+import Squeal.PostgreSQL.List
97+import Squeal.PostgreSQL.PG
11798import Squeal.PostgreSQL.Render
11899import Squeal.PostgreSQL.Schema
119100
101+-- $setup
102+-- >>> import Squeal.PostgreSQL
103+
120104{- |
121105The process of retrieving or the command to retrieve data from a database
122106is called a `Query`. Let's see some examples of queries.
@@ -403,27 +387,40 @@ q1 `exceptAll` q2 = UnsafeQuery $
403387SELECT queries
404388-----------------------------------------}
405389
390+{- | The simplest kinds of `Selection` are `Star` and `DotStar` which
391+emits all columns that a `TableExpression` produces. A select `List`
392+is a list of `Expression`s. A `Selection` could be a list of
393+`WindowFunction`s `Over` `WindowDefinition`. `Additional` `Selection`s can
394+be selected with `Also`.
395+-}
406396data Selection outer commons grp schemas params from row where
407− List
408− :: SListI row
409− => NP (Aliased (Expression outer commons grp schemas params from)) row
410− -> Selection outer commons grp schemas params from row
411397 Star
412398 :: HasUnique tab from row
413399 => Selection outer commons 'Ungrouped schemas params from row
400+ -- ^ `HasUnique` table in the `FromClause`
414401 DotStar
415402 :: Has tab from row
416403 => Alias tab
404+ -- ^ `Has` table with `Alias`
417405 -> Selection outer commons 'Ungrouped schemas params from row
418− Also
419− :: Selection outer commons grp schemas params from right
420− -> Selection outer commons grp schemas params from left
421− -> Selection outer commons grp schemas params from (Join left right)
406+ List
407+ :: SListI row
408+ => NP (Aliased (Expression outer commons grp schemas params from)) row
409+ -- ^ `NP` list of `Aliased` `Expression`s
410+ -> Selection outer commons grp schemas params from row
422411 Over
423412 :: SListI row
424413 => NP (Aliased (WindowFunction outer commons grp schemas params from)) row
414+ -- ^ `NP` list of `Aliased` `WindowFunction`s
425415 -> WindowDefinition outer commons grp schemas params from
426416 -> Selection outer commons grp schemas params from row
417+ Also
418+ :: Selection outer commons grp schemas params from right
419+ -- ^ `Additional` `Selection`
420+ -> Selection outer commons grp schemas params from left
421+ -> Selection outer commons grp schemas params from (Join left right)
422+instance Additional (Selection outer commons grp schemas params from) where
423+ also = Also
427424instance (KnownSymbol col, row ~ '[col ::: ty])
428425 => Aliasable col
429426 (Expression outer commons grp schemas params from ty)
@@ -711,170 +708,6 @@ offset
711708 -> TableExpression outer commons grp schemas params from
712709offset off rels = rels {offsetClause = off : offsetClause rels}
713710
714−{-----------------------------------------
715−JSON stuff
716−-----------------------------------------}
717−
718−unsafeSetOfFunction
719− :: ByteString
720− -> Expression outer commons 'Ungrouped schemas params '[] ty
721− -> FromClause outer commons schemas params from
722−unsafeSetOfFunction fun expr = UnsafeFromClause $
723− fun <> "(" <> renderSQL expr <> ")"
724−
725−-- | Expand an array to a set of rows
726−unnest
727− :: Expression outer commons 'Ungrouped schemas params '[] (nullity ('PGvararray ty))
728− -> FromClause outer commons schemas params '["unnest" ::: '["unnest" ::: ty]]
729−unnest = unsafeSetOfFunction "unnest"
730−
731−-- | Expands the outermost JSON object into a set of key/value pairs.
732−-- >>> import Data.Aeson
733−-- >>> printSQL (select Star (from (jsonEach (literal (Json (object ["a" .= "foo", "b" .= "bar"]))))))
734−-- SELECT * FROM json_each(('{"a":"foo","b":"bar"}' :: json))
735−jsonEach
736− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
737− -> FromClause outer commons schemas params
738− '["json_each" ::: '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGjson]]
739−jsonEach = unsafeSetOfFunction "json_each"
740−
741−-- | Expands the outermost binary JSON object into a set of key/value pairs.
742−-- >>> import Data.Aeson
743−-- >>> printSQL (select Star (from (jsonbEach (literal (Jsonb (object ["a" .= "foo", "b" .= "bar"]))))))
744−-- SELECT * FROM jsonb_each(('{"a":"foo","b":"bar"}' :: jsonb))
745−jsonbEach
746− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
747− -> FromClause outer commons schemas params
748− '["jsonb_each" ::: '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGjson]]
749−jsonbEach = unsafeSetOfFunction "jsonb_each"
750−
751−-- | Expands the outermost JSON object into a set of key/value pairs.-- | Expands the outermost binary JSON object into a set of key/value pairs.
752−-- >>> import Data.Aeson
753−-- >>> printSQL (select Star (from (jsonEachText (literal (Json (object ["a" .= "foo", "b" .= "bar"]))))))
754−-- SELECT * FROM json_each_text(('{"a":"foo","b":"bar"}' :: json))
755−jsonEachText
756− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
757− -> FromClause outer commons schemas params
758− '["json_each" ::: '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGtext]]
759−jsonEachText = unsafeSetOfFunction "json_each_text"
760−
761−-- | Expands the outermost binary JSON object into a set of key/value pairs.
762−-- >>> import Data.Aeson
763−-- >>> printSQL (select Star (from (jsonbEachText (literal (Jsonb (object ["a" .= "foo", "b" .= "bar"]))))))
764−-- SELECT * FROM jsonb_each_text(('{"a":"foo","b":"bar"}' :: jsonb))
765−jsonbEachText
766− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
767− -> FromClause outer commons schemas params
768− '["jsonb_each" ::: '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGtext]]
769−jsonbEachText = unsafeSetOfFunction "jsonb_each_text"
770−
771−-- | Returns set of keys in the outermost JSON object.
772−-- >>> import Data.Aeson
773−-- >>> printSQL (jsonObjectKeys (literal (Json (object ["a" .= "foo", "b" .= "bar"]))))
774−-- json_object_keys(('{"a":"foo","b":"bar"}' :: json))
775−jsonObjectKeys
776− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
777− -> FromClause outer commons schemas params
778− '["json_object_keys" ::: '["json_object_keys" ::: 'NotNull 'PGtext]]
779−jsonObjectKeys = unsafeSetOfFunction "json_object_keys"
780−
781−-- | Returns set of keys in the outermost JSON object.
782−-- >>> import Data.Aeson
783−-- >>> printSQL (jsonbObjectKeys (literal (Jsonb (object ["a" .= "foo", "b" .= "bar"]))))
784−-- jsonb_object_keys(('{"a":"foo","b":"bar"}' :: jsonb))
785−jsonbObjectKeys
786− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
787− -> FromClause outer commons schemas params
788− '["jsonb_object_keys" ::: '["jsonb_object_keys" ::: 'NotNull 'PGtext]]
789−jsonbObjectKeys = unsafeSetOfFunction "jsonb_object_keys"
790−
791−unsafePopulateFunction
792− :: KnownSymbol fun
793− => Alias fun
794− -> TypeExpression schemas (nullity ('PGcomposite row))
795− -> Expression outer commons 'Ungrouped schemas params '[] ty
796− -> FromClause outer commons schemas params '[fun ::: row]
797−unsafePopulateFunction fun ty expr = UnsafeFromClause $ renderSQL fun
798− <> parenthesized ("null::" <> renderSQL ty <> ", " <> renderSQL expr)
799−
800−-- | Expands the JSON expression to a row whose columns match the record
801−-- type defined by the given table.
802−jsonPopulateRecord
803− :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
804− -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
805− -> FromClause outer commons schemas params '["json_populate_record" ::: row]
806−jsonPopulateRecord = unsafePopulateFunction #json_populate_record
807−
808−-- | Expands the binary JSON expression to a row whose columns match the record
809−-- type defined by the given table.
810−jsonbPopulateRecord
811− :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
812− -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
813− -> FromClause outer commons schemas params '["jsonb_populate_record" ::: row]
814−jsonbPopulateRecord = unsafePopulateFunction #jsonb_populate_record
815−
816−-- | Expands the outermost array of objects in the given JSON expression to a
817−-- set of rows whose columns match the record type defined by the given table.
818−jsonPopulateRecordSet
819− :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
820− -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json array
821− -> FromClause outer commons schemas params '["json_populate_record_set" ::: row]
822−jsonPopulateRecordSet = unsafePopulateFunction #json_populate_record_set
823−
824−-- | Expands the outermost array of objects in the given binary JSON expression
825−-- to a set of rows whose columns match the record type defined by the given
826−-- table.
827−jsonbPopulateRecordSet
828− :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
829− -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb array
830− -> FromClause outer commons schemas params '["jsonb_populate_record_set" ::: row]
831−jsonbPopulateRecordSet = unsafePopulateFunction #jsonb_populate_record_set
832−
833−unsafeRecordFunction
834− :: (SListI record, json `In` PGJsonType)
835− => ByteString
836− -> Expression outer commons 'Ungrouped schemas params '[] (nullity json)
837− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
838− -> FromClause outer commons schemas params '[tab ::: record]
839−unsafeRecordFunction fun expr (types `As` tab) = UnsafeFromClause $
840− fun <> parenthesized (renderSQL expr) <+> "AS" <+> renderSQL tab
841− <> parenthesized (renderCommaSeparated renderTy types)
842− where
843− renderTy :: Aliased (TypeExpression schemas) ty -> ByteString
844− renderTy (ty `As` alias) = renderSQL alias <+> renderSQL ty
845−
846−-- | Builds an arbitrary record from a JSON object.
847−jsonToRecord
848− :: SListI record
849− => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
850− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
851− -> FromClause outer commons schemas params '[tab ::: record]
852−jsonToRecord = unsafeRecordFunction "json_to_record"
853−
854−-- | Builds an arbitrary record from a binary JSON object.
855−jsonbToRecord
856− :: SListI record
857− => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
858− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
859− -> FromClause outer commons schemas params '[tab ::: record]
860−jsonbToRecord = unsafeRecordFunction "jsonb_to_record"
861−
862−-- | Builds an arbitrary set of records from a JSON array of objects.
863−jsonToRecordSet
864− :: SListI record
865− => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json array
866− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
867− -> FromClause outer commons schemas params '[tab ::: record]
868−jsonToRecordSet = unsafeRecordFunction "json_to_record_set"
869−
870−-- | Builds an arbitrary set of records from a binary JSON array of objects.
871−jsonbToRecordSet
872− :: SListI record
873− => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb array
874− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
875− -> FromClause outer commons schemas params '[tab ::: record]
876−jsonbToRecordSet = unsafeRecordFunction "jsonb_to_record_set"
877−
878711{-----------------------------------------
879712FROM clauses
880713-----------------------------------------}
@@ -901,7 +734,7 @@ table (tab `As` alias) = UnsafeFromClause $
901734subquery
902735 :: Aliased (Query outer commons schemas params) query
903736 -> FromClause outer commons schemas params '[query]
904−subquery = UnsafeFromClause . renderAliased (parenthesized . renderQuery)
737+subquery = UnsafeFromClause . renderAliased (parenthesized . renderSQL)
905738
906739-- | `view` derives a table from a `View`.
907740view
@@ -919,11 +752,16 @@ common
919752common (cte `As` alias) = UnsafeFromClause $
920753 renderSQL cte <+> "AS" <+> renderSQL alias
921754
922−{- | @left & crossJoin right@. For every possible combination of rows from
923− @left@ and @right@ (i.e., a Cartesian product), the joined table will contain
924− a row consisting of all columns in @left@ followed by all columns in @right@.
925− If the tables have @n@ and @m@ rows respectively, the joined table will
926− have @n * m@ rows.
755+instance Additional (FromClause outer commons schemas params) where
756+ also right left = UnsafeFromClause $
757+ renderSQL left <> ", " <> renderSQL right
758+
759+{- |
760+@left & crossJoin right@. For every possible combination of rows from
761+@left@ and @right@ (i.e., a Cartesian product), the joined table will contain
762+a row consisting of all columns in @left@ followed by all columns in @right@.
763+If the tables have @n@ and @m@ rows respectively, the joined table will
764+have @n * m@ rows.
927765-}
928766crossJoin
929767 :: FromClause outer commons schemas params right
@@ -1084,90 +922,6 @@ instance RenderSQL (HavingClause outer commons grp schemas params from) where
1084922 Having conditions ->
1085923 " HAVING" <+> commaSeparated (renderSQL <$> conditions)
1086924
1087−{- |
1088−The argument of `exists` is an arbitrary subquery. The subquery is evaluated
1089−to determine whether it returns any rows. If it returns at least one row,
1090−the result of `exists` is `true`; if the subquery returns no rows,
1091−the result of `exists` is `false`.
1092−
1093−The subquery can refer to variables from the surrounding query,
1094−which will act as constants during any one evaluation of the subquery.
1095−
1096−The subquery will generally only be executed long enough to determine whether
1097−at least one row is returned, not all the way to completion.
1098−-}
1099−exists
1100− :: Query (Join outer from) commons schemas params row
1101− -> Condition outer commons grp schemas params from
1102−exists query = UnsafeExpression $ "EXISTS" <+> parenthesized (renderSQL query)
1103−
1104−{- |
1105−The right-hand side is a parenthesized subquery, which must return
1106−exactly one column. The left-hand expression is evaluated and compared to each
1107−row of the subquery result using the given `Operator`,
1108−which must yield a Boolean result. The result of `subAll` is `true`
1109−if all rows yield true (including the case where the subquery returns no rows).
1110−The result is `false` if any `false` result is found.
1111−The result is `null_` if no comparison with a subquery row returns `false`,
1112−and at least one comparison returns `null_`.
1113−
1114−>>> printSQL $ subAll true (.==) (values_ (true `as` #foo))
1115−(TRUE = ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo")))
1116−-}
1117−subAll
1118− :: Expression outer commons grp schemas params from ty1 -- ^ expression
1119− -> Operator ty1 ty2 ('Null 'PGbool) -- ^ operator
1120− -> Query (Join outer from) commons schemas params '[col ::: ty2] -- ^ subquery
1121− -> Condition outer commons grp schemas params from
1122−subAll expr (?) qry = expr ?
1123− (UnsafeExpression $ "ALL" <+> parenthesized (renderSQL qry))
1124−
1125−{- |
1126−The right-hand side is a parenthesized subquery, which must return exactly one column.
1127−The left-hand expression is evaluated and compared to each row of the subquery result
1128−using the given `Operator`, which must yield a Boolean result. The result of `subAny` is `true`
1129−if any `true` result is obtained. The result is `false` if no true result is found
1130−(including the case where the subquery returns no rows).
1131−
1132−>>> printSQL $ subAll "foo" like (values_ ("foobar" `as` #foo))
1133−(E'foo' LIKE ALL (SELECT * FROM (VALUES (E'foobar')) AS t ("foo")))
1134−-}
1135−subAny
1136− :: Expression outer commons grp schemas params from ty1 -- ^ expression
1137− -> Operator ty1 ty2 ('Null 'PGbool) -- ^ operator
1138− -> Query (Join outer from) commons schemas params '[col ::: ty2] -- ^ subquery
1139− -> Condition outer commons grp schemas params from
1140−subAny expr (?) qry = expr ?
1141− (UnsafeExpression $ "ANY" <+> parenthesized (renderSQL qry))
1142−
1143−{- |
1144−The result is `true` if the left-hand expression's result is equal
1145−to any of the right-hand expressions.
1146−
1147−>>> printSQL $ true `in_` [true, false, null_]
1148−TRUE IN (TRUE, FALSE, NULL)
1149−-}
1150−in_
1151− :: Expression outer commons grp schemas params from ty -- ^ expression
1152− -> [Expression outer commons grp schemas params from ty]
1153− -> Condition outer commons grp schemas params from
1154−expr `in_` exprs = UnsafeExpression $ renderSQL expr <+> "IN"
1155− <+> parenthesized (commaSeparated (renderSQL <$> exprs))
1156−
1157−{- |
1158−The result is `true` if the left-hand expression's result is not equal
1159−to any of the right-hand expressions.
1160−
1161−>>> printSQL $ true `notIn` [false, null_]
1162−TRUE NOT IN (FALSE, NULL)
1163−-}
1164−notIn
1165− :: Expression outer commons grp schemas params from ty -- ^ expression
1166− -> [Expression outer commons grp schemas params from ty]
1167− -> Condition outer commons grp schemas params from
1168−expr `notIn` exprs = UnsafeExpression $ renderSQL expr <+> "NOT IN"
1169− <+> parenthesized (commaSeparated (renderSQL <$> exprs))
1170−
1171925-- | A `CommonTableExpression` is an auxiliary statement in a `with` clause.
1172926data CommonTableExpression statement
1173927 (schemas :: SchemasType)
squeal-postgresql/src/Squeal/PostgreSQL/Render.hs+18−3
@@ -12,6 +12,7 @@ Rendering helper functions.
1212 AllowAmbiguousTypes
1313 , ConstraintKinds
1414 , FlexibleContexts
15+ , LambdaCase
1516 , MagicHash
1617 , OverloadedStrings
1718 , PolyKinds
@@ -22,7 +23,10 @@ Rendering helper functions.
2223
2324module Squeal.PostgreSQL.Render
2425 ( -- * Render
25− parenthesized
26+ RenderSQL (..)
27+ , printSQL
28+ , escape
29+ , parenthesized
2630 , bracketed
2731 , (<+>)
2832 , commaSeparated
@@ -34,8 +38,6 @@ module Squeal.PostgreSQL.Render
3438 , renderCommaSeparatedMaybe
3539 , renderNat
3640 , renderSymbol
37− , RenderSQL (..)
38− , printSQL
3941 ) where
4042
4143import Control.Monad.IO.Class (MonadIO (..))
@@ -128,3 +130,16 @@ class RenderSQL sql where renderSQL :: sql -> ByteString
128130-- | Print SQL.
129131printSQL :: (RenderSQL sql, MonadIO io) => sql -> io ()
130132printSQL = liftIO . Char8.putStrLn . renderSQL
133+
134+-- | `escape` a character to prevent injection
135+escape :: Char -> String
136+escape = \case
137+ '\NUL' -> "\\0"
138+ '\'' -> "''"
139+ '"' -> "\\\""
140+ '\b' -> "\\b"
141+ '\n' -> "\\n"
142+ '\r' -> "\\r"
143+ '\t' -> "\\t"
144+ '\\' -> "\\\\"
145+ c -> [c]
squeal-postgresql/src/Squeal/PostgreSQL/Schema.hs+18−304
@@ -1,16 +1,15 @@
11{-|
22Module: Squeal.PostgreSQL.Schema
33Description: Embedding of PostgreSQL type and alias system
4−Copyright: (c) Eitan Chatav, 2017
4+Copyright: (c) Eitan Chatav, 2019
55Maintainer: eitan@morphism.tech
66Stability: experimental
77
8−This module provides a type-level DSL for kinds of Postgres types,
8+`Squeal.PostgreSQL.Schema` provides a type-level DSL for kinds of Postgres types,
99tables, schema, constraints, aliases, enumerated labels, and groupings.
1010It also defines useful type families to operate on these. Finally,
1111it defines an embedding of Haskell types into Postgres types.
1212-}
13−{-# OPTIONS_GHC -fno-warn-unticked-promoted-constructors #-}
1413{-# LANGUAGE
1514 AllowAmbiguousTypes
1615 , ConstraintKinds
@@ -54,33 +53,9 @@ module Squeal.PostgreSQL.Schema
5453 , TableConstraint (..)
5554 , TableConstraints
5655 , 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 (..)
7356 -- * Enumerated Labels
7457 , IsPGlabel (..)
7558 , PGlabel (..)
76− -- * Grouping
77− , Grouping (..)
78− , GroupedBy
79− -- * Aligned lists
80− , AlignedList (..)
81− , single
82− , extractList
83− , alignedMap
8459 -- * Data Definitions
8560 , Create
8661 , Drop
@@ -88,14 +63,8 @@ module Squeal.PostgreSQL.Schema
8863 , Rename
8964 , ConstraintInvolves
9065 , DropIfConstraintsInvolve
91− -- * Lists
92− , Join
93− , disjoin
94− , Elem
95− , In
96− , Length
97− , (*:)
98− , (+++)
66+ , IsNotElem
67+ , AllUnique
9968 -- * Type Classifications
10069 , HasOid (..)
10170 , PGNum
@@ -118,20 +87,15 @@ module Squeal.PostgreSQL.Schema
11887 ) where
11988
12089import Control.Category
121−import Control.DeepSeq
122−import Data.ByteString (ByteString)
12390import Data.Kind
12491import Data.Monoid hiding (All)
125−import Data.String
12692import Data.Type.Bool
12793import Data.Word (Word32)
12894import Generics.SOP
129−import GHC.OverloadedLabels
13095import GHC.TypeLits
13196import Prelude hiding (id, (.))
13297
133−import qualified GHC.Generics as GHC
134−
98+import Squeal.PostgreSQL.Alias
13599import Squeal.PostgreSQL.Render
136100
137101-- | `PGType` is the promoted datakind of PostgreSQL types.
@@ -216,11 +180,6 @@ data NullityType
216180type (:=>) constraint ty = '(constraint,ty)
217181infixr 7 :=>
218182
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−
224183-- | `ColumnConstraint` encodes the availability of @DEFAULT@ for inserts and updates.
225184-- A column can be assigned a default value.
226185-- A data `Squeal.PostgreSQL.Manipulations.Manipulation` command can also
@@ -327,203 +286,6 @@ type family TableToColumns (table :: TableType) :: ColumnsType where
327286type family TableToRow (table :: TableType) :: RowType where
328287 TableToRow tab = ColumnsToRow (TableToColumns tab)
329288
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−
527289-- | @Elem@ is a promoted `Data.List.elem`.
528290type family Elem x xs where
529291 Elem x '[] = 'False
@@ -584,22 +346,6 @@ type family NullifyFrom (tables :: FromType) :: FromType where
584346 NullifyFrom (table ::: columns ': tables) =
585347 table ::: NullifyRow columns ': NullifyFrom tables
586348
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−
603349-- | @Create alias x xs@ adds @alias ::: x@ to the end of @xs@ and is used in
604350-- `Squeal.PostgreSQL.Definition.createTable` statements and in @ALTER TABLE@
605351-- `Squeal.PostgreSQL.Definition.addColumn`.
@@ -706,6 +452,7 @@ There are several reasons why one might want to use schemas:
706452-}
707453type SchemasType = [(Symbol,SchemaType)]
708454
455+-- | A type family to use for a single schema database.
709456type family Public (schema :: SchemaType) :: SchemasType
710457 where Public schema = '["public" ::: schema]
711458
@@ -734,48 +481,15 @@ type PGJsonKey = '[ 'PGint2, 'PGint4, 'PGtext ]
734481-- | Is a type a valid JSON type?
735482type PGJsonType = '[ 'PGjson, 'PGjsonb ]
736483
737−-- | An `AlignedList` is a type-aligned list or free category.
738−data AlignedList p x0 x1 where
739− Done :: AlignedList p x x
740− (:>>) :: p x0 x1 -> AlignedList p x1 x2 -> AlignedList p x0 x2
741−infixr 7 :>>
742−instance Category (AlignedList p) where
743− id = Done
744− (.) list = \case
745− Done -> list
746− step :>> steps -> step :>> (steps >>> list)
747−instance (forall t0 t1. RenderSQL (p t0 t1))
748− => RenderSQL (AlignedList p x0 x1) 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 +++
484+-- | Utility class for `AllUnique` to provide nicer error messages.
485+class IsNotElem x isElem where
486+instance IsNotElem x 'False where
487+instance (TypeError ( 'Text "Cannot assign to "
488+ ':<>: 'ShowType alias
489+ ':<>: 'Text " more than once"))
490+ => IsNotElem '(alias, a) 'True where
491+
492+-- | No elem of @xs@ appears more than once, in the context of assignment.
493+class AllUnique (xs :: [(Symbol, a)]) where
494+instance AllUnique '[] where
495+instance (IsNotElem x (Elem x xs), AllUnique xs) => AllUnique (x ': xs) where
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