@@ -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
@@ -116,7 +95,6 @@ import Squeal.PostgreSQL.Alias
116import Squeal.PostgreSQL.Expression
95import Squeal.PostgreSQL.Expression
117import Squeal.PostgreSQL.Expression.Logic
96import Squeal.PostgreSQL.Expression.Logic
118import Squeal.PostgreSQL.Expression.Sort
97import Squeal.PostgreSQL.Expression.Sort
119−import Squeal.PostgreSQL.Expression.Type
120import Squeal.PostgreSQL.Expression.Window
98import Squeal.PostgreSQL.Expression.Window
121import Squeal.PostgreSQL.List
99import Squeal.PostgreSQL.List
122import Squeal.PostgreSQL.PG
100import Squeal.PostgreSQL.PG
@@ -125,10 +103,55 @@ import Squeal.PostgreSQL.Schema
127-- >>> 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
130−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.
131−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)
133simple query:
156simple query:
@@ -136,7 +159,7 @@ simple query:
136>>> type Schema = '["tab" ::: 'Table ('[] :=> Columns)]
159>>> type Schema = '["tab" ::: 'Table ('[] :=> Columns)]
139− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
162+ query :: Query_ (Public Schema) () (Row Int32 Int32)
140 query = select Star (from (table #tab))
163 query = select Star (from (table #tab))
141in printSQL query
164in printSQL query
@@ -146,21 +169,21 @@ restricted query:
149− query :: Query '[] '[] (Public Schema) '[] '["sum" ::: 'NotNull 'PGint4, "col1" ::: 'NotNull 'PGint4]
172+ query :: Query_ (Public Schema) () (Row Int32 Int32)
151− select_ ((#col1 + #col2) `as` #sum :* #col1)
174+ select_ ((#col1 + #col2) `as` #col1 :* #col1 `as` #col2)
152 ( from (table #tab)
175 ( from (table #tab)
153 & where_ (#col1 .> #col2)
176 & where_ (#col1 .> #col2)
154 & where_ (#col2 .> 0) )
177 & where_ (#col2 .> 0) )
155in printSQL query
178in printSQL query
157−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))
163− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
186+ query :: Query_ (Public Schema) () (Row Int32 Int32)
164 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)))
165in printSQL query
188in printSQL query
@@ -170,7 +193,7 @@ limits and offsets:
173− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
196+ query :: Query_ (Public Schema) () (Row Int32 Int32)
174 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)
175in printSQL query
198in printSQL query
@@ -180,7 +203,7 @@ parameterized query:
183− query :: Query '[] '[] (Public Schema) '[ 'NotNull 'PGint4] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
206+ query :: Query_ (Public Schema) (Only Int32) (Row Int32 Int32)
184 query = select Star (from (table #tab) & where_ (#col1 .> param @1))
207 query = select Star (from (table #tab) & where_ (#col1 .> param @1))
185in printSQL query
208in printSQL query
@@ -190,21 +213,21 @@ aggregation query:
193− query :: Query '[] '[] (Public Schema) '[] '["sum" ::: 'NotNull 'PGint4, "col1" ::: 'NotNull 'PGint4 ]
216+ query :: Query_ (Public Schema) () (Row Int64 Int32)
195− select_ (sum_ (All #col2) `as` #sum :* #col1)
218+ select_ ((fromNull 0 (sum_ (All #col2))) `as` #col1 :* #col1 `as` #col2)
196 ( from (table (#tab `as` #table1))
219 ( from (table (#tab `as` #table1))
197 & groupBy #col1
220 & groupBy #col1
198− & having (#col1 + sum_ (Distinct #col2) .> 1) )
221+ & having (sum_ (Distinct #col2) .> 1) )
199in printSQL query
222in printSQL query
201−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)
203sorted query:
226sorted query:
207− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
230+ query :: Query_ (Public Schema) () (Row Int32 Int32)
208 query = select Star (from (table #tab) & orderBy [#col1 & Asc])
231 query = select Star (from (table #tab) & orderBy [#col1 & Asc])
209in printSQL query
232in printSQL query
@@ -212,14 +235,12 @@ SELECT * FROM "tab" AS "tab" ORDER BY "col1" ASC
215−>>> :set -XFlexibleContexts
217type OrdersColumns =
239type OrdersColumns =
218 '[ "id" ::: 'NoDef :=> 'NotNull 'PGint4
240 '[ "id" ::: 'NoDef :=> 'NotNull 'PGint4
219− , "price" ::: 'NoDef :=> 'NotNull 'PGfloat4
241+ , "price" ::: 'NoDef :=> 'NotNull 'PGfloat4
220− , "customer_id" ::: 'NoDef :=> 'NotNull 'PGint4
242+ , "customer_id" ::: 'NoDef :=> 'NotNull 'PGint4
221− , "shipper_id" ::: 'NoDef :=> 'NotNull 'PGint4
243+ , "shipper_id" ::: 'NoDef :=> 'NotNull 'PGint4 ]
@@ -235,22 +256,27 @@ type OrdersConstraints =
236type OrdersSchema =
257type OrdersSchema =
237 '[ "orders" ::: 'Table (OrdersConstraints :=> OrdersColumns)
258 '[ "orders" ::: 'Table (OrdersConstraints :=> OrdersColumns)
238− , "customers" ::: 'Table (CustomersConstraints :=> NamesColumns)
259+ , "customers" ::: 'Table (CustomersConstraints :=> NamesColumns)
239− , "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
244− query :: Query '[] '[] (Public OrdersSchema)
275+ query :: Query_ (Public OrdersSchema) () Order
246− '[ "order_price" ::: 'NotNull 'PGfloat4
247− , "customer_name" ::: 'NotNull 'PGtext
248− , "shipper_name" ::: 'NotNull 'PGtext
250 query = select_
276 query = select_
251− ( #o ! #price `as` #order_price :*
277+ ( #o ! #price `as` #price :*
252− #c ! #name `as` #customer_name :*
278+ #c ! #name `as` #customerName :*
253− #s ! #name `as` #shipper_name )
279+ #s ! #name `as` #shipperName )
254 ( from (table (#orders `as` #o)
280 ( from (table (#orders `as` #o)
255 & innerJoin (table (#customers `as` #c))
281 & innerJoin (table (#customers `as` #c))
256 (#o ! #customer_id .== #c ! #id)
282 (#o ! #customer_id .== #c ! #id)
@@ -258,14 +284,16 @@ let
258 (#o ! #shipper_id .== #s ! #id)) )
284 (#o ! #shipper_id .== #s ! #id)) )
259in printSQL query
285in printSQL query
261−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")
263self-join:
289self-join:
267− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
293+ query :: Query_ (Public Schema) () (Row Int32 Int32)
268− 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))))
269in printSQL query
297in printSQL query
271SELECT "t1".* FROM "tab" AS "t1" CROSS JOIN "tab" AS "t2"
299SELECT "t1".* FROM "tab" AS "t1" CROSS JOIN "tab" AS "t2"
@@ -274,17 +302,19 @@ value queries:
277− query :: Query '[] commons schemas '[] '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
305+ query :: Query_ schemas () (Row String Bool)
278− 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]
279in printSQL query
309in printSQL query
281−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")
283set operations:
313set operations:
287− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
317+ query :: Query_ (Public Schema) () (Row Int32 Int32)
288 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))
289in printSQL query
319in printSQL query
@@ -294,7 +324,7 @@ with queries:
297− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4, "col2" ::: 'NotNull 'PGint4]
327+ query :: Query_ (Public Schema) () (Row Int32 Int32)
298 query = with (
328 query = with (
299 select Star (from (table #tab)) `as` #cte1 :>>
329 select Star (from (table #tab)) `as` #cte1 :>>
300 select Star (from (common #cte1)) `as` #cte2
330 select Star (from (common #cte1)) `as` #cte2
@@ -307,40 +337,35 @@ window function queries
310− query :: Query '[] '[] (Public Schema) '[] ["col1" ::: 'NotNull 'PGint4, "rank" ::: 'NotNull 'PGint8]
340+ query :: Query_ (Public Schema) () (Row Int32 Int64)
311 query = select
341 query = select
312− (#col1 & Also (rank `as` #rank `Over` (partitionBy #col1 & orderBy [#col2 & Asc])))
342+ (#col1 & Also (rank `as` #col2 `Over` (partitionBy #col1 & orderBy [#col2 & Asc])))
313 (from (table #tab))
343 (from (table #tab))
314in printSQL query
344in printSQL query
316−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"
318correlated subqueries
348correlated subqueries
322− query :: Query '[] '[] (Public Schema) '[] '["col1" ::: 'NotNull 'PGint4]
352+ query :: Query_ (Public Schema) () (Only Int32)
324− select #col1 (from (table (#tab `as` #t1))
354+ select (#col1 `as` #fromOnly) (from (table (#tab `as` #t1))
325 & where_ (exists (
355 & where_ (exists (
326 select Star (from (table (#tab `as` #t2))
356 select Star (from (table (#tab `as` #t2))
327 & where_ (#t2 ! #col2 .== #t1 ! #col1)))))
357 & where_ (#t2 ! #col2 .== #t1 ! #col1)))))
328in printSQL query
358in printSQL query
330−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"))
332−newtype Query
363+type family Query_
334− (commons :: FromType)
335 (schemas :: SchemasType)
364 (schemas :: SchemasType)
336− (params :: [NullityType])
365+ (parameters :: Type)
337− (row :: RowType)
366+ (row :: Type) where
338− = UnsafeQuery { renderQuery :: ByteString }
367+ Query_ schemas params row =
339− deriving (GHC.Generic,Show,Eq,Ord,NFData)
340−instance RenderSQL (Query outer commons schemas params row) where renderSQL = renderQuery
342−type family Query_ (schemas :: SchemasType) (params :: Type) (row :: Type) where
343− Query_ schemas params row = Query '[] '[] schemas (TuplePG params) (RowPG row)
368+ Query '[] '[] schemas (TuplePG params) (RowPG row)
345-- | 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
346-- `union`. Duplicate rows are eliminated.
371-- `union`. Duplicate rows are eliminated.
@@ -412,27 +437,40 @@ q1 `exceptAll` q2 = UnsafeQuery $
412SELECT queries
437SELECT queries
413-----------------------------------------}
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`.
415data Selection outer commons grp schemas params from row where
446data Selection outer commons grp schemas params from row where
418− => NP (Aliased (Expression outer commons grp schemas params from)) row
419− -> Selection outer commons grp schemas params from row
421 :: HasUnique tab from row
448 :: HasUnique tab from row
422 => Selection outer commons 'Ungrouped schemas params from row
449 => Selection outer commons 'Ungrouped schemas params from row
450+ -- ^ `HasUnique` table in the `FromClause`
424 :: Has tab from row
452 :: Has tab from row
425 => Alias tab
453 => Alias tab
454+ -- ^ `Has` table with `Alias`
426 -> Selection outer commons 'Ungrouped schemas params from row
455 -> Selection outer commons 'Ungrouped schemas params from row
428− :: Selection outer commons grp schemas params from right
458+ => NP (Aliased (Expression outer commons grp schemas params from)) row
429− -> Selection outer commons grp schemas params from left
459+ -- ^ `NP` list of `Aliased` `Expression`s
430− -> Selection outer commons grp schemas params from (Join left right)
460+ -> Selection outer commons grp schemas params from row
432 :: SListI row
462 :: SListI row
433 => 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
434 -> WindowDefinition outer commons grp schemas params from
465 -> WindowDefinition outer commons grp schemas params from
435 -> 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
436instance (KnownSymbol col, row ~ '[col ::: ty])
474instance (KnownSymbol col, row ~ '[col ::: ty])
437 => Aliasable col
475 => Aliasable col
438 (Expression outer commons grp schemas params from ty)
476 (Expression outer commons grp schemas params from ty)
@@ -492,7 +530,7 @@ instance IsString
493 :: (SListI row, row ~ (x ': xs))
531 :: (SListI row, row ~ (x ': xs))
494 => Selection outer commons grp schemas params from row
532 => Selection outer commons grp schemas params from row
495− -- ^ select list
533+ -- ^ selection
496 -> TableExpression outer commons grp schemas params from
534 -> TableExpression outer commons grp schemas params from
497 -- ^ intermediate virtual table
535 -- ^ intermediate virtual table
498 -> Query outer commons schemas params row
536 -> Query outer commons schemas params row
@@ -501,20 +539,24 @@ select selection tabexpr = UnsafeQuery $
501 <+> renderSQL selection
539 <+> renderSQL selection
502 <+> renderSQL tabexpr
540 <+> renderSQL tabexpr
542+-- | Like `select` but takes an `NP` list of `Expression`s instead
543+-- of a general `Selection`.
505 :: (SListI row, row ~ (x ': xs))
545 :: (SListI row, row ~ (x ': xs))
506 => NP (Aliased (Expression outer commons grp schemas params from)) row
546 => NP (Aliased (Expression outer commons grp schemas params from)) row
507 -> TableExpression outer commons grp schemas params from
548 -> TableExpression outer commons grp schemas params from
549+ -- ^ intermediate virtual table
508 -> Query outer commons schemas params row
550 -> Query outer commons schemas params row
509−select_ list = select (List list)
551+select_ = select . List
511-- | After the select list has been processed, the result table can
553-- | After the select list has been processed, the result table can
512-- be subject to the elimination of duplicate rows using `selectDistinct`.
554-- be subject to the elimination of duplicate rows using `selectDistinct`.
513selectDistinct
555selectDistinct
514 :: (SListI columns, columns ~ (col ': cols))
556 :: (SListI columns, columns ~ (col ': cols))
515− => Selection outer commons 'Ungrouped schemas params from columns
557+ => Selection outer commons grp schemas params from columns
516− -- ^ select list
558+ -- ^ selection
517− -> TableExpression outer commons 'Ungrouped schemas params from
559+ -> TableExpression outer commons grp schemas params from
518 -- ^ intermediate virtual table
560 -- ^ intermediate virtual table
519 -> Query outer commons schemas params columns
561 -> Query outer commons schemas params columns
520selectDistinct selection tabexpr = UnsafeQuery $
562selectDistinct selection tabexpr = UnsafeQuery $
@@ -522,14 +564,65 @@ selectDistinct selection tabexpr = UnsafeQuery $
522 <+> renderSQL selection
564 <+> renderSQL selection
523 <+> renderSQL tabexpr
565 <+> renderSQL tabexpr
567+-- | Like `selectDistinct` but takes an `NP` list of `Expression`s instead
568+-- of a general `Selection`.
525selectDistinct_
569selectDistinct_
526 :: (SListI columns, columns ~ (col ': cols))
570 :: (SListI columns, columns ~ (col ': cols))
527− => NP (Aliased (Expression outer commons 'Ungrouped schemas params from)) columns
571+ => NP (Aliased (Expression outer commons grp schemas params from)) columns
528 -- ^ select list
572 -- ^ select list
529− -> TableExpression outer commons 'Ungrouped schemas params from
573+ -> TableExpression outer commons grp schemas params from
530 -- ^ intermediate virtual table
574 -- ^ intermediate virtual table
531 -> Query outer commons schemas params columns
575 -> Query outer commons schemas params columns
532−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
534-- | `values` computes a row value or set of row values
627-- | `values` computes a row value or set of row values
535-- specified by value expressions. It is most commonly used
628-- specified by value expressions. It is most commonly used
@@ -664,7 +757,7 @@ instance RenderSQL (TableExpression outer commons grp schemas params from) where
665 :: FromClause outer commons schemas params from -- ^ table reference
758 :: FromClause outer commons schemas params from -- ^ table reference
666 -> TableExpression outer commons 'Ungrouped schemas params from
759 -> TableExpression outer commons 'Ungrouped schemas params from
667−from tab = TableExpression tab [] NoGroups NoHaving [] [] []
760+from tab = TableExpression tab [] noGroups NoHaving [] [] []
669-- | A `where_` is an endomorphism of `TableExpression`s which adds a
762-- | A `where_` is an endomorphism of `TableExpression`s which adds a
670-- search condition to the `whereClause`.
763-- search condition to the `whereClause`.
@@ -685,7 +778,7 @@ groupBy
685groupBy bys rels = TableExpression
778groupBy bys rels = TableExpression
686 { fromClause = fromClause rels
779 { fromClause = fromClause rels
687 , whereClause = whereClause rels
780 , whereClause = whereClause rels
688− , groupByClause = Group bys
781+ , groupByClause = group bys
689 , havingClause = Having []
782 , havingClause = Having []
690 , orderByClause = []
783 , orderByClause = []
691 , limitClause = limitClause rels
784 , limitClause = limitClause rels
@@ -720,170 +813,6 @@ offset
720 -> TableExpression outer commons grp schemas params from
813 -> TableExpression outer commons grp schemas params from
721offset off rels = rels {offsetClause = off : offsetClause rels}
814offset off rels = rels {offsetClause = off : offsetClause rels}
723−{-----------------------------------------
725−-----------------------------------------}
729− -> Expression outer commons 'Ungrouped schemas params '[] ty
730− -> FromClause outer commons schemas params from
731−unsafeSetOfFunction fun expr = UnsafeFromClause $
732− fun <> "(" <> renderSQL expr <> ")"
734−-- | Expand an array to a set of rows
736− :: Expression outer commons 'Ungrouped schemas params '[] (nullity ('PGvararray ty))
737− -> FromClause outer commons schemas params '["unnest" ::: '["unnest" ::: ty]]
738−unnest = unsafeSetOfFunction "unnest"
740−-- | Expands the outermost JSON object into a set of key/value pairs.
741−-- >>> import Data.Aeson
742−-- >>> printSQL (select Star (from (jsonEach (literal (Json (object ["a" .= "foo", "b" .= "bar"]))))))
743−-- SELECT * FROM json_each(('{"a":"foo","b":"bar"}' :: json))
745− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
746− -> FromClause outer commons schemas params
747− '["json_each" ::: '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGjson]]
748−jsonEach = unsafeSetOfFunction "json_each"
750−-- | Expands the outermost binary JSON object into a set of key/value pairs.
751−-- >>> import Data.Aeson
752−-- >>> printSQL (select Star (from (jsonbEach (literal (Jsonb (object ["a" .= "foo", "b" .= "bar"]))))))
753−-- SELECT * FROM jsonb_each(('{"a":"foo","b":"bar"}' :: jsonb))
755− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
756− -> FromClause outer commons schemas params
757− '["jsonb_each" ::: '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGjson]]
758−jsonbEach = unsafeSetOfFunction "jsonb_each"
760−-- | 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.
761−-- >>> import Data.Aeson
762−-- >>> printSQL (select Star (from (jsonEachText (literal (Json (object ["a" .= "foo", "b" .= "bar"]))))))
763−-- SELECT * FROM json_each_text(('{"a":"foo","b":"bar"}' :: json))
765− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
766− -> FromClause outer commons schemas params
767− '["json_each" ::: '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGtext]]
768−jsonEachText = unsafeSetOfFunction "json_each_text"
770−-- | Expands the outermost binary JSON object into a set of key/value pairs.
771−-- >>> import Data.Aeson
772−-- >>> printSQL (select Star (from (jsonbEachText (literal (Jsonb (object ["a" .= "foo", "b" .= "bar"]))))))
773−-- SELECT * FROM jsonb_each_text(('{"a":"foo","b":"bar"}' :: jsonb))
775− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
776− -> FromClause outer commons schemas params
777− '["jsonb_each" ::: '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGtext]]
778−jsonbEachText = unsafeSetOfFunction "jsonb_each_text"
780−-- | Returns set of keys in the outermost JSON object.
781−-- >>> import Data.Aeson
782−-- >>> printSQL (jsonObjectKeys (literal (Json (object ["a" .= "foo", "b" .= "bar"]))))
783−-- json_object_keys(('{"a":"foo","b":"bar"}' :: json))
785− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
786− -> FromClause outer commons schemas params
787− '["json_object_keys" ::: '["json_object_keys" ::: 'NotNull 'PGtext]]
788−jsonObjectKeys = unsafeSetOfFunction "json_object_keys"
790−-- | Returns set of keys in the outermost JSON object.
791−-- >>> import Data.Aeson
792−-- >>> printSQL (jsonbObjectKeys (literal (Jsonb (object ["a" .= "foo", "b" .= "bar"]))))
793−-- jsonb_object_keys(('{"a":"foo","b":"bar"}' :: jsonb))
795− :: Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
796− -> FromClause outer commons schemas params
797− '["jsonb_object_keys" ::: '["jsonb_object_keys" ::: 'NotNull 'PGtext]]
798−jsonbObjectKeys = unsafeSetOfFunction "jsonb_object_keys"
800−unsafePopulateFunction
803− -> TypeExpression schemas (nullity ('PGcomposite row))
804− -> Expression outer commons 'Ungrouped schemas params '[] ty
805− -> FromClause outer commons schemas params '[fun ::: row]
806−unsafePopulateFunction fun ty expr = UnsafeFromClause $ renderSQL fun
807− <> parenthesized ("null::" <> renderSQL ty <> ", " <> renderSQL expr)
809−-- | Expands the JSON expression to a row whose columns match the record
810−-- type defined by the given table.
812− :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
813− -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
814− -> FromClause outer commons schemas params '["json_populate_record" ::: row]
815−jsonPopulateRecord = unsafePopulateFunction #json_populate_record
817−-- | Expands the binary JSON expression to a row whose columns match the record
818−-- type defined by the given table.
820− :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
821− -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
822− -> FromClause outer commons schemas params '["jsonb_populate_record" ::: row]
823−jsonbPopulateRecord = unsafePopulateFunction #jsonb_populate_record
825−-- | Expands the outermost array of objects in the given JSON expression to a
826−-- set of rows whose columns match the record type defined by the given table.
827−jsonPopulateRecordSet
828− :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
829− -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json array
830− -> FromClause outer commons schemas params '["json_populate_record_set" ::: row]
831−jsonPopulateRecordSet = unsafePopulateFunction #json_populate_record_set
833−-- | Expands the outermost array of objects in the given binary JSON expression
834−-- to a set of rows whose columns match the record type defined by the given
836−jsonbPopulateRecordSet
837− :: TypeExpression schemas (nullity ('PGcomposite row)) -- ^ row type
838− -> Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb array
839− -> FromClause outer commons schemas params '["jsonb_populate_record_set" ::: row]
840−jsonbPopulateRecordSet = unsafePopulateFunction #jsonb_populate_record_set
843− :: (SListI record, json `In` PGJsonType)
845− -> Expression outer commons 'Ungrouped schemas params '[] (nullity json)
846− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
847− -> FromClause outer commons schemas params '[tab ::: record]
848−unsafeRecordFunction fun expr (types `As` tab) = UnsafeFromClause $
849− fun <> parenthesized (renderSQL expr) <+> "AS" <+> renderSQL tab
850− <> parenthesized (renderCommaSeparated renderTy types)
852− renderTy :: Aliased (TypeExpression schemas) ty -> ByteString
853− renderTy (ty `As` alias) = renderSQL alias <+> renderSQL ty
855−-- | Builds an arbitrary record from a JSON object.
858− => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json object
859− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
860− -> FromClause outer commons schemas params '[tab ::: record]
861−jsonToRecord = unsafeRecordFunction "json_to_record"
863−-- | Builds an arbitrary record from a binary JSON object.
866− => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb object
867− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
868− -> FromClause outer commons schemas params '[tab ::: record]
869−jsonbToRecord = unsafeRecordFunction "jsonb_to_record"
871−-- | Builds an arbitrary set of records from a JSON array of objects.
874− => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjson) -- ^ json array
875− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
876− -> FromClause outer commons schemas params '[tab ::: record]
877−jsonToRecordSet = unsafeRecordFunction "json_to_record_set"
879−-- | Builds an arbitrary set of records from a binary JSON array of objects.
882− => Expression outer commons 'Ungrouped schemas params '[] (nullity 'PGjsonb) -- ^ jsonb array
883− -> Aliased (NP (Aliased (TypeExpression schemas))) (tab ::: record)
884− -> FromClause outer commons schemas params '[tab ::: record]
885−jsonbToRecordSet = unsafeRecordFunction "jsonb_to_record_set"
887{-----------------------------------------
816{-----------------------------------------
888FROM clauses
817FROM clauses
889-----------------------------------------}
818-----------------------------------------}
@@ -910,7 +839,7 @@ table (tab `As` alias) = UnsafeFromClause $
911 :: Aliased (Query outer commons schemas params) query
840 :: Aliased (Query outer commons schemas params) query
912 -> FromClause outer commons schemas params '[query]
841 -> FromClause outer commons schemas params '[query]
913−subquery = UnsafeFromClause . renderAliased (parenthesized . renderQuery)
842+subquery = UnsafeFromClause . renderAliased (parenthesized . renderSQL)
915-- | `view` derives a table from a `View`.
844-- | `view` derives a table from a `View`.
@@ -928,11 +857,16 @@ common
928common (cte `As` alias) = UnsafeFromClause $
857common (cte `As` alias) = UnsafeFromClause $
929 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
931−{- | @left & crossJoin right@. For every possible combination of rows from
865+@left & crossJoin right@. For every possible combination of rows from
932− @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
933− 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@.
934− 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
935− have @n * m@ rows.
869+have @n * m@ rows.
938 :: FromClause outer commons schemas params right
872 :: FromClause outer commons schemas params right
@@ -1058,19 +992,20 @@ instance (Has rel rels cols, Has col cols ty, bys ~ '[ '(rel, col)])
1058-- done on @NoGroups@ while all output `Expression`s must be aggregated
992-- done on @NoGroups@ while all output `Expression`s must be aggregated
1059-- in @Group Nil@. In general, all output `Expression`s in the
993-- in @Group Nil@. In general, all output `Expression`s in the
1060-- complement of @bys@ must be aggregated in @Group bys@.
994-- complement of @bys@ must be aggregated in @Group bys@.
1061−data GroupByClause grp from where
995+newtype GroupByClause grp from = UnsafeGroupByClause
1062− NoGroups :: GroupByClause 'Ungrouped from
996+ { renderGroupByClause :: ByteString }
1063− Group
997+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
1065− => NP (By from) bys
1066− -> GroupByClause ('Grouped bys) from
1068−-- | Renders a `GroupByClause`.
1069instance RenderSQL (GroupByClause grp from) where
998instance RenderSQL (GroupByClause grp from) where
1070− renderSQL = \case
999+ renderSQL = renderGroupByClause
1071− 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
1072− Group Nil -> ""
1007+ Nil -> ""
1073− Group bys -> " GROUP BY" <+> renderCommaSeparated renderSQL bys
1008+ bys' -> " GROUP BY" <+> renderCommaSeparated renderSQL bys'
1075-- | 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.
1076-- An `Ungrouped` `TableExpression` may only use `NoHaving` while a `Grouped`
1011-- An `Ungrouped` `TableExpression` may only use `NoHaving` while a `Grouped`
@@ -1093,90 +1028,6 @@ instance RenderSQL (HavingClause outer commons grp schemas params from) where
1093 Having conditions ->
1028 Having conditions ->
1094 " HAVING" <+> commaSeparated (renderSQL <$> conditions)
1029 " HAVING" <+> commaSeparated (renderSQL <$> conditions)
1097−The argument of `exists` is an arbitrary subquery. The subquery is evaluated
1098−to determine whether it returns any rows. If it returns at least one row,
1099−the result of `exists` is `true`; if the subquery returns no rows,
1100−the result of `exists` is `false`.
1102−The subquery can refer to variables from the surrounding query,
1103−which will act as constants during any one evaluation of the subquery.
1105−The subquery will generally only be executed long enough to determine whether
1106−at least one row is returned, not all the way to completion.
1109− :: Query (Join outer from) commons schemas params row
1110− -> Condition outer commons grp schemas params from
1111−exists query = UnsafeExpression $ "EXISTS" <+> parenthesized (renderSQL query)
1114−The right-hand side is a parenthesized subquery, which must return
1115−exactly one column. The left-hand expression is evaluated and compared to each
1116−row of the subquery result using the given `Operator`,
1117−which must yield a Boolean result. The result of `subAll` is `true`
1118−if all rows yield true (including the case where the subquery returns no rows).
1119−The result is `false` if any `false` result is found.
1120−The result is `null_` if no comparison with a subquery row returns `false`,
1121−and at least one comparison returns `null_`.
1123−>>> printSQL $ subAll true (.==) (values_ (true `as` #foo))
1124−(TRUE = ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo")))
1127− :: Expression outer commons grp schemas params from ty1 -- ^ expression
1128− -> Operator ty1 ty2 ('Null 'PGbool) -- ^ operator
1129− -> Query (Join outer from) commons schemas params '[col ::: ty2] -- ^ subquery
1130− -> Condition outer commons grp schemas params from
1131−subAll expr (?) qry = expr ?
1132− (UnsafeExpression $ "ALL" <+> parenthesized (renderSQL qry))
1135−The right-hand side is a parenthesized subquery, which must return exactly one column.
1136−The left-hand expression is evaluated and compared to each row of the subquery result
1137−using the given `Operator`, which must yield a Boolean result. The result of `subAny` is `true`
1138−if any `true` result is obtained. The result is `false` if no true result is found
1139−(including the case where the subquery returns no rows).
1141−>>> printSQL $ subAll "foo" like (values_ ("foobar" `as` #foo))
1142−(E'foo' LIKE ALL (SELECT * FROM (VALUES (E'foobar')) AS t ("foo")))
1145− :: Expression outer commons grp schemas params from ty1 -- ^ expression
1146− -> Operator ty1 ty2 ('Null 'PGbool) -- ^ operator
1147− -> Query (Join outer from) commons schemas params '[col ::: ty2] -- ^ subquery
1148− -> Condition outer commons grp schemas params from
1149−subAny expr (?) qry = expr ?
1150− (UnsafeExpression $ "ANY" <+> parenthesized (renderSQL qry))
1153−The result is `true` if the left-hand expression's result is equal
1154−to any of the right-hand expressions.
1156−>>> printSQL $ true `in_` [true, false, null_]
1157−TRUE IN (TRUE, FALSE, NULL)
1160− :: Expression outer commons grp schemas params from ty -- ^ expression
1161− -> [Expression outer commons grp schemas params from ty]
1162− -> Condition outer commons grp schemas params from
1163−expr `in_` exprs = UnsafeExpression $ renderSQL expr <+> "IN"
1164− <+> parenthesized (commaSeparated (renderSQL <$> exprs))
1167−The result is `true` if the left-hand expression's result is not equal
1168−to any of the right-hand expressions.
1170−>>> printSQL $ true `notIn` [false, null_]
1171−TRUE NOT IN (FALSE, NULL)
1174− :: Expression outer commons grp schemas params from ty -- ^ expression
1175− -> [Expression outer commons grp schemas params from ty]
1176− -> Condition outer commons grp schemas params from
1177−expr `notIn` exprs = UnsafeExpression $ renderSQL expr <+> "NOT IN"
1178− <+> parenthesized (commaSeparated (renderSQL <$> exprs))
1180-- | A `CommonTableExpression` is an auxiliary statement in a `with` clause.
1031-- | A `CommonTableExpression` is an auxiliary statement in a `with` clause.
1181data CommonTableExpression statement
1032data CommonTableExpression statement
1182 (schemas :: SchemasType)
1033 (schemas :: SchemasType)
@@ -1198,8 +1049,8 @@ instance
1198 , commons1 ~ (cte ::: common ': commons)
1049 , commons1 ~ (cte ::: common ': commons)
1199 ) => Aliasable cte
1050 ) => Aliasable cte
1200 (statement commons schemas params common)
1051 (statement commons schemas params common)
1201− (AlignedList (CommonTableExpression statement schemas params) commons commons1) where
1052+ (Path (CommonTableExpression statement schemas params) commons commons1) where
1202− statement `as` cte = single (statement `as` cte)
1053+ statement `as` cte = csingleton (statement `as` cte)
1204instance (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
1205 (CommonTableExpression statement schemas params commons0 commons1) where
1056 (CommonTableExpression statement schemas params commons0 commons1) where
@@ -1211,7 +1062,7 @@ instance (forall c s p r. RenderSQL (statement c s p r)) => RenderSQL
1211-- defining temporary tables that exist just for one query.
1062-- defining temporary tables that exist just for one query.
1212class With statement where
1063class With statement where
1214− :: AlignedList (CommonTableExpression statement schemas params) commons0 commons1
1065+ :: Path (CommonTableExpression statement schemas params) commons0 commons1
1215 -- ^ common table expressions
1066 -- ^ common table expressions
1216 -> statement commons1 schemas params row
1067 -> statement commons1 schemas params row
1217 -- ^ larger query
1068 -- ^ larger query
@@ -1219,4 +1070,29 @@ class With statement where
1219instance With (Query outer) where
1070instance With (Query outer) where
1220 with Done query = query
1071 with Done query = query
1221 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)
1222− "WITH" <+> renderSQL ctes <+> renderSQL query
1098+ <+> renderSQL query