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

Add filter types to schema #21

Merged
merged 4 commits into from
Aug 20, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions engine/src/main/java/io/graphqlcrud/Filters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package io.graphqlcrud;

import graphql.Scalars;
import graphql.schema.*;

public class Filters {

public static GraphQLInputObjectType.Builder pageInputBuilder() {
return GraphQLInputObjectType.newInputObject().name("PageRequest")
.field(GraphQLInputObjectField.newInputObjectField().name("limit").type(Scalars.GraphQLInt))
.field(GraphQLInputObjectField.newInputObjectField().name("offset").type(Scalars.GraphQLInt));
}

public static GraphQLInputObjectType.Builder filterInputBuilder() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this some form of sandbox. It contains business model where it should be generic?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the schema we read from the Database we are applying the where filters and page limits apply automatically.

return GraphQLInputObjectType.newInputObject().name("QueryFilter")
.field(GraphQLInputObjectField.newInputObjectField().name("id").type(GraphQLTypeReference.typeRef("IDInput")))
.field(GraphQLInputObjectField.newInputObjectField().name("title").type(GraphQLTypeReference.typeRef("StringInput")))
.field(GraphQLInputObjectField.newInputObjectField().name("clickCount").type(GraphQLTypeReference.typeRef("IntInput")))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now using these types add FilterInput on Customer like

CustomerFilterInput {
  SSN StringInput,
  ID    IDInput,
  ...
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding it additionally or modifying the existing type Customer to type CustomerFilterInput?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a separate type, see https://graphqlcrud.org/docs/next/find and example of NoteFilter and also see at the end of the page

.field(GraphQLInputObjectField.newInputObjectField().name("floatValue").type(GraphQLTypeReference.typeRef("FloatInput")))
.field(GraphQLInputObjectField.newInputObjectField().name("description").type(GraphQLTypeReference.typeRef("StringInput")))
.field(GraphQLInputObjectField.newInputObjectField().name("and").type(GraphQLList.list(GraphQLTypeReference.typeRef("QueryFilter"))))
.field(GraphQLInputObjectField.newInputObjectField().name("or").type(GraphQLList.list(GraphQLTypeReference.typeRef("QueryFilter"))))
.field(GraphQLInputObjectField.newInputObjectField().name("not").type(GraphQLTypeReference.typeRef("QueryFilter")));
}

public static GraphQLInputObjectType.Builder stringInputBuilder() {
return GraphQLInputObjectType.newInputObject().name("StringInput")
.field(GraphQLInputObjectField.newInputObjectField().name("ne").type(Scalars.GraphQLString))
.field(GraphQLInputObjectField.newInputObjectField().name("eq").type(Scalars.GraphQLString))
.field(GraphQLInputObjectField.newInputObjectField().name("le").type(Scalars.GraphQLString))
.field(GraphQLInputObjectField.newInputObjectField().name("lt").type(Scalars.GraphQLString))
.field(GraphQLInputObjectField.newInputObjectField().name("ge").type(Scalars.GraphQLString))
.field(GraphQLInputObjectField.newInputObjectField().name("gt").type(Scalars.GraphQLString))
.field(GraphQLInputObjectField.newInputObjectField().name("in").type(GraphQLList.list(GraphQLNonNull.nonNull(Scalars.GraphQLString))))
.field(GraphQLInputObjectField.newInputObjectField().name("contains").type(Scalars.GraphQLString))
.field(GraphQLInputObjectField.newInputObjectField().name("startsWith").type(Scalars.GraphQLString))
.field(GraphQLInputObjectField.newInputObjectField().name("endsWith").type(Scalars.GraphQLString));

}

public static GraphQLInputObjectType.Builder idInputBuilder() {
return GraphQLInputObjectType.newInputObject().name("IDInput")
.field(GraphQLInputObjectField.newInputObjectField().name("ne").type(Scalars.GraphQLID))
.field(GraphQLInputObjectField.newInputObjectField().name("eq").type(Scalars.GraphQLID))
.field(GraphQLInputObjectField.newInputObjectField().name("in").type(GraphQLList.list(GraphQLNonNull.nonNull(Scalars.GraphQLID))));

}

public static GraphQLInputObjectType.Builder booleanInputBuilder() {
return GraphQLInputObjectType.newInputObject().name("BooleanInput")
.field(GraphQLInputObjectField.newInputObjectField().name("ne").type(Scalars.GraphQLBoolean))
.field(GraphQLInputObjectField.newInputObjectField().name("eq").type(Scalars.GraphQLBoolean));
}

public static GraphQLInputObjectType.Builder floatInputBuilder() {
return GraphQLInputObjectType.newInputObject().name("FloatInput")
.field(GraphQLInputObjectField.newInputObjectField().name("ne").type(Scalars.GraphQLFloat))
.field(GraphQLInputObjectField.newInputObjectField().name("eq").type(Scalars.GraphQLFloat))
.field(GraphQLInputObjectField.newInputObjectField().name("le").type(Scalars.GraphQLFloat))
.field(GraphQLInputObjectField.newInputObjectField().name("lt").type(Scalars.GraphQLFloat))
.field(GraphQLInputObjectField.newInputObjectField().name("ge").type(Scalars.GraphQLFloat))
.field(GraphQLInputObjectField.newInputObjectField().name("gt").type(Scalars.GraphQLFloat))
.field(GraphQLInputObjectField.newInputObjectField().name("in").type(GraphQLList.list(GraphQLNonNull.nonNull(Scalars.GraphQLFloat))));
}

public static GraphQLInputObjectType.Builder intInputBuilder() {
return GraphQLInputObjectType.newInputObject().name("IntInput")
.field(GraphQLInputObjectField.newInputObjectField().name("ne").type(Scalars.GraphQLInt))
.field(GraphQLInputObjectField.newInputObjectField().name("eq").type(Scalars.GraphQLInt))
.field(GraphQLInputObjectField.newInputObjectField().name("le").type(Scalars.GraphQLInt))
.field(GraphQLInputObjectField.newInputObjectField().name("lt").type(Scalars.GraphQLInt))
.field(GraphQLInputObjectField.newInputObjectField().name("ge").type(Scalars.GraphQLInt))
.field(GraphQLInputObjectField.newInputObjectField().name("gt").type(Scalars.GraphQLInt))
.field(GraphQLInputObjectField.newInputObjectField().name("in").type(GraphQLList.list(GraphQLNonNull.nonNull(Scalars.GraphQLInt))));
}

public static GraphQLInputObjectType.Builder orderByInputBuilder() {
return GraphQLInputObjectType.newInputObject().name("OrderByInput")
.field(GraphQLInputObjectField.newInputObjectField().name("field").type(GraphQLNonNull.nonNull(Scalars.GraphQLString)))
.field(GraphQLInputObjectField.newInputObjectField().name("order").type(GraphQLTypeReference.typeRef("SortDirectionEnum")).defaultValue("ASC"));
}

public static GraphQLEnumType.Builder sortDirectionEnumBuilder() {
return GraphQLEnumType.newEnum().name("SortDirectionEnum")
.value("ASC")
.value("DESC");
}
}
25 changes: 15 additions & 10 deletions engine/src/main/java/io/graphqlcrud/GraphQLSchemaBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,15 @@
import java.util.ArrayList;
import java.util.List;

import graphql.schema.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import graphql.Scalars;
import graphql.language.OperationTypeDefinition;
import graphql.language.SchemaDefinition;
import graphql.language.TypeName;
import graphql.schema.FieldCoordinates;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLCodeRegistry;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLNonNull;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLObjectType.Builder;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLTypeReference;
import io.graphqlcrud.model.Entity;
import io.graphqlcrud.model.Schema;
import io.graphqlcrud.types.JdbcTypeMap;
Expand All @@ -61,9 +53,19 @@ public GraphQLSchema buildSchema(Schema schema) {
GraphQLObjectType.Builder queryTypeBuilder = GraphQLObjectType.newObject();
queryTypeBuilder.name("QueryType");

//add filters for queries
builder.additionalType(Filters.pageInputBuilder().build());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. I think it will be amazing to use https://github.com/graphql-java/graphql-java/blob/master/src/main/java/graphql/schema/idl/SchemaPrinter.java to print schema for us to verify this under some different model use cases (we can use snapshot testing for this or anything else to validate correctness and call it a day.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are printing the schema using SchemaPrinter, once you run you can look for the schema.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running this now! thank you

builder.additionalType(Filters.stringInputBuilder().build());
builder.additionalType(Filters.intInputBuilder().build());
builder.additionalType(Filters.idInputBuilder().build());
builder.additionalType(Filters.booleanInputBuilder().build());
builder.additionalType(Filters.floatInputBuilder().build());
builder.additionalType(Filters.sortDirectionEnumBuilder().build());
builder.additionalType(Filters.orderByInputBuilder().build());
builder.additionalType(Filters.filterInputBuilder().build());

schema.getEntities().stream().forEach(entity -> {
GraphQLObjectType.Builder typeBuilder = GraphQLObjectType.newObject();
//TODO : Model Annotations
typeBuilder.description(entity.getDescription());
typeBuilder.name(entity.getName());
typeBuilder.withDirective(SQLDirective.newDirective().tablename(entity.getFullName()).build());
Expand Down Expand Up @@ -101,6 +103,9 @@ private void addQueryOperationsForEntity(Entity entity, Builder queryTypeBuilder
String name = StringUtil.plural(entity.getName()).toLowerCase();
GraphQLFieldDefinition.Builder builder = GraphQLFieldDefinition.newFieldDefinition();
builder.name(name);
builder.argument(GraphQLArgument.newArgument().name("page").type(GraphQLTypeReference.typeRef("PageRequest")).build());
builder.argument(GraphQLArgument.newArgument().name("filter").type(GraphQLTypeReference.typeRef("QueryFilter")).build());
builder.argument(GraphQLArgument.newArgument().name("orderBy").type(GraphQLTypeReference.typeRef("OrderByInput")).build());
builder.type(GraphQLList.list(new GraphQLTypeReference(entity.getName())));
queryTypeBuilder.field(builder.build());
codeBuilder.dataFetcher(FieldCoordinates.coordinates("QueryType", name), DEFAULT_DATA_FETCHER_FACTORY);
Expand Down
31 changes: 7 additions & 24 deletions engine/src/main/java/io/graphqlcrud/SQLDataFetcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,10 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import graphql.schema.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.teiid.language.AndOr;
import org.teiid.language.ColumnReference;
import org.teiid.language.Comparison;
import org.teiid.language.Condition;
import org.teiid.language.DerivedColumn;
import org.teiid.language.Expression;
import org.teiid.language.Join;
import org.teiid.language.Literal;
import org.teiid.language.NamedTable;
import org.teiid.language.Select;
import org.teiid.language.TableReference;
import org.teiid.language.*;
import org.teiid.language.visitor.SQLStringVisitor;

import graphql.language.Argument;
Expand All @@ -45,14 +36,6 @@
import graphql.language.Selection;
import graphql.language.StringValue;
import graphql.language.Value;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLModifiedType;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLTypeUtil;
import graphql.schema.SelectedField;

// This must be thread safe, as it will be called by multiple threads at same time
// This is very simple naively written class, will require more structure here
Expand Down Expand Up @@ -88,12 +71,12 @@ private String buildSQL(DataFetchingEnvironment environment) {
null, null);

// the parent query like "customers" definition
Field rootFeild = environment.getField();
Field rootField = environment.getField();
GraphQLFieldDefinition rootDefinition = environment.getFieldDefinition();
NamedTable table = buildTable(rootDefinition, inc);

// add select
TableReference from = buildSelect(environment, select, table, rootFeild, rootDefinition, null, inc);
TableReference from = buildSelect(environment, select, table, rootField, rootDefinition, null, inc);

// add from
select.getFrom().add(from);
Expand All @@ -104,7 +87,7 @@ private String buildSQL(DataFetchingEnvironment environment) {
}

private TableReference buildSelect(DataFetchingEnvironment environment, Select select, TableReference left,
Field rootFeild, GraphQLFieldDefinition rootFieldDefinition, String fqn, AtomicInteger inc) {
Field rootField, GraphQLFieldDefinition rootFieldDefinition, String fqn, AtomicInteger inc) {

NamedTable table = null;
if (left instanceof NamedTable) {
Expand All @@ -121,7 +104,7 @@ private TableReference buildSelect(DataFetchingEnvironment environment, Select s
}

if (GraphQLTypeUtil.isScalar(rootType)) {
select.getDerivedColumns().add(new DerivedColumn(null, new ColumnReference(table, rootFeild.getName(), null, null)));
select.getDerivedColumns().add(new DerivedColumn(null, new ColumnReference(table, rootField.getName(), null, null)));
} else if (rootType instanceof GraphQLObjectType) {
SQLDirective sqlDirective = SQLDirective.find(((GraphQLObjectType)rootType).getDirectives());
if (rootSqlDirective != null && rootSqlDirective.getPrimaryFields() != null) {
Expand All @@ -143,7 +126,7 @@ private TableReference buildSelect(DataFetchingEnvironment environment, Select s
}

// since this is object type loop through and selected fields
List<Selection> fields = rootFeild.getSelectionSet().getSelections();
List<Selection> fields = rootField.getSelectionSet().getSelections();
for (int i = 0; i < fields.size(); i++) {
Field f = (Field)fields.get(i);
String fieldName = fqn == null ? f.getName() : fqn+"/"+f.getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ public void testSchemaPrint() throws Exception {
Assertions.assertNotNull(accountsDef.getDirective("sql"));
Assertions.assertEquals("[SSN]", accountsDef.getDirective("sql").getArgument("keys").getValue().toString());
Assertions.assertEquals("[SSN]", accountsDef.getDirective("sql").getArgument("reference_keys").getValue().toString());

GraphQLFieldDefinition fieldDefinition = objectType.getFieldDefinition("customers");
Assertions.assertEquals("page",fieldDefinition.getArguments().get(0).getName());
Assertions.assertEquals("filter", fieldDefinition.getArguments().get(1).getName());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you expand the test to show to full filter and its arguments

}
}
}