Database policy support for PUT/PATCH operations - PostgreSQL#3694
Database policy support for PUT/PATCH operations - PostgreSQL#3694ArjunNarendra wants to merge 19 commits into
Conversation
…ate error/success message is returned
…te/update policies
There was a problem hiding this comment.
Pull request overview
This PR extends PostgreSQL PUT/PATCH (upsert) behavior to correctly apply database policies for both the update and insert branches, aligning Postgres behavior with the expected policy semantics for REST upserts.
Changes:
- Updated PostgreSQL upsert SQL generation to evaluate update-policy vs create-policy depending on which branch executes, while preserving “try update then insert” semantics.
- Added a PostgreSQL-specific
GetMultipleResultSetsIfAnyAsyncimplementation to interpret multi-result-set upsert outcomes and return 403/404 appropriately. - Updated PostgreSQL REST integration tests and test config to exercise create-policy behavior for PUT/PATCH insert cases.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Service.Tests/SqlTests/RestApiTests/Put/PostgreSqlPutApiTests.cs | Adds expected SQL for PUT insert-with-policy and removes ignored overrides so tests run. |
| src/Service.Tests/SqlTests/RestApiTests/Patch/PostgreSqlPatchApiTests.cs | Adds expected SQL for PATCH insert-with-policy and removes ignored overrides so tests run. |
| src/Service.Tests/dab-config.PostgreSql.json | Adds a PostgreSQL create-action database policy used by the new PUT/PATCH scenarios. |
| src/Core/Resolvers/SqlMutationEngine.cs | Passes additional context into the result-set handler for upsert result interpretation. |
| src/Core/Resolvers/PostgresQueryBuilder.cs | Reworks PostgreSQL upsert SQL to support separate create/update policies and emit a PK-existence count result set. |
| src/Core/Resolvers/PostgreSqlExecutor.cs | Implements PostgreSQL-specific multi-result-set handling to map policy failures vs not-found. |
| src/Core/Configurations/RuntimeConfigValidator.cs | Allows PostgreSQL to define create-action database policies in config validation. |
| config-generators/postgresql-commands.txt | Updates generator commands to set separate create/read permissions and the new create policy. |
…er a PostgreSQL upsert operation performed an update or not. Reliance on this flag could potentially lead to incorrect HTTP response code being sent in certain race scenarios.
… whether an upsert operation performed an update or not because it is required by other database providers and does not apply for PostgreSQL
|
So it looks like all the automated tests in Azure pipelines are passing now but at one point one of the Cosmos tests was failing. Specifically, one of the tests in the |
|
/azp run |
|
Azure Pipelines: Successfully started running 6 pipeline(s). |
RubenCerna2079
left a comment
There was a problem hiding this comment.
I don't think the use of the isFallbackToUpdate is correct. Please remove it if possible from the PostgreSqlExecutor.cs file.
|
|
||
| if (isFallbackToUpdate) | ||
| { | ||
| if (args is not null && args.Count > 1) |
There was a problem hiding this comment.
Isn't this if statement redundant? If isFallbackToUpdate true then it means args is is not null and its count is > 2.
There was a problem hiding this comment.
Yes, you are right. I will make this change.
There was a problem hiding this comment.
I removed the check and I am getting the below compiler/build error:
In externally visible method 'Task<DbResultSet> PostgreSqlQueryExecutor.GetMultipleResultSetsIfAnyAsync(DbDataReader dbDataReader, List<string>? args = null)', validate parameter 'args' is non-null before using it. If appropriate, throw an 'ArgumentNullException' when the argument is 'null'.[CA1062](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062)
For some reason, even though we are guaranteed that both args is not null and count > 2 because isFallbackToUpdate is true, the compiler is not able to make this logical leap. I played around with it and, of the two predicates, only args is not null is required for the code to compile successfully.
I am going to leave the code as is to make the compiler happy, but let me know if you still have any concerns with this.
There was a problem hiding this comment.
I think you should be able to avoid this issue by stating the args value is not null by giving it the ! sign. So I think it would look something like this args![0]. But if it doesn't work it is fine to leave it as it is.
There was a problem hiding this comment.
Yep, it works! Thanks. I made the change.
There was a problem hiding this comment.
Wait...I think the check is necessary now. Since I took isFallbackToUpdate out of args, we have no idea what args contains anymore (since we don't extract isFallbackToUpdate from args[2] anymore before this if check). I am not going to make the change. Let me know if my logic makes sense.
…via the query result instead
…or insert, update, and update incremental operations
| ? new List<string> { prettyPrintPk, entityName } | ||
| : new List<string> { prettyPrintPk, entityName }); |
There was a problem hiding this comment.
the ternary conditions are same and this doesn't make any difference.
There was a problem hiding this comment.
Since the isFallbackToUpdate was removed from the PostgresQueryBuilder then we can just remove this shorthand if else statement.
There was a problem hiding this comment.
Oh my bad...must have missed it. Thanks for catching it.
| $"SELECT {BuildListOfLabels(structure.OutputColumns)}, {UPSERT_IDENTIFIER_COLUMN_NAME} FROM update_cte UNION ALL " + | ||
| $"SELECT {BuildListOfLabels(structure.OutputColumns)}, {UPSERT_IDENTIFIER_COLUMN_NAME} FROM insert_cte;"; | ||
|
|
||
| return $"{countQuery}; {cteQuery}"; |
There was a problem hiding this comment.
question- this won't have a race conditions right?
There was a problem hiding this comment.
I don't think so since it is within a SQL statement. However, Mihir did bring up a race condition that I fixed here: #3694 (comment)
Why make this change?
As per the behaviour expected from PUT/PATCH operations with database policies discussed in #1430, implement db policy support for PostgreSQL to fix #1372.
What is this change?
Prior to this change, there was only one database policy for each operation. Since now database policies will be supported for both insert (or create)/update actions via PUT/PATCH operations, these 2 operations can have 2 database policies defined for them, one for each action.
The query generated by
PostgresQueryBuilder.Build(SqlUpsertQueryStructure structure)is modified to accommodate create/update policies while also keeping intact the normal upsert behavior expected (try update, then insert).The method
IQueryExecutor.GetMultipleResultSetsIfAnyAsynchas been provided another implementation specific to PostgreSql inPostgreSqlExecutor. TheDbDataReaderinstance for the query being executed for the PUT/PATCH operation will always contain two result sets.Different scenarios are added to the method
PostgreSqlExecutor.GetMultipleResultSetsIfAnyAsyncto throw appropriate exceptions (Forbidden/Authorization failure - 403 and NotFound - 404). Appropriate comments are added within the code to demonstrate each case.How was this tested?
Integration Tests - Done