Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ public int getDefaultLimit() {
return sqrlConfig.asInt("default-limit").get();
}

@Override
public boolean generatePaginatedResults() {
return sqrlConfig.asBool("paginated-results").withDefault(false).get();
}

public enum Endpoints {
OPS_ONLY, // only support the pre-defined operations in the GraphQL API, do not support flexible
// GraphQL queries TODO: not yet implemented
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.datasqrl.server.ApiSource;
import com.datasqrl.server.ApiSources;
import com.datasqrl.server.GraphqlSchemaHandler;
import com.datasqrl.server.OffsetPageInfoUtil;
import com.datasqrl.server.ScriptFiles;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -75,9 +76,17 @@ public LoadResult load(ServerPhysicalPlan serverPlan) {
}

if (!shouldUseInferredSchema(apiVersions)) {
apiVersions.forEach(
apiVersion -> graphqlSchemaHandler.validateSchema(apiVersion, serverPlan));
return new LoadResult(apiVersions, Optional.empty());
var injected =
apiVersions.stream()
.map(
apiVersion ->
new ApiSources(
apiVersion.version(),
OffsetPageInfoUtil.injectPaginationType(apiVersion.schema()),
Comment thread
velo marked this conversation as resolved.
Outdated
apiVersion.operations()))
.toList();
injected.forEach(apiVersion -> graphqlSchemaHandler.validateSchema(apiVersion, serverPlan));
return new LoadResult(injected, Optional.empty());
}

List<ApiSource> operations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ interface CompilerApiConfig {
int getMaxResultDepth();

int getDefaultLimit();

boolean generatePaginatedResults();
}

interface ExplainConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ protected void visitQuery(
ObjectTypeDefinition parentType,
FieldDefinition atField,
SqrlTableFunction tableFunction,
TypeDefinitionRegistry registry) {
TypeDefinitionRegistry registry,
boolean paged) {
// As we no more merge user provided graphQL schema with the inferred schema, we no more need to
// generate as many queries as the permutations of its arguments.
// We now have a single executable query linked to the table function and already fully defined
Expand All @@ -168,13 +169,18 @@ protected void visitQuery(
.map(InputValueDefinition::getName)
.anyMatch(
name -> name.equals(SchemaConstants.LIMIT) || name.equals(SchemaConstants.OFFSET));
var countSql = paged ? buildCountSql(executableJdbcReadQuery.getSql()) : null;
Comment thread
velo marked this conversation as resolved.
Outdated
var countWithEventTimesSql =
paged ? buildCountWithEventTimesSql(tableFunction, executableJdbcReadQuery.getSql()) : null;
queryBase =
new SqlQuery(
executableJdbcReadQuery.getSql(),
parameters,
hasLimitOrOffset ? PaginationType.LIMIT_AND_OFFSET : PaginationType.NONE,
executableJdbcReadQuery.getCacheDuration().toMillis(),
executableJdbcReadQuery.getDatabase());
executableJdbcReadQuery.getDatabase(),
countSql,
countWithEventTimesSql);
var coordsBuilder =
ArgumentLookupQueryCoords.builder()
.parentType(parentType.getName())
Expand All @@ -186,6 +192,34 @@ protected void visitQuery(
queryCoords.add(coordsBuilder.build());
}

/** Builds the companion COUNT(*) query for a paginated result. */
private static String buildCountSql(String baseSql) {
return "SELECT COUNT(*) AS \"total_records\" FROM (" + baseSql + ") x";
}

/**
* Variant of the count query that also computes MIN/MAX over the designated rowtime column for
* {@code firstEventTime}/{@code lastEventTime}. Returns null when the result has no rowtime. The
* rowtime column name is the same identifier as in the base query's output.
*/
private static String buildCountWithEventTimesSql(
SqrlTableFunction tableFunction, String baseSql) {
return tableFunction
.getRowTime()
.map(tableFunction::getField)
.map(RelDataTypeField::getName)
.map(
col ->
"SELECT COUNT(*) AS \"total_records\", MIN(\""
+ col
+ "\") AS \"first_event_time\", MAX(\""
+ col
+ "\") AS \"last_event_time\" FROM ("
+ baseSql
+ ") x")
.orElse(null);
}
Comment thread
velo marked this conversation as resolved.
Outdated

private static QueryParameterHandler convert(FunctionParameter fnParam) {
final var sqrlParam = (SqrlFunctionParameter) fnParam;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import graphql.schema.GraphQLDirective;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLNonNull;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLSchema;
Expand Down Expand Up @@ -72,11 +73,13 @@ public class GraphqlSchemaFactory {
private final boolean extendedScalarTypes;
private final boolean addApiDirective;
private final int defaultLimit;
private final boolean generatePaginatedResults;

public GraphqlSchemaFactory(CompilerConfig config) {
this.extendedScalarTypes = config.isExtendedScalarTypes();
this.addApiDirective = !config.getApiConfig().isGraphQLProtocolOnly();
this.defaultLimit = config.getApiConfig().getDefaultLimit();
this.generatePaginatedResults = config.getApiConfig().generatePaginatedResults();
}

public GraphQLSchema generate(ServerPhysicalPlan serverPlan) {
Expand Down Expand Up @@ -284,9 +287,7 @@ private Optional<GraphQLObjectType> createRootType(

final var type =
tableFunctionsType == AccessModifier.QUERY
? (GraphQLOutputType)
wrapMultiplicity(
createTypeReference(tableFunction), tableFunction.getMultiplicity())
? createQueryResultType(tableFunction)
: createTypeReference(
tableFunction); // type is nullable because there can be no update in the
// subscription
Expand Down Expand Up @@ -346,13 +347,15 @@ private Optional<GraphQLFieldDefinition> createRelationshipField(SqrlTableFuncti
}

// reference the type that will be defined when the table function relationship is processed
var fieldType =
relationship.getVisibility().access() == AccessModifier.QUERY
? createQueryResultType(relationship)
: (GraphQLOutputType)
wrapMultiplicity(createTypeReference(relationship), relationship.getMultiplicity());
var field =
GraphQLFieldDefinition.newFieldDefinition()
.name(fieldName)
.type(
(GraphQLOutputType)
wrapMultiplicity(
createTypeReference(relationship), relationship.getMultiplicity()))
.type(fieldType)
.arguments(createArguments(relationship));
relationship.getDocumentation().getDocStringOpt().ifPresent(field::description);

Expand Down Expand Up @@ -432,6 +435,44 @@ private GraphQLOutputType createTypeReference(SqrlTableFunction tableFunction) {
return new GraphQLTypeReference(typeName);
}

/**
* Result type of a query table function: when paginated results are enabled, MANY results are
* wrapped in a {@code <Type>Page {results, pagination}} type so the server computes {@code
* OffsetPageInfo} metadata; otherwise the plain multiplicity-wrapped type.
*/
private GraphQLOutputType createQueryResultType(SqrlTableFunction tableFunction) {
if (generatePaginatedResults && tableFunction.getMultiplicity() == Multiplicity.MANY) {
return GraphQLNonNull.nonNull(createPageType(tableFunction));
}
return (GraphQLOutputType)
wrapMultiplicity(createTypeReference(tableFunction), tableFunction.getMultiplicity());
}

private GraphQLTypeReference createPageType(SqrlTableFunction tableFunction) {
var elementType = (GraphQLTypeReference) createTypeReference(tableFunction);
var pageTypeName = elementType.getName() + "Page";
if (!definedTypeNames.contains(pageTypeName)) {
definedTypeNames.add(pageTypeName);
objectTypes.add(
GraphQLObjectType.newObject()
.name(pageTypeName)
.field(
GraphQLFieldDefinition.newFieldDefinition()
.name("results")
.type((GraphQLOutputType) wrapMultiplicity(elementType, Multiplicity.MANY)))
.field(
GraphQLFieldDefinition.newFieldDefinition()
.name("pagination")
.type(new GraphQLTypeReference(OffsetPageInfoUtil.PAGINATION_TYPE_NAME)))
.build());
if (!definedTypeNames.contains(OffsetPageInfoUtil.PAGINATION_TYPE_NAME)) {
definedTypeNames.add(OffsetPageInfoUtil.PAGINATION_TYPE_NAME);
objectTypes.add(OffsetPageInfoUtil.createPageInfoType());
}
}
return new GraphQLTypeReference(pageTypeName);
}

public static final String API_DIRECTIVE_NAME = "api";

private void addApiDirectiveTypes(GraphQLSchema.Builder graphQLSchemaBuilder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,23 @@ protected void visitQuery(
ObjectTypeDefinition parentType,
FieldDefinition atField,
SqrlTableFunction tableFunction,
TypeDefinitionRegistry registry) {
TypeDefinitionRegistry registry,
boolean paged) {
checkValidArrayNonNullType(atField.getType());
if (paged) {
OffsetPageInfoUtil.validatePaginationType(registry);
var argNames =
atField.getInputValueDefinitions().stream()
.map(InputValueDefinition::getName)
.collect(Collectors.toSet());
checkState(
argNames.contains(LIMIT) && argNames.contains(OFFSET),
atField.getSourceLocation(),
"Paginated query [%s] must declare both '%s' and '%s' arguments",
atField.getName(),
LIMIT,
OFFSET);
}
checkArgumentsMatchParameters(atField, tableFunction, registry);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import com.datasqrl.canonicalizer.Name;
import com.datasqrl.canonicalizer.NamePath;
import com.datasqrl.plan.table.Multiplicity;
import com.datasqrl.planner.dag.plan.MutationTable;
import com.datasqrl.planner.parser.AccessModifier;
import com.datasqrl.planner.tables.SqrlTableFunction;
Expand Down Expand Up @@ -138,14 +139,34 @@ private void walkTableFunction(
typeDefinition.getSourceLocation(),
"Could not infer non-object type on graphql schema: %s",
typeDefinition.getName());
var resultType = (ObjectTypeDefinition) typeDefinition;

// A page wrapper ({results: [Element!], pagination: OffsetPageInfo}) is treated like a list of
// Element: validate/walk the element type against the function row type and compute pagination
// metadata via a companion count query.
var pagedElement = OffsetPageInfoUtil.getPagedElementType(resultType, registry);
var paged = pagedElement.isPresent();
if (paged) {
checkState(
tableFunction.getVisibility().access() == AccessModifier.QUERY,
atField.getSourceLocation(),
"Paginated result types are only supported for queries: %s",
atField.getName());
checkState(
tableFunction.getMultiplicity() == Multiplicity.MANY,
atField.getSourceLocation(),
"Paginated result types require a multi-row result (no LIMIT 1): %s",
atField.getName());
resultType = pagedElement.get();
}

if (tableFunction.getVisibility().access()
== AccessModifier.QUERY) { // walking a query table function
visitQuery(parentType, atField, tableFunction, registry);
visitQuery(parentType, atField, tableFunction, registry, paged);
} else { // walking a subscription table function
visitSubscription(atField, tableFunction, registry);
}
var functionRowType = tableFunction.getRowType();
var resultType = (ObjectTypeDefinition) typeDefinition;
walkObjectType(true, resultType, Optional.of(functionRowType), registry);
}

Expand Down Expand Up @@ -257,7 +278,8 @@ protected abstract void visitQuery(
ObjectTypeDefinition parentType,
FieldDefinition atField,
SqrlTableFunction tableFunction,
TypeDefinitionRegistry registry);
TypeDefinitionRegistry registry,
boolean paged);

protected abstract void visitSubscription(
FieldDefinition atField, SqrlTableFunction tableFunction, TypeDefinitionRegistry registry);
Expand Down
Loading