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

Cosmos DB: Adds Patch Support #2161

Merged
merged 38 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
150f42b
wip
sourabh1007 Apr 11, 2024
7bf06bd
remove unused file
sourabh1007 Apr 12, 2024
abc2e62
refcators code
sourabh1007 Apr 12, 2024
d2c014b
implement patch func
sourabh1007 Apr 12, 2024
41701e9
fix patch
sourabh1007 Apr 12, 2024
51dd0b1
updated test
sourabh1007 Apr 14, 2024
c7f830b
wip
sourabh1007 Apr 15, 2024
cc06d5f
refactor code
sourabh1007 Apr 15, 2024
482e196
rename tets
sourabh1007 Apr 15, 2024
9b5b7b0
fix test
sourabh1007 Apr 15, 2024
434f4cf
fix test
sourabh1007 Apr 15, 2024
1d1b3a7
updates test
sourabh1007 Apr 16, 2024
fc450b5
fix format
sourabh1007 Apr 16, 2024
31b6d97
config update
sourabh1007 Apr 16, 2024
85eb02a
verified txt
sourabh1007 Apr 16, 2024
b406a1c
add cosmos db check
sourabh1007 Apr 16, 2024
3da78e9
format fix
sourabh1007 Apr 16, 2024
a707128
cli fixes
sourabh1007 Apr 17, 2024
529999d
used datasource instead of flag
sourabh1007 Apr 17, 2024
0da866a
updated more file
sourabh1007 Apr 17, 2024
d0893d6
refactor code
sourabh1007 Apr 17, 2024
aa022b7
fix format
sourabh1007 Apr 17, 2024
6ecb8af
removed patch as separate permission
sourabh1007 Apr 23, 2024
78ef01b
clean up
sourabh1007 Apr 23, 2024
0ec7142
refactor code
sourabh1007 Apr 23, 2024
553f95f
add doc
sourabh1007 Apr 23, 2024
8233fdd
review comments
sourabh1007 Apr 26, 2024
0a89888
change error message
sourabh1007 Apr 29, 2024
16db369
fix assertion
sourabh1007 Apr 29, 2024
b4e1a6e
made changes to honor update access insteadof adding patch access dyn…
sourabh1007 Apr 29, 2024
0d7a9b7
revert some changes
sourabh1007 Apr 29, 2024
2d3cb47
revert few more files
sourabh1007 Apr 29, 2024
d85c486
fixed tests
sourabh1007 Apr 30, 2024
4d7c8c9
fix formatting
sourabh1007 Apr 30, 2024
3d5bf95
Update src/Core/Services/TypeHelper.cs
sourabh1007 Apr 30, 2024
67c0e00
fix test
sourabh1007 Apr 30, 2024
9b515ab
Merge branch 'main' into users/sourabhjain/addpatchsupport
sourabh1007 Apr 30, 2024
1d2f536
Merge branch 'main' into users/sourabhjain/addpatchsupport
sourabh1007 Apr 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 20 additions & 7 deletions src/Cli/ConfigGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ public static bool TryAddNewEntity(AddOptions options, RuntimeConfig initialRunt
// Try to get the source object as string or DatabaseObjectSource for new Entity
if (!TryCreateSourceObjectForNewEntity(
options,
initialRuntimeConfig.DataSource.DatabaseType == DatabaseType.CosmosDB_NoSQL,
initialRuntimeConfig.DataSource.DatabaseType is DatabaseType.CosmosDB_NoSQL,
out EntitySource? source))
{
_logger.LogError("Unable to create the source object.");
Expand All @@ -374,7 +374,11 @@ public static bool TryAddNewEntity(AddOptions options, RuntimeConfig initialRunt
EntityActionPolicy? policy = GetPolicyForOperation(options.PolicyRequest, options.PolicyDatabase);
EntityActionFields? field = GetFieldsForOperation(options.FieldsToInclude, options.FieldsToExclude);

EntityPermission[]? permissionSettings = ParsePermission(options.Permissions, policy, field, source.Type);
EntityPermission[]? permissionSettings = ParsePermission(
options.Permissions,
policy,
field,
source.Type);
if (permissionSettings is null)
{
_logger.LogError("Please add permission in the following format. --permissions \"<<role>>:<<actions>>\"");
Expand Down Expand Up @@ -424,7 +428,7 @@ public static bool TryAddNewEntity(AddOptions options, RuntimeConfig initialRunt
}
}

EntityRestOptions restOptions = ConstructRestOptions(options.RestRoute, SupportedRestMethods, initialRuntimeConfig.DataSource.DatabaseType == DatabaseType.CosmosDB_NoSQL);
EntityRestOptions restOptions = ConstructRestOptions(options.RestRoute, SupportedRestMethods, initialRuntimeConfig.DataSource.DatabaseType is DatabaseType.CosmosDB_NoSQL);
EntityGraphQLOptions graphqlOptions = ConstructGraphQLTypeDetails(options.GraphQLType, graphQLOperationsForStoredProcedures);

// Create new entity.
Expand Down Expand Up @@ -630,7 +634,7 @@ public static bool TryUpdateExistingEntity(UpdateOptions options, RuntimeConfig
}
}

EntityRestOptions updatedRestDetails = ConstructUpdatedRestDetails(entity, options, initialConfig.DataSource.DatabaseType == DatabaseType.CosmosDB_NoSQL);
EntityRestOptions updatedRestDetails = ConstructUpdatedRestDetails(entity, options, initialConfig.DataSource.DatabaseType is DatabaseType.CosmosDB_NoSQL);
EntityGraphQLOptions updatedGraphQLDetails = ConstructUpdatedGraphQLDetails(entity, options);
EntityPermission[]? updatedPermissions = entity!.Permissions;
Dictionary<string, EntityRelationship>? updatedRelationships = entity.Relationships;
Expand All @@ -648,7 +652,13 @@ public static bool TryUpdateExistingEntity(UpdateOptions options, RuntimeConfig
if (options.Permissions is not null && options.Permissions.Any())
{
// Get the Updated Permission Settings
updatedPermissions = GetUpdatedPermissionSettings(entity, options.Permissions, updatedPolicy, updatedFields, updatedSourceType);
updatedPermissions = GetUpdatedPermissionSettings(
entity,
options.Permissions,
updatedPolicy,
updatedFields,
updatedSourceType,
initialConfig.DataSource.DatabaseType);

if (updatedPermissions is null)
{
Expand Down Expand Up @@ -739,7 +749,8 @@ public static bool TryUpdateExistingEntity(UpdateOptions options, RuntimeConfig
IEnumerable<string> permissions,
EntityActionPolicy? policy,
EntityActionFields? fields,
EntitySourceType? sourceType)
EntitySourceType? sourceType,
DatabaseType databaseType)
{

// Parse role and operations from the permissions string
Expand Down Expand Up @@ -781,7 +792,9 @@ public static bool TryUpdateExistingEntity(UpdateOptions options, RuntimeConfig
else
{
// User didn't use WILDCARD, and wants to update some of the operations.
IDictionary<EntityActionOperation, EntityAction> existingOperations = ConvertOperationArrayToIEnumerable(permission.Actions, entityToUpdate.Source.Type);
IDictionary<EntityActionOperation, EntityAction> existingOperations
= ConvertOperationArrayToIEnumerable(
permission.Actions, entityToUpdate.Source.Type, databaseType);

// Merge existing operations with new operations
EntityAction[] updatedOperationArray = GetUpdatedOperationArray(newOperationArray, policy, fields, existingOperations);
Expand Down
11 changes: 8 additions & 3 deletions src/Cli/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public static EntityAction[] CreateOperations(string operations, EntityActionPol
/// </summary>
/// <param name="operations">Array of operations which is of type JsonElement.</param>
/// <returns>Dictionary of operations</returns>
public static IDictionary<EntityActionOperation, EntityAction> ConvertOperationArrayToIEnumerable(EntityAction[] operations, EntitySourceType? sourceType)
public static IDictionary<EntityActionOperation, EntityAction> ConvertOperationArrayToIEnumerable(EntityAction[] operations, EntitySourceType? sourceType, DatabaseType databaseType)
{
Dictionary<EntityActionOperation, EntityAction> result = new();
foreach (EntityAction operation in operations)
Expand All @@ -96,8 +96,13 @@ public static EntityAction[] CreateOperations(string operations, EntityActionPol
if (op is EntityActionOperation.All)
{
HashSet<EntityActionOperation> resolvedOperations = sourceType is EntitySourceType.StoredProcedure ?
EntityAction.ValidStoredProcedurePermissionOperations :
EntityAction.ValidPermissionOperations;
EntityAction.ValidStoredProcedurePermissionOperations : EntityAction.ValidPermissionOperations;

if (databaseType is DatabaseType.CosmosDB_NoSQL && resolvedOperations.Contains(EntityActionOperation.Update))
{
resolvedOperations.Add(EntityActionOperation.Patch);
}

// Expand wildcard to all valid operations (except execute)
sourabh1007 marked this conversation as resolved.
Show resolved Hide resolved
foreach (EntityActionOperation validOp in resolvedOperations)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Config/ObjectModel/EntityActionOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public enum EntityActionOperation
Delete, Read,

// cosmosdb_nosql operations
Upsert, Create,
Upsert, Create, Patch,
sourabh1007 marked this conversation as resolved.
Show resolved Hide resolved

// Sql operations
Insert, Update, UpdateGraphQL,
Expand Down
5 changes: 4 additions & 1 deletion src/Config/ObjectModel/RuntimeEntities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using Azure.DataApiBuilder.Config.Converters;
using Azure.DataApiBuilder.Service.Exceptions;
using Humanizer;

namespace Azure.DataApiBuilder.Config.ObjectModel;
Expand Down Expand Up @@ -67,7 +68,9 @@ public bool ContainsKey(string key)
}
else
{
throw new ApplicationException($"The entity '{key}' was not found in the dab-config json");
sourabh1007 marked this conversation as resolved.
Show resolved Hide resolved
throw new DataApiBuilderException(message: $"The entity '{key}' was not found in the dab-config json",
statusCode: System.Net.HttpStatusCode.ServiceUnavailable,
subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError);
}
}
}
Expand Down
36 changes: 29 additions & 7 deletions src/Core/Authorization/AuthorizationResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ public void SetEntityPermissionMap(RuntimeConfig runtimeConfig)
// so that it doesn't need to be evaluated per request.
PopulateAllowedExposedColumns(operationToColumn.AllowedExposedColumns, entityName, allowedColumns, metadataProvider);

IEnumerable<EntityActionOperation> operations = GetAllOperationsForObjectType(operation, entity.Source.Type);
IEnumerable<EntityActionOperation> operations = GetAllOperationsForObjectType(runtimeConfig.DataSource.DatabaseType, operation, entity.Source.Type);
foreach (EntityActionOperation crudOperation in operations)
{
// Try to add the opElement to the map if not present.
Expand All @@ -345,7 +345,7 @@ public void SetEntityPermissionMap(RuntimeConfig runtimeConfig)

foreach (string allowedColumn in allowedColumns)
{
entityToRoleMap.FieldToRolesMap.TryAdd(key: allowedColumn, CreateOperationToRoleMap(entity.Source.Type));
entityToRoleMap.FieldToRolesMap.TryAdd(key: allowedColumn, CreateOperationToRoleMap(runtimeConfig.DataSource.DatabaseType, entity.Source.Type));
entityToRoleMap.FieldToRolesMap[allowedColumn][crudOperation].Add(role);
}

Expand Down Expand Up @@ -419,17 +419,31 @@ public void SetEntityPermissionMap(RuntimeConfig runtimeConfig)
/// Stored procedures only support Operation.Execute.
/// In case the operation is Operation.All (wildcard), it gets resolved to a set of CRUD operations.
/// </summary>
/// <param name="databaseType">database Type i.e Cosmos_NoSQL etc.</param>
/// <param name="operation">operation type.</param>
/// <param name="sourceType">Type of database object: Table, View, or Stored Procedure.</param>
/// <returns>IEnumerable of all available operations.</returns>
public static IEnumerable<EntityActionOperation> GetAllOperationsForObjectType(EntityActionOperation operation, EntitySourceType? sourceType)
public static IEnumerable<EntityActionOperation> GetAllOperationsForObjectType(DatabaseType databaseType, EntityActionOperation operation, EntitySourceType? sourceType)
{
if (sourceType is EntitySourceType.StoredProcedure)
{
return new List<EntityActionOperation> { EntityActionOperation.Execute };
}

return operation is EntityActionOperation.All ? EntityAction.ValidPermissionOperations : new List<EntityActionOperation> { operation };
IEnumerable<EntityActionOperation> validOperations =
operation is EntityActionOperation.All ? EntityAction.ValidPermissionOperations : new List<EntityActionOperation> { operation };

//For CosmosDB, add Patch operation to the list of valid operations if Update operation is present.
if (databaseType is DatabaseType.CosmosDB_NoSQL &&
validOperations.Contains(EntityActionOperation.Update))
{
List<EntityActionOperation> tempOperationList = validOperations.ToList<EntityActionOperation>();
tempOperationList.Add(EntityActionOperation.Patch);

validOperations = tempOperationList;
};

return validOperations;
}

/// <summary>
Expand Down Expand Up @@ -803,10 +817,10 @@ private static IEnumerable<string> ResolveEntityDefinitionColumns(string entityN
/// Creates new key value map of
/// Key: operationType
/// Value: Collection of role names.
/// There are only five possible operations
/// There are only six possible operations
/// </summary>
/// <returns>Dictionary: Key - Operation | Value - List of roles.</returns>
private static Dictionary<EntityActionOperation, List<string>> CreateOperationToRoleMap(EntitySourceType? sourceType)
private static Dictionary<EntityActionOperation, List<string>> CreateOperationToRoleMap(DatabaseType databaseType, EntitySourceType? sourceType)
{
if (sourceType is EntitySourceType.StoredProcedure)
{
Expand All @@ -816,13 +830,21 @@ private static IEnumerable<string> ResolveEntityDefinitionColumns(string entityN
};
}

return new Dictionary<EntityActionOperation, List<string>>()
Dictionary<EntityActionOperation, List<string>> operationMap = new()
{
{ EntityActionOperation.Create, new List<string>()},
{ EntityActionOperation.Read, new List<string>()},
{ EntityActionOperation.Update, new List<string>()},
{ EntityActionOperation.Delete, new List<string>()}
};

// Patch operation is supported only for NOSQL databases i.e. CosmosDB
if (databaseType is DatabaseType.CosmosDB_NoSQL)
{
operationMap.Add(EntityActionOperation.Patch, new List<string>());
}

return operationMap;
}

#endregion
Expand Down
12 changes: 8 additions & 4 deletions src/Core/Configurations/RuntimeConfigValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public void ValidateConfigProperties()

if (runtimeConfig.IsGraphQLEnabled)
{
ValidateEntitiesDoNotGenerateDuplicateQueriesOrMutation(runtimeConfig.Entities);
ValidateEntitiesDoNotGenerateDuplicateQueriesOrMutation(runtimeConfig.DataSource.DatabaseType, runtimeConfig.Entities);
}
}
}
Expand Down Expand Up @@ -318,10 +318,11 @@ private void LogConfigValidationExceptions()
/// create mutation name: createBook
/// update mutation name: updateBook
/// delete mutation name: deleteBook
/// patch mutation name: patchBook
/// </summary>
/// <param name="entityCollection">Entity definitions</param>
/// <exception cref="DataApiBuilderException"></exception>
public void ValidateEntitiesDoNotGenerateDuplicateQueriesOrMutation(RuntimeEntities entityCollection)
public void ValidateEntitiesDoNotGenerateDuplicateQueriesOrMutation(DatabaseType databaseType, RuntimeEntities entityCollection)
{
HashSet<string> graphQLOperationNames = new();

Expand All @@ -345,7 +346,8 @@ public void ValidateEntitiesDoNotGenerateDuplicateQueriesOrMutation(RuntimeEntit
}
else
{
// For entities (table/view) that have graphQL exposed, two queries and three mutations would be generated.
// For entities (table/view) that have graphQL exposed, two queries, three mutations for Relational databases (e.g. MYSQL, MSSQL etc.)
// and four mutations for CosmosDb_NoSQL would be generated.
// Primary Key Query: For fetching an item using its primary key.
// List Query: To fetch a paginated list of items.
// Query names for both these queries are determined.
Expand All @@ -356,12 +358,14 @@ public void ValidateEntitiesDoNotGenerateDuplicateQueriesOrMutation(RuntimeEntit
string createMutationName = $"create{GraphQLNaming.GetDefinedSingularName(entityName, entity)}";
string updateMutationName = $"update{GraphQLNaming.GetDefinedSingularName(entityName, entity)}";
string deleteMutationName = $"delete{GraphQLNaming.GetDefinedSingularName(entityName, entity)}";
string patchMutationName = $"patch{GraphQLNaming.GetDefinedSingularName(entityName, entity)}";

if (!graphQLOperationNames.Add(pkQueryName)
|| !graphQLOperationNames.Add(listQueryName)
|| !graphQLOperationNames.Add(createMutationName)
|| !graphQLOperationNames.Add(updateMutationName)
|| !graphQLOperationNames.Add(deleteMutationName))
|| !graphQLOperationNames.Add(deleteMutationName)
|| ((databaseType is DatabaseType.CosmosDB_NoSQL) && !graphQLOperationNames.Add(patchMutationName)))
{
containsDuplicateOperationNames = true;
}
Expand Down