-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Conditional delete/update based on rows affected #2156
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
Comments
Overall, this seems quite complex. Not necessarily because of the proposed solution, but because of the problem itself, I guess. Maybe we can find a way to make this whole thing simpler? I think we don't need too many options. The basic motivation should be: "When sending a request for mutation, I should be able to tell PostgREST what kind of mutation (number of rows) I roughly expect. If the result differs from that (heavily), then the request should be rejected because it's assumed to be faulty." I think this is also the basic idea behind From my perspective, we should:
I'm not sure whether I like the idea of using a
Agreed. |
True, not useful, I think they were done like that because of consistency(there are tests too).
Basically for perf reasons, deleting a large amount of rows can lock a table for too much time.
|
The simplest thing could be to make |
Or instead make |
This reminds me of #1655. Here we discussed how the accept header has two different things mixed together: The shape of the output + the "I need exactly 1 row". Maybe we can decouple a few different things:
|
Eh, I think I mis-remembered how So part of the suggestion would then be to change the way Edit: And that would still be inconsistent. To make it fully consistent even a |
Yes, correct.
Cool, I agree.
Another benefit of db-max-rows is to prevent OOMs from happening(accidental requests on big tables),. An error might be too harsh when trying reads I think.
How about reusing the specified
Same could be done with It just doesn't seem right to add an additional header to specify a number when In fact, we already have the Range header, that acts the same as limit, we could reuse that as well. |
If a request is deemed as "accidental", it should not return success. The problem with not returning an error if
This is the part that I think makes it so complicated.
And this is exactly the thing I think we should not do. The Range headers have quite clear semantics - and I don't think they make any sense for mutation requests. The RFC even says:
Clearly, And I understand our |
Yeah, I agree somewhat. However until now
So that means a POST request can never return "206 Partial Content" and thus it shouldn't be limited with
So with that we're left with a new header. Hm, |
Maybe just |
Hm. The change required from the client-side isn't much really - client requests would just need to add a I think the current implementation of Alternatively, we implement the same things with a different name and deprecate
This reminds me of the discussion around aggregates and using query cost estimation to block queries, too. So there are for sure some things we can do to prevent malicious requests.
Agreed. |
Since #2164 has gotten a bit complicated how about taking a different path.
The above behavior is useful(other RDBMS have it, it's a common use case in pg and is something that users would expect) so I'll go ahead and implement it. This will also mitigate the problem of updating/deleting too much which is really the main goal here. The discussion about the RFC above regarding "206 Partial Content" is mostly adequate in the context of range headers but our
(From #2164 (comment)) |
The limited update/delete technique from https://blog.crunchydata.com/blog/simulating-update-or-delete-with-limit-in-postgres-ctes-to-the-rescue has some limitations:
1 could be circumvented by inferring a PK or unique column from the view's source table, this would depend on the internal cache freshness. 2 doesn't have a way around unfortunately so UPDATE + SELECT privilege will be a requirement. |
Actually it doesn't make much sense to have an UPDATE privilege without having a SELECT privilege on the PK at least - this is needed for updating particular rows otherwise it would all be full table UPDATEs. So this requirement seems fine. |
Continuing this on #2887, a conditional request looks more fit than a Prefer. |
Uh oh!
There was an error while loading. Please reload this page.
Problem
Even with safeupdate, a
DELETE /employees?salary=gt.1000
can still delete a large amount of rows.We already have code for handling a special case of 1 affected row for update/delete, when using
application/vnd.pgrst.object+json
(CTSingularJSON).postgrest/src/PostgREST/App.hs
Lines 569 to 574 in bd62008
We can extend this logic to accept an arbitrary number of affected rows and reject the request if it doesn't comply. With this a user can ensure that an expected number of rows will be updated/deleted.
Proposal
Use a new Prefer header to specify the affected rows, a couple of options:
Prefer: rows-affected=<number>
Specifies the affected rows in the header value. The default behavior we have being like
Prefer: rows-affected=unlimited/infinity
. APrefer: rows-affected=1
would be equivalent toapplication/vnd.pgrst.object+json
.Prefer: rows-affected=same-as-limit
This would reuse the
?limit=<number>
query param value. The default behavior would also be "unlimited". To do an equivalentapplication/vnd.pgrst.object+json
the request would becurl -X DELETE "host/tbl?limit=1" -H "Prefer: rows-affected=same-as-limit"
. We could also have arows-affected=less-than-limit
to use the limit as a threshold instead of an exact value. Though maybe just aPrefer: rows-affected=limited
with a behavior ofrows affected <= limit
would be enough.With this option, to globally disallow unlimited deletes/updates, we could change the default Prefer to
rows-affected=limited
and combine it withmax-rows-mutated=<number>
(#2155). If users wants to do a full table delete, they'd need to be explicit and specify aPrefer: rows-affected=unlimited
.Notes
The Prefer RFC says:
Before I understood that as "Prefer cannot reject a request", but handling=strict says:
So I think the first paragraph above is mostly saying that we should ignore unrecognized/conflicting prefers, we can reject requests with Prefer.
The text was updated successfully, but these errors were encountered: