public Predicate Parse()

in src/Core/Models/GraphQLFilterParsers.cs [56:271]


    public Predicate Parse(
        IMiddlewareContext ctx,
        IInputField filterArgumentSchema,
        List<ObjectFieldNode> fields,
        BaseQueryStructure queryStructure)
    {
        string schemaName = queryStructure.DatabaseObject.SchemaName;
        string sourceName = queryStructure.DatabaseObject.Name;
        string sourceAlias = queryStructure.SourceAlias;
        string entityName = queryStructure.EntityName;
        SourceDefinition sourceDefinition = queryStructure.GetUnderlyingSourceDefinition();

        string dataSourceName = _configProvider.GetConfig().GetDataSourceNameFromEntityName(entityName);
        ISqlMetadataProvider metadataProvider = _metadataProviderFactory.GetMetadataProvider(dataSourceName);

        InputObjectType filterArgumentObject = ExecutionHelper.InputObjectTypeFromIInputField(filterArgumentSchema);

        List<PredicateOperand> predicates = new();
        foreach (ObjectFieldNode field in fields)
        {
            object? fieldValue = ExecutionHelper.ExtractValueFromIValueNode(
                value: field.Value,
                argumentSchema: filterArgumentObject.Fields[field.Name.Value],
                variables: ctx.Variables);

            if (fieldValue is null)
            {
                continue;
            }

            string name = field.Name.ToString();

            bool fieldIsAnd = string.Equals(name, $"{PredicateOperation.AND}", StringComparison.OrdinalIgnoreCase);
            bool fieldIsOr = string.Equals(name, $"{PredicateOperation.OR}", StringComparison.OrdinalIgnoreCase);

            InputObjectType filterInputObjectType = ExecutionHelper.InputObjectTypeFromIInputField(filterArgumentObject.Fields[name]);
            if (fieldIsAnd || fieldIsOr)
            {
                PredicateOperation op = fieldIsAnd ? PredicateOperation.AND : PredicateOperation.OR;
                List<IValueNode> otherPredicates;
                if (fieldValue is IEnumerable<IValueNode>)
                {
                    otherPredicates = (List<IValueNode>)fieldValue;
                }
                else if (fieldValue is IEnumerable<ObjectFieldNode>)
                {
                    ObjectFieldNode fieldObject = ((List<ObjectFieldNode>)fieldValue).First();
                    ObjectValueNode value = new(fieldObject);
                    otherPredicates = new List<IValueNode> { value };
                }
                else
                {
                    throw new DataApiBuilderException(
                        message: "Invalid filter object input value type.",
                        statusCode: HttpStatusCode.BadRequest,
                        subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
                }

                predicates.Push(new PredicateOperand(ParseAndOr(
                    ctx,
                    argumentSchema: filterArgumentObject.Fields[name],
                    filterArgumentSchema: filterArgumentSchema,
                    otherPredicates,
                    queryStructure,
                    op)));
            }
            else
            {
                List<ObjectFieldNode> subfields = (List<ObjectFieldNode>)fieldValue;

                // Preserve the name value present in the filter.
                // In some cases, 'name' represents a relationship field on a source entity,
                // and not a field on a relationship target entity.
                // Additionally, 'name' may not be the same as the relationship's target entity because
                // runtime configuration supports overriding the entity name in the source entity configuration.
                // e.g.
                // comics (Source entity)  series (Target entity)
                // - id                    - id
                // - myseries [series]     - name
                // e.g. GraphQL request
                // {  comics ( filter: { myseries: { name: { eq: 'myName' } } } ) { <field selection> } }
                string backingColumnName = name;

                metadataProvider.TryGetBackingColumn(queryStructure.EntityName, field: name, out string? resolvedBackingColumnName);

                // When runtime configuration defines relationship metadata,
                // an additional field on the entity representing GraphQL type
                // will exist. That relationship field does not represent a column in
                // the representative database table and the relationship field will not have an authorization
                // rule entry. Authorization is handled by permissions defined for the relationship's
                // target entity.
                bool relationshipField = true;
                if (!string.IsNullOrWhiteSpace(resolvedBackingColumnName))
                {
                    backingColumnName = resolvedBackingColumnName;
                    relationshipField = false;
                }

                // Only perform field (column) authorization when the field is not a relationship field.
                // Due to the recursive behavior of SqlExistsQueryStructure compilation, the column authorization
                // check only occurs when access to the column's owner entity is confirmed.
                if (!relationshipField)
                {
                    string graphQLTypeName = queryStructure.EntityName;
                    string originalEntityName = metadataProvider.GetEntityName(graphQLTypeName);

                    bool columnAccessPermitted = queryStructure.AuthorizationResolver.AreColumnsAllowedForOperation(
                        entityName: originalEntityName,
                        roleName: GetHttpContextFromMiddlewareContext(ctx).Request.Headers[CLIENT_ROLE_HEADER].ToString(),
                        operation: EntityActionOperation.Read,
                        columns: new[] { name });

                    if (!columnAccessPermitted)
                    {
                        throw new DataApiBuilderException(
                            message: DataApiBuilderException.GRAPHQL_FILTER_FIELD_AUTHZ_FAILURE,
                            statusCode: HttpStatusCode.Forbidden,
                            subStatusCode: DataApiBuilderException.SubStatusCodes.AuthorizationCheckFailed);
                    }
                }

                // A non-standard InputType is inferred to be referencing an entity.
                // Example: {entityName}FilterInput
                if (!StandardQueryInputs.IsStandardInputType(filterInputObjectType.Name))
                {
                    if (sourceDefinition.PrimaryKey.Count != 0)
                    {
                        // For SQL i.e. when there are primary keys on the source, we need to perform a join
                        // between the parent entity being filtered and the related entity representing the
                        // non-scalar filter input.
                        HandleNestedFilterForSql(
                            ctx,
                            filterArgumentObject.Fields[name],
                            subfields,
                            predicates,
                            queryStructure,
                            metadataProvider);
                    }
                    else if (queryStructure is CosmosQueryStructure cosmosQueryStructure)
                    {
                        // This path will never get called for sql since the primary key will always required
                        // This path will only be exercised for CosmosDb_NoSql
                        FieldDefinitionNode? fieldDefinitionNode = metadataProvider.GetSchemaGraphQLFieldFromFieldName(cosmosQueryStructure.EntityName, name);

                        if (fieldDefinitionNode is null)
                        {
                            throw new DataApiBuilderException(
                                message: "Invalid filter object used as a nested field input value type.",
                                statusCode: HttpStatusCode.BadRequest,
                                subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
                        }

                        string nestedFieldTypeName = fieldDefinitionNode.Type.NamedType().Name.Value;
                        if (fieldDefinitionNode.Type.IsListType())
                        {
                            HandleNestedFilterForCosmos(
                                ctx,
                                filterArgumentObject.Fields[name],
                                subfields,
                                backingColumnName,
                                nestedFieldTypeName,
                                predicates,
                                cosmosQueryStructure,
                                metadataProvider);
                        }
                        else
                        {
                            cosmosQueryStructure.DatabaseObject.Name = sourceName + "." + backingColumnName;
                            cosmosQueryStructure.SourceAlias = sourceName + "." + backingColumnName;
                            cosmosQueryStructure.EntityName = metadataProvider.GetEntityName(nestedFieldTypeName);

                            predicates.Push(new PredicateOperand(Parse(ctx,
                                filterArgumentObject.Fields[name],
                                subfields,
                                cosmosQueryStructure)));

                            cosmosQueryStructure.DatabaseObject.Name = sourceName;
                            cosmosQueryStructure.SourceAlias = sourceAlias;
                        }
                    }
                }
                else
                {
                    bool isListType = false;
                    if (queryStructure is CosmosQueryStructure)
                    {
                        FieldDefinitionNode? fieldDefinitionNode = metadataProvider.GetSchemaGraphQLFieldFromFieldName(queryStructure.EntityName, name);
                        if (fieldDefinitionNode is null)
                        {
                            throw new DataApiBuilderException(
                                message: "Invalid filter object used as a nested field input value type.",
                                statusCode: HttpStatusCode.BadRequest,
                                subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
                        }

                        isListType = fieldDefinitionNode.Type.IsListType();
                    }

                    predicates.Push(
                   new PredicateOperand(
                       ParseScalarType(
                           ctx: ctx,
                           argumentSchema: filterArgumentObject.Fields[name],
                           fieldName: backingColumnName,
                           fields: subfields,
                           schemaName: schemaName,
                           tableName: sourceName,
                           tableAlias: sourceAlias,
                           processLiterals: queryStructure.MakeDbConnectionParam,
                           isListType: isListType)));
                }
            }
        }

        return MakeChainPredicate(predicates, PredicateOperation.AND);
    }