on dev help sign in
Merge pull request #78 from morphismtech/dev-refactor-manipulations

Refactor manipulations

commit16e8cbe20d0a63ed7b9215dbe5cae1bf4e563661 authorEitan Chatav <eitan.chatav@gmail.com> dateThu 6 Dec 2018, 09:20 (8 years ago) parent33d1550136b084
11 files changed+344−263
squeal-postgresql/exe/Example.hs+6−8 squeal-postgresql/src/Squeal/PostgreSQL.hs+11−21 squeal-postgresql/src/Squeal/PostgreSQL/Definition.hs+1−1 squeal-postgresql/src/Squeal/PostgreSQL/Expression.hs+27−27 squeal-postgresql/src/Squeal/PostgreSQL/Manipulation.hs+260−195 squeal-postgresql/src/Squeal/PostgreSQL/Migration.hs+2−3 squeal-postgresql/src/Squeal/PostgreSQL/PQ.hs+4 squeal-postgresql/src/Squeal/PostgreSQL/Render.hs+1−2 squeal-postgresql/src/Squeal/PostgreSQL/Schema.hs+26−2 squeal-postgresql/test/Specs/ExceptionHandling.hs+6−4 squeal-presentation-2018-09-12.md → squeal-presentation-raveline.md
squeal-postgresql/exe/Example.hs+6−8
@@ -66,16 +66,14 @@ teardown = dropTable #emails >>> dropTable #users
6666
6767insertUser :: Manipulation DB '[ 'NotNull 'PGtext, 'NotNull ('PGvararray ('Null 'PGint2))]
6868 '[ "fromOnly" ::: 'NotNull 'PGint4 ]
69−insertUser = insertRows #users
70− (Default `as` #id :* Set (param @1) `as` #name :* Set (param @2) `as` #vec) []
71− OnConflictDoNothing (Returning (#id `as` #fromOnly))
69+insertUser = insertInto #users
70+ (Values_ (defaultAs #id :* param @1 `as` #name :* param @2 `as` #vec))
71+ (OnConflict (OnConstraint #pk_users) DoNothing) (Returning (#id `as` #fromOnly))
7272
7373insertEmail :: Manipulation DB '[ 'NotNull 'PGint4, 'Null 'PGtext] '[]
74−insertEmail = insertRows #emails
75− ( Default `as` #id :*
76− Set (param @1) `as` #user_id :*
77− Set (param @2) `as` #email ) []
78− OnConflictDoNothing (Returning Nil)
74+insertEmail = insertInto #emails
75+ (Values_ (defaultAs #id :* param @1 `as` #user_id :* param @2 `as` #email))
76+ (OnConflict (OnConstraint #pk_emails) DoNothing) (Returning Nil)
7977
8078getUsers :: Query DB '[]
8179 '[ "userName" ::: 'NotNull 'PGtext
squeal-postgresql/src/Squeal/PostgreSQL.hs+11−21
@@ -12,7 +12,7 @@ First, we need some language extensions because Squeal uses modern GHC
1212features.
1313
1414>>> :set -XDataKinds -XDeriveGeneric -XOverloadedLabels
15−>>> :set -XOverloadedStrings -XTypeApplications -XTypeOperators
15+>>> :set -XOverloadedStrings -XTypeApplications -XTypeOperators -XGADTs
1616
1717We'll need some imports.
1818
@@ -118,26 +118,18 @@ of our inserts.
118118
119119>>> :{
120120let
121− insertUser :: Manipulation DB '[ 'NotNull 'PGtext ] '[ "fromOnly" ::: 'NotNull 'PGint4 ]
122− insertUser = insertRow #users
123− (Default `as` #id :* Set (param @1) `as` #name)
124− OnConflictDoNothing (Returning (#id `as` #fromOnly))
125−:}
126−
127−>>> :{
128−let
129− insertEmail :: Manipulation DB '[ 'NotNull 'PGint4, 'Null 'PGtext] '[]
130− insertEmail = insertRow #emails
131− ( Default `as` #id :*
132− Set (param @1) `as` #user_id :*
133− Set (param @2) `as` #email )
134− OnConflictDoNothing (Returning Nil)
121+ insertUser :: Manipulation DB '[ 'NotNull 'PGtext, 'Null 'PGtext ] '[]
122+ insertUser = with (u `as` #u) e
123+ where
124+ u = insertInto #users
125+ (Values_ (defaultAs #id :* param @1 `as` #name))
126+ OnConflictDoRaise (Returning (#id :* param @2 `as` #email))
127+ e = insertInto_ #emails
128+ (Select (defaultAs #id :* #u ! #id `as` #user_id :* #u ! #email) (from (common #u)))
135129:}
136130
137131>>> printSQL insertUser
138−INSERT INTO "users" ("id", "name") VALUES (DEFAULT, ($1 :: text)) ON CONFLICT DO NOTHING RETURNING "id" AS "fromOnly"
139−>>> printSQL insertEmail
140−INSERT INTO "emails" ("id", "user_id", "email") VALUES (DEFAULT, ($1 :: int4), ($2 :: text)) ON CONFLICT DO NOTHING
132+WITH "u" AS (INSERT INTO "users" ("id", "name") VALUES (DEFAULT, ($1 :: text)) RETURNING "id" AS "id", ($2 :: text) AS "email") INSERT INTO "emails" ("user_id", "email") SELECT "u"."id", "u"."email" FROM "u" AS "u"
141133
142134Next we write a `Query` to retrieve users from the database. We're not
143135interested in the ids here, just the usernames and email addresses. We
@@ -193,9 +185,7 @@ transformer and when the schema doesn't change we can use `Monad` and
193185let
194186 session :: PQ Schemas Schemas IO ()
195187 session = do
196− idResults <- traversePrepared insertUser (Only . userName <$> users)
197− ids <- traverse (fmap fromOnly . getRow 0) idResults
198− traversePrepared_ insertEmail (zip (ids :: [Int32]) (userEmail <$> users))
188+ _ <- traversePrepared_ insertUser users
199189 usersResult <- runQuery getUsers
200190 usersRows <- getRows usersResult
201191 liftBase $ print (usersRows :: [User])
squeal-postgresql/src/Squeal/PostgreSQL/Definition.hs+1−1
@@ -732,7 +732,7 @@ alterColumn
732732 , Has tab schema ('Table table0)
733733 , table0 ~ (constraints :=> columns)
734734 , Has column columns ty0
735− , tables1 ~ (constraints :=> Alter column ty1 columns))
735+ , table1 ~ (constraints :=> Alter column ty1 columns))
736736 => Alias column -- ^ column to alter
737737 -> AlterColumn schemas ty0 ty1 -- ^ alteration to perform
738738 -> AlterTable sch tab schemas table1
squeal-postgresql/src/Squeal/PostgreSQL/Expression.hs+27−27
@@ -269,30 +269,30 @@ param
269269 => Expression db from grouping params ty -- ^ param
270270param = parameter @n (pgtype @schemas)
271271
272−instance (HasUnique table from columns, Has column columns ty)
273− => IsLabel column (Expression db from 'Ungrouped params ty) where
274− fromLabel = UnsafeExpression $ renderAlias (Alias @column)
275−instance (HasUnique table from columns, Has column columns ty)
276− => IsLabel column
277− (Aliased (Expression db from 'Ungrouped params) (column ::: ty)) where
278− fromLabel = fromLabel @column `As` Alias @column
279−instance (HasUnique table from columns, Has column columns ty)
280− => IsLabel column
281− (NP (Aliased (Expression db from 'Ungrouped params)) '[column ::: ty]) where
282− fromLabel = fromLabel @column :* Nil
283−
284−instance (Has table from columns, Has column columns ty)
285− => IsQualified table column (Expression db from 'Ungrouped params ty) where
286− table ! column = UnsafeExpression $
287− renderAlias table <> "." <> renderAlias column
288−instance (Has table from columns, Has column columns ty)
289− => IsQualified table column
290− (Aliased (Expression db from 'Ungrouped params) (column ::: ty)) where
291− table ! column = table ! column `As` column
292−instance (Has table from columns, Has column columns ty)
293− => IsQualified table column
294− (NP (Aliased (Expression db from 'Ungrouped params)) '[column ::: ty]) where
295− table ! column = table ! column :* Nil
272+instance (HasUnique tab from row, Has col row ty)
273+ => IsLabel col (Expression db from 'Ungrouped params ty) where
274+ fromLabel = UnsafeExpression $ renderAlias (Alias @col)
275+instance (HasUnique tab from row, Has col row ty, column ~ (col ::: ty))
276+ => IsLabel col
277+ (Aliased (Expression db from 'Ungrouped params) column) where
278+ fromLabel = fromLabel @col `As` Alias
279+instance (HasUnique tab from row, Has col row ty, columns ~ '[col ::: ty])
280+ => IsLabel col
281+ (NP (Aliased (Expression db from 'Ungrouped params)) columns) where
282+ fromLabel = fromLabel @col :* Nil
283+
284+instance (Has tab from row, Has col row ty)
285+ => IsQualified tab col (Expression db from 'Ungrouped params ty) where
286+ tab ! col = UnsafeExpression $
287+ renderAlias tab <> "." <> renderAlias col
288+instance (Has tab from row, Has col row ty, column ~ (col ::: ty))
289+ => IsQualified tab col
290+ (Aliased (Expression db from 'Ungrouped params) column) where
291+ tab ! col = tab ! col `As` col
292+instance (Has tab from row, Has col row ty, columns ~ '[col ::: ty])
293+ => IsQualified tab col
294+ (NP (Aliased (Expression db from 'Ungrouped params)) columns) where
295+ tab ! col = tab ! col :* Nil
296296
297297instance
298298 ( HasUnique table from columns
@@ -965,11 +965,11 @@ like = unsafeBinaryOp "LIKE"
965965-- >>> printSQL $ "abc" `ilike` "a%"
966966-- (E'abc' ILIKE E'a%')
967967ilike
968− :: Expression schema from grouping params (nullity 'PGtext)
968+ :: Expression db from grouping params (nullity 'PGtext)
969969 -- ^ string
970− -> Expression schema from grouping params (nullity 'PGtext)
970+ -> Expression db from grouping params (nullity 'PGtext)
971971 -- ^ pattern
972− -> Expression schema from grouping params (nullity 'PGbool)
972+ -> Expression db from grouping params (nullity 'PGbool)
973973ilike = unsafeBinaryOp "ILIKE"
974974
975975{-----------------------------------------
squeal-postgresql/src/Squeal/PostgreSQL/Manipulation.hs+260−195
@@ -11,32 +11,41 @@ Squeal data manipulation language.
1111{-# LANGUAGE
1212 DeriveGeneric
1313 , FlexibleContexts
14+ , FlexibleInstances
1415 , GADTs
1516 , GeneralizedNewtypeDeriving
1617 , LambdaCase
18+ , MultiParamTypeClasses
1719 , OverloadedStrings
20+ , PatternSynonyms
1821 , RankNTypes
22+ , ScopedTypeVariables
23+ , TypeApplications
1924 , TypeInType
2025 , TypeOperators
26+ , UndecidableInstances
2127#-}
2228
2329module Squeal.PostgreSQL.Manipulation
2430 ( -- * Manipulation
25− Manipulation (UnsafeManipulation, renderManipulation)
31+ Manipulation (..)
2632 , queryStatement
27− , ColumnValue (..)
28− , ReturningClause (ReturningStar, Returning)
29− , ConflictClause (OnConflictDoRaise, OnConflictDoNothing, OnConflictDoUpdate)
33+ , QueryClause (..)
34+ , pattern Values_
35+ , DefaultAliasable (..)
36+ , ColumnExpression (..)
37+ , ReturningClause (..)
38+ , ConflictClause (..)
39+ , ConflictTarget (..)
40+ , ConflictAction (..)
3041 , UsingClause (..)
3142 -- * Insert
32− , insertRows
33− , insertRow
34− , insertRows_
35− , insertRow_
36− , insertQuery
37− , insertQuery_
43+ , insertInto
44+ , insertInto_
3845 , renderReturningClause
3946 , renderConflictClause
47+ , renderConflictTarget
48+ , renderConflictAction
4049 -- * Update
4150 , update
4251 , update_
@@ -48,6 +57,7 @@ module Squeal.PostgreSQL.Manipulation
4857
4958import Control.DeepSeq
5059import Data.ByteString hiding (foldr)
60+import GHC.TypeLits (KnownSymbol)
5161
5262import qualified Generics.SOP as SOP
5363import qualified GHC.Generics as GHC
@@ -70,7 +80,7 @@ simple insert:
7080let
7181 manipulation :: Manipulation (DBof (Public Schema)) '[] '[]
7282 manipulation =
73− insertRow_ #tab (Set 2 `as` #col1 :* Default `as` #col2)
83+ insertInto_ #tab (Values_ (2 `as` #col1 :* defaultAs #col2))
7484in printSQL manipulation
7585:}
7686INSERT INTO "tab" ("col1", "col2") VALUES (2, DEFAULT)
@@ -83,8 +93,7 @@ parameterized insert:
8393let
8494 manipulation :: Manipulation (DBof (Public Schema)) '[ 'NotNull 'PGint4, 'NotNull 'PGint4 ] '[]
8595 manipulation =
86− insertRow_ #tab
87− (Set (param @1) `as` #col1 :* Set (param @2) `as` #col2)
96+ insertInto_ #tab (Values_ (param @1 `as` #col1 :* param @2 `as` #col2))
8897in printSQL manipulation
8998:}
9099INSERT INTO "tab" ("col1", "col2") VALUES (($1 :: int4), ($2 :: int4))
@@ -95,7 +104,7 @@ returning insert:
95104let
96105 manipulation :: Manipulation (DBof (Public Schema)) '[] '["fromOnly" ::: 'NotNull 'PGint4]
97106 manipulation =
98− insertRow #tab (Set 2 `as` #col1 :* Set 3 `as` #col2)
107+ insertInto #tab (Values_ (2 `as` #col1 :* 3 `as` #col2))
99108 OnConflictDoRaise (Returning (#col1 `as` #fromOnly))
100109in printSQL manipulation
101110:}
@@ -103,29 +112,28 @@ INSERT INTO "tab" ("col1", "col2") VALUES (2, 3) RETURNING "col1" AS "fromOnly"
103112
104113upsert:
105114
106−>>> type Columns = '["col1" ::: 'NoDef :=> 'NotNull 'PGint4, "col2" ::: 'NoDef :=> 'NotNull 'PGint4]
107−>>> type Schema = '["tab" ::: 'Table ('[] :=> Columns)]
115+>>> type CustomersColumns = '["name" ::: 'NoDef :=> 'NotNull 'PGtext, "email" ::: 'NoDef :=> 'NotNull 'PGtext]
116+>>> type CustomersConstraints = '["uq" ::: 'Unique '["name"]]
117+>>> type CustomersSchema = '["customers" ::: 'Table (CustomersConstraints :=> CustomersColumns)]
108118>>> :{
109119let
110− manipulation :: Manipulation (DBof (Public Schema)) '[] '[ "sum" ::: 'NotNull 'PGint4]
120+ manipulation :: Manipulation (DBof (Public CustomersSchema)) '[] '[]
111121 manipulation =
112− insertRows #tab
113− (Set 2 `as` #col1 :* Set 4 `as` #col2)
114− [Set 6 `as` #col1 :* Set 8 `as` #col2]
115− (OnConflictDoUpdate
116− (Set 2 `as` #col1 :* Same `as` #col2)
117− [#col1 .== #col2])
118− (Returning $ (#col1 + #col2) `as` #sum)
122+ insertInto #customers
123+ (Values_ ("John Smith" `as` #name :* "john@smith.com" `as` #email))
124+ (OnConflict (OnConstraint #uq)
125+ (DoUpdate (((#excluded ! #email) <> "; " <> (#customers ! #email)) `as` #email) []))
126+ (Returning Nil)
119127in printSQL manipulation
120128:}
121−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"
129+INSERT INTO "customers" ("name", "email") VALUES (E'John Smith', E'john@smith.com') ON CONFLICT ON CONSTRAINT "uq" DO UPDATE SET "email" = ("excluded"."email" || (E'; ' || "customers"."email"))
122130
123131query insert:
124132
125133>>> :{
126134let
127135 manipulation :: Manipulation (DBof (Public Schema)) '[] '[]
128− manipulation = insertQuery_ #tab (selectStar (from (table #tab)))
136+ manipulation = insertInto_ #tab (Subquery (selectStar (from (table #tab))))
129137in printSQL manipulation
130138:}
131139INSERT INTO "tab" SELECT * FROM "tab" AS "tab"
@@ -135,9 +143,7 @@ update:
135143>>> :{
136144let
137145 manipulation :: Manipulation (DBof (Public Schema)) '[] '[]
138− manipulation =
139− update_ #tab (Set 2 `as` #col1 :* Same `as` #col2)
140− (#col1 ./= #col2)
146+ manipulation = update_ #tab (2 `as` #col1) (#col1 ./= #col2)
141147in printSQL manipulation
142148:}
143149UPDATE "tab" SET "col1" = 2 WHERE ("col1" <> "col2")
@@ -183,7 +189,7 @@ let
183189 manipulation :: Manipulation (DBof (Public ProductsSchema)) '[ 'NotNull 'PGdate] '[]
184190 manipulation = with
185191 (deleteFrom #products NoUsing (#date .< param @1) ReturningStar `as` #del)
186− (insertQuery_ #products_deleted (selectStar (from (common (#del)))))
192+ (insertInto_ #products_deleted (Subquery (selectStar (from (common #del)))))
187193in printSQL manipulation
188194:}
189195WITH "del" AS (DELETE FROM "products" WHERE ("date" < ($1 :: date)) RETURNING *) INSERT INTO "products_deleted" SELECT * FROM "del" AS "del"
@@ -213,138 +219,184 @@ queryStatement q = UnsafeManipulation $ renderQuery q
213219INSERT statements
214220-----------------------------------------}
215221
216−-- | Insert multiple rows.
217−--
218−-- When a table is created, it contains no data. The first thing to do
219−-- before a database can be of much use is to insert data. Data is
220−-- conceptually inserted one row at a time. Of course you can also insert
221−-- more than one row, but there is no way to insert less than one row.
222−-- Even if you know only some column values, a complete row must be created.
223−insertRows
224− :: ( SOP.SListI columns
225− , SOP.SListI results
226− , db ~ (commons :=> schemas)
222+{- |
223+When a table is created, it contains no data. The first thing to do
224+before a database can be of much use is to insert data. Data is
225+conceptually inserted one row at a time. Of course you can also insert
226+more than one row, but there is no way to insert less than one row.
227+Even if you know only some column values, a complete row must be created.
228+-}
229+insertInto
230+ :: ( db ~ (commons :=> schemas)
227231 , Has sch schemas schema
228232 , Has tab schema ('Table table)
233+ , columns ~ TableToColumns table
229234 , row ~ TableToRow table
230− , columns ~ TableToColumns table )
231− => QualifiedAlias sch tab -- ^ table to insert into
232− -> NP (Aliased (ColumnValue db '[] params)) columns -- ^ row to insert
233− -> [NP (Aliased (ColumnValue db '[] params)) columns] -- ^ more rows to insert
234− -> ConflictClause db table params
235− -- ^ what to do in case of constraint conflict
236− -> ReturningClause db params row results -- ^ results to return
237− -> Manipulation db params results
238−insertRows tab rw rws conflict returning = UnsafeManipulation $
235+ , SOP.SListI columns
236+ , SOP.SListI result )
237+ => QualifiedAlias sch tab
238+ -> QueryClause db params columns
239+ -> ConflictClause tab db params table
240+ -> ReturningClause db params row result
241+ -> Manipulation db params result
242+insertInto tab qry conflict ret = UnsafeManipulation $
239243 "INSERT" <+> "INTO" <+> renderQualifiedAlias tab
240− <+> parenthesized (renderCommaSeparated renderAliasPart rw)
241− <+> "VALUES"
242− <+> commaSeparated
243− ( parenthesized
244− . renderCommaSeparated renderColumnValuePart <$> rw:rws )
245− <> renderConflictClause conflict
246− <> renderReturningClause returning
247− where
248− renderAliasPart, renderColumnValuePart
249− :: Aliased (ColumnValue db '[] params) ty -> ByteString
250− renderAliasPart (_ `As` name) = renderAlias name
251− renderColumnValuePart (value `As` _) = case value of
252− Default -> "DEFAULT"
253− Set expression -> renderExpression expression
254−
255−-- | Insert a single row.
256−insertRow
257− :: ( SOP.SListI columns
258− , SOP.SListI results
259− , db ~ (commons :=> schemas)
260− , Has sch schemas schema
261− , Has tab schema ('Table table)
262− , row ~ TableToRow table
263− , columns ~ TableToColumns table )
264− => QualifiedAlias sch tab -- ^ table to insert into
265− -> NP (Aliased (ColumnValue db '[] params)) columns -- ^ row to insert
266− -> ConflictClause db table params
267− -- ^ what to do in case of constraint conflict
268− -> ReturningClause db params row results -- ^ results to return
269− -> Manipulation db params results
270−insertRow tab rw = insertRows tab rw []
244+ <+> renderQueryClause qry
245+ <> renderConflictClause conflict
246+ <> renderReturningClause ret
271247
272−-- | Insert multiple rows returning `Nil` and raising an error on conflicts.
273−insertRows_
274− :: ( SOP.SListI columns
275− , db ~ (commons :=> schemas)
276− , Has sch schemas schema
277− , Has tab schema ('Table table)
278− , columns ~ TableToColumns table )
279− => QualifiedAlias sch tab -- ^ table to insert into
280− -> NP (Aliased (ColumnValue db '[] params)) columns -- ^ row to insert
281− -> [NP (Aliased (ColumnValue db '[] params)) columns] -- ^ more rows to insert
282− -> Manipulation db params '[]
283−insertRows_ tab rw rws =
284− insertRows tab rw rws OnConflictDoRaise (Returning Nil)
285−
286−-- | Insert a single row returning `Nil` and raising an error on conflicts.
287−insertRow_
288− :: ( SOP.SListI columns
289− , db ~ (commons :=> schemas)
290− , Has sch schemas schema
291− , Has tab schema ('Table table)
292− , columns ~ TableToColumns table )
293− => QualifiedAlias sch tab -- ^ table to insert into
294− -> NP (Aliased (ColumnValue db '[] params)) columns -- ^ row to insert
295− -> Manipulation db params '[]
296−insertRow_ tab rw = insertRow tab rw OnConflictDoRaise (Returning Nil)
297−
298−-- | Insert a `Query`.
299−insertQuery
300− :: ( SOP.SListI columns
301− , SOP.SListI results
302− , db ~ (commons :=> schemas)
248+insertInto_
249+ :: ( db ~ (commons :=> schemas)
303250 , Has sch schemas schema
304251 , Has tab schema ('Table table)
252+ , columns ~ TableToColumns table
305253 , row ~ TableToRow table
306− , columns ~ TableToColumns table )
307− => QualifiedAlias sch tab -- ^ table to insert into
308− -> Query db params (TableToRow table)
309− -> ConflictClause db table params
310− -- ^ what to do in case of constraint conflict
311− -> ReturningClause db params row results -- ^ results to return
312− -> Manipulation db params results
313−insertQuery tab query conflict returning = UnsafeManipulation $
314− "INSERT" <+> "INTO" <+> renderQualifiedAlias tab
315− <+> renderQuery query
316− <> renderConflictClause conflict
317− <> renderReturningClause returning
318−
319−-- | Insert a `Query` returning `Nil` and raising an error on conflicts.
320−insertQuery_
321− :: ( SOP.SListI columns
322− , db ~ (commons :=> schemas)
323− , Has sch schemas schema
324− , Has tab schema ('Table table)
325− , columns ~ TableToColumns table )
326− => QualifiedAlias sch tab -- ^ table to insert into
327− -> Query db params (TableToRow table)
254+ , SOP.SListI columns )
255+ => QualifiedAlias sch tab
256+ -> QueryClause db params columns
328257 -> Manipulation db params '[]
329−insertQuery_ tab query =
330− insertQuery tab query OnConflictDoRaise (Returning Nil)
331−
332−-- | `ColumnValue`s are values to insert or update in a row.
333−-- `Same` updates with the same value.
334−-- `Default` inserts or updates with the @DEFAULT@ value.
335−-- `Set` sets a value to be an `Expression`, which can refer to
336−-- existing value in the row for an update.
337−data ColumnValue
338− (db :: DBType)
339− (columns :: RowType)
340− (params :: [NullityType])
341− (ty :: ColumnType)
258+insertInto_ tab qry =
259+ insertInto tab qry OnConflictDoRaise (Returning Nil)
260+
261+data QueryClause db params columns where
262+ Values
263+ :: SOP.SListI columns
264+ => NP (ColumnExpression db '[] 'Ungrouped params) columns
265+ -> [NP (ColumnExpression db '[] 'Ungrouped params) columns]
266+ -> QueryClause db params columns
267+ Select
268+ :: SOP.SListI columns
269+ => NP (ColumnExpression db from grp params) columns
270+ -> TableExpression db params from grp
271+ -> QueryClause db params columns
272+ Subquery
273+ :: ColumnsToRow columns ~ row
274+ => Query db params row
275+ -> QueryClause db params columns
276+
277+renderQueryClause
278+ :: QueryClause db params columns
279+ -> ByteString
280+renderQueryClause = \case
281+ Values row0 rows ->
282+ parenthesized (renderCommaSeparated renderAliasPart row0)
283+ <+> "VALUES"
284+ <+> commaSeparated
285+ ( parenthesized
286+ . renderCommaSeparated renderValuePart <$> row0 : rows )
287+ Select row0 tab ->
288+ parenthesized (renderCommaSeparatedMaybe renderAliasPartMaybe row0)
289+ <+> "SELECT"
290+ <+> renderCommaSeparatedMaybe renderValuePartMaybe row0
291+ <+> renderTableExpression tab
292+ Subquery qry -> renderQuery qry
342293 where
343− Same :: ColumnValue db (column ': columns) params ty
344− Default :: ColumnValue db columns params ('Def :=> ty)
345− Set
346− :: (forall table. Expression db '[table ::: columns] 'Ungrouped params ty)
347− -> ColumnValue db columns params (constraint :=> ty)
294+ renderAliasPartMaybe, renderValuePartMaybe
295+ :: ColumnExpression db from grp params column -> Maybe ByteString
296+ renderAliasPartMaybe = \case
297+ DefaultAs _ -> Nothing
298+ Specific (_ `As` name) -> Just $ renderAlias name
299+ renderValuePartMaybe = \case
300+ DefaultAs _ -> Nothing
301+ Specific (value `As` _) -> Just $ renderExpression value
302+ renderAliasPart, renderValuePart
303+ :: ColumnExpression db from grp params column -> ByteString
304+ renderAliasPart = \case
305+ DefaultAs name -> renderAlias name
306+ Specific (_ `As` name) -> renderAlias name
307+ renderValuePart = \case
308+ DefaultAs _ -> "DEFAULT"
309+ Specific (value `As` _) -> renderExpression value
310+
311+pattern Values_
312+ :: SOP.SListI columns
313+ => NP (ColumnExpression db '[] 'Ungrouped params) columns
314+ -> QueryClause db params columns
315+pattern Values_ vals = Values vals []
316+
317+data ColumnExpression db from grp params column where
318+ DefaultAs
319+ :: KnownSymbol col
320+ => Alias col
321+ -> ColumnExpression db from grp params (col ::: 'Def :=> ty)
322+ Specific
323+ :: Aliased (Expression db from grp params) (col ::: ty)
324+ -> ColumnExpression db from grp params (col ::: defness :=> ty)
325+
326+instance (KnownSymbol col, column ~ (col ::: defness :=> ty))
327+ => Aliasable col
328+ (Expression db from grp params ty)
329+ (ColumnExpression db from grp params column) where
330+ expression `as` col = Specific (expression `As` col)
331+instance (KnownSymbol col, columns ~ '[col ::: defness :=> ty])
332+ => Aliasable col
333+ (Expression db from grp params ty)
334+ (NP (ColumnExpression db from grp params) columns) where
335+ expression `as` col = expression `as` col :* Nil
336+instance (KnownSymbol col, column ~ (col ::: 'Def :=> ty))
337+ => DefaultAliasable col (ColumnExpression db from grp params column) where
338+ defaultAs = DefaultAs
339+instance (KnownSymbol col, columns ~ '[col ::: 'Def :=> ty])
340+ => DefaultAliasable col (NP (ColumnExpression db from grp params) columns) where
341+ defaultAs col = defaultAs col :* Nil
342+
343+instance (HasUnique tab from row, Has col row ty, colty ~ (col ::: defness :=> ty))
344+ => IsLabel col (ColumnExpression db from 'Ungrouped params colty) where
345+ fromLabel = fromLabel @col `as` Alias
346+instance (HasUnique tab from row, Has col row ty, coltys ~ '[col ::: defness :=> ty])
347+ => IsLabel col
348+ (NP (ColumnExpression db from 'Ungrouped params) coltys) where
349+ fromLabel = fromLabel @col `as` Alias
350+
351+instance (Has tab from row, Has col row ty, colty ~ (col ::: defness :=> ty))
352+ => IsQualified tab col (ColumnExpression db from 'Ungrouped params colty) where
353+ tab ! col = tab ! col `as` col
354+instance (Has tab from row, Has col row ty, coltys ~ '[col ::: defness :=> ty])
355+ => IsQualified tab col (NP (ColumnExpression db from 'Ungrouped params) coltys) where
356+ tab ! col = tab ! col `as` col
357+
358+instance
359+ ( HasUnique tab from row
360+ , Has col row ty
361+ , GroupedBy tab col bys
362+ , colty ~ (col ::: defness :=> ty)
363+ ) => IsLabel col
364+ (ColumnExpression db from ('Grouped bys) params colty) where
365+ fromLabel = fromLabel @col `as` Alias
366+instance
367+ ( HasUnique tab from row
368+ , Has col row ty
369+ , GroupedBy tab col bys
370+ , coltys ~ '[col ::: defness :=> ty]
371+ ) => IsLabel col
372+ (NP (ColumnExpression db from ('Grouped bys) params) coltys) where
373+ fromLabel = fromLabel @col `as` Alias
374+
375+instance
376+ ( Has tab from row
377+ , Has col row ty
378+ , GroupedBy tab col bys
379+ , colty ~ (col ::: defness :=> ty)
380+ ) => IsQualified tab col
381+ (ColumnExpression db from ('Grouped bys) params colty) where
382+ tab ! col = tab ! col `as` col
383+instance
384+ ( Has tab from row
385+ , Has col row ty
386+ , GroupedBy tab col bys
387+ , coltys ~ '[col ::: defness :=> ty]
388+ ) => IsQualified tab col
389+ (NP (ColumnExpression db from ('Grouped bys) params) coltys) where
390+ tab ! col = tab ! col :* Nil
391+
392+renderColumnExpression
393+ :: ColumnExpression db from grp params column
394+ -> ByteString
395+renderColumnExpression = \case
396+ DefaultAs col ->
397+ renderAlias col <+> "=" <+> "DEFAULT"
398+ Specific (expression `As` col) ->
399+ renderAlias col <+> "=" <+> renderExpression expression
348400
349401-- | A `ReturningClause` computes and return value(s) based
350402-- on each row actually inserted, updated or deleted. This is primarily
@@ -384,42 +436,61 @@ renderReturningClause = \case
384436-- `OnConflictDoNothing` simply avoids inserting a row.
385437-- `OnConflictDoUpdate` updates the existing row that conflicts with the row
386438-- proposed for insertion.
387−data ConflictClause
388− (db :: DBType)
389− (table :: TableType)
390− (params :: [NullityType]) where
391− OnConflictDoRaise :: ConflictClause db table params
392− OnConflictDoNothing :: ConflictClause db table params
393− OnConflictDoUpdate
394− :: (row ~ TableToRow table, columns ~ TableToColumns table)
395− => NP (Aliased (ColumnValue db row params)) columns
396− -> [Condition db '[t ::: row] 'Ungrouped params]
397− -> ConflictClause db table params
439+data ConflictClause tab db params table where
440+ OnConflictDoRaise :: ConflictClause tab db params table
441+ OnConflict
442+ :: ConflictTarget constraints
443+ -> ConflictAction tab db params columns
444+ -> ConflictClause tab db params (constraints :=> columns)
398445
399446-- | Render a `ConflictClause`.
400447renderConflictClause
401448 :: SOP.SListI (TableToColumns table)
402− => ConflictClause db table params
449+ => ConflictClause tab db params table
403450 -> ByteString
404451renderConflictClause = \case
405452 OnConflictDoRaise -> ""
406− OnConflictDoNothing -> " ON CONFLICT DO NOTHING"
407− OnConflictDoUpdate updates whs'
408− -> " ON CONFLICT DO UPDATE SET"
409− <+> renderCommaSeparatedMaybe renderUpdate updates
453+ OnConflict target action -> " ON CONFLICT"
454+ <+> renderConflictTarget target <+> renderConflictAction action
455+
456+data ConflictAction tab db params columns where
457+ DoNothing :: ConflictAction tab db params columns
458+ DoUpdate
459+ :: ( row ~ ColumnsToRow columns
460+ , SOP.SListI columns
461+ , columns ~ (col0 ': cols)
462+ , SOP.All (HasIn columns) subcolumns
463+ , AllUnique subcolumns )
464+ => NP (ColumnExpression db '[tab ::: row, "excluded" ::: row] 'Ungrouped params) subcolumns
465+ -> [Condition db '[tab ::: row, "excluded" ::: row] 'Ungrouped params]
466+ -> ConflictAction tab db params columns
467+
468+renderConflictAction
469+ :: ConflictAction tab db params columns
470+ -> ByteString
471+renderConflictAction = \case
472+ DoNothing -> "DO NOTHING"
473+ DoUpdate updates whs'
474+ -> "DO UPDATE SET"
475+ <+> renderCommaSeparated renderColumnExpression updates
410476 <> case whs' of
411477 [] -> ""
412478 wh:whs -> " WHERE" <+> renderExpression (foldr (.&&) wh whs)
413− where
414− renderUpdate
415− :: Aliased (ColumnValue db columns params) column
416− -> Maybe ByteString
417− renderUpdate = \case
418− Same `As` _ -> Nothing
419− Default `As` column -> Just $
420− renderAlias column <+> "=" <+> "DEFAULT"
421− Set expression `As` column -> Just $
422− renderAlias column <+> "=" <+> renderExpression expression
479+
480+-- | A `ConflictTarget` specifies the constraint violation that triggers a
481+-- `ConflictAction`.
482+data ConflictTarget constraints where
483+ OnConstraint
484+ :: Has con constraints constraint
485+ => Alias con
486+ -> ConflictTarget constraints
487+
488+-- | Render a `ConflictTarget`
489+renderConflictTarget
490+ :: ConflictTarget constraints
491+ -> ByteString
492+renderConflictTarget (OnConstraint con) =
493+ "ON" <+> "CONSTRAINT" <+> renderAlias con
423494
424495{-----------------------------------------
425496UPDATE statements
@@ -434,11 +505,13 @@ update
434505 , Has sch schemas schema
435506 , Has tab schema ('Table table)
436507 , row ~ TableToRow table
437− , columns ~ TableToColumns table )
508+ , columns ~ TableToColumns table
509+ , SOP.All (HasIn columns) subcolumns
510+ , AllUnique subcolumns )
438511 => QualifiedAlias sch tab -- ^ table to update
439− -> NP (Aliased (ColumnValue db row params)) columns
512+ -> NP (ColumnExpression db '[tab ::: row] 'Ungrouped params) subcolumns
440513 -- ^ modified values to replace old values
441− -> (forall t. Condition db '[t ::: row] 'Ungrouped params)
514+ -> Condition db '[tab ::: row] 'Ungrouped params
442515 -- ^ condition under which to perform update on a row
443516 -> ReturningClause db params row results -- ^ results to return
444517 -> Manipulation db params results
@@ -446,19 +519,9 @@ update tab columns wh returning = UnsafeManipulation $
446519 "UPDATE"
447520 <+> renderQualifiedAlias tab
448521 <+> "SET"
449− <+> renderCommaSeparatedMaybe renderUpdate columns
522+ <+> renderCommaSeparated renderColumnExpression columns
450523 <+> "WHERE" <+> renderExpression wh
451524 <> renderReturningClause returning
452− where
453− renderUpdate
454− :: Aliased (ColumnValue db columns params) column
455− -> Maybe ByteString
456− renderUpdate = \case
457− Same `As` _ -> Nothing
458− Default `As` column -> Just $
459− renderAlias column <+> "=" <+> "DEFAULT"
460− Set expression `As` column -> Just $
461− renderAlias column <+> "=" <+> renderExpression expression
462525
463526-- | Update a row returning `Nil`.
464527update_
@@ -467,11 +530,13 @@ update_
467530 , Has sch schemas schema
468531 , Has tab schema ('Table table)
469532 , row ~ TableToRow table
470− , columns ~ TableToColumns table )
533+ , columns ~ TableToColumns table
534+ , SOP.All (HasIn columns) subcolumns
535+ , AllUnique subcolumns )
471536 => QualifiedAlias sch tab -- ^ table to update
472− -> NP (Aliased (ColumnValue db row params)) columns
537+ -> NP (ColumnExpression db '[tab ::: row] 'Ungrouped params) subcolumns
473538 -- ^ modified values to replace old values
474− -> (forall t. Condition db '[t ::: row] 'Ungrouped params)
539+ -> Condition db '[tab ::: row] 'Ungrouped params
475540 -- ^ condition under which to perform update on a row
476541 -> Manipulation db params '[]
477542update_ tab columns wh = update tab columns wh (Returning Nil)
squeal-postgresql/src/Squeal/PostgreSQL/Migration.hs+2−3
@@ -276,9 +276,8 @@ createMigrations =
276276insertMigration
277277 :: Has "migrations" schemas MigrationsSchema
278278 => Manipulation ('[] :=> schemas) '[ 'NotNull 'PGtext] '[]
279−insertMigration = insertRow_ (#migrations ! #schema_migrations)
280− ( Set (param @1) `as` #name :*
281− Default `as` #executed_at )
279+insertMigration = insertInto_ (#migrations ! #schema_migrations) . Values_ $
280+ (param @1) `as` #name :* defaultAs #executed_at
282281
283282-- | Deletes a `Migration` from the `MigrationsTable`
284283deleteMigration
squeal-postgresql/src/Squeal/PostgreSQL/PQ.hs+4
@@ -504,6 +504,10 @@ instance (MonadBase b m, schemas0 ~ schemas1)
504504 => MonadBase b (PQ schemas0 schemas1 m) where
505505 liftBase = lift . liftBase
506506
507+instance (MonadIO m, schema0 ~ schema1)
508+ => MonadIO (PQ schema0 schema1 m) where
509+ liftIO = lift . liftIO
510+
507511-- | A snapshot of the state of a `PQ` computation.
508512type PQRun schemas =
509513 forall m x. Monad m => PQ schemas schemas m x -> m (K x schemas)
squeal-postgresql/src/Squeal/PostgreSQL/Render.hs+1−2
@@ -16,7 +16,6 @@ Rendering helper functions.
1616 , PolyKinds
1717 , RankNTypes
1818 , ScopedTypeVariables
19− , TypeApplications
2019#-}
2120
2221module Squeal.PostgreSQL.Render
@@ -37,7 +36,7 @@ module Squeal.PostgreSQL.Render
3736
3837import Control.Monad.Base
3938import Data.ByteString (ByteString)
40−import Data.Maybe
39+import Data.Maybe (catMaybes)
4140import Data.Monoid ((<>))
4241import Data.Text (Text)
4342import Generics.SOP
squeal-postgresql/src/Squeal/PostgreSQL/Schema.hs+26−2
@@ -79,6 +79,10 @@ module Squeal.PostgreSQL.Schema
7979 , HasAll
8080 , QualifiedAlias (..)
8181 , IsQualified (..)
82+ , HasIn
83+ , IsNotElem
84+ , AllUnique
85+ , DefaultAliasable (..)
8286 , renderQualifiedAlias
8387 -- * Enumerated Labels
8488 , IsPGlabel (..)
@@ -123,6 +127,7 @@ module Squeal.PostgreSQL.Schema
123127 , NullifyFrom
124128 -- * Table Conversions
125129 , TableToColumns
130+ , ColumnsToRow
126131 , TableToRow
127132 ) where
128133
@@ -419,9 +424,9 @@ class KnownSymbol alias => Aliasable alias expression aliased
419424 | aliased -> expression
420425 , aliased -> alias
421426 where as :: expression -> Alias alias -> aliased
422−instance (KnownSymbol alias, alias ~ alias1) => Aliasable alias
427+instance (KnownSymbol alias, aliased ~ (alias ::: ty)) => Aliasable alias
423428 (expression ty)
424− (Aliased expression (alias1 ::: ty))
429+ (Aliased expression aliased)
425430 where
426431 as = As
427432instance (KnownSymbol alias, tys ~ '[alias ::: ty]) => Aliasable alias
@@ -455,6 +460,25 @@ instance {-# OVERLAPPING #-} KnownSymbol alias
455460instance {-# OVERLAPPABLE #-} (KnownSymbol alias, Has alias fields field)
456461 => Has alias (field' ': fields) field
457462
463+class HasIn fields (x :: (Symbol, a)) where
464+instance (Has alias fields field) => HasIn fields '(alias, field) where
465+
466+-- | Utility class for `AllUnique` to provide nicer error messages.
467+class IsNotElem x isElem where
468+instance IsNotElem x 'False where
469+instance (TypeError ( 'Text "Cannot assign to "
470+ ':<>: 'ShowType alias
471+ ':<>: 'Text " more than once"))
472+ => IsNotElem '(alias, a) 'True where
473+ -- | No elem of @xs@ appears more than once, in the context of assignment.
474+class AllUnique (xs :: [(Symbol, a)]) where
475+instance AllUnique '[] where
476+instance (IsNotElem x (Elem x xs), AllUnique xs) => AllUnique (x ': xs) where
477+
478+class KnownSymbol alias
479+ => DefaultAliasable alias aliased | aliased -> alias where
480+ defaultAs :: Alias alias -> aliased
481+
458482-- | `HasAll` extends `Has` to take lists of @aliases@ and @fields@ and infer
459483-- a list of @subfields@.
460484class
squeal-postgresql/test/Specs/ExceptionHandling.hs+6−4
@@ -60,8 +60,8 @@ instance SOP.HasDatatypeInfo User
6060
6161insertUser :: Manipulation DB '[ 'NotNull 'PGtext, 'NotNull ('PGvararray ('Null 'PGint2))]
6262 '[ "fromOnly" ::: 'NotNull 'PGint4 ]
63−insertUser = insertRows #users
64− (Default `as` #id :* Set (param @1) `as` #name :* Set (param @2) `as` #vec) []
63+insertUser = insertInto #users
64+ (Values_ (defaultAs #id :* param @1 `as` #name :* param @2 `as` #vec))
6565 OnConflictDoRaise (Returning (#id `as` #fromOnly))
6666
6767setup :: Definition (Public '[]) Schemas
@@ -91,11 +91,13 @@ migration = Migration { name = "test"
9191
9292setupDB :: IO ()
9393setupDB = void . withConnection connectionString $
94− migrateUp $ single migration
94+ manipulate (UnsafeManipulation "SET client_min_messages TO WARNING;")
95+ & pqThen (migrateUp (single migration))
9596
9697dropDB :: IO ()
9798dropDB = void . withConnection connectionString $
98− migrateDown $ single migration
99+ manipulate (UnsafeManipulation "SET client_min_messages TO WARNING;")
100+ & pqThen (migrateDown (single migration))
99101
100102connectionString :: Char8.ByteString
101103connectionString = "host=localhost port=5432 dbname=exampledb"
squeal-presentation-2018-09-12.md → squeal-presentation-raveline.mdrenamed

The content is the same.