diff --git a/documentation/docs/configuration-default.md b/documentation/docs/configuration-default.md index bf98faef51..9f3d6515c0 100644 --- a/documentation/docs/configuration-default.md +++ b/documentation/docs/configuration-default.md @@ -10,7 +10,7 @@ The following is the [default configuration file](https://raw.githubusercontent. "logger": "print", "compile-flink-plan": true, "extended-scalar-types": true, - "predicate-pushdown-rules": "LIMITED_TABLE_SOURCE_RULES", + "predicate-pushdown-rules": "LIMITED_RULES_NO_SOURCE", "cost-model": "DEFAULT", "explain": { "sql": false, @@ -23,7 +23,8 @@ The following is the [default configuration file](https://raw.githubusercontent. "endpoints": "FULL", "add-prefix": true, "max-result-depth": 3, - "default-limit": 10 + "default-limit": 10, + "paginated-results": false } }, "engines": { @@ -31,7 +32,8 @@ The following is the [default configuration file](https://raw.githubusercontent. "config": { "execution.runtime-mode": "STREAMING", "security.delegation.tokens.enabled": false, - "state.backend.type": "rocksdb" + "state.backend.type": "rocksdb", + "table.exec.sink.require-on-conflict": false } }, "duckdb": { diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index 08141d17c1..f48ba68454 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -162,7 +162,8 @@ Configuration options that control the compiler, such as where logging output is "endpoints": "FULL", // endpoint generation strategy ("FULL", "GRAPHQL", "OPS_ONLY") "add-prefix": true, // add an operation-type prefix to function names to ensure uniqueness "max-result-depth": 3, // maximum depth of graph traversal when generating operations from a schema - "default-limit": 10 // default query result limit + "default-limit": 10, // default query result limit + "paginated-results": false // wrap generated query results in a page with pagination metadata } } } diff --git a/documentation/docs/interface.md b/documentation/docs/interface.md index ed405b60d8..b4c31ec151 100644 --- a/documentation/docs/interface.md +++ b/documentation/docs/interface.md @@ -67,6 +67,53 @@ You can customize the GraphQL schema by: The compiler raises errors when the provided GraphQL schema is not compatible with the object-relationship model. ::: +#### Pagination + +Every generated query endpoint takes `limit` and `offset` arguments to page through the result (`limit` defaults to the configured `default-limit`). By default the endpoint returns the rows directly and the client tracks the offsets itself. + +Set `paginated-results` to `true` in the [`api` compiler configuration](configuration.md#compiler-compiler) to get pagination metadata alongside the rows. The generated schema then wraps every multi-row query result in a page type: + +```graphql +type PersonPage { + results: [Person!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} +``` + +A query then selects the rows and the metadata it needs: + +```graphql +query GetPeople { + Person(limit: 10, offset: 20) { + results { name email } + pagination { currentPage hasNextPage nextOffset } + } +} +``` + +`firstEventTime` and `lastEventTime` are the earliest and latest event time of the *entire* result set, not of the returned page. They are `null` when the query result has no event time (rowtime) column. + +If you provide your own GraphQL schema, pagination is opt-in per query: give the query a result type with exactly two fields — a list of the result type, and a field of type `OffsetPageInfo` — and declare `OffsetPageInfo` exactly as shown above. The field names of the wrapper type are up to you. The compiler validates that: +* the `OffsetPageInfo` type is declared and matches the definition above +* the paginated query declares both a `limit` and an `offset` argument +* the query returns multiple rows (i.e. it is not restricted to a single row) and is a query, not a subscription + +The server computes only the metadata a request actually selects, so paginated queries cost no more than unpaginated ones unless you ask for more: +* `pageSize`, `currentPage`, `hasPreviousPage`, and `prevOffset` are derived from the request arguments and cost nothing. +* `hasNextPage` and `nextOffset` make the query fetch one extra row, which is discarded before the results are returned. Without a `limit` argument the page holds every remaining row and `hasNextPage` is `false`. +* `firstEventTime` and `lastEventTime` run a second `MIN`/`MAX` query over the event time column. The compiler adds an index on that column for paginated queries. + #### Authoritative Model DataSQRL uses the GraphQL schemas the authoritative model for all API protocols. It serves as the foundational model on which operations, endpoints, and access patterns are defined. This simplifies the conceptual model and server execution since any API operation maps to a GraphQL query which is executed by a centralized and optimized GraphQL engine. diff --git a/sqrl-cli/src/main/java/com/datasqrl/compile/CompilationProcess.java b/sqrl-cli/src/main/java/com/datasqrl/compile/CompilationProcess.java index 9b190e0964..f5d566ee10 100644 --- a/sqrl-cli/src/main/java/com/datasqrl/compile/CompilationProcess.java +++ b/sqrl-cli/src/main/java/com/datasqrl/compile/CompilationProcess.java @@ -71,8 +71,6 @@ public Pair executeCompilation(Optional testsPath) var dagBuilder = planner.getDagBuilder(); var dag = dagPlanner.optimize(dagBuilder.getDag()); var physicalPlan = dagPlanner.assemble(dag, environment); - var rewriters = ServiceLoaderDiscovery.getAll(PhysicalPlanRewriter.class); - physicalPlan = physicalPlan.applyRewriting(rewriters, environment); var mutationDatabase = physicalPlan.getMutationDatabase(); writeDeploymentArtifactsHook.run(dag, planner.getCompleteScript().toString(), mutationDatabase); @@ -121,6 +119,9 @@ public Pair executeCompilation(Optional testsPath) } } + var rewriters = ServiceLoaderDiscovery.getAll(PhysicalPlanRewriter.class); + physicalPlan = physicalPlan.applyRewriting(rewriters, environment); + // Read database file if configured and check compatibility mainScript .getMutationDatabase() diff --git a/sqrl-planner/src/main/java/com/datasqrl/config/CompilerApiConfigImpl.java b/sqrl-planner/src/main/java/com/datasqrl/config/CompilerApiConfigImpl.java index 367aeed8a3..76bd9545fc 100644 --- a/sqrl-planner/src/main/java/com/datasqrl/config/CompilerApiConfigImpl.java +++ b/sqrl-planner/src/main/java/com/datasqrl/config/CompilerApiConfigImpl.java @@ -57,6 +57,11 @@ public int getDefaultLimit() { return sqrlConfig.asInt("default-limit").get(); } + @Override + public boolean generatePaginatedResults() { + return sqrlConfig.asBool("paginated-results").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 diff --git a/sqrl-planner/src/main/java/com/datasqrl/config/PackageJson.java b/sqrl-planner/src/main/java/com/datasqrl/config/PackageJson.java index 680dcddd5c..ac989c0bb0 100644 --- a/sqrl-planner/src/main/java/com/datasqrl/config/PackageJson.java +++ b/sqrl-planner/src/main/java/com/datasqrl/config/PackageJson.java @@ -79,6 +79,8 @@ interface CompilerApiConfig { int getMaxResultDepth(); int getDefaultLimit(); + + boolean generatePaginatedResults(); } interface ExplainConfig { diff --git a/sqrl-planner/src/main/java/com/datasqrl/engine/PhysicalPlan.java b/sqrl-planner/src/main/java/com/datasqrl/engine/PhysicalPlan.java index 3031e9bc41..de4233a8aa 100644 --- a/sqrl-planner/src/main/java/com/datasqrl/engine/PhysicalPlan.java +++ b/sqrl-planner/src/main/java/com/datasqrl/engine/PhysicalPlan.java @@ -56,8 +56,8 @@ public PhysicalPlan applyRewriting( for (PhysicalStagePlan stagePlan : stagePlans) { var enginePlan = stagePlan.plan; for (PhysicalPlanRewriter rewriter : rewriters) { - if (rewriter.appliesTo(enginePlan)) { - enginePlan = rewriter.rewrite(enginePlan, sqrlEnv); + if (rewriter.satisfied(this) && rewriter.appliesTo(enginePlan)) { + enginePlan = rewriter.rewrite(this, enginePlan, sqrlEnv); } } builder.stagePlan(new PhysicalStagePlan(stagePlan.stage, enginePlan)); diff --git a/sqrl-planner/src/main/java/com/datasqrl/engine/server/ServerPhysicalPlan.java b/sqrl-planner/src/main/java/com/datasqrl/engine/server/ServerPhysicalPlan.java index 7331cc761f..b9b9883803 100644 --- a/sqrl-planner/src/main/java/com/datasqrl/engine/server/ServerPhysicalPlan.java +++ b/sqrl-planner/src/main/java/com/datasqrl/engine/server/ServerPhysicalPlan.java @@ -16,13 +16,16 @@ package com.datasqrl.engine.server; import com.datasqrl.engine.EnginePhysicalPlan; +import com.datasqrl.planner.analyzer.TableAnalysis; import com.datasqrl.planner.dag.plan.MutationTable; import com.datasqrl.planner.tables.SqrlTableFunction; import com.datasqrl.server.graphql.RootGraphQLModel; import com.fasterxml.jackson.annotation.JsonIgnore; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -44,4 +47,10 @@ public class ServerPhysicalPlan implements EnginePhysicalPlan { * plan later. */ final Map models = new LinkedHashMap<>(); + + /** + * Base tables of paginated queries, collected during model generation so a rowtime index can be + * added to their physical tables afterwards. + */ + @JsonIgnore final Set pagedRowTimeTables = new LinkedHashSet<>(); } diff --git a/sqrl-planner/src/main/java/com/datasqrl/plan/global/JdbcIndexOptimization.java b/sqrl-planner/src/main/java/com/datasqrl/plan/global/JdbcIndexOptimization.java index 78a9082c4c..dddb99949a 100644 --- a/sqrl-planner/src/main/java/com/datasqrl/plan/global/JdbcIndexOptimization.java +++ b/sqrl-planner/src/main/java/com/datasqrl/plan/global/JdbcIndexOptimization.java @@ -16,6 +16,7 @@ package com.datasqrl.plan.global; import com.datasqrl.engine.EnginePhysicalPlan; +import com.datasqrl.engine.PhysicalPlan; import com.datasqrl.engine.database.relational.AbstractJDBCDatabaseEngine; import com.datasqrl.engine.database.relational.JdbcPhysicalPlan; import com.datasqrl.planner.Sqrl2FlinkSQLTranslator; @@ -32,14 +33,16 @@ public class JdbcIndexOptimization implements PhysicalPlanRewriter { @Override - public boolean appliesTo(EnginePhysicalPlan plan) { - return plan instanceof JdbcPhysicalPlan jpp + public boolean appliesTo(EnginePhysicalPlan enginePlan) { + return enginePlan instanceof JdbcPhysicalPlan jpp && jpp.stage().engine() instanceof AbstractJDBCDatabaseEngine; } @Override - public JdbcPhysicalPlan rewrite(EnginePhysicalPlan plan, Sqrl2FlinkSQLTranslator sqrlEnv) { - var jdbcPlan = (JdbcPhysicalPlan) plan; + public JdbcPhysicalPlan rewrite( + PhysicalPlan fullPlan, EnginePhysicalPlan enginePlan, Sqrl2FlinkSQLTranslator sqrlEnv) { + + var jdbcPlan = (JdbcPhysicalPlan) enginePlan; var engine = (AbstractJDBCDatabaseEngine) jdbcPlan.stage().engine(); /*TODO: optimize the order of primary key columns (unless primary key is explicitly defined by hint) - partition keys come first diff --git a/sqrl-planner/src/main/java/com/datasqrl/plan/global/PagedRowTimeIndexRewriter.java b/sqrl-planner/src/main/java/com/datasqrl/plan/global/PagedRowTimeIndexRewriter.java new file mode 100644 index 0000000000..8894a524e3 --- /dev/null +++ b/sqrl-planner/src/main/java/com/datasqrl/plan/global/PagedRowTimeIndexRewriter.java @@ -0,0 +1,108 @@ +/* + * Copyright © 2021 DataSQRL (contact@datasqrl.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datasqrl.plan.global; + +import com.datasqrl.deployment.model.JdbcStatementModel.Type; +import com.datasqrl.engine.EnginePhysicalPlan; +import com.datasqrl.engine.PhysicalPlan; +import com.datasqrl.engine.database.relational.AbstractJDBCDatabaseEngine; +import com.datasqrl.engine.database.relational.JdbcPhysicalPlan; +import com.datasqrl.engine.database.relational.JdbcStatement; +import com.datasqrl.engine.server.ServerPhysicalPlan; +import com.datasqrl.planner.Sqrl2FlinkSQLTranslator; +import com.google.auto.service.AutoService; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Adds a btree index on the rowtime column of every physical table that backs a paginated ({@code + * OffsetPageInfo}) query. Those queries run a companion {@code MIN/MAX(rowtime)} aggregate for + * their {@code first/lastEventTime}; the btree lets the database answer it from the index endpoints + * instead of scanning the table. + */ +@AutoService(PhysicalPlanRewriter.class) +public class PagedRowTimeIndexRewriter implements PhysicalPlanRewriter { + + @Override + public boolean appliesTo(EnginePhysicalPlan enginePlan) { + return enginePlan instanceof JdbcPhysicalPlan jpp + && jpp.stage().engine() instanceof AbstractJDBCDatabaseEngine; + } + + @Override + public boolean satisfied(PhysicalPlan fullPlan) { + return getServerPlan(fullPlan).isPresent(); + } + + @Override + public JdbcPhysicalPlan rewrite( + PhysicalPlan fullPlan, EnginePhysicalPlan enginePlan, Sqrl2FlinkSQLTranslator sqrlEnv) { + var jdbcPlan = (JdbcPhysicalPlan) enginePlan; + var serverPlan = + getServerPlan(fullPlan) + .orElseThrow(() -> new IllegalStateException("Server physical plan is missing")); + + var pagedTables = serverPlan.getPagedRowTimeTables(); + if (pagedTables.isEmpty()) { + return jdbcPlan; + } + + var engine = (AbstractJDBCDatabaseEngine) jdbcPlan.stage().engine(); + if (!engine.getIndexSelectorConfig().supportedIndexTypes().contains(IndexType.BTREE)) { + return jdbcPlan; + } + var stmtFactory = engine.getStatementFactory(); + // Existing indexes (e.g. from JdbcIndexOptimization) may already cover the rowtime column. + var existingIndexNames = + jdbcPlan.getStatementsForType(Type.INDEX).stream() + .map(JdbcStatement::getName) + .collect(Collectors.toSet()); + + var builder = jdbcPlan.toBuilder(); + for (var createTbl : jdbcPlan.tableIdMap().values()) { + var engineTable = createTbl.getEngineTable(); + var tableAnalysis = engineTable.tableAnalysis(); + if (!pagedTables.contains(tableAnalysis) + && !pagedTables.contains(tableAnalysis.getBaseTable())) { + continue; + } + + var rowTime = tableAnalysis.getRowTime(); + if (rowTime.isEmpty()) { + continue; + } + + var index = + new IndexDefinition( + engineTable.tableName(), + List.of(rowTime.get()), + tableAnalysis.getRowType().getFieldNames(), + -1, + IndexType.BTREE); + + if (existingIndexNames.add(index.getName())) { + builder.statement(stmtFactory.addIndex(index)); + } + } + + return builder.build(); + } + + private Optional getServerPlan(PhysicalPlan fullPlan) { + return fullPlan.getPlans(ServerPhysicalPlan.class).findAny(); + } +} diff --git a/sqrl-planner/src/main/java/com/datasqrl/plan/global/PhysicalPlanRewriter.java b/sqrl-planner/src/main/java/com/datasqrl/plan/global/PhysicalPlanRewriter.java index 6a92724f66..755464019c 100644 --- a/sqrl-planner/src/main/java/com/datasqrl/plan/global/PhysicalPlanRewriter.java +++ b/sqrl-planner/src/main/java/com/datasqrl/plan/global/PhysicalPlanRewriter.java @@ -16,11 +16,17 @@ package com.datasqrl.plan.global; import com.datasqrl.engine.EnginePhysicalPlan; +import com.datasqrl.engine.PhysicalPlan; import com.datasqrl.planner.Sqrl2FlinkSQLTranslator; public interface PhysicalPlanRewriter { - boolean appliesTo(EnginePhysicalPlan plan); + boolean appliesTo(EnginePhysicalPlan enginePlan); - EnginePhysicalPlan rewrite(EnginePhysicalPlan plan, Sqrl2FlinkSQLTranslator sqrlEnv); + EnginePhysicalPlan rewrite( + PhysicalPlan fullPlan, EnginePhysicalPlan enginePlan, Sqrl2FlinkSQLTranslator sqrlEnv); + + default boolean satisfied(PhysicalPlan fullPlan) { + return true; + } } diff --git a/sqrl-planner/src/main/java/com/datasqrl/server/GenerateServerModel.java b/sqrl-planner/src/main/java/com/datasqrl/server/GenerateServerModel.java index 0ef405df0c..1ab1035cf8 100644 --- a/sqrl-planner/src/main/java/com/datasqrl/server/GenerateServerModel.java +++ b/sqrl-planner/src/main/java/com/datasqrl/server/GenerateServerModel.java @@ -48,6 +48,7 @@ public RootGraphQLModel generateGraphQLModel(ApiSources api, ServerPhysicalPlan new GraphqlModelGenerator( serverPlan.getFunctions(), serverPlan.getMutations(), errorCollector); graphqlModelGenerator.walkAPISource(api.schema()); + serverPlan.getPagedRowTimeTables().addAll(graphqlModelGenerator.getPagedRowTimeTables()); var schema = StringSchema.builder().schema(api.schema().getDefinition()).build(); var graphSchema = converter.getSchema(schema.getSchema()); var apiConfig = configuration.getCompilerConfig().getApiConfig(); diff --git a/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlModelGenerator.java b/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlModelGenerator.java index 9c6e42e9bc..749dece323 100644 --- a/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlModelGenerator.java +++ b/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlModelGenerator.java @@ -22,6 +22,7 @@ import com.datasqrl.engine.log.kafka.KafkaLogEngine; import com.datasqrl.engine.log.kafka.KafkaQuery; import com.datasqrl.error.ErrorCollector; +import com.datasqrl.planner.analyzer.TableAnalysis; import com.datasqrl.planner.dag.plan.MutationTable; import com.datasqrl.planner.parser.AccessModifier; import com.datasqrl.planner.tables.SqrlFunctionParameter; @@ -50,6 +51,7 @@ import graphql.language.ObjectTypeDefinition; import graphql.schema.idl.TypeDefinitionRegistry; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -68,6 +70,10 @@ public class GraphqlModelGenerator extends GraphqlSchemaWalker { List queryCoords = new ArrayList<>(); List mutations = new ArrayList<>(); List subscriptions = new ArrayList<>(); + + /** Base tables of paginated queries, so a row-time index can be generated for them. */ + Set pagedRowTimeTables = new LinkedHashSet<>(); + private final ErrorCollector errorCollector; public GraphqlModelGenerator( @@ -148,7 +154,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 @@ -168,13 +175,23 @@ protected void visitQuery( .map(InputValueDefinition::getName) .anyMatch( name -> name.equals(SchemaConstants.LIMIT) || name.equals(SchemaConstants.OFFSET)); + var pagination = + paged + ? PaginationType.OFFSET_PAGE_INFO + : hasLimitOrOffset ? PaginationType.LIMIT_AND_OFFSET : PaginationType.NONE; + var eventTimesSql = + paged ? buildEventTimesSql(tableFunction, executableJdbcReadQuery.getSql()) : null; + if (eventTimesSql != null) { + pagedRowTimeTables.add(tableFunction.getBaseTable()); + } queryBase = new SqlQuery( executableJdbcReadQuery.getSql(), parameters, - hasLimitOrOffset ? PaginationType.LIMIT_AND_OFFSET : PaginationType.NONE, + pagination, executableJdbcReadQuery.getCacheDuration().toMillis(), - executableJdbcReadQuery.getDatabase()); + executableJdbcReadQuery.getDatabase(), + eventTimesSql); var coordsBuilder = ArgumentLookupQueryCoords.builder() .parentType(parentType.getName()) @@ -186,6 +203,28 @@ protected void visitQuery( queryCoords.add(coordsBuilder.build()); } + /** + * Builds the companion aggregate query computing 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 buildEventTimesSql(SqrlTableFunction tableFunction, String baseSql) { + return tableFunction + .getRowTime() + .map(tableFunction::getField) + .map(RelDataTypeField::getName) + .map( + col -> + "SELECT MIN(\"" + + col + + "\") AS \"first_event_time\", MAX(\"" + + col + + "\") AS \"last_event_time\" FROM (" + + baseSql + + ") x") + .orElse(null); + } + private static QueryParameterHandler convert(FunctionParameter fnParam) { final var sqrlParam = (SqrlFunctionParameter) fnParam; diff --git a/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlSchemaFactory.java b/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlSchemaFactory.java index cfe46976b2..e35fdfdd8b 100644 --- a/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlSchemaFactory.java +++ b/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlSchemaFactory.java @@ -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; @@ -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) { @@ -284,9 +287,7 @@ private Optional 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 @@ -346,13 +347,15 @@ private Optional 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); @@ -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 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) { diff --git a/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlSchemaValidator.java b/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlSchemaValidator.java index 9bb4026ee4..1bb910202f 100644 --- a/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlSchemaValidator.java +++ b/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlSchemaValidator.java @@ -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, atField.getSourceLocation()); + 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); } diff --git a/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlSchemaWalker.java b/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlSchemaWalker.java index 6900f5ee91..08ae9525cb 100644 --- a/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlSchemaWalker.java +++ b/sqrl-planner/src/main/java/com/datasqrl/server/GraphqlSchemaWalker.java @@ -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; @@ -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; the pagination + // metadata is computed by the server. + 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); } @@ -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); diff --git a/sqrl-planner/src/main/java/com/datasqrl/server/OffsetPageInfoUtil.java b/sqrl-planner/src/main/java/com/datasqrl/server/OffsetPageInfoUtil.java new file mode 100644 index 0000000000..c18514483c --- /dev/null +++ b/sqrl-planner/src/main/java/com/datasqrl/server/OffsetPageInfoUtil.java @@ -0,0 +1,183 @@ +/* + * Copyright © 2021 DataSQRL (contact@datasqrl.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datasqrl.server; + +import static com.datasqrl.server.util.GraphqlCheckUtil.checkState; + +import com.datasqrl.server.graphql.CustomScalars; +import graphql.Scalars; +import graphql.language.FieldDefinition; +import graphql.language.ListType; +import graphql.language.NonNullType; +import graphql.language.ObjectTypeDefinition; +import graphql.language.SourceLocation; +import graphql.language.Type; +import graphql.language.TypeName; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLNonNull; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLOutputType; +import graphql.schema.idl.TypeDefinitionRegistry; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +/** + * Opt-in pagination support: a query whose result type is a page wrapper ({@code {results: + * [Element!] pagination: OffsetPageInfo}}) returns its rows plus pagination metadata. This util + * detects the wrapper shape and validates the user-declared {@code OffsetPageInfo} type. + */ +public final class OffsetPageInfoUtil { + + private OffsetPageInfoUtil() {} + + public static final String PAGINATION_TYPE_NAME = "OffsetPageInfo"; + + /** Canonical field -> printed type, kept in declaration order for the injected SDL. */ + private static final Map PAGINATION_FIELDS = new LinkedHashMap<>(); + + static { + PAGINATION_FIELDS.put("pageSize", "Int!"); + PAGINATION_FIELDS.put("currentPage", "Int!"); + PAGINATION_FIELDS.put("hasNextPage", "Boolean!"); + PAGINATION_FIELDS.put("hasPreviousPage", "Boolean!"); + PAGINATION_FIELDS.put("nextOffset", "Int"); + PAGINATION_FIELDS.put("prevOffset", "Int"); + PAGINATION_FIELDS.put("firstEventTime", "DateTime"); + PAGINATION_FIELDS.put("lastEventTime", "DateTime"); + } + + private static final String CANONICAL_SDL = buildCanonicalSdl(); + + /** Printed type -> GraphQL type, used to build the schema type from the canonical fields. */ + private static final Map GRAPHQL_TYPES = + Map.of( + "Int!", + GraphQLNonNull.nonNull(Scalars.GraphQLInt), + "Boolean!", + GraphQLNonNull.nonNull(Scalars.GraphQLBoolean), + "Int", + Scalars.GraphQLInt, + "DateTime", + CustomScalars.FLEXIBLE_DATETIME); + + /** Builds the canonical {@code OffsetPageInfo} object type for generated schemas. */ + public static GraphQLObjectType createPageInfoType() { + var builder = GraphQLObjectType.newObject().name(PAGINATION_TYPE_NAME); + PAGINATION_FIELDS.forEach( + (name, type) -> + builder.field( + GraphQLFieldDefinition.newFieldDefinition() + .name(name) + .type(GRAPHQL_TYPES.get(type)))); + return builder.build(); + } + + private static String buildCanonicalSdl() { + var sb = new StringBuilder("type ").append(PAGINATION_TYPE_NAME).append(" {\n"); + PAGINATION_FIELDS.forEach( + (name, type) -> sb.append(" ").append(name).append(": ").append(type).append("\n")); + return sb.append("}\n").toString(); + } + + /** + * Validates that a paginated query's schema declares the {@code OffsetPageInfo} type and that it + * matches the canonical definition. Users must declare the type themselves; we only validate it. + * Must be called from within the schema validator so errors are reported like other schema + * errors. + */ + public static void validatePaginationType( + TypeDefinitionRegistry registry, SourceLocation location) { + var existing = registry.getType(PAGINATION_TYPE_NAME); + checkState( + existing.isPresent(), + location, + "Paginated results require the %s type to be declared in the schema:\n%s", + PAGINATION_TYPE_NAME, + CANONICAL_SDL); + checkState( + existing.get() instanceof ObjectTypeDefinition, + existing.get().getSourceLocation(), + "%s must be an object type", + PAGINATION_TYPE_NAME); + validateMatchesCanonical((ObjectTypeDefinition) existing.get()); + } + + /** + * Detects the page wrapper shape: an object type with exactly two fields, one a list of an object + * type (the results) and the other of type {@code OffsetPageInfo}. Returns the element object + * type when the shape matches. + */ + public static Optional getPagedElementType( + ObjectTypeDefinition wrapper, TypeDefinitionRegistry registry) { + var fields = wrapper.getFieldDefinitions(); + if (fields.size() != 2) { + return Optional.empty(); + } + + ObjectTypeDefinition elementType = null; + boolean hasPagination = false; + for (FieldDefinition field : fields) { + var type = unwrapNonNull(field.getType()); + if (type instanceof ListType listType) { + var element = unwrapNonNull(listType.getType()); + if (element instanceof TypeName typeName) { + elementType = + registry + .getType(typeName.getName()) + .filter(t -> t instanceof ObjectTypeDefinition) + .map(t -> (ObjectTypeDefinition) t) + .orElse(null); + } + } else if (type instanceof TypeName typeName + && PAGINATION_TYPE_NAME.equals(typeName.getName())) { + hasPagination = true; + } + } + + return hasPagination ? Optional.ofNullable(elementType) : Optional.empty(); + } + + private static void validateMatchesCanonical(ObjectTypeDefinition userType) { + var actual = new LinkedHashMap(); + for (FieldDefinition field : userType.getFieldDefinitions()) { + actual.put(field.getName(), printType(field.getType())); + } + checkState( + actual.equals(PAGINATION_FIELDS), + userType.getSourceLocation(), + "User-defined %s does not match the expected definition:\n%s", + PAGINATION_TYPE_NAME, + CANONICAL_SDL); + } + + private static String printType(Type type) { + if (type instanceof NonNullType nonNull) { + return printType(nonNull.getType()) + "!"; + } + if (type instanceof ListType listType) { + return "[" + printType(listType.getType()) + "]"; + } + if (type instanceof TypeName typeName) { + return typeName.getName(); + } + return type.toString(); + } + + private static Type unwrapNonNull(Type type) { + return type instanceof NonNullType nonNull ? unwrapNonNull(nonNull.getType()) : type; + } +} diff --git a/sqrl-planner/src/main/resources/default-package.json b/sqrl-planner/src/main/resources/default-package.json index a882ad6268..1a305e18ba 100644 --- a/sqrl-planner/src/main/resources/default-package.json +++ b/sqrl-planner/src/main/resources/default-package.json @@ -18,7 +18,8 @@ "endpoints": "FULL", "add-prefix": true, "max-result-depth": 3, - "default-limit": 10 + "default-limit": 10, + "paginated-results": false } }, "engines": { diff --git a/sqrl-planner/src/main/resources/jsonSchema/packageSchema.json b/sqrl-planner/src/main/resources/jsonSchema/packageSchema.json index 5be2a3813b..a6c6390f90 100644 --- a/sqrl-planner/src/main/resources/jsonSchema/packageSchema.json +++ b/sqrl-planner/src/main/resources/jsonSchema/packageSchema.json @@ -97,6 +97,9 @@ }, "default-limit": { "type": "integer" + }, + "paginated-results": { + "type": "boolean" } }, "additionalProperties": false diff --git a/sqrl-server/sqrl-server-core/src/main/java/com/datasqrl/server/PaginationType.java b/sqrl-server/sqrl-server-core/src/main/java/com/datasqrl/server/PaginationType.java index 576465e48b..d5402028fb 100644 --- a/sqrl-server/sqrl-server-core/src/main/java/com/datasqrl/server/PaginationType.java +++ b/sqrl-server/sqrl-server-core/src/main/java/com/datasqrl/server/PaginationType.java @@ -17,5 +17,9 @@ public enum PaginationType { NONE, - LIMIT_AND_OFFSET; + LIMIT_AND_OFFSET, + /** + * Like {@link #LIMIT_AND_OFFSET}, but the result is wrapped in an {@code OffsetPageInfo} page. + */ + OFFSET_PAGE_INFO; } diff --git a/sqrl-server/sqrl-server-core/src/main/java/com/datasqrl/server/graphql/GraphQLEngineBuilder.java b/sqrl-server/sqrl-server-core/src/main/java/com/datasqrl/server/graphql/GraphQLEngineBuilder.java index cc0dd2a96c..aee3a0a23b 100644 --- a/sqrl-server/sqrl-server-core/src/main/java/com/datasqrl/server/graphql/GraphQLEngineBuilder.java +++ b/sqrl-server/sqrl-server-core/src/main/java/com/datasqrl/server/graphql/GraphQLEngineBuilder.java @@ -203,6 +203,9 @@ public ResolvedQuery visitSqlQuery(SqlQuery query, ServerContext context) { case NONE: break; case LIMIT_AND_OFFSET: + case OFFSET_PAGE_INFO: + // OFFSET_PAGE_INFO shares the limit/offset mechanics; it only adds a page wrapper computed + // at execution time. // special case where database doesn't support binding for limit/offset => need to create // query dynamically and not prepare if (!query.getDatabase().supportsLimitOffsetBinding) { diff --git a/sqrl-server/sqrl-server-core/src/main/java/com/datasqrl/server/graphql/RootGraphQLModel.java b/sqrl-server/sqrl-server-core/src/main/java/com/datasqrl/server/graphql/RootGraphQLModel.java index b78cd11c8c..ad6954d140 100644 --- a/sqrl-server/sqrl-server-core/src/main/java/com/datasqrl/server/graphql/RootGraphQLModel.java +++ b/sqrl-server/sqrl-server-core/src/main/java/com/datasqrl/server/graphql/RootGraphQLModel.java @@ -20,6 +20,7 @@ import com.datasqrl.server.jdbc.DatabaseType; import com.datasqrl.server.operation.ApiOperation; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes.Type; @@ -298,13 +299,22 @@ public static class SqlQuery implements QueryBase { /** The database the query is executed against */ DatabaseType database; + /** + * Companion aggregate query computing MIN/MAX over the rowtime column for {@code + * firstEventTime}/{@code lastEventTime}. Only relevant when {@link #pagination} is {@link + * PaginationType#OFFSET_PAGE_INFO}, and only executed when the request selects an event-time + * field. Null when the result has no rowtime column. + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + String eventTimesSql; + @Override public R accept(QueryBaseVisitor visitor, C context) { return visitor.visitSqlQuery(this, context); } public SqlQuery updateSql(String newSql) { - return new SqlQuery(newSql, parameters, pagination, cacheDurationMs, database); + return new SqlQuery(newSql, parameters, pagination, cacheDurationMs, database, eventTimesSql); } } diff --git a/sqrl-server/sqrl-server-vertx-base/src/main/java/com/datasqrl/server/jdbc/OffsetPageInfoQuery.java b/sqrl-server/sqrl-server-vertx-base/src/main/java/com/datasqrl/server/jdbc/OffsetPageInfoQuery.java new file mode 100644 index 0000000000..561b70f152 --- /dev/null +++ b/sqrl-server/sqrl-server-vertx-base/src/main/java/com/datasqrl/server/jdbc/OffsetPageInfoQuery.java @@ -0,0 +1,166 @@ +/* + * Copyright © 2021 DataSQRL (contact@datasqrl.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datasqrl.server.jdbc; + +import com.datasqrl.server.PaginationType; +import com.datasqrl.server.graphql.RootGraphQLModel.SqlQuery; +import graphql.schema.DataFetchingEnvironment; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLNonNull; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLOutputType; +import io.vertx.core.json.JsonObject; +import java.util.List; + +/** + * A {@link PaginationType#OFFSET_PAGE_INFO} request: the rows of a page plus the {@code + * OffsetPageInfo} metadata around them. + * + *

Everything here is decided up front from the GraphQL request and computed in memory - it runs + * no queries. {@link VertxQueryExecutionContext} executes what {@link #pageQuery} and {@link + * #needsEventTimes} ask for and hands the rows back to {@link #toPage}: + * + *

    + *
  1. {@link #from} reads the selection set once. Which metadata fields were selected is the only + * thing that decides what has to be executed. + *
  2. {@link #pageQuery} is the same limit/offset query {@link PaginationType#LIMIT_AND_OFFSET} + * runs, fetching one extra row when {@code hasNextPage}/{@code nextOffset} were selected - + * that extra row is how they are answered without a COUNT. + *
  3. {@link #toPage} trims that extra row back off and assembles {@code {results, pagination}}. + *
+ */ +final class OffsetPageInfoQuery { + + private static final String FIRST_EVENT_TIME_COLUMN = "first_event_time"; + private static final String LAST_EVENT_TIME_COLUMN = "last_event_time"; + + private final PageRequest page; + private final PageFields fields; + private final boolean needsNextPage; + private final boolean needsEventTimes; + + private OffsetPageInfoQuery( + PageRequest page, PageFields fields, boolean needsNextPage, boolean needsEventTimes) { + this.page = page; + this.fields = fields; + this.needsNextPage = needsNextPage; + this.needsEventTimes = needsEventTimes; + } + + static OffsetPageInfoQuery from(DataFetchingEnvironment environment) { + var fields = pageFields(environment.getFieldType()); + var pagination = fields.pagination(); + var selection = environment.getSelectionSet(); + return new OffsetPageInfoQuery( + PageRequest.from(environment), + fields, + selection.containsAnyOf(pagination + "/hasNextPage", pagination + "/nextOffset"), + selection.containsAnyOf(pagination + "/firstEventTime", pagination + "/lastEventTime")); + } + + /** + * The query returning the page rows, over-fetching by one row when a next page is in question. + */ + PageRequest.BoundQuery pageQuery(SqlQuery query, List baseParams) { + return (fetchesExtraRow() ? page.plusOneRow() : page).applyTo(query, baseParams); + } + + /** Whether the caller has to run the MIN/MAX rowtime aggregate to answer this request. */ + boolean needsEventTimes() { + return needsEventTimes; + } + + /** + * Assembles the page from the rows the caller fetched. {@code eventTimes} is the single row of + * the rowtime aggregate, or an empty object when it did not run. + */ + JsonObject toPage(List rows, JsonObject eventTimes) { + Boolean hasNextPage = null; + if (needsNextPage) { + hasNextPage = fetchesExtraRow() && rows.size() > page.limit(); + if (hasNextPage) { + rows = rows.subList(0, page.limit()); + } + } + + var pagination = + paginationMetadata( + page.pageSize(rows.size()), + page.offset(), + hasNextPage, + eventTimes.getValue(FIRST_EVENT_TIME_COLUMN), + eventTimes.getValue(LAST_EVENT_TIME_COLUMN)); + + return new JsonObject().put(fields.results(), rows).put(fields.pagination(), pagination); + } + + /** + * {@code hasNextPage}/{@code nextOffset} are derived from one over-fetched row. An unbounded page + * holds every remaining row, so there is no next page and nothing to over-fetch. + */ + private boolean fetchesExtraRow() { + return needsNextPage && !page.unbounded(); + } + + /** + * Builds the {@code OffsetPageInfo} object. A null {@code hasNextPage} means the request did not + * select the next-page fields, so they are left out entirely - GraphQL never reads them. + */ + static JsonObject paginationMetadata( + int pageSize, int offset, Boolean hasNextPage, Object firstEventTime, Object lastEventTime) { + var hasPreviousPage = offset > 0; + var pagination = + new JsonObject() + .put("pageSize", pageSize) + .put("currentPage", pageSize == 0 ? 1 : offset / pageSize + 1) + .put("hasPreviousPage", hasPreviousPage) + .put( + "prevOffset", + hasPreviousPage ? Integer.valueOf(Math.max(0, offset - pageSize)) : null) + .put("firstEventTime", firstEventTime) + .put("lastEventTime", lastEventTime); + + if (hasNextPage != null) { + pagination + .put("hasNextPage", hasNextPage) + .put("nextOffset", hasNextPage ? Integer.valueOf(offset + pageSize) : null); + } + return pagination; + } + + /** The results/pagination field names of the page wrapper type this request returns. */ + private static PageFields pageFields(GraphQLOutputType fieldType) { + var objectType = (GraphQLObjectType) unwrapNonNull(fieldType); + String results = null; + String pagination = null; + for (var field : objectType.getFieldDefinitions()) { + if (unwrapNonNull(field.getType()) instanceof GraphQLList) { + results = field.getName(); + } else { + pagination = field.getName(); + } + } + return new PageFields(results, pagination); + } + + private static GraphQLOutputType unwrapNonNull(GraphQLOutputType type) { + return type instanceof GraphQLNonNull nonNull + ? (GraphQLOutputType) nonNull.getWrappedType() + : type; + } + + private record PageFields(String results, String pagination) {} +} diff --git a/sqrl-server/sqrl-server-vertx-base/src/main/java/com/datasqrl/server/jdbc/PageRequest.java b/sqrl-server/sqrl-server-vertx-base/src/main/java/com/datasqrl/server/jdbc/PageRequest.java new file mode 100644 index 0000000000..d8524d650a --- /dev/null +++ b/sqrl-server/sqrl-server-vertx-base/src/main/java/com/datasqrl/server/jdbc/PageRequest.java @@ -0,0 +1,83 @@ +/* + * Copyright © 2021 DataSQRL (contact@datasqrl.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datasqrl.server.jdbc; + +import static com.datasqrl.server.jdbc.SchemaConstants.LIMIT; +import static com.datasqrl.server.jdbc.SchemaConstants.OFFSET; + +import com.datasqrl.server.PaginationType; +import com.datasqrl.server.graphql.RootGraphQLModel.SqlQuery; +import graphql.schema.DataFetchingEnvironment; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * The {@code limit}/{@code offset} arguments of one request. A null limit means the caller did not + * ask for one, so the query returns every remaining row. + * + *

This is the only piece {@link PaginationType#LIMIT_AND_OFFSET} and {@link + * PaginationType#OFFSET_PAGE_INFO} share: both slice the result the same way, they differ only in + * what they return around the rows. + */ +record PageRequest(Integer limit, int offset) { + + /** Bind parameters must be a number; this stands in for "no limit" when binding. */ + private static final int NO_LIMIT = Integer.MAX_VALUE; + + static PageRequest from(DataFetchingEnvironment environment) { + Integer limit = environment.getArgument(LIMIT); + int offset = Optional.ofNullable(environment.getArgument(OFFSET)).orElse(0); + return new PageRequest(limit, offset); + } + + boolean unbounded() { + return limit == null; + } + + /** The number of rows on the page, which for an unbounded request is whatever came back. */ + int pageSize(int rowCount) { + return unbounded() ? rowCount : limit; + } + + /** Same page, one row longer. An unbounded page already holds every row, so it is unchanged. */ + PageRequest plusOneRow() { + return unbounded() ? this : new PageRequest(limit + 1, offset); + } + + /** + * Applies this page to {@code query}. Databases that support binding get limit/offset appended as + * bind parameters, the others (e.g. Snowflake) get them written into the SQL text. {@code + * baseParams} is never modified: the caller may still need it for a companion query. + */ + BoundQuery applyTo(SqlQuery query, List baseParams) { + if (!query.getDatabase().supportsLimitOffsetBinding) { + var sql = + AbstractQueryExecutionContext.addLimitOffsetToQuery( + query.getSql(), unbounded() ? "ALL" : String.valueOf(limit), String.valueOf(offset)); + return new BoundQuery(sql, baseParams); + } + + // ArrayList rather than List.copyOf: parameter values may be null + var params = new ArrayList<>(baseParams); + params.add(unbounded() ? NO_LIMIT : limit); + params.add(offset); + return new BoundQuery(query.getSql(), params); + } + + /** The SQL to execute and the full parameter list to bind to it. */ + record BoundQuery(String sql, List params) {} +} diff --git a/sqrl-server/sqrl-server-vertx-base/src/main/java/com/datasqrl/server/jdbc/VertxQueryExecutionContext.java b/sqrl-server/sqrl-server-vertx-base/src/main/java/com/datasqrl/server/jdbc/VertxQueryExecutionContext.java index bbf702cf01..81d4dd169d 100644 --- a/sqrl-server/sqrl-server-vertx-base/src/main/java/com/datasqrl/server/jdbc/VertxQueryExecutionContext.java +++ b/sqrl-server/sqrl-server-vertx-base/src/main/java/com/datasqrl/server/jdbc/VertxQueryExecutionContext.java @@ -15,20 +15,19 @@ */ package com.datasqrl.server.jdbc; -import static com.datasqrl.server.jdbc.SchemaConstants.LIMIT; -import static com.datasqrl.server.jdbc.SchemaConstants.OFFSET; - import com.datasqrl.server.VertxServerContext; import com.datasqrl.server.graphql.RootGraphQLModel; import com.datasqrl.server.graphql.RootGraphQLModel.Argument; import com.datasqrl.server.graphql.RootGraphQLModel.ResolvedSqlQuery; +import com.datasqrl.server.graphql.RootGraphQLModel.SqlQuery; +import com.datasqrl.server.jdbc.PageRequest.BoundQuery; import graphql.schema.DataFetchingEnvironment; import io.vertx.core.Future; +import io.vertx.core.json.JsonObject; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.RowSet; import io.vertx.sqlclient.Tuple; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.StreamSupport; @@ -38,6 +37,9 @@ * queries (paginated or not) in Vert.x and mapping the database resultSet to json for using in * GraphQL responses. It also implements the parameters and arguments visitors for the {@link * RootGraphQLModel} visitors + * + *

Each pagination type has its own execution path; the pagination logic itself lives in {@link + * PageRequest} and {@link OffsetPageInfoQuery}, this class only runs queries and maps rows. */ public class VertxQueryExecutionContext extends AbstractQueryExecutionContext { @@ -55,77 +57,98 @@ public VertxQueryExecutionContext( @Override public CompletableFuture runQuery(ResolvedSqlQuery resolvedQuery, boolean isList) { - getParamArgumentsFuture(resolvedQuery.getQuery().getParameters()) + var query = resolvedQuery.getQuery(); + getParamArgumentsFuture(query.getParameters()) .whenComplete( - (paramObj, throwable) -> { + (params, throwable) -> { if (throwable != null) { cf.completeExceptionally(throwable); - } else { - runQueryInternal(resolvedQuery, isList, paramObj); + return; + } + switch (query.getPagination()) { + case NONE -> + runRowQuery(resolvedQuery, new BoundQuery(query.getSql(), params), isList); + case LIMIT_AND_OFFSET -> + runRowQuery( + resolvedQuery, + PageRequest.from(environment).applyTo(query, params), + isList); + case OFFSET_PAGE_INFO -> runOffsetPageInfoQuery(resolvedQuery, params); + default -> + cf.completeExceptionally( + new UnsupportedOperationException( + "Unsupported pagination: " + query.getPagination())); } }); return cf; } - private CompletableFuture runQueryInternal( - ResolvedSqlQuery resolvedQuery, boolean isList, List paramObj) { + /** Completes with the rows themselves: no page wrapper, no metadata. */ + private void runRowQuery(ResolvedSqlQuery resolvedQuery, BoundQuery boundQuery, boolean isList) { + execute(resolvedQuery, boundQuery) + .map(rows -> unboxList(toJson(rows), isList)) + .onSuccess(cf::complete) + .onFailure(this::failQuery); + } - var preparedQueryContainer = (PreparedVertxSqrlQuery) resolvedQuery.getPreparedQueryContainer(); + /** + * Completes with a page: the page query always runs, the rowtime aggregate behind {@code + * firstEventTime}/{@code lastEventTime} only when the request selected them and the query has a + * rowtime column. {@link OffsetPageInfoQuery} decides both and assembles the response. + */ + private void runOffsetPageInfoQuery(ResolvedSqlQuery resolvedQuery, List params) { var query = resolvedQuery.getQuery(); - var unpreparedSqlQuery = query.getSql(); - switch (query.getPagination()) { - case NONE: - break; - case LIMIT_AND_OFFSET: - var limit = Optional.ofNullable(environment.getArgument(LIMIT)); - var offset = Optional.ofNullable(environment.getArgument(OFFSET)); + var pageInfoQuery = OffsetPageInfoQuery.from(environment); - // special case where database doesn't support binding for limit/offset => need - // to execute dynamically - if (!query.getDatabase().supportsLimitOffsetBinding) { - assert preparedQueryContainer == null; - unpreparedSqlQuery = - AbstractQueryExecutionContext.addLimitOffsetToQuery( - unpreparedSqlQuery, - limit.map(Object::toString).orElse("ALL"), - String.valueOf(offset.orElse(0))); - } else { - paramObj.add(limit.orElse(Integer.MAX_VALUE)); - paramObj.add(offset.orElse(0)); - } - break; - default: - throw new UnsupportedOperationException("Unsupported pagination: " + query.getPagination()); - } + var pageFuture = execute(resolvedQuery, pageInfoQuery.pageQuery(query, params)); + var eventTimesFuture = + pageInfoQuery.needsEventTimes() && query.getEventTimesSql() != null + ? executeEventTimes(query, params) + : Future.>succeededFuture(null); - // execute the preparedQuery with the arguments extracted above - Future> future; - var params = Tuple.from(paramObj); - var database = resolvedQuery.getQuery().getDatabase(); + Future.all(pageFuture, eventTimesFuture) + .map( + ignored -> + pageInfoQuery.toPage( + toJson(pageFuture.result()), firstRowAsJson(eventTimesFuture.result()))) + .onSuccess(cf::complete) + .onFailure(this::failQuery); + } - if (preparedQueryContainer == null) { - future = serverContext.getSqlClient().execute(database, unpreparedSqlQuery, params); - } else { - var preparedQuery = preparedQueryContainer.preparedQuery(); - future = serverContext.getSqlClient().execute(preparedQuery, params); + private Future> execute(ResolvedSqlQuery resolvedQuery, BoundQuery boundQuery) { + var container = (PreparedVertxSqrlQuery) resolvedQuery.getPreparedQueryContainer(); + var params = Tuple.from(boundQuery.params()); + if (container == null) { + return serverContext + .getSqlClient() + .execute(resolvedQuery.getQuery().getDatabase(), boundQuery.sql(), params); } + return serverContext.getSqlClient().execute(container.preparedQuery(), params); + } - // map the resultSet to json for GraphQL response - future - .map(r -> resultMapper(r, isList)) - .onSuccess(cf::complete) - .onFailure( - f -> { - f.printStackTrace(); - cf.completeExceptionally(f); - }); - return cf; + /** The rowtime aggregate is never prepared and binds the base parameters only. */ + private Future> executeEventTimes(SqlQuery query, List params) { + return serverContext + .getSqlClient() + .execute(query.getDatabase(), query.getEventTimesSql(), Tuple.from(params)); } - private Object resultMapper(RowSet r, boolean isList) { - var o = StreamSupport.stream(r.spliterator(), false).map(Row::toJson).toList(); + private void failQuery(Throwable throwable) { + throwable.printStackTrace(); + cf.completeExceptionally(throwable); + } + + private static List toJson(RowSet rows) { + return StreamSupport.stream(rows.spliterator(), false).map(Row::toJson).toList(); + } - return unboxList(o, isList); + /** The single row of an aggregate query, empty when the query did not run or returned nothing. */ + private static JsonObject firstRowAsJson(RowSet rows) { + if (rows == null) { + return new JsonObject(); + } + var iterator = rows.iterator(); + return iterator.hasNext() ? iterator.next().toJson() : new JsonObject(); } } diff --git a/sqrl-server/sqrl-server-vertx-base/src/test/java/com/datasqrl/server/PagedQueryIT.java b/sqrl-server/sqrl-server-vertx-base/src/test/java/com/datasqrl/server/PagedQueryIT.java new file mode 100644 index 0000000000..56a1d86e4c --- /dev/null +++ b/sqrl-server/sqrl-server-vertx-base/src/test/java/com/datasqrl/server/PagedQueryIT.java @@ -0,0 +1,402 @@ +/* + * Copyright © 2021 DataSQRL (contact@datasqrl.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datasqrl.server; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datasqrl.server.graphql.CustomScalars; +import com.datasqrl.server.graphql.GraphQLEngineBuilder; +import com.datasqrl.server.graphql.RootGraphQLModel; +import com.datasqrl.server.graphql.RootGraphQLModel.ArgumentLookupQueryCoords; +import com.datasqrl.server.graphql.RootGraphQLModel.QueryWithArguments; +import com.datasqrl.server.graphql.RootGraphQLModel.SqlQuery; +import com.datasqrl.server.graphql.RootGraphQLModel.StringSchema; +import com.datasqrl.server.jdbc.DatabaseType; +import com.datasqrl.server.jdbc.VertxJdbcClient; +import com.datasqrl.server.jdbc.VertxParamArgumentTypeMapper; +import graphql.ExecutionInput; +import graphql.GraphQL; +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.junit5.VertxExtension; +import io.vertx.pgclient.PgBuilder; +import io.vertx.pgclient.PgConnectOptions; +import io.vertx.sqlclient.PoolOptions; +import io.vertx.sqlclient.PrepareOptions; +import io.vertx.sqlclient.PreparedQuery; +import io.vertx.sqlclient.Query; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.RowSet; +import io.vertx.sqlclient.SqlClient; +import io.vertx.sqlclient.SqlResult; +import io.vertx.sqlclient.Tuple; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Function; +import java.util.stream.Collector; +import lombok.SneakyThrows; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.postgresql.PostgreSQLContainer; +import org.testcontainers.utility.DockerImageName; + +/** + * Proves that pagination metadata is computed lazily from the selection set: the MIN/MAX aggregate + * query only runs when event times are selected, and {@code hasNextPage} alone is answered by + * fetching LIMIT+1 rows instead of any aggregate. + */ +@ExtendWith(VertxExtension.class) +@Testcontainers +class PagedQueryIT { + + private static final String BASE_SQL = "SELECT customerid, ts FROM customer ORDER BY customerid"; + private static final String EVENT_TIMES_SQL = + "SELECT MIN(\"ts\") AS \"first_event_time\", MAX(\"ts\") AS \"last_event_time\" FROM (" + + BASE_SQL + + ") x"; + + @Container + private static final PostgreSQLContainer postgresContainer = + new PostgreSQLContainer(DockerImageName.parse("postgres:16")) + .withDatabaseName("datasqrl") + .withUsername("foo") + .withPassword("secret"); + + private SqlClient client; + private RecordingSqlClient recordingClient; + private GraphQL graphQL; + + @BeforeEach + void init(Vertx vertx) { + var options = new PgConnectOptions(); + options.setDatabase(postgresContainer.getDatabaseName()); + options.setHost(postgresContainer.getHost()); + options.setPort(postgresContainer.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT)); + options.setUser(postgresContainer.getUsername()); + options.setPassword(postgresContainer.getPassword()); + + client = PgBuilder.client().with(new PoolOptions()).connectingTo(options).using(vertx).build(); + await(client.query("DROP TABLE IF EXISTS customer").execute()); + await(client.query("CREATE TABLE customer (customerid INT, ts TIMESTAMPTZ)").execute()); + await( + client + .query( + """ + INSERT INTO customer VALUES + (1, '2024-01-01T00:00:00Z'), + (2, '2024-01-02T00:00:00Z'), + (3, '2024-01-03T00:00:00Z'), + (4, '2024-01-04T00:00:00Z'), + (5, '2024-01-05T00:00:00Z') + """) + .execute()); + + recordingClient = new RecordingSqlClient(client); + graphQL = + getPagedModel() + .accept( + new GraphQLEngineBuilder.Builder() + .withExtendedScalarTypes(CustomScalars.getExtendedScalars()) + .build(), + new VertxServerContext( + new VertxJdbcClient(Map.of(DatabaseType.POSTGRES, recordingClient)), + null, + null, + new VertxParamArgumentTypeMapper())) + .build(); + recordingClient.executed.clear(); + } + + @AfterEach + void after() { + client.close(); + } + + @Test + void givenOnlyResultsSelected_whenQuery_thenNoAggregateQueryRuns() { + var customers = execute("{ customers(limit: 2, offset: 0) { results { customerid } } }"); + + assertThat(results(customers)).extracting("customerid").containsExactly(1, 2); + assertThat(recordingClient.executed).hasSize(1); + assertThat(recordingClient.executed.get(0).params().getInteger(0)).isEqualTo(2); + } + + @Test + void givenOnlyOffsetDerivedFieldsSelected_whenQuery_thenNoAggregateQueryRuns() { + var customers = + execute( + "{ customers(limit: 2, offset: 2) { results { customerid }" + + " pagination { pageSize currentPage hasPreviousPage prevOffset } } }"); + + assertThat(recordingClient.executed).hasSize(1); + var pagination = pagination(customers); + assertThat(pagination) + .containsEntry("pageSize", 2) + .containsEntry("currentPage", 2) + .containsEntry("hasPreviousPage", true) + .containsEntry("prevOffset", 0); + } + + @Test + void givenOnlyHasNextPageSelected_whenQuery_thenLimitPlusOneReplacesCount() { + var customers = + execute( + "{ customers(limit: 2, offset: 0) { results { customerid }" + + " pagination { hasNextPage nextOffset } } }"); + + assertThat(recordingClient.executed).hasSize(1); + // the single data query fetched limit+1 rows and the extra row was trimmed + assertThat(recordingClient.executed.get(0).params().getInteger(0)).isEqualTo(3); + assertThat(results(customers)).extracting("customerid").containsExactly(1, 2); + assertThat(pagination(customers)) + .containsEntry("hasNextPage", true) + .containsEntry("nextOffset", 2); + } + + @Test + void givenHasNextPageSelectedOnLastPage_whenQuery_thenNoNextPage() { + var customers = + execute( + "{ customers(limit: 2, offset: 4) { results { customerid }" + + " pagination { hasNextPage nextOffset } } }"); + + assertThat(recordingClient.executed).hasSize(1); + assertThat(results(customers)).extracting("customerid").containsExactly(5); + assertThat(pagination(customers)).containsEntry("hasNextPage", false); + } + + @Test + void givenEventTimesSelected_whenQuery_thenMinMaxAggregateRuns() { + var customers = + execute( + "{ customers(limit: 2, offset: 2) { results { customerid }" + + " pagination { firstEventTime lastEventTime } } }"); + + assertThat(recordingClient.executed).hasSize(2); + assertThat(sqlOf(recordingClient.executed)).contains(EVENT_TIMES_SQL); + var pagination = pagination(customers); + // MIN/MAX cover the whole result, not just the requested page + assertThat(String.valueOf(pagination.get("firstEventTime"))).startsWith("2024-01-01"); + assertThat(String.valueOf(pagination.get("lastEventTime"))).startsWith("2024-01-05"); + } + + @Test + void givenNoLimitArgument_whenQuery_thenPageSizeReportsRowCountNotSentinel() { + var customers = + execute( + "{ customers: customersUnbounded { results { customerid }" + + " pagination { pageSize hasNextPage } } }"); + + assertThat(results(customers)).hasSize(5); + // an absent limit fetches every row; pageSize reflects the rows returned, not Integer.MAX_VALUE + assertThat(pagination(customers)) + .containsEntry("pageSize", 5) + .containsEntry("hasNextPage", false); + } + + @Test + void givenEventTimesSelectedButNoRowtime_whenQuery_thenNoAggregateRunsAndEventTimesAreNull() { + var customers = + execute( + "{ customers: customersNoRowtime(limit: 2) { results { customerid }" + + " pagination { firstEventTime lastEventTime } } }"); + + // no rowtime => no aggregate query exists, so only the data query runs and event times stay + // null + assertThat(recordingClient.executed).hasSize(1); + var pagination = pagination(customers); + assertThat(pagination.get("firstEventTime")).isNull(); + assertThat(pagination.get("lastEventTime")).isNull(); + } + + @SneakyThrows + private Map execute(String query) { + var result = graphQL.execute(ExecutionInput.newExecutionInput().query(query).build()); + assertThat(result.getErrors()).isEmpty(); + Map data = result.getData(); + return (Map) data.get("customers"); + } + + @SuppressWarnings("unchecked") + private static List> results(Map customers) { + return (List>) customers.get("results"); + } + + @SuppressWarnings("unchecked") + private static Map pagination(Map customers) { + return (Map) customers.get("pagination"); + } + + private static List sqlOf(List statements) { + return statements.stream().map(ExecutedStatement::sql).toList(); + } + + private RootGraphQLModel getPagedModel() { + return RootGraphQLModel.builder() + .schema( + StringSchema.builder() + .schema( + """ + scalar DateTime + type Query { + customers(limit: Int = 10, offset: Int = 0): CustomerPage! + customersUnbounded(limit: Int, offset: Int = 0): CustomerPage! + customersNoRowtime(limit: Int = 10, offset: Int = 0): CustomerPage! + } + type Customer { + customerid: Int + } + type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo + } + type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime + } + """) + .build()) + .query( + ArgumentLookupQueryCoords.builder() + .parentType("Query") + .fieldName("customers") + .exec( + QueryWithArguments.builder() + .query( + new SqlQuery( + BASE_SQL, + List.of(), + PaginationType.OFFSET_PAGE_INFO, + 0, + DatabaseType.POSTGRES, + EVENT_TIMES_SQL)) + .build()) + .build()) + .query( + ArgumentLookupQueryCoords.builder() + .parentType("Query") + .fieldName("customersUnbounded") + .exec( + QueryWithArguments.builder() + .query( + new SqlQuery( + BASE_SQL, + List.of(), + PaginationType.OFFSET_PAGE_INFO, + 0, + DatabaseType.POSTGRES, + EVENT_TIMES_SQL)) + .build()) + .build()) + .query( + ArgumentLookupQueryCoords.builder() + .parentType("Query") + .fieldName("customersNoRowtime") + .exec( + QueryWithArguments.builder() + .query( + new SqlQuery( + BASE_SQL, + List.of(), + PaginationType.OFFSET_PAGE_INFO, + 0, + DatabaseType.POSTGRES, + null)) + .build()) + .build()) + .build(); + } + + @SneakyThrows + private static T await(Future future) { + return future.toCompletionStage().toCompletableFuture().get(); + } + + private record ExecutedStatement(String sql, Tuple params) {} + + /** Delegating {@link SqlClient} that records every executed statement with its bound tuple. */ + private static final class RecordingSqlClient implements SqlClient { + + private final SqlClient delegate; + final List executed = new CopyOnWriteArrayList<>(); + + RecordingSqlClient(SqlClient delegate) { + this.delegate = delegate; + } + + @Override + public Query> query(String sql) { + return delegate.query(sql); + } + + @Override + public PreparedQuery> preparedQuery(String sql) { + return recording(sql, delegate.preparedQuery(sql)); + } + + @Override + public PreparedQuery> preparedQuery(String sql, PrepareOptions options) { + return recording(sql, delegate.preparedQuery(sql, options)); + } + + @Override + public Future close() { + return delegate.close(); + } + + private PreparedQuery> recording(String sql, PreparedQuery> delegate) { + return new PreparedQuery<>() { + @Override + public Future> execute(Tuple tuple) { + executed.add(new ExecutedStatement(sql, tuple)); + return delegate.execute(tuple); + } + + @Override + public Future> execute() { + executed.add(new ExecutedStatement(sql, Tuple.tuple())); + return delegate.execute(); + } + + @Override + public Future> executeBatch(List batch) { + throw new UnsupportedOperationException(); + } + + @Override + public PreparedQuery> collecting(Collector collector) { + throw new UnsupportedOperationException(); + } + + @Override + public PreparedQuery> mapping(Function mapper) { + throw new UnsupportedOperationException(); + } + }; + } + } +} diff --git a/sqrl-server/sqrl-server-vertx-base/src/test/java/com/datasqrl/server/WriteIT.java b/sqrl-server/sqrl-server-vertx-base/src/test/java/com/datasqrl/server/WriteIT.java index 4db031b61e..63d24ce7b1 100644 --- a/sqrl-server/sqrl-server-vertx-base/src/test/java/com/datasqrl/server/WriteIT.java +++ b/sqrl-server/sqrl-server-vertx-base/src/test/java/com/datasqrl/server/WriteIT.java @@ -171,7 +171,8 @@ private RootGraphQLModel getCustomerModel() { List.of(), PaginationType.NONE, 0, - DatabaseType.POSTGRES)) + DatabaseType.POSTGRES, + null)) .build()) .build()) .mutation( diff --git a/sqrl-server/sqrl-server-vertx-base/src/test/java/com/datasqrl/server/jdbc/PaginationMetadataTest.java b/sqrl-server/sqrl-server-vertx-base/src/test/java/com/datasqrl/server/jdbc/PaginationMetadataTest.java new file mode 100644 index 0000000000..7f7a121dad --- /dev/null +++ b/sqrl-server/sqrl-server-vertx-base/src/test/java/com/datasqrl/server/jdbc/PaginationMetadataTest.java @@ -0,0 +1,84 @@ +/* + * Copyright © 2021 DataSQRL (contact@datasqrl.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datasqrl.server.jdbc; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class PaginationMetadataTest { + + @Test + void givenFirstPageWithNext_whenBuildMetadata_thenHasNextNoPrevious() { + var json = OffsetPageInfoQuery.paginationMetadata(10, 0, true, null, null); + + assertThat(json.getInteger("pageSize")).isEqualTo(10); + assertThat(json.getInteger("currentPage")).isEqualTo(1); + assertThat(json.getBoolean("hasNextPage")).isTrue(); + assertThat(json.getBoolean("hasPreviousPage")).isFalse(); + assertThat(json.getInteger("nextOffset")).isEqualTo(10); + assertThat(json.getInteger("prevOffset")).isNull(); + } + + @Test + void givenMiddlePageWithNext_whenBuildMetadata_thenHasBothNeighbours() { + var json = OffsetPageInfoQuery.paginationMetadata(10, 10, true, null, null); + + assertThat(json.getInteger("currentPage")).isEqualTo(2); + assertThat(json.getBoolean("hasNextPage")).isTrue(); + assertThat(json.getBoolean("hasPreviousPage")).isTrue(); + assertThat(json.getInteger("nextOffset")).isEqualTo(20); + assertThat(json.getInteger("prevOffset")).isZero(); + } + + @Test + void givenLastPage_whenBuildMetadata_thenNoNextHasPrevious() { + var json = OffsetPageInfoQuery.paginationMetadata(10, 20, false, null, null); + + assertThat(json.getInteger("currentPage")).isEqualTo(3); + assertThat(json.getBoolean("hasNextPage")).isFalse(); + assertThat(json.getInteger("nextOffset")).isNull(); + assertThat(json.getBoolean("hasPreviousPage")).isTrue(); + assertThat(json.getInteger("prevOffset")).isEqualTo(10); + } + + @Test + void givenZeroLimit_whenBuildMetadata_thenDoesNotDivideByZero() { + var json = OffsetPageInfoQuery.paginationMetadata(0, 0, null, null, null); + + assertThat(json.getInteger("currentPage")).isEqualTo(1); + } + + @Test + void givenEventTimes_whenBuildMetadata_thenPassedThrough() { + var json = + OffsetPageInfoQuery.paginationMetadata( + 10, 0, null, "2024-01-01T00:00:00Z", "2024-01-02T00:00:00Z"); + + assertThat(json.getString("firstEventTime")).isEqualTo("2024-01-01T00:00:00Z"); + assertThat(json.getString("lastEventTime")).isEqualTo("2024-01-02T00:00:00Z"); + } + + @Test + void givenNoNextPageInfoQueried_whenBuildMetadata_thenNextFieldsOmitted() { + var json = OffsetPageInfoQuery.paginationMetadata(10, 0, null, null, null); + + assertThat(json.containsKey("hasNextPage")).isFalse(); + assertThat(json.containsKey("nextOffset")).isFalse(); + assertThat(json.getInteger("pageSize")).isEqualTo(10); + assertThat(json.getBoolean("hasPreviousPage")).isFalse(); + } +} diff --git a/sqrl-testing/sqrl-testing-integration/src/test/java/com/datasqrl/GraphQLValidationTest.java b/sqrl-testing/sqrl-testing-integration/src/test/java/com/datasqrl/GraphQLValidationTest.java index 967f344a37..c5fd47c1c1 100644 --- a/sqrl-testing/sqrl-testing-integration/src/test/java/com/datasqrl/GraphQLValidationTest.java +++ b/sqrl-testing/sqrl-testing-integration/src/test/java/com/datasqrl/GraphQLValidationTest.java @@ -22,6 +22,8 @@ import com.datasqrl.util.ArgumentsProviders; import java.nio.file.Path; import java.util.function.Predicate; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -50,6 +52,13 @@ void testUseCase(Path graphQLSchema) { UseCaseTestHelper.defaultPlanDirFilter()); } + @Test + @Disabled + void specificSchema() { + var schema = USECASE_DIR.resolve("comprehensiveTest-paged-results.graphqls"); + testUseCase(schema); + } + private Predicate getBuildDirFilter() { return file -> { var fileName = file.getFileName().toString(); diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-no-limit-offset.graphqls b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-no-limit-offset.graphqls new file mode 100644 index 0000000000..9af1217a3a --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-no-limit-offset.graphqls @@ -0,0 +1,32 @@ +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2: CustomerPage! +} diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-subscription.graphqls b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-subscription.graphqls new file mode 100644 index 0000000000..8e8772da3f --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-subscription.graphqls @@ -0,0 +1,25 @@ +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +type Subscription { + CustomerSubscription: CustomerPage +} diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-undeclared.graphqls b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-undeclared.graphqls new file mode 100644 index 0000000000..fce97c9ce1 --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-undeclared.graphqls @@ -0,0 +1,21 @@ +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-unknown-field.graphqls b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-unknown-field.graphqls new file mode 100644 index 0000000000..bc41941f10 --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-unknown-field.graphqls @@ -0,0 +1,33 @@ +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + doesNotExist: String! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-wrong-pagination-type.graphqls b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-wrong-pagination-type.graphqls new file mode 100644 index 0000000000..bd7e007f4b --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-fail-paged-wrong-pagination-type.graphqls @@ -0,0 +1,26 @@ +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type OffsetPageInfo { + totalRecords: Long! + pageSize: Int! +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-paged-results.graphqls b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-paged-results.graphqls new file mode 100644 index 0000000000..70a840a44a --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-paged-results.graphqls @@ -0,0 +1,32 @@ +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-paged-userdefined.graphqls b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-paged-userdefined.graphqls new file mode 100644 index 0000000000..2119ac727f --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/graphql-validation/comprehensiveTest-paged-userdefined.graphqls @@ -0,0 +1,38 @@ +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + related(limit: Int = 10, offset: Int = 0): CustomerRelatedPage +} + +type CustomerRelatedPage { + items: [Customer!] + meta: OffsetPageInfo +} + +type CustomerPage { + items: [Customer!] + meta: OffsetPageInfo +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): CustomerPage! +} diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/DAGWriterJsonTest/clickstream-package-paginated.txt b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/DAGWriterJsonTest/clickstream-package-paginated.txt new file mode 100644 index 0000000000..09227d4bae --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/DAGWriterJsonTest/clickstream-package-paginated.txt @@ -0,0 +1,212 @@ +>>>pipeline_explain.json +[ { + "id" : "access:Click", + "name" : "Click", + "type" : "query", + "stage" : "postgres", + "inputs" : [ "default_catalog.default_database.Click" ], + "annotations" : [ { + "name" : "stream-root", + "description" : "Click" + }, { + "name" : "base-table", + "description" : "Click" + } ], + "plan" : "LogicalProject(timestamp=[$0], userid=[$1], url=[$2])\n LogicalTableScan(table=[[default_catalog, default_database, Click]])\n", + "sql" : "SELECT *\nFROM `default_catalog`.`default_database`.`Click`" +}, { + "id" : "access:Recommendation", + "name" : "Recommendation", + "type" : "query", + "stage" : "postgres", + "documentation" : "Recommend pages that are visited shortly after", + "inputs" : [ "default_catalog.default_database.Recommendation" ], + "annotations" : [ { + "name" : "parameters", + "description" : "url" + }, { + "name" : "base-table", + "description" : "Recommendation" + } ], + "plan" : "LogicalProject(url=[$0], rec=[$1], frequency=[$2])\n LogicalFilter(condition=[=($0, ?0)])\n LogicalTableScan(table=[[default_catalog, default_database, Recommendation]])\n", + "sql" : "SELECT *\nFROM `default_catalog`.`default_database`.`Recommendation`\nWHERE `url` = ?" +}, { + "id" : "access:Trending", + "name" : "Trending", + "type" : "query", + "stage" : "postgres", + "documentation" : "Most visited pages", + "inputs" : [ "default_catalog.default_database.Trending" ], + "annotations" : [ { + "name" : "base-table", + "description" : "Trending" + } ], + "plan" : "LogicalProject(url=[$0], total=[$1])\n LogicalTableScan(table=[[default_catalog, default_database, Trending]])\n", + "sql" : "SELECT *\nFROM `default_catalog`.`default_database`.`Trending`" +}, { + "id" : "access:VisitAfter", + "name" : "VisitAfter", + "type" : "query", + "stage" : "postgres", + "inputs" : [ "default_catalog.default_database.VisitAfter" ], + "annotations" : [ { + "name" : "base-table", + "description" : "VisitAfter" + } ], + "plan" : "LogicalProject(beforeURL=[$0], afterURL=[$1], timestamp=[$2])\n LogicalTableScan(table=[[default_catalog, default_database, VisitAfter]])\n", + "sql" : "SELECT *\nFROM `default_catalog`.`default_database`.`VisitAfter`" +}, { + "id" : "default_catalog.default_database.Click", + "name" : "Click", + "type" : "stream", + "stage" : "flink", + "inputs" : [ "default_catalog.default_database.Click__base" ], + "annotations" : [ { + "name" : "stream-root", + "description" : "Click" + } ], + "plan" : "LogicalWatermarkAssigner(rowtime=[timestamp], watermark=[-($0, 1:INTERVAL SECOND)])\n LogicalTableScan(table=[[default_catalog, default_database, Click]])\n", + "sql" : "CREATE TEMPORARY TABLE `Click__schema` (\n `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL,\n `userid` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL,\n `url` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL\n)\nWITH (\n 'connector' = 'filesystem',\n 'format' = 'flexible-json',\n 'path' = '${DATA_PATH}/click.jsonl'\n);\nCREATE TABLE `Click` (\n PRIMARY KEY (`url`, `userid`, `timestamp`) NOT ENFORCED,\n WATERMARK FOR `timestamp` AS `timestamp` - INTERVAL '0.001' SECOND\n)\nLIKE `Click__schema`", + "timestamp" : "timestamp", + "schema" : [ { + "name" : "timestamp", + "type" : "TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL" + }, { + "name" : "userid", + "type" : "VARCHAR(2147483647) CHARACTER SET \"UTF-16LE\" NOT NULL" + }, { + "name" : "url", + "type" : "VARCHAR(2147483647) CHARACTER SET \"UTF-16LE\" NOT NULL" + } ], + "primary_key" : [ "url", "userid", "timestamp" ], + "row_count" : "~1e8" +}, { + "id" : "default_catalog.default_database.Click__base", + "name" : "Click", + "type" : "import", + "stage" : "flink", + "connector" : { + "format" : "flexible-json", + "path" : "${DATA_PATH}/click.jsonl", + "connector" : "filesystem" + } +}, { + "id" : "default_catalog.default_database.Recommendation", + "name" : "Recommendation", + "type" : "state", + "stage" : "flink", + "documentation" : "Recommend pages that are visited shortly after", + "inputs" : [ "default_catalog.default_database.VisitAfter" ], + "annotations" : [ { + "name" : "sort", + "description" : "[0 ASC-nulls-first, 2 DESC-nulls-last]" + } ], + "plan" : "LogicalAggregate(group=[{0, 1}], frequency=[COUNT()])\n LogicalProject(url=[$0], rec=[$1])\n LogicalTableScan(table=[[default_catalog, default_database, VisitAfter]])\n", + "sql" : "CREATE VIEW `Recommendation` AS SELECT beforeURL AS url, afterURL AS rec,\n count(1) AS frequency FROM VisitAfter\n GROUP BY beforeURL, afterURL ORDER BY url ASC, frequency DESC;\n", + "timestamp" : "-", + "schema" : [ { + "name" : "url", + "type" : "VARCHAR(2147483647) CHARACTER SET \"UTF-16LE\" NOT NULL" + }, { + "name" : "rec", + "type" : "VARCHAR(2147483647) CHARACTER SET \"UTF-16LE\" NOT NULL", + "description" : "the recommended page URL" + }, { + "name" : "frequency", + "type" : "BIGINT NOT NULL", + "description" : "the number of visitors that co-visited that page" + } ], + "primary_key" : [ "url", "rec" ], + "row_count" : "~2e7" +}, { + "id" : "default_catalog.default_database.Trending", + "name" : "Trending", + "type" : "state", + "stage" : "flink", + "documentation" : "Most visited pages", + "inputs" : [ "default_catalog.default_database.Click" ], + "annotations" : [ { + "name" : "sort", + "description" : "[1 DESC-nulls-last, 0 ASC-nulls-first]" + } ], + "plan" : "LogicalAggregate(group=[{0}], total=[COUNT()])\n LogicalProject(url=[$2])\n LogicalTableScan(table=[[default_catalog, default_database, Click]])\n", + "sql" : "CREATE VIEW `Trending` AS SELECT url, count(1) AS total\n FROM Click\n GROUP BY url ORDER BY total DESC, url ASC;\n", + "timestamp" : "-", + "schema" : [ { + "name" : "url", + "type" : "VARCHAR(2147483647) CHARACTER SET \"UTF-16LE\" NOT NULL", + "description" : "URL of the top visited page" + }, { + "name" : "total", + "type" : "BIGINT NOT NULL", + "description" : "Total number of visitors" + } ], + "primary_key" : [ "url" ], + "row_count" : "~1e7" +}, { + "id" : "default_catalog.default_database.VisitAfter", + "name" : "VisitAfter", + "type" : "stream", + "stage" : "flink", + "inputs" : [ "default_catalog.default_database.Click" ], + "annotations" : [ ], + "plan" : "LogicalProject(beforeURL=[$2], afterURL=[$5], timestamp=[$3])\n LogicalJoin(condition=[AND(=($1, $4), <($0, $3), >=($0, -($3, *(10, 60000:INTERVAL MINUTE))))], joinType=[inner])\n LogicalTableScan(table=[[default_catalog, default_database, Click]])\n LogicalTableScan(table=[[default_catalog, default_database, Click]])\n", + "sql" : "CREATE VIEW `VisitAfter` AS SELECT b.url AS beforeURL, a.url AS afterURL,\n a.`timestamp` AS `timestamp`\n FROM Click b JOIN Click a ON b.userid=a.userid AND\n b.`timestamp` < a.`timestamp` AND\n b.`timestamp` >= a.`timestamp` - INTERVAL 10 MINUTE;\n", + "timestamp" : "timestamp", + "schema" : [ { + "name" : "beforeURL", + "type" : "VARCHAR(2147483647) CHARACTER SET \"UTF-16LE\" NOT NULL" + }, { + "name" : "afterURL", + "type" : "VARCHAR(2147483647) CHARACTER SET \"UTF-16LE\" NOT NULL" + }, { + "name" : "timestamp", + "type" : "TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL" + } ], + "row_count" : "~3e7" +} ] +>>>pipeline_source.sqrl +CREATE TEMPORARY TABLE `Click__schema` ( + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `userid` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `url` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL +) +WITH ( + 'connector' = 'filesystem', + 'format' = 'flexible-json', + 'path' = '${DATA_PATH}/click.jsonl' +); +CREATE TABLE `Click` ( + PRIMARY KEY (`url`, `userid`, `timestamp`) NOT ENFORCED, + WATERMARK FOR `timestamp` AS `timestamp` - INTERVAL '0.001' SECOND +) +LIKE `Click__schema`; +/** Most visited pages + Columns: + - url: URL of the top visited page + - total: Total number of visitors +*/ +Trending := SELECT url, count(1) AS total + FROM Click + GROUP BY url ORDER BY total DESC, url ASC; +VisitAfter := SELECT b.url AS beforeURL, a.url AS afterURL, + a.`timestamp` AS `timestamp` + FROM Click b JOIN Click a ON b.userid=a.userid AND + b.`timestamp` < a.`timestamp` AND + b.`timestamp` >= a.`timestamp` - INTERVAL 10 MINUTE; +/** Recommend pages that are visited shortly after + Argument: + * url: the URL to get recommendations for + Columns: + * rec: the recommended page URL + * frequency: the number of visitors that co-visited that page +*/ +/*+query_by_all(url) */ +Recommendation := SELECT beforeURL AS url, afterURL AS rec, + count(1) AS frequency FROM VisitAfter + GROUP BY beforeURL, afterURL ORDER BY url ASC, frequency DESC; +/*+test */ +RankTest := SELECT url, count(1) AS total + FROM Click + GROUP BY url ORDER BY total DESC, url ASC; + diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-no-limit-offset.txt b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-no-limit-offset.txt new file mode 100644 index 0000000000..3e2bcc6bad --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-no-limit-offset.txt @@ -0,0 +1,28 @@ +[NOTICE] This table does not propagate the source row time columns: default_catalog.default_database._Customer[timestamp] +in script:comprehensiveTest.sqrl [10:1]: +CustomerFilteredDistinct := DISTINCT Customer ON customerid ORDER BY lastUpdated DESC; + +AnotherCustomer := SELECT customerid, email, lastUpdated FROM _Customer WHERE customerid > 100; +^ + +[NOTICE] You can rewrite the join as a temporal join for greater efficiency by adding: FOR SYSTEM_TIME AS OF `time` +in script:comprehensiveTest.sqrl [18:1]: +InvalidDistinct := SELECT customerid, `timestamp`, name AS namee FROM (SELECT *, (ROW_NUMBER() OVER (PARTITION BY customerid ORDER BY `timestamp` DESC)) AS _rownum FROM Customer) WHERE (_rownum = 1); + +MissedTemporalJoin := SELECT * FROM ExternalOrders o JOIN ExplicitDistinct c ON o.customerid = c.customerid; +^ + +[NOTICE] This table does not propagate the source row time columns: default_catalog.default_database.SelectCustomers[timestamp] +in script:comprehensiveTest.sqrl [51:1]: +); + +CustomerTimeWindow := SELECT +^ + +[FATAL] Paginated query [CustomerByTime2] must declare both 'limit' and 'offset' arguments +in script:comprehensiveTest-fail-paged-no-limit-offset.graphqls [31:5]: + +type Query { + CustomerByTime2: CustomerPage! +----^ + diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-subscription.txt b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-subscription.txt new file mode 100644 index 0000000000..a980021259 --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-subscription.txt @@ -0,0 +1,28 @@ +[NOTICE] This table does not propagate the source row time columns: default_catalog.default_database._Customer[timestamp] +in script:comprehensiveTest.sqrl [10:1]: +CustomerFilteredDistinct := DISTINCT Customer ON customerid ORDER BY lastUpdated DESC; + +AnotherCustomer := SELECT customerid, email, lastUpdated FROM _Customer WHERE customerid > 100; +^ + +[NOTICE] You can rewrite the join as a temporal join for greater efficiency by adding: FOR SYSTEM_TIME AS OF `time` +in script:comprehensiveTest.sqrl [18:1]: +InvalidDistinct := SELECT customerid, `timestamp`, name AS namee FROM (SELECT *, (ROW_NUMBER() OVER (PARTITION BY customerid ORDER BY `timestamp` DESC)) AS _rownum FROM Customer) WHERE (_rownum = 1); + +MissedTemporalJoin := SELECT * FROM ExternalOrders o JOIN ExplicitDistinct c ON o.customerid = c.customerid; +^ + +[NOTICE] This table does not propagate the source row time columns: default_catalog.default_database.SelectCustomers[timestamp] +in script:comprehensiveTest.sqrl [51:1]: +); + +CustomerTimeWindow := SELECT +^ + +[FATAL] Paginated result types are only supported for queries: CustomerSubscription +in script:comprehensiveTest-fail-paged-subscription.graphqls [24:5]: + +type Subscription { + CustomerSubscription: CustomerPage +----^ + diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-undeclared.txt b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-undeclared.txt new file mode 100644 index 0000000000..f1e03f13fe --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-undeclared.txt @@ -0,0 +1,39 @@ +[NOTICE] This table does not propagate the source row time columns: default_catalog.default_database._Customer[timestamp] +in script:comprehensiveTest.sqrl [10:1]: +CustomerFilteredDistinct := DISTINCT Customer ON customerid ORDER BY lastUpdated DESC; + +AnotherCustomer := SELECT customerid, email, lastUpdated FROM _Customer WHERE customerid > 100; +^ + +[NOTICE] You can rewrite the join as a temporal join for greater efficiency by adding: FOR SYSTEM_TIME AS OF `time` +in script:comprehensiveTest.sqrl [18:1]: +InvalidDistinct := SELECT customerid, `timestamp`, name AS namee FROM (SELECT *, (ROW_NUMBER() OVER (PARTITION BY customerid ORDER BY `timestamp` DESC)) AS _rownum FROM Customer) WHERE (_rownum = 1); + +MissedTemporalJoin := SELECT * FROM ExternalOrders o JOIN ExplicitDistinct c ON o.customerid = c.customerid; +^ + +[NOTICE] This table does not propagate the source row time columns: default_catalog.default_database.SelectCustomers[timestamp] +in script:comprehensiveTest.sqrl [51:1]: +); + +CustomerTimeWindow := SELECT +^ + +[FATAL] Paginated results require the OffsetPageInfo type to be declared in the schema: +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +in script:comprehensiveTest-fail-paged-undeclared.graphqls [20:5]: + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +----^ + diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-unknown-field.txt b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-unknown-field.txt new file mode 100644 index 0000000000..dc5e91431a --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-unknown-field.txt @@ -0,0 +1,28 @@ +[NOTICE] This table does not propagate the source row time columns: default_catalog.default_database._Customer[timestamp] +in script:comprehensiveTest.sqrl [10:1]: +CustomerFilteredDistinct := DISTINCT Customer ON customerid ORDER BY lastUpdated DESC; + +AnotherCustomer := SELECT customerid, email, lastUpdated FROM _Customer WHERE customerid > 100; +^ + +[NOTICE] You can rewrite the join as a temporal join for greater efficiency by adding: FOR SYSTEM_TIME AS OF `time` +in script:comprehensiveTest.sqrl [18:1]: +InvalidDistinct := SELECT customerid, `timestamp`, name AS namee FROM (SELECT *, (ROW_NUMBER() OVER (PARTITION BY customerid ORDER BY `timestamp` DESC)) AS _rownum FROM Customer) WHERE (_rownum = 1); + +MissedTemporalJoin := SELECT * FROM ExternalOrders o JOIN ExplicitDistinct c ON o.customerid = c.customerid; +^ + +[NOTICE] This table does not propagate the source row time columns: default_catalog.default_database.SelectCustomers[timestamp] +in script:comprehensiveTest.sqrl [51:1]: +); + +CustomerTimeWindow := SELECT +^ + +[FATAL] Unknown field at location doesNotExist. Possible scalars are [customerid, email, name, lastUpdated, timestamp] +in script:comprehensiveTest-fail-paged-unknown-field.graphqls [12:5]: + lastUpdated: Long! + timestamp: DateTime! + doesNotExist: String! +----^ + diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-wrong-pagination-type.txt b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-wrong-pagination-type.txt new file mode 100644 index 0000000000..a7a8c6905f --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-fail-paged-wrong-pagination-type.txt @@ -0,0 +1,39 @@ +[NOTICE] This table does not propagate the source row time columns: default_catalog.default_database._Customer[timestamp] +in script:comprehensiveTest.sqrl [10:1]: +CustomerFilteredDistinct := DISTINCT Customer ON customerid ORDER BY lastUpdated DESC; + +AnotherCustomer := SELECT customerid, email, lastUpdated FROM _Customer WHERE customerid > 100; +^ + +[NOTICE] You can rewrite the join as a temporal join for greater efficiency by adding: FOR SYSTEM_TIME AS OF `time` +in script:comprehensiveTest.sqrl [18:1]: +InvalidDistinct := SELECT customerid, `timestamp`, name AS namee FROM (SELECT *, (ROW_NUMBER() OVER (PARTITION BY customerid ORDER BY `timestamp` DESC)) AS _rownum FROM Customer) WHERE (_rownum = 1); + +MissedTemporalJoin := SELECT * FROM ExternalOrders o JOIN ExplicitDistinct c ON o.customerid = c.customerid; +^ + +[NOTICE] This table does not propagate the source row time columns: default_catalog.default_database.SelectCustomers[timestamp] +in script:comprehensiveTest.sqrl [51:1]: +); + +CustomerTimeWindow := SELECT +^ + +[FATAL] User-defined OffsetPageInfo does not match the expected definition: +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +in script:comprehensiveTest-fail-paged-wrong-pagination-type.graphqls [6:1]: +scalar Long + +type OffsetPageInfo { +^ + diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-limit-offset-combinations.txt b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-limit-offset-combinations.txt index 9e9a86e006..2630e05b72 100644 --- a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-limit-offset-combinations.txt +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-limit-offset-combinations.txt @@ -269,6 +269,153 @@ type Query { TableFunctionCallsTblFct(arg1: Int, arg2: Int, limit: Int = 10, offset: Int = 0): [Customer!] } +>>>comprehensiveTest-fail-paged-no-limit-offset.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2: CustomerPage! +} + +>>>comprehensiveTest-fail-paged-subscription.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +type Subscription { + CustomerSubscription: CustomerPage +} + +>>>comprehensiveTest-fail-paged-undeclared.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-fail-paged-unknown-field.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + doesNotExist: String! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-fail-paged-wrong-pagination-type.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type OffsetPageInfo { + totalRecords: Long! + pageSize: Int! +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + >>>comprehensiveTest-fail-parameters-names.graphqls "An RFC-3339 compliant DateTime Scalar" scalar DateTime @@ -417,6 +564,80 @@ type Query { SelectCustomers(offset: Int = 0): [Customer!] } +>>>comprehensiveTest-paged-results.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-paged-userdefined.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + related(limit: Int = 10, offset: Int = 0): CustomerRelatedPage +} + +type CustomerRelatedPage { + items: [Customer!] + meta: OffsetPageInfo +} + +type CustomerPage { + items: [Customer!] + meta: OffsetPageInfo +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): CustomerPage! +} + >>>comprehensiveTest-parameters-order.graphqls "An RFC-3339 compliant DateTime Scalar" scalar DateTime diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-paged-results.txt b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-paged-results.txt new file mode 100644 index 0000000000..d3264b3364 --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-paged-results.txt @@ -0,0 +1,1844 @@ +>>>comprehensiveTest-fail-duplicate-tablefunction.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Subscription { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-duplicate-tablefunction2.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-empty-mutation.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Mutation { +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-empty-query.graphqls +type Query { +} + +>>>comprehensiveTest-fail-empty-result-type.graphqls +type Customer { +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-empty-subscription.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Subscription { +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-invalid-field-name.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name~: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-invalid-type-name.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer~ { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer~!] +} + +>>>comprehensiveTest-fail-no-query-root-type.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Mutation { + Orders(event: OrdersInput!): OrdersResultOutput! +} + +input OrdersInput { + orderid: Int! + amount: Float +} + +type OrdersResultOutput { + orderid: Int! + amount: Float +} + +type Subscription { + CustomerSubscription: Customer +} + +>>>comprehensiveTest-fail-no-type.graphqls +type Query { + AnotherCustomer(limit: Int = 10, offset: Int = 0): [AnotherCustomer!] +} + +>>>comprehensiveTest-fail-not-equal-mutations-field-types.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Mutation { + Orders(event: OrdersInput!): OrdersResultOutput! +} + +input OrdersInput { + orderid: Int! + amount: Float +} + +type OrdersResultOutput { + orderid: Int! + amount: Int +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-not-equal-mutations-inout-types.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Mutation { + Orders(event: OrdersInput!): OrdersResultOutput! +} + +input OrdersInput { + orderid: Int! + amount: Float +} + +type OrdersResultOutput { + orderid: Int! + amount: Float + different_output: Float +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-nullableArgForNotNull.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int, arg2: Int, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-paged-no-limit-offset.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2: CustomerPage! +} + +>>>comprehensiveTest-fail-paged-subscription.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +type Subscription { + CustomerSubscription: CustomerPage +} + +>>>comprehensiveTest-fail-paged-undeclared.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-fail-paged-unknown-field.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + doesNotExist: String! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-fail-paged-wrong-pagination-type.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type OffsetPageInfo { + totalRecords: Long! + pageSize: Int! +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-fail-parameters-names.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(wrongName: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-parameters-types.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Float!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-query-with-no-table-function.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct1(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-subscription-with-no-table-function.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Subscription { + TableFunctionCallsTblFct1(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-unknown-field.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] + unknown: Int +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-wrong-query.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [[Customer!]] +} + +>>>comprehensiveTest-limit-offset-combinations.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] + CustomerByTime2: [Customer!] + CustomerByMultipleTime(limit: Int = 10): [Customer!] + SelectCustomers(offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-paged-results.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-paged-userdefined.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + related(limit: Int = 10, offset: Int = 0): CustomerRelatedPage +} + +type CustomerRelatedPage { + items: [Customer!] + meta: OffsetPageInfo +} + +type CustomerPage { + items: [Customer!] + meta: OffsetPageInfo +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-parameters-order.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg2: Int!, arg1: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest.graphqls +type AnotherCustomer { + customerid: Long! + email: String! + lastUpdated: Long! +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type CustomerByMultipleTime { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime +} + +type CustomerByTime2 { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime +} + +type CustomerFilteredDistinct { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime +} + +type CustomerTimeWindow { + window_start: DateTime! + window_end: DateTime! + unique_email_count: Long! +} + +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime + +type ExplicitDistinct { + customerid: Long! + timestamp: DateTime + name: String! +} + +type ExternalOrders { + id: Long! + customerid: Long! + time: DateTime! + entries: [ExternalOrders_entriesOutput]! +} + +type ExternalOrders_entriesOutput { + productid: Long! + quantity: Long! + unit_price: Float! + discount: Float +} + +"A 64-bit signed integer" +scalar Long + +type InvalidDistinct { + customerid: Long! + timestamp: DateTime + namee: String! +} + +type MissedTemporalJoin { + id: Long! + customerid: Long! + time: DateTime! + entries: [MissedTemporalJoin_entriesOutput]! + customerid0: Long! + timestamp: DateTime + name: String! +} + +type MissedTemporalJoin_entriesOutput { + productid: Long! + quantity: Long! + unit_price: Float! + discount: Float +} + +type Mutation { + Orders(event: OrdersInput!): OrdersResultOutput! +} + +type Orders { + orderid: Int! + amount: Float +} + +input OrdersInput { + orderid: Int! + amount: Float +} + +type OrdersResultOutput { + orderid: Int! + amount: Float +} + +type Query { + AnotherCustomer(limit: Int = 10, offset: Int = 0): [AnotherCustomer!] + Customer(limit: Int = 10, offset: Int = 0): [Customer!] + CustomerByMultipleTime(limit: Int = 10, offset: Int = 0): [CustomerByMultipleTime!] + CustomerByTime2(limit: Int = 10, offset: Int = 0): [CustomerByTime2!] + CustomerFilteredDistinct(limit: Int = 10, offset: Int = 0): [CustomerFilteredDistinct!] + CustomerTimeWindow(limit: Int = 10, offset: Int = 0): [CustomerTimeWindow!] + ExplicitDistinct(limit: Int = 10, offset: Int = 0): [ExplicitDistinct!] + ExternalOrders(limit: Int = 10, offset: Int = 0): [ExternalOrders!] + InvalidDistinct(limit: Int = 10, offset: Int = 0): [InvalidDistinct!] + MissedTemporalJoin(limit: Int = 10, offset: Int = 0): [MissedTemporalJoin!] + Orders(limit: Int = 10, offset: Int = 0): [Orders!] + """ + This is for selected customers + and their orders + """ + SelectCustomers(limit: Int = 10, offset: Int = 0): [Customer!] + TemporalJoin(limit: Int = 10, offset: Int = 0): [TemporalJoin!] + UnnestOrders(limit: Int = 10, offset: Int = 0): [UnnestOrders!] + CustomerById(minId: Int!, limit: Int = 10, offset: Int = 0): [Customer!] + CustomerQuery(id: Long!, limit: Int = 10, offset: Int = 0): [AnotherCustomer!] + TableFunctionCallsTblFct(arg1: Int, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Subscription { + CustomerSubscription: Customer + CustomerSubscriptionById(minId: Int!): Customer +} + +type TemporalJoin { + id: Long! + customerid: Long! + time: DateTime! + entries: [TemporalJoin_entriesOutput]! + customerid0: Long! + timestamp: DateTime + name: String! +} + +type TemporalJoin_entriesOutput { + productid: Long! + quantity: Long! + unit_price: Float! + discount: Float +} + +type UnnestOrders { + id: Long! + customerid: Long! + time: DateTime! + productid: Long! + quantity: Long! + discount: Float + newId: Long! +} + +>>>pipeline_explain.txt +=== AnotherCustomer +ID: default_catalog.default_database.AnotherCustomer +Type: stream +Stage: flink +Primary key: customerid, lastUpdated +Timestamp: - +Row count: ~5e7 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL +Inputs: + - default_catalog.default_database._Customer +Annotations: + - stream-root: _Customer + +=== Customer +ID: default_catalog.default_database.Customer +Type: stream +Stage: flink +Primary key: customerid, lastUpdated +Timestamp: timestamp +Row count: ~1e8 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database.Customer__base +Annotations: + - stream-root: Customer + +=== CustomerById +ID: default_catalog.default_database.CustomerById +Type: query +Stage: postgres +--- +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + - parameters: minId + - base-table: Customer + +=== CustomerByMultipleTime +ID: default_catalog.default_database.CustomerByMultipleTime +Type: state +Stage: flink +Primary key: customerid, email +Timestamp: timestamp +Row count: ~2e7 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - mostRecentDistinct: true + - stream-root: Customer + +=== CustomerByTime2 +ID: default_catalog.default_database.CustomerByTime2 +Type: state +Stage: flink +Primary key: customerid +Timestamp: timestamp +Row count: ~2e7 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - mostRecentDistinct: true + - stream-root: Customer + +=== CustomerFilteredDistinct +ID: default_catalog.default_database.CustomerFilteredDistinct +Type: state +Stage: flink +Primary key: customerid +Timestamp: timestamp +Row count: ~2e6 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + +=== CustomerQuery +ID: default_catalog.default_database.CustomerQuery +Type: query +Stage: postgres +--- +Inputs: + - default_catalog.default_database.AnotherCustomer +Annotations: + - stream-root: _Customer + - parameters: id + - base-table: AnotherCustomer + +=== CustomerSubscription +ID: default_catalog.default_database.CustomerSubscription +Type: stream +Stage: flink +Primary key: customerid, lastUpdated +Timestamp: timestamp +Row count: ~1e8 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + +=== CustomerSubscriptionById +ID: default_catalog.default_database.CustomerSubscriptionById +Type: query +Stage: kafka +--- +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + - parameters: minId + - base-table: Customer + +=== CustomerTimeWindow +ID: default_catalog.default_database.CustomerTimeWindow +Type: stream +Stage: flink +Primary key: window_start, window_end +Timestamp: - +Row count: ~3e7 +--- +Schema: + - window_start: TIMESTAMP(3) NOT NULL + - window_end: TIMESTAMP(3) NOT NULL + - unique_email_count: BIGINT NOT NULL +Inputs: + - default_catalog.default_database.SelectCustomers +Annotations: + - features: STREAM_WINDOW_AGGREGATION (feature) + +=== ExplicitDistinct +ID: default_catalog.default_database.ExplicitDistinct +Type: state +Stage: flink +Primary key: customerid +Timestamp: timestamp +Row count: ~2e7 +--- +Schema: + - customerid: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + +=== ExternalOrders +ID: default_catalog.default_database.ExternalOrders +Type: stream +Stage: flink +Primary key: id, time +Timestamp: time +Row count: ~1e8 +--- +Schema: + - id: BIGINT NOT NULL + - customerid: BIGINT NOT NULL + - time: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - entries: RecordType:peek_no_expand(BIGINT NOT NULL productid, BIGINT NOT NULL quantity, DOUBLE NOT NULL unit_price, DOUBLE discount) NOT NULL ARRAY NOT NULL +Inputs: + - default_catalog.default_database.ExternalOrders__base +Annotations: + - features: DENORMALIZE (feature) + - stream-root: ExternalOrders + +=== InvalidDistinct +ID: default_catalog.default_database.InvalidDistinct +Type: state +Stage: flink +Primary key: customerid +Timestamp: timestamp +Row count: ~2e7 +--- +Schema: + - customerid: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - namee: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + +=== MissedTemporalJoin +ID: default_catalog.default_database.MissedTemporalJoin +Type: state +Stage: postgres +Primary key: id, time +Timestamp: time +Row count: ~1e8 +--- +Schema: + - id: BIGINT NOT NULL + - customerid: BIGINT NOT NULL + - time: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - entries: RecordType:peek_no_expand(BIGINT NOT NULL productid, BIGINT NOT NULL quantity, DOUBLE NOT NULL unit_price, DOUBLE discount) NOT NULL ARRAY NOT NULL + - customerid0: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL +Inputs: + - default_catalog.default_database.ExplicitDistinct + - default_catalog.default_database.ExternalOrders + +=== Orders +ID: default_catalog.default_database.Orders +Type: state +Stage: flink +Primary key: orderid +Timestamp: - +Row count: ~1e8 +--- +Schema: + - orderid: INTEGER NOT NULL + - amount: FLOAT +Inputs: + - default_catalog.default_database.Orders__base + +=== SelectCustomers +ID: default_catalog.default_database.SelectCustomers +Type: stream +Stage: flink +Primary key: customerid, name +Timestamp: timestamp +Row count: ~5e7 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + - sort: [4 DESC-nulls-last] + +=== TableFunctionCallsTblFct +ID: default_catalog.default_database.TableFunctionCallsTblFct +Type: query +Stage: postgres +--- +Inputs: + - default_catalog.default_database.CustomerById +Annotations: + - features: TABLE_FUNCTION_SCAN (feature) + - stream-root: Customer + - parameters: arg1, arg2 + - base-table: Customer + +=== TemporalJoin +ID: default_catalog.default_database.TemporalJoin +Type: stream +Stage: flink +Primary key: id, time +Timestamp: time +Row count: ~9e7 +--- +Schema: + - id: BIGINT NOT NULL + - customerid: BIGINT NOT NULL + - time: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - entries: RecordType:peek_no_expand(BIGINT NOT NULL productid, BIGINT NOT NULL quantity, DOUBLE NOT NULL unit_price, DOUBLE discount) NOT NULL ARRAY NOT NULL + - customerid0: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL +Inputs: + - default_catalog.default_database.ExplicitDistinct + - default_catalog.default_database.ExternalOrders +Annotations: + - stream-root: ExternalOrders + +=== UnnestOrders +ID: default_catalog.default_database.UnnestOrders +Type: stream +Stage: flink +Primary key: - +Timestamp: time +Row count: ~1e8 +--- +Schema: + - id: BIGINT NOT NULL + - customerid: BIGINT NOT NULL + - time: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - productid: BIGINT NOT NULL + - quantity: BIGINT NOT NULL + - discount: DOUBLE + - newId: BIGINT NOT NULL +Inputs: + - default_catalog.default_database.ExternalOrders +Annotations: + - stream-root: ExternalOrders + +=== _Customer +ID: default_catalog.default_database._Customer +Type: stream +Stage: flink +Primary key: customerid, lastUpdated +Timestamp: timestamp +Row count: ~1e8 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database._Customer__base +Annotations: + - stream-root: _Customer + +=== customersink +ID: mysink.customersink +Type: export +Stage: flink +Connector: print +--- +Inputs: + - default_catalog.default_database.TemporalJoin + +=== TimeWindow +ID: print.TimeWindow +Type: export +Stage: flink +--- +Inputs: + - default_catalog.default_database.CustomerTimeWindow + +>>>flink-sql-no-functions.sql +CREATE TABLE `Customer` ( + `customerid` BIGINT NOT NULL, + `email` STRING NOT NULL, + `name` STRING NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` AS COALESCE(`TO_TIMESTAMP_LTZ`(`lastUpdated`, 0), TIMESTAMP '1970-01-01 00:00:00.000'), + PRIMARY KEY (`customerid`, `lastUpdated`) NOT ENFORCED, + WATERMARK FOR `timestamp` AS `timestamp` - INTERVAL '0.001' SECOND +) +WITH ( + 'connector' = 'filesystem', + 'format' = 'flexible-json', + 'path' = 'file:/mock', + 'source.monitor-interval' = '10 sec' +); +CREATE TABLE `ExternalOrders` ( + `id` BIGINT NOT NULL, + `customerid` BIGINT NOT NULL, + `time` TIMESTAMP_LTZ(3) NOT NULL, + `entries` ROW(`productid` BIGINT NOT NULL, `quantity` BIGINT NOT NULL, `unit_price` DOUBLE NOT NULL, `discount` DOUBLE) NOT NULL ARRAY NOT NULL, + PRIMARY KEY (`id`, `time`) NOT ENFORCED, + WATERMARK FOR `time` AS `time` - INTERVAL '0.001' SECOND +) +WITH ( + 'connector' = 'filesystem', + 'format' = 'flexible-json', + 'path' = 'file:/mock', + 'source.monitor-interval' = '10 sec' +); +CREATE TABLE `_Customer` ( + `customerid` BIGINT NOT NULL, + `email` STRING NOT NULL, + `name` STRING NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` AS COALESCE(`TO_TIMESTAMP_LTZ`(`lastUpdated`, 0), TIMESTAMP '1970-01-01 00:00:00.000'), + PRIMARY KEY (`customerid`, `lastUpdated`) NOT ENFORCED, + WATERMARK FOR `timestamp` AS `timestamp` - INTERVAL '0.001' SECOND +) +WITH ( + 'connector' = 'filesystem', + 'format' = 'flexible-json', + 'path' = 'file:/mock', + 'source.monitor-interval' = '10 sec' +); +CREATE TABLE `_Orders` ( + `id` BIGINT NOT NULL, + `customerid` BIGINT NOT NULL, + `time` TIMESTAMP_LTZ(3) NOT NULL, + `entries` ROW(`productid` BIGINT NOT NULL, `quantity` BIGINT NOT NULL, `unit_price` DOUBLE NOT NULL, `discount` DOUBLE) NOT NULL ARRAY NOT NULL, + PRIMARY KEY (`id`, `time`) NOT ENFORCED, + WATERMARK FOR `time` AS `time` - INTERVAL '0.001' SECOND +) +WITH ( + 'connector' = 'filesystem', + 'format' = 'flexible-json', + 'path' = 'file:/mock', + 'source.monitor-interval' = '10 sec' +); +CREATE TABLE `_Product` ( + `productid` BIGINT NOT NULL, + `name` STRING NOT NULL, + `description` STRING NOT NULL, + `category` STRING NOT NULL, + `_ingest_time` TIMESTAMP_LTZ(3) NOT NULL, + PRIMARY KEY (`productid`, `name`, `description`, `category`) NOT ENFORCED, + WATERMARK FOR `_ingest_time` AS `_ingest_time` - INTERVAL '0.001' SECOND +) +WITH ( + 'connector' = 'filesystem', + 'format' = 'flexible-json', + 'path' = 'file:/mock', + 'source.monitor-interval' = '10 sec' +); +CREATE VIEW `CustomerByTime2` +AS +SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp` +FROM (SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp`, ROW_NUMBER() OVER (PARTITION BY `customerid` ORDER BY `timestamp` DESC NULLS LAST) AS `__sqrlinternal_rownum` + FROM `default_catalog`.`default_database`.`Customer`) AS `t` +WHERE `__sqrlinternal_rownum` = 1; +CREATE VIEW `CustomerFilteredDistinct` +AS +SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp` +FROM (SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp`, ROW_NUMBER() OVER (PARTITION BY `customerid` ORDER BY `lastUpdated` DESC NULLS LAST) AS `$f5` + FROM (SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp` + FROM (SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp`, LAG(`email`, 1) OVER (PARTITION BY `customerid` ORDER BY `timestamp`) AS `$f5`, LAG(`name`, 1) OVER (PARTITION BY `customerid` ORDER BY `timestamp`) AS `$f6` + FROM (SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp`, MAX(`lastUpdated`) OVER (PARTITION BY `customerid` ORDER BY `timestamp` RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS `$f5` + FROM `default_catalog`.`default_database`.`Customer`) AS `t` + WHERE `lastUpdated` >= `$f5`) AS `t1` + WHERE `$f5` IS NULL AND `$f6` IS NULL OR `email` <> `$f5` OR `name` <> `$f6`) AS `t3`) AS `t4` +WHERE `$f5` = 1; +CREATE VIEW `AnotherCustomer` +AS +SELECT `customerid`, `email`, `lastUpdated` +FROM `_Customer` +WHERE `customerid` > 100; +CREATE VIEW `CustomerByMultipleTime` +AS +SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp` +FROM (SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp`, ROW_NUMBER() OVER (PARTITION BY `customerid`, `email` ORDER BY `timestamp` DESC NULLS LAST, `lastUpdated` NULLS FIRST) AS `__sqrlinternal_rownum` + FROM `default_catalog`.`default_database`.`Customer`) AS `t` +WHERE `__sqrlinternal_rownum` = 1; +CREATE VIEW `ExplicitDistinct` +AS +SELECT `customerid`, `timestamp`, `name` +FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY `customerid` ORDER BY `timestamp` DESC) AS `_rownum` + FROM `Customer`) +WHERE `_rownum` = 1; +CREATE VIEW `InvalidDistinct` +AS +SELECT `customerid`, `timestamp`, `name` AS `namee` +FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY `customerid` ORDER BY `timestamp` DESC) AS `_rownum` + FROM `Customer`) +WHERE `_rownum` = 1; +CREATE VIEW `MissedTemporalJoin` +AS +SELECT * +FROM `ExternalOrders` AS `o` + INNER JOIN `ExplicitDistinct` AS `c` ON `o`.`customerid` = `c`.`customerid`; +CREATE VIEW `TemporalJoin` +AS +SELECT * +FROM `ExternalOrders` AS `o` + INNER JOIN `ExplicitDistinct` FOR SYSTEM_TIME AS OF `time` AS `c` ON `o`.`customerid` = `c`.`customerid`; +CREATE VIEW `SelectCustomers` +AS +SELECT * +FROM `Customer` +WHERE `customerid` > 0; +CREATE VIEW `CustomerSubscription` +AS +SELECT * +FROM `Customer`; +CREATE VIEW `UnnestOrders` +AS +SELECT `o`.`id`, `o`.`customerid`, `o`.`time`, `e`.`productid`, `e`.`quantity`, `e`.`discount` +FROM `ExternalOrders` AS `o` + CROSS JOIN UNNEST(`entries`) AS `e`; +ALTER VIEW `UnnestOrders` +AS +SELECT `id`, `customerid`, `time`, `productid`, `quantity`, `discount`, `id` + `productid` AS `newId` +FROM (SELECT `o`.`id`, `o`.`customerid`, `o`.`time`, `e`.`productid`, `e`.`quantity`, `e`.`discount` + FROM `ExternalOrders` AS `o` + CROSS JOIN UNNEST(`entries`) AS `e`); +CREATE TABLE `Orders` ( + `orderid` INTEGER, + `amount` FLOAT, + PRIMARY KEY (`orderid`) NOT ENFORCED +) +WITH ( + 'connector' = 'upsert-kafka', + 'key.format' = 'flexible-json', + 'properties.auto.offset.reset' = 'earliest', + 'properties.bootstrap.servers' = '${KAFKA_BOOTSTRAP_SERVERS}', + 'properties.compression.type' = 'zstd', + 'properties.group.id' = '${KAFKA_GROUP_ID}', + 'topic' = 'Orders', + 'value.fields-include' = 'ALL', + 'value.format' = 'flexible-json' +); +CREATE VIEW `CustomerTimeWindow` +AS +SELECT `window_start`, `window_end`, COUNT(DISTINCT `email`) AS `unique_email_count` +FROM TABLE(TUMBLE(TABLE `SelectCustomers`, DESCRIPTOR(`timestamp`), INTERVAL '1' MINUTE)) +GROUP BY `window_start`, `window_end`; +CREATE TEMPORARY TABLE `MyPrintSink_ex1__schema` ( + `id` BIGINT NOT NULL, + `customerid` BIGINT NOT NULL, + `time` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `entries` ROW(`productid` BIGINT NOT NULL, `quantity` BIGINT NOT NULL, `unit_price` DOUBLE NOT NULL, `discount` DOUBLE) NOT NULL ARRAY NOT NULL, + `customerid0` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL +) +WITH ( + 'connector' = 'datagen' +); +CREATE TABLE `MyPrintSink_ex1` ( + PRIMARY KEY (`customerid`) NOT ENFORCED +) +WITH ( + 'connector' = 'print' +) +LIKE `MyPrintSink_ex1__schema`; +CREATE TABLE `AnotherCustomer_1` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + PRIMARY KEY (`customerid`, `lastUpdated`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'AnotherCustomer', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `Customer_2` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + PRIMARY KEY (`customerid`, `lastUpdated`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'Customer', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `Customer_3` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL +) +WITH ( + 'connector' = 'kafka', + 'format' = 'flexible-json', + 'properties.bootstrap.servers' = '${KAFKA_BOOTSTRAP_SERVERS}', + 'properties.compression.type' = 'zstd', + 'properties.group.id' = '${KAFKA_GROUP_ID}', + 'topic' = 'Customer' +); +CREATE TABLE `CustomerByMultipleTime_4` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + PRIMARY KEY (`customerid`, `email`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'TIMESTAMP', + 'sink.on-conflict.timestamp-column' = 'timestamp', + 'table-name' = 'CustomerByMultipleTime', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `CustomerByTime2_5` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + PRIMARY KEY (`customerid`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'TIMESTAMP', + 'sink.on-conflict.timestamp-column' = 'timestamp', + 'table-name' = 'CustomerByTime2', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `CustomerFilteredDistinct_6` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + PRIMARY KEY (`customerid`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'TIMESTAMP', + 'sink.on-conflict.timestamp-column' = 'timestamp', + 'table-name' = 'CustomerFilteredDistinct', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `CustomerSubscription_7` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL +) +WITH ( + 'connector' = 'kafka', + 'format' = 'flexible-json', + 'properties.bootstrap.servers' = '${KAFKA_BOOTSTRAP_SERVERS}', + 'properties.compression.type' = 'zstd', + 'properties.group.id' = '${KAFKA_GROUP_ID}', + 'topic' = 'CustomerSubscription' +); +CREATE TABLE `TimeWindow_8` ( + `window_start` TIMESTAMP(3) NOT NULL, + `window_end` TIMESTAMP(3) NOT NULL, + `unique_email_count` BIGINT NOT NULL, + PRIMARY KEY (`window_start`, `window_end`) NOT ENFORCED +) +WITH ( + 'connector' = 'print', + 'print-identifier' = 'TimeWindow' +); +CREATE TABLE `CustomerTimeWindow_9` ( + `window_start` TIMESTAMP(3) NOT NULL, + `window_end` TIMESTAMP(3) NOT NULL, + `unique_email_count` BIGINT NOT NULL, + PRIMARY KEY (`window_start`, `window_end`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'CustomerTimeWindow', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `ExplicitDistinct_10` ( + `customerid` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + PRIMARY KEY (`customerid`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'TIMESTAMP', + 'sink.on-conflict.timestamp-column' = 'timestamp', + 'table-name' = 'ExplicitDistinct', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `ExternalOrders_11` ( + `id` BIGINT NOT NULL, + `customerid` BIGINT NOT NULL, + `time` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `entries` RAW('com.datasqrl.flinkrunner.stdlib.json.FlinkJsonType', 'AERjb20uZGF0YXNxcmwuZmxpbmtydW5uZXIuc3RkbGliLmpzb24uRmxpbmtKc29uVHlwZVNlcmlhbGl6ZXJTbmFwc2hvdAAAAAM='), + PRIMARY KEY (`id`, `time`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'ExternalOrders', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `InvalidDistinct_12` ( + `customerid` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `namee` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + PRIMARY KEY (`customerid`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'TIMESTAMP', + 'sink.on-conflict.timestamp-column' = 'timestamp', + 'table-name' = 'InvalidDistinct', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `Orders_13` ( + `orderid` INTEGER NOT NULL, + `amount` FLOAT, + PRIMARY KEY (`orderid`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'table-name' = 'Orders', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `SelectCustomers_14` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + PRIMARY KEY (`customerid`, `name`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'SelectCustomers', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `TemporalJoin_15` ( + `id` BIGINT NOT NULL, + `customerid` BIGINT NOT NULL, + `time` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `entries` RAW('com.datasqrl.flinkrunner.stdlib.json.FlinkJsonType', 'AERjb20uZGF0YXNxcmwuZmxpbmtydW5uZXIuc3RkbGliLmpzb24uRmxpbmtKc29uVHlwZVNlcmlhbGl6ZXJTbmFwc2hvdAAAAAM='), + `customerid0` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + PRIMARY KEY (`id`, `time`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'TemporalJoin', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `UnnestOrders_16` ( + `id` BIGINT NOT NULL, + `customerid` BIGINT NOT NULL, + `time` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `productid` BIGINT NOT NULL, + `quantity` BIGINT NOT NULL, + `discount` DOUBLE, + `newId` BIGINT NOT NULL, + `__pk_hash` CHAR(32) CHARACTER SET `UTF-16LE`, + PRIMARY KEY (`__pk_hash`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'UnnestOrders', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +EXECUTE STATEMENT SET BEGIN +INSERT INTO `default_catalog`.`default_database`.`AnotherCustomer_1` +SELECT * +FROM `default_catalog`.`default_database`.`AnotherCustomer` +; +INSERT INTO `default_catalog`.`default_database`.`Customer_2` +SELECT * +FROM `default_catalog`.`default_database`.`Customer` +; +INSERT INTO `default_catalog`.`default_database`.`Customer_3` +SELECT * +FROM `default_catalog`.`default_database`.`Customer` +; +INSERT INTO `default_catalog`.`default_database`.`CustomerByMultipleTime_4` +SELECT * +FROM `default_catalog`.`default_database`.`Customer` +; +INSERT INTO `default_catalog`.`default_database`.`CustomerByTime2_5` +SELECT * +FROM `default_catalog`.`default_database`.`Customer` +; +INSERT INTO `default_catalog`.`default_database`.`CustomerFilteredDistinct_6` +SELECT * +FROM `default_catalog`.`default_database`.`CustomerFilteredDistinct` +; +INSERT INTO `default_catalog`.`default_database`.`CustomerSubscription_7` +SELECT * +FROM `default_catalog`.`default_database`.`CustomerSubscription` +; +INSERT INTO `default_catalog`.`default_database`.`TimeWindow_8` +SELECT * +FROM `default_catalog`.`default_database`.`CustomerTimeWindow` +; +INSERT INTO `default_catalog`.`default_database`.`CustomerTimeWindow_9` +SELECT * +FROM `default_catalog`.`default_database`.`CustomerTimeWindow` +; +INSERT INTO `default_catalog`.`default_database`.`ExplicitDistinct_10` +SELECT * +FROM `default_catalog`.`default_database`.`ExplicitDistinct` +; +INSERT INTO `default_catalog`.`default_database`.`ExternalOrders_11` +SELECT `id`, `customerid`, `time`, `to_jsonb`(`entries`) AS `entries` +FROM `default_catalog`.`default_database`.`ExternalOrders` +; +INSERT INTO `default_catalog`.`default_database`.`InvalidDistinct_12` +SELECT * +FROM `default_catalog`.`default_database`.`InvalidDistinct` +; +INSERT INTO `default_catalog`.`default_database`.`Orders_13` +SELECT * +FROM `default_catalog`.`default_database`.`Orders` +; +INSERT INTO `default_catalog`.`default_database`.`SelectCustomers_14` +SELECT * +FROM `default_catalog`.`default_database`.`SelectCustomers` +; +INSERT INTO `default_catalog`.`default_database`.`MyPrintSink_ex1` +SELECT * +FROM `default_catalog`.`default_database`.`TemporalJoin` +; +INSERT INTO `default_catalog`.`default_database`.`TemporalJoin_15` +SELECT `id`, `customerid`, `time`, `to_jsonb`(`entries`) AS `entries`, `customerid0`, CAST(`timestamp` AS TIMESTAMP(3) WITH LOCAL TIME ZONE) AS `timestamp`, `name` +FROM `default_catalog`.`default_database`.`TemporalJoin` +; +INSERT INTO `default_catalog`.`default_database`.`UnnestOrders_16` +SELECT `id`, `customerid`, `time`, `productid`, `quantity`, `discount`, `newId`, `hash_columns`(`id`, `customerid`, `time`, `productid`, `quantity`, `discount`, `newId`) AS `__pk_hash` +FROM `default_catalog`.`default_database`.`UnnestOrders` +; +END +>>>kafka.json +{ + "topics" : [ + { + "topicName" : "Customer", + "tableName" : "Customer_3", + "format" : "flexible-json", + "numPartitions" : 1, + "replicationFactor" : 3, + "type" : "SUBSCRIPTION", + "messageKeys" : [ ], + "messageSchema" : "", + "config" : { } + }, + { + "topicName" : "CustomerSubscription", + "tableName" : "CustomerSubscription_7", + "format" : "flexible-json", + "numPartitions" : 1, + "replicationFactor" : 3, + "type" : "SUBSCRIPTION", + "messageKeys" : [ ], + "messageSchema" : "", + "config" : { } + }, + { + "topicName" : "Orders", + "tableName" : "Orders", + "format" : "flexible-json", + "numPartitions" : 1, + "replicationFactor" : 3, + "type" : "MUTATION", + "messageKeys" : [ + "orderid" + ], + "messageSchema" : "", + "config" : { + "cleanup.policy" : "compact", + "retention.ms" : "129600000" + } + } + ], + "testRunnerTopics" : [ ] +} +>>>postgres-schema.sql +CREATE TABLE IF NOT EXISTS "AnotherCustomer" ("customerid" BIGINT NOT NULL, "email" TEXT NOT NULL, "lastUpdated" BIGINT NOT NULL, PRIMARY KEY ("customerid","lastUpdated")); +CREATE TABLE IF NOT EXISTS "Customer" ("customerid" BIGINT NOT NULL, "email" TEXT NOT NULL, "name" TEXT NOT NULL, "lastUpdated" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("customerid","lastUpdated")); +CREATE TABLE IF NOT EXISTS "CustomerByMultipleTime" ("customerid" BIGINT NOT NULL, "email" TEXT NOT NULL, "name" TEXT NOT NULL, "lastUpdated" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("customerid","email")); +CREATE TABLE IF NOT EXISTS "CustomerByTime2" ("customerid" BIGINT NOT NULL, "email" TEXT NOT NULL, "name" TEXT NOT NULL, "lastUpdated" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("customerid")); +CREATE TABLE IF NOT EXISTS "CustomerFilteredDistinct" ("customerid" BIGINT NOT NULL, "email" TEXT NOT NULL, "name" TEXT NOT NULL, "lastUpdated" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("customerid")); +CREATE TABLE IF NOT EXISTS "CustomerTimeWindow" ("window_start" TIMESTAMP WITHOUT TIME ZONE NOT NULL, "window_end" TIMESTAMP WITHOUT TIME ZONE NOT NULL, "unique_email_count" BIGINT NOT NULL, PRIMARY KEY ("window_start","window_end")); +CREATE TABLE IF NOT EXISTS "ExplicitDistinct" ("customerid" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "name" TEXT NOT NULL, PRIMARY KEY ("customerid")); +CREATE TABLE IF NOT EXISTS "ExternalOrders" ("id" BIGINT NOT NULL, "customerid" BIGINT NOT NULL, "time" TIMESTAMP WITH TIME ZONE NOT NULL, "entries" JSONB, PRIMARY KEY ("id","time")); +CREATE TABLE IF NOT EXISTS "InvalidDistinct" ("customerid" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "namee" TEXT NOT NULL, PRIMARY KEY ("customerid")); +CREATE TABLE IF NOT EXISTS "Orders" ("orderid" INTEGER NOT NULL, "amount" FLOAT, PRIMARY KEY ("orderid")); +CREATE TABLE IF NOT EXISTS "SelectCustomers" ("customerid" BIGINT NOT NULL, "email" TEXT NOT NULL, "name" TEXT NOT NULL, "lastUpdated" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("customerid","name")); +CREATE TABLE IF NOT EXISTS "TemporalJoin" ("id" BIGINT NOT NULL, "customerid" BIGINT NOT NULL, "time" TIMESTAMP WITH TIME ZONE NOT NULL, "entries" JSONB, "customerid0" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "name" TEXT NOT NULL, PRIMARY KEY ("id","time")); +CREATE TABLE IF NOT EXISTS "UnnestOrders" ("id" BIGINT NOT NULL, "customerid" BIGINT NOT NULL, "time" TIMESTAMP WITH TIME ZONE NOT NULL, "productid" BIGINT NOT NULL, "quantity" BIGINT NOT NULL, "discount" DOUBLE PRECISION, "newId" BIGINT NOT NULL, "__pk_hash" TEXT, PRIMARY KEY ("__pk_hash")); + +CREATE INDEX IF NOT EXISTS "SelectCustomers_hash_c2" ON "SelectCustomers" USING hash ("name"); +CREATE INDEX IF NOT EXISTS "CustomerByTime2_btree_c4" ON "CustomerByTime2" USING btree ("timestamp") +>>>postgres-views.sql +CREATE OR REPLACE VIEW "MissedTemporalJoin"("id", "customerid", "time", "entries", "customerid0", "timestamp", "name") AS SELECT * +FROM "ExternalOrders" + INNER JOIN "ExplicitDistinct" ON "ExternalOrders"."customerid" = "ExplicitDistinct"."customerid" +>>>vertx.json +{ + "models" : { + "v1" : { + "queries" : [ + { + "type" : "args", + "parentType" : "Query", + "fieldName" : "CustomerByTime2", + "exec" : { + "arguments" : [ + { + "type" : "variable", + "path" : "limit" + }, + { + "type" : "variable", + "path" : "offset" + } + ], + "query" : { + "type" : "SqlQuery", + "sql" : "SELECT *\nFROM \"CustomerByTime2\"", + "parameters" : [ ], + "pagination" : "OFFSET_PAGE_INFO", + "cacheDurationMs" : 0, + "database" : "POSTGRES", + "eventTimesSql" : "SELECT MIN(\"timestamp\") AS \"first_event_time\", MAX(\"timestamp\") AS \"last_event_time\" FROM (SELECT *\nFROM \"CustomerByTime2\") x" + } + } + } + ], + "mutations" : [ ], + "subscriptions" : [ ], + "operations" : [ + { + "function" : { + "name" : "GetCustomerByTime2", + "parameters" : { + "type" : "object", + "properties" : { + "offset" : { + "type" : "integer" + }, + "limit" : { + "type" : "integer" + } + }, + "required" : [ ] + } + }, + "format" : "JSON", + "apiQuery" : { + "query" : "query CustomerByTime2($limit: Int = 10, $offset: Int = 0) {\nCustomerByTime2(limit: $limit, offset: $offset) {\nresults {\ncustomerid\nemail\nname\nlastUpdated\ntimestamp\n}\npagination {\npageSize\ncurrentPage\nhasNextPage\nhasPreviousPage\nnextOffset\nprevOffset\nfirstEventTime\nlastEventTime\n}\n}\n\n}", + "queryName" : "CustomerByTime2", + "operationType" : "QUERY" + }, + "mcpMethod" : "TOOL", + "restMethod" : "GET", + "uriTemplate" : "queries/CustomerByTime2{?offset,limit}" + } + ], + "schema" : { + "type" : "string", + "schema" : "\"An RFC-3339 compliant DateTime Scalar\"\nscalar DateTime\n\"A 64-bit signed integer\"\nscalar Long\n\ntype Customer {\n customerid: Long!\n email: String!\n name: String!\n lastUpdated: Long!\n timestamp: DateTime!\n}\n\ntype CustomerPage {\n results: [Customer!]\n pagination: OffsetPageInfo\n}\n\ntype OffsetPageInfo {\n pageSize: Int!\n currentPage: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n nextOffset: Int\n prevOffset: Int\n firstEventTime: DateTime\n lastEventTime: DateTime\n}\n\ntype Query {\n CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage!\n}\n" + } + } + } +} diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-paged-userdefined.txt b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-paged-userdefined.txt new file mode 100644 index 0000000000..e35040d472 --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-paged-userdefined.txt @@ -0,0 +1,1868 @@ +>>>comprehensiveTest-fail-duplicate-tablefunction.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Subscription { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-duplicate-tablefunction2.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-empty-mutation.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Mutation { +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-empty-query.graphqls +type Query { +} + +>>>comprehensiveTest-fail-empty-result-type.graphqls +type Customer { +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-empty-subscription.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Subscription { +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-invalid-field-name.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name~: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-invalid-type-name.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer~ { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer~!] +} + +>>>comprehensiveTest-fail-no-query-root-type.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Mutation { + Orders(event: OrdersInput!): OrdersResultOutput! +} + +input OrdersInput { + orderid: Int! + amount: Float +} + +type OrdersResultOutput { + orderid: Int! + amount: Float +} + +type Subscription { + CustomerSubscription: Customer +} + +>>>comprehensiveTest-fail-no-type.graphqls +type Query { + AnotherCustomer(limit: Int = 10, offset: Int = 0): [AnotherCustomer!] +} + +>>>comprehensiveTest-fail-not-equal-mutations-field-types.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Mutation { + Orders(event: OrdersInput!): OrdersResultOutput! +} + +input OrdersInput { + orderid: Int! + amount: Float +} + +type OrdersResultOutput { + orderid: Int! + amount: Int +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-not-equal-mutations-inout-types.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Mutation { + Orders(event: OrdersInput!): OrdersResultOutput! +} + +input OrdersInput { + orderid: Int! + amount: Float +} + +type OrdersResultOutput { + orderid: Int! + amount: Float + different_output: Float +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-nullableArgForNotNull.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int, arg2: Int, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-paged-no-limit-offset.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2: CustomerPage! +} + +>>>comprehensiveTest-fail-paged-subscription.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +type Subscription { + CustomerSubscription: CustomerPage +} + +>>>comprehensiveTest-fail-paged-undeclared.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-fail-paged-unknown-field.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + doesNotExist: String! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-fail-paged-wrong-pagination-type.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type OffsetPageInfo { + totalRecords: Long! + pageSize: Int! +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-fail-parameters-names.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(wrongName: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-parameters-types.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Float!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-query-with-no-table-function.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct1(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-subscription-with-no-table-function.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Subscription { + TableFunctionCallsTblFct1(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-unknown-field.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] + unknown: Int +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-fail-wrong-query.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [[Customer!]] +} + +>>>comprehensiveTest-limit-offset-combinations.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] + CustomerByTime2: [Customer!] + CustomerByMultipleTime(limit: Int = 10): [Customer!] + SelectCustomers(offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest-paged-results.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-paged-userdefined.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + related(limit: Int = 10, offset: Int = 0): CustomerRelatedPage +} + +type CustomerRelatedPage { + items: [Customer!] + meta: OffsetPageInfo +} + +type CustomerPage { + items: [Customer!] + meta: OffsetPageInfo +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-parameters-order.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Query { + TableFunctionCallsTblFct(arg2: Int!, arg1: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +>>>comprehensiveTest.graphqls +type AnotherCustomer { + customerid: Long! + email: String! + lastUpdated: Long! +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime + related(limit: Int = 10, offset: Int = 0): [Customer!] + relatedByLength(length: Int, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type CustomerByMultipleTime { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime +} + +type CustomerByTime2 { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime +} + +type CustomerFilteredDistinct { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime +} + +type CustomerTimeWindow { + window_start: DateTime! + window_end: DateTime! + unique_email_count: Long! +} + +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime + +type ExplicitDistinct { + customerid: Long! + timestamp: DateTime + name: String! +} + +type ExternalOrders { + id: Long! + customerid: Long! + time: DateTime! + entries: [ExternalOrders_entriesOutput]! +} + +type ExternalOrders_entriesOutput { + productid: Long! + quantity: Long! + unit_price: Float! + discount: Float +} + +"A 64-bit signed integer" +scalar Long + +type InvalidDistinct { + customerid: Long! + timestamp: DateTime + namee: String! +} + +type MissedTemporalJoin { + id: Long! + customerid: Long! + time: DateTime! + entries: [MissedTemporalJoin_entriesOutput]! + customerid0: Long! + timestamp: DateTime + name: String! +} + +type MissedTemporalJoin_entriesOutput { + productid: Long! + quantity: Long! + unit_price: Float! + discount: Float +} + +type Mutation { + Orders(event: OrdersInput!): OrdersResultOutput! +} + +type Orders { + orderid: Int! + amount: Float +} + +input OrdersInput { + orderid: Int! + amount: Float +} + +type OrdersResultOutput { + orderid: Int! + amount: Float +} + +type Query { + AnotherCustomer(limit: Int = 10, offset: Int = 0): [AnotherCustomer!] + Customer(limit: Int = 10, offset: Int = 0): [Customer!] + CustomerByMultipleTime(limit: Int = 10, offset: Int = 0): [CustomerByMultipleTime!] + CustomerByTime2(limit: Int = 10, offset: Int = 0): [CustomerByTime2!] + CustomerFilteredDistinct(limit: Int = 10, offset: Int = 0): [CustomerFilteredDistinct!] + CustomerTimeWindow(limit: Int = 10, offset: Int = 0): [CustomerTimeWindow!] + ExplicitDistinct(limit: Int = 10, offset: Int = 0): [ExplicitDistinct!] + ExternalOrders(limit: Int = 10, offset: Int = 0): [ExternalOrders!] + InvalidDistinct(limit: Int = 10, offset: Int = 0): [InvalidDistinct!] + MissedTemporalJoin(limit: Int = 10, offset: Int = 0): [MissedTemporalJoin!] + Orders(limit: Int = 10, offset: Int = 0): [Orders!] + """ + This is for selected customers + and their orders + """ + SelectCustomers(limit: Int = 10, offset: Int = 0): [Customer!] + TemporalJoin(limit: Int = 10, offset: Int = 0): [TemporalJoin!] + UnnestOrders(limit: Int = 10, offset: Int = 0): [UnnestOrders!] + CustomerById(minId: Int!, limit: Int = 10, offset: Int = 0): [Customer!] + CustomerQuery(id: Long!, limit: Int = 10, offset: Int = 0): [AnotherCustomer!] + TableFunctionCallsTblFct(arg1: Int, arg2: Int!, limit: Int = 10, offset: Int = 0): [Customer!] +} + +type Subscription { + CustomerSubscription: Customer + CustomerSubscriptionById(minId: Int!): Customer +} + +type TemporalJoin { + id: Long! + customerid: Long! + time: DateTime! + entries: [TemporalJoin_entriesOutput]! + customerid0: Long! + timestamp: DateTime + name: String! +} + +type TemporalJoin_entriesOutput { + productid: Long! + quantity: Long! + unit_price: Float! + discount: Float +} + +type UnnestOrders { + id: Long! + customerid: Long! + time: DateTime! + productid: Long! + quantity: Long! + discount: Float + newId: Long! +} + +>>>pipeline_explain.txt +=== AnotherCustomer +ID: default_catalog.default_database.AnotherCustomer +Type: stream +Stage: flink +Primary key: customerid, lastUpdated +Timestamp: - +Row count: ~5e7 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL +Inputs: + - default_catalog.default_database._Customer +Annotations: + - stream-root: _Customer + +=== Customer +ID: default_catalog.default_database.Customer +Type: stream +Stage: flink +Primary key: customerid, lastUpdated +Timestamp: timestamp +Row count: ~1e8 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database.Customer__base +Annotations: + - stream-root: Customer + +=== CustomerById +ID: default_catalog.default_database.CustomerById +Type: query +Stage: postgres +--- +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + - parameters: minId + - base-table: Customer + +=== CustomerByMultipleTime +ID: default_catalog.default_database.CustomerByMultipleTime +Type: state +Stage: flink +Primary key: customerid, email +Timestamp: timestamp +Row count: ~2e7 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - mostRecentDistinct: true + - stream-root: Customer + +=== CustomerByTime2 +ID: default_catalog.default_database.CustomerByTime2 +Type: state +Stage: flink +Primary key: customerid +Timestamp: timestamp +Row count: ~2e7 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - mostRecentDistinct: true + - stream-root: Customer + +=== CustomerFilteredDistinct +ID: default_catalog.default_database.CustomerFilteredDistinct +Type: state +Stage: flink +Primary key: customerid +Timestamp: timestamp +Row count: ~2e6 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + +=== CustomerQuery +ID: default_catalog.default_database.CustomerQuery +Type: query +Stage: postgres +--- +Inputs: + - default_catalog.default_database.AnotherCustomer +Annotations: + - stream-root: _Customer + - parameters: id + - base-table: AnotherCustomer + +=== CustomerSubscription +ID: default_catalog.default_database.CustomerSubscription +Type: stream +Stage: flink +Primary key: customerid, lastUpdated +Timestamp: timestamp +Row count: ~1e8 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + +=== CustomerSubscriptionById +ID: default_catalog.default_database.CustomerSubscriptionById +Type: query +Stage: kafka +--- +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + - parameters: minId + - base-table: Customer + +=== CustomerTimeWindow +ID: default_catalog.default_database.CustomerTimeWindow +Type: stream +Stage: flink +Primary key: window_start, window_end +Timestamp: - +Row count: ~3e7 +--- +Schema: + - window_start: TIMESTAMP(3) NOT NULL + - window_end: TIMESTAMP(3) NOT NULL + - unique_email_count: BIGINT NOT NULL +Inputs: + - default_catalog.default_database.SelectCustomers +Annotations: + - features: STREAM_WINDOW_AGGREGATION (feature) + +=== ExplicitDistinct +ID: default_catalog.default_database.ExplicitDistinct +Type: state +Stage: flink +Primary key: customerid +Timestamp: timestamp +Row count: ~2e7 +--- +Schema: + - customerid: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + +=== ExternalOrders +ID: default_catalog.default_database.ExternalOrders +Type: stream +Stage: flink +Primary key: id, time +Timestamp: time +Row count: ~1e8 +--- +Schema: + - id: BIGINT NOT NULL + - customerid: BIGINT NOT NULL + - time: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - entries: RecordType:peek_no_expand(BIGINT NOT NULL productid, BIGINT NOT NULL quantity, DOUBLE NOT NULL unit_price, DOUBLE discount) NOT NULL ARRAY NOT NULL +Inputs: + - default_catalog.default_database.ExternalOrders__base +Annotations: + - features: DENORMALIZE (feature) + - stream-root: ExternalOrders + +=== InvalidDistinct +ID: default_catalog.default_database.InvalidDistinct +Type: state +Stage: flink +Primary key: customerid +Timestamp: timestamp +Row count: ~2e7 +--- +Schema: + - customerid: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - namee: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + +=== MissedTemporalJoin +ID: default_catalog.default_database.MissedTemporalJoin +Type: state +Stage: postgres +Primary key: id, time +Timestamp: time +Row count: ~1e8 +--- +Schema: + - id: BIGINT NOT NULL + - customerid: BIGINT NOT NULL + - time: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - entries: RecordType:peek_no_expand(BIGINT NOT NULL productid, BIGINT NOT NULL quantity, DOUBLE NOT NULL unit_price, DOUBLE discount) NOT NULL ARRAY NOT NULL + - customerid0: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL +Inputs: + - default_catalog.default_database.ExplicitDistinct + - default_catalog.default_database.ExternalOrders + +=== Orders +ID: default_catalog.default_database.Orders +Type: state +Stage: flink +Primary key: orderid +Timestamp: - +Row count: ~1e8 +--- +Schema: + - orderid: INTEGER NOT NULL + - amount: FLOAT +Inputs: + - default_catalog.default_database.Orders__base + +=== SelectCustomers +ID: default_catalog.default_database.SelectCustomers +Type: stream +Stage: flink +Primary key: customerid, name +Timestamp: timestamp +Row count: ~5e7 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database.Customer +Annotations: + - stream-root: Customer + - sort: [4 DESC-nulls-last] + +=== TableFunctionCallsTblFct +ID: default_catalog.default_database.TableFunctionCallsTblFct +Type: query +Stage: postgres +--- +Inputs: + - default_catalog.default_database.CustomerById +Annotations: + - features: TABLE_FUNCTION_SCAN (feature) + - stream-root: Customer + - parameters: arg1, arg2 + - base-table: Customer + +=== TemporalJoin +ID: default_catalog.default_database.TemporalJoin +Type: stream +Stage: flink +Primary key: id, time +Timestamp: time +Row count: ~9e7 +--- +Schema: + - id: BIGINT NOT NULL + - customerid: BIGINT NOT NULL + - time: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - entries: RecordType:peek_no_expand(BIGINT NOT NULL productid, BIGINT NOT NULL quantity, DOUBLE NOT NULL unit_price, DOUBLE discount) NOT NULL ARRAY NOT NULL + - customerid0: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL +Inputs: + - default_catalog.default_database.ExplicitDistinct + - default_catalog.default_database.ExternalOrders +Annotations: + - stream-root: ExternalOrders + +=== UnnestOrders +ID: default_catalog.default_database.UnnestOrders +Type: stream +Stage: flink +Primary key: - +Timestamp: time +Row count: ~1e8 +--- +Schema: + - id: BIGINT NOT NULL + - customerid: BIGINT NOT NULL + - time: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - productid: BIGINT NOT NULL + - quantity: BIGINT NOT NULL + - discount: DOUBLE + - newId: BIGINT NOT NULL +Inputs: + - default_catalog.default_database.ExternalOrders +Annotations: + - stream-root: ExternalOrders + +=== _Customer +ID: default_catalog.default_database._Customer +Type: stream +Stage: flink +Primary key: customerid, lastUpdated +Timestamp: timestamp +Row count: ~1e8 +--- +Schema: + - customerid: BIGINT NOT NULL + - email: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - name: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - lastUpdated: BIGINT NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database._Customer__base +Annotations: + - stream-root: _Customer + +=== customersink +ID: mysink.customersink +Type: export +Stage: flink +Connector: print +--- +Inputs: + - default_catalog.default_database.TemporalJoin + +=== TimeWindow +ID: print.TimeWindow +Type: export +Stage: flink +--- +Inputs: + - default_catalog.default_database.CustomerTimeWindow + +>>>flink-sql-no-functions.sql +CREATE TABLE `Customer` ( + `customerid` BIGINT NOT NULL, + `email` STRING NOT NULL, + `name` STRING NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` AS COALESCE(`TO_TIMESTAMP_LTZ`(`lastUpdated`, 0), TIMESTAMP '1970-01-01 00:00:00.000'), + PRIMARY KEY (`customerid`, `lastUpdated`) NOT ENFORCED, + WATERMARK FOR `timestamp` AS `timestamp` - INTERVAL '0.001' SECOND +) +WITH ( + 'connector' = 'filesystem', + 'format' = 'flexible-json', + 'path' = 'file:/mock', + 'source.monitor-interval' = '10 sec' +); +CREATE TABLE `ExternalOrders` ( + `id` BIGINT NOT NULL, + `customerid` BIGINT NOT NULL, + `time` TIMESTAMP_LTZ(3) NOT NULL, + `entries` ROW(`productid` BIGINT NOT NULL, `quantity` BIGINT NOT NULL, `unit_price` DOUBLE NOT NULL, `discount` DOUBLE) NOT NULL ARRAY NOT NULL, + PRIMARY KEY (`id`, `time`) NOT ENFORCED, + WATERMARK FOR `time` AS `time` - INTERVAL '0.001' SECOND +) +WITH ( + 'connector' = 'filesystem', + 'format' = 'flexible-json', + 'path' = 'file:/mock', + 'source.monitor-interval' = '10 sec' +); +CREATE TABLE `_Customer` ( + `customerid` BIGINT NOT NULL, + `email` STRING NOT NULL, + `name` STRING NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` AS COALESCE(`TO_TIMESTAMP_LTZ`(`lastUpdated`, 0), TIMESTAMP '1970-01-01 00:00:00.000'), + PRIMARY KEY (`customerid`, `lastUpdated`) NOT ENFORCED, + WATERMARK FOR `timestamp` AS `timestamp` - INTERVAL '0.001' SECOND +) +WITH ( + 'connector' = 'filesystem', + 'format' = 'flexible-json', + 'path' = 'file:/mock', + 'source.monitor-interval' = '10 sec' +); +CREATE TABLE `_Orders` ( + `id` BIGINT NOT NULL, + `customerid` BIGINT NOT NULL, + `time` TIMESTAMP_LTZ(3) NOT NULL, + `entries` ROW(`productid` BIGINT NOT NULL, `quantity` BIGINT NOT NULL, `unit_price` DOUBLE NOT NULL, `discount` DOUBLE) NOT NULL ARRAY NOT NULL, + PRIMARY KEY (`id`, `time`) NOT ENFORCED, + WATERMARK FOR `time` AS `time` - INTERVAL '0.001' SECOND +) +WITH ( + 'connector' = 'filesystem', + 'format' = 'flexible-json', + 'path' = 'file:/mock', + 'source.monitor-interval' = '10 sec' +); +CREATE TABLE `_Product` ( + `productid` BIGINT NOT NULL, + `name` STRING NOT NULL, + `description` STRING NOT NULL, + `category` STRING NOT NULL, + `_ingest_time` TIMESTAMP_LTZ(3) NOT NULL, + PRIMARY KEY (`productid`, `name`, `description`, `category`) NOT ENFORCED, + WATERMARK FOR `_ingest_time` AS `_ingest_time` - INTERVAL '0.001' SECOND +) +WITH ( + 'connector' = 'filesystem', + 'format' = 'flexible-json', + 'path' = 'file:/mock', + 'source.monitor-interval' = '10 sec' +); +CREATE VIEW `CustomerByTime2` +AS +SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp` +FROM (SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp`, ROW_NUMBER() OVER (PARTITION BY `customerid` ORDER BY `timestamp` DESC NULLS LAST) AS `__sqrlinternal_rownum` + FROM `default_catalog`.`default_database`.`Customer`) AS `t` +WHERE `__sqrlinternal_rownum` = 1; +CREATE VIEW `CustomerFilteredDistinct` +AS +SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp` +FROM (SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp`, ROW_NUMBER() OVER (PARTITION BY `customerid` ORDER BY `lastUpdated` DESC NULLS LAST) AS `$f5` + FROM (SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp` + FROM (SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp`, LAG(`email`, 1) OVER (PARTITION BY `customerid` ORDER BY `timestamp`) AS `$f5`, LAG(`name`, 1) OVER (PARTITION BY `customerid` ORDER BY `timestamp`) AS `$f6` + FROM (SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp`, MAX(`lastUpdated`) OVER (PARTITION BY `customerid` ORDER BY `timestamp` RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS `$f5` + FROM `default_catalog`.`default_database`.`Customer`) AS `t` + WHERE `lastUpdated` >= `$f5`) AS `t1` + WHERE `$f5` IS NULL AND `$f6` IS NULL OR `email` <> `$f5` OR `name` <> `$f6`) AS `t3`) AS `t4` +WHERE `$f5` = 1; +CREATE VIEW `AnotherCustomer` +AS +SELECT `customerid`, `email`, `lastUpdated` +FROM `_Customer` +WHERE `customerid` > 100; +CREATE VIEW `CustomerByMultipleTime` +AS +SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp` +FROM (SELECT `customerid`, `email`, `name`, `lastUpdated`, `timestamp`, ROW_NUMBER() OVER (PARTITION BY `customerid`, `email` ORDER BY `timestamp` DESC NULLS LAST, `lastUpdated` NULLS FIRST) AS `__sqrlinternal_rownum` + FROM `default_catalog`.`default_database`.`Customer`) AS `t` +WHERE `__sqrlinternal_rownum` = 1; +CREATE VIEW `ExplicitDistinct` +AS +SELECT `customerid`, `timestamp`, `name` +FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY `customerid` ORDER BY `timestamp` DESC) AS `_rownum` + FROM `Customer`) +WHERE `_rownum` = 1; +CREATE VIEW `InvalidDistinct` +AS +SELECT `customerid`, `timestamp`, `name` AS `namee` +FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY `customerid` ORDER BY `timestamp` DESC) AS `_rownum` + FROM `Customer`) +WHERE `_rownum` = 1; +CREATE VIEW `MissedTemporalJoin` +AS +SELECT * +FROM `ExternalOrders` AS `o` + INNER JOIN `ExplicitDistinct` AS `c` ON `o`.`customerid` = `c`.`customerid`; +CREATE VIEW `TemporalJoin` +AS +SELECT * +FROM `ExternalOrders` AS `o` + INNER JOIN `ExplicitDistinct` FOR SYSTEM_TIME AS OF `time` AS `c` ON `o`.`customerid` = `c`.`customerid`; +CREATE VIEW `SelectCustomers` +AS +SELECT * +FROM `Customer` +WHERE `customerid` > 0; +CREATE VIEW `CustomerSubscription` +AS +SELECT * +FROM `Customer`; +CREATE VIEW `UnnestOrders` +AS +SELECT `o`.`id`, `o`.`customerid`, `o`.`time`, `e`.`productid`, `e`.`quantity`, `e`.`discount` +FROM `ExternalOrders` AS `o` + CROSS JOIN UNNEST(`entries`) AS `e`; +ALTER VIEW `UnnestOrders` +AS +SELECT `id`, `customerid`, `time`, `productid`, `quantity`, `discount`, `id` + `productid` AS `newId` +FROM (SELECT `o`.`id`, `o`.`customerid`, `o`.`time`, `e`.`productid`, `e`.`quantity`, `e`.`discount` + FROM `ExternalOrders` AS `o` + CROSS JOIN UNNEST(`entries`) AS `e`); +CREATE TABLE `Orders` ( + `orderid` INTEGER, + `amount` FLOAT, + PRIMARY KEY (`orderid`) NOT ENFORCED +) +WITH ( + 'connector' = 'upsert-kafka', + 'key.format' = 'flexible-json', + 'properties.auto.offset.reset' = 'earliest', + 'properties.bootstrap.servers' = '${KAFKA_BOOTSTRAP_SERVERS}', + 'properties.compression.type' = 'zstd', + 'properties.group.id' = '${KAFKA_GROUP_ID}', + 'topic' = 'Orders', + 'value.fields-include' = 'ALL', + 'value.format' = 'flexible-json' +); +CREATE VIEW `CustomerTimeWindow` +AS +SELECT `window_start`, `window_end`, COUNT(DISTINCT `email`) AS `unique_email_count` +FROM TABLE(TUMBLE(TABLE `SelectCustomers`, DESCRIPTOR(`timestamp`), INTERVAL '1' MINUTE)) +GROUP BY `window_start`, `window_end`; +CREATE TEMPORARY TABLE `MyPrintSink_ex1__schema` ( + `id` BIGINT NOT NULL, + `customerid` BIGINT NOT NULL, + `time` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `entries` ROW(`productid` BIGINT NOT NULL, `quantity` BIGINT NOT NULL, `unit_price` DOUBLE NOT NULL, `discount` DOUBLE) NOT NULL ARRAY NOT NULL, + `customerid0` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL +) +WITH ( + 'connector' = 'datagen' +); +CREATE TABLE `MyPrintSink_ex1` ( + PRIMARY KEY (`customerid`) NOT ENFORCED +) +WITH ( + 'connector' = 'print' +) +LIKE `MyPrintSink_ex1__schema`; +CREATE TABLE `AnotherCustomer_1` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + PRIMARY KEY (`customerid`, `lastUpdated`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'AnotherCustomer', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `Customer_2` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + PRIMARY KEY (`customerid`, `lastUpdated`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'Customer', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `Customer_3` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL +) +WITH ( + 'connector' = 'kafka', + 'format' = 'flexible-json', + 'properties.bootstrap.servers' = '${KAFKA_BOOTSTRAP_SERVERS}', + 'properties.compression.type' = 'zstd', + 'properties.group.id' = '${KAFKA_GROUP_ID}', + 'topic' = 'Customer' +); +CREATE TABLE `CustomerByMultipleTime_4` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + PRIMARY KEY (`customerid`, `email`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'TIMESTAMP', + 'sink.on-conflict.timestamp-column' = 'timestamp', + 'table-name' = 'CustomerByMultipleTime', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `CustomerByTime2_5` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + PRIMARY KEY (`customerid`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'TIMESTAMP', + 'sink.on-conflict.timestamp-column' = 'timestamp', + 'table-name' = 'CustomerByTime2', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `CustomerFilteredDistinct_6` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + PRIMARY KEY (`customerid`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'TIMESTAMP', + 'sink.on-conflict.timestamp-column' = 'timestamp', + 'table-name' = 'CustomerFilteredDistinct', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `CustomerSubscription_7` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL +) +WITH ( + 'connector' = 'kafka', + 'format' = 'flexible-json', + 'properties.bootstrap.servers' = '${KAFKA_BOOTSTRAP_SERVERS}', + 'properties.compression.type' = 'zstd', + 'properties.group.id' = '${KAFKA_GROUP_ID}', + 'topic' = 'CustomerSubscription' +); +CREATE TABLE `TimeWindow_8` ( + `window_start` TIMESTAMP(3) NOT NULL, + `window_end` TIMESTAMP(3) NOT NULL, + `unique_email_count` BIGINT NOT NULL, + PRIMARY KEY (`window_start`, `window_end`) NOT ENFORCED +) +WITH ( + 'connector' = 'print', + 'print-identifier' = 'TimeWindow' +); +CREATE TABLE `CustomerTimeWindow_9` ( + `window_start` TIMESTAMP(3) NOT NULL, + `window_end` TIMESTAMP(3) NOT NULL, + `unique_email_count` BIGINT NOT NULL, + PRIMARY KEY (`window_start`, `window_end`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'CustomerTimeWindow', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `ExplicitDistinct_10` ( + `customerid` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + PRIMARY KEY (`customerid`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'TIMESTAMP', + 'sink.on-conflict.timestamp-column' = 'timestamp', + 'table-name' = 'ExplicitDistinct', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `ExternalOrders_11` ( + `id` BIGINT NOT NULL, + `customerid` BIGINT NOT NULL, + `time` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `entries` RAW('com.datasqrl.flinkrunner.stdlib.json.FlinkJsonType', 'AERjb20uZGF0YXNxcmwuZmxpbmtydW5uZXIuc3RkbGliLmpzb24uRmxpbmtKc29uVHlwZVNlcmlhbGl6ZXJTbmFwc2hvdAAAAAM='), + PRIMARY KEY (`id`, `time`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'ExternalOrders', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `InvalidDistinct_12` ( + `customerid` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `namee` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + PRIMARY KEY (`customerid`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'TIMESTAMP', + 'sink.on-conflict.timestamp-column' = 'timestamp', + 'table-name' = 'InvalidDistinct', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `Orders_13` ( + `orderid` INTEGER NOT NULL, + `amount` FLOAT, + PRIMARY KEY (`orderid`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'table-name' = 'Orders', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `SelectCustomers_14` ( + `customerid` BIGINT NOT NULL, + `email` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `lastUpdated` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + PRIMARY KEY (`customerid`, `name`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'SelectCustomers', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `TemporalJoin_15` ( + `id` BIGINT NOT NULL, + `customerid` BIGINT NOT NULL, + `time` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `entries` RAW('com.datasqrl.flinkrunner.stdlib.json.FlinkJsonType', 'AERjb20uZGF0YXNxcmwuZmxpbmtydW5uZXIuc3RkbGliLmpzb24uRmxpbmtKc29uVHlwZVNlcmlhbGl6ZXJTbmFwc2hvdAAAAAM='), + `customerid0` BIGINT NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `name` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + PRIMARY KEY (`id`, `time`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'TemporalJoin', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `UnnestOrders_16` ( + `id` BIGINT NOT NULL, + `customerid` BIGINT NOT NULL, + `time` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `productid` BIGINT NOT NULL, + `quantity` BIGINT NOT NULL, + `discount` DOUBLE, + `newId` BIGINT NOT NULL, + `__pk_hash` CHAR(32) CHARACTER SET `UTF-16LE`, + PRIMARY KEY (`__pk_hash`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'UnnestOrders', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +EXECUTE STATEMENT SET BEGIN +INSERT INTO `default_catalog`.`default_database`.`AnotherCustomer_1` +SELECT * +FROM `default_catalog`.`default_database`.`AnotherCustomer` +; +INSERT INTO `default_catalog`.`default_database`.`Customer_2` +SELECT * +FROM `default_catalog`.`default_database`.`Customer` +; +INSERT INTO `default_catalog`.`default_database`.`Customer_3` +SELECT * +FROM `default_catalog`.`default_database`.`Customer` +; +INSERT INTO `default_catalog`.`default_database`.`CustomerByMultipleTime_4` +SELECT * +FROM `default_catalog`.`default_database`.`Customer` +; +INSERT INTO `default_catalog`.`default_database`.`CustomerByTime2_5` +SELECT * +FROM `default_catalog`.`default_database`.`Customer` +; +INSERT INTO `default_catalog`.`default_database`.`CustomerFilteredDistinct_6` +SELECT * +FROM `default_catalog`.`default_database`.`CustomerFilteredDistinct` +; +INSERT INTO `default_catalog`.`default_database`.`CustomerSubscription_7` +SELECT * +FROM `default_catalog`.`default_database`.`CustomerSubscription` +; +INSERT INTO `default_catalog`.`default_database`.`TimeWindow_8` +SELECT * +FROM `default_catalog`.`default_database`.`CustomerTimeWindow` +; +INSERT INTO `default_catalog`.`default_database`.`CustomerTimeWindow_9` +SELECT * +FROM `default_catalog`.`default_database`.`CustomerTimeWindow` +; +INSERT INTO `default_catalog`.`default_database`.`ExplicitDistinct_10` +SELECT * +FROM `default_catalog`.`default_database`.`ExplicitDistinct` +; +INSERT INTO `default_catalog`.`default_database`.`ExternalOrders_11` +SELECT `id`, `customerid`, `time`, `to_jsonb`(`entries`) AS `entries` +FROM `default_catalog`.`default_database`.`ExternalOrders` +; +INSERT INTO `default_catalog`.`default_database`.`InvalidDistinct_12` +SELECT * +FROM `default_catalog`.`default_database`.`InvalidDistinct` +; +INSERT INTO `default_catalog`.`default_database`.`Orders_13` +SELECT * +FROM `default_catalog`.`default_database`.`Orders` +; +INSERT INTO `default_catalog`.`default_database`.`SelectCustomers_14` +SELECT * +FROM `default_catalog`.`default_database`.`SelectCustomers` +; +INSERT INTO `default_catalog`.`default_database`.`MyPrintSink_ex1` +SELECT * +FROM `default_catalog`.`default_database`.`TemporalJoin` +; +INSERT INTO `default_catalog`.`default_database`.`TemporalJoin_15` +SELECT `id`, `customerid`, `time`, `to_jsonb`(`entries`) AS `entries`, `customerid0`, CAST(`timestamp` AS TIMESTAMP(3) WITH LOCAL TIME ZONE) AS `timestamp`, `name` +FROM `default_catalog`.`default_database`.`TemporalJoin` +; +INSERT INTO `default_catalog`.`default_database`.`UnnestOrders_16` +SELECT `id`, `customerid`, `time`, `productid`, `quantity`, `discount`, `newId`, `hash_columns`(`id`, `customerid`, `time`, `productid`, `quantity`, `discount`, `newId`) AS `__pk_hash` +FROM `default_catalog`.`default_database`.`UnnestOrders` +; +END +>>>kafka.json +{ + "topics" : [ + { + "topicName" : "Customer", + "tableName" : "Customer_3", + "format" : "flexible-json", + "numPartitions" : 1, + "replicationFactor" : 3, + "type" : "SUBSCRIPTION", + "messageKeys" : [ ], + "messageSchema" : "", + "config" : { } + }, + { + "topicName" : "CustomerSubscription", + "tableName" : "CustomerSubscription_7", + "format" : "flexible-json", + "numPartitions" : 1, + "replicationFactor" : 3, + "type" : "SUBSCRIPTION", + "messageKeys" : [ ], + "messageSchema" : "", + "config" : { } + }, + { + "topicName" : "Orders", + "tableName" : "Orders", + "format" : "flexible-json", + "numPartitions" : 1, + "replicationFactor" : 3, + "type" : "MUTATION", + "messageKeys" : [ + "orderid" + ], + "messageSchema" : "", + "config" : { + "cleanup.policy" : "compact", + "retention.ms" : "129600000" + } + } + ], + "testRunnerTopics" : [ ] +} +>>>postgres-schema.sql +CREATE TABLE IF NOT EXISTS "AnotherCustomer" ("customerid" BIGINT NOT NULL, "email" TEXT NOT NULL, "lastUpdated" BIGINT NOT NULL, PRIMARY KEY ("customerid","lastUpdated")); +CREATE TABLE IF NOT EXISTS "Customer" ("customerid" BIGINT NOT NULL, "email" TEXT NOT NULL, "name" TEXT NOT NULL, "lastUpdated" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("customerid","lastUpdated")); +CREATE TABLE IF NOT EXISTS "CustomerByMultipleTime" ("customerid" BIGINT NOT NULL, "email" TEXT NOT NULL, "name" TEXT NOT NULL, "lastUpdated" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("customerid","email")); +CREATE TABLE IF NOT EXISTS "CustomerByTime2" ("customerid" BIGINT NOT NULL, "email" TEXT NOT NULL, "name" TEXT NOT NULL, "lastUpdated" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("customerid")); +CREATE TABLE IF NOT EXISTS "CustomerFilteredDistinct" ("customerid" BIGINT NOT NULL, "email" TEXT NOT NULL, "name" TEXT NOT NULL, "lastUpdated" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("customerid")); +CREATE TABLE IF NOT EXISTS "CustomerTimeWindow" ("window_start" TIMESTAMP WITHOUT TIME ZONE NOT NULL, "window_end" TIMESTAMP WITHOUT TIME ZONE NOT NULL, "unique_email_count" BIGINT NOT NULL, PRIMARY KEY ("window_start","window_end")); +CREATE TABLE IF NOT EXISTS "ExplicitDistinct" ("customerid" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "name" TEXT NOT NULL, PRIMARY KEY ("customerid")); +CREATE TABLE IF NOT EXISTS "ExternalOrders" ("id" BIGINT NOT NULL, "customerid" BIGINT NOT NULL, "time" TIMESTAMP WITH TIME ZONE NOT NULL, "entries" JSONB, PRIMARY KEY ("id","time")); +CREATE TABLE IF NOT EXISTS "InvalidDistinct" ("customerid" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "namee" TEXT NOT NULL, PRIMARY KEY ("customerid")); +CREATE TABLE IF NOT EXISTS "Orders" ("orderid" INTEGER NOT NULL, "amount" FLOAT, PRIMARY KEY ("orderid")); +CREATE TABLE IF NOT EXISTS "SelectCustomers" ("customerid" BIGINT NOT NULL, "email" TEXT NOT NULL, "name" TEXT NOT NULL, "lastUpdated" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("customerid","name")); +CREATE TABLE IF NOT EXISTS "TemporalJoin" ("id" BIGINT NOT NULL, "customerid" BIGINT NOT NULL, "time" TIMESTAMP WITH TIME ZONE NOT NULL, "entries" JSONB, "customerid0" BIGINT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "name" TEXT NOT NULL, PRIMARY KEY ("id","time")); +CREATE TABLE IF NOT EXISTS "UnnestOrders" ("id" BIGINT NOT NULL, "customerid" BIGINT NOT NULL, "time" TIMESTAMP WITH TIME ZONE NOT NULL, "productid" BIGINT NOT NULL, "quantity" BIGINT NOT NULL, "discount" DOUBLE PRECISION, "newId" BIGINT NOT NULL, "__pk_hash" TEXT, PRIMARY KEY ("__pk_hash")); + +CREATE INDEX IF NOT EXISTS "SelectCustomers_hash_c2" ON "SelectCustomers" USING hash ("name"); +CREATE INDEX IF NOT EXISTS "Customer_btree_c4" ON "Customer" USING btree ("timestamp"); +CREATE INDEX IF NOT EXISTS "SelectCustomers_btree_c4" ON "SelectCustomers" USING btree ("timestamp") +>>>postgres-views.sql +CREATE OR REPLACE VIEW "MissedTemporalJoin"("id", "customerid", "time", "entries", "customerid0", "timestamp", "name") AS SELECT * +FROM "ExternalOrders" + INNER JOIN "ExplicitDistinct" ON "ExternalOrders"."customerid" = "ExplicitDistinct"."customerid" +>>>vertx.json +{ + "models" : { + "v1" : { + "queries" : [ + { + "type" : "args", + "parentType" : "Query", + "fieldName" : "TableFunctionCallsTblFct", + "exec" : { + "arguments" : [ + { + "type" : "variable", + "path" : "limit" + }, + { + "type" : "variable", + "path" : "offset" + }, + { + "type" : "variable", + "path" : "arg2" + }, + { + "type" : "variable", + "path" : "arg1" + } + ], + "query" : { + "type" : "SqlQuery", + "sql" : "SELECT *\nFROM (SELECT *\n FROM \"Customer\"\n WHERE \"customerid\" > $2) AS \"t0\"\nWHERE \"customerid\" > $1", + "parameters" : [ + { + "type" : "arg", + "path" : "arg1", + "sqlType" : "INTEGER" + }, + { + "type" : "arg", + "path" : "arg2", + "sqlType" : "INTEGER" + } + ], + "pagination" : "OFFSET_PAGE_INFO", + "cacheDurationMs" : 0, + "database" : "POSTGRES", + "eventTimesSql" : "SELECT MIN(\"timestamp\") AS \"first_event_time\", MAX(\"timestamp\") AS \"last_event_time\" FROM (SELECT *\nFROM (SELECT *\n FROM \"Customer\"\n WHERE \"customerid\" > $2) AS \"t0\"\nWHERE \"customerid\" > $1) x" + } + } + }, + { + "type" : "args", + "parentType" : "Customer", + "fieldName" : "related", + "exec" : { + "arguments" : [ + { + "type" : "variable", + "path" : "limit" + }, + { + "type" : "variable", + "path" : "offset" + } + ], + "query" : { + "type" : "SqlQuery", + "sql" : "SELECT *\nFROM \"Customer\"\nWHERE $1 = \"customerid\"\nORDER BY \"timestamp\" NULLS FIRST", + "parameters" : [ + { + "type" : "source", + "key" : "customerid" + } + ], + "pagination" : "OFFSET_PAGE_INFO", + "cacheDurationMs" : 0, + "database" : "POSTGRES", + "eventTimesSql" : "SELECT MIN(\"timestamp\") AS \"first_event_time\", MAX(\"timestamp\") AS \"last_event_time\" FROM (SELECT *\nFROM \"Customer\"\nWHERE $1 = \"customerid\"\nORDER BY \"timestamp\" NULLS FIRST) x" + } + } + } + ], + "mutations" : [ ], + "subscriptions" : [ ], + "operations" : [ ], + "schema" : { + "type" : "string", + "schema" : "\"An RFC-3339 compliant DateTime Scalar\"\nscalar DateTime\n\"A 64-bit signed integer\"\nscalar Long\n\ntype OffsetPageInfo {\n pageSize: Int!\n currentPage: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n nextOffset: Int\n prevOffset: Int\n firstEventTime: DateTime\n lastEventTime: DateTime\n}\n\ntype Customer {\n customerid: Long!\n email: String!\n name: String!\n lastUpdated: Long!\n timestamp: DateTime!\n related(limit: Int = 10, offset: Int = 0): CustomerRelatedPage\n}\n\ntype CustomerRelatedPage {\n items: [Customer!]\n meta: OffsetPageInfo\n}\n\ntype CustomerPage {\n items: [Customer!]\n meta: OffsetPageInfo\n}\n\ntype Query {\n TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): CustomerPage!\n}\n" + } + } + } +} diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-parameters-order.txt b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-parameters-order.txt index ca94187fb9..89f8ca2a7b 100644 --- a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-parameters-order.txt +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest-parameters-order.txt @@ -269,6 +269,153 @@ type Query { TableFunctionCallsTblFct(arg1: Int, arg2: Int, limit: Int = 10, offset: Int = 0): [Customer!] } +>>>comprehensiveTest-fail-paged-no-limit-offset.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2: CustomerPage! +} + +>>>comprehensiveTest-fail-paged-subscription.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +type Subscription { + CustomerSubscription: CustomerPage +} + +>>>comprehensiveTest-fail-paged-undeclared.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-fail-paged-unknown-field.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + doesNotExist: String! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-fail-paged-wrong-pagination-type.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type OffsetPageInfo { + totalRecords: Long! + pageSize: Int! +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + >>>comprehensiveTest-fail-parameters-names.graphqls "An RFC-3339 compliant DateTime Scalar" scalar DateTime @@ -417,6 +564,80 @@ type Query { SelectCustomers(offset: Int = 0): [Customer!] } +>>>comprehensiveTest-paged-results.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-paged-userdefined.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + related(limit: Int = 10, offset: Int = 0): CustomerRelatedPage +} + +type CustomerRelatedPage { + items: [Customer!] + meta: OffsetPageInfo +} + +type CustomerPage { + items: [Customer!] + meta: OffsetPageInfo +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): CustomerPage! +} + >>>comprehensiveTest-parameters-order.graphqls "An RFC-3339 compliant DateTime Scalar" scalar DateTime diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest.txt b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest.txt index 5c36bc6b0b..b1a7917ed3 100644 --- a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest.txt +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/GraphQLValidationTest/comprehensiveTest.txt @@ -269,6 +269,153 @@ type Query { TableFunctionCallsTblFct(arg1: Int, arg2: Int, limit: Int = 10, offset: Int = 0): [Customer!] } +>>>comprehensiveTest-fail-paged-no-limit-offset.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2: CustomerPage! +} + +>>>comprehensiveTest-fail-paged-subscription.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +type Subscription { + CustomerSubscription: CustomerPage +} + +>>>comprehensiveTest-fail-paged-undeclared.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-fail-paged-unknown-field.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + doesNotExist: String! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-fail-paged-wrong-pagination-type.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type OffsetPageInfo { + totalRecords: Long! + pageSize: Int! +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + >>>comprehensiveTest-fail-parameters-names.graphqls "An RFC-3339 compliant DateTime Scalar" scalar DateTime @@ -417,6 +564,80 @@ type Query { SelectCustomers(offset: Int = 0): [Customer!] } +>>>comprehensiveTest-paged-results.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! +} + +type CustomerPage { + results: [Customer!] + pagination: OffsetPageInfo +} + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + CustomerByTime2(limit: Int = 10, offset: Int = 0): CustomerPage! +} + +>>>comprehensiveTest-paged-userdefined.graphqls +"An RFC-3339 compliant DateTime Scalar" +scalar DateTime +"A 64-bit signed integer" +scalar Long + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Customer { + customerid: Long! + email: String! + name: String! + lastUpdated: Long! + timestamp: DateTime! + related(limit: Int = 10, offset: Int = 0): CustomerRelatedPage +} + +type CustomerRelatedPage { + items: [Customer!] + meta: OffsetPageInfo +} + +type CustomerPage { + items: [Customer!] + meta: OffsetPageInfo +} + +type Query { + TableFunctionCallsTblFct(arg1: Int!, arg2: Int!, limit: Int = 10, offset: Int = 0): CustomerPage! +} + >>>comprehensiveTest-parameters-order.graphqls "An RFC-3339 compliant DateTime Scalar" scalar DateTime diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/UseCaseCompileTest/clickstream-package-paginated.txt b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/UseCaseCompileTest/clickstream-package-paginated.txt new file mode 100644 index 0000000000..a5f2908de7 --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/UseCaseCompileTest/clickstream-package-paginated.txt @@ -0,0 +1,526 @@ +>>>inferred_schema.graphqls +type Click { + timestamp: DateTime! + userid: String! + url: String! +} + +type ClickPage { + results: [Click!] + pagination: OffsetPageInfo +} + +"An RFC-3339 compliant Full Date Scalar" +scalar Date + +"A DateTime scalar that handles both full RFC3339 and shorter timestamp formats" +scalar DateTime + +"A JSON scalar" +scalar JSON + +"24-hour clock time value string in the format `hh:mm:ss` or `hh:mm:ss.sss`." +scalar LocalTime + +"A 64-bit signed integer" +scalar Long + +type OffsetPageInfo { + pageSize: Int! + currentPage: Int! + hasNextPage: Boolean! + hasPreviousPage: Boolean! + nextOffset: Int + prevOffset: Int + firstEventTime: DateTime + lastEventTime: DateTime +} + +type Query { + Click(limit: Int = 10, offset: Int = 0): ClickPage! + "Recommend pages that are visited shortly after" + Recommendation( + "the URL to get recommendations for" + url: String!, + limit: Int = 10, + offset: Int = 0 + ): RecommendationPage! + "Most visited pages" + Trending(limit: Int = 10, offset: Int = 0): TrendingPage! + VisitAfter(limit: Int = 10, offset: Int = 0): VisitAfterPage! +} + +"Recommend pages that are visited shortly after" +type Recommendation { + url: String! + "the recommended page URL" + rec: String! + "the number of visitors that co-visited that page" + frequency: Long! +} + +type RecommendationPage { + results: [Recommendation!] + pagination: OffsetPageInfo +} + +"Most visited pages" +type Trending { + "URL of the top visited page" + url: String! + "Total number of visitors" + total: Long! +} + +type TrendingPage { + results: [Trending!] + pagination: OffsetPageInfo +} + +type VisitAfter { + beforeURL: String! + afterURL: String! + timestamp: DateTime! +} + +type VisitAfterPage { + results: [VisitAfter!] + pagination: OffsetPageInfo +} + +enum _McpMethodType { + NONE + TOOL + RESOURCE +} + +enum _RestMethodType { + NONE + GET + POST +} + +directive @api(mcp: _McpMethodType, rest: _RestMethodType, uri: String) on QUERY | MUTATION | FIELD_DEFINITION + +>>>pipeline_explain.txt +=== Click +ID: default_catalog.default_database.Click +Type: stream +Stage: flink +Primary key: url, userid, timestamp +Timestamp: timestamp +Row count: ~1e8 +--- +Schema: + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL + - userid: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - url: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL +Inputs: + - default_catalog.default_database.Click__base +Annotations: + - stream-root: Click + +=== Recommendation +ID: default_catalog.default_database.Recommendation +Type: state +Stage: flink +Primary key: url, rec +Timestamp: - +Row count: ~2e7 +--- +Schema: + - url: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - rec: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - frequency: BIGINT NOT NULL +Inputs: + - default_catalog.default_database.VisitAfter +Annotations: + - sort: [0 ASC-nulls-first, 2 DESC-nulls-last] + +=== Trending +ID: default_catalog.default_database.Trending +Type: state +Stage: flink +Primary key: url +Timestamp: - +Row count: ~1e7 +--- +Schema: + - url: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - total: BIGINT NOT NULL +Inputs: + - default_catalog.default_database.Click +Annotations: + - sort: [1 DESC-nulls-last, 0 ASC-nulls-first] + +=== VisitAfter +ID: default_catalog.default_database.VisitAfter +Type: stream +Stage: flink +Primary key: - +Timestamp: timestamp +Row count: ~3e7 +--- +Schema: + - beforeURL: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - afterURL: VARCHAR(2147483647) CHARACTER SET "UTF-16LE" NOT NULL + - timestamp: TIMESTAMP_LTZ(3) *ROWTIME* NOT NULL +Inputs: + - default_catalog.default_database.Click + +>>>flink-sql-no-functions.sql +CREATE TEMPORARY TABLE `Click__schema` ( + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `userid` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `url` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL +) +WITH ( + 'connector' = 'filesystem', + 'format' = 'flexible-json', + 'path' = '${DATA_PATH}/click.jsonl' +); +CREATE TABLE `Click` ( + PRIMARY KEY (`url`, `userid`, `timestamp`) NOT ENFORCED, + WATERMARK FOR `timestamp` AS `timestamp` - INTERVAL '0.001' SECOND +) +LIKE `Click__schema`; +CREATE VIEW `Trending` +AS +SELECT `url`, COUNT(1) AS `total` +FROM `Click` +GROUP BY `url`; +CREATE VIEW `VisitAfter` +AS +SELECT `b`.`url` AS `beforeURL`, `a`.`url` AS `afterURL`, `a`.`timestamp` AS `timestamp` +FROM `Click` AS `b` + INNER JOIN `Click` AS `a` ON `b`.`userid` = `a`.`userid` AND `b`.`timestamp` < `a`.`timestamp` AND `b`.`timestamp` >= `a`.`timestamp` - (INTERVAL 10 MINUTE); +CREATE VIEW `Recommendation` +AS +SELECT `beforeURL` AS `url`, `afterURL` AS `rec`, COUNT(1) AS `frequency` +FROM `VisitAfter` +GROUP BY `beforeURL`, `afterURL`; +CREATE TABLE `Click_1` ( + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `userid` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `url` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + PRIMARY KEY (`url`, `userid`, `timestamp`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'Click', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `Recommendation_2` ( + `url` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `rec` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `frequency` BIGINT NOT NULL, + PRIMARY KEY (`url`, `rec`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'table-name' = 'Recommendation', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `Trending_3` ( + `url` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `total` BIGINT NOT NULL, + PRIMARY KEY (`url`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'table-name' = 'Trending', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +CREATE TABLE `VisitAfter_4` ( + `beforeURL` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `afterURL` VARCHAR(2147483647) CHARACTER SET `UTF-16LE` NOT NULL, + `timestamp` TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL, + `__pk_hash` CHAR(32) CHARACTER SET `UTF-16LE`, + PRIMARY KEY (`__pk_hash`) NOT ENFORCED +) +WITH ( + 'connector' = 'jdbc-sqrl', + 'driver' = 'org.postgresql.Driver', + 'password' = '${POSTGRES_PASSWORD}', + 'sink.on-conflict.action' = 'IGNORE', + 'table-name' = 'VisitAfter', + 'url' = 'jdbc:postgresql://${POSTGRES_AUTHORITY}', + 'username' = '${POSTGRES_USERNAME}' +); +EXECUTE STATEMENT SET BEGIN +INSERT INTO `default_catalog`.`default_database`.`Click_1` +SELECT * +FROM `default_catalog`.`default_database`.`Click` +; +INSERT INTO `default_catalog`.`default_database`.`Recommendation_2` +SELECT * +FROM `default_catalog`.`default_database`.`Recommendation` +; +INSERT INTO `default_catalog`.`default_database`.`Trending_3` +SELECT * +FROM `default_catalog`.`default_database`.`Trending` +; +INSERT INTO `default_catalog`.`default_database`.`VisitAfter_4` +SELECT `beforeURL`, `afterURL`, `timestamp`, `hash_columns`(`beforeURL`, `afterURL`, `timestamp`) AS `__pk_hash` +FROM `default_catalog`.`default_database`.`VisitAfter` +; +END +>>>postgres-schema.sql +CREATE TABLE IF NOT EXISTS "Click" ("timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "userid" TEXT NOT NULL, "url" TEXT NOT NULL, PRIMARY KEY ("url","userid","timestamp")); +CREATE TABLE IF NOT EXISTS "Recommendation" ("url" TEXT NOT NULL, "rec" TEXT NOT NULL, "frequency" BIGINT NOT NULL, PRIMARY KEY ("url","rec")); +CREATE TABLE IF NOT EXISTS "Trending" ("url" TEXT NOT NULL, "total" BIGINT NOT NULL, PRIMARY KEY ("url")); +CREATE TABLE IF NOT EXISTS "VisitAfter" ("beforeURL" TEXT NOT NULL, "afterURL" TEXT NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "__pk_hash" TEXT, PRIMARY KEY ("__pk_hash")); + +CREATE INDEX IF NOT EXISTS "Click_btree_c0" ON "Click" USING btree ("timestamp"); +CREATE INDEX IF NOT EXISTS "VisitAfter_btree_c2" ON "VisitAfter" USING btree ("timestamp") +>>>vertx.json +{ + "models" : { + "v1" : { + "queries" : [ + { + "type" : "args", + "parentType" : "Query", + "fieldName" : "Click", + "exec" : { + "arguments" : [ + { + "type" : "variable", + "path" : "limit" + }, + { + "type" : "variable", + "path" : "offset" + } + ], + "query" : { + "type" : "SqlQuery", + "sql" : "SELECT *\nFROM \"Click\"", + "parameters" : [ ], + "pagination" : "OFFSET_PAGE_INFO", + "cacheDurationMs" : 0, + "database" : "POSTGRES", + "eventTimesSql" : "SELECT MIN(\"timestamp\") AS \"first_event_time\", MAX(\"timestamp\") AS \"last_event_time\" FROM (SELECT *\nFROM \"Click\") x" + } + } + }, + { + "type" : "args", + "parentType" : "Query", + "fieldName" : "Recommendation", + "exec" : { + "arguments" : [ + { + "type" : "variable", + "path" : "limit" + }, + { + "type" : "variable", + "path" : "offset" + }, + { + "type" : "variable", + "path" : "url" + } + ], + "query" : { + "type" : "SqlQuery", + "sql" : "SELECT *\nFROM (SELECT \"url\", \"rec\", \"frequency\"\n FROM \"Recommendation\"\n ORDER BY \"url\" NULLS FIRST, \"frequency\" DESC NULLS LAST) AS \"t\"\nWHERE \"url\" = $1", + "parameters" : [ + { + "type" : "arg", + "path" : "url", + "sqlType" : "VARCHAR" + } + ], + "pagination" : "OFFSET_PAGE_INFO", + "cacheDurationMs" : 0, + "database" : "POSTGRES" + } + } + }, + { + "type" : "args", + "parentType" : "Query", + "fieldName" : "Trending", + "exec" : { + "arguments" : [ + { + "type" : "variable", + "path" : "limit" + }, + { + "type" : "variable", + "path" : "offset" + } + ], + "query" : { + "type" : "SqlQuery", + "sql" : "SELECT *\nFROM (SELECT \"url\", \"total\"\n FROM \"Trending\"\n ORDER BY \"total\" DESC NULLS LAST, \"url\" NULLS FIRST) AS \"t\"", + "parameters" : [ ], + "pagination" : "OFFSET_PAGE_INFO", + "cacheDurationMs" : 0, + "database" : "POSTGRES" + } + } + }, + { + "type" : "args", + "parentType" : "Query", + "fieldName" : "VisitAfter", + "exec" : { + "arguments" : [ + { + "type" : "variable", + "path" : "limit" + }, + { + "type" : "variable", + "path" : "offset" + } + ], + "query" : { + "type" : "SqlQuery", + "sql" : "SELECT \"beforeURL\", \"afterURL\", \"timestamp\"\nFROM \"VisitAfter\"", + "parameters" : [ ], + "pagination" : "OFFSET_PAGE_INFO", + "cacheDurationMs" : 0, + "database" : "POSTGRES", + "eventTimesSql" : "SELECT MIN(\"timestamp\") AS \"first_event_time\", MAX(\"timestamp\") AS \"last_event_time\" FROM (SELECT \"beforeURL\", \"afterURL\", \"timestamp\"\nFROM \"VisitAfter\") x" + } + } + } + ], + "mutations" : [ ], + "subscriptions" : [ ], + "operations" : [ + { + "function" : { + "name" : "GetClick", + "parameters" : { + "type" : "object", + "properties" : { + "offset" : { + "type" : "integer" + }, + "limit" : { + "type" : "integer" + } + }, + "required" : [ ] + } + }, + "format" : "JSON", + "apiQuery" : { + "query" : "query Click($limit: Int = 10, $offset: Int = 0) {\nClick(limit: $limit, offset: $offset) {\nresults {\ntimestamp\nuserid\nurl\n}\npagination {\npageSize\ncurrentPage\nhasNextPage\nhasPreviousPage\nnextOffset\nprevOffset\nfirstEventTime\nlastEventTime\n}\n}\n\n}", + "queryName" : "Click", + "operationType" : "QUERY" + }, + "mcpMethod" : "TOOL", + "restMethod" : "GET", + "uriTemplate" : "queries/Click{?offset,limit}" + }, + { + "function" : { + "name" : "GetRecommendation", + "description" : "Recommend pages that are visited shortly after", + "parameters" : { + "type" : "object", + "properties" : { + "offset" : { + "type" : "integer" + }, + "limit" : { + "type" : "integer" + }, + "url" : { + "type" : "string", + "description" : "the URL to get recommendations for" + } + }, + "required" : [ + "url" + ] + } + }, + "format" : "JSON", + "apiQuery" : { + "query" : "query Recommendation($url: String!, $limit: Int = 10, $offset: Int = 0) {\nRecommendation(url: $url, limit: $limit, offset: $offset) {\nresults {\nurl\nrec\nfrequency\n}\npagination {\npageSize\ncurrentPage\nhasNextPage\nhasPreviousPage\nnextOffset\nprevOffset\nfirstEventTime\nlastEventTime\n}\n}\n\n}", + "queryName" : "Recommendation", + "operationType" : "QUERY" + }, + "mcpMethod" : "TOOL", + "restMethod" : "GET", + "uriTemplate" : "queries/Recommendation{?offset,limit,url}" + }, + { + "function" : { + "name" : "GetTrending", + "description" : "Most visited pages", + "parameters" : { + "type" : "object", + "properties" : { + "offset" : { + "type" : "integer" + }, + "limit" : { + "type" : "integer" + } + }, + "required" : [ ] + } + }, + "format" : "JSON", + "apiQuery" : { + "query" : "query Trending($limit: Int = 10, $offset: Int = 0) {\nTrending(limit: $limit, offset: $offset) {\nresults {\nurl\ntotal\n}\npagination {\npageSize\ncurrentPage\nhasNextPage\nhasPreviousPage\nnextOffset\nprevOffset\nfirstEventTime\nlastEventTime\n}\n}\n\n}", + "queryName" : "Trending", + "operationType" : "QUERY" + }, + "mcpMethod" : "TOOL", + "restMethod" : "GET", + "uriTemplate" : "queries/Trending{?offset,limit}" + }, + { + "function" : { + "name" : "GetVisitAfter", + "parameters" : { + "type" : "object", + "properties" : { + "offset" : { + "type" : "integer" + }, + "limit" : { + "type" : "integer" + } + }, + "required" : [ ] + } + }, + "format" : "JSON", + "apiQuery" : { + "query" : "query VisitAfter($limit: Int = 10, $offset: Int = 0) {\nVisitAfter(limit: $limit, offset: $offset) {\nresults {\nbeforeURL\nafterURL\ntimestamp\n}\npagination {\npageSize\ncurrentPage\nhasNextPage\nhasPreviousPage\nnextOffset\nprevOffset\nfirstEventTime\nlastEventTime\n}\n}\n\n}", + "queryName" : "VisitAfter", + "operationType" : "QUERY" + }, + "mcpMethod" : "TOOL", + "restMethod" : "GET", + "uriTemplate" : "queries/VisitAfter{?offset,limit}" + } + ], + "schema" : { + "type" : "string", + "schema" : "type Click {\n timestamp: DateTime!\n userid: String!\n url: String!\n}\n\ntype ClickPage {\n results: [Click!]\n pagination: OffsetPageInfo\n}\n\n\"An RFC-3339 compliant Full Date Scalar\"\nscalar Date\n\n\"A DateTime scalar that handles both full RFC3339 and shorter timestamp formats\"\nscalar DateTime\n\n\"A JSON scalar\"\nscalar JSON\n\n\"24-hour clock time value string in the format `hh:mm:ss` or `hh:mm:ss.sss`.\"\nscalar LocalTime\n\n\"A 64-bit signed integer\"\nscalar Long\n\ntype OffsetPageInfo {\n pageSize: Int!\n currentPage: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n nextOffset: Int\n prevOffset: Int\n firstEventTime: DateTime\n lastEventTime: DateTime\n}\n\ntype Query {\n Click(limit: Int = 10, offset: Int = 0): ClickPage!\n \"Recommend pages that are visited shortly after\"\n Recommendation(\n \"the URL to get recommendations for\"\n url: String!,\n limit: Int = 10,\n offset: Int = 0\n ): RecommendationPage!\n \"Most visited pages\"\n Trending(limit: Int = 10, offset: Int = 0): TrendingPage!\n VisitAfter(limit: Int = 10, offset: Int = 0): VisitAfterPage!\n}\n\n\"Recommend pages that are visited shortly after\"\ntype Recommendation {\n url: String!\n \"the recommended page URL\"\n rec: String!\n \"the number of visitors that co-visited that page\"\n frequency: Long!\n}\n\ntype RecommendationPage {\n results: [Recommendation!]\n pagination: OffsetPageInfo\n}\n\n\"Most visited pages\"\ntype Trending {\n \"URL of the top visited page\"\n url: String!\n \"Total number of visitors\"\n total: Long!\n}\n\ntype TrendingPage {\n results: [Trending!]\n pagination: OffsetPageInfo\n}\n\ntype VisitAfter {\n beforeURL: String!\n afterURL: String!\n timestamp: DateTime!\n}\n\ntype VisitAfterPage {\n results: [VisitAfter!]\n pagination: OffsetPageInfo\n}\n\nenum _McpMethodType {\n NONE\n TOOL\n RESOURCE\n}\n\nenum _RestMethodType {\n NONE\n GET\n POST\n}\n\ndirective @api(mcp: _McpMethodType, rest: _RestMethodType, uri: String) on QUERY | MUTATION | FIELD_DEFINITION\n" + } + } + } +} diff --git a/sqrl-testing/sqrl-testing-integration/src/test/resources/usecases/clickstream/package-paginated.json b/sqrl-testing/sqrl-testing-integration/src/test/resources/usecases/clickstream/package-paginated.json new file mode 100644 index 0000000000..d84685ffad --- /dev/null +++ b/sqrl-testing/sqrl-testing-integration/src/test/resources/usecases/clickstream/package-paginated.json @@ -0,0 +1,17 @@ +{ + "version": "1", + "enabled-engines": ["vertx", "postgres", "flink"], + "compiler": { + "api": { + "paginated-results": true + } + }, + "script": { + "main": "clickstream-teaser.sqrl" + }, + "test-runner": { + "snapshot-folder": "snapshots-clickstream-teaser", + "test-folder": "tests-clickstream-teaser", + "delay-sec": -1 + } +}