Skip to content

Commit

Permalink
Merge branch 'main' into users/sourabhjain/addpatchsupport
Browse files Browse the repository at this point in the history
  • Loading branch information
sourabh1007 committed Apr 25, 2024
2 parents 2eb0f71 + e5cc8de commit cea72a8
Show file tree
Hide file tree
Showing 16 changed files with 1,473 additions and 9 deletions.
7 changes: 6 additions & 1 deletion config-generators/mssql-commands.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
init --config "dab-config.MsSql.json" --database-type mssql --set-session-context true --connection-string "Server=tcp:127.0.0.1,1433;Persist Security Info=False;User ID=sa;Password=REPLACEME;MultipleActiveResultSets=False;Connection Timeout=5;" --host-mode Development --cors-origin "http://localhost:5000"
init --config "dab-config.MsSql.json" --database-type mssql --set-session-context true --connection-string "Server=tcp:127.0.0.1,1433;Persist Security Info=False;User ID=sa;Password=REPLACEME;MultipleActiveResultSets=False;Connection Timeout=5;" --host-mode Development --cors-origin "http://localhost:5000" --graphql.multiple-create.enabled true
add Publisher --config "dab-config.MsSql.json" --source publishers --permissions "anonymous:read"
add Stock --config "dab-config.MsSql.json" --source stocks --permissions "anonymous:create,read,update,delete"
add Book --config "dab-config.MsSql.json" --source books --permissions "anonymous:create,read,update,delete" --graphql "book:books"
Expand Down Expand Up @@ -33,6 +33,8 @@ add User_NonAutogenRelationshipColumn --config "dab-config.MsSql.json" --source
add UserProfile --config "dab-config.MsSql.json" --source "user_profiles" --permissions "anonymous:*" --rest true --graphql true
add User_AutogenRelationshipColumn --config "dab-config.MsSql.json" --source "users" --permissions "anonymous:*" --rest true --graphql true
add User_AutogenToNonAutogenRelationshipColumn --config "dab-config.MsSql.json" --source "users" --permissions "anonymous:*" --rest true --graphql true
add User_RepeatedReferencingColumnToOneEntity --config "dab-config.MsSql.json" --source "users" --permissions "anonymous:*" --rest true --graphql true
add UserProfile_RepeatedReferencingColumnToTwoEntities --config "dab-config.MsSql.json" --source "user_profiles" --permissions "anonymous:*" --rest true --graphql true
add GetBooks --config "dab-config.MsSql.json" --source "get_books" --source.type "stored-procedure" --permissions "anonymous:execute" --rest true --graphql true
add GetBook --config "dab-config.MsSql.json" --source "get_book_by_id" --source.type "stored-procedure" --permissions "anonymous:execute" --rest true --graphql false
add GetPublisher --config "dab-config.MsSql.json" --source "get_publisher_by_id" --source.type "stored-procedure" --permissions "anonymous:execute" --rest true --graphql true --graphql.operation "query"
Expand Down Expand Up @@ -145,6 +147,9 @@ update ArtOfWar --config "dab-config.MsSql.json" --permissions "authenticated:*"
update User_NonAutogenRelationshipColumn --config "dab-config.MsSql.json" --relationship UserProfile_NonAutogenRelationshipColumn --target.entity UserProfile --cardinality one --relationship.fields "username:username"
update User_AutogenRelationshipColumn --config "dab-config.MsSql.json" --relationship UserProfile_AutogenRelationshipColumn --target.entity UserProfile --cardinality one --relationship.fields "userid:profileid"
update User_AutogenToNonAutogenRelationshipColumn --config "dab-config.MsSql.json" --relationship UserProfile_AutogenToNonAutogenRelationshipColumn --target.entity UserProfile --cardinality one --relationship.fields "userid,username:userid,username"
update User_RepeatedReferencingColumnToOneEntity --config "dab-config.MsSql.json" --relationship UserProfile --target.entity UserProfile --cardinality one --relationship.fields "username,username:profilepictureurl,username"
update UserProfile_RepeatedReferencingColumnToTwoEntities --config "dab-config.MsSql.json" --relationship book --target.entity Book --cardinality one --relationship.fields "userid:id"
update UserProfile_RepeatedReferencingColumnToTwoEntities --config "dab-config.MsSql.json" --relationship publisher --target.entity Publisher --cardinality one --relationship.fields "userid:id"
update GetBook --config "dab-config.MsSql.json" --permissions "authenticated:execute" --rest.methods "Get"
update GetPublisher --config "dab-config.MsSql.json" --permissions "authenticated:execute"
update GetBooks --config "dab-config.MsSql.json" --permissions "authenticated:execute" --graphql.operation "Query" --rest.methods "Get"
Expand Down
4 changes: 4 additions & 0 deletions src/Config/DataApiBuilderException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ public enum SubStatusCodes
/// </summary>
EntityNotFound,
/// <summary>
/// The relationship for a pair of source/target entities does not exist.
/// </summary>
RelationshipNotFound,
/// <summary>
/// Request failed authentication. i.e. No/Invalid JWT token
/// </summary>
AuthenticationChallenge,
Expand Down
16 changes: 15 additions & 1 deletion src/Config/ObjectModel/RuntimeEntities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,21 @@ public bool ContainsKey(string key)
return Entities.ContainsKey(key);
}

public Entity this[string key] => Entities[key];
public Entity this[string key]
{
get
{
Entity? entity;
if (Entities.TryGetValue(key, out entity) && entity is not null)
{
return entity;
}
else
{
throw new ApplicationException($"The entity '{key}' was not found in the dab-config json");
}
}
}

IEnumerator IEnumerable.GetEnumerator()
{
Expand Down
48 changes: 48 additions & 0 deletions src/Core/Models/MultipleMutationEntityInputValidationContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Azure.DataApiBuilder.Core.Models
{
/// <summary>
/// Class to represent input for an entity in a multiple-create request.
/// </summary>
public class MultipleMutationEntityInputValidationContext
{
/// <summary>
/// Current entity name.
/// </summary>
public string EntityName { get; }

/// <summary>
/// Parent entity name. For the topmost entity, this will be set as an empty string.
/// </summary>
public string ParentEntityName { get; }

/// <summary>
/// Set of columns in the current entity whose values are derived from insertion in the parent entity
/// (i.e. parent entity would have been the referenced entity).
/// For the topmost entity, this will be an empty set.</param>
/// </summary>
public HashSet<string> ColumnsDerivedFromParentEntity { get; }

/// <summary>
/// Set of columns in the current entity whose values are to be derived from insertion in the entity or its subsequent
/// referenced entities and returned to the parent entity so as to provide values for the corresponding referencing fields
/// (i.e. parent entity would have been the referencing entity)
/// For the topmost entity, this will be an empty set.
/// </summary>
public HashSet<string> ColumnsToBeDerivedFromEntity { get; }

public MultipleMutationEntityInputValidationContext(
string entityName,
string parentEntityName,
HashSet<string> columnsDerivedFromParentEntity,
HashSet<string> columnsToBeDerivedFromEntity)
{
EntityName = entityName;
ParentEntityName = parentEntityName;
ColumnsDerivedFromParentEntity = columnsDerivedFromParentEntity;
ColumnsToBeDerivedFromEntity = columnsToBeDerivedFromEntity;
}
}
}
25 changes: 22 additions & 3 deletions src/Core/Resolvers/SqlMutationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,25 @@ public class SqlMutationEngine : IMutationEngine
// If authorization fails, an exception will be thrown and request execution halts.
AuthorizeMutation(context, parameters, entityName, mutationOperation);

// Multiple create mutation request is validated to ensure that the request is valid semantically.
string inputArgumentName = IsPointMutation(context) ? MutationBuilder.ITEM_INPUT_ARGUMENT_NAME : MutationBuilder.ARRAY_INPUT_ARGUMENT_NAME;
if (parameters.TryGetValue(inputArgumentName, out object? param) && mutationOperation is EntityActionOperation.Create)
{
IInputField schemaForArgument = context.Selection.Field.Arguments[inputArgumentName];
MultipleMutationEntityInputValidationContext multipleMutationEntityInputValidationContext = new(
entityName: entityName,
parentEntityName: string.Empty,
columnsDerivedFromParentEntity: new(),
columnsToBeDerivedFromEntity: new());
MultipleMutationInputValidator multipleMutationInputValidator = new(sqlMetadataProviderFactory: _sqlMetadataProviderFactory, runtimeConfigProvider: _runtimeConfigProvider);
multipleMutationInputValidator.ValidateGraphQLValueNode(
schema: schemaForArgument,
context: context,
parameters: param,
nestingLevel: 0,
multipleMutationEntityInputValidationContext: multipleMutationEntityInputValidationContext);
}

// The presence of READ permission is checked in the current role (with which the request is executed) as well as Anonymous role. This is because, for GraphQL requests,
// READ permission is inherited by other roles from Anonymous role when present.
bool isReadPermissionConfigured = _authorizationResolver.AreRoleAndOperationDefinedForEntity(entityName, roleName, EntityActionOperation.Read)
Expand Down Expand Up @@ -1225,7 +1244,7 @@ private bool AreFieldsAuthorizedForEntity(string clientRole, string entityName,
/// title: "book #1",
/// reviews: [{ content: "Good book." }, { content: "Great book." }],
/// publishers: { name: "Macmillan publishers" },
/// authors: [{ birthdate: "1997-09-03", name: "Red house authors", author_name: "Dan Brown" }]
/// authors: [{ birthdate: "1997-09-03", name: "Red house authors", royal_percentage: 4.6 }]
/// })
/// {
/// id
Expand All @@ -1236,13 +1255,13 @@ private bool AreFieldsAuthorizedForEntity(string clientRole, string entityName,
/// title: "book #1",
/// reviews: [{ content: "Good book." }, { content: "Great book." }],
/// publishers: { name: "Macmillan publishers" },
/// authors: [{ birthdate: "1997-09-03", name: "Red house authors", author_name: "Dan Brown" }]
/// authors: [{ birthdate: "1997-09-03", name: "Red house authors", royal_percentage: 4.9 }]
/// },
/// {
/// title: "book #2",
/// reviews: [{ content: "Awesome book." }, { content: "Average book." }],
/// publishers: { name: "Pearson Education" },
/// authors: [{ birthdate: "1990-11-04", name: "Penguin Random House", author_name: "William Shakespeare" }]
/// authors: [{ birthdate: "1990-11-04", name: "Penguin Random House", royal_percentage: 8.2 }]
/// }])
/// {
/// items{
Expand Down
25 changes: 25 additions & 0 deletions src/Core/Services/MetadataProviders/ISqlMetadataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,5 +208,30 @@ public DatabaseObject GetDatabaseObjectForGraphQLType(string graphqlType)
void InitializeAsync(
Dictionary<string, DatabaseObject> entityToDatabaseObject,
Dictionary<string, string> graphQLStoredProcedureExposedNameToEntityNameMap);

/// <summary>
/// Helper method to get the Foreign Key definition in the object definition of the source entity which relates it
/// with the target entity. In the Foreign key definition, the table backing the referencing entity acts as the referencing table
/// and the table backing the referenced entity acts as the referenced table.
/// </summary>
/// <param name="sourceEntityName">Source entity name.</param>
/// <param name="targetEntityName">Target entity name.</param>
/// <param name="referencedEntityName">Referenced entity name.</param>
/// <param name="referencingEntityName">Referencing entity name.</param>
/// <param name="foreignKeyDefinition">Stores the required foreign key definition from the referencing to referenced entity.</param>
/// <returns>true when the foreign key definition is successfully determined.</returns>
/// <example>
/// For a 1:N relationship between Publisher: Book entity defined in Publisher entity's config:
/// sourceEntityName: Publisher (The entity in whose config the relationship is defined)
/// targetEntityName: Book (The target.entity in the relationship config)
/// referencingEntityName: Book (Entity holding foreign key reference)
/// referencedEntityName: Publisher (Entity being referenced by foreign key).
/// </example>
public bool TryGetFKDefinition(
string sourceEntityName,
string targetEntityName,
string referencingEntityName,
string referencedEntityName,
[NotNullWhen(true)] out ForeignKeyDefinition? foreignKeyDefinition) => throw new NotImplementedException();
}
}
32 changes: 32 additions & 0 deletions src/Core/Services/MetadataProviders/SqlMetadataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1911,6 +1911,38 @@ public bool IsDevelopmentMode()
{
return _runtimeConfigProvider.GetConfig().IsDevelopmentMode();
}

/// <inheritdoc/>
public bool TryGetFKDefinition(
string sourceEntityName,
string targetEntityName,
string referencingEntityName,
string referencedEntityName,
[NotNullWhen(true)] out ForeignKeyDefinition? foreignKeyDefinition)
{
if (GetEntityNamesAndDbObjects().TryGetValue(sourceEntityName, out DatabaseObject? sourceDbObject) &&
GetEntityNamesAndDbObjects().TryGetValue(referencingEntityName, out DatabaseObject? referencingDbObject) &&
GetEntityNamesAndDbObjects().TryGetValue(referencedEntityName, out DatabaseObject? referencedDbObject))
{
DatabaseTable referencingDbTable = (DatabaseTable)referencingDbObject;
DatabaseTable referencedDbTable = (DatabaseTable)referencedDbObject;
SourceDefinition sourceDefinition = sourceDbObject.SourceDefinition;
RelationShipPair referencingReferencedPair = new(referencingDbTable, referencedDbTable);
List<ForeignKeyDefinition> fKDefinitions = sourceDefinition.SourceEntityRelationshipMap[sourceEntityName].TargetEntityToFkDefinitionMap[targetEntityName];

// At this point, DAB guarantees that a valid foreign key definition exists between the the referencing entity
// and the referenced entity. That's because DAB validates that all foreign key metadata
// was inferred for each relationship during startup.
foreignKeyDefinition = fKDefinitions.FirstOrDefault(
fk => fk.Pair.Equals(referencingReferencedPair) &&
fk.ReferencingColumns.Count > 0
&& fk.ReferencedColumns.Count > 0)!;
return true;
}

foreignKeyDefinition = null;
return false;
}
}
}

0 comments on commit cea72a8

Please sign in to comment.