All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.stargate.sgv2.graphql.schema.cqlfirst.dml.DmlSchemaBuilder Maven / Gradle / Ivy

There is a newer version: 2.0.0-ALPHA-17
Show newest version
/*
 * Copyright The Stargate Authors
 *
 * 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 io.stargate.sgv2.graphql.schema.cqlfirst.dml;

import static graphql.Scalars.GraphQLFloat;
import static graphql.Scalars.GraphQLInt;
import static graphql.Scalars.GraphQLString;
import static graphql.schema.GraphQLList.list;

import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.FormatMethod;
import com.google.errorprone.annotations.FormatString;
import graphql.Scalars;
import graphql.introspection.Introspection;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLDirective;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLInputObjectField;
import graphql.schema.GraphQLInputObjectType;
import graphql.schema.GraphQLInputType;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLNonNull;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLTypeReference;
import io.stargate.bridge.proto.QueryOuterClass.ColumnSpec;
import io.stargate.bridge.proto.Schema.CqlKeyspaceDescribe;
import io.stargate.bridge.proto.Schema.CqlTable;
import io.stargate.sgv2.graphql.schema.CassandraFetcher;
import io.stargate.sgv2.graphql.schema.SchemaConstants;
import io.stargate.sgv2.graphql.schema.cqlfirst.dml.fetchers.BulkInsertMutationFetcher;
import io.stargate.sgv2.graphql.schema.cqlfirst.dml.fetchers.DeleteMutationFetcher;
import io.stargate.sgv2.graphql.schema.cqlfirst.dml.fetchers.InsertMutationFetcher;
import io.stargate.sgv2.graphql.schema.cqlfirst.dml.fetchers.QueryFetcher;
import io.stargate.sgv2.graphql.schema.cqlfirst.dml.fetchers.UpdateMutationFetcher;
import io.stargate.sgv2.graphql.schema.cqlfirst.dml.fetchers.aggregations.SupportedGraphqlFunction;
import io.stargate.sgv2.graphql.schema.scalars.CqlScalar;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DmlSchemaBuilder {

  private static final Logger LOG = LoggerFactory.getLogger(DmlSchemaBuilder.class);

  private static final GraphQLInputType MUTATION_OPTIONS =
      GraphQLInputObjectType.newInputObject()
          .name("MutationOptions")
          .description("The execution options for the mutation.")
          .field(
              GraphQLInputObjectField.newInputObjectField()
                  .name("consistency")
                  .type(
                      GraphQLEnumType.newEnum()
                          .name("MutationConsistency")
                          .value("LOCAL_ONE")
                          .value("LOCAL_QUORUM")
                          .value("ALL")
                          .build())
                  .defaultValue(CassandraFetcher.DEFAULT_CONSISTENCY.getValue().name())
                  .build())
          .field(
              GraphQLInputObjectField.newInputObjectField()
                  .name("serialConsistency")
                  .type(
                      GraphQLEnumType.newEnum()
                          .name("SerialConsistency")
                          .value("SERIAL")
                          .value("LOCAL_SERIAL")
                          .build())
                  .defaultValue(CassandraFetcher.DEFAULT_SERIAL_CONSISTENCY.getValue().name())
                  .build())
          .field(
              GraphQLInputObjectField.newInputObjectField()
                  .name("ttl")
                  .type(Scalars.GraphQLInt)
                  .build())
          .build();

  private final CqlKeyspaceDescribe cqlSchema;
  private final String keyspaceName;
  private final List warnings = new ArrayList<>();
  private final FieldInputTypeCache fieldInputTypes;
  private final FieldOutputTypeCache fieldOutputTypes;
  private final FieldFilterInputTypeCache fieldFilterInputTypes;
  private final NameMapping nameMapping;
  private final Map entityResultMap = new HashMap<>();

  /** Describes the different kind of types generated from a table */
  private enum DmlType {
    QueryOutput,
    MutationOutput,
    Input,
    FilterInput,
    Order
  }

  public DmlSchemaBuilder(CqlKeyspaceDescribe cqlSchema) {
    this.cqlSchema = cqlSchema;
    // Note that cqlSchema also contains the keyspace name, but it's the decorated one. We pass the
    // undecorated one all the way from GraphqlCache, because that's what we want to use in the
    // user-facing GraphQL schema.
    this.keyspaceName = cqlSchema.getCqlKeyspace().getName();

    this.nameMapping =
        new NameMapping(cqlSchema.getTablesList(), cqlSchema.getTypesList(), warnings);
    this.fieldInputTypes = new FieldInputTypeCache(this.nameMapping, warnings);
    this.fieldOutputTypes = new FieldOutputTypeCache(this.nameMapping, warnings);
    this.fieldFilterInputTypes =
        new FieldFilterInputTypeCache(this.fieldInputTypes, this.nameMapping);
  }

  public GraphQLSchema build() {
    GraphQLSchema.Builder builder = new GraphQLSchema.Builder();

    List queryFields = new ArrayList<>();
    List mutationFields = new ArrayList<>();

    // Tables must be iterated one at a time. If a table is unfulfillable, it is skipped
    for (CqlTable table : cqlSchema.getTablesList()) {
      if (nameMapping.getGraphqlName(table) == null) {
        // This means there was a name clash. We already added a warning in NameMapping.
        continue;
      }

      try {
        builder.additionalTypes(buildTypesForTable(table));
        queryFields.addAll(buildQuery(table));
        mutationFields.addAll(buildMutations(table));
      } catch (Exception e) {
        warn(e, "Could not convert table %s, skipping", table.getName());
      }
    }

    addAtomicDirective(builder);
    addAsyncDirective(builder);

    builder.additionalType(buildQueryOptionsInputType());

    queryFields.add(buildWarnings());
    builder.query(buildQueries(queryFields));

    if (mutationFields.isEmpty()) {
      GraphQLFieldDefinition emptyMutationField =
          GraphQLFieldDefinition.newFieldDefinition()
              .name("keyspaceEmptyMutation")
              .description("Placeholder mutation that is exposed when a keyspace is empty.")
              .type(Scalars.GraphQLBoolean)
              .dataFetcher((d) -> true)
              .build();
      mutationFields.add(emptyMutationField);
    }
    builder.mutation(buildMutationRoot(mutationFields));

    return builder.build();
  }

  private Set buildTypesForTable(CqlTable table) {
    Set additionalTypes = new HashSet<>();

    additionalTypes.add(buildType(table));
    additionalTypes.add(buildInputType(table));
    additionalTypes.add(buildOrderType(table));
    additionalTypes.add(buildMutationResult(table));
    additionalTypes.add(buildFilterInput(table));
    additionalTypes.add(buildGroupByInput(table));
    return additionalTypes;
  }

  public GraphQLObjectType buildType(CqlTable table) {
    GraphQLObjectType.Builder builder =
        GraphQLObjectType.newObject()
            .name(nameMapping.getGraphqlName(table))
            .description(getTypeDescription(table, DmlType.QueryOutput));
    addToType(table.getPartitionKeyColumnsList(), table, builder);
    addToType(table.getClusteringKeyColumnsList(), table, builder);
    addToType(table.getColumnsList(), table, builder);
    addToType(table.getStaticColumnsList(), table, builder);

    buildAggregationFunctions(builder);

    return builder.build();
  }

  private void addToType(
      List columns, CqlTable table, GraphQLObjectType.Builder builder) {
    for (ColumnSpec column : columns) {
      String graphqlName = nameMapping.getGraphqlName(table, column);
      if (graphqlName != null) {
        try {
          GraphQLFieldDefinition.Builder fieldBuilder =
              new GraphQLFieldDefinition.Builder()
                  .name(graphqlName)
                  .type(fieldOutputTypes.get(column.getType()));
          builder.field(fieldBuilder.build());
        } catch (Exception e) {
          warn(
              e,
              "Could not create output type for column %s in table %s, skipping",
              column.getName(),
              table.getName());
        }
      }
    }
  }

  private String getTypeDescription(CqlTable table, DmlType dmlType) {
    StringBuilder builder = new StringBuilder();
    switch (dmlType) {
      case Input:
        builder.append("The input type");
        break;
      case FilterInput:
        builder.append("The input type used for filtering with non-equality operators");
        break;
      case Order:
        builder.append("The enum used to order a query result based on one or more fields");
        break;
      case QueryOutput:
        builder.append("The type used to represent results of a query");
        break;
      case MutationOutput:
        builder.append("The type used to represent results of a mutation");
        break;
      default:
        builder.append("Type");
        break;
    }

    builder.append(" for the table '");
    builder.append(table.getName());
    builder.append("'.");

    if (dmlType == DmlType.Input || dmlType == DmlType.FilterInput) {
      primaryKeyDescription(table, builder);
    }

    return builder.toString();
  }

  private void primaryKeyDescription(CqlTable table, StringBuilder builder) {
    // Include partition key information that is relevant to making a query
    List primaryKeys =
        Stream.concat(
                table.getPartitionKeyColumnsList().stream(),
                table.getClusteringKeyColumnsList().stream())
            .map(c -> nameMapping.getGraphqlName(table, c))
            .collect(Collectors.toList());
    builder.append("\nNote that ").append("'").append(primaryKeys.get(0)).append("'");
    for (int i = 1; i < primaryKeys.size(); i++) {
      if (i == primaryKeys.size() - 1) {
        builder.append(" and ");
      } else {
        builder.append(", ");
      }
      builder.append("'").append(primaryKeys.get(i)).append("'");
    }
    if (primaryKeys.size() > 1) {
      builder.append(" are the fields that correspond to the table primary key.");
    } else {
      builder.append(" is the field that corresponds to the table primary key.");
    }
  }

  private String primaryKeyDescription(CqlTable table) {
    StringBuilder builder = new StringBuilder();
    primaryKeyDescription(table, builder);
    return builder.toString();
  }

  private void buildAggregationFunctions(GraphQLObjectType.Builder builder) {
    builder.field(buildFunctionField(SupportedGraphqlFunction.INT_FUNCTION, GraphQLInt));
    // The GraphQLFloat corresponds to CQL double
    builder.field(buildFunctionField(SupportedGraphqlFunction.DOUBLE_FUNCTION, GraphQLFloat));
    builder.field(
        buildFunctionField(
            SupportedGraphqlFunction.BIGINT_FUNCTION, CqlScalar.BIGINT.getGraphqlType()));
    builder.field(
        buildFunctionField(
            SupportedGraphqlFunction.DECIMAL_FUNCTION, CqlScalar.DECIMAL.getGraphqlType()));
    builder.field(
        buildFunctionField(
            SupportedGraphqlFunction.VARINT_FUNCTION, CqlScalar.VARINT.getGraphqlType()));
    builder.field(
        buildFunctionField(
            SupportedGraphqlFunction.FLOAT_FUNCTION, CqlScalar.FLOAT.getGraphqlType()));
    builder.field(
        buildFunctionField(
            SupportedGraphqlFunction.SMALLINT_FUNCTION, CqlScalar.SMALLINT.getGraphqlType()));
    builder.field(
        buildFunctionField(
            SupportedGraphqlFunction.TINYINT_FUNCTION, CqlScalar.TINYINT.getGraphqlType()));
  }

  private GraphQLFieldDefinition buildFunctionField(
      SupportedGraphqlFunction graphqlFunction, GraphQLScalarType returnType) {
    return GraphQLFieldDefinition.newFieldDefinition()
        .name(graphqlFunction.getName())
        .description(
            String.format("Invocation of an aggregate function that returns %s.", returnType))
        .argument(
            GraphQLArgument.newArgument()
                .name("name")
                .description("Name of the function to invoke")
                .type(new GraphQLNonNull(GraphQLString)))
        .argument(
            GraphQLArgument.newArgument()
                .name("args")
                .description("Arguments passed to a function. It can be a list of column names.")
                .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString)))))
        .type(returnType)
        .build();
  }

  private GraphQLType buildInputType(CqlTable table) {
    GraphQLInputObjectType.Builder input =
        GraphQLInputObjectType.newInputObject()
            .name(nameMapping.getGraphqlName(table) + "Input")
            .description(getTypeDescription(table, DmlType.Input));

    addToInputType(table.getPartitionKeyColumnsList(), table, input);
    addToInputType(table.getClusteringKeyColumnsList(), table, input);
    addToInputType(table.getColumnsList(), table, input);
    addToInputType(table.getStaticColumnsList(), table, input);
    return input.build();
  }

  private void addToInputType(
      List columns, CqlTable table, GraphQLInputObjectType.Builder input) {
    for (ColumnSpec column : columns) {
      String graphqlName = nameMapping.getGraphqlName(table, column);
      if (graphqlName != null) {
        try {
          GraphQLInputObjectField field =
              GraphQLInputObjectField.newInputObjectField()
                  .name(graphqlName)
                  .type(fieldInputTypes.get(column.getType()))
                  .build();
          input.field(field);
        } catch (Exception e) {
          warn(
              e,
              "Could not create input type for column %s in table %s, skipping",
              column.getName(),
              table.getName());
        }
      }
    }
  }

  private GraphQLType buildOrderType(CqlTable table) {
    GraphQLEnumType.Builder input =
        GraphQLEnumType.newEnum()
            .name(nameMapping.getGraphqlName(table) + "Order")
            .description(getTypeDescription(table, DmlType.Order));

    addToOrderType(table.getPartitionKeyColumnsList(), table, input);
    addToOrderType(table.getClusteringKeyColumnsList(), table, input);
    addToOrderType(table.getColumnsList(), table, input);
    addToOrderType(table.getStaticColumnsList(), table, input);
    return input.build();
  }

  private void addToOrderType(
      List columns, CqlTable table, GraphQLEnumType.Builder input) {
    for (ColumnSpec column : columns) {
      String graphqlName = nameMapping.getGraphqlName(table, column);
      if (graphqlName != null) {
        input.value(graphqlName + "_DESC");
        input.value(graphqlName + "_ASC");
      }
    }
  }

  private GraphQLOutputType buildMutationResult(CqlTable table) {
    return GraphQLObjectType.newObject()
        .name(nameMapping.getGraphqlName(table) + "MutationResult")
        .description(getTypeDescription(table, DmlType.MutationOutput))
        .field(
            GraphQLFieldDefinition.newFieldDefinition()
                .name("applied")
                .type(Scalars.GraphQLBoolean))
        .field(
            GraphQLFieldDefinition.newFieldDefinition()
                .name("accepted")
                .description(
                    String.format(
                        "This field is relevant and fulfilled with data, only when used with the @%s directive",
                        SchemaConstants.ASYNC_DIRECTIVE))
                .type(Scalars.GraphQLBoolean))
        .field(
            GraphQLFieldDefinition.newFieldDefinition()
                .name("value")
                .type(new GraphQLTypeReference(nameMapping.getGraphqlName(table))))
        .build();
  }

  private GraphQLType buildFilterInput(CqlTable table) {
    return GraphQLInputObjectType.newInputObject()
        .name(nameMapping.getGraphqlName(table) + "FilterInput")
        .description(getTypeDescription(table, DmlType.FilterInput))
        .fields(buildFilterInputFields(table))
        .build();
  }

  private List buildFilterInputFields(CqlTable table) {
    List fields = new ArrayList<>();
    addToFilterInputFields(table.getPartitionKeyColumnsList(), table, fields);
    addToFilterInputFields(table.getClusteringKeyColumnsList(), table, fields);
    addToFilterInputFields(table.getColumnsList(), table, fields);
    addToFilterInputFields(table.getStaticColumnsList(), table, fields);
    return fields;
  }

  private void addToFilterInputFields(
      List columns, CqlTable table, List fields) {
    for (ColumnSpec column : columns) {
      String graphqlName = nameMapping.getGraphqlName(table, column);
      if (graphqlName != null) {
        try {
          fields.add(
              GraphQLInputObjectField.newInputObjectField()
                  .name(graphqlName)
                  .type(fieldFilterInputTypes.get(column.getType()))
                  .build());
        } catch (Exception e) {
          warn(
              e,
              "Could not create filter input type for column %s in table %s, skipping",
              column.getName(),
              table.getName());
        }
      }
    }
  }

  private GraphQLType buildGroupByInput(CqlTable table) {
    GraphQLInputObjectType.Builder builder =
        GraphQLInputObjectType.newInputObject()
            .name(nameMapping.getGraphqlName(table) + "GroupByInput");
    addToGroupByInput(table.getPartitionKeyColumnsList(), table, builder);
    addToGroupByInput(table.getClusteringKeyColumnsList(), table, builder);
    return builder.build();
  }

  private void addToGroupByInput(
      List columns, CqlTable table, GraphQLInputObjectType.Builder builder) {
    for (ColumnSpec column : columns) {
      String graphqlName = nameMapping.getGraphqlName(table, column);
      if (graphqlName != null) {
        builder.field(
            GraphQLInputObjectField.newInputObjectField()
                .name(graphqlName)
                .type(Scalars.GraphQLBoolean)
                .build());
      }
    }
  }

  private List buildQuery(CqlTable table) {
    String graphqlName = nameMapping.getGraphqlName(table);
    GraphQLFieldDefinition query =
        GraphQLFieldDefinition.newFieldDefinition()
            .name(graphqlName)
            .description(
                String.format(
                    "Query for the table '%s'.%s", table.getName(), primaryKeyDescription(table)))
            .argument(
                GraphQLArgument.newArgument()
                    .name("value")
                    .type(new GraphQLTypeReference(graphqlName + "Input")))
            .argument(
                GraphQLArgument.newArgument()
                    .name("filter")
                    .type(new GraphQLTypeReference(graphqlName + "FilterInput")))
            .argument(
                GraphQLArgument.newArgument()
                    .name("orderBy")
                    .type(new GraphQLList(new GraphQLTypeReference(graphqlName + "Order"))))
            .argument(
                GraphQLArgument.newArgument()
                    .name("groupBy")
                    .description("The columns to group results by.")
                    .type(new GraphQLTypeReference(graphqlName + "GroupByInput")))
            .argument(
                GraphQLArgument.newArgument()
                    .name("options")
                    .type(new GraphQLTypeReference("QueryOptions")))
            .type(buildEntityResultOutput(table))
            .dataFetcher(new QueryFetcher(keyspaceName, table, nameMapping))
            .build();

    GraphQLFieldDefinition filterQuery =
        GraphQLFieldDefinition.newFieldDefinition()
            .name(graphqlName + "Filter")
            .deprecate("No longer supported. Use root type instead.")
            .argument(
                GraphQLArgument.newArgument()
                    .name("filter")
                    .type(new GraphQLTypeReference(graphqlName + "FilterInput")))
            .argument(
                GraphQLArgument.newArgument()
                    .name("orderBy")
                    .type(new GraphQLList(new GraphQLTypeReference(graphqlName + "Order"))))
            .argument(
                GraphQLArgument.newArgument()
                    .name("options")
                    .type(new GraphQLTypeReference("QueryOptions")))
            .type(buildEntityResultOutput(table))
            .dataFetcher(new QueryFetcher(keyspaceName, table, nameMapping))
            .build();

    return ImmutableList.of(query, filterQuery);
  }

  private GraphQLOutputType buildEntityResultOutput(CqlTable table) {
    if (entityResultMap.containsKey(table)) {
      return entityResultMap.get(table);
    }

    GraphQLOutputType entityResultType =
        GraphQLObjectType.newObject()
            .name(nameMapping.getGraphqlName(table) + "Result")
            .field(
                GraphQLFieldDefinition.newFieldDefinition().name("pageState").type(GraphQLString))
            .field(
                GraphQLFieldDefinition.newFieldDefinition()
                    .name("values")
                    .type(
                        new GraphQLList(
                            new GraphQLNonNull(
                                new GraphQLTypeReference(nameMapping.getGraphqlName(table))))))
            .build();

    entityResultMap.put(table, entityResultType);

    return entityResultType;
  }

  private GraphQLFieldDefinition buildWarnings() {
    StringBuilder description =
        new StringBuilder("Warnings encountered during the CQL to GraphQL conversion.");
    if (warnings.isEmpty()) {
      description.append("\nNo warnings found, this will return an empty list.");
    } else {
      description.append("\nThis will return:");
      for (String warning : warnings) {
        description.append("\n- ").append(warning);
      }
    }
    return GraphQLFieldDefinition.newFieldDefinition()
        .name("conversionWarnings")
        .description(description.toString())
        .type(list(GraphQLString))
        .dataFetcher((d) -> warnings)
        .build();
  }

  private GraphQLType buildQueryOptionsInputType() {
    return GraphQLInputObjectType.newInputObject()
        .name("QueryOptions")
        .description("The execution options for the query.")
        .field(
            GraphQLInputObjectField.newInputObjectField()
                .name("consistency")
                .type(
                    GraphQLEnumType.newEnum()
                        .name("QueryConsistency")
                        .value("LOCAL_ONE")
                        .value("LOCAL_QUORUM")
                        .value("ALL")
                        .value("SERIAL")
                        .value("LOCAL_SERIAL")
                        .build())
                .defaultValue(CassandraFetcher.DEFAULT_CONSISTENCY.getValue().name())
                .build())
        .field(
            GraphQLInputObjectField.newInputObjectField()
                .name("limit")
                .type(Scalars.GraphQLInt)
                .build())
        .field(
            GraphQLInputObjectField.newInputObjectField()
                .name("pageSize")
                .type(Scalars.GraphQLInt)
                .defaultValue(CassandraFetcher.DEFAULT_PAGE_SIZE)
                .build())
        .field(
            GraphQLInputObjectField.newInputObjectField()
                .name("pageState")
                .type(Scalars.GraphQLString)
                .build())
        .build();
  }

  private GraphQLObjectType buildQueries(List queryFields) {
    GraphQLObjectType.Builder builder = GraphQLObjectType.newObject().name("Query");

    for (GraphQLFieldDefinition fieldDefinition : queryFields) {
      builder.field(fieldDefinition);
    }

    return builder.build();
  }

  private List buildMutations(CqlTable table) {
    List mutationFields = new ArrayList<>();
    mutationFields.add(buildDelete(table));
    mutationFields.add(buildInsert(table));
    mutationFields.add(buildBulkInsert(table));
    mutationFields.add(buildUpdate(table));
    return mutationFields;
  }

  private GraphQLFieldDefinition buildDelete(CqlTable table) {
    return GraphQLFieldDefinition.newFieldDefinition()
        .name("delete" + nameMapping.getGraphqlName(table))
        .description(
            String.format(
                "Delete mutation for the table '%s'.%s",
                table.getName(), primaryKeyDescription(table)))
        .argument(
            GraphQLArgument.newArgument()
                .name("value")
                .type(
                    new GraphQLNonNull(
                        new GraphQLTypeReference(nameMapping.getGraphqlName(table) + "Input"))))
        .argument(GraphQLArgument.newArgument().name("ifExists").type(Scalars.GraphQLBoolean))
        .argument(
            GraphQLArgument.newArgument()
                .name("ifCondition")
                .type(new GraphQLTypeReference(nameMapping.getGraphqlName(table) + "FilterInput")))
        .argument(GraphQLArgument.newArgument().name("options").type(MUTATION_OPTIONS))
        .type(new GraphQLTypeReference(nameMapping.getGraphqlName(table) + "MutationResult"))
        .dataFetcher(new DeleteMutationFetcher(keyspaceName, table, nameMapping))
        .build();
  }

  private GraphQLFieldDefinition buildInsert(CqlTable table) {
    return GraphQLFieldDefinition.newFieldDefinition()
        .name("insert" + nameMapping.getGraphqlName(table))
        .description(
            String.format(
                "Insert mutation for the table '%s'.%s",
                table.getName(), primaryKeyDescription(table)))
        .argument(
            GraphQLArgument.newArgument()
                .name("value")
                .type(
                    new GraphQLNonNull(
                        new GraphQLTypeReference(nameMapping.getGraphqlName(table) + "Input"))))
        .argument(GraphQLArgument.newArgument().name("ifNotExists").type(Scalars.GraphQLBoolean))
        .argument(GraphQLArgument.newArgument().name("options").type(MUTATION_OPTIONS))
        .type(new GraphQLTypeReference(nameMapping.getGraphqlName(table) + "MutationResult"))
        .dataFetcher(new InsertMutationFetcher(keyspaceName, table, nameMapping))
        .build();
  }

  private GraphQLFieldDefinition buildBulkInsert(CqlTable table) {
    return GraphQLFieldDefinition.newFieldDefinition()
        .name("bulkInsert" + nameMapping.getGraphqlName(table))
        .description(
            String.format(
                "Bulk insert mutations for the table '%s'.%s",
                table.getName(), primaryKeyDescription(table)))
        .argument(
            GraphQLArgument.newArgument()
                .name("values")
                .type(
                    new GraphQLList(
                        new GraphQLNonNull(
                            new GraphQLTypeReference(
                                nameMapping.getGraphqlName(table) + "Input")))))
        .argument(GraphQLArgument.newArgument().name("ifNotExists").type(Scalars.GraphQLBoolean))
        .argument(GraphQLArgument.newArgument().name("options").type(MUTATION_OPTIONS))
        .type(
            new GraphQLList(
                new GraphQLTypeReference(nameMapping.getGraphqlName(table) + "MutationResult")))
        .dataFetcher(new BulkInsertMutationFetcher(keyspaceName, table, nameMapping))
        .build();
  }

  private GraphQLFieldDefinition buildUpdate(CqlTable table) {
    return GraphQLFieldDefinition.newFieldDefinition()
        .name("update" + nameMapping.getGraphqlName(table))
        .description(
            String.format(
                "Update mutation for the table '%s'.%s",
                table.getName(), primaryKeyDescription(table)))
        .argument(
            GraphQLArgument.newArgument()
                .name("value")
                .type(
                    new GraphQLNonNull(
                        new GraphQLTypeReference(nameMapping.getGraphqlName(table) + "Input"))))
        .argument(GraphQLArgument.newArgument().name("ifExists").type(Scalars.GraphQLBoolean))
        .argument(
            GraphQLArgument.newArgument()
                .name("ifCondition")
                .type(new GraphQLTypeReference(nameMapping.getGraphqlName(table) + "FilterInput")))
        .argument(GraphQLArgument.newArgument().name("options").type(MUTATION_OPTIONS))
        .type(new GraphQLTypeReference(nameMapping.getGraphqlName(table) + "MutationResult"))
        .dataFetcher(new UpdateMutationFetcher(keyspaceName, table, nameMapping))
        .build();
  }

  private void addAtomicDirective(GraphQLSchema.Builder builder) {
    builder.additionalDirective(
        GraphQLDirective.newDirective()
            .validLocation(Introspection.DirectiveLocation.MUTATION)
            .name(SchemaConstants.ATOMIC_DIRECTIVE)
            .description("Instructs the server to apply the mutations in a LOGGED batch")
            .build());
  }

  private void addAsyncDirective(GraphQLSchema.Builder builder) {
    builder.additionalDirective(
        GraphQLDirective.newDirective()
            .validLocation(Introspection.DirectiveLocation.MUTATION)
            .name(SchemaConstants.ASYNC_DIRECTIVE)
            .description(
                "Instructs the server to apply the mutations asynchronously without waiting for the result.")
            .build());
  }

  private GraphQLObjectType buildMutationRoot(List mutationFields) {
    GraphQLObjectType.Builder builder = GraphQLObjectType.newObject().name("Mutation");
    for (GraphQLFieldDefinition mutation : mutationFields) {
      builder.field(mutation);
    }
    return builder.build();
  }

  @FormatMethod
  private void warn(Exception e, @FormatString String format, Object... arguments) {
    String message = String.format(format, arguments);
    warnings.add(message + " (" + e.getMessage() + ")");
    if (!(e instanceof SchemaWarningException)) {
      LOG.warn(message, e);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy