From 5419f2ac0b31c020909773c281a7ed153a099d1a Mon Sep 17 00:00:00 2001 From: Ayush Agarwal Date: Fri, 26 Apr 2024 06:43:01 +0530 Subject: [PATCH 1/2] Validation to ensure equality of fk definitions from db and config when multiple-create is enabled --- .../MetadataProviders/SqlMetadataProvider.cs | 90 ++++++++++++++++--- 1 file changed, 79 insertions(+), 11 deletions(-) diff --git a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs index fd4a763b69..bfae641198 100644 --- a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs @@ -1784,14 +1784,23 @@ private void FillInferredFkInfo( // that this source is related to. foreach ((string targetEntityName, List fKDefinitionsToTarget) in relationshipData.TargetEntityToFkDefinitionMap) { - // // Scenario 1: When a FK constraint is defined between source and target entities in the database. // In this case, there will be exactly one ForeignKeyDefinition with the right pair of Referencing and Referenced tables. // Scenario 2: When no FK constraint is defined between source and target entities, but the relationship fields are configured through config file // In this case, two entries will be created. // First entry: Referencing table: Source entity, Referenced table: Target entity - // Second entry: Referencing table: Target entity, Referenced table: Source entity - List validatedFKDefinitionsToTarget = GetValidatedFKs(fKDefinitionsToTarget); + // Second entry: Referencing table: Target entity, Referenced table: Source entity + if (!TryGetValidatedFKs(fKDefinitionsToTarget, out List validatedFKDefinitionsToTarget)) + { + HandleOrRecordException( + new DataApiBuilderException( + message: $"Cannot support multiple-create due to mismatch in the metadata inferred from the database and the " + + $"metadata inferred from the config for a relationship defined between the source entity: {sourceEntityName} and" + + $" target entity: {targetEntityName}.", + statusCode: HttpStatusCode.BadRequest, + subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization)); + } + relationshipData.TargetEntityToFkDefinitionMap[targetEntityName] = validatedFKDefinitionsToTarget; } } @@ -1807,11 +1816,14 @@ private void FillInferredFkInfo( /// the pair of (source, target) entities. /// /// List of FK definitions defined from source to target. - /// List of validated FK definitions from source to target. - private List GetValidatedFKs( - List fKDefinitionsToTarget) + /// Stores the validate FK definitions to be returned to the caller. + /// true if valid foreign key definitions could be determined successfully, else return false. + private bool TryGetValidatedFKs( + List fKDefinitionsToTarget, out List validatedFKDefinitionsToTarget) { - List validatedFKDefinitionsToTarget = new(); + // Returns true only for MsSql for now when multiple-create is enabled. + bool isMultipleCreateEnabled = _runtimeConfigProvider.GetConfig().IsMultipleCreateOperationEnabled(); + validatedFKDefinitionsToTarget = new(); foreach (ForeignKeyDefinition fKDefinitionToTarget in fKDefinitionsToTarget) { // This code block adds FK definitions between source and target entities when there is an FK constraint defined @@ -1819,7 +1831,7 @@ private List GetValidatedFKs( // Add the referencing and referenced columns for this foreign key definition for the target. if (PairToFkDefinition is not null && - PairToFkDefinition.TryGetValue(fKDefinitionToTarget.Pair, out ForeignKeyDefinition? inferredFKDefinition)) + PairToFkDefinition.TryGetValue(fKDefinitionToTarget.Pair, out ForeignKeyDefinition? dbFKDefinition)) { // Being here indicates that we inferred an FK constraint for the current foreign key definition. // The count of referencing and referenced columns being > 0 indicates that source.fields and target.fields @@ -1827,13 +1839,31 @@ private List GetValidatedFKs( // In this scenario, higher precedence is given to the fields configured through the config file. So, the existing FK definition is retained as is. if (fKDefinitionToTarget.ReferencingColumns.Count > 0 && fKDefinitionToTarget.ReferencedColumns.Count > 0) { - validatedFKDefinitionsToTarget.Add(fKDefinitionToTarget); + if (isMultipleCreateEnabled) + { + if (!AreFKDefinitionsEqual(dbFKDefinition: dbFKDefinition, configFKDefinition: fKDefinitionToTarget)) + { + return false; + } + else + { + // If multiple-create is enabled, preferenced is given to relationship metadata (source.fields/target.fields) + // inferred from the database. + validatedFKDefinitionsToTarget.Add(dbFKDefinition); + } + } + else + { + // If multiple-create is not enabled, preferenced is given to relationship metadata (source.fields/target.fields) + // inferred from the config. + validatedFKDefinitionsToTarget.Add(fKDefinitionToTarget); + } } // The count of referenced and referencing columns being = 0 indicates that source.fields and target.fields // are not configured through the config file. In this case, the FK fields inferred from the database are populated. else { - validatedFKDefinitionsToTarget.Add(inferredFKDefinition); + validatedFKDefinitionsToTarget.Add(dbFKDefinition); } } else @@ -1873,7 +1903,45 @@ private List GetValidatedFKs( } } - return validatedFKDefinitionsToTarget; + return true; + } + + /// + /// Helper method to compare two foreign key definitions inferred from the database and the config for equality on the basis of the + /// referencing -> referenced column mappings present in them. + /// The equality ensures that both the foreign key definitions have: + /// 1. Same set of referencing and referenced tables, + /// 2. Same number of referencing/referenced columns, + /// 3. Same mappings from referencing -> referenced column. + /// + /// Foreign key definition inferred from the database. + /// Foreign key definition generated based on relationship metadata provided in the config. + /// true if all the above mentioned conditions are met, else false. + private static bool AreFKDefinitionsEqual(ForeignKeyDefinition dbFKDefinition, ForeignKeyDefinition configFKDefinition) + { + if (!dbFKDefinition.Pair.Equals(configFKDefinition.Pair) || dbFKDefinition.ReferencingColumns.Count != configFKDefinition.ReferencingColumns.Count) + { + return false; + } + + Dictionary referencingToReferencedColumnsInDb = dbFKDefinition.ReferencingColumns.Zip( + dbFKDefinition.ReferencedColumns, (key, value) => new { Key = key, Value = value }).ToDictionary(item => item.Key, item => item.Value); + + // Traverse through each (referencing, referenced) columns pair in the foreign key definition sourced from the config. + for (int idx = 0; idx < configFKDefinition.ReferencingColumns.Count; idx++) + { + string referencingColumnNameInDb = configFKDefinition.ReferencingColumns[idx]; + if (!referencingToReferencedColumnsInDb.TryGetValue(referencingColumnNameInDb, out string? referencedColumnNameInDb) + || !referencedColumnNameInDb.Equals(configFKDefinition.ReferencedColumns[idx])) + { + // This indicates that either there is no mapping defined for referencingColumnName in the second foreign key definition + // or the referencing -> referenced column mapping in the second foreign key definition do not match the mapping in the first foreign key definition. + // In both the cases, it is implied that the two foreign key definitions do not match. + return false; + } + } + + return true; } /// From 8693242a2d3d855ef823ba409fb3fa80550354c2 Mon Sep 17 00:00:00 2001 From: Ayush Agarwal Date: Wed, 1 May 2024 15:31:31 +0530 Subject: [PATCH 2/2] logging warning instead of throwing exception --- .../Services/MetadataProviders/SqlMetadataProvider.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs index bfae641198..620b4d5ab0 100644 --- a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs @@ -1792,13 +1792,9 @@ private void FillInferredFkInfo( // Second entry: Referencing table: Target entity, Referenced table: Source entity if (!TryGetValidatedFKs(fKDefinitionsToTarget, out List validatedFKDefinitionsToTarget)) { - HandleOrRecordException( - new DataApiBuilderException( - message: $"Cannot support multiple-create due to mismatch in the metadata inferred from the database and the " + - $"metadata inferred from the config for a relationship defined between the source entity: {sourceEntityName} and" + - $" target entity: {targetEntityName}.", - statusCode: HttpStatusCode.BadRequest, - subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization)); + _logger.LogWarning("Cannot support multiple-create due to mismatch in the metadata inferred from the database and the " + + "metadata inferred from the config for a relationship defined between the source entity: {sourceEntityName} and " + + "target entity: {targetEntityName}.", sourceEntityName, targetEntityName); } relationshipData.TargetEntityToFkDefinitionMap[targetEntityName] = validatedFKDefinitionsToTarget;