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

Validation to ensure equality of foreign key definitions inferred from db and config when multiple-create is enabled #2191

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
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
86 changes: 75 additions & 11 deletions src/Core/Services/MetadataProviders/SqlMetadataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1807,14 +1807,19 @@ IEnumerable<List<ForeignKeyDefinition>> foreignKeysForAllTargetEntities
// that this source is related to.
foreach ((string targetEntityName, List<ForeignKeyDefinition> 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<ForeignKeyDefinition> validatedFKDefinitionsToTarget = GetValidatedFKs(fKDefinitionsToTarget);
// Second entry: Referencing table: Target entity, Referenced table: Source entity
if (!TryGetValidatedFKs(fKDefinitionsToTarget, out List<ForeignKeyDefinition> validatedFKDefinitionsToTarget))
{
_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;
}
}
Expand All @@ -1830,33 +1835,54 @@ IEnumerable<List<ForeignKeyDefinition>> foreignKeysForAllTargetEntities
/// the pair of (source, target) entities.
/// </summary>
/// <param name="fKDefinitionsToTarget">List of FK definitions defined from source to target.</param>
/// <returns>List of validated FK definitions from source to target.</returns>
private List<ForeignKeyDefinition> GetValidatedFKs(
List<ForeignKeyDefinition> fKDefinitionsToTarget)
/// <param name="validatedFKDefinitionsToTarget">Stores the validate FK definitions to be returned to the caller.</param>
/// <returns>true if valid foreign key definitions could be determined successfully, else return false.</returns>
private bool TryGetValidatedFKs(
List<ForeignKeyDefinition> fKDefinitionsToTarget, out List<ForeignKeyDefinition> validatedFKDefinitionsToTarget)
{
List<ForeignKeyDefinition> 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
// in the database, either from source->target or target->source entities or both.

// 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
// have been specified in the config file.
// 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
Expand Down Expand Up @@ -1896,7 +1922,45 @@ IEnumerable<List<ForeignKeyDefinition>> foreignKeysForAllTargetEntities
}
}

return validatedFKDefinitionsToTarget;
return true;
}

/// <summary>
/// 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.
/// </summary>
/// <param name="dbFKDefinition">Foreign key definition inferred from the database.</param>
/// <param name="configFKDefinition">Foreign key definition generated based on relationship metadata provided in the config.</param>
/// <returns>true if all the above mentioned conditions are met, else false.</returns>
private static bool AreFKDefinitionsEqual(ForeignKeyDefinition dbFKDefinition, ForeignKeyDefinition configFKDefinition)
{
if (!dbFKDefinition.Pair.Equals(configFKDefinition.Pair) || dbFKDefinition.ReferencingColumns.Count != configFKDefinition.ReferencingColumns.Count)
{
return false;
}

Dictionary<string, string> 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;
}

/// <summary>
Expand Down