@@ -1,7 +1,7 @@
2Module: Squeal.PostgreSQL.Query
2Module: Squeal.PostgreSQL.Query
3Description: Squeal queries
3Description: Squeal queries
4−Copyright: (c) Eitan Chatav, 2019
4+Copyright: (c) Eitan Chatav, 2017
5Maintainer: eitan@morphism.tech
5Maintainer: eitan@morphism.tech
6Stability: experimental
6Stability: experimental
@@ -17,12 +17,8 @@ Squeal queries.
17 , GeneralizedNewtypeDeriving
17 , GeneralizedNewtypeDeriving
18 , LambdaCase
18 , LambdaCase
19 , MultiParamTypeClasses
19 , MultiParamTypeClasses
21 , OverloadedStrings
20 , OverloadedStrings
22− , QuantifiedConstraints
23− , ScopedTypeVariables
24 , StandaloneDeriving
21 , StandaloneDeriving
26 , TypeFamilies
22 , TypeFamilies
27 , TypeInType
23 , TypeInType
28 , TypeOperators
24 , TypeOperators
@@ -32,16 +28,14 @@ Squeal queries.
33module Squeal.PostgreSQL.Query
29module Squeal.PostgreSQL.Query
34 ( -- * Queries
30 ( -- * Queries
35− Query_
31+ Query (UnsafeQuery, renderQuery)
37 -- ** Select
32 -- ** Select
40 , selectDistinct
34 , selectDistinct
41− , selectDistinct_
35+ , selectStar
42− , selectDistinctOn
36+ , selectDistinctStar
43− , selectDistinctOn_
37+ , selectDotStar
44− , Selection (..)
38+ , selectDistinctDotStar
45 -- ** Values
39 -- ** Values
@@ -53,15 +47,33 @@ module Squeal.PostgreSQL.Query
54 , exceptAll
48 , exceptAll
55 -- ** With
49 -- ** With
56− , With (..)
50+ , With (with)
57 , CommonTableExpression (..)
51 , CommonTableExpression (..)
58− , withRecursive
52+ , renderCommonTableExpression
53+ , renderCommonTableExpressions
62+ , jsonbPopulateRecord
63+ , jsonPopulateRecordSet
64+ , jsonbPopulateRecordSet
59 -- * Table Expressions
69 -- * Table Expressions
60 , TableExpression (..)
70 , TableExpression (..)
71+ , renderTableExpression
67 -- * From Clauses
79 -- * From Clauses
@@ -76,14 +88,46 @@ module Squeal.PostgreSQL.Query
76 , rightOuterJoin
88 , rightOuterJoin
77 , fullOuterJoin
89 , fullOuterJoin
78 -- * Grouping
90 -- * Grouping
79− , By (..)
91+ , By (By1, By2)
80− , GroupByClause (..)
92+ , renderBy
81− , HavingClause (..)
93+ , GroupByClause (NoGroups, Group)
94+ , renderGroupByClause
95+ , HavingClause (NoHaving, Having)
98+ , SortExpression (..)
99+ , renderSortExpression
100+ -- * Subquery Expressions
84import Control.DeepSeq
129import Control.DeepSeq
85import Data.ByteString (ByteString)
130import Data.ByteString (ByteString)
86−import Data.Kind (Type)
87import Data.String
131import Data.String
88import Data.Word
132import Data.Word
89import Generics.SOP hiding (from)
133import Generics.SOP hiding (from)
@@ -91,67 +135,13 @@ import GHC.TypeLits
92import qualified GHC.Generics as GHC
136import qualified GHC.Generics as GHC
94−import Squeal.PostgreSQL.Alias
95import Squeal.PostgreSQL.Expression
138import Squeal.PostgreSQL.Expression
96−import Squeal.PostgreSQL.Expression.Logic
97−import Squeal.PostgreSQL.Expression.Sort
98−import Squeal.PostgreSQL.Expression.Window
99−import Squeal.PostgreSQL.List
100−import Squeal.PostgreSQL.PG
101import Squeal.PostgreSQL.Render
139import Squeal.PostgreSQL.Render
102import Squeal.PostgreSQL.Schema
140import Squeal.PostgreSQL.Schema
105−-- >>> import Squeal.PostgreSQL
106−-- >>> import Data.Int (Int32, Int64)
107−-- >>> import Data.Monoid (Sum (..))
108−-- >>> import Data.Text (Text)
109−-- >>> import qualified Generics.SOP as SOP
112−The process of retrieving or the command to retrieve data from
143+The process of retrieving or the command to retrieve data from a database
113−a database is called a `Query`.
144+is called a `Query`. Let's see some examples of queries.
115−The general `Query` type is parameterized by
117−* @outer :: FromType@ - outer scope for a correlated subquery,
118−* @commons :: FromType@ - scope for all `common` table expressions,
119−* @schemas :: SchemasType@ - scope for all `table`s and `view`s,
120−* @params :: [NullityType]@ - scope for all `Squeal.Expression.Parameter.parameter`s,
121−* @row :: RowType@ - return type of the `Query`.
125− (commons :: FromType)
126− (schemas :: SchemasType)
127− (params :: [NullityType])
129− = UnsafeQuery { renderQuery :: ByteString }
130− deriving (GHC.Generic,Show,Eq,Ord,NFData)
131−instance RenderSQL (Query outer commons schemas params row) where renderSQL = renderQuery
134−The top level `Query_` type is parameterized by a @schemas@ `SchemasType`,
135−against which the query is type-checked, an input @parameters@ Haskell `Type`,
136−and an ouput row Haskell `Type`.
138−A top-level query can be run
139−using `Squeal.PostgreSQL.PQ.runQueryParams`, or if @parameters = ()@
140−using `Squeal.PostgreSQL.PQ.runQuery`.
142−Generally, @parameters@ will be a Haskell tuple or record whose entries
143−may be referenced using positional
144−`Squeal.PostgreSQL.Expression.Parameter.parameter`s and @row@ will be a
145−Haskell record, whose entries will be targeted using overloaded labels.
147−Let's see some examples of queries.
149−>>> :set -XDeriveAnyClass -XDerivingStrategies
151−data Row a b = Row { col1 :: a, col2 :: b }
152− deriving stock (GHC.Generic)
153− deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
156simple query:
146simple query:
@@ -159,8 +149,8 @@ simple query:
159>>> type Schema = '["tab" ::: 'Table ('[] :=> Columns)]
149>>> type Schema = '["tab" ::: 'Table ('[] :=> Columns)]
162− query :: Query_ (Public Schema) () (Row Int32 Int32)
152+ query :: Query (DBof (Public Schema)) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
163− query = select Star (from (table #tab))
153+ query = selectStar (from (table #tab))
164in printSQL query
154in printSQL query
166SELECT * FROM "tab" AS "tab"
156SELECT * FROM "tab" AS "tab"
@@ -169,22 +159,22 @@ restricted query:
172− query :: Query_ (Public Schema) () (Row Int32 Int32)
162+ query :: Query (DBof (Public Schema)) '[] '["sum" ::: 'NotNull 'PGint4, "col1" ::: 'NotNull 'PGint4]
174− select_ ((#col1 + #col2) `as` #col1 :* #col1 `as` #col2)
164+ select ((#col1 + #col2) `as` #sum :* #col1)
175 ( from (table #tab)
165 ( from (table #tab)
176 & where_ (#col1 .> #col2)
166 & where_ (#col1 .> #col2)
177 & where_ (#col2 .> 0) )
167 & where_ (#col2 .> 0) )
178in printSQL query
168in printSQL query
180−SELECT ("col1" + "col2") AS "col1", "col1" AS "col2" FROM "tab" AS "tab" WHERE (("col1" > "col2") AND ("col2" > 0))
170+SELECT ("col1" + "col2") AS "sum", "col1" AS "col1" FROM "tab" AS "tab" WHERE (("col1" > "col2") AND ("col2" > 0))
186− query :: Query_ (Public Schema) () (Row Int32 Int32)
176+ query :: Query (DBof (Public Schema)) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
187− query = select Star (from (subquery (select Star (from (table #tab)) `as` #sub)))
177+ query = selectStar (from (subquery (selectStar (from (table #tab)) `as` #sub)))
188in printSQL query
178in printSQL query
190SELECT * FROM (SELECT * FROM "tab" AS "tab") AS "sub"
180SELECT * FROM (SELECT * FROM "tab" AS "tab") AS "sub"
@@ -193,8 +183,8 @@ limits and offsets:
196− query :: Query_ (Public Schema) () (Row Int32 Int32)
186+ query :: Query (DBof (Public Schema)) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
197− query = select Star (from (table #tab) & limit 100 & offset 2 & limit 50 & offset 2)
187+ query = selectStar (from (table #tab) & limit 100 & offset 2 & limit 50 & offset 2)
198in printSQL query
188in printSQL query
200SELECT * FROM "tab" AS "tab" LIMIT 50 OFFSET 4
190SELECT * FROM "tab" AS "tab" LIMIT 50 OFFSET 4
@@ -203,8 +193,8 @@ parameterized query:
206− query :: Query_ (Public Schema) (Only Int32) (Row Int32 Int32)
196+ query :: Query (DBof (Public Schema)) '[ 'NotNull 'PGint4] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
207− query = select Star (from (table #tab) & where_ (#col1 .> param @1))
197+ query = selectStar (from (table #tab) & where_ (#col1 .> param @1))
208in printSQL query
198in printSQL query
210SELECT * FROM "tab" AS "tab" WHERE ("col1" > ($1 :: int4))
200SELECT * FROM "tab" AS "tab" WHERE ("col1" > ($1 :: int4))
@@ -213,34 +203,36 @@ aggregation query:
216− query :: Query_ (Public Schema) () (Row Int64 Int32)
206+ query :: Query (DBof (Public Schema)) '[] '["sum" ::: 'NotNull 'PGint4, "col1" ::: 'NotNull 'PGint4 ]
218− select_ ((fromNull 0 (sum_ (All #col2))) `as` #col1 :* #col1 `as` #col2)
208+ select (sum_ #col2 `as` #sum :* #col1)
219 ( from (table (#tab `as` #table1))
209 ( from (table (#tab `as` #table1))
220 & groupBy #col1
210 & groupBy #col1
221− & having (sum_ (Distinct #col2) .> 1) )
211+ & having (#col1 + sum_ #col2 .> 1) )
222in printSQL query
212in printSQL query
224−SELECT COALESCE(sum(ALL "col2"), 0) AS "col1", "col1" AS "col2" FROM "tab" AS "table1" GROUP BY "col1" HAVING (sum(DISTINCT "col2") > 1)
214+SELECT sum("col2") AS "sum", "col1" AS "col1" FROM "tab" AS "table1" GROUP BY "col1" HAVING (("col1" + sum("col2")) > 1)
226sorted query:
216sorted query:
230− query :: Query_ (Public Schema) () (Row Int32 Int32)
220+ query :: Query (DBof (Public Schema)) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
231− query = select Star (from (table #tab) & orderBy [#col1 & Asc])
221+ query = selectStar (from (table #tab) & orderBy [#col1 & Asc])
232in printSQL query
222in printSQL query
234SELECT * FROM "tab" AS "tab" ORDER BY "col1" ASC
224SELECT * FROM "tab" AS "tab" ORDER BY "col1" ASC
228+>>> :set -XFlexibleContexts
239type OrdersColumns =
230type OrdersColumns =
240 '[ "id" ::: 'NoDef :=> 'NotNull 'PGint4
231 '[ "id" ::: 'NoDef :=> 'NotNull 'PGint4
241− , "price" ::: 'NoDef :=> 'NotNull 'PGfloat4
232+ , "price" ::: 'NoDef :=> 'NotNull 'PGfloat4
242− , "customer_id" ::: 'NoDef :=> 'NotNull 'PGint4
233+ , "customer_id" ::: 'NoDef :=> 'NotNull 'PGint4
243− , "shipper_id" ::: 'NoDef :=> 'NotNull 'PGint4 ]
234+ , "shipper_id" ::: 'NoDef :=> 'NotNull 'PGint4
@@ -256,27 +248,22 @@ type OrdersConstraints =
257type OrdersSchema =
249type OrdersSchema =
258 '[ "orders" ::: 'Table (OrdersConstraints :=> OrdersColumns)
250 '[ "orders" ::: 'Table (OrdersConstraints :=> OrdersColumns)
259− , "customers" ::: 'Table (CustomersConstraints :=> NamesColumns)
251+ , "customers" ::: 'Table (CustomersConstraints :=> NamesColumns)
260− , "shippers" ::: 'Table (ShippersConstraints :=> NamesColumns) ]
252+ , "shippers" ::: 'Table (ShippersConstraints :=> NamesColumns) ]
266− , customerName :: Text
267− , shipperName :: Text
268− } deriving GHC.Generic
269−instance SOP.Generic Order
270−instance SOP.HasDatatypeInfo Order
275− query :: Query_ (Public OrdersSchema) () Order
257+ query :: Query (DBof (Public OrdersSchema))
276− query = select_
258+ '[]
277− ( #o ! #price `as` #price :*
259+ '[ "order_price" ::: 'NotNull 'PGfloat4
278− #c ! #name `as` #customerName :*
260+ , "customer_name" ::: 'NotNull 'PGtext
279− #s ! #name `as` #shipperName )
261+ , "shipper_name" ::: 'NotNull 'PGtext
264+ ( #o ! #price `as` #order_price :*
265+ #c ! #name `as` #customer_name :*
266+ #s ! #name `as` #shipper_name )
280 ( from (table (#orders `as` #o)
267 ( from (table (#orders `as` #o)
281 & innerJoin (table (#customers `as` #c))
268 & innerJoin (table (#customers `as` #c))
282 (#o ! #customer_id .== #c ! #id)
269 (#o ! #customer_id .== #c ! #id)
@@ -284,16 +271,14 @@ let
284 (#o ! #shipper_id .== #s ! #id)) )
271 (#o ! #shipper_id .== #s ! #id)) )
285in printSQL query
272in printSQL query
287−SELECT "o"."price" AS "price", "c"."name" AS "customerName", "s"."name" AS "shipperName" FROM "orders" AS "o" INNER JOIN "customers" AS "c" ON ("o"."customer_id" = "c"."id") INNER JOIN "shippers" AS "s" ON ("o"."shipper_id" = "s"."id")
274+SELECT "o"."price" AS "order_price", "c"."name" AS "customer_name", "s"."name" AS "shipper_name" FROM "orders" AS "o" INNER JOIN "customers" AS "c" ON ("o"."customer_id" = "c"."id") INNER JOIN "shippers" AS "s" ON ("o"."shipper_id" = "s"."id")
289self-join:
276self-join:
293− query :: Query_ (Public Schema) () (Row Int32 Int32)
280+ query :: Query (DBof (Public Schema)) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
294− query = select
281+ query = selectDotStar #t1 (from (table (#tab `as` #t1) & crossJoin (table (#tab `as` #t2))))
296− (from (table (#tab `as` #t1) & crossJoin (table (#tab `as` #t2))))
297in printSQL query
282in printSQL query
299SELECT "t1".* FROM "tab" AS "t1" CROSS JOIN "tab" AS "t2"
284SELECT "t1".* FROM "tab" AS "t1" CROSS JOIN "tab" AS "t2"
@@ -302,20 +287,18 @@ value queries:
305− query :: Query_ schemas () (Row String Bool)
290+ query :: Query db '[] '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
306− query = values
291+ query = values (1 `as` #foo :* true `as` #bar) [2 `as` #foo :* false `as` #bar]
307− ("true" `as` #col1 :* true `as` #col2)
308− ["false" `as` #col1 :* false `as` #col2]
309in printSQL query
292in printSQL query
311−SELECT * FROM (VALUES (E'true', TRUE), (E'false', FALSE)) AS t ("col1", "col2")
294+SELECT * FROM (VALUES (1, TRUE), (2, FALSE)) AS t ("foo", "bar")
313set operations:
296set operations:
317− query :: Query_ (Public Schema) () (Row Int32 Int32)
300+ query :: Query (DBof (Public Schema)) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
318− query = select Star (from (table #tab)) `unionAll` select Star (from (table #tab))
301+ query = selectStar (from (table #tab)) `unionAll` selectStar (from (table #tab))
319in printSQL query
302in printSQL query
321(SELECT * FROM "tab" AS "tab") UNION ALL (SELECT * FROM "tab" AS "tab")
304(SELECT * FROM "tab" AS "tab") UNION ALL (SELECT * FROM "tab" AS "tab")
@@ -324,305 +307,167 @@ with queries:
327− query :: Query_ (Public Schema) () (Row Int32 Int32)
310+ query :: Query (DBof (Public Schema)) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
328 query = with (
311 query = with (
329− select Star (from (table #tab)) `as` #cte1 :>>
312+ selectStar (from (table #tab)) `as` #cte1 :>>
330− select Star (from (common #cte1)) `as` #cte2
313+ selectStar (from (common #cte1)) `as` #cte2
331− ) (select Star (from (common #cte2)))
314+ ) (selectStar (from (common #cte2)))
332in printSQL query
315in printSQL query
334WITH "cte1" AS (SELECT * FROM "tab" AS "tab"), "cte2" AS (SELECT * FROM "cte1" AS "cte1") SELECT * FROM "cte2" AS "cte2"
317WITH "cte1" AS (SELECT * FROM "tab" AS "tab"), "cte2" AS (SELECT * FROM "cte1" AS "cte1") SELECT * FROM "cte2" AS "cte2"
336−window function queries
340− query :: Query_ (Public Schema) () (Row Int32 Int64)
342− (#col1 & Also (rank `as` #col2 `Over` (partitionBy #col1 & orderBy [#col2 & Asc])))
346−SELECT "col1" AS "col1", rank() OVER (PARTITION BY "col1" ORDER BY "col2" ASC) AS "col2" FROM "tab" AS "tab"
348−correlated subqueries
352− query :: Query_ (Public Schema) () (Only Int32)
354− select (#col1 `as` #fromOnly) (from (table (#tab `as` #t1))
356− select Star (from (table (#tab `as` #t2))
357− & where_ (#t2 ! #col2 .== #t1 ! #col1)))))
360−SELECT "col1" AS "fromOnly" FROM "tab" AS "t1" WHERE EXISTS (SELECT * FROM "tab" AS "t2" WHERE ("t2"."col2" = "t1"."col1"))
363−type family Query_
319+newtype Query
364− (schemas :: SchemasType)
320+ (db :: DBType)
365− (parameters :: Type)
321+ (params :: [NullityType])
366− (row :: Type) where
322+ (columns :: RowType)
367− Query_ schemas params row =
323+ = UnsafeQuery { renderQuery :: ByteString }
368− Query '[] '[] schemas (TuplePG params) (RowPG row)
324+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
325+instance RenderSQL (Query db params columns) where renderSQL = renderQuery
370-- | The results of two queries can be combined using the set operation
327-- | The results of two queries can be combined using the set operation
371-- `union`. Duplicate rows are eliminated.
328-- `union`. Duplicate rows are eliminated.
373− :: Query outer commons schemas params columns
330+ :: Query db params columns
374− -> Query outer commons schemas params columns
331+ -> Query db params columns
375− -> Query outer commons schemas params columns
332+ -> Query db params columns
376q1 `union` q2 = UnsafeQuery $
333q1 `union` q2 = UnsafeQuery $
377− parenthesized (renderSQL q1)
334+ parenthesized (renderQuery q1)
378 <+> "UNION"
335 <+> "UNION"
379− <+> parenthesized (renderSQL q2)
336+ <+> parenthesized (renderQuery q2)
381-- | The results of two queries can be combined using the set operation
338-- | The results of two queries can be combined using the set operation
382-- `unionAll`, the disjoint union. Duplicate rows are retained.
339-- `unionAll`, the disjoint union. Duplicate rows are retained.
384− :: Query outer commons schemas params columns
341+ :: Query db params columns
385− -> Query outer commons schemas params columns
342+ -> Query db params columns
386− -> Query outer commons schemas params columns
343+ -> Query db params columns
387q1 `unionAll` q2 = UnsafeQuery $
344q1 `unionAll` q2 = UnsafeQuery $
388− parenthesized (renderSQL q1)
345+ parenthesized (renderQuery q1)
389 <+> "UNION" <+> "ALL"
346 <+> "UNION" <+> "ALL"
390− <+> parenthesized (renderSQL q2)
347+ <+> parenthesized (renderQuery q2)
392-- | The results of two queries can be combined using the set operation
349-- | The results of two queries can be combined using the set operation
393-- `intersect`, the intersection. Duplicate rows are eliminated.
350-- `intersect`, the intersection. Duplicate rows are eliminated.
395− :: Query outer commons schemas params columns
352+ :: Query db params columns
396− -> Query outer commons schemas params columns
353+ -> Query db params columns
397− -> Query outer commons schemas params columns
354+ -> Query db params columns
398q1 `intersect` q2 = UnsafeQuery $
355q1 `intersect` q2 = UnsafeQuery $
399− parenthesized (renderSQL q1)
356+ parenthesized (renderQuery q1)
400 <+> "INTERSECT"
357 <+> "INTERSECT"
401− <+> parenthesized (renderSQL q2)
358+ <+> parenthesized (renderQuery q2)
403-- | The results of two queries can be combined using the set operation
360-- | The results of two queries can be combined using the set operation
404-- `intersectAll`, the intersection. Duplicate rows are retained.
361-- `intersectAll`, the intersection. Duplicate rows are retained.
405intersectAll
362intersectAll
406− :: Query outer commons schemas params columns
363+ :: Query db params columns
407− -> Query outer commons schemas params columns
364+ -> Query db params columns
408− -> Query outer commons schemas params columns
365+ -> Query db params columns
409q1 `intersectAll` q2 = UnsafeQuery $
366q1 `intersectAll` q2 = UnsafeQuery $
410− parenthesized (renderSQL q1)
367+ parenthesized (renderQuery q1)
411 <+> "INTERSECT" <+> "ALL"
368 <+> "INTERSECT" <+> "ALL"
412− <+> parenthesized (renderSQL q2)
369+ <+> parenthesized (renderQuery q2)
414-- | The results of two queries can be combined using the set operation
371-- | The results of two queries can be combined using the set operation
415-- `except`, the set difference. Duplicate rows are eliminated.
372-- `except`, the set difference. Duplicate rows are eliminated.
417− :: Query outer commons schemas params columns
374+ :: Query db params columns
418− -> Query outer commons schemas params columns
375+ -> Query db params columns
419− -> Query outer commons schemas params columns
376+ -> Query db params columns
420q1 `except` q2 = UnsafeQuery $
377q1 `except` q2 = UnsafeQuery $
421− parenthesized (renderSQL q1)
378+ parenthesized (renderQuery q1)
422 <+> "EXCEPT"
379 <+> "EXCEPT"
423− <+> parenthesized (renderSQL q2)
380+ <+> parenthesized (renderQuery q2)
425-- | The results of two queries can be combined using the set operation
382-- | The results of two queries can be combined using the set operation
426-- `exceptAll`, the set difference. Duplicate rows are retained.
383-- `exceptAll`, the set difference. Duplicate rows are retained.
428− :: Query outer commons schemas params columns
385+ :: Query db params columns
429− -> Query outer commons schemas params columns
386+ -> Query db params columns
430− -> Query outer commons schemas params columns
387+ -> Query db params columns
431q1 `exceptAll` q2 = UnsafeQuery $
388q1 `exceptAll` q2 = UnsafeQuery $
432− parenthesized (renderSQL q1)
389+ parenthesized (renderQuery q1)
433 <+> "EXCEPT" <+> "ALL"
390 <+> "EXCEPT" <+> "ALL"
434− <+> parenthesized (renderSQL q2)
391+ <+> parenthesized (renderQuery q2)
436{-----------------------------------------
393{-----------------------------------------
437SELECT queries
394SELECT queries
438-----------------------------------------}
395-----------------------------------------}
440−{- | The simplest kinds of `Selection` are `Star` and `DotStar` which
441−emits all columns that a `TableExpression` produces. A select `List`
442−is a list of `Expression`s. A `Selection` could be a list of
443−`WindowFunction`s `Over` `WindowDefinition`. `Additional` `Selection`s can
444−be selected with `Also`.
446−data Selection outer commons grp schemas params from row where
448− :: HasUnique tab from row
449− => Selection outer commons 'Ungrouped schemas params from row
450− -- ^ `HasUnique` table in the `FromClause`
454− -- ^ `Has` table with `Alias`
455− -> Selection outer commons 'Ungrouped schemas params from row
458− => NP (Aliased (Expression outer commons grp schemas params from)) row
459− -- ^ `NP` list of `Aliased` `Expression`s
460− -> Selection outer commons grp schemas params from row
463− => NP (Aliased (WindowFunction outer commons grp schemas params from)) row
464− -- ^ `NP` list of `Aliased` `WindowFunction`s
465− -> WindowDefinition outer commons grp schemas params from
466− -> Selection outer commons grp schemas params from row
468− :: Selection outer commons grp schemas params from right
469− -- ^ `Additional` `Selection`
470− -> Selection outer commons grp schemas params from left
471− -> Selection outer commons grp schemas params from (Join left right)
472−instance Additional (Selection outer commons grp schemas params from) where
474−instance (KnownSymbol col, row ~ '[col ::: ty])
476− (Expression outer commons grp schemas params from ty)
477− (Selection outer commons grp schemas params from row) where
478− expr `as` col = List (expr `as` col)
479−instance (Has tab (Join outer from) row0, Has col row0 ty, row1 ~ '[col ::: ty])
480− => IsQualified tab col
481− (Selection outer commons 'Ungrouped schemas params from row1) where
482− tab ! col = tab ! col `as` col
484− ( Has tab (Join outer from) row0
486− , row1 ~ '[col ::: ty]
487− , GroupedBy tab col bys )
488− => IsQualified tab col
489− (Selection outer commons ('Grouped bys) schemas params from row1) where
490− tab ! col = tab ! col `as` col
491−instance (HasUnique tab (Join outer from) row0, Has col row0 ty, row1 ~ '[col ::: ty])
493− (Selection outer commons 'Ungrouped schemas params from row1) where
494− fromLabel = fromLabel @col `as` Alias
496− ( HasUnique tab (Join outer from) row0
498− , row1 ~ '[col ::: ty]
499− , GroupedBy tab col bys )
501− (Selection outer commons ('Grouped bys) schemas params from row1) where
502− fromLabel = fromLabel @col `as` Alias
504−instance RenderSQL (Selection outer commons grp schemas params from row) where
506− List list -> renderCommaSeparated (renderAliased renderSQL) list
508− DotStar tab -> renderSQL tab <> ".*"
509− Also right left -> renderSQL left <> ", " <> renderSQL right
510− Over winFns winDef ->
513− :: Aliased (WindowFunction outer commons grp schemas params from) field
515− renderOver (winFn `As` col) = renderSQL winFn
516− <+> "OVER" <+> parenthesized (renderSQL winDef)
517− <+> "AS" <+> renderSQL col
519− renderCommaSeparated renderOver winFns
522− (Selection outer commons grp schemas params from '["fromOnly" ::: 'NotNull 'PGtext]) where
523− fromString str = fromString str `as` Alias
525-- | the `TableExpression` in the `select` command constructs an intermediate
397-- | the `TableExpression` in the `select` command constructs an intermediate
526-- virtual table by possibly combining tables, views, eliminating rows,
398-- virtual table by possibly combining tables, views, eliminating rows,
527-- grouping, etc. This table is finally passed on to processing by
399-- grouping, etc. This table is finally passed on to processing by
528−-- the select list. The `Selection` determines which columns of
400+-- the select list. The select list determines which columns of
529-- the intermediate table are actually output.
401-- the intermediate table are actually output.
531− :: (SListI row, row ~ (x ': xs))
403+ :: (SListI columns, columns ~ (col ': cols))
532− => Selection outer commons grp schemas params from row
404+ => NP (Aliased (Expression db from grouping params)) columns
534− -> TableExpression outer commons grp schemas params from
535− -- ^ intermediate virtual table
536− -> Query outer commons schemas params row
537−select selection tabexpr = UnsafeQuery $
539− <+> renderSQL selection
540− <+> renderSQL tabexpr
542−-- | Like `select` but takes an `NP` list of `Expression`s instead
543−-- of a general `Selection`.
545− :: (SListI row, row ~ (x ': xs))
546− => NP (Aliased (Expression outer commons grp schemas params from)) row
547 -- ^ select list
405 -- ^ select list
548− -> TableExpression outer commons grp schemas params from
406+ -> TableExpression db params from grouping
549 -- ^ intermediate virtual table
407 -- ^ intermediate virtual table
550− -> Query outer commons schemas params row
408+ -> Query db params columns
551−select_ = select . List
409+select list rels = UnsafeQuery $
411+ <+> renderCommaSeparated (renderAliasedAs renderExpression) list
412+ <+> renderTableExpression rels
553-- | After the select list has been processed, the result table can
414-- | After the select list has been processed, the result table can
554-- be subject to the elimination of duplicate rows using `selectDistinct`.
415-- be subject to the elimination of duplicate rows using `selectDistinct`.
555selectDistinct
416selectDistinct
556 :: (SListI columns, columns ~ (col ': cols))
417 :: (SListI columns, columns ~ (col ': cols))
557− => Selection outer commons grp schemas params from columns
418+ => NP (Aliased (Expression db from 'Ungrouped params)) columns
558− -- ^ selection
419+ -- ^ select list
559− -> TableExpression outer commons grp schemas params from
420+ -> TableExpression db params from 'Ungrouped
560 -- ^ intermediate virtual table
421 -- ^ intermediate virtual table
561− -> Query outer commons schemas params columns
422+ -> Query db params columns
562−selectDistinct selection tabexpr = UnsafeQuery $
423+selectDistinct list rels = UnsafeQuery $
563 "SELECT DISTINCT"
424 "SELECT DISTINCT"
564− <+> renderSQL selection
425+ <+> renderCommaSeparated (renderAliasedAs renderExpression) list
565− <+> renderSQL tabexpr
426+ <+> renderTableExpression rels
567−-- | Like `selectDistinct` but takes an `NP` list of `Expression`s instead
428+-- | The simplest kind of query is `selectStar` which emits all columns
568−-- of a general `Selection`.
429+-- that the table expression produces.
569−selectDistinct_
430+selectStar
570− :: (SListI columns, columns ~ (col ': cols))
431+ :: HasUnique table from columns
571− => NP (Aliased (Expression outer commons grp schemas params from)) columns
432+ => TableExpression db params from 'Ungrouped
573− -> TableExpression outer commons grp schemas params from
574 -- ^ intermediate virtual table
433 -- ^ intermediate virtual table
575− -> Query outer commons schemas params columns
434+ -> Query db params columns
576−selectDistinct_ = selectDistinct . List
435+selectStar rels = UnsafeQuery $ "SELECT" <+> "*" <+> renderTableExpression rels
578−{-|
437+-- | A `selectDistinctStar` emits all columns that the table expression
579−`selectDistinctOn` keeps only the first row of each set of rows where
438+-- produces and eliminates duplicate rows.
580−the given expressions evaluate to equal. The DISTINCT ON expressions are
439+selectDistinctStar
581−interpreted using the same rules as for ORDER BY. ORDER BY is used to
440+ :: HasUnique table from columns
582−ensure that the desired row appears first.
441+ => TableExpression db params from 'Ungrouped
584−The DISTINCT ON expression(s) must match the leftmost ORDER BY expression(s).
585−The ORDER BY clause will normally contain additional expression(s) that
586−determine the desired precedence of rows within each DISTINCT ON group.
588−In order to guarantee they match and reduce redundancy, this function
589−will prepend the The DISTINCT ON expressions to the ORDER BY clause.
592− :: (SListI columns, columns ~ (col ': cols))
593− => [SortExpression outer commons grp schemas params from]
594− -- ^ DISTINCT ON expression(s) and prepended to ORDER BY clause
595− -> Selection outer commons grp schemas params from columns
597− -> TableExpression outer commons grp schemas params from
598 -- ^ intermediate virtual table
442 -- ^ intermediate virtual table
599− -> Query outer commons schemas params columns
443+ -> Query db params columns
600−selectDistinctOn distincts selection tab = UnsafeQuery $
444+selectDistinctStar rels = UnsafeQuery $
601− "SELECT DISTINCT ON"
445+ "SELECT DISTINCT" <+> "*" <+> renderTableExpression rels
602− <+> parenthesized (commaSeparated (renderDistinctOn <$> distincts))
446+
603− <+> renderSQL selection
447+-- | When working with multiple tables, it can also be useful to ask
604− <+> renderSQL (tab {orderByClause = distincts <> orderByClause tab})
448+-- for all the columns of a particular table, using `selectDotStar`.
605− where
449+selectDotStar
606− renderDistinctOn = \case
450+ :: Has table from columns
607− Asc expression -> renderSQL expression
451+ => Alias table
608− Desc expression -> renderSQL expression
452+ -- ^ particular virtual subtable
609− AscNullsFirst expression -> renderSQL expression
453+ -> TableExpression db params from 'Ungrouped
610− DescNullsFirst expression -> renderSQL expression
611− AscNullsLast expression -> renderSQL expression
612− DescNullsLast expression -> renderSQL expression
614−-- | Like `selectDistinctOn` but takes an `NP` list of `Expression`s instead
615−-- of a general `Selection`.
617− :: (SListI columns, columns ~ (col ': cols))
618− => [SortExpression outer commons grp schemas params from]
619− -- ^ distinct on and return the first row in ordering
620− -> NP (Aliased (Expression outer commons grp schemas params from)) columns
622− -> TableExpression outer commons grp schemas params from
623 -- ^ intermediate virtual table
454 -- ^ intermediate virtual table
624− -> Query outer commons schemas params columns
455+ -> Query db params columns
625−selectDistinctOn_ distincts = selectDistinctOn distincts . List
456+selectDotStar rel tab = UnsafeQuery $
457+ "SELECT" <+> renderAlias rel <> ".*" <+> renderTableExpression tab
459+-- | A `selectDistinctDotStar` asks for all the columns of a particular table,
460+-- and eliminates duplicate rows.
461+selectDistinctDotStar
462+ :: Has table from columns
464+ -- ^ particular virtual table
465+ -> TableExpression db params from 'Ungrouped
466+ -- ^ intermediate virtual table
467+ -> Query db params columns
468+selectDistinctDotStar rel tab = UnsafeQuery $
469+ "SELECT DISTINCT" <+> renderAlias rel <> ".*"
470+ <+> renderTableExpression tab
627-- | `values` computes a row value or set of row values
472-- | `values` computes a row value or set of row values
628-- specified by value expressions. It is most commonly used
473-- specified by value expressions. It is most commonly used
@@ -630,16 +475,16 @@ selectDistinctOn_ distincts = selectDistinctOn distincts . List
630-- but it can be used on its own.
475-- but it can be used on its own.
632-- >>> type Row = '["a" ::: 'NotNull 'PGint4, "b" ::: 'NotNull 'PGtext]
477-- >>> type Row = '["a" ::: 'NotNull 'PGint4, "b" ::: 'NotNull 'PGtext]
633−-- >>> let query = values (1 `as` #a :* "one" `as` #b) [] :: Query outer commons schemas '[] Row
478+-- >>> let query = values (1 `as` #a :* "one" `as` #b) [] :: Query db '[] Row
634-- >>> printSQL query
479-- >>> printSQL query
635-- SELECT * FROM (VALUES (1, E'one')) AS t ("a", "b")
480-- SELECT * FROM (VALUES (1, E'one')) AS t ("a", "b")
637 :: SListI cols
482 :: SListI cols
638− => NP (Aliased (Expression outer commons 'Ungrouped schemas params '[] )) cols
483+ => NP (Aliased (Expression db '[] 'Ungrouped params)) cols
639− -> [NP (Aliased (Expression outer commons 'Ungrouped schemas params '[] )) cols]
484+ -> [NP (Aliased (Expression db '[] 'Ungrouped params)) cols]
640 -- ^ When more than one row is specified, all the rows must
485 -- ^ When more than one row is specified, all the rows must
641 -- must have the same number of elements
486 -- must have the same number of elements
642− -> Query outer commons schemas params cols
487+ -> Query db params cols
643values rw rws = UnsafeQuery $ "SELECT * FROM"
488values rw rws = UnsafeQuery $ "SELECT * FROM"
644 <+> parenthesized (
489 <+> parenthesized (
@@ -650,17 +495,17 @@ values rw rws = UnsafeQuery $ "SELECT * FROM"
650 <+> parenthesized (renderCommaSeparated renderAliasPart rw)
495 <+> parenthesized (renderCommaSeparated renderAliasPart rw)
652 renderAliasPart, renderValuePart
497 renderAliasPart, renderValuePart
653− :: Aliased (Expression outer commons 'Ungrouped schemas params '[] ) ty -> ByteString
498+ :: Aliased (Expression db '[] 'Ungrouped params) ty -> ByteString
654− renderAliasPart (_ `As` name) = renderSQL name
499+ renderAliasPart (_ `As` name) = renderAlias name
655− renderValuePart (value `As` _) = renderSQL value
500+ renderValuePart (value `As` _) = renderExpression value
657-- | `values_` computes a row value or set of row values
502-- | `values_` computes a row value or set of row values
658-- specified by value expressions.
503-- specified by value expressions.
660 :: SListI cols
505 :: SListI cols
661− => NP (Aliased (Expression outer commons 'Ungrouped schemas params '[] )) cols
506+ => NP (Aliased (Expression db '[] 'Ungrouped params)) cols
662 -- ^ one row of values
507 -- ^ one row of values
663− -> Query outer commons schemas params cols
508+ -> Query db params cols
664values_ rw = values rw []
509values_ rw = values rw []
666{-----------------------------------------
511{-----------------------------------------
@@ -674,17 +519,15 @@ Table Expressions
674-- to a table on disk, a so-called base table, but more complex expressions
519-- to a table on disk, a so-called base table, but more complex expressions
675-- can be used to modify or combine base tables in various ways.
520-- can be used to modify or combine base tables in various ways.
676data TableExpression
521data TableExpression
677− (outer :: FromType)
522+ (db :: DBType)
678− (commons :: FromType)
680− (schemas :: SchemasType)
681 (params :: [NullityType])
523 (params :: [NullityType])
682 (from :: FromType)
524 (from :: FromType)
525+ (grouping :: Grouping)
683 = TableExpression
526 = TableExpression
684− { fromClause :: FromClause outer commons schemas params from
527+ { fromClause :: FromClause db params from
685 -- ^ A table reference that can be a table name, or a derived table such
528 -- ^ A table reference that can be a table name, or a derived table such
686 -- as a subquery, a @JOIN@ construct, or complex combinations of these.
529 -- as a subquery, a @JOIN@ construct, or complex combinations of these.
687− , whereClause :: [Condition outer commons 'Ungrouped schemas params from]
530+ , whereClause :: [Condition db from 'Ungrouped params]
688 -- ^ optional search coditions, combined with `.&&`. After the processing
531 -- ^ optional search coditions, combined with `.&&`. After the processing
689 -- of the `fromClause` is done, each row of the derived virtual table
532 -- of the `fromClause` is done, each row of the derived virtual table
690 -- is checked against the search condition. If the result of the
533 -- is checked against the search condition. If the result of the
@@ -693,20 +536,20 @@ data TableExpression
693 -- at least one column of the table generated in the `fromClause`;
536 -- at least one column of the table generated in the `fromClause`;
694 -- this is not required, but otherwise the WHERE clause will
537 -- this is not required, but otherwise the WHERE clause will
695 -- be fairly useless.
538 -- be fairly useless.
696− , groupByClause :: GroupByClause grp from
539+ , groupByClause :: GroupByClause from grouping
697 -- ^ The `groupByClause` is used to group together those rows in a table
540 -- ^ The `groupByClause` is used to group together those rows in a table
698 -- that have the same values in all the columns listed. The order in which
541 -- that have the same values in all the columns listed. The order in which
699 -- the columns are listed does not matter. The effect is to combine each
542 -- the columns are listed does not matter. The effect is to combine each
700 -- set of rows having common values into one group row that represents all
543 -- set of rows having common values into one group row that represents all
701 -- rows in the group. This is done to eliminate redundancy in the output
544 -- rows in the group. This is done to eliminate redundancy in the output
702 -- and/or compute aggregates that apply to these groups.
545 -- and/or compute aggregates that apply to these groups.
703− , havingClause :: HavingClause outer commons grp schemas params from
546+ , havingClause :: HavingClause db from grouping params
704 -- ^ If a table has been grouped using `groupBy`, but only certain groups
547 -- ^ If a table has been grouped using `groupBy`, but only certain groups
705 -- are of interest, the `havingClause` can be used, much like a
548 -- are of interest, the `havingClause` can be used, much like a
706 -- `whereClause`, to eliminate groups from the result. Expressions in the
549 -- `whereClause`, to eliminate groups from the result. Expressions in the
707 -- `havingClause` can refer both to grouped expressions and to ungrouped
550 -- `havingClause` can refer both to grouped expressions and to ungrouped
708 -- expressions (which necessarily involve an aggregate function).
551 -- expressions (which necessarily involve an aggregate function).
709− , orderByClause :: [SortExpression outer commons grp schemas params from]
552+ , orderByClause :: [SortExpression db from grouping params]
710 -- ^ The `orderByClause` is for optional sorting. When more than one
553 -- ^ The `orderByClause` is for optional sorting. When more than one
711 -- `SortExpression` is specified, the later (right) values are used to sort
554 -- `SortExpression` is specified, the later (right) values are used to sort
712 -- rows that are equal according to the earlier (left) values.
555 -- rows that are equal according to the earlier (left) values.
@@ -716,69 +559,72 @@ data TableExpression
716 -- will be returned (but possibly fewer, if the query itself yields
559 -- will be returned (but possibly fewer, if the query itself yields
717 -- fewer rows).
560 -- fewer rows).
718 , offsetClause :: [Word64]
561 , offsetClause :: [Word64]
719− -- ^ The `offsetClause` is combined with `Prelude.+` to give an offset count
562+ -- ^ The `offsetClause` is combined with `+` to give an offset count
720 -- if nonempty. The offset count says to skip that many rows before
563 -- if nonempty. The offset count says to skip that many rows before
721 -- beginning to return rows. The rows are skipped before the limit count
564 -- beginning to return rows. The rows are skipped before the limit count
722 -- is applied.
565 -- is applied.
723− } deriving (GHC.Generic)
566+ }
725-- | Render a `TableExpression`
568-- | Render a `TableExpression`
726−instance RenderSQL (TableExpression outer commons grp schemas params from) where
569+renderTableExpression
727− renderSQL
570+ :: TableExpression db params from grouping
728− (TableExpression frm' whs' grps' hvs' srts' lims' offs') = mconcat
571+ -> ByteString
729− [ "FROM ", renderSQL frm'
572+renderTableExpression
730− , renderWheres whs'
573+ (TableExpression frm' whs' grps' hvs' srts' lims' offs') = mconcat
731− , renderSQL grps'
574+ [ "FROM ", renderFromClause frm'
732− , renderSQL hvs'
575+ , renderWheres whs'
733− , renderOrderByClause srts'
576+ , renderGroupByClause grps'
734− , renderLimits lims'
577+ , renderHavingClause hvs'
735− , renderOffsets offs' ]
578+ , renderOrderByClause srts'
736− where
579+ , renderLimits lims'
737− renderWheres = \case
580+ , renderOffsets offs'
739− wh:[] -> " WHERE" <+> renderSQL wh
582+ where
740− wh:whs -> " WHERE" <+> renderSQL (foldr (.&&) wh whs)
583+ renderWheres = \case
741− renderOrderByClause = \case
584+ [] -> ""
742− [] -> ""
585+ wh:[] -> " WHERE" <+> renderExpression wh
743− srts -> " ORDER BY"
586+ wh:whs -> " WHERE" <+> renderExpression (foldr (.&&) wh whs)
744− <+> commaSeparated (renderSQL <$> srts)
587+ renderOrderByClause = \case
745− renderLimits = \case
588+ [] -> ""
746− [] -> ""
589+ srts -> " ORDER BY"
747− lims -> " LIMIT" <+> fromString (show (minimum lims))
590+ <+> commaSeparated (renderSortExpression <$> srts)
748− renderOffsets = \case
591+ renderLimits = \case
749− [] -> ""
592+ [] -> ""
750− offs -> " OFFSET" <+> fromString (show (sum offs))
593+ lims -> " LIMIT" <+> fromString (show (minimum lims))
594+ renderOffsets = \case
596+ offs -> " OFFSET" <+> fromString (show (sum offs))
752-- | A `from` generates a `TableExpression` from a table reference that can be
598-- | A `from` generates a `TableExpression` from a table reference that can be
753-- a table name, or a derived table such as a subquery, a JOIN construct,
599-- a table name, or a derived table such as a subquery, a JOIN construct,
754-- or complex combinations of these. A `from` may be transformed by `where_`,
600-- or complex combinations of these. A `from` may be transformed by `where_`,
755−-- `groupBy`, `having`, `orderBy`, `limit` and `offset`, using the `&` operator
601+-- `group`, `having`, `orderBy`, `limit` and `offset`, using the `&` operator
756-- to match the left-to-right sequencing of their placement in SQL.
602-- to match the left-to-right sequencing of their placement in SQL.
758− :: FromClause outer commons schemas params from -- ^ table reference
604+ :: FromClause db params from -- ^ table reference
759− -> TableExpression outer commons 'Ungrouped schemas params from
605+ -> TableExpression db params from 'Ungrouped
760−from tab = TableExpression tab [] noGroups NoHaving [] [] []
606+from rels = TableExpression rels [] NoGroups NoHaving [] [] []
762-- | A `where_` is an endomorphism of `TableExpression`s which adds a
608-- | A `where_` is an endomorphism of `TableExpression`s which adds a
763-- search condition to the `whereClause`.
609-- search condition to the `whereClause`.
765− :: Condition outer commons 'Ungrouped schemas params from -- ^ filtering condition
611+ :: Condition db from 'Ungrouped params -- ^ filtering condition
766− -> TableExpression outer commons grp schemas params from
612+ -> TableExpression db params from grouping
767− -> TableExpression outer commons grp schemas params from
613+ -> TableExpression db params from grouping
768where_ wh rels = rels {whereClause = wh : whereClause rels}
614where_ wh rels = rels {whereClause = wh : whereClause rels}
770-- | A `groupBy` is a transformation of `TableExpression`s which switches
616-- | A `groupBy` is a transformation of `TableExpression`s which switches
771−-- its `Grouping` from `Ungrouped` to `Grouped`. Use @groupBy Nil@ to perform
617+-- its `Grouping` from `Ungrouped` to `Grouped`. Use @group Nil@ to perform
772-- a "grand total" aggregation query.
618-- a "grand total" aggregation query.
774 :: SListI bys
620 :: SListI bys
775 => NP (By from) bys -- ^ grouped columns
621 => NP (By from) bys -- ^ grouped columns
776− -> TableExpression outer commons 'Ungrouped schemas params from
622+ -> TableExpression db params from 'Ungrouped
777− -> TableExpression outer commons ('Grouped bys) schemas params from
623+ -> TableExpression db params from ('Grouped bys)
778groupBy bys rels = TableExpression
624groupBy bys rels = TableExpression
779 { fromClause = fromClause rels
625 { fromClause = fromClause rels
780 , whereClause = whereClause rels
626 , whereClause = whereClause rels
781− , groupByClause = group bys
627+ , groupByClause = Group bys
782 , havingClause = Having []
628 , havingClause = Having []
783 , orderByClause = []
629 , orderByClause = []
784 , limitClause = limitClause rels
630 , limitClause = limitClause rels
@@ -788,31 +634,177 @@ groupBy bys rels = TableExpression
788-- | A `having` is an endomorphism of `TableExpression`s which adds a
634-- | A `having` is an endomorphism of `TableExpression`s which adds a
789-- search condition to the `havingClause`.
635-- search condition to the `havingClause`.
791− :: Condition outer commons ('Grouped bys) schemas params from -- ^ having condition
637+ :: Condition db from ('Grouped bys) params -- ^ having condition
792− -> TableExpression outer commons ('Grouped bys) schemas params from
638+ -> TableExpression db params from ('Grouped bys)
793− -> TableExpression outer commons ('Grouped bys) schemas params from
639+ -> TableExpression db params from ('Grouped bys)
794having hv rels = rels
640having hv rels = rels
795 { havingClause = case havingClause rels of Having hvs -> Having (hv:hvs) }
641 { havingClause = case havingClause rels of Having hvs -> Having (hv:hvs) }
797−instance OrderBy TableExpression where
643+-- | An `orderBy` is an endomorphism of `TableExpression`s which appends an
798− orderBy srts rels = rels {orderByClause = orderByClause rels ++ srts}
644+-- ordering to the right of the `orderByClause`.
646+ :: [SortExpression db from grouping params] -- ^ sort expressions
647+ -> TableExpression db params from grouping
648+ -> TableExpression db params from grouping
649+orderBy srts rels = rels {orderByClause = orderByClause rels ++ srts}
800-- | A `limit` is an endomorphism of `TableExpression`s which adds to the
651-- | A `limit` is an endomorphism of `TableExpression`s which adds to the
801-- `limitClause`.
652-- `limitClause`.
803 :: Word64 -- ^ limit parameter
654 :: Word64 -- ^ limit parameter
804− -> TableExpression outer commons grp schemas params from
655+ -> TableExpression db params from grouping
805− -> TableExpression outer commons grp schemas params from
656+ -> TableExpression db params from grouping
806limit lim rels = rels {limitClause = lim : limitClause rels}
657limit lim rels = rels {limitClause = lim : limitClause rels}
808-- | An `offset` is an endomorphism of `TableExpression`s which adds to the
659-- | An `offset` is an endomorphism of `TableExpression`s which adds to the
809-- `offsetClause`.
660-- `offsetClause`.
811 :: Word64 -- ^ offset parameter
662 :: Word64 -- ^ offset parameter
812− -> TableExpression outer commons grp schemas params from
663+ -> TableExpression db params from grouping
813− -> TableExpression outer commons grp schemas params from
664+ -> TableExpression db params from grouping
814offset off rels = rels {offsetClause = off : offsetClause rels}
665offset off rels = rels {offsetClause = off : offsetClause rels}
667+{-----------------------------------------
669+-----------------------------------------}
673+ -> Expression db '[] 'Ungrouped params ty
674+ -> Query db params row
675+unsafeSetOfFunction fun expr = UnsafeQuery $
676+ "SELECT * FROM " <> fun <> "(" <> renderExpression expr <> ")"
678+-- | Expands the outermost JSON object into a set of key/value pairs.
680+ :: Expression db '[] 'Ungrouped params (nullity 'PGjson) -- ^ json object
682+ '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGjson]
683+jsonEach = unsafeSetOfFunction "json_each"
685+-- | Expands the outermost binary JSON object into a set of key/value pairs.
687+ :: Expression db '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb object
689+ '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGjsonb]
690+jsonbEach = unsafeSetOfFunction "jsonb_each"
692+-- | Expands the outermost JSON object into a set of key/value pairs.
694+ :: Expression db '[] 'Ungrouped params (nullity 'PGjson) -- ^ json object
696+ '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGtext]
697+jsonEachAsText = unsafeSetOfFunction "json_each_text"
699+-- | Expands the outermost binary JSON object into a set of key/value pairs.
701+ :: Expression db '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb object
703+ '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGtext]
704+jsonbEachAsText = unsafeSetOfFunction "jsonb_each_text"
706+-- | Returns set of keys in the outermost JSON object.
708+ :: Expression db '[] 'Ungrouped params (nullity 'PGjson) -- ^ json object
709+ -> Query db params '["json_object_keys" ::: 'NotNull 'PGtext]
710+jsonObjectKeys = unsafeSetOfFunction "json_object_keys"
712+-- | Returns set of keys in the outermost JSON object.
714+ :: Expression db '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb object
715+ -> Query db params '["jsonb_object_keys" ::: 'NotNull 'PGtext]
716+jsonbObjectKeys = unsafeSetOfFunction "jsonb_object_keys"
718+unsafePopulateFunction
720+ -> TypeExpression schemas (nullity ('PGcomposite row))
721+ -> Expression (commons :=> schemas) '[] 'Ungrouped params ty
722+ -> Query (commons :=> schemas) params row
723+unsafePopulateFunction fun ty expr = UnsafeQuery $
724+ "SELECT * FROM " <> fun <> "("
725+ <> "null::" <> renderTypeExpression ty <> ", "
726+ <> renderExpression expr <> ")"
728+-- | Expands the JSON expression to a row whose columns match the record
729+-- type defined by the given table.
731+ :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
732+ -> Expression (commons :=> schemas) '[] 'Ungrouped params (nullity 'PGjson) -- ^ json object
733+ -> Query (commons :=> schemas) params row
734+jsonPopulateRecord = unsafePopulateFunction "json_populate_record"
736+-- | Expands the binary JSON expression to a row whose columns match the record
737+-- type defined by the given table.
739+ :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
740+ -> Expression (commons :=> schemas) '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb object
741+ -> Query (commons :=> schemas) params row
742+jsonbPopulateRecord = unsafePopulateFunction "jsonb_populate_record"
744+-- | Expands the outermost array of objects in the given JSON expression to a
745+-- set of rows whose columns match the record type defined by the given table.
746+jsonPopulateRecordSet
747+ :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
748+ -> Expression (commons :=> schemas) '[] 'Ungrouped params (nullity 'PGjson) -- ^ json array
749+ -> Query (commons :=> schemas) params row
750+jsonPopulateRecordSet = unsafePopulateFunction "json_populate_record_set"
752+-- | Expands the outermost array of objects in the given binary JSON expression
753+-- to a set of rows whose columns match the record type defined by the given
755+jsonbPopulateRecordSet
756+ :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
757+ -> Expression (commons :=> schemas) '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb array
758+ -> Query (commons :=> schemas) params row
759+jsonbPopulateRecordSet = unsafePopulateFunction "jsonb_populate_record_set"
762+ :: (SListI record, json `In` PGJsonType)
764+ -> Expression (commons :=> schemas) '[] 'Ungrouped params (nullity json)
765+ -> NP (Aliased (TypeExpression schemas)) record
766+ -> Query (commons :=> schemas) params record
767+unsafeRecordFunction fun expr types = UnsafeQuery $
768+ "SELECT * FROM " <> fun <> "("
769+ <> renderExpression expr <> ")"
770+ <+> "AS" <+> "x" <> parenthesized (renderCommaSeparated renderTy types)
772+ renderTy :: Aliased (TypeExpression db) ty -> ByteString
773+ renderTy (ty `As` alias) =
774+ renderAlias alias <+> renderTypeExpression ty
776+-- | Builds an arbitrary record from a JSON object.
779+ => Expression (commons :=> schemas) '[] 'Ungrouped params (nullity 'PGjson) -- ^ json object
780+ -> NP (Aliased (TypeExpression schemas)) record -- ^ record types
781+ -> Query (commons :=> schemas) params record
782+jsonToRecord = unsafeRecordFunction "json_to_record"
784+-- | Builds an arbitrary record from a binary JSON object.
787+ => Expression (commons :=> schemas) '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb object
788+ -> NP (Aliased (TypeExpression schemas)) record -- ^ record types
789+ -> Query (commons :=> schemas) params record
790+jsonbToRecord = unsafeRecordFunction "jsonb_to_record"
792+-- | Builds an arbitrary set of records from a JSON array of objects.
795+ => Expression (commons :=> schemas) '[] 'Ungrouped params (nullity 'PGjson) -- ^ json array
796+ -> NP (Aliased (TypeExpression schemas)) record -- ^ record types
797+ -> Query (commons :=> schemas) params record
798+jsonToRecordSet = unsafeRecordFunction "json_to_record_set"
800+-- | Builds an arbitrary set of records from a binary JSON array of objects.
803+ => Expression (commons :=> schemas) '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb array
804+ -> NP (Aliased (TypeExpression schemas)) record -- ^ record types
805+ -> Query (commons :=> schemas) params record
806+jsonbToRecordSet = unsafeRecordFunction "jsonb_to_record_set"
816{-----------------------------------------
808{-----------------------------------------
817FROM clauses
809FROM clauses
818-----------------------------------------}
810-----------------------------------------}
@@ -821,76 +813,68 @@ FROM clauses
821A `FromClause` can be a table name, or a derived table such
813A `FromClause` can be a table name, or a derived table such
822as a subquery, a @JOIN@ construct, or complex combinations of these.
814as a subquery, a @JOIN@ construct, or complex combinations of these.
824−newtype FromClause outer commons schemas params from
816+newtype FromClause db params from
825 = UnsafeFromClause { renderFromClause :: ByteString }
817 = UnsafeFromClause { renderFromClause :: ByteString }
826 deriving (GHC.Generic,Show,Eq,Ord,NFData)
818 deriving (GHC.Generic,Show,Eq,Ord,NFData)
827−instance RenderSQL (FromClause outer commons schemas params from) where
828− renderSQL = renderFromClause
830-- | A real `table` is a table from the database.
820-- | A real `table` is a table from the database.
832 :: (Has sch schemas schema, Has tab schema ('Table table))
822 :: (Has sch schemas schema, Has tab schema ('Table table))
833 => Aliased (QualifiedAlias sch) (alias ::: tab)
823 => Aliased (QualifiedAlias sch) (alias ::: tab)
834− -> FromClause outer commons schemas params '[alias ::: TableToRow table]
824+ -> FromClause (commons :=> schemas) params '[alias ::: TableToRow table]
835table (tab `As` alias) = UnsafeFromClause $
825table (tab `As` alias) = UnsafeFromClause $
836− renderSQL tab <+> "AS" <+> renderSQL alias
826+ renderQualifiedAlias tab <+> "AS" <+> renderAlias alias
838-- | `subquery` derives a table from a `Query`.
828-- | `subquery` derives a table from a `Query`.
840− :: Aliased (Query outer commons schemas params) query
830+ :: Aliased (Query db params) query
841− -> FromClause outer commons schemas params '[query]
831+ -> FromClause db params '[query]
842−subquery = UnsafeFromClause . renderAliased (parenthesized . renderSQL)
832+subquery = UnsafeFromClause . renderAliasedAs (parenthesized . renderQuery)
844-- | `view` derives a table from a `View`.
834-- | `view` derives a table from a `View`.
846 :: (Has sch schemas schema, Has vw schema ('View view))
836 :: (Has sch schemas schema, Has vw schema ('View view))
847 => Aliased (QualifiedAlias sch) (alias ::: vw)
837 => Aliased (QualifiedAlias sch) (alias ::: vw)
848− -> FromClause outer commons schemas params '[alias ::: view]
838+ -> FromClause (commons :=> schemas) params '[alias ::: view]
849view (vw `As` alias) = UnsafeFromClause $
839view (vw `As` alias) = UnsafeFromClause $
850− renderSQL vw <+> "AS" <+> renderSQL alias
840+ renderQualifiedAlias vw <+> "AS" <+> renderAlias alias
852−-- | `common` derives a table from a common table expression.
854 :: Has cte commons common
843 :: Has cte commons common
855 => Aliased Alias (alias ::: cte)
844 => Aliased Alias (alias ::: cte)
856− -> FromClause outer commons schemas params '[alias ::: common]
845+ -> FromClause (commons :=> schemas) params '[alias ::: common]
857common (cte `As` alias) = UnsafeFromClause $
846common (cte `As` alias) = UnsafeFromClause $
858− renderSQL cte <+> "AS" <+> renderSQL alias
847+ renderAlias cte <+> "AS" <+> renderAlias alias
860−instance Additional (FromClause outer commons schemas params) where
849+{- | @left & crossJoin right@. For every possible combination of rows from
861− also right left = UnsafeFromClause $
850+ @left@ and @right@ (i.e., a Cartesian product), the joined table will contain
862− renderSQL left <> ", " <> renderSQL right
851+ a row consisting of all columns in @left@ followed by all columns in @right@.
863−
852+ If the tables have @n@ and @m@ rows respectively, the joined table will
864−{- |
853+ have @n * m@ rows.
865−@left & crossJoin right@. For every possible combination of rows from
866−@left@ and @right@ (i.e., a Cartesian product), the joined table will contain
867−a row consisting of all columns in @left@ followed by all columns in @right@.
868−If the tables have @n@ and @m@ rows respectively, the joined table will
872− :: FromClause outer commons schemas params right
856+ :: FromClause db params right
873 -- ^ right
857 -- ^ right
874− -> FromClause outer commons schemas params left
858+ -> FromClause db params left
875 -- ^ left
859 -- ^ left
876− -> FromClause outer commons schemas params (Join left right)
860+ -> FromClause db params (Join left right)
877crossJoin right left = UnsafeFromClause $
861crossJoin right left = UnsafeFromClause $
878− renderSQL left <+> "CROSS JOIN" <+> renderSQL right
862+ renderFromClause left <+> "CROSS JOIN" <+> renderFromClause right
880{- | @left & innerJoin right on@. The joined table is filtered by
864{- | @left & innerJoin right on@. The joined table is filtered by
881the @on@ condition.
865the @on@ condition.
884− :: FromClause outer commons schemas params right
868+ :: FromClause db params right
885 -- ^ right
869 -- ^ right
886− -> Condition outer commons 'Ungrouped schemas params (Join left right)
870+ -> Condition db (Join left right) 'Ungrouped params
887 -- ^ @on@ condition
871 -- ^ @on@ condition
888− -> FromClause outer commons schemas params left
872+ -> FromClause db params left
889 -- ^ left
873 -- ^ left
890− -> FromClause outer commons schemas params (Join left right)
874+ -> FromClause db params (Join left right)
891innerJoin right on left = UnsafeFromClause $
875innerJoin right on left = UnsafeFromClause $
892− renderSQL left <+> "INNER JOIN" <+> renderSQL right
876+ renderFromClause left <+> "INNER JOIN" <+> renderFromClause right
893− <+> "ON" <+> renderSQL on
877+ <+> "ON" <+> renderExpression on
895{- | @left & leftOuterJoin right on@. First, an inner join is performed.
879{- | @left & leftOuterJoin right on@. First, an inner join is performed.
896 Then, for each row in @left@ that does not satisfy the @on@ condition with
880 Then, for each row in @left@ that does not satisfy the @on@ condition with
@@ -898,16 +882,16 @@ innerJoin right on left = UnsafeFromClause $
898 Thus, the joined table always has at least one row for each row in @left@.
882 Thus, the joined table always has at least one row for each row in @left@.
900leftOuterJoin
884leftOuterJoin
901− :: FromClause outer commons schemas params right
885+ :: FromClause db params right
902 -- ^ right
886 -- ^ right
903− -> Condition outer commons 'Ungrouped schemas params (Join left right)
887+ -> Condition db (Join left right) 'Ungrouped params
904 -- ^ @on@ condition
888 -- ^ @on@ condition
905− -> FromClause outer commons schemas params left
889+ -> FromClause db params left
906 -- ^ left
890 -- ^ left
907− -> FromClause outer commons schemas params (Join left (NullifyFrom right))
891+ -> FromClause db params (Join left (NullifyFrom right))
908leftOuterJoin right on left = UnsafeFromClause $
892leftOuterJoin right on left = UnsafeFromClause $
909− renderSQL left <+> "LEFT OUTER JOIN" <+> renderSQL right
893+ renderFromClause left <+> "LEFT OUTER JOIN" <+> renderFromClause right
910− <+> "ON" <+> renderSQL on
894+ <+> "ON" <+> renderExpression on
912{- | @left & rightOuterJoin right on@. First, an inner join is performed.
896{- | @left & rightOuterJoin right on@. First, an inner join is performed.
913 Then, for each row in @right@ that does not satisfy the @on@ condition with
897 Then, for each row in @right@ that does not satisfy the @on@ condition with
@@ -916,16 +900,16 @@ leftOuterJoin right on left = UnsafeFromClause $
916 have a row for each row in @right@.
900 have a row for each row in @right@.
918rightOuterJoin
902rightOuterJoin
919− :: FromClause outer commons schemas params right
903+ :: FromClause db params right
920 -- ^ right
904 -- ^ right
921− -> Condition outer commons 'Ungrouped schemas params (Join left right)
905+ -> Condition db (Join left right) 'Ungrouped params
922 -- ^ @on@ condition
906 -- ^ @on@ condition
923− -> FromClause outer commons schemas params left
907+ -> FromClause db params left
924 -- ^ left
908 -- ^ left
925− -> FromClause outer commons schemas params (Join (NullifyFrom left) right)
909+ -> FromClause db params (Join (NullifyFrom left) right)
926rightOuterJoin right on left = UnsafeFromClause $
910rightOuterJoin right on left = UnsafeFromClause $
927− renderSQL left <+> "RIGHT OUTER JOIN" <+> renderSQL right
911+ renderFromClause left <+> "RIGHT OUTER JOIN" <+> renderFromClause right
928− <+> "ON" <+> renderSQL on
912+ <+> "ON" <+> renderExpression on
930{- | @left & fullOuterJoin right on@. First, an inner join is performed.
914{- | @left & fullOuterJoin right on@. First, an inner join is performed.
931 Then, for each row in @left@ that does not satisfy the @on@ condition with
915 Then, for each row in @left@ that does not satisfy the @on@ condition with
@@ -935,23 +919,23 @@ rightOuterJoin right on left = UnsafeFromClause $
935 is added.
919 is added.
937fullOuterJoin
921fullOuterJoin
938− :: FromClause outer commons schemas params right
922+ :: FromClause db params right
939 -- ^ right
923 -- ^ right
940− -> Condition outer commons 'Ungrouped schemas params (Join left right)
924+ -> Condition db (Join left right) 'Ungrouped params
941 -- ^ @on@ condition
925 -- ^ @on@ condition
942− -> FromClause outer commons schemas params left
926+ -> FromClause db params left
943 -- ^ left
927 -- ^ left
944− -> FromClause outer commons schemas params
928+ -> FromClause db params
945 (Join (NullifyFrom left) (NullifyFrom right))
929 (Join (NullifyFrom left) (NullifyFrom right))
946fullOuterJoin right on left = UnsafeFromClause $
930fullOuterJoin right on left = UnsafeFromClause $
947− renderSQL left <+> "FULL OUTER JOIN" <+> renderSQL right
931+ renderFromClause left <+> "FULL OUTER JOIN" <+> renderFromClause right
948− <+> "ON" <+> renderSQL on
932+ <+> "ON" <+> renderExpression on
950{-----------------------------------------
934{-----------------------------------------
952-----------------------------------------}
936-----------------------------------------}
954−-- | `By`s are used in `groupBy` to reference a list of columns which are then
938+-- | `By`s are used in `group` to reference a list of columns which are then
955-- used to group together those rows in a table that have the same values
939-- used to group together those rows in a table that have the same values
956-- in all the columns listed. @By \#col@ will reference an unambiguous
940-- in all the columns listed. @By \#col@ will reference an unambiguous
957-- column @col@; otherwise @By2 (\#tab \! \#col)@ will reference a table
941-- column @col@; otherwise @By2 (\#tab \! \#col)@ will reference a table
@@ -971,10 +955,6 @@ data By
971deriving instance Show (By from by)
955deriving instance Show (By from by)
972deriving instance Eq (By from by)
956deriving instance Eq (By from by)
973deriving instance Ord (By from by)
957deriving instance Ord (By from by)
974−instance RenderSQL (By from by) where
976− By1 column -> renderSQL column
977− By2 rel column -> renderSQL rel <> "." <> renderSQL column
979instance (HasUnique rel rels cols, Has col cols ty, by ~ '(rel, col))
959instance (HasUnique rel rels cols, Has col cols ty, by ~ '(rel, col))
980 => IsLabel col (By rels by) where fromLabel = By1 fromLabel
960 => IsLabel col (By rels by) where fromLabel = By1 fromLabel
@@ -986,113 +966,424 @@ instance (Has rel rels cols, Has col cols ty, bys ~ '[ '(rel, col)])
986 => IsQualified rel col (NP (By rels) bys) where
966 => IsQualified rel col (NP (By rels) bys) where
987 rel ! col = By2 rel col :* Nil
967 rel ! col = By2 rel col :* Nil
970+renderBy :: By from by -> ByteString
972+ By1 column -> renderAlias column
973+ By2 rel column -> renderAlias rel <> "." <> renderAlias column
989-- | A `GroupByClause` indicates the `Grouping` of a `TableExpression`.
975-- | A `GroupByClause` indicates the `Grouping` of a `TableExpression`.
990-- A `NoGroups` indicates `Ungrouped` while a `Group` indicates `Grouped`.
976-- A `NoGroups` indicates `Ungrouped` while a `Group` indicates `Grouped`.
991-- @NoGroups@ is distinguised from @Group Nil@ since no aggregation can be
977-- @NoGroups@ is distinguised from @Group Nil@ since no aggregation can be
992-- done on @NoGroups@ while all output `Expression`s must be aggregated
978-- done on @NoGroups@ while all output `Expression`s must be aggregated
993-- in @Group Nil@. In general, all output `Expression`s in the
979-- in @Group Nil@. In general, all output `Expression`s in the
994-- complement of @bys@ must be aggregated in @Group bys@.
980-- complement of @bys@ must be aggregated in @Group bys@.
995−newtype GroupByClause grp from = UnsafeGroupByClause
981+data GroupByClause from grouping where
996− { renderGroupByClause :: ByteString }
982+ NoGroups :: GroupByClause from 'Ungrouped
997− deriving (GHC.Generic,Show,Eq,Ord,NFData)
983+ Group
998−instance RenderSQL (GroupByClause grp from) where
984+ :: SListI bys
999− renderSQL = renderGroupByClause
985+ => NP (By from) bys
1000−noGroups :: GroupByClause 'Ungrouped from
986+ -> GroupByClause from ('Grouped bys)
1001−noGroups = UnsafeGroupByClause ""
987+
1002−group
988+-- | Renders a `GroupByClause`.
1003− :: SListI bys
989+renderGroupByClause :: GroupByClause from grouping -> ByteString
1004− => NP (By from) bys
990+renderGroupByClause = \case
1005− -> GroupByClause ('Grouped bys) from
991+ NoGroups -> ""
1006−group bys = UnsafeGroupByClause $ case bys of
992+ Group Nil -> ""
1007− Nil -> ""
993+ Group bys -> " GROUP BY" <+> renderCommaSeparated renderBy bys
1008− bys' -> " GROUP BY" <+> renderCommaSeparated renderSQL bys'
1010-- | A `HavingClause` is used to eliminate groups that are not of interest.
995-- | A `HavingClause` is used to eliminate groups that are not of interest.
1011-- An `Ungrouped` `TableExpression` may only use `NoHaving` while a `Grouped`
996-- An `Ungrouped` `TableExpression` may only use `NoHaving` while a `Grouped`
1012-- `TableExpression` must use `Having` whose conditions are combined with
997-- `TableExpression` must use `Having` whose conditions are combined with
1013-- `.&&`.
998-- `.&&`.
1014−data HavingClause outer commons grp schemas params from where
999+data HavingClause db from grouping params where
1015− NoHaving :: HavingClause outer commons 'Ungrouped schemas params from
1000+ NoHaving :: HavingClause db from 'Ungrouped params
1017− :: [Condition outer commons ('Grouped bys) schemas params from]
1002+ :: [Condition db from ('Grouped bys) params]
1018− -> HavingClause outer commons ('Grouped bys) schemas params from
1003+ -> HavingClause db from ('Grouped bys) params
1019−deriving instance Show (HavingClause outer commons grp schemas params from)
1004+deriving instance Show (HavingClause db from grouping params)
1020−deriving instance Eq (HavingClause outer commons grp schemas params from)
1005+deriving instance Eq (HavingClause db from grouping params)
1021−deriving instance Ord (HavingClause outer commons grp schemas params from)
1006+deriving instance Ord (HavingClause db from grouping params)
1023-- | Render a `HavingClause`.
1008-- | Render a `HavingClause`.
1024−instance RenderSQL (HavingClause outer commons grp schemas params from) where
1009+renderHavingClause :: HavingClause db from grouping params -> ByteString
1025− renderSQL = \case
1010+renderHavingClause = \case
1026− NoHaving -> ""
1011+ NoHaving -> ""
1027− Having [] -> ""
1012+ Having [] -> ""
1028− Having conditions ->
1013+ Having conditions ->
1029− " HAVING" <+> commaSeparated (renderSQL <$> conditions)
1014+ " HAVING" <+> commaSeparated (renderExpression <$> conditions)
1016+{-----------------------------------------
1018+-----------------------------------------}
1020+-- | `SortExpression`s are used by `sortBy` to optionally sort the results
1021+-- of a `Query`. `Asc` or `Desc` set the sort direction of a `NotNull` result
1022+-- column to ascending or descending. Ascending order puts smaller values
1023+-- first, where "smaller" is defined in terms of the `.<` operator. Similarly,
1024+-- descending order is determined with the `.>` operator. `AscNullsFirst`,
1025+-- `AscNullsLast`, `DescNullsFirst` and `DescNullsLast` options are used to
1026+-- determine whether nulls appear before or after non-null values in the sort
1027+-- ordering of a `Null` result column.
1028+data SortExpression db from grouping params where
1030+ :: Expression db from grouping params ('NotNull ty)
1031+ -> SortExpression db from grouping params
1033+ :: Expression db from grouping params ('NotNull ty)
1034+ -> SortExpression db from grouping params
1036+ :: Expression db from grouping params ('Null ty)
1037+ -> SortExpression db from grouping params
1039+ :: Expression db from grouping params ('Null ty)
1040+ -> SortExpression db from grouping params
1042+ :: Expression db from grouping params ('Null ty)
1043+ -> SortExpression db from grouping params
1045+ :: Expression db from grouping params ('Null ty)
1046+ -> SortExpression db from grouping params
1047+deriving instance Show (SortExpression db from grouping params)
1049+-- | Render a `SortExpression`.
1050+renderSortExpression :: SortExpression db from grouping params -> ByteString
1051+renderSortExpression = \case
1052+ Asc expression -> renderExpression expression <+> "ASC"
1053+ Desc expression -> renderExpression expression <+> "DESC"
1054+ AscNullsFirst expression -> renderExpression expression
1055+ <+> "ASC NULLS FIRST"
1056+ DescNullsFirst expression -> renderExpression expression
1057+ <+> "DESC NULLS FIRST"
1058+ AscNullsLast expression -> renderExpression expression <+> "ASC NULLS LAST"
1059+ DescNullsLast expression -> renderExpression expression <+> "DESC NULLS LAST"
1061+unsafeSubqueryExpression
1063+ -> Expression db from grp params ty
1064+ -> Query db params '[alias ::: ty]
1065+ -> Expression db from grp params (nullity 'PGbool)
1066+unsafeSubqueryExpression op x q = UnsafeExpression $
1067+ renderExpression x <+> op <+> parenthesized (renderQuery q)
1069+unsafeRowSubqueryExpression
1072+ -> NP (Aliased (Expression db from grp params)) row
1073+ -> Query db params row
1074+ -> Expression db from grp params (nullity 'PGbool)
1075+unsafeRowSubqueryExpression op xs q = UnsafeExpression $
1076+ renderExpression (row xs) <+> op <+> parenthesized (renderQuery q)
1078+-- | The right-hand side is a sub`Query`, which must return exactly one column.
1079+-- The left-hand expression is evaluated and compared to each row of the
1080+-- sub`Query` result. The result of `in_` is `true` if any equal subquery row is found.
1081+-- The result is `false` if no equal row is found
1082+-- (including the case where the subquery returns no rows).
1084+-- >>> printSQL $ true `in_` values_ (true `as` #foo)
1085+-- TRUE IN (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
1087+ :: Expression db from grp params ty -- ^ expression
1088+ -> Query db params '[alias ::: ty] -- ^ subquery
1089+ -> Expression db from grp params (nullity 'PGbool)
1090+in_ = unsafeSubqueryExpression "IN"
1092+{- | The left-hand side of this form of `rowIn` is a row constructor.
1093+The right-hand side is a sub`Query`,
1094+which must return exactly as many columns as
1095+there are expressions in the left-hand row.
1096+The left-hand expressions are evaluated and compared row-wise to each row
1097+of the subquery result. The result of `rowIn`
1098+is `true` if any equal subquery row is found.
1099+The result is `false` if no equal row is found
1100+(including the case where the subquery returns no rows).
1102+>>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression db from grp params)) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
1103+>>> printSQL $ myRow `rowIn` values_ myRow
1104+ROW(1, FALSE) IN (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
1108+ => NP (Aliased (Expression db from grp params)) row -- ^ row constructor
1109+ -> Query db params row -- ^ subquery
1110+ -> Expression db from grp params (nullity 'PGbool)
1111+rowIn = unsafeRowSubqueryExpression "IN"
1113+-- | >>> printSQL $ true `eqAll` values_ (true `as` #foo)
1114+-- TRUE = ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
1116+ :: Expression db from grp params ty -- ^ expression
1117+ -> Query db params '[alias ::: ty] -- ^ subquery
1118+ -> Expression db from grp params (nullity 'PGbool)
1119+eqAll = unsafeSubqueryExpression "= ALL"
1121+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression db from grp params)) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
1122+-- >>> printSQL $ myRow `rowEqAll` values_ myRow
1123+-- ROW(1, FALSE) = ALL (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
1126+ => NP (Aliased (Expression db from grp params)) row -- ^ row constructor
1127+ -> Query db params row -- ^ subquery
1128+ -> Expression db from grp params (nullity 'PGbool)
1129+rowEqAll = unsafeRowSubqueryExpression "= ALL"
1131+-- | >>> printSQL $ true `eqAny` values_ (true `as` #foo)
1132+-- TRUE = ANY (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
1134+ :: Expression db from grp params ty -- ^ expression
1135+ -> Query db params '[alias ::: ty] -- ^ subquery
1136+ -> Expression db from grp params (nullity 'PGbool)
1137+eqAny = unsafeSubqueryExpression "= ANY"
1139+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression db from grp params)) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
1140+-- >>> printSQL $ myRow `rowEqAny` values_ myRow
1141+-- ROW(1, FALSE) = ANY (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
1144+ => NP (Aliased (Expression db from grp params)) row -- ^ row constructor
1145+ -> Query db params row -- ^ subquery
1146+ -> Expression db from grp params (nullity 'PGbool)
1147+rowEqAny = unsafeRowSubqueryExpression "= ANY"
1149+-- | >>> printSQL $ true `neqAll` values_ (true `as` #foo)
1150+-- TRUE <> ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
1152+ :: Expression db from grp params ty -- ^ expression
1153+ -> Query db params '[alias ::: ty] -- ^ subquery
1154+ -> Expression db from grp params (nullity 'PGbool)
1155+neqAll = unsafeSubqueryExpression "<> ALL"
1157+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression db from grp params)) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
1158+-- >>> printSQL $ myRow `rowNeqAll` values_ myRow
1159+-- ROW(1, FALSE) <> ALL (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
1162+ => NP (Aliased (Expression db from grp params)) row -- ^ row constructor
1163+ -> Query db params row -- ^ subquery
1164+ -> Expression db from grp params (nullity 'PGbool)
1165+rowNeqAll = unsafeRowSubqueryExpression "<> ALL"
1167+-- | >>> printSQL $ true `neqAny` values_ (true `as` #foo)
1168+-- TRUE <> ANY (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
1170+ :: Expression db from grp params ty -- ^ expression
1171+ -> Query db params '[alias ::: ty] -- ^ subquery
1172+ -> Expression db from grp params (nullity 'PGbool)
1173+neqAny = unsafeSubqueryExpression "<> ANY"
1175+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression db from grp params)) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
1176+-- >>> printSQL $ myRow `rowNeqAny` values_ myRow
1177+-- ROW(1, FALSE) <> ANY (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
1180+ => NP (Aliased (Expression db from grp params)) row -- ^ row constructor
1181+ -> Query db params row -- ^ subquery
1182+ -> Expression db from grp params (nullity 'PGbool)
1183+rowNeqAny = unsafeRowSubqueryExpression "<> ANY"
1185+-- | >>> printSQL $ true `allLt` values_ (true `as` #foo)
1186+-- TRUE ALL < (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
1188+ :: Expression db from grp params ty -- ^ expression
1189+ -> Query db params '[alias ::: ty] -- ^ subquery
1190+ -> Expression db from grp params (nullity 'PGbool)
1191+allLt = unsafeSubqueryExpression "ALL <"
1193+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression db from grp params)) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
1194+-- >>> printSQL $ myRow `rowLtAll` values_ myRow
1195+-- ROW(1, FALSE) ALL < (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
1198+ => NP (Aliased (Expression db from grp params)) row -- ^ row constructor
1199+ -> Query db params row -- ^ subquery
1200+ -> Expression db from grp params (nullity 'PGbool)
1201+rowLtAll = unsafeRowSubqueryExpression "ALL <"
1203+-- | >>> printSQL $ true `ltAny` values_ (true `as` #foo)
1204+-- TRUE ANY < (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
1206+ :: Expression db from grp params ty -- ^ expression
1207+ -> Query db params '[alias ::: ty] -- ^ subquery
1208+ -> Expression db from grp params (nullity 'PGbool)
1209+ltAny = unsafeSubqueryExpression "ANY <"
1211+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression db from grp params)) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
1212+-- >>> printSQL $ myRow `rowLtAll` values_ myRow
1213+-- ROW(1, FALSE) ALL < (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
1216+ => NP (Aliased (Expression db from grp params)) row -- ^ row constructor
1217+ -> Query db params row -- ^ subquery
1218+ -> Expression db from grp params (nullity 'PGbool)
1219+rowLtAny = unsafeRowSubqueryExpression "ANY <"
1221+-- | >>> printSQL $ true `lteAll` values_ (true `as` #foo)
1222+-- TRUE <= ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
1224+ :: Expression db from grp params ty -- ^ expression
1225+ -> Query db params '[alias ::: ty] -- ^ subquery
1226+ -> Expression db from grp params (nullity 'PGbool)
1227+lteAll = unsafeSubqueryExpression "<= ALL"
1229+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression db from grp params)) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
1230+-- >>> printSQL $ myRow `rowLteAll` values_ myRow
1231+-- ROW(1, FALSE) <= ALL (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
1234+ => NP (Aliased (Expression db from grp params)) row -- ^ row constructor
1235+ -> Query db params row -- ^ subquery
1236+ -> Expression db from grp params (nullity 'PGbool)
1237+rowLteAll = unsafeRowSubqueryExpression "<= ALL"
1239+-- | >>> printSQL $ true `lteAny` values_ (true `as` #foo)
1240+-- TRUE <= ANY (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
1242+ :: Expression db from grp params ty -- ^ expression
1243+ -> Query db params '[alias ::: ty] -- ^ subquery
1244+ -> Expression db from grp params (nullity 'PGbool)
1245+lteAny = unsafeSubqueryExpression "<= ANY"
1247+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression db from grp params)) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
1248+-- >>> printSQL $ myRow `rowLteAny` values_ myRow
1249+-- ROW(1, FALSE) <= ANY (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
1252+ => NP (Aliased (Expression db from grp params)) row -- ^ row constructor
1253+ -> Query db params row -- ^ subquery
1254+ -> Expression db from grp params (nullity 'PGbool)
1255+rowLteAny = unsafeRowSubqueryExpression "<= ANY"
1257+-- | >>> printSQL $ true `gtAll` values_ (true `as` #foo)
1258+-- TRUE > ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
1260+ :: Expression db from grp params ty -- ^ expression
1261+ -> Query db params '[alias ::: ty] -- ^ subquery
1262+ -> Expression db from grp params (nullity 'PGbool)
1263+gtAll = unsafeSubqueryExpression "> ALL"
1265+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression db from grp params)) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
1266+-- >>> printSQL $ myRow `rowGtAll` values_ myRow
1267+-- ROW(1, FALSE) > ALL (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
1270+ => NP (Aliased (Expression db from grp params)) row -- ^ row constructor
1271+ -> Query db params row -- ^ subquery
1272+ -> Expression db from grp params (nullity 'PGbool)
1273+rowGtAll = unsafeRowSubqueryExpression "> ALL"
1275+-- | >>> printSQL $ true `gtAny` values_ (true `as` #foo)
1276+-- TRUE > ANY (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
1278+ :: Expression db from grp params ty -- ^ expression
1279+ -> Query db params '[alias ::: ty] -- ^ subquery
1280+ -> Expression db from grp params (nullity 'PGbool)
1281+gtAny = unsafeSubqueryExpression "> ANY"
1283+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression db from grp params)) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
1284+-- >>> printSQL $ myRow `rowGtAny` values_ myRow
1285+-- ROW(1, FALSE) > ANY (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
1288+ => NP (Aliased (Expression db from grp params)) row -- ^ row constructor
1289+ -> Query db params row -- ^ subquery
1290+ -> Expression db from grp params (nullity 'PGbool)
1291+rowGtAny = unsafeRowSubqueryExpression "> ANY"
1293+-- | >>> printSQL $ true `gteAll` values_ (true `as` #foo)
1294+-- TRUE >= ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
1296+ :: Expression db from grp params ty -- ^ expression
1297+ -> Query db params '[alias ::: ty] -- ^ subquery
1298+ -> Expression db from grp params (nullity 'PGbool)
1299+gteAll = unsafeSubqueryExpression ">= ALL"
1301+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression db from grp params)) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
1302+-- >>> printSQL $ myRow `rowGteAll` values_ myRow
1303+-- ROW(1, FALSE) >= ALL (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
1306+ => NP (Aliased (Expression db from grp params)) row -- ^ row constructor
1307+ -> Query db params row -- ^ subquery
1308+ -> Expression db from grp params (nullity 'PGbool)
1309+rowGteAll = unsafeRowSubqueryExpression ">= ALL"
1311+-- | >>> printSQL $ true `gteAny` values_ (true `as` #foo)
1312+-- TRUE >= ANY (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
1314+ :: Expression db from grp params ty -- ^ expression
1315+ -> Query db params '[alias ::: ty] -- ^ subquery
1316+ -> Expression db from grp params (nullity 'PGbool)
1317+gteAny = unsafeSubqueryExpression ">= ANY"
1319+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression db from grp params)) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
1320+-- >>> printSQL $ myRow `rowGteAny` values_ myRow
1321+-- ROW(1, FALSE) >= ANY (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
1324+ => NP (Aliased (Expression db from grp params)) row -- ^ row constructor
1325+ -> Query db params row -- ^ subquery
1326+ -> Expression db from grp params (nullity 'PGbool)
1327+rowGteAny = unsafeRowSubqueryExpression ">= ANY"
1031-- | A `CommonTableExpression` is an auxiliary statement in a `with` clause.
1329-- | A `CommonTableExpression` is an auxiliary statement in a `with` clause.
1032data CommonTableExpression statement
1330data CommonTableExpression statement
1033− (schemas :: SchemasType)
1034 (params :: [NullityType])
1331 (params :: [NullityType])
1035− (commons0 :: FromType)
1332+ (db0 :: DBType)
1036− (commons1 :: FromType) where
1333+ (db1 :: DBType) where
1037 CommonTableExpression
1334 CommonTableExpression
1038− :: Aliased (statement commons schemas params) (cte ::: common)
1335+ :: Aliased (statement (commons :=> schemas) params) (cte ::: common)
1039− -> CommonTableExpression statement schemas params commons (cte ::: common ': commons)
1336+ -> CommonTableExpression statement params
1337+ (commons :=> schemas) ((cte ::: common ': commons) :=> schemas)
1041 ( KnownSymbol cte
1339 ( KnownSymbol cte
1042− , commons1 ~ (cte ::: common ': commons)
1340+ , db0 ~ (commons :=> schemas)
1341+ , db1 ~ ((cte ::: common ': commons) :=> schemas)
1043 ) => Aliasable cte
1342 ) => Aliasable cte
1044− (statement commons schemas params common)
1343+ (statement db0 params common)
1045− (CommonTableExpression statement schemas params commons commons1) where
1344+ (CommonTableExpression statement params db0 db1) where
1046 statement `as` cte = CommonTableExpression (statement `as` cte)
1345 statement `as` cte = CommonTableExpression (statement `as` cte)
1048 ( KnownSymbol cte
1347 ( KnownSymbol cte
1049− , commons1 ~ (cte ::: common ': commons)
1348+ , db0 ~ (commons :=> schemas)
1349+ , db1 ~ ((cte ::: common ': commons) :=> schemas)
1050 ) => Aliasable cte
1350 ) => Aliasable cte
1051− (statement commons schemas params common)
1351+ (statement db0 params common)
1052− (Path (CommonTableExpression statement schemas params) commons commons1) where
1352+ (AlignedList (CommonTableExpression statement params) db0 db1) where
1053− statement `as` cte = csingleton (statement `as` cte)
1353+ statement `as` cte = single (statement `as` cte)
1055−instance (forall c s p r. RenderSQL (statement c s p r)) => RenderSQL
1355+-- | render a `CommonTableExpression`.
1056− (CommonTableExpression statement schemas params commons0 commons1) where
1356+renderCommonTableExpression
1057− renderSQL (CommonTableExpression (statement `As` cte)) =
1357+ :: (forall db ps row. statement db ps row -> ByteString) -- ^ render statement
1058− renderSQL cte <+> "AS" <+> parenthesized (renderSQL statement)
1358+ -> CommonTableExpression statement params db0 db1 -> ByteString
1359+renderCommonTableExpression renderStatement
1360+ (CommonTableExpression (statement `As` alias)) =
1361+ renderAlias alias <+> "AS" <+> parenthesized (renderStatement statement)
1363+-- | render a non-empty `AlignedList` of `CommonTableExpression`s.
1364+renderCommonTableExpressions
1365+ :: (forall db ps row. statement db ps row -> ByteString) -- ^ render statement
1366+ -> CommonTableExpression statement params db0 db1
1367+ -> AlignedList (CommonTableExpression statement params) db1 db2
1369+renderCommonTableExpressions renderStatement cte ctes =
1370+ renderCommonTableExpression renderStatement cte <> case ctes of
1372+ cte' :>> ctes' -> "," <+>
1373+ renderCommonTableExpressions renderStatement cte' ctes'
1060-- | `with` provides a way to write auxiliary statements for use in a larger query.
1375-- | `with` provides a way to write auxiliary statements for use in a larger query.
1061-- These statements, referred to as `CommonTableExpression`s, can be thought of as
1376-- These statements, referred to as `CommonTableExpression`s, can be thought of as
1062-- defining temporary tables that exist just for one query.
1377-- defining temporary tables that exist just for one query.
1063class With statement where
1378class With statement where
1065− :: Path (CommonTableExpression statement schemas params) commons0 commons1
1380+ :: AlignedList (CommonTableExpression statement params) db0 db1
1066 -- ^ common table expressions
1381 -- ^ common table expressions
1067− -> statement commons1 schemas params row
1382+ -> statement db1 params row
1068 -- ^ larger query
1383 -- ^ larger query
1069− -> statement commons0 schemas params row
1384+ -> statement db0 params row
1070−instance With (Query outer) where
1385+instance With Query where
1071 with Done query = query
1386 with Done query = query
1072− with ctes query = UnsafeQuery $
1387+ with (cte :>> ctes) query = UnsafeQuery $
1073− "WITH" <+> commaSeparated (ctoList renderSQL ctes) <+> renderSQL query
1388+ "WITH" <+> renderCommonTableExpressions renderQuery cte ctes
1074−
1389+ <+> renderQuery query
1076−>>> import Data.Monoid (Sum (..))
1077−>>> import Data.Int (Int64)
1080− query :: Query_ schema () (Sum Int64)
1081− query = withRecursive
1082− ( values_ ((1 & astype int) `as` #n)
1084− select_ ((#n + 1) `as` #n)
1085− (from (common #t) & where_ (#n .< 100)) `as` #t )
1086− ( select_ (fromNull 0 (sum_ (All #n)) `as` #getSum) (from (common #t) & groupBy Nil))
1089−WITH RECURSIVE "t" AS ((SELECT * FROM (VALUES ((1 :: int))) AS t ("n")) UNION ALL (SELECT ("n" + 1) AS "n" FROM "t" AS "t" WHERE ("n" < 100))) SELECT COALESCE(sum(ALL "n"), 0) AS "getSum" FROM "t" AS "t"
1092− :: Aliased (Query outer (recursive ': commons) schemas params) recursive
1093− -> Query outer (recursive ': commons) schemas params row
1094− -> Query outer commons schemas params row
1095−withRecursive (recursive `As` cte) query = UnsafeQuery $
1096− "WITH RECURSIVE" <+> renderSQL cte
1097− <+> "AS" <+> parenthesized (renderSQL recursive)
1098− <+> renderSQL query