-
Notifications
You must be signed in to change notification settings - Fork 352
Database policy support for PUT/PATCH operations - PostgreSQL #3694
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
base: main
Are you sure you want to change the base?
Changes from all commits
626d6f0
3c9b908
73d536c
b5f3734
000ec1b
4e06b23
2333d8e
e744cae
849b32f
fa64d55
ddde125
b96b0c6
af1f7b2
a2a1de1
fa30fcd
56723d6
02b8411
3fddd8f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,11 +2,13 @@ | |
| // Licensed under the MIT License. | ||
|
|
||
| using System.Data.Common; | ||
| using System.Net; | ||
|
unattendedfaxmachine marked this conversation as resolved.
|
||
| using Azure.Core; | ||
| using Azure.DataApiBuilder.Config; | ||
| using Azure.DataApiBuilder.Config.ObjectModel; | ||
| using Azure.DataApiBuilder.Core.Configurations; | ||
| using Azure.DataApiBuilder.Core.Models; | ||
| using Azure.DataApiBuilder.Service.Exceptions; | ||
| using Azure.Identity; | ||
| using Microsoft.AspNetCore.Http; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
@@ -146,6 +148,87 @@ private static bool ShouldManagedIdentityAccessBeAttempted(NpgsqlConnectionStrin | |
| return string.IsNullOrEmpty(builder.Password); | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override async Task<DbResultSet> GetMultipleResultSetsIfAnyAsync( | ||
| DbDataReader dbDataReader, List<string>? args = null) | ||
| { | ||
| // RS1: COUNT of rows matching PK (no policy) — used to distinguish | ||
| // "row doesn't exist" from "row exists but policy blocked". | ||
| DbResultSet resultSetWithCountOfRowsWithGivenPk = await ExtractResultSetFromDbDataReaderAsync(dbDataReader); | ||
| DbResultSetRow? resultSetRowWithCountOfRowsWithGivenPk = resultSetWithCountOfRowsWithGivenPk.Rows.FirstOrDefault(); | ||
| int numOfRecordsWithGivenPK; | ||
| bool isFallbackToUpdate; | ||
|
|
||
| if (resultSetRowWithCountOfRowsWithGivenPk is not null && | ||
| resultSetRowWithCountOfRowsWithGivenPk.Columns.TryGetValue(PostgresQueryBuilder.COUNT_ROWS_WITH_GIVEN_PK, out object? rowsWithGivenPK) && | ||
| resultSetRowWithCountOfRowsWithGivenPk.Columns.TryGetValue(PostgresQueryBuilder.IS_FALLBACK_TO_UPDATE, out object? fallbackToUpdate)) | ||
| { | ||
| // PostgreSQL COUNT(*) returns Int64; convert to int. | ||
| numOfRecordsWithGivenPK = Convert.ToInt32(rowsWithGivenPK!); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it always asserted that
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, with the way the SQL is written I am pretty sure that
|
||
| isFallbackToUpdate = Convert.ToBoolean(fallbackToUpdate!); | ||
| } | ||
| else | ||
| { | ||
| throw new DataApiBuilderException( | ||
| message: $"Neither insert nor update could be performed.", | ||
| statusCode: HttpStatusCode.InternalServerError, | ||
| subStatusCode: DataApiBuilderException.SubStatusCodes.UnexpectedError); | ||
| } | ||
|
|
||
| // RS2: UPDATE result, or UPDATE+INSERT CTE result. | ||
| DbResultSet dbResultSet = await dbDataReader.NextResultAsync() | ||
| ? await ExtractResultSetFromDbDataReaderAsync(dbDataReader) | ||
| : throw new DataApiBuilderException( | ||
| message: $"Neither insert nor update could be performed.", | ||
| statusCode: HttpStatusCode.InternalServerError, | ||
| subStatusCode: DataApiBuilderException.SubStatusCodes.UnexpectedError); | ||
|
|
||
| if (numOfRecordsWithGivenPK == 1) // Row existed — we attempted an UPDATE. | ||
| { | ||
| if (dbResultSet.Rows.Count == 0) | ||
| { | ||
| // Row exists but UPDATE returned no rows — update policy blocked it. | ||
| throw new DataApiBuilderException( | ||
| message: DataApiBuilderException.AUTHORIZATION_FAILURE, | ||
| statusCode: HttpStatusCode.Forbidden, | ||
| subStatusCode: DataApiBuilderException.SubStatusCodes.DatabasePolicyFailure); | ||
| } | ||
| } | ||
| else if (dbResultSet.Rows.Count == 0) | ||
| { | ||
| // If true, the row simply didn't exist — return 404 (same as MsSql's null-RS2 path). | ||
| // If false, the INSERT ran but create policy blocked it — return 403. | ||
|
|
||
| if (isFallbackToUpdate) | ||
| { | ||
| if (args is not null && args.Count > 1) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this if statement redundant? If
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, you are right. I will make this change.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the check and I am getting the below compiler/build error:
For some reason, even though we are guaranteed that both 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. |
||
| { | ||
| string prettyPrintPk = args[0]; | ||
| string entityName = args[1]; | ||
|
|
||
| throw new DataApiBuilderException( | ||
| message: $"Cannot perform INSERT and could not find {entityName} " + | ||
| $"with primary key {prettyPrintPk} to perform UPDATE on.", | ||
| statusCode: HttpStatusCode.NotFound, | ||
| subStatusCode: DataApiBuilderException.SubStatusCodes.ItemNotFound); | ||
| } | ||
|
|
||
| throw new DataApiBuilderException( | ||
| message: $"Neither insert nor update could be performed.", | ||
| statusCode: HttpStatusCode.InternalServerError, | ||
| subStatusCode: DataApiBuilderException.SubStatusCodes.UnexpectedError); | ||
| } | ||
|
|
||
| // Row didn't exist but INSERT returned no rows — create policy blocked it. | ||
| throw new DataApiBuilderException( | ||
| message: DataApiBuilderException.AUTHORIZATION_FAILURE, | ||
| statusCode: HttpStatusCode.Forbidden, | ||
| subStatusCode: DataApiBuilderException.SubStatusCodes.DatabasePolicyFailure); | ||
| } | ||
|
|
||
| return dbResultSet; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines if the saved default azure credential's access token is valid and not expired. | ||
| /// </summary> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.