Developing with the Stargate GraphQL API (Schema-first)

Stargate is a data gateway deployed between client applications and a database.

The GraphQL API modifies and queries your table data using GraphQL object types, queries, and mutations.

The schema-first approach allows you to create idiomatic GraphQL types, mutations, and queries in a manner familiar to GraphQL developers. The schema is deployed and can be updated by deploying a new schema without recreating the tables and columns directly. Under the covers, this approach will create and modify the CQL tables to match the GraphQL types. The schema can be modified for CQL decorated schema, such as specifying table and column names in Cassandra, while retaining GraphQL-centric names for the types that correspond. If you are a GraphQL developer, this approach is for you.

For more information about the GraphQL API, see the blog post on the GraphQL API.

Prerequisites

If you are looking to just get started, DataStax Astra Database-as-a-Service can get you started with no install steps.

  • Install cURL, a utility for running REST, Document, or GraphQL queries on the command line.

  • [Optional] If you prefer, you can use Postman as a client interface for exploring the APIs

    • You will also find links to downloadable collections and environments in Using Postman

  • [Optional] If you going to use the GraphQL API, you will want to use the GraphQL Playground to deploy schema and execute mutations and queries.

  • [Optional] For the REST and Document APIs, you can use the Swagger UI.

  • Install Docker for Desktop

  • Pull a Stargate Docker image

  • Cassandra 4.0

  • Cassandra 3.x

  • DSE 6.8

v2

For Stargate v2, you’ll need to pull an image for coordinator, plus an image for each API that you wish to run: restapi, graphql, and docsapi. The coordinator image contains a Apache Cassandra backend, the Cassandra Query Language (CQL), and the gRPC API.

The following are the commands for each of those images using the tag v2:

docker pull stargateio/coordinator-4_0:v2
docker pull stargateio/restapi:v2
docker pull stargateio/docsapi:v2
docker pull stargateio/graphqlapi:v2
v1

This image contains the Cassandra Query Language (CQL), REST, Document, GraphQL APIs, and GraphQL Playground, along with an Apache Cassandra 4.0 backend.

docker pull stargateio/stargate-4_0:v1.0.57
v2

For Stargate v2, you’ll need to pull an image for coordinator, plus an image for each API that you wish to run: restapi, graphql, and docsapi. The coordinator image contains a Apache Cassandra backend, the Cassandra Query Language (CQL), and the gRPC API.

The following are the commands for each of those images using the tag v2:

docker pull stargateio/coordinator-3_11:v2
docker pull stargateio/restapi:v2
docker pull stargateio/docsapi:v2
docker pull stargateio/graphqlapi:v2
v1

This image contains the Cassandra Query Language (CQL), REST, Document, GraphQL APIs, and GraphQL Playground, along with an Apache Cassandra 3.11 backend.

docker pull stargateio/stargate-3_11:v1.0.57
v2

For Stargate v2, you’ll need to pull an image for coordinator, plus an image for each API that you wish to run: restapi, graphql, and docsapi. The coordinator image contains a Apache Cassandra backend, the Cassandra Query Language (CQL), and the gRPC API.

The following are the commands for each of those images using the tag v2:

docker pull stargateio/coordinator-68:v2
docker pull stargateio/restapi:v2
docker pull stargateio/docsapi:v2
docker pull stargateio/graphqlapi:v2
v1

This image contains the Cassandra Query Language (CQL), REST, Document, GraphQL APIs, and GraphQL Playground, along with a DataStax Enterprise 6.8 backend.

docker pull stargateio/stargate-dse-68:v1.0.57
  • Run the Stargate Docker image

  • Cassandra 4.0

  • Cassandra 3.x

  • DSE 6.8

v2

Use this docker-compose shell script to start the coordinator and APIs in developer mode. The easiest way to do that is to navigate to the <install_location>/stargate/docker-compose directory, and run the script. You will want to run, for example:

./start_cass_4_0_dev_mode.sh

This command will start using the latest available coordinator and API images with the v2 tag.

You may also select a specific image tag using the -t <image_tag> option. A list of the available tags for the coordinator can be found here.

v1

Start the Stargate container in developer mode. Developer mode removes the need to set up a separate Cassandra instance and is meant for development and testing only.

docker run --name stargate \
  -p 8080:8080 \
  -p 8081:8081 \
  -p 8082:8082 \
  -p 127.0.0.1:9042:9042 \
  -d \
  -e CLUSTER_NAME=stargate \
  -e CLUSTER_VERSION=4.0 \
  -e DEVELOPER_MODE=true \
  stargateio/stargate-4_0:v1.0.57
v2

Use this docker-compose shell script to start the coordinator and APIs in developer mode. The easiest way to do that is to navigate to the <install_location>/stargate/docker-compose directory, and run the script. You will want to run, for example:

./start_cass_3_11_dev_mode.sh

This command will start using the latest available coordinator and API images with the v2 tag.

You may also select a specific image tag using the -t <image_tag> option. A list of the available tags for the coordinator can be found here.

v1

Start the Stargate container in developer mode. Developer mode removes the need to set up a separate Cassandra instance and is meant for development and testing only.

docker run --name stargate \
  -p 8080:8080 \
  -p 8081:8081 \
  -p 8082:8082 \
  -p 127.0.0.1:9042:9042 \
  -d \
  -e CLUSTER_NAME=stargate \
  -e CLUSTER_VERSION=3.11 \
  -e DEVELOPER_MODE=true \
  stargateio/stargate-3_11:v1.0.57
v2

Use this docker-compose shell script to start the coordinator and APIs in developer mode. The easiest way to do that is to navigate to the <install_location>/stargate/docker-compose directory, and run the script. You will want to run, for example:

./start_dse_68_dev_mode.sh

This command will start using the latest available coordinator and API images with the v2 tag.

You may also select a specific image tag using the -t <image_tag> option. A list of the available tags for the coordinator can be found here.

v1

Start the Stargate container in developer mode. Developer mode removes the need to set up a separate DSE instance and is meant for development and testing only.

docker run --name stargate \
  -p 8080:8080 \
  -p 8081:8081 \
  -p 8082:8082 \
  -p 127.0.0.1:9042:9042 \
  -d \
  -e CLUSTER_NAME=stargate \
  -e CLUSTER_VERSION=6.8 \
  -e DEVELOPER_MODE=true \
  stargateio/stargate-dse-68:v1.0.57

About the GraphQL API endpoints

There are three Stargate GraphQL API endpoints, one for creating schema in schema-first, one for deploying schema in the graphql-first, and the third for querying or mutating a keyspace. The URLS are:

The schema endpoint is used to create or alter CQL schema in the schema-first GraphQL using direct schema manipulation. The admin endpoint is used to deploy GraphQL schema in the graphql-first GraphQL which will indirectly modify underlying CQL schema. The querying endpoint is constructed in the same manner for both graphql-first and schema-first.

Each request must have a valid application token. Each request can also have an optional unique request id. The request id is recommended in a production environment and can be useful in troubleshooting issues.

Generating UUIDs Consider using a tool like this online UUID generator to quickly create a random UUID to pass with your requests if you are submitting the queries manually using a tool like cURL.

Naming conventions for GraphQL

The GraphQL API uses specific naming conventions to preserve capitalization and special characters. Note that if typical GraphQL naming conventions are used, such as camelCase, that the underlying Cassandra storage tables will use double quoting to preserve the capitalization. If a naming conflict occurs, an error results that the table already exists.

Table 1. GraphQL naming convention
GraphQL table name CQL table name GraphQL mutation format

foo

foo

insertfoo

Foo

"Foo"

insertFoo

foo_bar

foo_bar

insertfoo_bar

FooBar

"FooBar"

insertFooBar

Hellox21_

"Hello!"

insertHellox21_

Using the GraphQL Playground

The easiest way to get started with GraphQL is to use the built-in GraphQL playground. In Stargate, go to your browser and launch the url: http://localhost:8080/playground

Add your application token to the HTTP HEADERS section at the lower lefthand corner of the GraphQL Playground window:

{"x-cassandra-token":"$AUTH_TOKEN"}

Once in the playground, you can create new schema and interact with the GraphQL APIs. The server paths are structured to provide access to creating and querying schema, as well as querying and modifying Cassandra data:

  • /graphql-schema

    • An API for exploring and creating schema, or Data Definition Language (DDL). For example, Stargate has queries to create, modify, or drop tables, such as createTable, or dropTable.

  • /graphql/<keyspace>

    • An API for querying and modifying your tables using GraphQL fields. Generally, you will start the playground with /graphql-schema to create some schema.

Create or delete schema

In order to use the GraphQL API, you must deploy schema that defines the types, mutations, and queries.

Create a keyspace

Before you can start using the GraphQL API, you must first create a Cassandra keyspace in your database. The keyspace is a container that stores data with replication.

Inside the GraphQL playground, navigate to http://localhost:8080/graphql-schema and create a keyspace by executing the following mutation:

# create a keyspace called library
mutation createKsLibrary {
  createKeyspace(name:"library", replicas: 1)
}

For each keyspace created in your Cassandra schema, a new path is created under the graphql-path root (default is: /graphql). For example, the mutation just executed creates a path /graphql/library for the library keyspace when Cassandra creates the keyspace.

Add the auth token to the HTTP Headers box in the lower lefthand corner:

{
  "X-Cassandra-Token":"bff43799-4682-4375-99e8-23c8a9d0f304"
}

Notice that the key for this JSON token is different than the value that the generate token has. It is X-Cassandra-Token, not auth-token.

Now run the mutation to create the keyspace. You should see a return value of:

{
  "data": {
    "createKeyspace": true
  }
}

Delete a keyspace

You can delete a keyspace. All tables and table data will be deleted along with the keyspace schema.

mutation dropKsLibrary {
  dropKeyspace(name:"library", ifExists: true)
}

A note about what schema is

A full GraphQL schema that will be deployed can include user-defined types (UDTs), object types, indexes, queries, mutations, and payload types. The next sections describes the definition of these items before discussing how to deploy the schema once created.

Do you want to understand deployment first? Read Deploy Schema.

Data types

A column’s CQL data type is inferred from the GraphQL field type. GraphQL’s built-in scalar types are mapped:

GraphQL CQL

ID

uuid

String

varchar

Int

int

Float

double

Boolean

boolean

In addition, Stargate provides a set of custom scalar types that map directly to the CQL types of the same name: Uuid, TimeUuid, Inet, Date, Duration, BigInt, Counter, Ascii, Decimal, Varint, Float32, Blob, SmallInt, TinyInt, Timestamp, Time.

Sets and lists are defined by custom typed arrays. Maps and tuples are not supported, but UDTs can be used to create the same functionality.

In GraphQL syntax, square brackets denote an array, such as email: [String] defines a field named email that is an array of String values. Additionally, if an exclamation point follows a data type, such as String!, then the field cannot contain a null value.

Create object types

The most basic components of a GraphQL schema are object types, which just represent a kind of object you can fetch from your service, and specify the fields contained in the object. Object types also have directives, which are identifiers preceded by an @ character. Directives are GraphQL server-specific, and implemented in the server. Stargate implements directives that are specific to the Apache Cassandra backend, as well as Apollo data federation-specific directives that allow Stargate to work with the Apollo gateway.

  • Book type

  • Reader type

type Book @key @cql_entity(name: "book") @cql_input { (1) (2) (3)
 title: String! @cql_column(partitionKey: true, name: "book_title") (4) (5)
 isbn: String @cql_column(clusteringOrder: ASC) (6)
 author: [String] @cql_index(name: "author_idx", target: VALUES) (7)
}
type Reader @key @cql_entity(name: "reader") @cql_input {
 name: String! @cql_column(partitionKey: true)
 user_id: Uuid! @cql_column(clusteringOrder: ASC)
 birthdate: Date @cql_index(name: "date_idx") (8)
 email: [String] @cql_column(typeHint: "set<varchar>") (9)
 reviews: [Review] @cql_index(name: "review_idx", target: VALUES)
 address: [Address]
}

Directives in example:

1 Book: @key defines the fields that uniquely identify an object. Stargate assigns primary key fields automatically as @key fields. For other servers, the fields must be identified in a string, such as @key(fields: "title isbn"). Required for Apollo data federation.
2 Book: @cql_entity(name: "book") defines an alternate table name for the underlying database. Cassandra, Astra DB, and DataStax Enterprise all require double-quotes around table names that are CamelCase. Changing the name to lowercase makes the object name both GraphQL-friendly (Book) and CQL-friendly (book). Not required.
3 Book: @cql_input generates a BookInput type with the same fields as the type Book in the GraphQL schema, without duplicating the object type in the schema. Not required but strongly suggested to make writing queries and mutations easier.
4 Book: @cql_column(partitionKey: true) sets the field to a partition key in the underlying database table. This directive must be used for each field that comprises the partition key. IMPORTANT: If no field has a partitionKey: true, but the first field is of data type ID, Uuid, or TimeUuid, then that field is used as the partition key in the database. Information about partition keys and clustering keys can be found in the CQL reference.
5 Book: @cql_column(partitionKey: true, name: "book_title") also sets the field name to an alternate value that is CQL-friendly. Note the comma-separated items in @cql_column. Column renaming is not required.
6 Book: @cql_column(clusteringOrder: ASC) sets the field to a clustering column in the underlying database table. This directive must be used for each field that is a clustering column. The default clustering order is ASC, or ascending. Not required.
7 Book: @cql_index(name: "author_idx", target: VALUES) creates an index of the list of author names, using the values listed in the array. Note that the parameter target is required for arrays (set, list). Required if queries will use the field as a parameter to retrieve data.
8 Reader: @cql_index(name: "date_idx") creates an index of the birthdates. Note that no target is required. The @cql_index is discussed in Create indexes. Not required.
9 Reader: @cql_column(typeHint: "set<varchar>") sets the column data type to a set. The only valid defaults are set or making a CQL type frozen. Required if the field is a set or list.

The fields author, reviews, address are defined as arrays by the addition of square brackets, reviews: [Review]. Note that reviews and addresses are arrays of UDTs whereas author is a list of Strings to name each author.

Create an index

Cassandra supports indexing any regular, non-primary key fields in an object type. Any field designated as a partition key or clustering column cannot be indexed, unless DataStax Enterprise is the defined database. A field will be indexed if @cql_index is added to the field definition. Indexed fields can be used as parameters in queries.

  • Reader type

type Reader @key @cql_entity(name: "reader") @cql_input {
 name: String! @cql_column(partitionKey: true)
 user_id: Uuid! @cql_column(clusteringOrder: ASC)
 birthdate: Date @cql_index(name: "date_idx")
 email: [String] @cql_column(typeHint: "set<varchar>")
 reviews: [Review] @cql_index(name: "review_idx", target: FULL)
 address: [Address]
}

The directive @cql_index has the following optional arguments:

Argument

Default

Description

name

Generated

Custom index name.

class

Secondary index

Custom index class, such as SAI.

target

VALUES

Specifies set and list index type. Options are FULL and VALUES.

options

N/A

Any options to pass to the underlying index query.

Create a user-defined type (UDT)

User-defined types (UDTs) can be created as GraphQL object types. UDTs are optional, but if you wish to use a UDT in another object type definition, you’ll want to create the UDT first. Once created, you can include the type in the schema deployed via the GraphQL playground. Here are two examples that create a UDT called Address that includes a street, city, state, and zipcode, and a UDT called Review that includes a book title, comment, rating and review data.

  • Address UDT

  • Review UDT

type Address @cql_entity(target: UDT) @cql_input { (1)
 street: String
 city: String
 state: String
 zipCode: String @cql_column(name: "zip_code")
}
type Review @cql_entity(target: UDT) @cql_input {
 bookTitle: String @cql_column(name: "book_title")
 comment: String
 rating: Int
 reviewDate: Date @cql_column(name: "review_date")
}

Directives in example:

1 The @cql_entity(target: UDT) denotes that the type is stored as a user-defined type (UDT) in the underlying database table.

Create queries

Most types in your schema will just be normal object types, but there are two types that are special, the Query type and the Mutation type. Every GraphQL service has at least one query type. Mutation types are optional.

Queries

  • Queries

type Query {
 bookByTitleAndIsbn(title:String!, isbn:String): [Book] (1)
 readerByNameAndUserid(name:String!, user_id:Uuid): [Reader] (2)
}
1 This query is named bookByTitleAndIsbn, with inputs title (non-null String value) and isbn (String value), and outputs an array of objects of type Book.
2 This query is named readerByNameAndUserid, with inputs name (non-null String value) and user_id (Uuid value), and outputs an array of objects of type Reader.

Create mutations

Mutations work in a similar way to queries. An input and output is defined.

  • Mutations

type Mutation {
  insertBook(book:BookInput!): Book  (1)
  updateBook(book:BookInput!): Boolean @cql_update (2) (3)
  deleteBook(book:BookInput!): Boolean
  insertReader(reader:ReaderInput!): Reader
  deleteReader(reader:ReaderInput!): Boolean
  insertLibCollection(libColl: LibCollectionInput!): LibCollection
  deleteLibCollection(libColl: LibCollectionInput!): Boolean
}
1 An input of BookInput! is required, and must include the required fields as defined by the Book object type. The output is a Book object.
2 An input of BookInput! is required, and must include the required fields as defined by the Book object type. The output is a Boolean value.
3 The @cql_update directive makes this mutation an update, rather than an insert.

The insert mutation will insert an object of the specified type, and the update and delete mutations will return a Boolean value. If the update or delete mutation names begin with those words, the @cql_update directive is not required.

Conditional insertion

A mutation can be turned into a conditional insertion operation by ending the mutation name with ifNotExists. However, it may be useful to know if the insertion is successfully applied. In that case, see payload type below to see how a boolean field applied can be added to the response from insertions or annotating it with @cql_insert(ifNotExists: true).

Create payload types

Payloads types are not mapped to the database, and only serve as wrappers of an operation’s response. They typically pass a more complex object than just an entity. For example, you can add fields that check the applied status for a conditional query, or the pagingState.

In this example, a payload type SelectBookResult is created that accepts an array of books as the input. The associated query can use the required title and optional pagingState as input, and will return an array of books along with the pagingState.

  • Payload type

type SelectBookResult @cql_payload { (1)
  data: [Book]
  pagingState: String
}
type Query {
  books(
    title: String!,
    pagingState: String @cql_pagingState (2)
  ): SelectBookResult @cql_select(pageSize: 10) (3)
}

Directives in example:

1 The @cql_payload directive defines the type as a payload.
2 The @cql_pagingState directive defines the fields as a storing a paging state.
3 The @cql_select(pageSize: 10) directive defines how many results to return by page if the query is paginated, with an Integer input. Another parameter is also available, limit: 10, which is the maximum total number of results to return, with an Integer input.

An example of retrieving data using this query is found below.

Often, you wish to know if an operation was successful. Creating a payload type that uses a boolean field applied can definitively answer if a conditional operation completes correctly.

Create a payload type that uses your standard object type as a field, along with applied:

  • Payload type with conditional

type Book @key @cql_entity(name: "book") @cql_input {
 title: String! @cql_column(partitionKey: true)
 isbn: String @cql_column(clusteringOrder: ASC)
 author: [String] @cql_index(name: "author_idx", target: VALUES)
}

type InsertBookResponse @cql_payload {
  applied: Boolean!
  book: Book!
}
type Mutation {
  insertBookIfNotExists(book: BookInput!): InsertBookResponse
}

If the conditional insert is successful (the row did not exist), then applied will be true and book will echo back the data that was just inserted; otherwise, applied will be false and book will be the existing data.

Deploy or undeploy schema

Deploy schema manually

Now that you have created GraphQL types, queries, and mutations, it’s time to deploy the schema. Recall that the corresponding CQL schema is inferred and created from the GraphQL schema submitted.

A keyspace will be created as CQL-first unless a schema is deployed. After a schema is deployed, the keyspace should be accessed as schema-first.

Inside the GraphQL playground, navigate to http://localhost:8080/graphql-admin and create the schema to deploy to a previously defined keyspace:

  • Simple graphQL schema command

  • Simple result

  • Full schema command

mutation {
  deploySchema(
    keyspace: "library"
    expectedVersion: "1da4f190-b7fd-11eb-8258-1ff1380eaff5"
    schema: """
    # Stargate does not require definition of fields in @key,
    # it uses the primary key
    type Book @key @cql_entity(name: "book") @cql_input {
      title: String! @cql_column(partitionKey: true, name: "book_title")
      isbn: String @cql_column(clusteringOrder: ASC)
      author: [String] @cql_index(name: "author_idx", target: VALUES)
    }

    type SelectBookResult @cql_payload {
      data: [Book]
      pagingState: String
    }

    type InsertBookResponse @cql_payload {
      applied: Boolean!
      book: Book!
    }

    type Query {
      # books by partition key
      bookByTitle(title: String!): [Book]
      # books by partition key + clustering column (primary key)
      bookByTitleAndIsbn( title: String!, isbn: String): [Book]
      # books by indexed column author
      bookByAuthor(
        author: String @cql_where(field: "author", predicate: CONTAINS)
      ): [Book]
      # books by partition key + indexed column author
      bookByTitleAndAuthor(title: String!, author: String @cql_where(field: "author", predicate: CONTAINS)
      ): [Book]
      booksWithPaging(
        title: String!,
        pagingState: String @cql_pagingState
      ): SelectBookResult @cql_select(pageSize: 10)
      # books by partition key WHERE title is IN a list
      booksIn(title: [String] @cql_where(field: "title", predicate: IN)
      ): [Book]
      # books by author WHERE author is CONTAINED in the author array (list)
      booksContainAuthor(author: String @cql_where(field: "author", predicate: CONTAINS)
      ): [Book]
      bookGT(
        title: String
        isbn: String @cql_where(field: "isbn", predicate: GT)
      ): [Book]
      bookLT(
        title: String
        isbn: String @cql_where(field: "isbn", predicate: LT)
      ): [Book]
    }
    type Mutation {
      insertBook(book: BookInput!): Book
      updateBook(book: BookInput): Boolean @cql_update
      deleteBook(book: BookInput!): Boolean
    }
    """
  ) {
    version
    cqlChanges
  }
}
{
  "data": {
    "deploySchema": {
      "version": "4adc2e30-9e53-11eb-8fde-b341b9f82ca9",
      "cqlChanges": [
        "No changes, the CQL schema is up to date"
      ]
    }
  }
}
mutation {
  deploySchema(
    keyspace: "library"
    expectedVersion: "1da4f190-b7fd-11eb-8258-1ff1380eaff5"
    schema: """
    type Address @cql_entity(target: UDT) @cql_input {
          street: String
          city: String
          state: String
          zipCode: String @cql_column(name: "zip_code")
    }
    type Review @cql_entity(target: UDT) @cql_input {
      bookTitle: String @cql_column(name: "book_title")
      comment: String
      rating: Int
      reviewDate: Date @cql_column(name: "review_date")
    }
    # Stargate does not require definition of fields in @key,
    # it uses the primary key
    type Book @key @cql_entity(name: "book") @cql_input {
      title: String! @cql_column(partitionKey: true, name: "book_title")
      isbn: String @cql_column(clusteringOrder: ASC)
      author: [String] @cql_index(name: "author_idx", target: VALUES)
    }

    type BookI @key @cql_entity(name: "booki") @cql_input {
      isbn: String! @cql_column(partitionKey: true)
      title: String @cql_column(clusteringOrder: ASC, name: "book_title")
      author: [String] @cql_index(name: "authori_idx", target: VALUES)
    }

    type SelectBookResult @cql_payload {
      data: [Book]
      pagingState: String
    }

    type InsertBookResponse @cql_payload {
      applied: Boolean!
      book: Book!
    }

    type Reader @key @cql_entity(name: "reader") @cql_input {
      name: String! @cql_column(partitionKey: true)
      user_id: Uuid! @cql_column(clusteringOrder: ASC)
      birthdate: Date @cql_index(name: "date_idx")
      email: [String] @cql_column(typeHint: "set<varchar>")
      reviews: [Review]  @cql_index(name: "review_idx", target: VALUES)
      address: [Address]
    }

    type ReaderU @key @cql_entity(name: "readeru") @cql_input {
      user_id: Uuid! @cql_column(partitionKey: true)
      name: String! @cql_column(clusteringOrder: ASC)
      birthdate: Date @cql_index(name: "dateu_idx")
      email: [String] @cql_column(typeHint: "set<varchar>")
      reviews: [Review]  @cql_index(name: "reviewu_idx", target: VALUES)
      address: [Address]
    }

    type LibCollection @key @cql_entity(name: "lib_collection") @cql_input {
      type: String! @cql_column(partitionKey: true)
      lib_id: Int! @cql_column(partitionKey: true)
      lib_name: String @cql_column(clusteringOrder: ASC)
    }

    type Query {
      # books by partition key
      bookByTitle(title: String!): [Book]
      # books by partition key + clustering column (primary key)
      bookByTitleAndIsbn( title: String!, isbn: String): [Book]
      # books by indexed column author
      bookByAuthor(
        author: String @cql_where(field: "author", predicate: CONTAINS)
      ): [Book]
      # books by partition key + indexed column author
      bookByTitleAndAuthor(title: String!, author: String @cql_where(field: "author", predicate: CONTAINS)
      ): [Book]
      # books by isbn (object: BookI)
      bookIByIsbn(isbn: String): [BookI]
      # books with paging state, paging size = 10
      booksWithPaging(
        title: String!,
        pagingState: String @cql_pagingState
      ): SelectBookResult @cql_select(pageSize: 10)
      # books by partition key WHERE title is IN a list
      booksIn(title: [String] @cql_where(field: "title", predicate: IN)
      ): [Book]
      # books by author WHERE author is CONTAINED in the author array (list)
      booksContainAuthor(author: String @cql_where(field: "author", predicate: CONTAINS)
      ): [Book]
      bookGT(
        title: String
        isbn: String @cql_where(field: "isbn", predicate: GT)
      ): [Book]
      bookLT(
        title: String
        isbn: String @cql_where(field: "isbn", predicate: LT)
      ): [Book]

      # readers by partition key
      readerByName(name:String!): [Reader]
      # readers by partition key + clustering column (primary key)
      readerByNameAndUserid(name:String!, user_id:Uuid): [Reader]
      # reader by user_id (object: ReaderU)
      readerUByUserid(user_id: Uuid!): [ReaderU]
      # reader by review that CONTAINS information
      #readerCONTAINS(
      #  reviews: ReviewInput! @cql_where(field: "reviews", predicate: CONTAINS)
      #): [Reader]
      #readerGT(
      #  name: String!,
      #  user_id: Uuid! @cql_where(field: "user_id", predicate:GT)
      #): [Reader]

      #libCollByType(type: String!): [LibCollection]
      # lib collection by primary key (composite)
      libCollByTypeAndLibid(type: String!, lib_id: Int!): [LibCollection]
      # lib collection by indexed column lib_name
      #libCollByName(lib_name: String): [LibCollection]
      # lib collection by type IN and lib_id IN
      #libCollIn(
      #  type: [String!] @cql_where(field: "type", predicate: IN)
      #  lib_id: [Int!] @cql_where(field: "lib_id", predicate: IN)
      #): [LibCollection]
    }
    type Mutation {
      insertBook(book: BookInput!): Book
      insertBookI(booki: BookIInput!): BookI
      insertBookIfNotExists(book: BookInput!): InsertBookResponse
      updateBook(book: BookInput): Boolean @cql_update
      deleteBook(book: BookInput!): Boolean
      insertReader(reader: ReaderInput!): Reader
      updateReader(reader: ReaderInput!): Boolean @cql_update
      deleteReader(reader: ReaderInput!): Boolean
      insertLibCollection(libColl: LibCollectionInput!): LibCollection
      updateLibCollection(libColl: LibCollectionInput!): Boolean @cql_update
      deleteLibCollection(libColl: LibCollectionInput!): Boolean @cql_delete(ifExists: true)
    }
    """
  ) {
    version
    cqlChanges
  }
}

A defined mutation deploySchema is executed. The keyspace is specified, along with the schema, specified between triple quotes (""").

A number of additional options are used in the following manner:

Option

Default

Description

expectedVersion

N/A

Each schema is assigned a unique version number. If the current deployment is a modification, the version must be supplied.

dryRun

false

To test in a dryrun, use dryRun: true

force

false

Force a schema change

migrationStrategy

ADD_MISSING_TABLES_AND_COLUMNS

USE_EXISTING, ADD_MISSING_TABLES, ADD_MISSING_TABLES_AND_COLUMNS, DROP_AND_RECREATE_ALL, DROP_AND_RECREATE_IF_MISMATCH

Two items are returned in this example, the version that is assigned to the schema, and cqlChanges, the status of whether CQL changes occurred due to the schema deployment. Other responses are logs and query.

The migrationStrategy option needs further explanation on how deploySchema updates the underlying CQL schema, based on the options argument. The available strategies are:

ADD_MISSING_TABLES_AND_COLUMNS (default)

Create CQL tables and UDTs that don’t already exist. For those that exist, add any missing columns. Partition keys and clustering columns cannot be added after initial creation. This strategy will fail if the column already exists with a different data type.

USE_EXISTING

Don’t do anything. This is the most conservative strategy. All CQL tables and UDTs must match, otherwise the deployment is aborted.

ADD_MISSING_TABLES

Create CQL tables and UDTs that don’t already exist. Those that exist must match, otherwise the deployment is aborted.

DROP_AND_RECREATE_ALL

Drop and recreate all CQL tables and UDTs. This is a destructive operation: any existing data will be lost.

DROP_AND_RECREATE_IF_MISMATCH

Drop and recreate only the CQL tables and UDTs that don’t match. This is a destructive operation: any existing data in the recreated tables will be lost. Tables that are not recreated will retain their data.

Deploy schema file using cURL

Schema can also be deployed to a keyspace using a schema file upload. This mutation must be executed with a multipart request (note that your operations part must declare MIME type application/json).

In this case, deploySchemaFile is executed. This query must be executed in the command line with a cURL command:

  • graphQL command

  • Result

curl http://localhost:8080/graphql-admin \
  -H "X-Cassandra-Token: $AUTH_TOKEN" \
  -F operations='
{
  "query": "mutation($file: Upload!) { deploySchemaFile( keyspace: \"library\" schemaFile: $file force: true) { version } }",
  "variables": { "file": null }
};type=application/json' \
  -F map='{ "filePart": ["variables.file"] }' \
  -F filePart=@/tmp/schema.graphql
{"data":{"deploySchemaFile":{"version":"5c6c4190-a23f-11eb-8fde-b341b9f82ca9"}}}

The operations part contains the GraphQL payload. It consists of a parameterized mutation, which takes a single $file argument (note that we leave it as null in the payload, because it’s going to be set another way). The filePart argument contains the file. The map argument specifies that the file specified by filePart will map to the variables.file setting. In this example, the schema file supplied is located in /tmp/schema.graphql.

In order to deploy a schema file again, you’ll need to supply the expectedVersion for the schema to be replaced. Check the keyspace schema to get the current version.

  • graphQL command

  • Result

curl http://localhost:8080/graphql-admin \
  -H "X-Cassandra-Token: $AUTH_TOKEN" \
  -F operations='
{
  "query": "mutation($file: Upload!) { deploySchemaFile( keyspace: \"library\" expectedVersion: \"cb0b25f0-ef36-11eb-9cf6-afef380162ee\" schemaFile: $file) { version } }",
  "variables": { "file": null }
};type=application/json' \
  -F map='{ "filePart": ["variables.file"] }' \
  -F filePart=@/tmp/schema.graphql
{"data":{"deploySchemaFile":{"version":"26a6f680-a7a9-11eb-a22f-7bb5f4c20029"}}}

Modify schema

To modify the current schema, simply deploy again, supplying the expectedVersion as the current schema’s version if you wish to overwrite the definitions. Otherwise, a new schema with a new version id will be created. Either the GraphQL Playground or the cURL command can be used to update the schema.

Check the keyspace schema

To check if the schema exists, execute a GraphQL check in http://localhost:8080/graphql-admin:

For all versions of a keyspace schema:

  • graphQL command

  • Result

# Works in http://localhost:8080/graphql-admin
{
  schemas(keyspace: "library") {
    version
    deployDate
    contents
  }
}
{
  "data": {
    "schemas": [
      {
        "version": "4adc2e30-9e53-11eb-8fde-b341b9f82ca9",
        "deployDate": "2021-04-16T01:30:14.035Z[Etc/UTC]",
        "contents": "type Address @cql_entity(target: UDT) @cql_input {\n      street: String\n      city: String\n      state: String\n      zipCode: String @cql_column(name: \"zip_code\")\n    }\n    type Review @cql_entity(target: UDT) @cql_input {\n      bookTitle: String @cql_column(name: \"book_title\")\n      comment: String\n      rating: Int\n      reviewDate: Date @cql_column(name: \"review_date\")\n   }\n    type Book @key @cql_entity(name: \"book\") @cql_input {\n      title: String! @cql_column(partitionKey: true)\n      author: String @cql_column(clusteringOrder: ASC)\n    }\n     type Reader @key @cql_entity(name: \"reader\") @cql_input {\n      name: String! @cql_column(partitionKey: true)\n      user_id: Uuid! @cql_column(clusteringOrder: ASC)\n      birthdate: Date\n      email: [String] @cql_column(typeHint: \"set<varchar>\")\n      reviews: [Review]\n      address: [Address]\n    }\n    type Query {\n      book(title:String!, author:String): Book\n      reader(name:String!, user_id:Uuid): Reader\n    }\n    type Mutation {\n      insertBook(book:BookInput!): Book\n      deleteUser(book:BookInput!): Boolean\n      insertReader(reader:ReaderInput!):Reader\n      deleteReader(reader:ReaderInput!):Boolean\n    }"
      },
      {
        "version": "ba500230-9e50-11eb-8fde-b341b9f82ca9",
        "deployDate": "2021-04-16T01:11:52.531Z[Etc/UTC]",
        "contents": "type Address @cql_entity(target: UDT) @cql_input {\n      street: String\n      city: String\n      state: String\n      zipCode: String @cql_column(name: \"zip_code\")\n    }\n    type Review @cql_entity(target: UDT) @cql_input {\n      bookTitle: String @cql_column(name: \"book_title\")\n      comment: String\n      rating: Int\n      reviewDate: Date @cql_column(name: \"review_date\")\n   }\n    type Book @key @cql_entity(name: \"book\") @cql_input {\n      title: String! @cql_column(partitionKey: true)\n      author: String @cql_column(clusteringOrder: ASC)\n    }\n     type Reader @cql_entity(name: \"reader\") @cql_input {\n      name: String! @cql_column(partitionKey: true)\n      user_id: Uuid! @cql_column(clusteringOrder: ASC)\n      birthdate: Date\n      email: [String] @cql_column(typeHint: \"set<varchar>\")\n      reviews: [Review]\n      address: [Address]\n    }\n    type Query {\n      book(title:String!, author:String): Book\n      reader(name:String!, user_id:Uuid): Reader\n    }\n    type Mutation {\n      insertBook(book:BookInput!): Book\n      deleteUser(book:BookInput!): Boolean\n      insertReader(reader:ReaderInput!):Reader\n      deleteReader(reader:ReaderInput!):Boolean\n    }"
      },
      {
        "version": "afc1fb70-9e50-11eb-8fde-b341b9f82ca9",
        "deployDate": "2021-04-16T01:11:34.823Z[Etc/UTC]",
        "contents": "type Address @cql_entity(target: UDT) @cql_input {\n      street: String\n      city: String\n      state: String\n      zipCode: String @cql_column(name: \"zip_code\")\n    }\n    type Review @cql_entity(target: UDT) @cql_input {\n      bookTitle: String @cql_column(name: \"book_title\")\n      comment: String\n      rating: Int\n      reviewDate: Date @cql_column(name: \"review_date\")\n   }\n    type Book @key @cql_entity(name: \"book\") @cql_input {\n      title: String! @cql_column(partitionKey: true)\n      author: String @cql_column(clusteringOrder: ASC)\n    }\n     type Reader @cql_entity(name: \"reader\") @cql_input {\n      name: String! @cql_column(partitionKey: true)\n      user_id: Uuid! @cql_column(clusteringOrder: ASC)\n      birthdate: Date\n      email: [String] @cql_column(typeHint: \"set<varchar>\")\n      reviews: [Review]\n      address: [Address]\n    }\n    type Query {\n      book(title:String!, author:String): Book\n      reader(name:String!, user_id:Uuid): Reader\n    }\n    type Mutation {\n      insertBook(book:BookInput!): Book\n      deleteUser(book:BookInput!): Boolean\n      insertReader(reader:ReaderInput!):Reader\n      deleteReader(reader:ReaderInput!):Boolean\n    }"
      },
      {
        "version": "92ab6350-9e50-11eb-8fde-b341b9f82ca9",
        "deployDate": "2021-04-16T01:10:46.021Z[Etc/UTC]",
        "contents": "type Address @cql_entity(target: UDT) @cql_input {\n      street: String\n      city: String\n      state: String\n      zipCode: String @cql_column(name: \"zip_code\")\n    }\n    type Review @cql_entity(target: UDT) @cql_input {\n      bookTitle: String @cql_column(name: \"book_title\")\n      comment: String\n      rating: Int\n      reviewDate: Date @cql_column(name: \"review_date\")\n   }\n    type Book @key @cql_entity(name: \"book\") @cql_input {\n      title: String! @cql_column(partitionKey: true)\n      author: String @cql_column(clusteringOrder: ASC)\n    }\n     type Reader @cql_entity(name: \"reader\") @cql_input {\n      name: String! @cql_column(partitionKey: true)\n      user_id: Uuid! @cql_column(clusteringOrder: ASC)\n      birthdate: Date\n      email: [String] @cql_column(typeHint: \"set<varchar>\")\n      reviews: [Review]\n      address: [Address]\n    }\n    type Query {\n      book(title:String!, author:String): Book\n    }\n    type Mutation {\n      insertBook(book:BookInput!): Book\n      deleteUser(book:BookInput!): Boolean\n      insertReader(reader:ReaderInput!):Reader\n    }"
      },
      {
        "version": "72b6d890-9e50-11eb-8fde-b341b9f82ca9",
        "deployDate": "2021-04-16T01:09:52.409Z[Etc/UTC]",
        "contents": "type Address @cql_entity(target: UDT) @cql_input {\n      street: String\n      city: String\n      state: String\n      zipCode: String @cql_column(name: \"zip_code\")\n    }\n    type Review @cql_entity(target: UDT) @cql_input {\n      bookTitle: String @cql_column(name: \"book_title\")\n      comment: String\n      rating: Int\n      reviewDate: Date @cql_column(name: \"review_date\")\n   }\n    type Book @key @cql_entity(name: \"book\") @cql_input {\n      title: String! @cql_column(partitionKey: true)\n      author: String @cql_column(clusteringOrder: ASC)\n    }\n     type Reader @cql_entity(name: \"reader\") @cql_input {\n      name: String! @cql_column(partitionKey: true)\n      user_id: Uuid! @cql_column(clusteringOrder: ASC)\n      birthdate: Date\n      email: [String] @cql_column(typeHint: \"set<varchar>\")\n      reviews: [Review]\n      address: [Address]\n    }\n    type Query {\n      book(title:String!, author:String): Book\n    }\n    type Mutation {\n      insertBook(book:BookInput!): Book\n      deleteUser(book:BookInput!): Boolean\n    }"
      },
      {
        "version": "5d6f0020-9e50-11eb-8fde-b341b9f82ca9",
        "deployDate": "2021-04-16T01:09:16.706Z[Etc/UTC]",
        "contents": "type Address @cql_entity(target: UDT) @cql_input {\n      street: String\n      city: String\n      state: String\n      zipCode: String @cql_column(name: \"zip_code\")\n    }\n    type Review @cql_entity(target: UDT) @cql_input {\n      bookTitle: String @cql_column(name: \"book_title\")\n      comment: String\n      rating: Int\n      reviewDate: Date @cql_column(name: \"review_date\")\n   }\n    type Book @key @cql_entity(name: \"book\") @cql_input {\n      title: String! @cql_column(partitionKey: true)\n      author: String @cql_column(clusteringOrder: ASC)\n    }\n    type Query {\n      book(title:String!, author:String): Book\n    }\n    type Mutation {\n      insertBook(book:BookInput!): Book\n      deleteUser(book:BookInput!): Boolean\n    }\n   type Reader @cql_entity(name: \"reader\") @cql_input {\n      name: String! @cql_column(partitionKey: true)\n      user_id: Uuid! @cql_column(clusteringOrder: ASC)\n      birthdate: Date\n      email: [String] @cql_column(typeHint: \"set<varchar>\")\n      reviews: [Review]\n      address: [Address]\n  }"
      },
      {
        "version": "3f607370-9e50-11eb-8fde-b341b9f82ca9",
        "deployDate": "2021-04-16T01:08:26.279Z[Etc/UTC]",
        "contents": "  type Book @key @cql_entity(name: \"book\") @cql_input {\n    title: String! @cql_column(partitionKey: true)\n    author: String @cql_column(clusteringOrder: ASC)\n  }\n  type Query {\n    book(title:String!, author:String): Book\n  }\n  type Mutation {\n    insertBook(book:BookInput!): Book\n    deleteUser(book:BookInput!): Boolean\n  }\n  type Address @cql_entity(target: UDT) @cql_input {\n    street: String\n    city: String\n    state: String\n    zipCode: String @cql_column(name: \"zip_code\")\n  }\n  type Review @cql_entity(target: UDT) @cql_input {\n    bookTitle: String @cql_column(name: \"book_title\")\n    comment: String\n    rating: Int\n    reviewDate: Date @cql_column(name: \"review_date\")\n }\n type Reader @cql_entity(name: \"reader\") @cql_input {\n    name: String! @cql_column(partitionKey: true)\n    user_id: Uuid! @cql_column(clusteringOrder: ASC)\n    birthdate: Date\n    email: [String] @cql_column(typeHint: \"set<varchar>\")\n    reviews: [Review]\n    address: [Address]\n}"
      },
      {
        "version": "01a00a50-9e50-11eb-8fde-b341b9f82ca9",
        "deployDate": "2021-04-16T01:06:42.677Z[Etc/UTC]",
        "contents": "  type Book @key @cql_entity(name: \"book\") @cql_input {\n    title: String! @cql_column(partitionKey: true)\n    author: String @cql_column(clusteringOrder: ASC)\n  }\n  type Query {\n    book(title:String!, author:String): Book\n  }\n  type Mutation {\n    insertBook(book:BookInput!): Book\n    deleteUser(book:BookInput!): Boolean\n  }\n  type Address @cql_entity(target: UDT) @cql_input {\n    street: String\n    city: String\n    state: String\n    zipCode: String @cql_column(name: \"zip_code\")\n  }\n  type Review @cql_entity(target: UDT) @cql_input {\n    bookTitle: String @cql_column(name: \"book_title\")\n    comment: String\n    rating: Int\n    reviewDate: Date @cql_column(name: \"review_date\")\n }\n type Reader @cql_entity(name: \"reader\") @cql_input {\n    name: String! @cql_column(partitionKey: true)\n    user_id: Uuid! @cql_column(clusteringOrder: ASC)\n    birthdate: Date\n    email: [String] @cql_column(typeHint: \"set<varchar>\")\n    reviews: [Review]\n    address: [Address]\n}"
      },
      {
        "version": "a19da400-9e4f-11eb-8fde-b341b9f82ca9",
        "deployDate": "2021-04-16T01:04:01.600Z[Etc/UTC]",
        "contents": "  type Book @key @cql_entity(name: \"book\") @cql_input {\n    title: String! @cql_column(partitionKey: true)\n    author: String @cql_column(clusteringOrder: ASC)\n  }\n  type Query {\n    book(title:String!, author:String): Book\n  }\n  type Mutation {\n    insertBook(book:BookInput!): Book\n    deleteUser(book:BookInput!): Boolean\n  }\n  type Address @cql_entity(target: UDT) @cql_input {\n    street: String\n    city: String\n    state: String\n    zipCode: String @cql_column(name: \"zip_code\")\n  }\n  type Review @cql_entity(target: UDT) @cql_input {\n    bookTitle: String @cql_column(name: \"book_title\")\n    comment: String\n    rating: Int\n    reviewDate: Date @cql_column(name: \"review_date\")\n }\n type Reader @cql_entity(name: \"reader\") @cql_input {\n    name: String! @cql_column(partitionKey: true)\n    user_id: Uuid! @cql_column(clusteringOrder: ASC)\n    birthdate: Date\n    email: [String] @cql_column(typeHint: \"set<varchar>\")\n    reviews: [Review]\n    address: [Address]\n}"
      },
      {
        "version": "290b6630-9e4f-11eb-8fde-b341b9f82ca9",
        "deployDate": "2021-04-16T01:00:39.315Z[Etc/UTC]",
        "contents": " type Book @key @cql_entity(name: \"book\") @cql_input {\n   title: String! @cql_column(partitionKey: true)\n   author: String @cql_column(clusteringOrder: ASC)\n }\n type Query {\n   book(title:String!, author:String): Book\n }\n type Mutation {\n   insertBook(book:BookInput!): Book\n   deleteUser(book:BookInput!): Boolean\n }\n type Address @cql_entity(target: UDT) {\n   street: String\n   city: String\n   state: String\n   zipCode: String @cql_column(name: \"zip_code\")\n }\n type Review @cql_entity(target: UDT) @cql_input {\n   bookTitle: String @cql_column(name: \"book_title\")\n   comment: String\n   rating: Int\n   reviewDate: Date @cql_column(name: \"review_date\")\n}"
      }
    ]
  }
}

Note that the result lists the current schema version for every loaded schema.

For a particular version of a keyspace schema:

  • graphQL command

  • Result

{
  schema(keyspace: "library", version: "01a00a50-9e50-11eb-8fde-b341b9f82ca9") {
    version
    deployDate
    contents
  }
}
{
  "data": {
    "schema": {
      "version": "01a00a50-9e50-11eb-8fde-b341b9f82ca9",
      "deployDate": "2021-04-16T01:06:42.677Z[Etc/UTC]",
      "contents": "  type Book @key @cql_entity(name: \"book\") @cql_input {\n    title: String! @cql_column(partitionKey: true)\n    author: String @cql_column(clusteringOrder: ASC)\n  }\n  type Query {\n    book(title:String!, author:String): Book\n  }\n  type Mutation {\n    insertBook(book:BookInput!): Book\n    deleteUser(book:BookInput!): Boolean\n  }\n  type Address @cql_entity(target: UDT) @cql_input {\n    street: String\n    city: String\n    state: String\n    zipCode: String @cql_column(name: \"zip_code\")\n  }\n  type Review @cql_entity(target: UDT) @cql_input {\n    bookTitle: String @cql_column(name: \"book_title\")\n    comment: String\n    rating: Int\n    reviewDate: Date @cql_column(name: \"review_date\")\n }\n type Reader @cql_entity(name: \"reader\") @cql_input {\n    name: String! @cql_column(partitionKey: true)\n    user_id: Uuid! @cql_column(clusteringOrder: ASC)\n    birthdate: Date\n    email: [String] @cql_column(typeHint: \"set<varchar>\")\n    reviews: [Review]\n    address: [Address]\n}"
    }
  }
}

Undeploy schema

To undeploy an existing schema, use the following mutation.

  • graphQL command

  • Result

mutation dropSchema {
  undeploySchema( 
    "keyspace": "library" 
    "expectedVersion": "bd94fb30-e4f5-11eb-9cf6-afef380162ee")
}
{
  "data": {
    "undeploySchema": true
  }
}

The keyspace name and schema version must be supplied. One option is available, force, to force an erasure of the schema.

Insert data

If you have created schema for insertion, now you are ready to write data into the database.

First, let’s navigate to your new keyspace library inside the playground. Change the location to http://localhost:8080/graphql/library and add a couple of books:

  • graphQL command

  • Result

mutation insert2books {
  nativeson: insertBook(book: { title: "Native Son", isbn: "978-0061148507", author: ["Richard Wright"] }) {
    title
  }
  mobydick: insertBook(book: { title: "Moby Dick", isbn: "978-1503280786", author: ["Herman Melville"]}) {
    title
  }
}
{
  "data": {
    "insertBook": {
      "title": "Native Son"
    }
  }
}

{
  "data": {
    "insertBook": {
      "title": "Moby Dick"
    }
  }
}

The insertion is straightforward. The title and author are specified, and the title is returned in response to a successful insertion. Only the required fields must be specified, and any fields can be returned in the response. This same operation can update stored data, as insertions are upserts in all cases.

Conditional insertion

  • graphQL command

  • Result

type Book @key @cql_entity(name: "book") @cql_input {
 title: String! @cql_column(partitionKey: true)
 isbn: String @cql_column(clusteringOrder: ASC)
 author: [String] @cql_index(name: "author_idx", target: VALUES)
}

type InsertBookResponse @cql_payload {
  applied: Boolean!
  book: Book!
}
type Mutation {
  insertBookIfNotExists(book: BookInput!): InsertBookResponse
}

Insert arrays and UDTs

Inserting arrays and UDTs requires a few extra embellishments:

  • graphQL command

  • Result

mutation insert2Readers {
  janedoe: insertReader(
    reader: {
      name: "Jane Doe"
      user_id: "f02e2894-db48-4347-8360-34f28f958590"
      reviews: [
        {
          bookTitle: "Moby Dick"
          comment: "It's about a whale."
          rating: 3
          reviewDate: "2021-04-01"
        }
        {
          bookTitle: "Native Son"
          comment: "An awesome work of art."
          rating: 5
          reviewDate: "2021-01-01"
        }
      ]
    }
  ) {
    name
    user_id
    birthdate
    email
    address {
      street
      city
      state
      zipCode
    }
  }
  herman: insertReader(
    reader: {
      name: "Herman Melville"
      user_id: "e0ec47e1-2b46-41ad-961c-70e6de629810"
      birthdate: "1900-01-01"
      email: ["hermy@mobydick.org", "herman.melville@gmail.com"]
      address: {
        street: "100 Main St"
        city: "Boston"
        state: "MA"
        zipCode: "50050"
      }
    }
  ) {
    name
    user_id
    birthdate
    email
    address {
      street
      city
      state
      zipCode
    }
  }
}
{
  "data": {
    "insertReader": {
      "name": "Jane Doe",
      "user_id": "f02e2894-db48-4347-8360-34f28f958590"
    }
  }
}
{
  "data": {
    "insertReader": {
      "name": "Herman Melville",
      "birthdate": "1900-01-01",
      "email": [
        "hermy@mobydick.org",
        "herman.melville@gmail.com"
      ],
      "address": [
        {
          "street": "100 Main St",
          "city": "Boston",
          "state": "MA",
          "zipCode": "50050"
        }
      ]
    }
  }
}

Note the use of square brackets around arrays of objects, with commas separating array items.

Retrieve data

Let’s check that the data was inserted. Use the query book with the primary key to find a book based on its title. Use http://localhost:8080/graphql/library to execute the query in GraphQL playground:

  • graphQL command

  • Result

query fetchBook {
  book(title: "Native Son") {
    title
    author
  }
}
{
  "data": {
    "book": [
      {
        "title": "Native Son",
        "author": "Richard Wright"
      }
    ]
  }
}

It is also possible to find books with a partial primary key. If more than one book has the same title (partition key), for instance, but different isbn codes (clustering key), then a listing of all books that match can be retrieved:

  • graphQL command

  • Result

query fetchSameBook {
  bookByTitle(title: "Groundswell") {
    title
    isbn
    author
  }
}
{
  "data": {
    "bookByTitle": [
      {
        "title": "Groundswell",
        "isbn": "978-1422125007",
        "author": [
          "Charlene Li"
        ]
      },
      {
        "title": "Groundswell",
        "isbn": "978-1439183595",
        "author": [
          "Katie Lee"
        ]
      }
    ]
  }
}}

In both queries, the title, isbn, and author are specified as return results.

To display the contents of a UDT, notice the inclusion of addresses and its embedded fields in the values designated as return values in this query to retrieve the readers:

  • graphQL command

  • Result

query fetchJane {
  readerByNameAndUserid(
      name: "Jane Doe",
      user_id: "f02e2894-db48-4347-8360-34f28f958590"
    ) {
    name
    user_id
    birthdate
    email
    address {
      street
      city
      state
      zipCode
    }
    reviews {
      bookTitle
      comment
      rating
      reviewDate
    }
  }
}
query fetchHerman {
  readerByNameAndUserid(
    name: "Herman Melville"
    user_id: "e0ec47e1-2b46-41ad-961c-70e6de629810"
  ) {
    name
    user_id
    birthdate
    email
    address {
      street
      city
      state
      zipCode
    }
  }
}
{
  "data": {
    "reader": [
      {
        "name": "Jane Doe",
        "user_id": "f02e2894-db48-4347-8360-34f28f958590",
        "birthdate": null,
        "email": [],
        "address": [],
        "reviews": [
          {
            "bookTitle": "Moby Dick",
            "comment": "It's about a whale.",
            "rating": 3,
            "reviewDate": "2021-04-01"
          },
          {
            "bookTitle": "Native Son",
            "comment": "An awesome work of art.",
            "rating": 5,
            "reviewDate": "2021-01-01"
          }
        ]
      }
    ]
  }
}

{
  "data": {
    "reader": [
      {
        "name": "Herman Melville",
        "user_id": "e0ec47e1-2b46-41ad-961c-70e6de629810",
        "birthdate": "1900-01-01",
        "email": [
          "herman.melville@gmail.com",
          "hermy@mobydick.org"
        ],
        "address": [
          {
            "street": "100 Main St",
            "city": "Boston",
            "state": "MA",
            "zipCode": "50050"
          }
        ]
      }
    ]
  }
}

In this example, the primary key consists of both name and user_id, to distinguish readers who might have the same name.

Stargate only allows combinations of fields that have: * at most one indexed field in the query * if no indexed field is present, then all partition key fields must be present ** it’s possible to omit some or all of the clustering fields (in which case the query may return multiple rows

Filter options for reads

It’s possible to customize the CQL condition of each parameter with @cql_where with the following arguments:

  • field: the GraphQL field name to which the condition applies

  • predicate: the conditional predicate to use

The filters available are:

Predicate

GraphQL fields that can have condition applied

EQ (equal)

partition key, clustering key, regular indexed field

IN (within)

partition key, clustering key, regular indexed field

GT (greater than)

clustering key

GTE (greater than or equal to)

clustering key

LT (less than)

clustering key

LTE (less than or equal to)

clustering key

CONTAINS

regular indexed field that is a list and has an index target of VALUES

IN example, that finds the books that are listed in the supplied array:

  • graphQL command

  • Result

type Query {
  booksIn( 
    title: [String!] @cql_where(field: "title", predicate: IN)
  ): [Book]
}

query fetchBooksIn {
  booksIn(title:["Native Son", "Moby Dick"]) {
      title 
      author
  }
}
{
  "data": {
    "booksIn": [
      {
        "title": "Moby Dick",
        "author": "Herman Melville"
      },
      {
        "title": "Native Son",
        "author": "Richard Wright"
      }
    ]
  }
}

IN example with 2 partition keys, demonstrating that values for each can be supplied:

  • graphQL command

  • Result

type Query {
  libcoll(type: String!, lib_id: Int!): [LibCollection]
  libCollIn( 
    type: [String!] @cql_where(field: "type", predicate: IN)
    lib_id: [Int!] @cql_where(field: "lib_id", predicate: IN)
  ): [LibCollection]
}

query fetchLibCollIn {
  libCollIn(type:[ "photo", "book" ], lib_id: [ 12345, 12543 ]) {
      type 
      lib_id
      lib_name
  }
}
{
  "data": {
    "libCollIn": [
      {
        "type": "book",
        "lib_id": 12345,
        "lib_name": "CSRM"
      },
      {
        "type": "photo",
        "lib_id": 12345,
        "lib_name": "CSRM"
      },
      {
        "type": "photo",
        "lib_id": 12543,
        "lib_name": "West Sacramento Historical Society"
      }
    ]
  }
}

GT example, that looks for equality on the partition key, and then a range of possible values for the clustering field:

  • graphQL command

  • Result

type Query {
  readerGT( 
    name: [String!] 
    user_id: [Uuid!]
    @cql_where(field: "user_id", predicate: GT)
  ): [Reader]
}

query fetchReadersGT {
  readerGT( name:"Herman Melville",  user_id: "e0ec47e1-2b46-41ad-961c-70e6de629800" ) {
    name
    user_id
    birthdate
  }
}
{
  "data": {
    "readerGT": [
      {
        "name": "Herman Melville",
        "user_id": "e0ec47e1-2b46-41ad-961c-70e6de629810",
        "birthdate": "1900-01-01"
      }
    ]
  }
}

GT example #2, that looks for a book with a title and an isbn code that is greater than the value supplied:

  • graphQL command

  • Result

type Query {
bookGT(
  title: String
  isbn: String @cql_where(field: "isbn", predicate: GT)
): [Book]
}

# retrieves only one book, by Katie Lee
query fetchIsbnGT {
  bookGT(title: "Groundswell", isbn: "978-1422125007") {
    title
    isbn
    author
  }
}
{
  "data": {
    "bookGT": [
      {
        "title": "Groundswell",
        "isbn": "978-1439183595",
        "author": [
          "Katie Lee"
        ]
      }
    ]
  }
}

LT example, that looks for a book with the same title and an isbn code that is less than the value supplied:

  • graphQL command

  • Result

type Query {
bookLT(
  title: String
  isbn: String @cql_where(field: "isbn", predicate: LT)
): [Book]
}

# retrieves only one book, by Charlene Li
query fetchIsbnLT {
  bookLT(title: "Groundswell", isbn: "978-1422125008") {
    title
    isbn
    author
  }
}
{
  "data": {
    "bookLT": [
      {
        "title": "Groundswell",
        "isbn": "978-1422125007",
        "author": [
          "Charlene Li"
        ]
      }
    ]
  }
}

CONTAINS example that shows how to retrieve a reader that submitted a supplied review:

  • graphQL command

  • Result

type Query {
  readerCONTAINS(
    reviews: ReviewInput! @cql_where(field: "reviews", predicate: CONTAINS)
  ): [Reader]
}

query fetchReadersCONTAINS {
  readerCONTAINS( reviews: {
          bookTitle: "Moby Dick"
          comment: "It's about a whale."
          rating: 3
          reviewDate: "2021-04-01"
        } ) {
    name
    user_id
    birthdate
  }
}
{
  "data": {
    "readerCONTAINS": [
      {
        "name": "Jane Doe",
        "user_id": "f02e2894-db48-4347-8360-34f28f958590",
        "birthdate": null
      }
    ]
  }
}

Update data

A mutation can be turned into a update operation by starting the mutation name with update. A mutation will also be an update operation if it is annotated with @cql_update. The mutation must take a single argument that is an input type for a mapped entity. If successful, the mutation will return a Boolean value. In order to execute the mutation, all primary key fields and at least one non-primary key field must be input.

For example, the following mutation and associated query will update a single row:

  • graphQL command

  • Result of command

  • New record

type Mutation {
  updateBook(book: BookInput): Boolean @cql_update
}

mutation updatePap {
  updateBook(book: {
    title: "Pride and Prejudice",
    author: ["Jane Austen", "random other author"],
    isbn: "978-0141439518" })
}
{
  "data": {
    "updateBook": true
  }
}
{
  "data": {
    "bookByTitle": [
      {
        "title": "Pride and Prejudice",
        "isbn": "978-0141439518",
        "author": [
          "Jane Austen",
          "random other author"
        ]
      }
    ]
  }
}

Updates are upserts. If the row doesn’t exist, it will be created. If it does exist, it will be updated with the new row data.

Delete data

A mutation can be turned into a delete operation by starting the mutation name with delete or remove. A mutation will also be a delete operation if it is annotated with @cql_delete. The mutation must take a single argument that is an input type for a mapped entity, or individual primary key arguments like retrieving operations. If successful, the mutation will return a Boolean value.

At runtime, all partition key fields must be present, and a prefix of the clustering columns can be present (if using a single object argument, other fields will be ignored). The delete operation targets a single row if it operates on a full primary key, or multiple rows otherwise.

Let’s add another book "Pride and Prejudice" with an insertBook(), so that you can delete the book using deleteBook() to illustrate deleting data:

  • insert book

  • delete book

  • delete result

mutation insertAnotherBook {
 insertBook(book: {
   title: "Pride and Prejudice",
   isbn: "", author: "Jane Austen" }
  ) {
      title
      isbn
      author
  }
}
mutation deletepap {
  deleteBook(book: { title:"Pride and Prejudice"})
}
{
  "data": {
    "deleteBook": true
  }
}

To check for the existence of a record before deleting, use either method discussed above. This example shows the use of the directive @cql_delete( ifExists: true):

  • mutation

  • insert library collection

  • delete book with ifExists

  • delete result

deleteLibCollection(libColl: LibCollectionInput!): Boolean @cql_delete(ifExists: true)
mutation insertCSRMPhoto {
  insertLibColl(libColl: { type: "photo", lib_id: 12345, lib_name: "CSRM" }) {
    type
    lib_id
    lib_name
  }
}

mutation insertCSRMBook {
  insertLibColl(libColl: { type: "book", lib_id: 12345, lib_name: "CSRM" }) {
    type
    lib_id
    lib_name
  }
}
mutation insertWSacPhoto {
  insertLibColl(libColl: { type: "photo", lib_id: 12543, lib_name: "West Sacramento Historical Society" }) {
    type
    lib_id
    lib_name
  }
}

mutation insertDavisBook {
  insertLibColl(libColl: { type: "book", lib_id: 66666, lib_name: "Davis Historical Museum" }) {
    type
    lib_id
    lib_name
  }
}
# Note that the lib_id is INCORRECT,
# SO THIS delete operation will fail
mutation deleteDHM {
  deleteLibCollection(libColl: {
    type:"book",
    lib_id: 66665,
    lib_name: "Davis Historical Museum"
  })
}
  "data": {
    "deleteLibCollection": false
  }
}

Directives

Name

Description

Arguments

@cql_column

Set type field CQL values

name: String

partitionKey: true/false

clusteringOrder: ASC/DESC

typeHint: String

@cql_delete

Conditional delete clause, with possible consistency level, serial consistency level, or targetEntity

if Exists: true/false

consistencyLevel: MutationConsistency = ALL/LOCAL_ONE/LOCAL_QUORUM

serialConsistency: SerialConsistency = LOCAL_SERIAL/SERIAL

targetEntity: String

@cql_entity

Set a type field data type

name: String

target (UDT)

@cql_increment

Set a type field that will increment on UPDATE

field to increment (counter, set, and list only): String

The default is false, meaning that the value will be appended

prepend: Boolean = false

@cql_if

Set a type field that the predicate if <field> <predicate uses to check condition for DELETE and UPDATE.

field: String predicates (EQ, GT, GTE, IN, GT, LTE, and NEQ)

@cql_index

Create a type field index

name: String

class: (org.apache.cassandra.index.sasi.SASIIndex)

type: (FULL, VALUES)

options: (mode: 'CONTAINS')

target: String

@cql_input

Identify a type as an input type

name (optional): String

@cql_insert

Add a conditional clause, consistency level, serial consistency level, or time-to-live (TTL)

ifNotExists: true/false

consistencyLevel: MutationConsistency = ALL/LOCAL_ONE/LOCAL_QUORUM

serialConsistency: SerialConsistency = LOCAL_SERIAL/SERIAL

ttl: String (interpreted in seconds)

@cql_pagingState

The paging state to inject in the query

N/A

@cql_payload

Identify a type as a payload type

N/A

@cql_select

Set response arguments pageSize, limit, with possible consistencyLevel

consistencyLevel: QueryConsistency = ALL/LOCAL_ONE/LOCAL_QUORUM/LOCAL_SERIAL/SERIAL

pageSize: Int

limit: Int

@cql_update

Set an insertion to be an update, with possible conditional clause, consistency level, serial consistency level, time-to-live (TTL), or targetEntity

ifExists: true/false

consistencyLevel: MutationConsistency = ALL/LOCAL_ONE/LOCAL_QUORUM

serialConsistency: SerialConsistency = LOCAL_SERIAL/SERIAL

ttl: String (interpreted in seconds)

targetEntity: String

@cql_where

Predicates that customize the conditions of a parameter

field: String

predicate (EQ (default), GT, GTE, IN, LT, LTE, CONTAINS)

Getting directive information

To see information on all the CQL directives available in Stargate, run the following cURL command:

curl  http://localhost:8080/graphql-files/cql_directives.graphql \
  --header "X-Cassandra-Token: $AUTH_TOKEN"