private void ProcessRelationshipField()

in src/Core/Services/MultipleMutationInputValidator.cs [326:470]


        private void ProcessRelationshipField(
            IMiddlewareContext context,
            ISqlMetadataProvider metadataProvider,
            Dictionary<string, IValueNode?> backingColumnData,
            Dictionary<string, string> derivableColumnsFromRequestBody,
            Dictionary<string, HashSet<string>> fieldsToSupplyToReferencingEntities,
            Dictionary<string, HashSet<string>> fieldsToDeriveFromReferencedEntities,
            string relationshipName,
            IValueNode? relationshipFieldValue,
            int nestingLevel,
            MultipleMutationEntityInputValidationContext multipleMutationEntityInputValidationContext)
        {
            RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig();
            string entityName = multipleMutationEntityInputValidationContext.EntityName;
            // When the source of a referencing field in current entity is a relationship, the relationship's name is added to the value in
            // the KV pair of (referencing column, source) in derivableColumnsFromRequestBody with a prefix '$' so that if the relationship name
            // conflicts with the current entity's name or the parent entity's name, we are able to distinguish
            // with the help of this identifier. It should be noted that the identifier is not allowed in the names
            // of entities exposed in DAB.
            const string relationshipSourceIdentifier = "$";
            string targetEntityName = runtimeConfig.Entities![entityName].Relationships![relationshipName].TargetEntity;
            string? linkingObject = runtimeConfig.Entities![entityName].Relationships![relationshipName].LinkingObject;
            bool isMNRelationship = !string.IsNullOrWhiteSpace(linkingObject);

            // Determine the referencing entity for the current relationship field input.
            string referencingEntityName = MultipleCreateOrderHelper.GetReferencingEntityName(
                relationshipName: relationshipName,
                context: context,
                sourceEntityName: entityName,
                targetEntityName: targetEntityName,
                metadataProvider: metadataProvider,
                columnDataInSourceBody: backingColumnData,
                targetNodeValue: relationshipFieldValue,
                nestingLevel: nestingLevel,
                isMNRelationship: isMNRelationship);

            if (isMNRelationship)
            {
                // The presence of a linking object indicates that an M:N relationship exists between the current entity and the target/child entity.
                // The linking table acts as a referencing table for both the source/target entities which act as
                // referenced entities. Consequently:
                // - Column values for the target entity can't be derived from insertion in the current entity.
                // - Column values for the current entity can't be derived from the insertion in the target/child entity.
                return;
            }

            // Determine the referenced entity.
            string referencedEntityName = referencingEntityName.Equals(entityName) ? targetEntityName : entityName;

            // Get the required foreign key definition with the above inferred referencing and referenced entities.
            if (!metadataProvider.TryGetFKDefinition(
                sourceEntityName: entityName,
                targetEntityName: targetEntityName,
                referencingEntityName: referencingEntityName,
                referencedEntityName: referencedEntityName,
                foreignKeyDefinition: out ForeignKeyDefinition? fkDefinition,
                isMToNRelationship: false))
            {
                // This should not be hit ideally.
                throw new DataApiBuilderException(
                    message: $"Could not resolve relationship metadata for source: {entityName} and target: {targetEntityName} entities for " +
                    $"relationship: {relationshipName} at level: {nestingLevel}",
                    statusCode: HttpStatusCode.BadRequest,
                    subStatusCode: DataApiBuilderException.SubStatusCodes.RelationshipNotFound);
            }

            // Validate that one column in the referencing entity is not referencing multiple columns in the referenced entity
            // to avoid conflicting sources of truth for the value of referencing column.
            IEnumerable<string> listOfRepeatedReferencingFields = GetListOfRepeatedExposedReferencingColumns(
                    referencingEntityName: referencingEntityName,
                    referencingColumns: fkDefinition.ReferencingColumns,
                    metadataProvider: metadataProvider);

            if (listOfRepeatedReferencingFields.Count() > 0)
            {
                string repeatedReferencingFields = "{" + string.Join(", ", listOfRepeatedReferencingFields) + "}";
                // This indicates one column is holding reference to multiple referenced columns in the related entity,
                // which leads to possibility of two conflicting sources of truth for this column.
                // This is an invalid use case for multiple-create.
                throw new DataApiBuilderException(
                    message: $"The field(s): {repeatedReferencingFields} in the entity: {referencingEntityName} reference(s) multiple field(s) in the " +
                    $"related entity: {referencedEntityName} for the relationship: {relationshipName} at level: {nestingLevel}.",
                    statusCode: HttpStatusCode.BadRequest,
                    subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
            }

            // The current entity is the referencing entity.
            if (referencingEntityName.Equals(entityName))
            {
                for (int idx = 0; idx < fkDefinition.ReferencingColumns.Count; idx++)
                {
                    string referencingColumn = fkDefinition.ReferencingColumns[idx];
                    string referencedColumn = fkDefinition.ReferencedColumns[idx];

                    // The input data for current entity should not specify a value for a referencing column -
                    // as it's value will be derived from the insertion in the referenced (target) entity.
                    if (derivableColumnsFromRequestBody.TryGetValue(referencingColumn, out string? referencingColumnSource))
                    {
                        string conflictingSource;
                        if (referencingColumnSource.StartsWith(relationshipSourceIdentifier))
                        {
                            // If the source name starts with "$", this indicates the source for the referencing column
                            // was another relationship.
                            conflictingSource = "Relationship: " + referencingColumnSource.Substring(relationshipSourceIdentifier.Length);
                        }
                        else
                        {
                            conflictingSource = referencingColumnSource.Equals(multipleMutationEntityInputValidationContext.ParentEntityName) ? $"Parent entity: {referencingColumnSource}" : $"entity: {entityName}";
                        }

                        metadataProvider.TryGetExposedColumnName(entityName, referencingColumn, out string? exposedColumnName);
                        throw new DataApiBuilderException(
                            message: $"Found conflicting sources providing a value for the field: {exposedColumnName} for entity: {entityName} at level: {nestingLevel}." +
                            $"Source 1: {conflictingSource}, Source 2: Relationship: {relationshipName}.",
                            statusCode: HttpStatusCode.BadRequest,
                            subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
                    }

                    // Case 2: When a column whose value is to be derived from the insertion in current entity
                    // (happens when the parent entity is a referencing entity in a relationship with current entity),
                    // is a referencing column in the current relationship, we pass on the responsibility of getting the value
                    // of such a column to the target entity in the current relationship.
                    if (multipleMutationEntityInputValidationContext.ColumnsToBeDerivedFromEntity.Contains(referencingColumn))
                    {
                        // We optimistically assume that we will get the value of the referencing column
                        // from the insertion in the target entity.
                        multipleMutationEntityInputValidationContext.ColumnsToBeDerivedFromEntity.Remove(referencingColumn);
                    }

                    // Resolve the field(s) whose value(s) will be sourced from the creation of record in the current relationship's target entity.
                    fieldsToDeriveFromReferencedEntities.TryAdd(relationshipName, new());
                    fieldsToDeriveFromReferencedEntities[relationshipName].Add(referencedColumn);

                    // Value(s) for the current entity's referencing column(s) are sourced from the creation
                    // of record in the current relationship's target entity.
                    derivableColumnsFromRequestBody.TryAdd(referencingColumn, relationshipSourceIdentifier + relationshipName);
                }
            }
            else
            {
                // Keep track of the set of referencing columns in the target (referencing) entity which will get their value sourced from insertion
                // in the current entity.
                fieldsToSupplyToReferencingEntities.Add(relationshipName, new(fkDefinition.ReferencingColumns));
            }
        }