on dev help sign in
manipulation docs
ac91227Eitan Chatav
squeal-postgresql/exe/Example.hs+1−1
@@ -59,7 +59,7 @@ main = do
59 ( Values
59 ( Values
60 (param @1 `As` #col1 :* param @2 `As` #col2 :* Nil)
60 (param @1 `As` #col1 :* param @2 `As` #col2 :* Nil)
61 [param @3 `As` #col1 :* param @4 `As` #col2 :* Nil]
61 [param @3 `As` #col1 :* param @4 `As` #col2 :* Nil]
62− ) Conflict (Returning Nil)
62+ ) OnConflictDoNothing (Returning Nil)
63 liftBase $ Char8.putStrLn "manipulating"
63 liftBase $ Char8.putStrLn "manipulating"
64 _insertResult <- manipulateParams insert
64 _insertResult <- manipulateParams insert
65 (1::Int32,2::Int32,3::Int32,4::Int32)
65 (1::Int32,2::Int32,3::Int32,4::Int32)
squeal-postgresql/src/Squeal/PostgreSQL/Manipulation.hs+144−28
@@ -1,3 +1,13 @@
1+{-|
2+Module: Squeal.PostgreSQL.Manipulation
3+Description: Squeal data manipulation language
4+Copyright: (c) Eitan Chatav, 2017
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Squeal data manipulation language.
9+-}
10+
1{-# LANGUAGE
11{-# LANGUAGE
2 DataKinds
12 DataKinds
3 , DeriveDataTypeable
13 , DeriveDataTypeable
@@ -14,19 +24,21 @@
14#-}
24#-}
15
25
16module Squeal.PostgreSQL.Manipulation
26module Squeal.PostgreSQL.Manipulation
17− ( -- * Data Manipulation Language
27+ ( -- * Manipulation
18 Manipulation (UnsafeManipulation, renderManipulation)
28 Manipulation (UnsafeManipulation, renderManipulation)
19 , queryStatement
29 , queryStatement
30+ -- * Insert
20 , insertInto
31 , insertInto
21 , ValuesClause (Values, ValuesQuery)
32 , ValuesClause (Values, ValuesQuery)
22 , renderValuesClause
33 , renderValuesClause
23 , ReturningClause (ReturningStar, Returning)
34 , ReturningClause (ReturningStar, Returning)
24 , renderReturningClause
35 , renderReturningClause
25− , ConflictClause (Conflict, OnConflictDoNothing, OnConflictDoUpdate)
36+ , ConflictClause (OnConflictDoRaise, OnConflictDoNothing, OnConflictDoUpdate)
26 , renderConflictClause
37 , renderConflictClause
38+ -- * Update
39+ , update
27 , UpdateExpression (Same, Set)
40 , UpdateExpression (Same, Set)
28 , renderUpdateExpression
41 , renderUpdateExpression
29− , update
30 , deleteFrom
42 , deleteFrom
31 ) where
43 ) where
32
44
@@ -42,6 +54,10 @@ import Squeal.PostgreSQL.Prettyprint
42import Squeal.PostgreSQL.Query
54import Squeal.PostgreSQL.Query
43import Squeal.PostgreSQL.Schema
55import Squeal.PostgreSQL.Schema
44
56
57+-- | A manipulation is a statement which may modify data in the database,
58+-- but does not alter the schema. Examples are `insertInto`, `update` and
59+-- `deleteFrom`. A `Query` is also considered a manipulation even though
60+-- it does not modify data.
45newtype Manipulation
61newtype Manipulation
46 (schema :: TablesType)
62 (schema :: TablesType)
47 (params :: [ColumnType])
63 (params :: [ColumnType])
@@ -49,6 +65,7 @@ newtype Manipulation
49 = UnsafeManipulation { renderManipulation :: ByteString }
65 = UnsafeManipulation { renderManipulation :: ByteString }
50 deriving (GHC.Generic,Show,Eq,Ord,NFData)
66 deriving (GHC.Generic,Show,Eq,Ord,NFData)
51
67
68+-- | Convert a `Query` into a `Manipulation`.
52queryStatement
69queryStatement
53 :: Query schema params columns
70 :: Query schema params columns
54 -> Manipulation schema params columns
71 -> Manipulation schema params columns
@@ -58,12 +75,51 @@ queryStatement q = UnsafeManipulation $ renderQuery q <> ";"
58INSERT statements
75INSERT statements
59-----------------------------------------}
76-----------------------------------------}
60
77
78+-- | When a table is created, it contains no data. The first thing to do
79+-- before a database can be of much use is to insert data. Data is
80+-- conceptually inserted one row at a time. Of course you can also insert
81+-- more than one row, but there is no way to insert less than one row.
82+-- Even if you know only some column values, a complete row must be created.
83+--
84+-- >>> :{
85+-- let
86+-- manipulation :: Manipulation
87+-- '[ "tab" :::
88+-- '[ "col1" ::: 'Required ('NotNull 'PGint4)
89+-- , "col2" ::: 'Required ('NotNull 'PGint4) ]] '[] '[]
90+-- manipulation =
91+-- insertInto #tab (Values (2 `As` #col1 :* 4 `As` #col2 :* Nil) [])
92+-- OnConflictDoRaise (Returning Nil)
93+-- in renderManipulation manipulation
94+-- :}
95+-- "INSERT INTO tab (col1, col2) VALUES (2, 4);"
96+--
97+-- >>> :{
98+-- let
99+-- manipulation :: Manipulation
100+-- '[ "tab" :::
101+-- '[ "col1" ::: 'Required ('NotNull 'PGint4)
102+-- , "col2" ::: 'Required ('NotNull 'PGint4) ]]
103+-- '[] '[ "sum" ::: 'Required ('NotNull 'PGint4)]
104+-- manipulation =
105+-- insertInto #tab
106+-- (Values
107+-- (2 `As` #col1 :* 4 `As` #col2 :* Nil)
108+-- [6 `As` #col1 :* 8 `As` #col2 :* Nil])
109+-- (OnConflictDoUpdate
110+-- (Set 2 `As` #col1 :* Same `As` #col2 :* Nil)
111+-- (Just (#col1 .== #col2)))
112+-- (Returning $ (#col1 + #col2) `As` #sum :* Nil)
113+-- in renderManipulation manipulation
114+-- :}
115+-- "INSERT INTO tab (col1, col2) VALUES (2, 4), (6, 8) ON CONFLICT DO UPDATE SET col1 = 2 WHERE (col1 = col2) RETURNING (col1 + col2) AS sum;"
61insertInto
116insertInto
62 :: (SListI columns, SListI results, HasTable table schema columns)
117 :: (SListI columns, SListI results, HasTable table schema columns)
63− => Alias table
118+ => Alias table -- ^ table to insert into
64− -> ValuesClause schema params columns
119+ -> ValuesClause schema params columns -- ^ values to insert
65 -> ConflictClause columns params
120 -> ConflictClause columns params
121+ -- ^ what to do in case of constraint conflict
66− -> ReturningClause columns params results
122+ -> ReturningClause columns params results -- ^ results to return
67 -> Manipulation schema params results
123 -> Manipulation schema params results
68insertInto table insert conflict returning = UnsafeManipulation $
124insertInto table insert conflict returning = UnsafeManipulation $
69 "INSERT" <+> "INTO" <+> renderAlias table
125 "INSERT" <+> "INTO" <+> renderAlias table
@@ -71,6 +127,8 @@ insertInto table insert conflict returning = UnsafeManipulation $
71 <> renderConflictClause conflict
127 <> renderConflictClause conflict
72 <> renderReturningClause returning
128 <> renderReturningClause returning
73
129
130+-- | A `ValuesClause` lets you insert either values, free `Expression`s,
131+-- or the result of a `Query`.
74data ValuesClause
132data ValuesClause
75 (schema :: TablesType)
133 (schema :: TablesType)
76 (params :: [ColumnType])
134 (params :: [ColumnType])
@@ -78,8 +136,10 @@ data ValuesClause
78 = Values
136 = Values
79 (NP (Aliased (Expression '[] 'Ungrouped params)) columns)
137 (NP (Aliased (Expression '[] 'Ungrouped params)) columns)
80 [NP (Aliased (Expression '[] 'Ungrouped params)) columns]
138 [NP (Aliased (Expression '[] 'Ungrouped params)) columns]
139+ -- ^ at least one row of values
81 | ValuesQuery (Query schema params columns)
140 | ValuesQuery (Query schema params columns)
82
141
142+-- | Render a `ValuesClause`.
83renderValuesClause
143renderValuesClause
84 :: SListI columns
144 :: SListI columns
85 => ValuesClause schema params columns
145 => ValuesClause schema params columns
@@ -97,6 +157,16 @@ renderValuesClause = \case
97 renderValuePart (value `As` _) = renderExpression value
157 renderValuePart (value `As` _) = renderExpression value
98 ValuesQuery q -> renderQuery q
158 ValuesQuery q -> renderQuery q
99
159
160+-- | A `ReturningClause` computes and return value(s) based
161+-- on each row actually inserted, updated or deleted. This is primarily
162+-- useful for obtaining values that were supplied by defaults, such as a
163+-- serial sequence number. However, any expression using the table's columns
164+-- is allowed. Only rows that were successfully inserted or updated or
165+-- deleted will be returned. For example, if a row was locked
166+-- but not updated because an `OnConflictDoUpdate` condition was not satisfied,
167+-- the row will not be returned. `ReturningStar` will return all columns
168+-- in the row. Use `Returning Nil` in the common case where no return
169+-- values are desired.
100data ReturningClause
170data ReturningClause
101 (columns :: ColumnsType)
171 (columns :: ColumnsType)
102 (params :: [ColumnType])
172 (params :: [ColumnType])
@@ -109,6 +179,7 @@ data ReturningClause
109 results
179 results
110 -> ReturningClause columns params results
180 -> ReturningClause columns params results
111
181
182+-- | Render a `ReturningClause`.
112renderReturningClause
183renderReturningClause
113 :: SListI results
184 :: SListI results
114 => ReturningClause params columns results
185 => ReturningClause params columns results
@@ -119,20 +190,26 @@ renderReturningClause = \case
119 Returning results -> " RETURNING"
190 Returning results -> " RETURNING"
120 <+> renderCommaSeparated (renderAliased renderExpression) results <> ";"
191 <+> renderCommaSeparated (renderAliased renderExpression) results <> ";"
121
192
193+-- | A `ConflictClause` specifies an action to perform upon a constraint
194+-- violation. `OnConflictDoRaise` will raise an error.
195+-- `OnConflictDoNothing` simply avoids inserting a row.
196+-- `OnConflictDoUpdate` updates the existing row that conflicts with the row
197+-- proposed for insertion.
122data ConflictClause columns params where
198data ConflictClause columns params where
123− Conflict :: ConflictClause columns params
199+ OnConflictDoRaise :: ConflictClause columns params
124 OnConflictDoNothing :: ConflictClause columns params
200 OnConflictDoNothing :: ConflictClause columns params
125 OnConflictDoUpdate
201 OnConflictDoUpdate
126 :: NP (Aliased (UpdateExpression columns params)) columns
202 :: NP (Aliased (UpdateExpression columns params)) columns
127 -> Maybe (Condition '[table ::: columns] 'Ungrouped params)
203 -> Maybe (Condition '[table ::: columns] 'Ungrouped params)
128 -> ConflictClause columns params
204 -> ConflictClause columns params
129
205
206+-- | Render a `ConflictClause`.
130renderConflictClause
207renderConflictClause
131 :: SListI columns
208 :: SListI columns
132 => ConflictClause columns params
209 => ConflictClause columns params
133 -> ByteString
210 -> ByteString
134renderConflictClause = \case
211renderConflictClause = \case
135− Conflict -> ""
212+ OnConflictDoRaise -> ""
136 OnConflictDoNothing -> " ON CONFLICT DO NOTHING"
213 OnConflictDoNothing -> " ON CONFLICT DO NOTHING"
137 OnConflictDoUpdate updates whMaybe
214 OnConflictDoUpdate updates whMaybe
138 -> " ON CONFLICT DO UPDATE SET"
215 -> " ON CONFLICT DO UPDATE SET"
@@ -145,13 +222,50 @@ renderConflictClause = \case
145UPDATE statements
222UPDATE statements
146-----------------------------------------}
223-----------------------------------------}
147
224
225+-- | An `update` command changes the values of the specified columns
226+-- in all rows that satisfy the condition.
227+--
228+-- >>> :{
229+-- let
230+-- manipulation :: Manipulation
231+-- '[ "tab" :::
232+-- '[ "col1" ::: 'Required ('NotNull 'PGint4)
233+-- , "col2" ::: 'Required ('NotNull 'PGint4) ]] '[] '[]
234+-- manipulation =
235+-- update #tab (Set 2 `As` #col1 :* Same `As` #col2 :* Nil)
236+-- (#col1 ./= #col2) (Returning Nil)
237+-- in renderManipulation manipulation
238+-- :}
239+-- "UPDATE tab SET col1 = 2 WHERE (col1 <> col2);"
240+update
241+ :: (HasTable table schema columns, SListI columns, SListI results)
242+ => Alias table -- ^ table to update
243+ -> NP (Aliased (UpdateExpression columns params)) columns
244+ -- ^ modified values to replace old values
245+ -> Condition '[tab ::: columns] 'Ungrouped params
246+ -- ^ condition under which to perform update on a row
247+ -> ReturningClause columns params results -- ^ results to return
248+ -> Manipulation schema params results
249+update table columns wh returning = UnsafeManipulation $
250+ "UPDATE"
251+ <+> renderAlias table
252+ <+> "SET"
253+ <+> renderCommaSeparatedMaybe renderUpdateExpression columns
254+ <+> "WHERE" <+> renderExpression wh
255+ <> renderReturningClause returning
256+
257+-- | Columns to be updated are mentioned with `Set`; columns which are to
258+-- remain the same are mentioned with `Same`.
148data UpdateExpression columns params ty
259data UpdateExpression columns params ty
149 = Same
260 = Same
261+ -- ^ column to remain the same upon update
150 | Set (forall table. Expression '[table ::: columns] 'Ungrouped params ty)
262 | Set (forall table. Expression '[table ::: columns] 'Ungrouped params ty)
263+ -- ^ column to be updated
151deriving instance Show (UpdateExpression columns params ty)
264deriving instance Show (UpdateExpression columns params ty)
152deriving instance Eq (UpdateExpression columns params ty)
265deriving instance Eq (UpdateExpression columns params ty)
153deriving instance Ord (UpdateExpression columns params ty)
266deriving instance Ord (UpdateExpression columns params ty)
154
267
268+-- | Render an `UpdateExpression`.
155renderUpdateExpression
269renderUpdateExpression
156 :: Aliased (UpdateExpression params columns) column
270 :: Aliased (UpdateExpression params columns) column
157 -> Maybe ByteString
271 -> Maybe ByteString
@@ -160,30 +274,32 @@ renderUpdateExpression = \case
160 Set expression `As` column -> Just $
274 Set expression `As` column -> Just $
161 renderAlias column <+> "=" <+> renderExpression expression
275 renderAlias column <+> "=" <+> renderExpression expression
162
276
163−update
164− :: (HasTable table schema columns, SListI columns, SListI results)
165− => Alias table
166− -> NP (Aliased (UpdateExpression columns params)) columns
167− -> Condition '[tab ::: columns] 'Ungrouped params
168− -> ReturningClause columns params results
169− -> Manipulation schema params results
170−update table columns wh returning = UnsafeManipulation $
171− "UPDATE"
172− <+> renderAlias table
173− <+> "SET"
174− <+> renderCommaSeparatedMaybe renderUpdateExpression columns
175− <+> "WHERE" <+> renderExpression wh
176− <> renderReturningClause returning
177−
178{-----------------------------------------
277{-----------------------------------------
179DELETE statements
278DELETE statements
180-----------------------------------------}
279-----------------------------------------}
181
280
281+-- | Delete rows of a table.
282+--
283+-- >>> :{
284+-- let
285+-- manipulation :: Manipulation
286+-- '[ "tab" :::
287+-- '[ "col1" ::: 'Required ('NotNull 'PGint4)
288+-- , "col2" ::: 'Required ('NotNull 'PGint4) ]] '[]
289+-- '[ "col1" ::: 'Required ('NotNull 'PGint4)
290+-- , "col2" ::: 'Required ('NotNull 'PGint4) ]
291+-- manipulation = deleteFrom #tab (#col1 .== #col2) ReturningStar
292+-- in renderManipulation manipulation
293+-- :}
294+-- "DELETE FROM tab WHERE (col1 = col2) RETURNING *;"
182deleteFrom
295deleteFrom
183− :: HasTable table schema columns
296+ :: (SListI results, HasTable table schema columns)
184− => Alias table
297+ => Alias table -- ^ table to delete from
185 -> Condition '[table ::: columns] 'Ungrouped params
298 -> Condition '[table ::: columns] 'Ungrouped params
299+ -- ^ condition under which to delete a row
300+ -> ReturningClause columns params results -- ^ results to return
186− -> Manipulation schema params '[]
301+ -> Manipulation schema params results
187−deleteFrom table wh = UnsafeManipulation $
302+deleteFrom table wh returning = UnsafeManipulation $
188 "DELETE FROM" <+> renderAlias table
303 "DELETE FROM" <+> renderAlias table
189− <+> "WHERE" <+> renderExpression wh <> ";"
304+ <+> "WHERE" <+> renderExpression wh
305+ <> renderReturningClause returning
squeal-postgresql/test/Squeal/PostgreSQL/ManipulationSpec.hs+3−3
@@ -24,7 +24,7 @@ spec = do
24 statement :: Manipulation Tables '[] SumAndCol1
24 statement :: Manipulation Tables '[] SumAndCol1
25 statement =
25 statement =
26 insertInto #table1 (Values (2 `As` #col1 :* 4 `As` #col2 :* Nil) [])
26 insertInto #table1 (Values (2 `As` #col1 :* 4 `As` #col2 :* Nil) [])
27− Conflict
27+ OnConflictDoRaise
28 (Returning $ (#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
28 (Returning $ (#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
29 statement `manipulationRenders`
29 statement `manipulationRenders`
30 "INSERT INTO table1 (col1, col2) VALUES (2, 4)\
30 "INSERT INTO table1 (col1, col2) VALUES (2, 4)\
@@ -79,7 +79,7 @@ spec = do
79 it "correctly renders DELETEs" $ do
79 it "correctly renders DELETEs" $ do
80 let
80 let
81 statement :: Manipulation Tables '[] '[]
81 statement :: Manipulation Tables '[] '[]
82− statement = deleteFrom #table1 (#col1 .== #col2)
82+ statement = deleteFrom #table1 (#col1 .== #col2) (Returning Nil)
83 statement `manipulationRenders`
83 statement `manipulationRenders`
84 "DELETE FROM table1 WHERE (col1 = col2);"
84 "DELETE FROM table1 WHERE (col1 = col2);"
85 it "should be safe against SQL injection in literal text" $ do
85 it "should be safe against SQL injection in literal text" $ do
@@ -87,7 +87,7 @@ spec = do
87 statement :: Manipulation StudentsTable '[] '[]
87 statement :: Manipulation StudentsTable '[] '[]
88 statement = insertInto #students
88 statement = insertInto #students
89 (Values ("Robert'); DROP TABLE students;" `As` #name :* Nil) [])
89 (Values ("Robert'); DROP TABLE students;" `As` #name :* Nil) [])
90− Conflict (Returning Nil)
90+ OnConflictDoRaise (Returning Nil)
91 statement `manipulationRenders`
91 statement `manipulationRenders`
92 "INSERT INTO students (name) VALUES (E'Robert''); DROP TABLE students;');"
92 "INSERT INTO students (name) VALUES (E'Robert''); DROP TABLE students;');"
93
93