@@ -1,7 +1,7 @@
2Module: Squeal.PostgreSQL.Query
2Module: Squeal.PostgreSQL.Query
3Description: Squeal queries
3Description: Squeal queries
4−Copyright: (c) Eitan Chatav, 2017
4+Copyright: (c) Eitan Chatav, 2019
5Maintainer: eitan@morphism.tech
5Maintainer: eitan@morphism.tech
6Stability: experimental
6Stability: experimental
@@ -32,13 +32,15 @@ Squeal queries.
33module Squeal.PostgreSQL.Query
33module Squeal.PostgreSQL.Query
34 ( -- * Queries
34 ( -- * Queries
35− Query (..)
36+ , Query (..)
37 -- ** Select
37 -- ** Select
40 , selectDistinct
40 , selectDistinct
41 , selectDistinct_
41 , selectDistinct_
42 , Selection (..)
44 , Selection (..)
43 -- ** Values
45 -- ** Values
@@ -53,23 +55,7 @@ module Squeal.PostgreSQL.Query
53 -- ** With
55 -- ** With
54 , With (..)
56 , With (..)
55 , CommonTableExpression (..)
57 , CommonTableExpression (..)
56− -- ** Unnest
58+ , withRecursive
66− , jsonbPopulateRecord
67− , jsonPopulateRecordSet
68− , jsonbPopulateRecordSet
73 -- * Table Expressions
59 -- * Table Expressions
74 , TableExpression (..)
60 , TableExpression (..)
@@ -93,13 +79,6 @@ module Squeal.PostgreSQL.Query
94 , GroupByClause (..)
80 , GroupByClause (..)
95 , HavingClause (..)
81 , HavingClause (..)
96− -- * Subquery Expressions
105import Control.DeepSeq
84import Control.DeepSeq
@@ -124,10 +103,55 @@ import Squeal.PostgreSQL.Schema
126-- >>> import Squeal.PostgreSQL
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
129−The process of retrieving or the command to retrieve data from a database
112+The process of retrieving or the command to retrieve data from
113+a database is called a `Query`.
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.
130−is called a `Query`. Let's see some examples of queries.
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)
132simple query:
156simple query:
@@ -135,7 +159,7 @@ simple query:
135>>> type Schema = '["tab" ::: 'Table ('[] :=> Columns)]
159>>> type Schema = '["tab" ::: 'Table ('[] :=> Columns)]
138− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
162+ query :: Query_ (Public Schema) () (Row Int32 Int32)
139 query = select Star (from (table #tab))
163 query = select Star (from (table #tab))
140in printSQL query
164in printSQL query
@@ -145,21 +169,21 @@ restricted query:
148− query :: Query '[] '[] (Public Schema) '[] '["sum" ::: 'NotNull 'PGint4, "col1" ::: 'NotNull 'PGint4]
172+ query :: Query_ (Public Schema) () (Row Int32 Int32)
150− select_ ((#col1 + #col2) `as` #sum :* #col1)
174+ select_ ((#col1 + #col2) `as` #col1 :* #col1 `as` #col2)
151 ( from (table #tab)
175 ( from (table #tab)
152 & where_ (#col1 .> #col2)
176 & where_ (#col1 .> #col2)
153 & where_ (#col2 .> 0) )
177 & where_ (#col2 .> 0) )
154in printSQL query
178in printSQL query
156−SELECT ("col1" + "col2") AS "sum", "col1" AS "col1" FROM "tab" AS "tab" WHERE (("col1" > "col2") AND ("col2" > 0))
180+SELECT ("col1" + "col2") AS "col1", "col1" AS "col2" FROM "tab" AS "tab" WHERE (("col1" > "col2") AND ("col2" > 0))
162− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
186+ query :: Query_ (Public Schema) () (Row Int32 Int32)
163 query = select Star (from (subquery (select Star (from (table #tab)) `as` #sub)))
187 query = select Star (from (subquery (select Star (from (table #tab)) `as` #sub)))
164in printSQL query
188in printSQL query
@@ -169,7 +193,7 @@ limits and offsets:
172− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
196+ query :: Query_ (Public Schema) () (Row Int32 Int32)
173 query = select Star (from (table #tab) & limit 100 & offset 2 & limit 50 & offset 2)
197 query = select Star (from (table #tab) & limit 100 & offset 2 & limit 50 & offset 2)
174in printSQL query
198in printSQL query
@@ -179,7 +203,7 @@ parameterized query:
182− query :: Query '[] '[] (Public Schema) '[ 'NotNull 'PGint4] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
206+ query :: Query_ (Public Schema) (Only Int32) (Row Int32 Int32)
183 query = select Star (from (table #tab) & where_ (#col1 .> param @1))
207 query = select Star (from (table #tab) & where_ (#col1 .> param @1))
184in printSQL query
208in printSQL query
@@ -189,21 +213,21 @@ aggregation query:
192− query :: Query '[] '[] (Public Schema) '[] '["sum" ::: 'NotNull 'PGint4, "col1" ::: 'NotNull 'PGint4 ]
216+ query :: Query_ (Public Schema) () (Row Int64 Int32)
194− select_ (sum_ (All #col2) `as` #sum :* #col1)
218+ select_ ((fromNull 0 (sum_ (All #col2))) `as` #col1 :* #col1 `as` #col2)
195 ( from (table (#tab `as` #table1))
219 ( from (table (#tab `as` #table1))
196 & groupBy #col1
220 & groupBy #col1
197− & having (#col1 + sum_ (Distinct #col2) .> 1) )
221+ & having (sum_ (Distinct #col2) .> 1) )
198in printSQL query
222in printSQL query
200−SELECT sum(ALL "col2") AS "sum", "col1" AS "col1" FROM "tab" AS "table1" GROUP BY "col1" HAVING (("col1" + sum(DISTINCT "col2")) > 1)
224+SELECT COALESCE(sum(ALL "col2"), 0) AS "col1", "col1" AS "col2" FROM "tab" AS "table1" GROUP BY "col1" HAVING (sum(DISTINCT "col2") > 1)
202sorted query:
226sorted query:
206− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
230+ query :: Query_ (Public Schema) () (Row Int32 Int32)
207 query = select Star (from (table #tab) & orderBy [#col1 & Asc])
231 query = select Star (from (table #tab) & orderBy [#col1 & Asc])
208in printSQL query
232in printSQL query
@@ -211,14 +235,12 @@ SELECT * FROM "tab" AS "tab" ORDER BY "col1" ASC
214−>>> :set -XFlexibleContexts
216type OrdersColumns =
239type OrdersColumns =
217 '[ "id" ::: 'NoDef :=> 'NotNull 'PGint4
240 '[ "id" ::: 'NoDef :=> 'NotNull 'PGint4
218− , "price" ::: 'NoDef :=> 'NotNull 'PGfloat4
241+ , "price" ::: 'NoDef :=> 'NotNull 'PGfloat4
219− , "customer_id" ::: 'NoDef :=> 'NotNull 'PGint4
242+ , "customer_id" ::: 'NoDef :=> 'NotNull 'PGint4
220− , "shipper_id" ::: 'NoDef :=> 'NotNull 'PGint4
243+ , "shipper_id" ::: 'NoDef :=> 'NotNull 'PGint4 ]
@@ -234,22 +256,27 @@ type OrdersConstraints =
235type OrdersSchema =
257type OrdersSchema =
236 '[ "orders" ::: 'Table (OrdersConstraints :=> OrdersColumns)
258 '[ "orders" ::: 'Table (OrdersConstraints :=> OrdersColumns)
237− , "customers" ::: 'Table (CustomersConstraints :=> NamesColumns)
259+ , "customers" ::: 'Table (CustomersConstraints :=> NamesColumns)
238− , "shippers" ::: 'Table (ShippersConstraints :=> NamesColumns) ]
260+ , "shippers" ::: 'Table (ShippersConstraints :=> NamesColumns) ]
266+ , customerName :: Text
267+ , shipperName :: Text
268+ } deriving GHC.Generic
269+instance SOP.Generic Order
270+instance SOP.HasDatatypeInfo Order
243− query :: Query '[] '[] (Public OrdersSchema)
275+ query :: Query_ (Public OrdersSchema) () Order
245− '[ "order_price" ::: 'NotNull 'PGfloat4
246− , "customer_name" ::: 'NotNull 'PGtext
247− , "shipper_name" ::: 'NotNull 'PGtext
249 query = select_
276 query = select_
250− ( #o ! #price `as` #order_price :*
277+ ( #o ! #price `as` #price :*
251− #c ! #name `as` #customer_name :*
278+ #c ! #name `as` #customerName :*
252− #s ! #name `as` #shipper_name )
279+ #s ! #name `as` #shipperName )
253 ( from (table (#orders `as` #o)
280 ( from (table (#orders `as` #o)
254 & innerJoin (table (#customers `as` #c))
281 & innerJoin (table (#customers `as` #c))
255 (#o ! #customer_id .== #c ! #id)
282 (#o ! #customer_id .== #c ! #id)
@@ -257,14 +284,16 @@ let
257 (#o ! #shipper_id .== #s ! #id)) )
284 (#o ! #shipper_id .== #s ! #id)) )
258in printSQL query
285in printSQL query
260−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")
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")
262self-join:
289self-join:
266− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
293+ query :: Query_ (Public Schema) () (Row Int32 Int32)
267− query = select (#t1 & DotStar) (from (table (#tab `as` #t1) & crossJoin (table (#tab `as` #t2))))
296+ (from (table (#tab `as` #t1) & crossJoin (table (#tab `as` #t2))))
268in printSQL query
297in printSQL query
270SELECT "t1".* FROM "tab" AS "t1" CROSS JOIN "tab" AS "t2"
299SELECT "t1".* FROM "tab" AS "t1" CROSS JOIN "tab" AS "t2"
@@ -273,17 +302,19 @@ value queries:
276− query :: Query '[] commons schemas '[] '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
305+ query :: Query_ schemas () (Row String Bool)
277− query = values (1 `as` #foo :* true `as` #bar) [2 `as` #foo :* false `as` #bar]
306+ query = values
307+ ("true" `as` #col1 :* true `as` #col2)
308+ ["false" `as` #col1 :* false `as` #col2]
278in printSQL query
309in printSQL query
280−SELECT * FROM (VALUES (1, TRUE), (2, FALSE)) AS t ("foo", "bar")
311+SELECT * FROM (VALUES (E'true', TRUE), (E'false', FALSE)) AS t ("col1", "col2")
282set operations:
313set operations:
286− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
317+ query :: Query_ (Public Schema) () (Row Int32 Int32)
287 query = select Star (from (table #tab)) `unionAll` select Star (from (table #tab))
318 query = select Star (from (table #tab)) `unionAll` select Star (from (table #tab))
288in printSQL query
319in printSQL query
@@ -293,7 +324,7 @@ with queries:
296− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
327+ query :: Query_ (Public Schema) () (Row Int32 Int32)
297 query = with (
328 query = with (
298 select Star (from (table #tab)) `as` #cte1 :>>
329 select Star (from (table #tab)) `as` #cte1 :>>
299 select Star (from (common #cte1)) `as` #cte2
330 select Star (from (common #cte1)) `as` #cte2
@@ -306,40 +337,35 @@ window function queries
309− query :: Query '[] '[] (Public Schema) '[] ["col1" ::: 'NotNull 'PGint4, "rank" ::: 'NotNull 'PGint8]
340+ query :: Query_ (Public Schema) () (Row Int32 Int64)
310 query = select
341 query = select
311− (#col1 & Also (rank `as` #rank `Over` (partitionBy #col1 & orderBy [#col2 & Asc])))
342+ (#col1 & Also (rank `as` #col2 `Over` (partitionBy #col1 & orderBy [#col2 & Asc])))
312 (from (table #tab))
343 (from (table #tab))
313in printSQL query
344in printSQL query
315−SELECT "col1" AS "col1", rank() OVER (PARTITION BY "col1" ORDER BY "col2" ASC) AS "rank" FROM "tab" AS "tab"
346+SELECT "col1" AS "col1", rank() OVER (PARTITION BY "col1" ORDER BY "col2" ASC) AS "col2" FROM "tab" AS "tab"
317correlated subqueries
348correlated subqueries
321− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4]
352+ query :: Query_ (Public Schema) () (Only Int32)
323− select #col1 (from (table (#tab `as` #t1))
354+ select (#col1 `as` #fromOnly) (from (table (#tab `as` #t1))
324 & where_ (exists (
355 & where_ (exists (
325 select Star (from (table (#tab `as` #t2))
356 select Star (from (table (#tab `as` #t2))
326 & where_ (#t2 ! #col2 .== #t1 ! #col1)))))
357 & where_ (#t2 ! #col2 .== #t1 ! #col1)))))
327in printSQL query
358in printSQL query
329−SELECT "col1" AS "col1" FROM "tab" AS "t1" WHERE EXISTS (SELECT * FROM "tab" AS "t2" 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"))
331−newtype Query
363+type family Query_
333− (commons :: FromType)
334 (schemas :: SchemasType)
364 (schemas :: SchemasType)
335− (params :: [NullityType])
365+ (parameters :: Type)
336− (row :: RowType)
366+ (row :: Type) where
337− = UnsafeQuery { renderQuery :: ByteString }
367+ Query_ schemas params row =
338− deriving (GHC.Generic,Show,Eq,Ord,NFData)
339−instance RenderSQL (Query outer commons schemas params row) where renderSQL = renderQuery
341−type family Query_ (schemas :: SchemasType) (params :: Type) (row :: Type) where
342− Query_ schemas params row = Query '[] '[] schemas (TuplePG params) (RowPG row)
368+ Query '[] '[] schemas (TuplePG params) (RowPG row)
344-- | The results of two queries can be combined using the set operation
370-- | The results of two queries can be combined using the set operation
345-- `union`. Duplicate rows are eliminated.
371-- `union`. Duplicate rows are eliminated.
@@ -411,27 +437,40 @@ q1 `exceptAll` q2 = UnsafeQuery $
411SELECT queries
437SELECT queries
412-----------------------------------------}
438-----------------------------------------}
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`.
414data Selection outer commons grp schemas params from row where
446data Selection outer commons grp schemas params from row where
417− => NP (Aliased (Expression outer commons grp schemas params from)) row
418− -> Selection outer commons grp schemas params from row
420 :: HasUnique tab from row
448 :: HasUnique tab from row
421 => Selection outer commons 'Ungrouped schemas params from row
449 => Selection outer commons 'Ungrouped schemas params from row
450+ -- ^ `HasUnique` table in the `FromClause`
423 :: Has tab from row
452 :: Has tab from row
424 => Alias tab
453 => Alias tab
454+ -- ^ `Has` table with `Alias`
425 -> Selection outer commons 'Ungrouped schemas params from row
455 -> Selection outer commons 'Ungrouped schemas params from row
427− :: Selection outer commons grp schemas params from right
458+ => NP (Aliased (Expression outer commons grp schemas params from)) row
428− -> Selection outer commons grp schemas params from left
459+ -- ^ `NP` list of `Aliased` `Expression`s
429− -> Selection outer commons grp schemas params from (Join left right)
460+ -> Selection outer commons grp schemas params from row
431 :: SListI row
462 :: SListI row
432 => NP (Aliased (WindowFunction 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
433 -> WindowDefinition outer commons grp schemas params from
465 -> WindowDefinition outer commons grp schemas params from
434 -> Selection outer commons grp schemas params from row
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
435instance (KnownSymbol col, row ~ '[col ::: ty])
474instance (KnownSymbol col, row ~ '[col ::: ty])
436 => Aliasable col
475 => Aliasable col
437 (Expression outer commons grp schemas params from ty)
476 (Expression outer commons grp schemas params from ty)
@@ -491,7 +530,7 @@ instance IsString
492 :: (SListI row, row ~ (x ': xs))
531 :: (SListI row, row ~ (x ': xs))
493 => Selection outer commons grp schemas params from row
532 => Selection outer commons grp schemas params from row
494− -- ^ select list
533+ -- ^ selection
495 -> TableExpression outer commons grp schemas params from
534 -> TableExpression outer commons grp schemas params from
496 -- ^ intermediate virtual table
535 -- ^ intermediate virtual table
497 -> Query outer commons schemas params row
536 -> Query outer commons schemas params row
@@ -500,20 +539,24 @@ select selection tabexpr = UnsafeQuery $
500 <+> renderSQL selection
539 <+> renderSQL selection
501 <+> renderSQL tabexpr
540 <+> renderSQL tabexpr
542+-- | Like `select` but takes an `NP` list of `Expression`s instead
543+-- of a general `Selection`.
504 :: (SListI row, row ~ (x ': xs))
545 :: (SListI row, row ~ (x ': xs))
505 => NP (Aliased (Expression outer commons grp schemas params from)) row
546 => NP (Aliased (Expression outer commons grp schemas params from)) row
506 -> TableExpression outer commons grp schemas params from
548 -> TableExpression outer commons grp schemas params from
549+ -- ^ intermediate virtual table
507 -> Query outer commons schemas params row
550 -> Query outer commons schemas params row
508−select_ list = select (List list)
551+select_ = select . List
510-- | After the select list has been processed, the result table can
553-- | After the select list has been processed, the result table can
511-- be subject to the elimination of duplicate rows using `selectDistinct`.
554-- be subject to the elimination of duplicate rows using `selectDistinct`.
512selectDistinct
555selectDistinct
513 :: (SListI columns, columns ~ (col ': cols))
556 :: (SListI columns, columns ~ (col ': cols))
514− => Selection outer commons 'Ungrouped schemas params from columns
557+ => Selection outer commons grp schemas params from columns
515− -- ^ select list
558+ -- ^ selection
516− -> TableExpression outer commons 'Ungrouped schemas params from
559+ -> TableExpression outer commons grp schemas params from
517 -- ^ intermediate virtual table
560 -- ^ intermediate virtual table
518 -> Query outer commons schemas params columns
561 -> Query outer commons schemas params columns
519selectDistinct selection tabexpr = UnsafeQuery $
562selectDistinct selection tabexpr = UnsafeQuery $
@@ -521,14 +564,65 @@ selectDistinct selection tabexpr = UnsafeQuery $
521 <+> renderSQL selection
564 <+> renderSQL selection
522 <+> renderSQL tabexpr
565 <+> renderSQL tabexpr
567+-- | Like `selectDistinct` but takes an `NP` list of `Expression`s instead
568+-- of a general `Selection`.
524selectDistinct_
569selectDistinct_
525 :: (SListI columns, columns ~ (col ': cols))
570 :: (SListI columns, columns ~ (col ': cols))
526− => NP (Aliased (Expression outer commons 'Ungrouped schemas params from)) columns
571+ => NP (Aliased (Expression outer commons grp schemas params from)) columns
527 -- ^ select list
572 -- ^ select list
528− -> TableExpression outer commons 'Ungrouped schemas params from
573+ -> TableExpression outer commons grp schemas params from
529 -- ^ intermediate virtual table
574 -- ^ intermediate virtual table
530 -> Query outer commons schemas params columns
575 -> Query outer commons schemas params columns
531−selectDistinct_ list = select (List list)
576+selectDistinct_ = selectDistinct . List
579+`selectDistinctOn` keeps only the first row of each set of rows where
580+the given expressions evaluate to equal. The DISTINCT ON expressions are
581+interpreted using the same rules as for ORDER BY. ORDER BY is used to
582+ensure that the desired row appears first.
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
599+ -> Query outer commons schemas params columns
600+selectDistinctOn distincts selection tab = UnsafeQuery $
601+ "SELECT DISTINCT ON"
602+ <+> parenthesized (commaSeparated (renderDistinctOn <$> distincts))
603+ <+> renderSQL selection
604+ <+> renderSQL (tab {orderByClause = distincts <> orderByClause tab})
606+ renderDistinctOn = \case
607+ Asc expression -> renderSQL expression
608+ Desc expression -> renderSQL expression
609+ AscNullsFirst expression -> renderSQL expression
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
624+ -> Query outer commons schemas params columns
625+selectDistinctOn_ distincts = selectDistinctOn distincts . List
533-- | `values` computes a row value or set of row values
627-- | `values` computes a row value or set of row values
534-- specified by value expressions. It is most commonly used
628-- specified by value expressions. It is most commonly used
@@ -663,7 +757,7 @@ instance RenderSQL (TableExpression outer commons grp schemas params from) where
664 :: FromClause outer commons schemas params from -- ^ table reference
758 :: FromClause outer commons schemas params from -- ^ table reference
665 -> TableExpression outer commons 'Ungrouped schemas params from
759 -> TableExpression outer commons 'Ungrouped schemas params from
666−from tab = TableExpression tab [] NoGroups NoHaving [] [] []
760+from tab = TableExpression tab [] noGroups NoHaving [] [] []
668-- | A `where_` is an endomorphism of `TableExpression`s which adds a
762-- | A `where_` is an endomorphism of `TableExpression`s which adds a
669-- search condition to the `whereClause`.
763-- search condition to the `whereClause`.
@@ -684,7 +778,7 @@ groupBy
684groupBy bys rels = TableExpression
778groupBy bys rels = TableExpression
685 { fromClause = fromClause rels
779 { fromClause = fromClause rels
686 , whereClause = whereClause rels
780 , whereClause = whereClause rels
687− , groupByClause = Group bys
781+ , groupByClause = group bys
688 , havingClause = Having []
782 , havingClause = Having []
689 , orderByClause = []
783 , orderByClause = []
690 , limitClause = limitClause rels
784 , limitClause = limitClause rels
@@ -719,170 +813,6 @@ offset
719 -> TableExpression outer commons grp schemas params from
813 -> TableExpression outer commons grp schemas params from
720offset off rels = rels {offsetClause = off : offsetClause rels}
814offset off rels = rels {offsetClause = off : offsetClause rels}
722−{-----------------------------------------
724−-----------------------------------------}
728− -> Expression outer commons 'Ungrouped schemas params '[] ty
729− -> FromClause outer commons schemas params from
730−unsafeSetOfFunction fun expr = UnsafeFromClause $
731− fun <> "(" <> renderSQL expr <> ")"
733−-- | Expand an array to a set of rows
735− :: Expression outer commons 'Ungrouped schemas params '[] (nullity ('PGvararray ty))
736− -> FromClause outer commons schemas params '["unnest" ::: '["unnest" ::: ty]]
737−unnest = unsafeSetOfFunction "unnest"
739−-- | Expands the outermost JSON object into a set of key/value pairs.
740−-- >>> import Data.Aeson
741−-- >>> printSQL (select Star (from (jsonEach (literal (Json (object ["a" .= "foo", "b" .= "bar"]))))))
742−-- SELECT * FROM json_each(('{"a":"foo","b":"bar"}' :: json))
744− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
745− -> FromClause outer commons schemas params
746− '["json_each" ::: '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGjson]]
747−jsonEach = unsafeSetOfFunction "json_each"
749−-- | Expands the outermost binary JSON object into a set of key/value pairs.
750−-- >>> import Data.Aeson
751−-- >>> printSQL (select Star (from (jsonbEach (literal (Jsonb (object ["a" .= "foo", "b" .= "bar"]))))))
752−-- SELECT * FROM jsonb_each(('{"a":"foo","b":"bar"}' :: jsonb))
754− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
755− -> FromClause outer commons schemas params
756− '["jsonb_each" ::: '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGjson]]
757−jsonbEach = unsafeSetOfFunction "jsonb_each"
759−-- | 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.
760−-- >>> import Data.Aeson
761−-- >>> printSQL (select Star (from (jsonEachText (literal (Json (object ["a" .= "foo", "b" .= "bar"]))))))
762−-- SELECT * FROM json_each_text(('{"a":"foo","b":"bar"}' :: json))
764− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
765− -> FromClause outer commons schemas params
766− '["json_each" ::: '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGtext]]
767−jsonEachText = unsafeSetOfFunction "json_each_text"
769−-- | Expands the outermost binary JSON object into a set of key/value pairs.
770−-- >>> import Data.Aeson
771−-- >>> printSQL (select Star (from (jsonbEachText (literal (Jsonb (object ["a" .= "foo", "b" .= "bar"]))))))
772−-- SELECT * FROM jsonb_each_text(('{"a":"foo","b":"bar"}' :: jsonb))
774− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
775− -> FromClause outer commons schemas params
776− '["jsonb_each" ::: '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGtext]]
777−jsonbEachText = unsafeSetOfFunction "jsonb_each_text"
779−-- | Returns set of keys in the outermost JSON object.
780−-- >>> import Data.Aeson
781−-- >>> printSQL (jsonObjectKeys (literal (Json (object ["a" .= "foo", "b" .= "bar"]))))
782−-- json_object_keys(('{"a":"foo","b":"bar"}' :: json))
784− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
785− -> FromClause outer commons schemas params
786− '["json_object_keys" ::: '["json_object_keys" ::: 'NotNull 'PGtext]]
787−jsonObjectKeys = unsafeSetOfFunction "json_object_keys"
789−-- | Returns set of keys in the outermost JSON object.
790−-- >>> import Data.Aeson
791−-- >>> printSQL (jsonbObjectKeys (literal (Jsonb (object ["a" .= "foo", "b" .= "bar"]))))
792−-- jsonb_object_keys(('{"a":"foo","b":"bar"}' :: jsonb))
794− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
795− -> FromClause outer commons schemas params
796− '["jsonb_object_keys" ::: '["jsonb_object_keys" ::: 'NotNull 'PGtext]]
797−jsonbObjectKeys = unsafeSetOfFunction "jsonb_object_keys"
799−unsafePopulateFunction
802− -> TypeExpression schemas (nullity ('PGcomposite row))
803− -> Expression outer commons 'Ungrouped schemas params '[] ty
804− -> FromClause outer commons schemas params '[fun ::: row]
805−unsafePopulateFunction fun ty expr = UnsafeFromClause $ renderSQL fun
806− <> parenthesized ("null::" <> renderSQL ty <> ", " <> renderSQL expr)
808−-- | Expands the JSON expression to a row whose columns match the record
809−-- type defined by the given table.
811− :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
812− -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
813− -> FromClause outer commons schemas params '["json_populate_record" ::: row]
814−jsonPopulateRecord = unsafePopulateFunction #json_populate_record
816−-- | Expands the binary JSON expression to a row whose columns match the record
817−-- type defined by the given table.
819− :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
820− -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
821− -> FromClause outer commons schemas params '["jsonb_populate_record" ::: row]
822−jsonbPopulateRecord = unsafePopulateFunction #jsonb_populate_record
824−-- | Expands the outermost array of objects in the given JSON expression to a
825−-- set of rows whose columns match the record type defined by the given table.
826−jsonPopulateRecordSet
827− :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
828− -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json array
829− -> FromClause outer commons schemas params '["json_populate_record_set" ::: row]
830−jsonPopulateRecordSet = unsafePopulateFunction #json_populate_record_set
832−-- | Expands the outermost array of objects in the given binary JSON expression
833−-- to a set of rows whose columns match the record type defined by the given
835−jsonbPopulateRecordSet
836− :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
837− -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb array
838− -> FromClause outer commons schemas params '["jsonb_populate_record_set" ::: row]
839−jsonbPopulateRecordSet = unsafePopulateFunction #jsonb_populate_record_set
842− :: (SListI record, json `In` PGJsonType)
844− -> Expression outer commons 'Ungrouped schemas params '[] (nullity json)
845− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
846− -> FromClause outer commons schemas params '[tab ::: record]
847−unsafeRecordFunction fun expr (types `As` tab) = UnsafeFromClause $
848− fun <> parenthesized (renderSQL expr) <+> "AS" <+> renderSQL tab
849− <> parenthesized (renderCommaSeparated renderTy types)
851− renderTy :: Aliased (TypeExpression schemas) ty -> ByteString
852− renderTy (ty `As` alias) = renderSQL alias <+> renderSQL ty
854−-- | Builds an arbitrary record from a JSON object.
857− => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
858− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
859− -> FromClause outer commons schemas params '[tab ::: record]
860−jsonToRecord = unsafeRecordFunction "json_to_record"
862−-- | Builds an arbitrary record from a binary JSON object.
865− => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
866− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
867− -> FromClause outer commons schemas params '[tab ::: record]
868−jsonbToRecord = unsafeRecordFunction "jsonb_to_record"
870−-- | Builds an arbitrary set of records from a JSON array of objects.
873− => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json array
874− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
875− -> FromClause outer commons schemas params '[tab ::: record]
876−jsonToRecordSet = unsafeRecordFunction "json_to_record_set"
878−-- | Builds an arbitrary set of records from a binary JSON array of objects.
881− => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb array
882− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
883− -> FromClause outer commons schemas params '[tab ::: record]
884−jsonbToRecordSet = unsafeRecordFunction "jsonb_to_record_set"
886{-----------------------------------------
816{-----------------------------------------
887FROM clauses
817FROM clauses
888-----------------------------------------}
818-----------------------------------------}
@@ -909,7 +839,7 @@ table (tab `As` alias) = UnsafeFromClause $
910 :: Aliased (Query outer commons schemas params) query
840 :: Aliased (Query outer commons schemas params) query
911 -> FromClause outer commons schemas params '[query]
841 -> FromClause outer commons schemas params '[query]
912−subquery = UnsafeFromClause . renderAliased (parenthesized . renderQuery)
842+subquery = UnsafeFromClause . renderAliased (parenthesized . renderSQL)
914-- | `view` derives a table from a `View`.
844-- | `view` derives a table from a `View`.
@@ -927,11 +857,16 @@ common
927common (cte `As` alias) = UnsafeFromClause $
857common (cte `As` alias) = UnsafeFromClause $
928 renderSQL cte <+> "AS" <+> renderSQL alias
858 renderSQL cte <+> "AS" <+> renderSQL alias
860+instance Additional (FromClause outer commons schemas params) where
861+ also right left = UnsafeFromClause $
862+ renderSQL left <> ", " <> renderSQL right
930−{- | @left & crossJoin right@. For every possible combination of rows from
865+@left & crossJoin right@. For every possible combination of rows from
931− @left@ and @right@ (i.e., a Cartesian product), the joined table will contain
866+@left@ and @right@ (i.e., a Cartesian product), the joined table will contain
932− a row consisting of all columns in @left@ followed by all columns in @right@.
867+a row consisting of all columns in @left@ followed by all columns in @right@.
933− If the tables have @n@ and @m@ rows respectively, the joined table will
868+If the tables have @n@ and @m@ rows respectively, the joined table will
934− have @n * m@ rows.
869+have @n * m@ rows.
937 :: FromClause outer commons schemas params right
872 :: FromClause outer commons schemas params right
@@ -1057,19 +992,20 @@ instance (Has rel rels cols, Has col cols ty, bys ~ '[ '(rel, col)])
1057-- done on @NoGroups@ while all output `Expression`s must be aggregated
992-- done on @NoGroups@ while all output `Expression`s must be aggregated
1058-- in @Group Nil@. In general, all output `Expression`s in the
993-- in @Group Nil@. In general, all output `Expression`s in the
1059-- complement of @bys@ must be aggregated in @Group bys@.
994-- complement of @bys@ must be aggregated in @Group bys@.
1060−data GroupByClause grp from where
995+newtype GroupByClause grp from = UnsafeGroupByClause
1061− NoGroups :: GroupByClause 'Ungrouped from
996+ { renderGroupByClause :: ByteString }
1062− Group
997+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
1064− => NP (By from) bys
1065− -> GroupByClause ('Grouped bys) from
1067−-- | Renders a `GroupByClause`.
1068instance RenderSQL (GroupByClause grp from) where
998instance RenderSQL (GroupByClause grp from) where
1069− renderSQL = \case
999+ renderSQL = renderGroupByClause
1070− NoGroups -> ""
1000+noGroups :: GroupByClause 'Ungrouped from
1001+noGroups = UnsafeGroupByClause ""
1004+ => NP (By from) bys
1005+ -> GroupByClause ('Grouped bys) from
1006+group bys = UnsafeGroupByClause $ case bys of
1071− Group Nil -> ""
1007+ Nil -> ""
1072− Group bys -> " GROUP BY" <+> renderCommaSeparated renderSQL bys
1008+ bys' -> " GROUP BY" <+> renderCommaSeparated renderSQL bys'
1074-- | A `HavingClause` is used to eliminate groups that are not of interest.
1010-- | A `HavingClause` is used to eliminate groups that are not of interest.
1075-- An `Ungrouped` `TableExpression` may only use `NoHaving` while a `Grouped`
1011-- An `Ungrouped` `TableExpression` may only use `NoHaving` while a `Grouped`
@@ -1092,90 +1028,6 @@ instance RenderSQL (HavingClause outer commons grp schemas params from) where
1092 Having conditions ->
1028 Having conditions ->
1093 " HAVING" <+> commaSeparated (renderSQL <$> conditions)
1029 " HAVING" <+> commaSeparated (renderSQL <$> conditions)
1096−The argument of `exists` is an arbitrary subquery. The subquery is evaluated
1097−to determine whether it returns any rows. If it returns at least one row,
1098−the result of `exists` is `true`; if the subquery returns no rows,
1099−the result of `exists` is `false`.
1101−The subquery can refer to variables from the surrounding query,
1102−which will act as constants during any one evaluation of the subquery.
1104−The subquery will generally only be executed long enough to determine whether
1105−at least one row is returned, not all the way to completion.
1108− :: Query (Join outer from) commons schemas params row
1109− -> Condition outer commons grp schemas params from
1110−exists query = UnsafeExpression $ "EXISTS" <+> parenthesized (renderSQL query)
1113−The right-hand side is a parenthesized subquery, which must return
1114−exactly one column. The left-hand expression is evaluated and compared to each
1115−row of the subquery result using the given `Operator`,
1116−which must yield a Boolean result. The result of `subAll` is `true`
1117−if all rows yield true (including the case where the subquery returns no rows).
1118−The result is `false` if any `false` result is found.
1119−The result is `null_` if no comparison with a subquery row returns `false`,
1120−and at least one comparison returns `null_`.
1122−>>> printSQL $ subAll true (.==) (values_ (true `as` #foo))
1123−(TRUE = ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo")))
1126− :: Expression outer commons grp schemas params from ty1 -- ^ expression
1127− -> Operator ty1 ty2 ('Null 'PGbool) -- ^ operator
1128− -> Query (Join outer from) commons schemas params '[col ::: ty2] -- ^ subquery
1129− -> Condition outer commons grp schemas params from
1130−subAll expr (?) qry = expr ?
1131− (UnsafeExpression $ "ALL" <+> parenthesized (renderSQL qry))
1134−The right-hand side is a parenthesized subquery, which must return exactly one column.
1135−The left-hand expression is evaluated and compared to each row of the subquery result
1136−using the given `Operator`, which must yield a Boolean result. The result of `subAny` is `true`
1137−if any `true` result is obtained. The result is `false` if no true result is found
1138−(including the case where the subquery returns no rows).
1140−>>> printSQL $ subAll "foo" like (values_ ("foobar" `as` #foo))
1141−(E'foo' LIKE ALL (SELECT * FROM (VALUES (E'foobar')) AS t ("foo")))
1144− :: Expression outer commons grp schemas params from ty1 -- ^ expression
1145− -> Operator ty1 ty2 ('Null 'PGbool) -- ^ operator
1146− -> Query (Join outer from) commons schemas params '[col ::: ty2] -- ^ subquery
1147− -> Condition outer commons grp schemas params from
1148−subAny expr (?) qry = expr ?
1149− (UnsafeExpression $ "ANY" <+> parenthesized (renderSQL qry))
1152−The result is `true` if the left-hand expression's result is equal
1153−to any of the right-hand expressions.
1155−>>> printSQL $ true `in_` [true, false, null_]
1156−TRUE IN (TRUE, FALSE, NULL)
1159− :: Expression outer commons grp schemas params from ty -- ^ expression
1160− -> [Expression outer commons grp schemas params from ty]
1161− -> Condition outer commons grp schemas params from
1162−expr `in_` exprs = UnsafeExpression $ renderSQL expr <+> "IN"
1163− <+> parenthesized (commaSeparated (renderSQL <$> exprs))
1166−The result is `true` if the left-hand expression's result is not equal
1167−to any of the right-hand expressions.
1169−>>> printSQL $ true `notIn` [false, null_]
1170−TRUE NOT IN (FALSE, NULL)
1173− :: Expression outer commons grp schemas params from ty -- ^ expression
1174− -> [Expression outer commons grp schemas params from ty]
1175− -> Condition outer commons grp schemas params from
1176−expr `notIn` exprs = UnsafeExpression $ renderSQL expr <+> "NOT IN"
1177− <+> parenthesized (commaSeparated (renderSQL <$> exprs))
1179-- | A `CommonTableExpression` is an auxiliary statement in a `with` clause.
1031-- | A `CommonTableExpression` is an auxiliary statement in a `with` clause.
1180data CommonTableExpression statement
1032data CommonTableExpression statement
1181 (schemas :: SchemasType)
1033 (schemas :: SchemasType)
@@ -1197,8 +1049,8 @@ instance
1197 , commons1 ~ (cte ::: common ': commons)
1049 , commons1 ~ (cte ::: common ': commons)
1198 ) => Aliasable cte
1050 ) => Aliasable cte
1199 (statement commons schemas params common)
1051 (statement commons schemas params common)
1200− (AlignedList (CommonTableExpression statement schemas params) commons commons1) where
1052+ (Path (CommonTableExpression statement schemas params) commons commons1) where
1201− statement `as` cte = single (statement `as` cte)
1053+ statement `as` cte = csingleton (statement `as` cte)
1203instance (forall c s p r. RenderSQL (statement c s p r)) => RenderSQL
1055instance (forall c s p r. RenderSQL (statement c s p r)) => RenderSQL
1204 (CommonTableExpression statement schemas params commons0 commons1) where
1056 (CommonTableExpression statement schemas params commons0 commons1) where
@@ -1210,7 +1062,7 @@ instance (forall c s p r. RenderSQL (statement c s p r)) => RenderSQL
1210-- defining temporary tables that exist just for one query.
1062-- defining temporary tables that exist just for one query.
1211class With statement where
1063class With statement where
1213− :: AlignedList (CommonTableExpression statement schemas params) commons0 commons1
1065+ :: Path (CommonTableExpression statement schemas params) commons0 commons1
1214 -- ^ common table expressions
1066 -- ^ common table expressions
1215 -> statement commons1 schemas params row
1067 -> statement commons1 schemas params row
1216 -- ^ larger query
1068 -- ^ larger query
@@ -1218,4 +1070,29 @@ class With statement where
1218instance With (Query outer) where
1070instance With (Query outer) where
1219 with Done query = query
1071 with Done query = query
1220 with ctes query = UnsafeQuery $
1072 with ctes query = UnsafeQuery $
1073+ "WITH" <+> commaSeparated (ctoList renderSQL ctes) <+> renderSQL 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)
1221− "WITH" <+> renderSQL ctes <+> renderSQL query
1098+ <+> renderSQL query