Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PREVIEW] graphQL schema generation for new SQRL language and planner #1066

Open
wants to merge 39 commits into
base: experiment/simplify
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
8cdc9af
Start implementing new GraphQL
echauchot Feb 18, 2025
f5fa400
Register BigInteger as extended scalar type in GraphQLServer. Reused …
echauchot Jan 14, 2025
afc5479
Add comments to GraphqlSchemaFactory.
echauchot Jan 14, 2025
38afa26
Add a new extendedScalarTypes configuration in package.json (in the c…
echauchot Jan 14, 2025
bf2cb79
Register extendedScalarTypes in GraphqlSchemaFactory base on the comp…
echauchot Jan 14, 2025
50b2ab6
Add comments to GraphqlSchemaFactory.
echauchot Jan 15, 2025
b7f95e6
Add the new extendedScalarTypes compiler parameter to the configurati…
echauchot Jan 15, 2025
3e00321
Treat all Calcite BIGINT types as ExtendedScalars.GraphQLBigInteger b…
echauchot Jan 16, 2025
30e0c3f
Add a new usecase test with BigInteger support enabled (#909).
echauchot Jan 17, 2025
1d47889
run FullUseCasesIT on simple-with-bighint-support test (#909).
echauchot Jan 17, 2025
b46a1cc
Test with providing a graphql schema containing BigInteger (#909).
echauchot Jan 17, 2025
e5a8211
Replace BigIntOrdersTest.snapshot with the snapshot generated in the …
echauchot Jan 23, 2025
27de109
Rename ExtendedScalars.GraphQLBigInteger string literal to GraphQLBig…
echauchot Jan 28, 2025
67a1035
First working graphql schema inference for simple sqrl
echauchot Feb 19, 2025
3887484
Implement relationships
echauchot Feb 20, 2025
3756b1f
Fix arguments execution time errors.
echauchot Feb 20, 2025
0105f5a
More cases with base tables
echauchot Feb 21, 2025
66ff5a9
Implement correct type declaration with relationships and base tables
echauchot Feb 21, 2025
eec39cb
Wire up root query object, fix base table for type references, avoid …
echauchot Feb 24, 2025
787f664
Remove uniqueness in name. Not needed ?
echauchot Feb 24, 2025
354a00e
Disable cleanInvalidTypes for now as it removes the nested types
echauchot Feb 24, 2025
bbf5703
TODOs
echauchot Feb 24, 2025
9906861
add failing script
echauchot Feb 24, 2025
8e1da9b
Remove no more needed guarding ifs around subscriptions processing
echauchot Feb 25, 2025
77349f8
Use guava preconditions instead of if/throws
echauchot Feb 25, 2025
b834e2d
uniquify relationship result type name
echauchot Feb 25, 2025
c09dcb0
Cleaning
echauchot Feb 25, 2025
bd452d4
First subscriptions version
echauchot Feb 25, 2025
fc265ce
Better encapsulate subscriptions and queries in the inference method
echauchot Feb 26, 2025
36c8d6d
Remove TEST goal test. And remove no more needed goal field in Graphq…
echauchot Feb 26, 2025
2943310
graphQL type mapping: Reduce call stack and improve naming.
echauchot Feb 26, 2025
efaee61
use the same uniquify method (full path and _) for structured types t…
echauchot Feb 26, 2025
ff00a3d
Remove overloading of table functions
echauchot Feb 26, 2025
3fb1dbc
Remove old subscriptions code
echauchot Feb 26, 2025
7617648
Add a comment on unsupported projections in kafka server
echauchot Feb 26, 2025
b620d8b
Use new base table system
echauchot Feb 27, 2025
ae5ab2d
re-enable root table function with no base table (projection) and com…
echauchot Feb 27, 2025
ab1e2ae
Simplify errorCollector initialization with graphql schema
echauchot Feb 27, 2025
3e49640
consolidate even more subscriptions and queries in the inference meth…
echauchot Feb 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/datasqrl-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ The `compiler` section of the configuration controls elements of the core compil
"compiler" : {
"addArguments": true,
"logger": "print",
"extendedScalarTypes": false,
"explain": {
"visual": true,
"text": true,
Expand All @@ -164,9 +165,9 @@ The `compiler` section of the configuration controls elements of the core compil

* `addArguments` specifies whether to include table columns as filters in the generated GraphQL schema. This only applies if the GraphQL schema is generated by the compiler.
* `logger` configures the logging framework used for logging statements like `EXPORT MyTable TO logger.MyTable;`. It is `print` by default which logs to STDOUT. Set it to the configured log engine for logging output to be sent to that engine, e.g. `"logger": "kafka"`. Set it to `none` to suppress logging output.
* `extendedScalarTypes` optional parameter (defaults to false) that configures the registration and use of graphQL extended scalar types. If enabled, the primary key of tables will be mapped to BigInteger instead of Float in GraphQL.
* `explain` configures how the DAG plan compiled by DataSQRL is presented in the `build` directory. If `visual` is true, a visual representation of the DAG is written to the `pipeline_visual.html` file which you can open in any browser. If `text` is true, a textual representation of the DAG is written to the `pipeline_explain.txt` file. If `extended` is true, the DAG outputs include more information like the relational plan which may be very verbose.


### Profiles

```json
Expand Down
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<junit.jupiter.version>5.8.2</junit.jupiter.version>
<calcite.version>1.27.0</calcite.version>
<graphql-java.version>19.2</graphql-java.version>
<graphql-java-extended-scalars.version>19.1</graphql-java-extended-scalars.version>
<projectreactor.version>3.5.6</projectreactor.version>
<avro.version>1.11.3</avro.version>
<freemarker.version>2.3.32</freemarker.version>
Expand Down Expand Up @@ -363,6 +364,13 @@
<version>${graphql-java.version}</version>
</dependency>

<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-extended-scalars</artifactId>
<version>${graphql-java-extended-scalars.version}</version>
</dependency>


<!-- Vertx -->
<dependency>
<groupId>io.vertx</groupId>
Expand Down
2 changes: 2 additions & 0 deletions sqrl-base/src/main/java/com/datasqrl/config/PackageJson.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ interface CompilerConfig {
boolean isAddArguments();

String getLogger();

boolean isExtendedScalarTypes();
}

interface OutputConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.datasqrl.calcite.schema.ExpandTableMacroRule.ExpandTableMacroConfig;
import com.datasqrl.calcite.schema.sql.SqlBuilders.SqlSelectBuilder;
import com.datasqrl.canonicalizer.ReservedName;
import com.datasqrl.graphql.server.CustomScalars;
import com.datasqrl.parse.SqrlParserImpl;
import com.datasqrl.util.DataContextImpl;
import java.util.Arrays;
Expand Down Expand Up @@ -185,6 +186,8 @@ public RelDataType parseDatatype(String datatype) {
return this.cluster.getTypeFactory().createSqlType(SqlTypeName.INTEGER);
} else if (datatype.equalsIgnoreCase("datetime")) {
return this.cluster.getTypeFactory().createSqlType(SqlTypeName.TIMESTAMP, 3);
} else if (datatype.equalsIgnoreCase(CustomScalars.GRAPHQL_BIGINTEGER.getName())) {
return this.cluster.getTypeFactory().createSqlType(SqlTypeName.BIGINT);
}

// Todo fix: only supporting precision for non-alien types
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import static com.datasqrl.graphql.generate.GraphqlSchemaUtil.getInputType;
import static com.datasqrl.graphql.generate.GraphqlSchemaUtil.getOutputType;
import static com.datasqrl.graphql.generate.GraphqlSchemaUtil.isValidGraphQLName;
import static com.datasqrl.graphql.generate.GraphqlSchemaUtil.wrap;
import static com.datasqrl.graphql.generate.GraphqlSchemaUtil.wrapNullable;
import static com.datasqrl.graphql.jdbc.SchemaConstants.LIMIT;
import static com.datasqrl.graphql.jdbc.SchemaConstants.OFFSET;
import static graphql.schema.GraphQLNonNull.nonNull;
Expand All @@ -19,24 +19,22 @@
import com.datasqrl.canonicalizer.NamePath;

import com.datasqrl.config.PackageJson.CompilerConfig;
import com.datasqrl.config.SystemBuiltInConnectors;
import com.datasqrl.engine.log.LogManager;
import com.datasqrl.function.SqrlFunctionParameter;
import com.datasqrl.graphql.server.CustomScalars;
import com.datasqrl.io.tables.TableType;
import com.datasqrl.plan.table.PhysicalRelationalTable;
import com.datasqrl.plan.table.ProxyImportRelationalTable;
import com.datasqrl.plan.table.QueryRelationalTable;
import com.datasqrl.plan.validate.ExecutionGoal;
import com.datasqrl.plan.validate.ResolvedImport;
import com.datasqrl.plan.validate.ScriptPlanner.Mutation;
import com.datasqrl.schema.Multiplicity;
import com.datasqrl.schema.NestedRelationship;
import com.datasqrl.schema.Relationship.JoinType;
import com.datasqrl.schema.RootSqrlTable;
import com.google.inject.Inject;
import graphql.Scalars;
import graphql.language.IntValue;
import graphql.scalars.ExtendedScalars;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLInputType;
Expand Down Expand Up @@ -67,7 +65,6 @@
import org.apache.calcite.schema.Table;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.commons.collections.ListUtils;
import scala.annotation.meta.field;

/**
* Creates a default graphql schema based on the SQRL schema
Expand All @@ -80,6 +77,7 @@ public class GraphqlSchemaFactory {
private final Set<String> usedNames = new HashSet<>();
private final SqrlSchema schema;
private final boolean addArguments;
private final boolean extendedScalarTypes;
private final LogManager logManager;
// Root path signifying the 'Query' type.
private Map<NamePath, List<SqrlTableMacro>> objectPathToTables;
Expand All @@ -89,13 +87,14 @@ public class GraphqlSchemaFactory {

@Inject
public GraphqlSchemaFactory(SqrlSchema schema, CompilerConfig config, LogManager logManager) {
this(schema, config.isAddArguments(), logManager);
this(schema, config.isAddArguments(), config.isExtendedScalarTypes(), logManager);
}

public GraphqlSchemaFactory(SqrlSchema schema, boolean addArguments, LogManager logManager) {
public GraphqlSchemaFactory(SqrlSchema schema, boolean addArguments, boolean extendedScalarTypes, LogManager logManager) {
this.schema = schema;
this.addArguments = addArguments;
this.logManager = logManager;
this.extendedScalarTypes = extendedScalarTypes;
}

public Optional<GraphQLSchema> generate(ExecutionGoal goal) {
Expand All @@ -105,21 +104,22 @@ public Optional<GraphQLSchema> generate(ExecutionGoal goal) {
this.fieldPathToTables = schema.getTableFunctions().stream()
.collect(Collectors.groupingBy(SqrlTableMacro::getAbsolutePath,
LinkedHashMap::new, Collectors.toList()));
for (Map.Entry<NamePath, List<SqrlTableMacro>> path : fieldPathToTables.entrySet()) {
if (path.getKey().getLast().isHidden()) continue;
for (Map.Entry<NamePath, List<SqrlTableMacro>> field : fieldPathToTables.entrySet()) {
if (field.getKey().getLast().isHidden()) continue; // hidden field not exposed in graphQL queries

// if (goal == ExecutionGoal.TEST) {
// if (!path.getValue().get(0).isTest()) continue;
// } else {
// if (path.getValue().get(0).isTest()) continue;
// }
Optional<GraphQLObjectType> graphQLObjectType = generateObject(path.getValue(),
objectPathToTables.getOrDefault(path.getKey(), List.of()));
Optional<GraphQLObjectType> graphQLObjectType = generateObject(field.getValue(), // list of table functions de field is in
objectPathToTables.getOrDefault(field.getKey(), List.of())); // List of table functions relationships
graphQLObjectType.map(objectTypes::add);
}

GraphQLObjectType queryType = createQueryType(goal, objectPathToTables.get(NamePath.ROOT));

// cleaning of invalid objectTypes
postProcess();

if (queryFields.isEmpty()) {
Expand All @@ -129,6 +129,9 @@ public Optional<GraphQLSchema> generate(ExecutionGoal goal) {

GraphQLSchema.Builder builder = GraphQLSchema.newSchema()
.query(queryType);
if (extendedScalarTypes) { // use the plural parameter name in place of only bigInteger to avoid having a conf parameter of each special type mapping feature in the future
builder.additionalTypes(Set.of(CustomScalars.GRAPHQL_BIGINTEGER));
}
if (goal != ExecutionGoal.TEST) {
if (logManager.hasLogEngine() && System.getenv().get("ENABLE_SUBSCRIPTIONS") != null) {
Optional<GraphQLObjectType.Builder> subscriptions = createSubscriptionTypes(schema);
Expand All @@ -137,7 +140,7 @@ public Optional<GraphQLSchema> generate(ExecutionGoal goal) {
Optional<GraphQLObjectType.Builder> mutations = createMutationTypes(schema);
mutations.map(builder::mutation);
}
builder.additionalTypes(new LinkedHashSet<>(objectTypes));
builder.additionalTypes(new LinkedHashSet<>(objectTypes)); // the inferred types

if (queryType.getFields().isEmpty()) {
if (goal == ExecutionGoal.TEST) {
Expand Down Expand Up @@ -174,7 +177,7 @@ private Optional<Builder> createMutationTypes(SqrlSchema schema) {
RelDataType type = mutation.getRelDataType();

// Create the 'event' argument which should mirror the structure of the type
GraphQLInputType inputType = GraphqlSchemaUtil.createInputTypeForRelDataType(type, NamePath.of(name), seen).orElseThrow(
GraphQLInputType inputType = GraphqlSchemaUtil.createInputTypeForRelDataType(type, NamePath.of(name), seen, extendedScalarTypes).orElseThrow(
() -> new IllegalArgumentException("Could not create input type for mutation: " + mutation.getName()));

GraphQLArgument inputArgument = GraphQLArgument.newArgument()
Expand All @@ -185,7 +188,7 @@ private Optional<Builder> createMutationTypes(SqrlSchema schema) {
GraphQLFieldDefinition subscriptionField = GraphQLFieldDefinition.newFieldDefinition()
.name(name)
.argument(inputArgument)
.type(createOutputTypeForRelDataType(type, NamePath.of(name), seen).get())
.type(createOutputTypeForRelDataType(type, NamePath.of(name), seen, extendedScalarTypes).get())
.build();

builder.field(subscriptionField);
Expand Down Expand Up @@ -226,7 +229,7 @@ public Optional<Builder> createSubscriptionTypes(SqrlSchema schema) {

GraphQLFieldDefinition subscriptionField = GraphQLFieldDefinition.newFieldDefinition()
.name(tableName)
.type(createOutputTypeForRelDataType(table.getRowType(), NamePath.of(tableName), seen).get())
.type(createOutputTypeForRelDataType(table.getRowType(), NamePath.of(tableName), seen, extendedScalarTypes).get())
.build();

subscriptionFields.add(subscriptionField);
Expand Down Expand Up @@ -258,7 +261,7 @@ private GraphQLObjectType createQueryType(ExecutionGoal goal, List<SqrlTableMacr

GraphQLFieldDefinition field = GraphQLFieldDefinition.newFieldDefinition()
.name(rel.getAbsolutePath().getDisplay())
.type(wrap(createTypeName(rel), rel.getMultiplicity()))
.type(GraphqlSchemaUtil.wrapMultiplicity(createTypeName(rel), rel.getMultiplicity()))
.arguments(createArguments(rel))
.build();
fields.add(field);
Expand All @@ -280,22 +283,23 @@ private Optional<GraphQLObjectType> generateObject(List<SqrlTableMacro> tableMac
//Todo check: The multiple table macros should all point to the same relnode type

Map<Name, List<SqrlTableMacro>> relByName = relationships.stream()
.collect(Collectors.groupingBy(g -> g.getFullPath().getLast(),
.collect(Collectors.groupingBy(g -> g.getFullPath().getLast(), //map from relationship field to list of table functions
LinkedHashMap::new, Collectors.toList()));

SqrlTableMacro first = tableMacros.get(0);
RelDataType rowType = first.getRowType();
SqrlTableMacro first = tableMacros.get(0); // take the first table function the field is in
RelDataType rowType = first.getRowType(); // and extract its calcite schema.
//todo: check that all table macros are compatible
// for (int i = 1; i < tableMacros.size(); i++) {
// }

// create the graphQL fields for non-relationship fields
List<GraphQLFieldDefinition> fields = new ArrayList<>();
for (RelDataTypeField field : rowType.getFieldList()) {
if (!relByName.containsKey(Name.system(field.getName()))) {
createRelationshipField(field).map(fields::add);
}
}

// create the graphQL fields for relationship fields
for (Map.Entry<Name, List<SqrlTableMacro>> rel : relByName.entrySet()) {
createRelationshipField(rel.getValue()).map(fields::add);
}
Expand Down Expand Up @@ -338,7 +342,7 @@ private Optional<GraphQLFieldDefinition> createRelationshipField(List<SqrlTableM

GraphQLFieldDefinition field = GraphQLFieldDefinition.newFieldDefinition()
.name(name)
.type(wrap(createTypeName(sqrlTableMacro), sqrlTableMacro.getMultiplicity()))
.type(GraphqlSchemaUtil.wrapMultiplicity(createTypeName(sqrlTableMacro), sqrlTableMacro.getMultiplicity()))
.arguments(createArguments(sqrlTableMacro))
.build();

Expand Down Expand Up @@ -367,10 +371,10 @@ private List<GraphQLArgument> createArguments(SqrlTableMacro field) {
} else {
return parameters.stream()
.filter(p->!((SqrlFunctionParameter)p).isInternal())
.filter(p->getInputType(p.getType(null), NamePath.of(p.getName()), seen).isPresent())
.filter(p->getInputType(p.getType(null), NamePath.of(p.getName()), seen, extendedScalarTypes).isPresent())
.map(parameter -> GraphQLArgument.newArgument()
.name(((SqrlFunctionParameter)parameter).getVariableName())
.type(nonNull(getInputType(parameter.getType(null), NamePath.of(parameter.getName()), seen).get()))
.type(nonNull(getInputType(parameter.getType(null), NamePath.of(parameter.getName()), seen, extendedScalarTypes).get()))
.build()).collect(Collectors.toList());
}
}
Expand Down Expand Up @@ -417,12 +421,12 @@ private List<GraphQLArgument> generatePermuted(SqrlTableMacro macro) {

return primaryKeys
.stream()
.filter(f -> getInputType(f.getType(), NamePath.of(tableName), seen).isPresent())
.filter(f -> getInputType(f.getType(), NamePath.of(tableName), seen, extendedScalarTypes).isPresent())
.filter(f -> isValidGraphQLName(f.getName()))
.filter(this::isVisible)
.map(f -> GraphQLArgument.newArgument()
.name(f.getName())
.type(getInputType(f.getType(), NamePath.of(f.getName()), seen).get())
.type(getInputType(f.getType(), NamePath.of(f.getName()), seen, extendedScalarTypes).get())
.build())
.collect(Collectors.toList());
}
Expand All @@ -438,12 +442,12 @@ private GraphQLOutputType createTypeName(SqrlTableMacro sqrlTableMacro) {
}

private Optional<GraphQLFieldDefinition> createRelationshipField(RelDataTypeField field) {
return getOutputType(field.getType(), NamePath.of(field.getName()), seen)
return getOutputType(field.getType(), NamePath.of(field.getName()), seen, extendedScalarTypes)
.filter(f->isValidGraphQLName(field.getName()))
.filter(f->isVisible(field))
.map(t -> GraphQLFieldDefinition.newFieldDefinition()
.name(field.getName())
.type(wrap(t, field.getType())).build());
.type(wrapNullable(t, field.getType())).build());
}

public void postProcess() {
Expand Down
Loading