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);
}