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’re looking to just get started, you can try DataStax Astra DB and skip the installation steps. The base URL will be |
-
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
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-dse-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
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
-
Generate an authorization token to access the interface by following the instructions in Table-based authentication/Authorization
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.
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
, ordropTable
.
-
-
/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 |
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.
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.
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.
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
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.
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
.
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
:
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:
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 |
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:
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.
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:
# 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:
{
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.
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:
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
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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)
:
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 |
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) |