in src/Core/Resolvers/Sql Query Structures/SqlQueryStructure.cs [369:534]
private SqlQueryStructure(
IMiddlewareContext ctx,
IDictionary<string, object?> queryParams,
ISqlMetadataProvider sqlMetadataProvider,
IAuthorizationResolver authorizationResolver,
IObjectField schemaField,
FieldNode? queryField,
IncrementingInteger counter,
RuntimeConfigProvider runtimeConfigProvider,
GQLFilterParser gQLFilterParser,
string entityName = "")
: this(sqlMetadataProvider,
authorizationResolver,
gQLFilterParser,
predicates: null,
entityName: entityName,
counter: counter
)
{
_ctx = ctx;
IOutputType outputType = schemaField.Type;
_underlyingFieldType = GraphQLUtils.UnderlyingGraphQLEntityType(outputType);
// extract the query argument schemas before switching schemaField to point to *Connetion.items
// since the pagination arguments are not placed on the items, but on the pagination query
IFieldCollection<IInputField> queryArgumentSchemas = schemaField.Arguments;
PaginationMetadata.IsPaginated = QueryBuilder.IsPaginationType(_underlyingFieldType);
if (PaginationMetadata.IsPaginated)
{
if (queryField != null && queryField.SelectionSet != null)
{
// process pagination fields without overriding them
ProcessPaginationFields(queryField.SelectionSet.Selections);
// override schemaField and queryField with the schemaField and queryField of *Connection.items
queryField = ExtractQueryField(queryField);
}
schemaField = ExtractItemsSchemaField(schemaField);
outputType = schemaField.Type;
_underlyingFieldType = GraphQLUtils.UnderlyingGraphQLEntityType(outputType);
// this is required to correctly keep track of which pagination metadata
// refers to what section of the json
// for a paginationless chain:
// getbooks > publisher > books > publisher
// each new entry in the chain corresponds to a subquery so there will be
// a matching pagination metadata object chain
// for a chain with pagination:
// books > items > publisher > books > publisher
// items do not have a matching subquery so the line of code below is
// required to build a pagination metadata chain matching the json result
PaginationMetadata.Subqueries.Add(QueryBuilder.PAGINATION_FIELD_NAME, PaginationMetadata.MakeEmptyPaginationMetadata());
}
EntityName = sqlMetadataProvider.GetDatabaseType() == DatabaseType.DWSQL ? GraphQLUtils.GetEntityNameFromContext(ctx) : _underlyingFieldType.Name;
bool isGroupByQuery = queryField?.Name.Value == QueryBuilder.GROUP_BY_FIELD_NAME;
if (GraphQLUtils.TryExtractGraphQLFieldModelName(_underlyingFieldType.Directives, out string? modelName))
{
EntityName = modelName;
}
DatabaseObject.SchemaName = sqlMetadataProvider.GetSchemaName(EntityName);
DatabaseObject.Name = sqlMetadataProvider.GetDatabaseObjectName(EntityName);
SourceAlias = CreateTableAlias();
// SelectionSet will not be null when a field is not a leaf.
// There may be another entity to resolve as a sub-query.
if (queryField != null && queryField.SelectionSet != null)
{
if (isGroupByQuery)
{
ProcessGroupByField(queryField, ctx);
}
else
{
AddGraphQLFields(queryField.SelectionSet.Selections, runtimeConfigProvider);
}
}
HttpContext httpContext = GraphQLFilterParser.GetHttpContextFromMiddlewareContext(ctx);
// Process Authorization Policy of the entity being processed.
AuthorizationPolicyHelpers.ProcessAuthorizationPolicies(EntityActionOperation.Read, queryStructure: this, httpContext, authorizationResolver, sqlMetadataProvider);
if (outputType.IsNonNullType())
{
IsListQuery = outputType.InnerType().IsListType();
}
else
{
IsListQuery = outputType.IsListType();
}
if (IsListQuery)
{
runtimeConfigProvider.TryGetConfig(out RuntimeConfig? runtimeConfig);
if (queryParams.ContainsKey(QueryBuilder.PAGE_START_ARGUMENT_NAME))
{
// parse first parameter for all list queries
object? firstObject = queryParams[QueryBuilder.PAGE_START_ARGUMENT_NAME];
_limit = runtimeConfig?.GetPaginationLimit((int?)firstObject);
}
else
{
// if first is not passed, we should use the default page size.
_limit = runtimeConfig?.DefaultPageSize();
}
}
if (IsListQuery && queryParams.ContainsKey(QueryBuilder.FILTER_FIELD_NAME))
{
object? filterObject = queryParams[QueryBuilder.FILTER_FIELD_NAME];
if (filterObject is not null)
{
List<ObjectFieldNode> filterFields = (List<ObjectFieldNode>)filterObject;
Predicates.Add(GraphQLFilterParser.Parse(
_ctx,
filterArgumentSchema: queryArgumentSchemas[QueryBuilder.FILTER_FIELD_NAME],
fields: filterFields,
queryStructure: this));
}
}
// primary key should only be added to order by for non groupby queries.
OrderByColumns = isGroupByQuery ? [] : PrimaryKeyAsOrderByColumns();
if (IsListQuery && queryParams.ContainsKey(QueryBuilder.ORDER_BY_FIELD_NAME))
{
object? orderByObject = queryParams[QueryBuilder.ORDER_BY_FIELD_NAME];
if (orderByObject is not null)
{
OrderByColumns = ProcessGqlOrderByArg((List<ObjectFieldNode>)orderByObject, queryArgumentSchemas[QueryBuilder.ORDER_BY_FIELD_NAME], isGroupByQuery);
}
}
// need to run after the rest of the query has been processed since it relies on
// TableName, SourceAlias, Columns, and _limit
if (PaginationMetadata.IsPaginated)
{
AddPaginationPredicate(SqlPaginationUtil.ParseAfterFromQueryParams(queryParams, PaginationMetadata, sqlMetadataProvider, EntityName, runtimeConfigProvider));
if (PaginationMetadata.RequestedEndCursor)
{
AddColumnsForEndCursor(isGroupByQuery);
}
if (PaginationMetadata.RequestedHasNextPage || PaginationMetadata.RequestedEndCursor)
{
_limit++;
}
}
// If there are no columns, add the primary key column
// to prevent failures when executing the database query.
if (!Columns.Any() && !isGroupByQuery)
{
AddColumn(PrimaryKey()[0]);
}
ParametrizeColumns();
}