Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for bulk-delete #2355

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
+ Limited to generating the plan of a json representation(`application/json`) but can be extended later to allow other representations.
+ The plan can be obtained in text(`Accept: application/vnd.pgrst.plan+text`) and json(`Accept: application/vnd.pgrst.plan+json` or `Accept: application/vnd.pgrst.plan`) format.
- #2397, Fix race conditions managing database connection helper - @robx
- #2314, Allow bulk delete by using DELETE with an array body - @laurenceisla

### Fixed

Expand Down
18 changes: 16 additions & 2 deletions src/PostgREST/Query/QueryBuilder.hs
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,24 @@ mutateRequestToQuery (Update mainQi uCols body logicForest pkFlts range ordts re
rangeCols = BS.intercalate ", " ((\col -> pgFmtIdent col <> " = (SELECT " <> pgFmtIdent col <> " FROM pgrst_update_body) ") <$> S.toList uCols)
(whereRangeIdF, rangeIdF) = mutRangeF mainQi (fst . otTerm <$> ordts)

mutateRequestToQuery (Delete mainQi logicForest range ordts returnings)
mutateRequestToQuery (Delete mainQi dCols body logicForest range ordts returnings)
-- The body is not mandatory
| range == allRange =
let whereLogic | null logicForest && hasEmptyBody = mempty
| otherwise = "WHERE " <> logicForestAndDelBodyF in
(if hasEmptyBody
then mempty
else "WITH " <> normalizedBody body <> " ") <>
"DELETE FROM " <> SQL.sql (fromQi mainQi) <> " " <>
(if hasEmptyBody
then mempty
else "USING (SELECT * FROM json_populate_recordset (null::" <> SQL.sql (fromQi mainQi) <> " , " <> SQL.sql selectBody <> " )) pgrst_delete_body ") <>
whereLogic <> " " <>
SQL.sql (returningF mainQi returnings)

-- When using limits, the payload is ignored.
| otherwise =
let whereLogic = if null logicForest then mempty else " WHERE " <> logicForestF in
"WITH " <>
"pgrst_affected_rows AS (" <>
"SELECT " <> SQL.sql rangeIdF <> " FROM " <> SQL.sql (fromQi mainQi) <>
Expand All @@ -164,7 +175,10 @@ mutateRequestToQuery (Delete mainQi logicForest range ordts returnings)
SQL.sql (returningF mainQi returnings)

where
whereLogic = if null logicForest then mempty else " WHERE " <> intercalateSnippet " AND " (pgFmtLogicTree mainQi <$> logicForest)
hasEmptyBody = isNothing body || null dCols
logicForestF = intercalateSnippet " AND " (pgFmtLogicTree mainQi <$> logicForest)
pgrstDeleteBodyF = SQL.sql (BS.intercalate " AND " $ (\x -> pgFmtColumn mainQi x <> " = " <> pgFmtColumn (QualifiedIdentifier mempty "pgrst_delete_body") x) <$> S.toList dCols)
logicForestAndDelBodyF = intercalateSnippet " AND " ([logicForestF | not (null logicForest)] ++ [pgrstDeleteBodyF | not hasEmptyBody])
(whereRangeIdF, rangeIdF) = mutRangeF mainQi (fst . otTerm <$> ordts)

requestToCallProcQuery :: CallRequest -> SQL.Snippet
Expand Down
2 changes: 2 additions & 0 deletions src/PostgREST/Request/ApiRequest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
columns = case action of
ActionMutate MutationCreate -> qsColumns
ActionMutate MutationUpdate -> qsColumns
ActionMutate MutationDelete -> qsColumns
ActionInvoke InvPost -> qsColumns
_ -> Nothing

Expand Down Expand Up @@ -321,6 +322,7 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
(ActionInvoke InvPost, _) -> True
(ActionMutate MutationSingleUpsert, _) -> True
(ActionMutate MutationUpdate, _) -> True
(ActionMutate MutationDelete, _) -> not (LBS.null reqBody)
_ -> False
relevantPayload = case (contentMediaType, action) of
-- Though ActionInvoke GET/HEAD doesn't really have a payload, we use the payload variable as a way
Expand Down
3 changes: 2 additions & 1 deletion src/PostgREST/Request/DbRequestBuilder.hs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,8 @@ mutateRequest mutation schema tName ApiRequest{..} pkCols readReq = mapLeft ApiR
then Right $ Insert qi iColumns body (Just (MergeDuplicates, pkCols)) combinedLogic returnings
else
Left InvalidFilters
MutationDelete -> Right $ Delete qi combinedLogic iTopLevelRange rootOrder returnings
MutationDelete -> Right $ Delete qi iColumns body combinedLogic iTopLevelRange rootOrder returnings

where
confCols = fromMaybe pkCols qsOnConflict
QueryParams.QueryParams{..} = iQueryParams
Expand Down
2 changes: 2 additions & 0 deletions src/PostgREST/Request/MutateQuery.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ data MutateQuery
}
| Delete
{ in_ :: QualifiedIdentifier
, delCols :: S.Set FieldName
, delBody :: Maybe LBS.ByteString
, where_ :: [LogicTree]
, mutRange :: NonnegRange
, mutOrder :: [OrderTerm]
Expand Down
Loading