@@ -30,18 +30,9 @@ Squeal data manipulation language.
3030
3131module Squeal.PostgreSQL.Manipulation
3232 ( -- * Manipulation
33− Manipulation (..)
34− , Manipulation_
33+ Manipulation_
34+ , Manipulation (..)
3535 , queryStatement
36− , QueryClause (..)
37− , pattern Values_
38− , Optional (..)
39− , ReturningClause (..)
40− , pattern Returning_
41− , ConflictClause (..)
42− , ConflictTarget (..)
43− , ConflictAction (..)
44− , UsingClause (..)
4536 -- * Insert
4637 , insertInto
4738 , insertInto_
@@ -51,6 +42,16 @@ module Squeal.PostgreSQL.Manipulation
5142 -- * Delete
5243 , deleteFrom
5344 , deleteFrom_
45+ -- * Clauses
46+ , QueryClause (..)
47+ , pattern Values_
48+ , Optional (..)
49+ , ReturningClause (..)
50+ , pattern Returning_
51+ , ConflictClause (..)
52+ , ConflictTarget (..)
53+ , ConflictAction (..)
54+ , UsingClause (..)
5455 ) where
5556
5657import Control.DeepSeq
@@ -71,19 +72,57 @@ import Squeal.PostgreSQL.Schema
7172
7273-- $setup
7374-- >>> import Squeal.PostgreSQL
75+-- >>> import Data.Int
76+-- >>> import Data.Time
7477
7578{- |
7679A `Manipulation` is a statement which may modify data in the database,
7780but does not alter its schemas. Examples are inserts, updates and deletes.
7881A `Query` is also considered a `Manipulation` even though it does not modify data.
7982
83+The general `Manipulation` type is parameterized by
84+
85+* @commons :: FromType@ - scope for all `common` table expressions,
86+* @schemas :: SchemasType@ - scope for all `table`s and `view`s,
87+* @params :: [NullityType]@ - scope for all `Squeal.Expression.Parameter.parameter`s,
88+* @row :: RowType@ - return type of the `Query`.
89+-}
90+newtype Manipulation
91+ (commons :: FromType)
92+ (schemas :: SchemasType)
93+ (params :: [NullityType])
94+ (columns :: RowType)
95+ = UnsafeManipulation { renderManipulation :: ByteString }
96+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
97+instance RenderSQL (Manipulation commons schemas params columns) where
98+ renderSQL = renderManipulation
99+instance With Manipulation where
100+ with Done manip = manip
101+ with ctes manip = UnsafeManipulation $
102+ "WITH" <+> renderSQL ctes <+> renderSQL manip
103+
104+{- |
105+The top level `Manipulation_` type is parameterized by a @schemas@ `SchemasType`,
106+against which the query is type-checked, an input @parameters@ Haskell `Type`,
107+and an ouput row Haskell `Type`.
108+
109+A top-level `Manipulation_` can be run
110+using `Squeal.PostgreSQL.PQ.manipulateParams`, or if @parameters = ()@
111+using `Squeal.PostgreSQL.PQ.manipulate`.
112+
113+Generally, @parameters@ will be a Haskell tuple or record whose entries
114+may be referenced using positional
115+`Squeal.PostgreSQL.Expression.Parameter.parameter`s and @row@ will be a
116+Haskell record or a generalized record using tuples of `P` types and normal Haskell
117+records, whose entries will be targeted using overloaded labels.
118+
80119simple insert:
81120
82121>>> type Columns = '["col1" ::: 'NoDef :=> 'Null 'PGint4, "col2" ::: 'Def :=> 'NotNull 'PGint4]
83122>>> type Schema = '["tab" ::: 'Table ('[] :=> Columns)]
84123>>> :{
85124let
86− manipulation :: Manipulation '[] (Public Schema) '[] '[]
125+ manipulation :: Manipulation_ (Public Schema) () ()
87126 manipulation =
88127 insertInto_ #tab (Values_ (Set 2 `as` #col1 :* Default `as` #col2))
89128in printSQL manipulation
@@ -96,7 +135,7 @@ parameterized insert:
96135>>> type Schema = '["tab" ::: 'Table ('[] :=> Columns)]
97136>>> :{
98137let
99− manipulation :: Manipulation '[] (Public Schema) '[ 'NotNull 'PGint4, 'NotNull 'PGint4 ] '[]
138+ manipulation :: Manipulation_ (Public Schema) (Int32, Int32) ()
100139 manipulation =
101140 insertInto_ #tab (Values_ (Set (param @1) `as` #col1 :* Set (param @2) `as` #col2))
102141in printSQL manipulation
@@ -107,7 +146,7 @@ returning insert:
107146
108147>>> :{
109148let
110− manipulation :: Manipulation '[] (Public Schema) '[] '["fromOnly" ::: 'NotNull 'PGint4]
149+ manipulation :: Manipulation_ (Public Schema) () (Only Int32)
111150 manipulation =
112151 insertInto #tab (Values_ (Set 2 `as` #col1 :* Set 3 `as` #col2))
113152 OnConflictDoRaise (Returning (#col1 `as` #fromOnly))
@@ -122,7 +161,7 @@ upsert:
122161>>> type CustomersSchema = '["customers" ::: 'Table (CustomersConstraints :=> CustomersColumns)]
123162>>> :{
124163let
125− manipulation :: Manipulation '[] (Public CustomersSchema) '[] '[]
164+ manipulation :: Manipulation_ (Public CustomersSchema) () ()
126165 manipulation =
127166 insertInto #customers
128167 (Values_ (Set "John Smith" `as` #name :* Set "john@smith.com" `as` #email))
@@ -137,7 +176,7 @@ query insert:
137176
138177>>> :{
139178let
140− manipulation :: Manipulation '[] (Public Schema) '[] '[]
179+ manipulation :: Manipulation_ (Public Schema) () ()
141180 manipulation = insertInto_ #tab (Subquery (select Star (from (table #tab))))
142181in printSQL manipulation
143182:}
@@ -147,7 +186,7 @@ update:
147186
148187>>> :{
149188let
150− manipulation :: Manipulation '[] (Public Schema) '[] '[]
189+ manipulation :: Manipulation_ (Public Schema) () ()
151190 manipulation = update_ #tab (Set 2 `as` #col1) (#col1 ./= #col2)
152191in printSQL manipulation
153192:}
@@ -157,9 +196,8 @@ delete:
157196
158197>>> :{
159198let
160− manipulation :: Manipulation '[] (Public Schema) '[]
161− '[ "col1" ::: 'NotNull 'PGint4
162− , "col2" ::: 'NotNull 'PGint4 ]
199+ manipulation :: Manipulation_ (Public Schema) ()
200+ (P ("col1" ::: Int32), P ("col2" ::: Int32))
163201 manipulation = deleteFrom #tab NoUsing (#col1 .== #col2) (Returning Star)
164202in printSQL manipulation
165203:}
@@ -170,13 +208,13 @@ delete and using clause:
170208>>> :{
171209type Schema3 =
172210 '[ "tab" ::: 'Table ('[] :=> Columns)
173− , "other_tab" ::: 'Table ('[] :=> Columns)
174− , "third_tab" ::: 'Table ('[] :=> Columns) ]
211+ , "other_tab" ::: 'Table ('[] :=> Columns)
212+ , "third_tab" ::: 'Table ('[] :=> Columns) ]
175213:}
176214
177215>>> :{
178216let
179− manipulation :: Manipulation '[] (Public Schema3) '[] '[]
217+ manipulation :: Manipulation_ (Public Schema3) () ()
180218 manipulation =
181219 deleteFrom #tab (Using (table #other_tab & also (table #third_tab)))
182220 ( (#tab ! #col2 .== #other_tab ! #col2)
@@ -192,7 +230,7 @@ with manipulation:
192230>>> type ProductsSchema = '["products" ::: 'Table ('[] :=> ProductsColumns), "products_deleted" ::: 'Table ('[] :=> ProductsColumns)]
193231>>> :{
194232let
195− manipulation :: Manipulation '[] (Public ProductsSchema) '[ 'NotNull 'PGdate] '[]
233+ manipulation :: Manipulation_ (Public ProductsSchema) (Only Day) ()
196234 manipulation = with
197235 (deleteFrom #products NoUsing (#date .< param @1) (Returning Star) `as` #del)
198236 (insertInto_ #products_deleted (Subquery (select Star (from (common #del)))))
@@ -200,20 +238,6 @@ in printSQL manipulation
200238:}
201239WITH "del" AS (DELETE FROM "products" WHERE ("date" < ($1 :: date)) RETURNING *) INSERT INTO "products_deleted" SELECT * FROM "del" AS "del"
202240-}
203−newtype Manipulation
204− (commons :: FromType)
205− (schemas :: SchemasType)
206− (params :: [NullityType])
207− (columns :: RowType)
208− = UnsafeManipulation { renderManipulation :: ByteString }
209− deriving (GHC.Generic,Show,Eq,Ord,NFData)
210−instance RenderSQL (Manipulation commons schemas params columns) where
211− renderSQL = renderManipulation
212−instance With Manipulation where
213− with Done manip = manip
214− with ctes manip = UnsafeManipulation $
215− "WITH" <+> renderSQL ctes <+> renderSQL manip
216−
217241type family Manipulation_ (schemas :: SchemasType) (params :: Type) (row :: Type) where
218242 Manipulation_ schemas params row = Manipulation '[] schemas (TuplePG params) (RowPG row)
219243
@@ -252,6 +276,7 @@ insertInto tab qry conflict ret = UnsafeManipulation $
252276 <> renderSQL conflict
253277 <> renderSQL ret
254278
279+-- | Like `insertInto` but with `OnConflictDoRaise` and no `ReturningClause`.
255280insertInto_
256281 :: ( Has sch schemas schema
257282 , Has tab schema ('Table table)
@@ -264,17 +289,22 @@ insertInto_
264289insertInto_ tab qry =
265290 insertInto tab qry OnConflictDoRaise (Returning_ Nil)
266291
292+-- | A `QueryClause` describes what to `insertInto` a table.
267293data QueryClause commons schemas params columns where
294+ -- | `Values` describes `NP` lists of `Aliased` `Optional` `Expression`s
295+ -- whose `ColumnsType` must match the tables'.
268296 Values
269297 :: SOP.SListI columns
270− => NP (Aliased (Optional (Expression '[] '[] 'Ungrouped schemas params '[]))) columns
271− -> [NP (Aliased (Optional (Expression '[] '[] 'Ungrouped schemas params '[]))) columns]
298+ => NP (Aliased (Optional (Expression '[] commons 'Ungrouped schemas params '[]))) columns
299+ -> [NP (Aliased (Optional (Expression '[] commons 'Ungrouped schemas params '[]))) columns]
272300 -> QueryClause commons schemas params columns
301+ -- | `Select` describes a subquery that permits use of `Optional` `Expression`s.
273302 Select
274303 :: SOP.SListI columns
275− => NP (Aliased (Optional (Expression '[] '[] grp schemas params from))) columns
304+ => NP (Aliased (Optional (Expression '[] commons grp schemas params from))) columns
276305 -> TableExpression '[] commons grp schemas params from
277306 -> QueryClause commons schemas params columns
307+ -- | `Subquery` describes a subquery whose `RowType` must match the tables'.
278308 Subquery
279309 :: ColumnsToRow columns ~ row
280310 => Query '[] commons schemas params row
@@ -296,7 +326,7 @@ instance RenderSQL (QueryClause commons schemas params columns) where
296326 Subquery qry -> renderQuery qry
297327 where
298328 renderSQLPartMaybe, renderValuePartMaybe
299− :: Aliased (Optional (Expression '[] '[] grp schemas params from)) column
329+ :: Aliased (Optional (Expression '[] commons grp schemas params from)) column
300330 -> Maybe ByteString
301331 renderSQLPartMaybe = \case
302332 Default `As` _ -> Nothing
@@ -305,19 +335,25 @@ instance RenderSQL (QueryClause commons schemas params columns) where
305335 Default `As` _ -> Nothing
306336 Set value `As` _ -> Just $ renderExpression value
307337 renderSQLPart, renderValuePart
308− :: Aliased (Optional (Expression '[] '[] grp schemas params from)) column
338+ :: Aliased (Optional (Expression '[] commons grp schemas params from)) column
309339 -> ByteString
310340 renderSQLPart (_ `As` name) = renderSQL name
311341 renderValuePart (value `As` _) = renderSQL value
312342
343+-- | `Values_` describes a single `NP` list of `Aliased` `Optional` `Expression`s
344+-- whose `ColumnsType` must match the tables'.
313345pattern Values_
314346 :: SOP.SListI columns
315− => NP (Aliased (Optional (Expression '[] '[] 'Ungrouped schemas params '[]))) columns
347+ => NP (Aliased (Optional (Expression '[] commons 'Ungrouped schemas params '[]))) columns
316348 -> QueryClause commons schemas params columns
317349pattern Values_ vals = Values vals []
318350
351+-- | `Optional` is either `Default` or a value, parameterized by an appropriate
352+-- `ColumnConstraint`.
319353data Optional expr ty where
354+ -- | Use the `Default` value for a column.
320355 Default :: Optional expr ('Def :=> ty)
356+ -- | `Set` a value for a column.
321357 Set :: expr ty -> Optional expr (def :=> ty)
322358
323359instance (forall x. RenderSQL (expr x)) => RenderSQL (Optional expr ty) where
@@ -343,6 +379,7 @@ instance RenderSQL (ReturningClause commons schemas params from row) where
343379 Returning (List Nil) -> ""
344380 Returning selection -> " RETURNING" <+> renderSQL selection
345381
382+-- | `Returning` a `List`
346383pattern Returning_
347384 :: SOP.SListI row
348385 => NP (Aliased (Expression '[] commons 'Ungrouped schemas params from)) row
@@ -369,8 +406,19 @@ instance SOP.SListI (TableToColumns table)
369406 OnConflict target action -> " ON CONFLICT"
370407 <+> renderSQL target <+> renderSQL action
371408
409+{- |
410+`ConflictAction` specifies an alternative `OnConflict` action.
411+It can be either `DoNothing`, or a `DoUpdate` clause specifying
412+the exact details of the `update` action to be performed in case of a conflict.
413+The `Set` and WHERE `Condition`s in `OnConflict` `DoUpdate` have access to the
414+existing row using the table's name (or an alias), and to rows proposed
415+for insertion using the special @#excluded@ table.
416+-}
372417data ConflictAction tab commons schemas params columns where
418+ -- | `OnConflict` `DoNothing` simply avoids inserting a row as its alternative action.
373419 DoNothing :: ConflictAction tab commons schemas params columns
420+ -- | `OnConflict` `DoUpdate` updates the existing row that conflicts
421+ -- with the row proposed for insertion as its alternative action.
374422 DoUpdate
375423 :: ( row ~ ColumnsToRow columns
376424 , SOP.SListI columns
@@ -379,6 +427,7 @@ data ConflictAction tab commons schemas params columns where
379427 , AllUnique subcolumns )
380428 => NP (Aliased (Optional (Expression '[] commons 'Ungrouped schemas params '[tab ::: row, "excluded" ::: row]))) subcolumns
381429 -> [Condition '[] commons 'Ungrouped schemas params '[tab ::: row, "excluded" ::: row]]
430+ -- ^ WHERE `Condition`s
382431 -> ConflictAction tab commons schemas params columns
383432
384433instance RenderSQL (ConflictAction tab commons schemas params columns) where
@@ -463,8 +512,17 @@ update_ tab columns wh = update tab columns wh (Returning_ Nil)
463512DELETE statements
464513-----------------------------------------}
465514
515+-- | Specify additional tables.
466516data UsingClause commons schemas params from where
517+ -- | No `UsingClause`
467518 NoUsing :: UsingClause commons schemas params '[]
519+ -- | An `also` list of table expressions, allowing columns
520+ -- from other tables to appear in the WHERE condition.
521+ -- This is similar to the list of tables that can be specified
522+ -- in the FROM Clause of a SELECT statement;
523+ -- for example, an alias for the table name can be specified.
524+ -- Do not repeat the target table in the `Using` list,
525+ -- unless you wish to set up a self-join.
468526 Using
469527 :: FromClause '[] commons schemas params from
470528 -> UsingClause commons schemas params from