@@ -36,16 +36,13 @@ module Squeal.PostgreSQL.Query
3636 , intersectAll
3737 , except
3838 , exceptAll
39− -- * SELECT queries
39+ -- * Select
4040 , select
4141 , selectDistinct
4242 , selectStar
4343 , selectDistinctStar
4444 , selectDotStar
4545 , selectDistinctDotStar
46− -- * FROM clauses
47− , FromClause (..)
48− , renderFromClause
4946 -- * Table Expressions
5047 , TableExpression (..)
5148 , renderTableExpression
@@ -56,6 +53,9 @@ module Squeal.PostgreSQL.Query
5653 , orderBy
5754 , limit
5855 , offset
56+ -- * From
57+ , FromClause (..)
58+ , renderFromClause
5959 -- * Grouping
6060 , By (By, By2)
6161 , renderBy
@@ -63,7 +63,6 @@ module Squeal.PostgreSQL.Query
6363 , renderGroupByClause
6464 , HavingClause (NoHaving, Having)
6565 , renderHavingClause
66− , GroupedBy (getGroup1, getGroup2)
6766 -- * Sorting
6867 , SortExpression (..)
6968 , renderSortExpression
@@ -84,7 +83,9 @@ import Squeal.PostgreSQL.Prettyprint
8483import Squeal.PostgreSQL.Schema
8584
8685-- | The process of retrieving or the command to retrieve data from a database
87−-- is called a `Query`. The `select` command is used to specify queries.
86+-- is called a `Query`. The `select`, `selectStar`, `selectDotStar`,
87+-- `selectDistinct`, `selectDistinctStar` and `selectDistinctDotStar` commands
88+-- are used to specify queries.
8889newtype Query
8990 (schema :: TablesType)
9091 (params :: [ColumnType])
@@ -92,10 +93,9 @@ newtype Query
9293 = UnsafeQuery { renderQuery :: ByteString }
9394 deriving (GHC.Generic,Show,Eq,Ord,NFData)
9495
95−-- | The results of two queries can be combined using the set operations
96−-- `union`, `intersect`, and `except` (set difference). Duplicates are
97−-- eliminated unless `unionAll`, `intersectAll` or `exceptAll` are used.
98−union, unionAll, intersect, intersectAll, except, exceptAll
96+-- | The results of two queries can be combined using the set operation
97+-- `union`. Duplicate rows are eliminated.
98+union
9999 :: Query schema params columns
100100 -> Query schema params columns
101101 -> Query schema params columns
@@ -103,22 +103,57 @@ q1 `union` q2 = UnsafeQuery $
103103 parenthesized (renderQuery q1)
104104 <+> "UNION"
105105 <+> parenthesized (renderQuery q2)
106+
107+-- | The results of two queries can be combined using the set operation
108+-- `unionAll`, the disjoint union. Duplicate rows are retained.
109+unionAll
110+ :: Query schema params columns
111+ -> Query schema params columns
112+ -> Query schema params columns
106113q1 `unionAll` q2 = UnsafeQuery $
107114 parenthesized (renderQuery q1)
108115 <+> "UNION" <+> "ALL"
109116 <+> parenthesized (renderQuery q2)
117+
118+-- | The results of two queries can be combined using the set operation
119+-- `intersect`, the intersection. Duplicate rows are eliminated.
120+intersect
121+ :: Query schema params columns
122+ -> Query schema params columns
123+ -> Query schema params columns
110124q1 `intersect` q2 = UnsafeQuery $
111125 parenthesized (renderQuery q1)
112126 <+> "INTERSECT"
113127 <+> parenthesized (renderQuery q2)
128+
129+-- | The results of two queries can be combined using the set operation
130+-- `intersectAll`, the intersection. Duplicate rows are retained.
131+intersectAll
132+ :: Query schema params columns
133+ -> Query schema params columns
134+ -> Query schema params columns
114135q1 `intersectAll` q2 = UnsafeQuery $
115136 parenthesized (renderQuery q1)
116137 <+> "INTERSECT" <+> "ALL"
117138 <+> parenthesized (renderQuery q2)
139+
140+-- | The results of two queries can be combined using the set operation
141+-- `except`, the set difference. Duplicate rows are eliminated.
142+except
143+ :: Query schema params columns
144+ -> Query schema params columns
145+ -> Query schema params columns
118146q1 `except` q2 = UnsafeQuery $
119147 parenthesized (renderQuery q1)
120148 <+> "EXCEPT"
121149 <+> parenthesized (renderQuery q2)
150+
151+-- | The results of two queries can be combined using the set operation
152+-- `exceptAll`, the set difference. Duplicate rows are retained.
153+exceptAll
154+ :: Query schema params columns
155+ -> Query schema params columns
156+ -> Query schema params columns
122157q1 `exceptAll` q2 = UnsafeQuery $
123158 parenthesized (renderQuery q1)
124159 <+> "EXCEPT" <+> "ALL"
@@ -128,101 +163,91 @@ q1 `exceptAll` q2 = UnsafeQuery $
128163SELECT queries
129164-----------------------------------------}
130165
166+-- | the `TableExpression` in the `select` command constructs an intermediate
167+-- virtual table by possibly combining tables, views, eliminating rows,
168+-- grouping, etc. This table is finally passed on to processing by
169+-- the select list. The select list determines which columns of
170+-- the intermediate table are actually output.
131171select
132172 :: SListI columns
133173 => NP (Aliased (Expression tables grouping params)) (column ': columns)
174+ -- ^ select list
134175 -> TableExpression schema params tables grouping
176+ -- ^ intermediate virtual table
135177 -> Query schema params (column ': columns)
136178select list tabs = UnsafeQuery $
137179 "SELECT"
138180 <+> renderCommaSeparated (renderAliased renderExpression) list
139181 <+> renderTableExpression tabs
140182
183+-- | After the select list has been processed, the result table can
184+-- be subject to the elimination of duplicate rows using `selectDistinct`.
141185selectDistinct
142186 :: SListI columns
143187 => NP (Aliased (Expression tables 'Ungrouped params)) (column ': columns)
188+ -- ^ select list
144189 -> TableExpression schema params tables 'Ungrouped
190+ -- ^ intermediate virtual table
145191 -> Query schema params (column ': columns)
146192selectDistinct list tabs = UnsafeQuery $
147193 "SELECT DISTINCT"
148194 <+> renderCommaSeparated (renderAliased renderExpression) list
149195 <+> renderTableExpression tabs
150196
151−selectStar, selectDistinctStar
197+-- | The simplest kind of query is `selectStar` which emits all columns
198+-- that the table expression produces.
199+selectStar
152200 :: HasUnique table tables columns
153201 => TableExpression schema params tables 'Ungrouped
202+ -- ^ intermediate virtual table
154203 -> Query schema params columns
155204selectStar tabs = UnsafeQuery $ "SELECT" <+> "*" <+> renderTableExpression tabs
205+
206+-- | A `selectDistinctStar` emits all columns that the table expression
207+-- produces and eliminates duplicate rows.
208+selectDistinctStar
209+ :: HasUnique table tables columns
210+ => TableExpression schema params tables 'Ungrouped
211+ -- ^ intermediate virtual table
212+ -> Query schema params columns
156213selectDistinctStar tabs = UnsafeQuery $
157214 "SELECT DISTINCT" <+> "*" <+> renderTableExpression tabs
158215
159−selectDotStar, selectDistinctDotStar
216+-- | When working with multiple tables, it can also be useful to ask
217+-- for all the columns of a particular table, using `selectDotStar`.
218+selectDotStar
160219 :: HasTable table tables columns
161220 => Alias table
221+ -- ^ particular virtual subtable
162222 -> TableExpression schema params tables 'Ungrouped
223+ -- ^ intermediate virtual table
163224 -> Query schema params columns
164225selectDotStar table tables = UnsafeQuery $
165226 "SELECT" <+> renderAlias table <> ".*" <+> renderTableExpression tables
227+
228+-- | A `selectDistinctDotStar` asks for all the columns of a particular table,
229+-- and eliminates duplicate rows.
230+selectDistinctDotStar
231+ :: HasTable table tables columns
232+ => Alias table
233+ -- ^ particular virtual subtable
234+ -> TableExpression schema params tables 'Ungrouped
235+ -- ^ intermediate virtual table
236+ -> Query schema params columns
166237selectDistinctDotStar table tables = UnsafeQuery $
167238 "SELECT DISTINCT" <+> renderAlias table <> ".*"
168239 <+> renderTableExpression tables
169240
170−{-----------------------------------------
171−FROM clauses
172−-----------------------------------------}
173−
174−data FromClause schema params tables where
175− Table
176− :: Aliased (Table schema) table
177− -> FromClause schema params '[table]
178− Subquery
179− :: Aliased (Query schema params) table
180− -> FromClause schema params '[table]
181− CrossJoin
182− :: FromClause schema params right
183− -> FromClause schema params left
184− -> FromClause schema params (Join left right)
185− InnerJoin
186− :: FromClause schema params right
187− -> Condition (Join left right) 'Ungrouped params
188− -> FromClause schema params left
189− -> FromClause schema params (Join left right)
190− LeftOuterJoin
191− :: FromClause schema params right
192− -> Condition (Join left right) 'Ungrouped params
193− -> FromClause schema params left
194− -> FromClause schema params (Join left (NullifyTables right))
195− RightOuterJoin
196− :: FromClause schema params right
197− -> Condition (Join left right) 'Ungrouped params
198− -> FromClause schema params left
199− -> FromClause schema params (Join (NullifyTables left) right)
200− FullOuterJoin
201− :: FromClause schema params right
202− -> Condition (Join left right) 'Ungrouped params
203− -> FromClause schema params left
204− -> FromClause schema params
205− (Join (NullifyTables left) (NullifyTables right))
206−
207−renderFromClause :: FromClause schema params tables -> ByteString
208−renderFromClause = \case
209− Table table -> renderAliased renderTable table
210− Subquery selection -> renderAliased (parenthesized . renderQuery) selection
211− CrossJoin right left ->
212− renderFromClause left <+> "CROSS JOIN" <+> renderFromClause right
213− InnerJoin right on left -> renderJoin "INNER JOIN" right on left
214− LeftOuterJoin right on left -> renderJoin "LEFT OUTER JOIN" right on left
215− RightOuterJoin right on left -> renderJoin "RIGHT OUTER JOIN" right on left
216− FullOuterJoin right on left -> renderJoin "FULL OUTER JOIN" right on left
217− where
218− renderJoin op right on left =
219− renderFromClause left <+> op <+> renderFromClause right
220− <+> "ON" <+> renderExpression on
221−
222241{-----------------------------------------
223242Table Expressions
224243-----------------------------------------}
225244
245+-- | A `TableExpression` computes a table. The table expression contains
246+-- a `fromClause` that is optionally followed by a `whereClause`,
247+-- `groupByClause`, `havingClause`, `orderByClause`, `limitClause`
248+-- and `offsetClause`s. Trivial table expressions simply refer
249+-- to a table on disk, a so-called base table, but more complex expressions
250+-- can be used to modify or combine base tables in various ways.
226251data TableExpression
227252 (schema :: TablesType)
228253 (params :: [ColumnType])
@@ -230,14 +255,47 @@ data TableExpression
230255 (grouping :: Grouping)
231256 = TableExpression
232257 { fromClause :: FromClause schema params tables
258+ -- ^ A table reference that can be a table name, or a derived table such
259+ -- as a subquery, a @JOIN@ construct, or complex combinations of these.
233260 , whereClause :: [Condition tables 'Ungrouped params]
261+ -- ^ optional search coditions, combined with `.&&`. After the processing
262+ -- of the `fromClause` is done, each row of the derived virtual table
263+ -- is checked against the search condition. If the result of the
264+ -- condition is true, the row is kept in the output table,
265+ -- otherwise it is discarded. The search condition typically references
266+ -- at least one column of the table generated in the `fromClause`;
267+ -- this is not required, but otherwise the WHERE clause will
268+ -- be fairly useless.
234269 , groupByClause :: GroupByClause tables grouping
270+ -- ^ The `groupByClause` is used to group together those rows in a table
271+ -- that have the same values in all the columns listed. The order in which
272+ -- the columns are listed does not matter. The effect is to combine each
273+ -- set of rows having common values into one group row that represents all
274+ -- rows in the group. This is done to eliminate redundancy in the output
275+ -- and/or compute aggregates that apply to these groups.
235276 , havingClause :: HavingClause tables grouping params
277+ -- ^ If a table has been grouped using `groupBy`, but only certain groups
278+ -- are of interest, the `havingClause` can be used, much like a
279+ -- `whereClause`, to eliminate groups from the result. Expressions in the
280+ -- `havingClause` can refer both to grouped expressions and to ungrouped
281+ -- expressions (which necessarily involve an aggregate function).
236282 , orderByClause :: [SortExpression tables grouping params]
283+ -- ^ The `orderByClause` is for optional sorting. When more than one
284+ -- `SortExpression` is specified, the later (right) values are used to sort
285+ -- rows that are equal according to the earlier (left) values.
237286 , limitClause :: [Word64]
287+ -- ^ The `limitClause` is combined with `min` to give a limit count
288+ -- if nonempty. If a limit count is given, no more than that many rows
289+ -- will be returned (but possibly fewer, if the query itself yields
290+ -- fewer rows).
238291 , offsetClause :: [Word64]
292+ -- ^ The `offsetClause` is combined with `+` to give an offset count
293+ -- if nonempty. The offset count says to skip that many rows before
294+ -- beginning to return rows. The rows are skipped before the limit count
295+ -- is applied.
239296 }
240297
298+-- | Render a `TableExpression`
241299renderTableExpression
242300 :: TableExpression schema params tables grouping
243301 -> ByteString
@@ -267,17 +325,27 @@ renderTableExpression
267325 [] -> ""
268326 offs -> " OFFSET" <+> fromString (show (sum offs))
269327
328+-- | A `from` generates a `TableExpression` from a table reference that can be
329+-- a table name, or a derived table such as a subquery, a JOIN construct,
330+-- or complex combinations of these. A `from` may be transformed by `where_`,
331+-- `group`, `having`, `orderBy`, `limit` and `offset`, using the `&` operator
332+-- to match the left-to-right sequencing of their placement in SQL.
270333from
271− :: FromClause schema params tables
334+ :: FromClause schema params tables -- ^ table reference
272335 -> TableExpression schema params tables 'Ungrouped
273336from tables = TableExpression tables [] NoGroups NoHaving [] [] []
274337
338+-- | A `where_` is an endomorphism of `TableExpression`s which adds a
339+-- search condition to the `whereClause`.
275340where_
276341 :: Condition tables 'Ungrouped params
277342 -> TableExpression schema params tables grouping
278343 -> TableExpression schema params tables grouping
279344where_ wh tables = tables {whereClause = wh : whereClause tables}
280345
346+-- | A `group` is a transformation of `TableExpression`s which switches
347+-- its `Grouping` from `Ungrouped` to `Grouped`. Use @group Nil@ to perform
348+-- a "grand total" aggregation query.
281349group
282350 :: SListI bys
283351 => NP (By tables) bys
@@ -293,6 +361,8 @@ group bys tables = TableExpression
293361 , offsetClause = offsetClause tables
294362 }
295363
364+-- | A `having` is an endomorphism of `TableExpression`s which adds a
365+-- search condition to the `havingClause`.
296366having
297367 :: Condition tables ('Grouped bys) params
298368 -> TableExpression schema params tables ('Grouped bys)
@@ -300,28 +370,134 @@ having
300370having hv tables = tables
301371 { havingClause = case havingClause tables of Having hvs -> Having (hv:hvs) }
302372
373+-- | An `orderBy` is an endomorphism of `TableExpression`s which appends an
374+-- ordering to the right of the `orderByClause`.
303375orderBy
304376 :: [SortExpression tables grouping params]
305377 -> TableExpression schema params tables grouping
306378 -> TableExpression schema params tables grouping
307379orderBy srts tables = tables {orderByClause = orderByClause tables ++ srts}
308380
381+-- | A `limit` is an endomorphism of `TableExpression`s which adds to the
382+-- `limitClause`.
309383limit
310384 :: Word64
311385 -> TableExpression schema params tables grouping
312386 -> TableExpression schema params tables grouping
313387limit lim tables = tables {limitClause = lim : limitClause tables}
314388
389+-- | An `offset` is an endomorphism of `TableExpression`s which adds to the
390+-- `offsetClause`.
315391offset
316392 :: Word64
317393 -> TableExpression schema params tables grouping
318394 -> TableExpression schema params tables grouping
319395offset off tables = tables {offsetClause = off : offsetClause tables}
320396
397+{-----------------------------------------
398+FROM clauses
399+-----------------------------------------}
400+
401+{- |
402+A `FromClause` can be a table name, or a derived table such
403+as a subquery, a @JOIN@ construct, or complex combinations of these.
404+
405+* A real `Table` is a table from the schema.
406+
407+* `Subquery` derives a table from a `Query`.
408+
409+* A joined table is a table derived from two other (real or derived) tables
410+according to the rules of the particular join type. `CrossJoin`, `InnerJoin`,
411+`LeftOuterJoin`, `RightOuterJoin` and `FullOuterJoin` are available and can
412+be nested using the `&` operator to match the left-to-right sequencing of
413+their placement in SQL.
414+
415+ * @t1 & CrossJoin t2@. For every possible combination of rows from
416+ @t1@ and @t2@ (i.e., a Cartesian product), the joined table will contain
417+ a row consisting of all columns in @t1@ followed by all columns in @t2@.
418+ If the tables have @n@ and @m@ rows respectively, the joined table will
419+ have @n * m@ rows.
420+
421+ * @t1 & InnerJoin t2 on@. For each row @r1@ of @t1@, the joined
422+ table has a row for each row in @t2@ that satisfies the on @condition@
423+ with @r1@
424+
425+ * @t1 & LeftOuterJoin t2 on@. First, an inner join is performed.
426+ Then, for each row in @t1@ that does not satisfy the on @condition@ with
427+ any row in @t2@, a joined row is added with null values in columns of @t2@.
428+ Thus, the joined table always has at least one row for each row in @t1@.
429+
430+ * @t1 & RightOuterJoin t2 on@. First, an inner join is performed.
431+ Then, for each row in @t2@ that does not satisfy the on condition with
432+ any row in @t1@, a joined row is added with null values in columns of @t1@.
433+ This is the converse of a left join: the result table will always
434+ have a row for each row in @t2@.
435+
436+ * @t1 & FullOuterJoin t2 on@. First, an inner join is performed.
437+ Then, for each row in @t1@ that does not satisfy the on condition with
438+ any row in @t2@, a joined row is added with null values in columns of @t2@.
439+ Also, for each row of @t2@ that does not satisfy the join condition
440+ with any row in @t1@, a joined row with null values in the columns of @t1@
441+ is added.
442+-}
443+data FromClause schema params tables where
444+ Table
445+ :: Aliased (Table schema) table
446+ -> FromClause schema params '[table]
447+ Subquery
448+ :: Aliased (Query schema params) table
449+ -> FromClause schema params '[table]
450+ CrossJoin
451+ :: FromClause schema params right
452+ -> FromClause schema params left
453+ -> FromClause schema params (Join left right)
454+ InnerJoin
455+ :: FromClause schema params right
456+ -> Condition (Join left right) 'Ungrouped params
457+ -> FromClause schema params left
458+ -> FromClause schema params (Join left right)
459+ LeftOuterJoin
460+ :: FromClause schema params right
461+ -> Condition (Join left right) 'Ungrouped params
462+ -> FromClause schema params left
463+ -> FromClause schema params (Join left (NullifyTables right))
464+ RightOuterJoin
465+ :: FromClause schema params right
466+ -> Condition (Join left right) 'Ungrouped params
467+ -> FromClause schema params left
468+ -> FromClause schema params (Join (NullifyTables left) right)
469+ FullOuterJoin
470+ :: FromClause schema params right
471+ -> Condition (Join left right) 'Ungrouped params
472+ -> FromClause schema params left
473+ -> FromClause schema params
474+ (Join (NullifyTables left) (NullifyTables right))
475+
476+-- | Renders a `FromClause`.
477+renderFromClause :: FromClause schema params tables -> ByteString
478+renderFromClause = \case
479+ Table table -> renderAliased renderTable table
480+ Subquery selection -> renderAliased (parenthesized . renderQuery) selection
481+ CrossJoin right left ->
482+ renderFromClause left <+> "CROSS JOIN" <+> renderFromClause right
483+ InnerJoin right on left -> renderJoin "INNER JOIN" right on left
484+ LeftOuterJoin right on left -> renderJoin "LEFT OUTER JOIN" right on left
485+ RightOuterJoin right on left -> renderJoin "RIGHT OUTER JOIN" right on left
486+ FullOuterJoin right on left -> renderJoin "FULL OUTER JOIN" right on left
487+ where
488+ renderJoin op right on left =
489+ renderFromClause left <+> op <+> renderFromClause right
490+ <+> "ON" <+> renderExpression on
491+
321492{-----------------------------------------
322493Grouping
323494-----------------------------------------}
324495
496+-- | `By`s are used in `group` to reference a list of columns which are then
497+-- used to group together those rows in a table that have the same values
498+-- in all the columns listed. @By \#col@ will reference an unambiguous
499+-- column @col@; otherwise @By2 \#tab \! \#col@ will reference a table
500+-- qualified column @tab.col@.
325501data By
326502 (tables :: TablesType)
327503 (by :: (Symbol,Symbol)) where
@@ -336,25 +512,36 @@ data By
336512deriving instance Show (By tables by)
337513deriving instance Eq (By tables by)
338514deriving instance Ord (By tables by)
339−
515+
516+-- | Renders a `By`.
340517renderBy :: By tables tabcolty -> ByteString
341518renderBy = \case
342519 By column -> renderAlias column
343520 By2 (table, column) -> renderAlias table <> "." <> renderAlias column
344−
521+
522+-- | A `GroupByClause` indicates the `Grouping` of a `TableExpression`.
523+-- A `NoGroups` indicates `Ungrouped` while a `Group` indicates `Grouped`.
524+-- @NoGroups@ is distinguised from @Group Nil@ since no aggregation can be
525+-- done on @NoGroups@ while all output `Expression`s must be aggregated
526+-- in @Group Nil@.
345527data GroupByClause tables grouping where
346528 NoGroups :: GroupByClause tables 'Ungrouped
347529 Group
348530 :: SListI bys
349531 => NP (By tables) bys
350532 -> GroupByClause tables ('Grouped bys)
351−
533+
534+-- | Renders a `GroupByClause`.
352535renderGroupByClause :: GroupByClause tables grouping -> ByteString
353536renderGroupByClause = \case
354537 NoGroups -> ""
355538 Group Nil -> ""
356539 Group bys -> " GROUP BY" <+> renderCommaSeparated renderBy bys
357−
540+
541+-- | A `HavingClause` is used to eliminate groups that are not of interest.
542+-- An `Ungrouped` `TableExpression` may only use `NoHaving` while a `Grouped`
543+-- `TableExpression` must use `Having` whose conditions are combined with
544+-- `.&&`.
358545data HavingClause tables grouping params where
359546 NoHaving :: HavingClause tables 'Ungrouped params
360547 Having
@@ -363,7 +550,8 @@ data HavingClause tables grouping params where
363550deriving instance Show (HavingClause tables grouping params)
364551deriving instance Eq (HavingClause tables grouping params)
365552deriving instance Ord (HavingClause tables grouping params)
366−
553+
554+-- | Render a `HavingClause`.
367555renderHavingClause :: HavingClause tables grouping params -> ByteString
368556renderHavingClause = \case
369557 NoHaving -> ""
@@ -375,6 +563,14 @@ renderHavingClause = \case
375563Sorting
376564-----------------------------------------}
377565
566+-- | `SortExpression`s are used by `sortBy` to optionally sort the results
567+-- of a `Query`. `Asc` or `Desc` set the sort direction of a `NotNull` result
568+-- column to ascending or descending. Ascending order puts smaller values
569+-- first, where "smaller" is defined in terms of the `.<` operator. Similarly,
570+-- descending order is determined with the `.>` operator. `AscNullsFirst`,
571+-- `AscNullsLast`, `DescNullsFirst` and `DescNullsLast` options are used to
572+-- determine whether nulls appear before or after non-null values in the sort
573+-- ordering of a `Null` result column.
378574data SortExpression tables grouping params where
379575 Asc
380576 :: Expression tables grouping params ('Required ('NotNull ty))
@@ -395,7 +591,8 @@ data SortExpression tables grouping params where
395591 :: Expression tables grouping params ('Required ('Null ty))
396592 -> SortExpression tables grouping params
397593deriving instance Show (SortExpression tables grouping params)
398−
594+
595+-- | Render a `SortExpression`.
399596renderSortExpression :: SortExpression tables grouping params -> ByteString
400597renderSortExpression = \case
401598 Asc expression -> renderExpression expression <+> "ASC"